Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed several underallocation issues that could trigger data corruption on `binary:replace`, `zlib:compress` and bsd socket recv code.
- Fixed a bug where `catch` would raise on regular atom results
- Fixed ESP32 socket driver holding the global socket-list lock across blocking TCP connects, leaking the port on connect failure, losing concurrent `accept` waiters, leaking `netbuf` on receive error paths, and a recycled-`netconn` race between socket close and the event handler
- `socket:send/2,3` now returns `{error, eagain}` on transient send backpressure (lwIP `ERR_MEM` / BSD `EAGAIN`|`EWOULDBLOCK`) instead of misreporting it as `{error, closed}`, and returns `{error, closed}` (rather than `{ok, Data}`) when the peer has closed the connection

## [0.7.0-alpha.1] - 2026-04-06

Expand Down
17 changes: 11 additions & 6 deletions src/libAtomVM/otp_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -2573,6 +2573,13 @@ static term nif_socket_send_internal(Context *ctx, int argc, term argv[], bool i

// {ok, RestData} | {error, Reason}

// Transient send backpressure (lwIP ERR_MEM / BSD EAGAIN|EWOULDBLOCK) is
// reported as {error, eagain} so callers can retry rather than mistaking it
// for a closed connection.
if (sent_data == SocketWouldBlock) {
return make_error_tuple(posix_errno_to_term(EAGAIN, global), ctx);
}

size_t rest_len = len - sent_data;
if (rest_len == 0) {
return OK_ATOM;
Expand All @@ -2588,12 +2595,10 @@ static term nif_socket_send_internal(Context *ctx, int argc, term argv[], bool i
return port_create_tuple2(ctx, OK_ATOM, rest);

} else if (sent_data == 0) {
if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, &data, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}

return port_create_tuple2(ctx, OK_ATOM, data);
// do_socket_send only returns 0 for a closed connection (SocketClosed:
// lwIP ERR_CLSD, BSD EBADF|ECONNRESET); an empty payload already
// returned ok above via the rest_len == 0 check. Report it as such.
return make_error_tuple(CLOSED_ATOM, ctx);
} else {
TRACE("Unable to send data: res=%zi.\n", sent_data);
return make_error_tuple(CLOSED_ATOM, ctx);
Expand Down
Loading