3 ms·
This is not an ideal design, surprised it has this many upvotes. Transferring the accepted connections this way involves an extra system call to epoll/kqueue t
by halayli 11y ago
This is not an ideal design, surprised it has this many upvotes.
Transferring the accepted connections this way involves an extra system call to epoll/kqueue to add the event into the particular thread and the accepting thread can become a bottleneck under high load.
A better design would be to share the listening socket across threads and have each thread accept at its own pace, at least this avoids the additional kqueue/epoll system call needed to add the new fd into the thread's poller, but it does cause lock contention in the OS which is still less expensive than a system call. What's even better is if you're on a newer linux version, or bsd, consider using SO_REUSEPORT which allow each thread to bind/accept on the same port and avoids the lock contention issue.
Also you should consider using accept4() to set the non-blocking flag during accept instead of the additional system call to set it to non-blocking.
- geocar 11y agoOne of the benefits of this design overlooked is that you can have several dedicated accepting-threads. The benefit is magnified when you have something like HTTP where the client speaks first. Another benefit of this design overlooked is that individual cores may not ever need to read memory -- the entire task can run in L1 or L2. If a single worker becomes too complicated this benefit is lost, and memory is much much slower than cache. SO_REUSEPORT is not panacea, and I do not agree that you should have threads accept and handle requests, but I do think SO_REUSEPORT is useful for making upgrades easier. I have (historically) simply blocked outgoing ICMP port-unreachable messages for ports that I know are supposed to be running since that's more portable than SO_REUSEPORT. accept4() is a good idea. I've added a note about that. Thank you.