diff options
| author | praks <none@none> | 2006-10-19 21:21:08 -0700 |
|---|---|---|
| committer | praks <none@none> | 2006-10-19 21:21:08 -0700 |
| commit | 11dc39dd3ec06044a7e912a5e26b6d5c55ecd731 (patch) | |
| tree | 3c61aad9156a9c36912c2706fd73b94538a1e396 /usr/src | |
| parent | 1fe696781bd9d06b5745b19d0c5161dfa09736de (diff) | |
| download | illumos-joyent-11dc39dd3ec06044a7e912a5e26b6d5c55ecd731.tar.gz | |
6468901 recursive mutex_enter in pollwakeup
Diffstat (limited to 'usr/src')
| -rw-r--r-- | usr/src/uts/common/fs/portfs/port_vnops.c | 8 | ||||
| -rw-r--r-- | usr/src/uts/common/os/port_subr.c | 60 | ||||
| -rw-r--r-- | usr/src/uts/common/sys/port_impl.h | 1 | ||||
| -rw-r--r-- | usr/src/uts/common/sys/port_kernel.h | 2 | ||||
| -rw-r--r-- | usr/src/uts/common/syscall/poll.c | 80 |
5 files changed, 133 insertions, 18 deletions
diff --git a/usr/src/uts/common/fs/portfs/port_vnops.c b/usr/src/uts/common/fs/portfs/port_vnops.c index 991f2d3751..14f3a60aa6 100644 --- a/usr/src/uts/common/fs/portfs/port_vnops.c +++ b/usr/src/uts/common/fs/portfs/port_vnops.c @@ -120,6 +120,14 @@ port_close_events(port_queue_t *portq) 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); } diff --git a/usr/src/uts/common/os/port_subr.c b/usr/src/uts/common/os/port_subr.c index c2985cd0dd..03231cd0d8 100644 --- a/usr/src/uts/common/os/port_subr.c +++ b/usr/src/uts/common/os/port_subr.c @@ -83,6 +83,52 @@ port_unblock(port_queue_t *portq) } /* + * 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 @@ port_send_event(port_kevent_t *pkevp) 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 @@ port_send_event(port_kevent_t *pkevp) 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 @@ port_send_event(port_kevent_t *pkevp) 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); diff --git a/usr/src/uts/common/sys/port_impl.h b/usr/src/uts/common/sys/port_impl.h index f569913712..8e21be1216 100644 --- a/usr/src/uts/common/sys/port_impl.h +++ b/usr/src/uts/common/sys/port_impl.h @@ -117,6 +117,7 @@ typedef struct port_queue { #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) diff --git a/usr/src/uts/common/sys/port_kernel.h b/usr/src/uts/common/sys/port_kernel.h index 177ab233c5..bfc65586fc 100644 --- a/usr/src/uts/common/sys/port_kernel.h +++ b/usr/src/uts/common/sys/port_kernel.h @@ -138,6 +138,8 @@ int port_dissociate_ksource(int, int, struct port_source *); /* 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 *, diff --git a/usr/src/uts/common/syscall/poll.c b/usr/src/uts/common/syscall/poll.c index 7299e1b998..6d3e0eb7ad 100644 --- a/usr/src/uts/common/syscall/poll.c +++ b/usr/src/uts/common/syscall/poll.c @@ -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 @@ pollwakeup(pollhead_t *php, short events_arg) { 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 @@ retry: * 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(pdp->pd_portev); + 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. + */ + 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 @@ retry: } } } + + + /* + * 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; + } } /* |
