Repository navigation
BSD sockets: connect without blocking the scheduler - #2416
gabrielmancini wants to merge 2 commits into
Conversation
|
In order to test this, a blackhole address is not needed. A loopback listener whose accept queue is full silently
Two smaller additions would round it off: a check that a connect to a closed loopback port |
9b2c0f2 to
76bd685
Compare
|
Thanks for the detailed recipe. I added |
d9f75be to
91d826b
Compare
|
The FreeBSD jobs failed in Pushed 91d826b (amended into the test commit): The esp32 / IDF 5.2.8 failure is |
6ba34be to
5582729
Compare
|
@petermm heads-up on the overlap with #2422: both PRs add I'd suggest #2422 goes first. I'll then rebase this one on top of it, drop my |
A TCP socket was connected while still blocking ("made non-blocking
after connect, for now"), so a connect to a peer that never answers
held the scheduler inside the stack until it gave up -- minutes on lwIP,
long enough for the ESP32 task watchdog to reset the device -- and
killing the caller did not free it.
Now the socket is non-blocking before connect. On EINPROGRESS,
socket:connect/2 waits for it to become writable (nif_select_write, with
enif_select_write, declared but missing until now) and reads the outcome
with SO_ERROR (nif_connect_result), as OTP atoms (econnrefused,
etimedout, ehostunreach...). Only the calling process waits; killing it
abandons the attempt. The NIF is now socket:nif_connect/2; LWIP sockets
are unchanged (they already trap until the connect callback).
Signed-off-by: Gabriel Mancini <gabriel.mancini@gmail.com>
Address review: connect errors go through posix_errno_to_term on both the
synchronous and the asynchronous path (no more {error, closed} when a connect
fails), test_accept_nowait no longer closes with linger 0, and new tests cover
a refused connect, a connect left in progress by a full accept queue (closed
by another process, caller killed, completed after an accept), and
enif_select_write.
Signed-off-by: Gabriel Mancini <gabriel.mancini@gmail.com>
5582729 to
cd4197d
Compare
On platforms with BSD sockets (ESP32, generic_unix) a TCP socket is connected while it is still blocking (
otp_socket.c: "TCP sockets are made non-blocking after connect, for now"). A connect to a peer that never answers (an empty address on the LAN, a firewall that drops) holds the scheduler inside the stack until it gives up: minutes on lwIP (TCP_SYNMAXRTX). Nothing else runs, killing the caller does not free it, and on the ESP32 the task watchdog resets the device after 30 s. Anygen_tcp:connect/ssl:connectto such a peer (an MQTT broker behind a dropping firewall, a powered-off Modbus device) does this.This makes the socket non-blocking before
connect. OnEINPROGRESS,socket:connect/2(now an Erlang wrapper overnif_connect/2) waits for the socket to become writable with a newnif_select_write/2, then reads the outcome withSO_ERROR(nif_connect_result/1) as the atoms OTP uses (econnrefused,etimedout,ehostunreach...). Only the calling process waits; killing it abandons the attempt.enif_select_write, declared inerl_nif.hbut not implemented, is added. LWIP sockets (rp2) are unchanged: their connect already traps until the callback.Tested
{error, econnrefused}at once.test-erlangpasses;test_estdlibpasses excepttest_gen_tcp's internet connect test, which also fails with the untouchedinetbackend here (this machine cannot reachwww.github.com:80).No new automated test: a "peer that never answers" depends on the network the CI runs in. Happy to add one if there is a known blackhole address to use.