Currently, most protocols transition to a listening socket with
something like the following:
SOCK_LOCK(so); error = solisten_proto_check(so); if (error) { SOCK_UNLOCK(so); return (error); } solisten_proto(so); SOCK_UNLOCK(so);
solisten_proto_check() fails if the socket is connected or connecting.
However, the socket lock is not used to initiate I/O, so this pattern is
racy.
The change modifies solisten_proto_check() to additionally acquire
socket buffer locks, and the calling thread holds them until
solisten_proto() or solisten_proto_abort() is called. Now that the
socket buffer locks are preserved by listen(2), this change allows
socket I/O paths to properly interlock with listen(2). For an
as-yet-unfixed example of what I'm talking about, look at
soo_aio_queue(): it blindly assumes that it can safely lock a sockbuf
and queue asynchronous I/O without checking for a listening socket.
Without these changes, it is impossible to perform that check correctly
without also acquiring the socket lock.
The change makes listen(2) rather heavyweight, but I think this is the
right tradeoff: listen(2) is called comparatively rarely, and we don't
want to penalize I/O performance for rare cases.