Mercurial > illumos > illumos-gate
changeset 2948:c89bd1dbe6d0
6468901 recursive mutex_enter in pollwakeup
author | praks |
---|---|
date | Thu, 19 Oct 2006 21:21:08 -0700 |
parents | 490e4976e8af |
children | 5581081d0989 |
files | usr/src/uts/common/fs/portfs/port_vnops.c usr/src/uts/common/os/port_subr.c usr/src/uts/common/sys/port_impl.h usr/src/uts/common/sys/port_kernel.h usr/src/uts/common/syscall/poll.c |
diffstat | 5 files changed, 133 insertions(+), 18 deletions(-) [+] |
line wrap: on
line diff
--- a/usr/src/uts/common/fs/portfs/port_vnops.c Thu Oct 19 19:27:06 2006 -0700 +++ b/usr/src/uts/common/fs/portfs/port_vnops.c Thu Oct 19 21:21:08 2006 -0700 @@ -120,6 +120,14 @@ port_free_event_local(pkevp, 0); mutex_enter(&portq->portq_mutex); } + + /* + * Wait for any thread in pollwakeup(), accessing this port to + * finish. + */ + while (portq->portq_flags & PORTQ_POLLWK_PEND) { + cv_wait(&portq->portq_closecv, &portq->portq_mutex); + } mutex_exit(&portq->portq_mutex); }
--- a/usr/src/uts/common/os/port_subr.c Thu Oct 19 19:27:06 2006 -0700 +++ b/usr/src/uts/common/os/port_subr.c Thu Oct 19 21:21:08 2006 -0700 @@ -83,6 +83,52 @@ } /* + * Called from pollwakeup(PORT_SOURCE_FD source) to determine + * if the port's fd needs to be notified of poll events. If yes, + * we mark the port indicating that pollwakeup() is referring + * it so that the port_t does not disappear. pollwakeup() + * calls port_pollwkdone() after notifying. In port_pollwkdone(), + * we clear the hold on the port_t (clear PORTQ_POLLWK_PEND). + */ +int +port_pollwkup(port_t *pp) +{ + int events = 0; + port_queue_t *portq; + portq = &pp->port_queue; + mutex_enter(&portq->portq_mutex); + + /* + * Normally, we should not have a situation where PORTQ_POLLIN + * and PORTQ_POLLWK_PEND are set at the same time, but it is + * possible. So, in pollwakeup() we ensure that no new fd's get + * added to the pollhead between the time it notifies poll events + * and calls poll_wkupdone() where we clear the PORTQ_POLLWK_PEND flag. + */ + if (portq->portq_flags & PORTQ_POLLIN && + !(portq->portq_flags & PORTQ_POLLWK_PEND)) { + portq->portq_flags &= ~PORTQ_POLLIN; + portq->portq_flags |= PORTQ_POLLWK_PEND; + events = POLLIN; + } + mutex_exit(&portq->portq_mutex); + return (events); +} + +void +port_pollwkdone(port_t *pp) +{ + port_queue_t *portq; + portq = &pp->port_queue; + ASSERT(portq->portq_flags & PORTQ_POLLWK_PEND); + mutex_enter(&portq->portq_mutex); + portq->portq_flags &= ~PORTQ_POLLWK_PEND; + cv_signal(&pp->port_cv); + mutex_exit(&portq->portq_mutex); +} + + +/* * The port_send_event() function is used by all event sources to submit * trigerred events to a port. All the data required for the event management * is already stored in the port_kevent_t structure. @@ -104,7 +150,7 @@ if (pkevp->portkev_flags & PORT_KEV_DONEQ) { /* Event already in the port queue */ - if (pkevp->portkev_flags & PORT_ALLOC_CACHED) { + if (pkevp->portkev_source == PORT_SOURCE_FD) { mutex_exit(&pkevp->portkev_lock); } mutex_exit(&portq->portq_mutex); @@ -122,7 +168,7 @@ portq->portq_flags &= ~PORTQ_WAIT_EVENTS; pkevp->portkev_flags |= PORT_KEV_DONEQ; /* event enqueued */ - if (pkevp->portkev_flags & PORT_ALLOC_CACHED) { + if (pkevp->portkev_source == PORT_SOURCE_FD) { mutex_exit(&pkevp->portkev_lock); } @@ -143,7 +189,15 @@ cv_signal(&portq->portq_thread->portget_cv); } - if (portq->portq_flags & PORTQ_POLLIN) { + /* + * If some thread is polling the port's fd, then notify it. + * For PORT_SOURCE_FD source, we don't need to call pollwakeup() + * here as it will result in a recursive call(PORT_SOURCE_FD source + * is pollwakeup()). Therefore pollwakeup() itself will notify the + * ports if being polled. + */ + if (pkevp->portkev_source != PORT_SOURCE_FD && + portq->portq_flags & PORTQ_POLLIN) { portq->portq_flags &= ~PORTQ_POLLIN; mutex_exit(&portq->portq_mutex); pollwakeup(&pkevp->portkev_port->port_pollhd, POLLIN);
--- a/usr/src/uts/common/sys/port_impl.h Thu Oct 19 19:27:06 2006 -0700 +++ b/usr/src/uts/common/sys/port_impl.h Thu Oct 19 21:21:08 2006 -0700 @@ -117,6 +117,7 @@ #define PORTQ_POLLIN 0x08 /* events available in the event queue */ #define PORTQ_POLLOUT 0x10 /* space available for new events */ #define PORTQ_BLOCKED 0x20 /* port is blocked by port_getn() */ +#define PORTQ_POLLWK_PEND 0x40 /* pollwakeup is pending, blocks port close */ #define VTOEP(v) ((struct port *)(v->v_data)) #define EPTOV(ep) ((struct vnode *)(ep)->port_vnode)
--- a/usr/src/uts/common/sys/port_kernel.h Thu Oct 19 19:27:06 2006 -0700 +++ b/usr/src/uts/common/sys/port_kernel.h Thu Oct 19 21:21:08 2006 -0700 @@ -138,6 +138,8 @@ /* event management */ int port_alloc_event(int, int, int, port_kevent_t **); +int port_pollwkup(struct port *); +void port_pollwkdone(struct port *); void port_send_event(port_kevent_t *); void port_free_event(port_kevent_t *); void port_init_event(port_kevent_t *, uintptr_t, void *,
--- a/usr/src/uts/common/syscall/poll.c Thu Oct 19 19:27:06 2006 -0700 +++ b/usr/src/uts/common/syscall/poll.c Thu Oct 19 21:21:08 2006 -0700 @@ -54,7 +54,7 @@ #include <sys/bitmap.h> #include <sys/kstat.h> #include <sys/rctl.h> -#include <sys/port_kernel.h> +#include <sys/port_impl.h> #include <sys/schedctl.h> #define NPHLOCKS 64 /* Number of locks; must be power of 2 */ @@ -758,20 +758,16 @@ { polldat_t *pdp; int events = (ushort_t)events_arg; + struct plist { + port_t *pp; + int pevents; + struct plist *next; + }; + struct plist *plhead = NULL, *pltail = NULL; retry: PH_ENTER(php); - /* - * About half of all pollwakeups don't do anything, because the - * pollhead list is empty (i.e, nobody is interested in the event). - * For this common case, we can optimize out locking overhead. - */ - if (php->ph_list == NULL) { - PH_EXIT(php); - return; - } - for (pdp = php->ph_list; pdp; pdp = pdp->pd_next) { if ((pdp->pd_events & events) || (events & (POLLHUP | POLLERR))) { @@ -784,19 +780,45 @@ * Object (fd) is associated with an event port, * => send event notification to the port. */ - ASSERT(pkevp->portkev_flags - & PORT_ALLOC_CACHED); + ASSERT(pkevp->portkev_source == PORT_SOURCE_FD); mutex_enter(&pkevp->portkev_lock); if (pkevp->portkev_flags & PORT_KEV_VALID) { + int pevents; + pkevp->portkev_flags &= ~PORT_KEV_VALID; pkevp->portkev_events |= events & (pdp->pd_events | POLLHUP | POLLERR); /* * portkev_lock mutex will be released - * by port_send_event() + * by port_send_event(). + */ + port_send_event(pkevp); + + /* + * If we have some thread polling the + * port's fd, add it to the list. They + * will be notified later. + * The port_pollwkup() will flag the + * port_t so that it will not disappear + * till port_pollwkdone() is called. */ - port_send_event(pdp->pd_portev); + pevents = + port_pollwkup(pkevp->portkev_port); + if (pevents) { + struct plist *t; + t = kmem_zalloc( + sizeof (struct plist), + KM_SLEEP); + t->pp = pkevp->portkev_port; + t->pevents = pevents; + if (plhead == NULL) { + plhead = t; + } else { + pltail->next = t; + } + pltail = t; + } } else { mutex_exit(&pkevp->portkev_lock); } @@ -855,7 +877,35 @@ } } } + + + /* + * Event ports - If this php is of the port on the list, + * call port_pollwkdone() to release it. The port_pollwkdone() + * needs to be called before dropping the PH lock so that any new + * thread attempting to poll this port are blocked. There can be + * only one thread here in pollwakeup notifying this port's fd. + */ + if (plhead != NULL && &plhead->pp->port_pollhd == php) { + struct plist *t; + port_pollwkdone(plhead->pp); + t = plhead; + plhead = plhead->next; + kmem_free(t, sizeof (struct plist)); + } PH_EXIT(php); + + /* + * Event ports - Notify threads polling the event port's fd. + * This is normally done in port_send_event() where it calls + * pollwakeup() on the port. But, for PORT_SOURCE_FD source alone, + * we do it here in pollwakeup() to avoid a recursive call. + */ + if (plhead != NULL) { + php = &plhead->pp->port_pollhd; + events = plhead->pevents; + goto retry; + } } /*