An irritating limitation of listening sockets

March 1, 2007

Traditional sockets implementations have an irritating limitation, at least for TCP sockets: it is impossible to gracefully and cleanly shut down listening (server) sockets. The problem is that kernels accept new TCP connections for listening sockets without asking your server; accept() doesn't actually accept the connection, it just gives you the next one that the kernel has already accepted on your behalf.

This means that to shut down cleanly, you need a two-stage process: first you tell the kernel to stop accepting new connections for you, and then you process all of the existing accepted connections. If you simply dump everything, clients will see abrupt disconnections of established connections, because from their perspective the connection is complete; their connect() finished, and they can even have been sending data.

Unfortunately, there's no two-stage close interface for server sockets (at least, none that I can see):

  • if you just close() the socket, you flush the pending accept() queue.
  • you can't un-listen() by setting the backlog size to 0 or -1 or the like.
  • shutdown() is either the same as close(), does nothing, or produces very peculiar results, depending on the system and the exact arguments; in no case does it cause further connect() calls from clients to be refused.
  • SO_ACCEPTCONN is a read-only socket option.

(You can do this with Unix domain sockets, by removing the socket file itself without closing your server socket fd.)

Among other consequences, this means that protocols where the client connections and immediately starts sending data are dangerous; the client had better be prepared for the entire conversation to fail. The only way to make sure that the server is really there (instead of in the process of shutting down) is to wait for it to send you something, which may be why so many Internet protocols start with greeting banners from the server.

(As a pragmatic matter the server can lower the risk by not closing things down until accept() on a non-blocking socket returns an error, but there's still a concurrency race, just a smaller one.)

Written on 01 March 2007.
« Using Unix domain sockets from Python
A story of network weirdness »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Thu Mar 1 23:36:41 2007
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.