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;
+	}
 }
 
 /*