-
Notifications
You must be signed in to change notification settings - Fork 114
Optimize allocation of memory in ESP32 socket_driver do_send #1526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pguyot
wants to merge
1
commit into
atomvm:release-0.6
Choose a base branch
from
pguyot:w06/fix-malloc-on-socket_driver
base: release-0.6
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1117,40 +1117,61 @@ static void do_send(Context *ctx, const GenMessage *gen_message) | |
term data = term_get_tuple_element(gen_message->req, 1); | ||
|
||
size_t buffer_size; | ||
switch (interop_iolist_size(data, &buffer_size)) { | ||
case InteropOk: | ||
break; | ||
case InteropMemoryAllocFail: | ||
fprintf(stderr, "error: failed alloc.\n"); | ||
return; | ||
case InteropBadArg: | ||
fprintf(stderr, "error: invalid iolist.\n"); | ||
return; | ||
} | ||
void *buffer = malloc(buffer_size); | ||
switch (interop_write_iolist(data, buffer)) { | ||
case InteropOk: | ||
break; | ||
case InteropMemoryAllocFail: | ||
free(buffer); | ||
fprintf(stderr, "error: failed alloc.\n"); | ||
return; | ||
case InteropBadArg: | ||
free(buffer); | ||
fprintf(stderr, "error: invalid iolist.\n"); | ||
return; | ||
const void *buffer; | ||
void *buffer_copy = NULL; | ||
if (term_is_binary(data)) { | ||
// No need to copy. | ||
buffer_size = term_binary_size(data); | ||
buffer = term_binary_data(data); | ||
} else { | ||
switch (interop_iolist_size(data, &buffer_size)) { | ||
case InteropOk: | ||
break; | ||
case InteropMemoryAllocFail: | ||
if (UNLIKELY(memory_ensure_free(ctx, REPLY_SIZE) != MEMORY_GC_OK)) { | ||
AVM_ABORT(); | ||
} | ||
do_send_reply(ctx, OUT_OF_MEMORY_ATOM, ref_ticks, pid); | ||
return; | ||
case InteropBadArg: | ||
if (UNLIKELY(memory_ensure_free(ctx, REPLY_SIZE) != MEMORY_GC_OK)) { | ||
AVM_ABORT(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question about aborting here... and again below several more times. |
||
} | ||
do_send_reply(ctx, BADARG_ATOM, ref_ticks, pid); | ||
return; | ||
} | ||
buffer_copy = malloc(buffer_size); | ||
switch (interop_write_iolist(data, buffer_copy)) { | ||
case InteropOk: | ||
break; | ||
case InteropMemoryAllocFail: | ||
free(buffer_copy); | ||
if (UNLIKELY(memory_ensure_free(ctx, REPLY_SIZE) != MEMORY_GC_OK)) { | ||
AVM_ABORT(); | ||
} | ||
do_send_reply(ctx, OUT_OF_MEMORY_ATOM, ref_ticks, pid); | ||
return; | ||
case InteropBadArg: | ||
free(buffer_copy); | ||
if (UNLIKELY(memory_ensure_free(ctx, REPLY_SIZE) != MEMORY_GC_OK)) { | ||
AVM_ABORT(); | ||
} | ||
do_send_reply(ctx, BADARG_ATOM, ref_ticks, pid); | ||
return; | ||
} | ||
buffer = buffer_copy; | ||
} | ||
err_t status = netconn_write(tcp_data->socket_data.conn, buffer, buffer_size, NETCONN_COPY); | ||
free(buffer); | ||
if (UNLIKELY(status != ERR_OK)) { | ||
fprintf(stderr, "write error: %i\n", status); | ||
return; | ||
} | ||
free(buffer_copy); | ||
|
||
if (UNLIKELY(memory_ensure_free(ctx, REPLY_SIZE) != MEMORY_GC_OK)) { | ||
AVM_ABORT(); | ||
} | ||
do_send_reply(ctx, OK_ATOM, ref_ticks, pid); | ||
if (UNLIKELY(status != ERR_OK)) { | ||
do_send_reply(ctx, ERROR_ATOM, ref_ticks, pid); | ||
} else { | ||
do_send_reply(ctx, OK_ATOM, ref_ticks, pid); | ||
} | ||
} | ||
|
||
static void do_sendto(Context *ctx, const GenMessage *gen_message) | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we send an out_of_memory AtomVM rather than abort here? Maybe the process should be allowed to crash instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for raising this. I copied what exists elsewhere, when we don't have enough RAM to send an out of memory message. I wonder what we could do. generic_unix also aborts but preallocates memory for the reply.
https://github.com/atomvm/AtomVM/blob/7396812e4bd379237644e55ad822f7798d79f9d8/src/platforms/generic_unix/lib/socket_driver.c#L1124C5-L1124C26
Alternatively, we could promote using the C stack for ports instead of the context heap which relies on malloc and GC.
Should we take this in another PR, though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Older code definitely aborts, but in updated drivers etc, when there isn't enough memory for a proper error return we simply return the out_of_memory atom alone. I think this is far better (since it will likely cause the process to crash) than aborting the whole VM, we should just go ahead and let the process crash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it would be more likely for failed allocations to result in a panic, again crashing the entire VM, instead of just the users process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be the best solution for now. Perhaps we need a meta-issue about reducing the use of abort in general in favor of returning an error and crashing user processes. There are quite a few places in the code base where we abort when returning an error would be preferable, like this one. It seems much better to return an error, that quit all together, when a payload fails to be sent. This might just be a transient low memory condition that occurs very infrequently during an applications execution, and might be remedied by supervisors that the user has already put in place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also just keep the original behavior of logging the failure to
stderr
and returning, in the hopes the next GC might free enough memory for normal operation to resume.