In C, if you don't call 'accept' new connections will:
- first: get queued in a kernel queue of SYN+ACKed connections
- second: when queue is full Linux stops sending SYN+ACK, thus forcing the client to re-send SYN.
Not calling 'accept' pushes pressure back to the clients, without sending RST, without introducing any state on the server.
> and then immediately drop new connections as they're made
That's not the same as pushing back the pressure. Dropping connections means: "go away, I'm broken". Not calling 'accept' means: "I'm busy right now, wait".
> And what does c.write being non-blocking have to do with crashing the server?
If 'write' is non-blocking _and_ always succeeds (as in Node) the node.js process may be asked to 'write' plenty of data. As it can't send it to the kernel, it'll get buffered in the node process, and eventually run out of memory. This is the case if you don't have any flow-control mechanisms: you'll accept all the requests and buffer in memory all the responses, possibly leading to a crash.
> but even if you're doing everything synchronously, you've still got to manage all that stuff.
Yes indeed! But at least it's obvious that you're spawning too many threads, or that all the threads are waiting on particular slow thing (disk for example). The state still need to be handled, but it's much easier to reason about it! (it's easier to count threads / coroutines than to count how many times a callback X was called and haven't yet called back the next callback on the path)
> Maybe someone can enlighten me?
I wanted to avoid the discussion about credit based flow control. It's possible to write flow-control aware code using callbacks, but it's generally harder. And requires a programmer to be aware of the problem.
Also, IMO flow control in callbacks style introduce spaghetti, as after the last thing ('write') succeeds you need to inform the first thing ('read') to proceed. But that's aesthetics and another discussion.