In the socket API, what does a non-blocking TCP connect or accept return when it cannot finish at once, and how does the program learn the outcome?
answer
- an error code meaning not yet
- the handshake continues in the stack
- writable means connect has settled
- then read the pending socket error
- listener readable means one is queued
basics
~10 sA non-blocking connect sends the SYN and returns EINPROGRESS; the socket turns writable once the handshake resolves, and SO_ERROR says how. A non-blocking accept with nothing queued returns EAGAIN or EWOULDBLOCK.
solid answer
~40 sBlocking calls wait: `connect` until the handshake succeeds or fails, `accept` until a completed connection is queued. On a non-blocking socket neither waits. `connect` sends the SYN and returns `-1` with `EINPROGRESS`; the handshake continues inside the stack, and the socket becomes **writable** once it resolves either way, so the program reads the pending error with `getsockopt(SO_ERROR)` — `0` means ESTABLISHED, `ECONNREFUSED` means a RST came back. `accept` on an empty queue returns `-1` with `EAGAIN` or `EWOULDBLOCK`; a listening socket is reported **readable** when a completed connection is waiting. Readiness is a hint, not a promise: a queued connection can be reset before `accept` runs, so the listener should itself be non-blocking. Reads follow the same model — `EAGAIN` means no data yet, while a return of `0` means the peer sent FIN.
go deeper
Recall that non-blocking calls return immediately with a not-yet code instead of waiting, and that the protocol on the wire is unchanged.
Explain the connect sequence — EINPROGRESS, writable, then SO_ERROR — and the difference between EAGAIN and a read returning 0.
Name the traps that bite in production: success judged from writability, missing connect deadlines, and the accept race that stalls a blocking listener behind readiness.
Decide where timeouts and failure detection belong in a service: the stack's retransmission schedule is not a latency budget, so connect deadlines must be owned explicitly.
## What non-blocking changes, and what it does not A socket in **blocking** mode makes the calling thread wait until the operation can complete. In **non-blocking** mode the call returns immediately, with a result if one is available and an error code meaning "not yet" if not. These semantics come from the POSIX socket API, not from an RFC. What does **not** change is the protocol. The same SYN, SYN-ACK and ACK cross the network, with the same retransmissions and the same states. Non-blocking mode only decides whether *your thread* waits for them. ## Non-blocking connect, step by step 1. The program calls `connect`. The stack picks an ephemeral port if needed, sends the SYN and moves the connection to SYN-SENT. 2. Because the handshake needs at least one round trip, `connect` returns `-1` with **`EINPROGRESS`**. This is not a failure; it means "started". (It can return `0` at once, for example to a local address — code must handle both.) 3. The stack carries on: it waits for the SYN-ACK, retransmits the SYN if needed, and finally either reaches ESTABLISHED or gives up. 4. When the attempt **resolves in either direction**, the socket is reported **writable**. A failure also leaves a pending error, so the socket may be reported readable as well. 5. The program reads the pending error with `getsockopt(SO_ERROR)`: `0` means connected; a value such as `ECONNREFUSED` (the peer answered with RST) or a timeout or unreachable error means the attempt failed. ```pseudocode s = socket(TCP); set_nonblocking(s) r = connect(s, 192.0.2.10, 443) if r == 0: connected(s) elif errno == EINPROGRESS: if not wait_writable(s, deadline): close(s); failed(TIMEOUT) // program's own deadline else: err = getsockopt(s, SO_ERROR) if err == 0: connected(s) else: close(s); failed(err) // e.g. ECONNREFUSED after RST else: close(s); failed(errno) ``` RFC 7413 (Appendix A.1) describes the same contract for TCP Fast Open's send-with-connect: on a non-blocking socket it "returns -1 with errno EINPROGRESS" and the caller writes again "when the socket is connected". ## Non-blocking accept A listening socket in non-blocking mode behaves like a queue you poll: - If a completed connection is waiting, `accept` returns a new socket for it immediately. - If the queue is empty, `accept` returns `-1` with **`EAGAIN`** or **`EWOULDBLOCK`** (the two names may share a value). - A listening socket is reported **readable** when at least one completed connection is queued. Readiness is only a hint. Between the readiness report and the `accept` call, a queued connection can be reset by the client and removed, or another thread can take it. A **blocking** listener would then hang in `accept` despite having been "ready", which is why listening sockets used with readiness notification should be non-blocking too. ## What readable and writable mean for a TCP socket | Report | Connected TCP socket | Listening socket | |---|---|---| | **Readable** | data buffered, or the peer's FIN arrived (read returns `0`), or an error is pending | a completed connection is waiting for `accept` | | **Writable** | space in the send buffer, or a pending `connect` resolved, or an error is pending | not meaningful | Two return values are easy to confuse on reads: - `EAGAIN` / `EWOULDBLOCK`: nothing to read **yet**; try again later. - `0`: the peer sent FIN and no more data will come. RFC 8085 (Section 5) notes this "indicates the end of the connection" for TCP. A non-blocking `send` similarly returns `EAGAIN` when the send buffer is full, which happens when the peer is not taking data as fast as you write it. ## Traps - **Treating `EINPROGRESS` as failure.** It is the normal result of a non-blocking connect. - **Judging success from writability alone.** Failure also makes the socket writable; always check `SO_ERROR`. - **Forgetting the timeout.** The stack will eventually abandon a SYN that gets no answer, but that can take far longer than a caller wants. With non-blocking connect, the deadline is the program's job: stop waiting and close the socket. - **Blocking listeners behind readiness.** The accept race above can stall a thread. - **Mixing `0` and `EAGAIN`.** One means "closed by peer", the other "no data yet". How a program waits on many sockets at once — the readiness loop itself — is a separate design topic; this model is what each individual call returns.
- Why is 'the socket became writable' not enough to conclude a non-blocking TCP connect succeeded?Because failure also resolves the pending connect, and a socket with a pending error is reported writable (and usually readable) so the program notices. A RST answering the SYN, or an unreachable-destination error, completes the attempt with an error. Reading `SO_ERROR` after writability is the portable way to tell success from failure.
- Who enforces the connect timeout when a TCP connect is non-blocking?The program. The stack keeps retransmitting the SYN on its own backoff schedule and eventually gives up, but that can take much longer than the caller's budget. With a non-blocking connect, the program waits for writability with its own deadline and closes the socket when the deadline passes, which abandons the attempt.
saying these in an interview costs you the question
- EINPROGRESS from a non-blocking connect means the connection failed.
- A socket that turns writable after connect has always connected successfully.
- Non-blocking sockets send different TCP segments than blocking sockets.
- A read returning 0 on a non-blocking socket means no data has arrived yet.
- Once a listener is reported readable, accept cannot come back empty.