diff options
author | Robert Watson <rwatson@FreeBSD.org> | 2004-06-02 04:15:39 +0000 |
---|---|---|
committer | Robert Watson <rwatson@FreeBSD.org> | 2004-06-02 04:15:39 +0000 |
commit | 2658b3bb8e5e924aad1b62f71664458ef23e5f7b (patch) | |
tree | 6cd76268318837a9c462ad6283cb7d6cabe7d0e5 /sys/kern/uipc_socket2.c | |
parent | 33e10417678956c7ba02eb8f6326b212a052f329 (diff) | |
download | src-2658b3bb8e5e924aad1b62f71664458ef23e5f7b.tar.gz src-2658b3bb8e5e924aad1b62f71664458ef23e5f7b.zip |
Integrate accept locking from rwatson_netperf, introducing a new
global mutex, accept_mtx, which serializes access to the following
fields across all sockets:
so_qlen so_incqlen so_qstate
so_comp so_incomp so_list
so_head
While providing only coarse granularity, this approach avoids lock
order issues between sockets by avoiding ownership of the fields
by a specific socket and its per-socket mutexes.
While here, rewrite soclose(), sofree(), soaccept(), and
sonewconn() to add assertions, close additional races and address
lock order concerns. In particular:
- Reorganize the optimistic concurrency behavior in accept1() to
always allocate a file descriptor with falloc() so that if we do
find a socket, we don't have to encounter the "Oh, there wasn't
a socket" race that can occur if falloc() sleeps in the current
code, which broke inbound accept() ordering, not to mention
requiring backing out socket state changes in a way that raced
with the protocol level. We may want to add a lockless read of
the queue state if polling of empty queues proves to be important
to optimize.
- In accept1(), soref() the socket while holding the accept lock
so that the socket cannot be free'd in a race with the protocol
layer. Likewise in netgraph equivilents of the accept1() code.
- In sonewconn(), loop waiting for the queue to be small enough to
insert our new socket once we've committed to inserting it, or
races can occur that cause the incomplete socket queue to
overfill. In the previously implementation, it was sufficient
to simply tested once since calling soabort() didn't release
synchronization permitting another thread to insert a socket as
we discard a previous one.
- In soclose()/sofree()/et al, it is the responsibility of the
caller to remove a socket from the incomplete connection queue
before calling soabort(), which prevents soabort() from having
to walk into the accept socket to release the socket from its
queue, and avoids races when releasing the accept mutex to enter
soabort(), permitting soabort() to avoid lock ordering issues
with the caller.
- Generally cluster accept queue related operations together
throughout these functions in order to facilitate locking.
Annotate new locking in socketvar.h.
Notes
Notes:
svn path=/head/; revision=129979
Diffstat (limited to 'sys/kern/uipc_socket2.c')
-rw-r--r-- | sys/kern/uipc_socket2.c | 66 |
1 files changed, 45 insertions, 21 deletions
diff --git a/sys/kern/uipc_socket2.c b/sys/kern/uipc_socket2.c index 36a90ed6a421..6e3d365f0615 100644 --- a/sys/kern/uipc_socket2.c +++ b/sys/kern/uipc_socket2.c @@ -113,32 +113,38 @@ void soisconnected(so) struct socket *so; { - struct socket *head = so->so_head; + struct socket *head; so->so_state &= ~(SS_ISCONNECTING|SS_ISDISCONNECTING|SS_ISCONFIRMING); so->so_state |= SS_ISCONNECTED; - if (head && (so->so_qstate & SQ_INCOMP)) { - if ((so->so_options & SO_ACCEPTFILTER) != 0) { - so->so_upcall = head->so_accf->so_accept_filter->accf_callback; + ACCEPT_LOCK(); + head = so->so_head; + if (head != NULL && (so->so_qstate & SQ_INCOMP)) { + if ((so->so_options & SO_ACCEPTFILTER) == 0) { + TAILQ_REMOVE(&head->so_incomp, so, so_list); + head->so_incqlen--; + so->so_qstate &= ~SQ_INCOMP; + TAILQ_INSERT_TAIL(&head->so_comp, so, so_list); + head->so_qlen++; + so->so_qstate |= SQ_COMP; + ACCEPT_UNLOCK(); + sorwakeup(head); + wakeup_one(&head->so_timeo); + } else { + ACCEPT_UNLOCK(); + so->so_upcall = + head->so_accf->so_accept_filter->accf_callback; so->so_upcallarg = head->so_accf->so_accept_filter_arg; so->so_rcv.sb_flags |= SB_UPCALL; so->so_options &= ~SO_ACCEPTFILTER; so->so_upcall(so, so->so_upcallarg, M_TRYWAIT); - return; } - TAILQ_REMOVE(&head->so_incomp, so, so_list); - head->so_incqlen--; - so->so_qstate &= ~SQ_INCOMP; - TAILQ_INSERT_TAIL(&head->so_comp, so, so_list); - head->so_qlen++; - so->so_qstate |= SQ_COMP; - sorwakeup(head); - wakeup_one(&head->so_timeo); - } else { - wakeup(&so->so_timeo); - sorwakeup(so); - sowwakeup(so); + return; } + ACCEPT_UNLOCK(); + wakeup(&so->so_timeo); + sorwakeup(so); + sowwakeup(so); } void @@ -182,8 +188,12 @@ sonewconn(head, connstatus) int connstatus; { register struct socket *so; + int over; - if (head->so_qlen > 3 * head->so_qlimit / 2) + ACCEPT_LOCK(); + over = (head->so_qlen > 3 * head->so_qlimit / 2); + ACCEPT_UNLOCK(); + if (over) return ((struct socket *)0); so = soalloc(M_NOWAIT); if (so == NULL) @@ -206,25 +216,39 @@ sonewconn(head, connstatus) sodealloc(so); return ((struct socket *)0); } - + ACCEPT_LOCK(); if (connstatus) { TAILQ_INSERT_TAIL(&head->so_comp, so, so_list); so->so_qstate |= SQ_COMP; head->so_qlen++; } else { - if (head->so_incqlen > head->so_qlimit) { + /* + * XXXRW: Keep removing sockets from the head until there's + * room for us to insert on the tail. In pre-locking + * revisions, this was a simple if(), but as we could be + * racing with other threads and soabort() requires dropping + * locks, we must loop waiting for the condition to be true. + */ + while (head->so_incqlen > head->so_qlimit) { struct socket *sp; sp = TAILQ_FIRST(&head->so_incomp); + TAILQ_REMOVE(&so->so_incomp, sp, so_list); + head->so_incqlen--; + sp->so_qstate &= ~SQ_INCOMP; + sp->so_head = NULL; + ACCEPT_UNLOCK(); (void) soabort(sp); + ACCEPT_LOCK(); } TAILQ_INSERT_TAIL(&head->so_incomp, so, so_list); so->so_qstate |= SQ_INCOMP; head->so_incqlen++; } + ACCEPT_UNLOCK(); if (connstatus) { + so->so_state |= connstatus; sorwakeup(head); wakeup_one(&head->so_timeo); - so->so_state |= connstatus; } return (so); } |