Skip to content

Sockets: fixes to ClientSocket_POSIX - #1783

Merged
Mysticial merged 2 commits into
mainfrom
sockets-posix-disconnect
Oct 31, 2026
Merged

Mysticial merged 2 commits into
mainfrom
sockets-posix-disconnect

Conversation

@pa-devbot

Copy link
Copy Markdown
Collaborator

Part of the socket state-machine fixes; the on_disconnect() hook landed in #1780.

What was wrong

  • Connection loss was invisible. recv()==0 (graceful close) was unhandled, and fatal recv() errors (ECONNRESET, ENOTCONN, ...) just exited the receive thread with the state stuck at CONNECTED. The UI stayed green and send() silently no-oped until the object was destroyed.
  • close() was a flag flip. It set DESTRUCTING and returned without joining the receive thread or closing the fd; the state could never get back to a reusable value.
  • Non-blocking connect was broken on Linux. The old loop retried connect() every 10 ms and treated EISCONN as "connected". On Linux, calling connect() while an attempt is in progress returns EISCONN even if the attempt is still pending or about to fail, so connecting to a dead port marked the socket CONNECTED with an empty error (verified against the kernel directly).
  • The fd was const. A socket whose connection ended cannot be reconnected, but there was no way to get a new one.

What this does

  • recv()==0 and fatal recv() errors now fire on_disconnect(error_message) and return the socket to NOT_RUNNING so connect() can be called again.
  • close() now joins the receive thread, closes the socket, and returns to NOT_RUNNING. It is safe to call multiple times and must not be called from within a listener callback (same contract as Sockets: fixes to ClientSocket_WinSocket #1774).
  • connect() always starts from a fresh socket; m_socket is std::atomic<int> since the receive thread and send() share it.
  • Non-blocking connect is now done the correct way: a single connect() call, then poll() for writability and getsockopt(SO_ERROR) for the result. Refused/unreachable/etc. report through on_connect_finished(error_message).

Testing

Full functional test against a real TCP server (loopback): connect, send, receive (including a >4 KB burst), graceful close, RST, refused connect, reconnect without close(), close() + reconnect, and 100 rapid connect/disconnect cycles - all pass. pa-build + pa-test pass on Linux.

@Mysticial
Mysticial force-pushed the sockets-posix-disconnect branch 2 times, most recently from df7d0e6 to 51484f3 Compare October 31, 2026 12:43
Ports the same state-machine fixes as the WinSocket PR to the POSIX
implementation:

- Detect connection loss: recv()==0 (graceful close) and fatal recv()
  errors (ECONNRESET, ENOTCONN, ...) now fire on_disconnect() and return
  the socket to NOT_RUNNING instead of exiting the thread silently with
  the state stuck at CONNECTED.
- Close connections properly: close() now joins the receive thread,
  closes the socket, and returns to NOT_RUNNING so connect() can be
  called again. It is safe to call multiple times.
- New sockets per connect(): a socket whose previous connection ended
  cannot be reconnected, so connect() always starts from a fresh
  socket. m_socket is now std::atomic since the receive thread and
  send() share it.
- Non-blocking connect done correctly: only one connect() call; while
  in progress, wait with poll() for the socket to become writable and
  check SO_ERROR for the result. (Calling connect() again while in
  progress returns EISCONN on Linux even when the attempt is still
  pending or will fail, which the old retry loop treated as success -
  connecting to a dead port marked the socket CONNECTED with an empty
  error.)
- Connect failures (refused, unreachable, ...) now report the error
  through on_connect_finished() and return to NOT_RUNNING.
Now that dispatch() spawns threads as needed, the receive thread no
longer needs dispatch_now_blocking(). The blocking variant can also
deadlock if connect() is called from a thread that is itself running
on a capped pool, since it blocks until a pool thread is free.
@Mysticial
Mysticial merged commit 6e2815a into main Oct 31, 2026
7 checks passed
@Mysticial
Mysticial deleted the sockets-posix-disconnect branch October 31, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants