Skip to content

Socket: nif_select_write/2 - #2422

Open
petermm wants to merge 8 commits into
atomvm:release-0.7from
petermm:nif_select_write/2
Open

petermm wants to merge 8 commits into
atomvm:release-0.7from
petermm:nif_select_write/2

Conversation

@petermm

@petermm petermm commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

little bit of mess with these PRs - so lets land a single one

fixes a plethora of issues with httpd etc.

this is the (original) work from #2345 (that PR was a git history rewrite of my work) progressed to PR readiness, also relates to #2416

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later

harmon25 and others added 4 commits October 9, 2026 12:13
Signed-off-by: harmon25 <dougwright1@gmail.com>
Make socket:send/2 retry partial stream sends and wait for write readiness when the native send path reports eagain. This keeps gen_tcp callers on the OTP-like success path instead of leaking {ok, Rest} or transient backpressure.

Add write-select plumbing for BSD and lwIP, including distinct read/write select state and lwIP tcp_sent/tcp_poll wakeups. Use the same readiness wait for SSL WANT_WRITE and make distribution/EPMD send failures explicit instead of dropping data.

Signed-off-by: Peter M <petermm@gmail.com>
Signed-off-by: Peter M <petermm@gmail.com>
Blocking sends require read and write readiness to coexist on one socket.
Sharing their owner lets a writer replace a reader, misroutes close
notifications, and cancels both directions when one selector exits.

Give each direction its own owner, monitor and reference, initialized
for both opened and accepted sockets. Implement ERL_NIF_SELECT_CANCEL
so selector death cancels only its direction, disposes its pending
message, and leaves the other direction armed.

Retain socket resources while lwIP write/error events are queued and
detach callbacks on close. Notify established-socket waiters of terminal
errors through direction-specific abort messages, independently of the
connect trap.

Keep distribution output event-driven so backpressure cannot block
incoming traffic or ticks. Preserve pending frame suffixes and queue
ticks behind them to maintain byte ordering.

Send empty binaries through the native socket path so closed sockets
still fail and connected UDP sockets transmit zero-length datagrams.

Add regressions for accepted-socket close, writer death, both selector
registration orders, exact-data backpressure recovery, cancellation
cleanup, and empty UDP sends. Move the changelog entry to beta.1.

See also: atomvm#2345
Signed-off-by: Peter M <petermm@gmail.com>
Comment thread tests/test-enif.c Fixed
Comment thread tests/test-enif.c Fixed
A cancelled selection retains its event for reuse. If the operating
system later assigns the same descriptor to another resource, dispatch
can find the inactive registration first and silently lose readiness
for the new socket.

Match only registrations that are open and armed for a reported
direction. Extend the cancellation regression to verify that another
resource using the same event receives readiness and both resources
are released.

Evaluate the test heap reservations outside assert expressions so
assert macro expansion does not introduce duplicate unevaluated calls
into the allocation-budget analysis.

Signed-off-by: Peter M <petermm@gmail.com>
@petermm
petermm force-pushed the nif_select_write/2 branch 2 times, most recently from 0aeb03c to 0e71177 Compare October 13, 2026 11:53
lwIP can free a TCP PCB before task-context error handling runs.
Queuing raw PCBs or treating listeners as connections can therefore
corrupt memory. Taking the VM socket lock from a callback would also
invert the task-context lock order. A full wake queue must not turn an
accepted payload into a failed enqueue and cause its resource reference
to be released twice.

Invalidate reset PCBs in the lwIP synchronization domain and retain
socket resources for deferred work without resurrecting dying resources.
Keep notification and monitor cleanup in task context. Use stable
pending-accept slots to preserve pre-accept EOF and discard reset
children without closing the listener.

Retain critical work independently of the bounded payload queue. Leave
refused TCP buffers with lwIP, make RP2 wakeups durable, and report
payload acceptance independently of global wake hints. Share the RP2
CAS lock across translation units and protect reference updates from
interrupts in single-scheduler builds as well.

Detach callbacks according to the PCB type and guard PCB access with
the lwIP lock. Preserve bytes accepted by tcp_write when tcp_output
fails so callers cannot duplicate them on retry. Release replaced
selector monitors and inactive BSD events on close, and provide
read-only selection cancellation for the public receive wrappers.

Signed-off-by: Peter M <petermm@gmail.com>
Receive and accept error cleanup currently stops both selections on a
socket. A read-side EOF can consequently strand an independent writer
that is waiting for its peer to drain buffered data.

Cancel only the read selection in these cleanup paths. Keep the public
send contract as ok or an error; document that sendto/3 is unchanged.

Exercise the public receive path with a blocked public send in both
selector registration orders. Fill the socket until backpressure is
observed rather than relying on a fixed payload size, then verify that
half-close preserves the writer and draining delivers every queued byte
and the final marker. Keep close and writer-death coverage alongside
this regression.

The half-close case times out with the old socket wrapper and passes
with read-only cleanup. Update the changelog with the user-visible
half-close behavior.

Signed-off-by: Peter M <petermm@gmail.com>
sendto/3 currently exposes transient backpressure instead of waiting
for acceptance like the default OTP call. Retrying a partial result as
a new datagram would also change packet boundaries.

Wait for write readiness and retry the original payload and destination.
Keep empty datagrams on the native path and return partial results
without resending their tails. Back off retries for 10 ms because raw
lwIP reports UDP write readiness immediately even after ERR_MEM.

Reject oversized IPv4 UDP payloads before conversion to lwIP packet
lengths, and preserve BSD sendto errors instead of reporting them as
closed sockets. Extend the UDP tests for empty and iodata packets,
oversized rejection, and sends on closed sockets.

Signed-off-by: Peter M <petermm@gmail.com>
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.

3 participants