summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
authorpraks <none@none>2006-10-19 21:21:08 -0700
committerpraks <none@none>2006-10-19 21:21:08 -0700
commit11dc39dd3ec06044a7e912a5e26b6d5c55ecd731 (patch)
tree3c61aad9156a9c36912c2706fd73b94538a1e396 /usr/src
parent1fe696781bd9d06b5745b19d0c5161dfa09736de (diff)
downloadillumos-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.c8
-rw-r--r--usr/src/uts/common/os/port_subr.c60
-rw-r--r--usr/src/uts/common/sys/port_impl.h1
-rw-r--r--usr/src/uts/common/sys/port_kernel.h2
-rw-r--r--usr/src/uts/common/syscall/poll.c80
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;
+ }
}
/*