Skip to content

Commit f9b71db

Browse files
committed
Fix a bug where native ports would be executed with no message
When a process is signaled, it is executed to process the signal, but the mailbox may be empty after the signal is processed. This would yield a crash on native handlers that expect `mailbox_first` to always return the message to consume. Also work around native ports that only process one message by rescheduling them if there is another message. Signed-off-by: Paul Guyot <[email protected]>
1 parent 22c8521 commit f9b71db

File tree

1 file changed

+31
-9
lines changed

1 file changed

+31
-9
lines changed

src/libAtomVM/scheduler.c

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#endif
4444

4545
static void scheduler_timeout_callback(struct TimerListItem *it);
46+
static void scheduler_make_ready(Context *ctx);
4647

4748
static int update_timer_list(GlobalContext *global)
4849
{
@@ -233,18 +234,39 @@ Context *scheduler_run(GlobalContext *global)
233234
// process signal messages and also empty outer list to inner list.
234235
scheduler_process_native_signal_messages(result);
235236
if (!(result->flags & Killed)) {
236-
if (result->native_handler(result) == NativeContinue) {
237-
// If native handler has memory fragments, garbage collect
238-
// them
239-
if (result->heap.root->next) {
240-
if (UNLIKELY(memory_ensure_free_opt(result, 0, MEMORY_FORCE_SHRINK) != MEMORY_GC_OK)) {
241-
fprintf(stderr, "Out of memory error in native handler\n");
242-
AVM_ABORT();
237+
if (mailbox_has_next(&result->mailbox)) {
238+
if (result->native_handler(result) == NativeContinue) {
239+
// If native handler has memory fragments, garbage collect
240+
// them
241+
if (result->heap.root->next) {
242+
if (UNLIKELY(memory_ensure_free_opt(result, 0, MEMORY_FORCE_SHRINK) != MEMORY_GC_OK)) {
243+
fprintf(stderr, "Out of memory error in native handler\n");
244+
AVM_ABORT();
245+
}
243246
}
247+
context_update_flags(result, ~Running, NoFlags);
248+
// The context was marked ready for the first message
249+
// However, another message may have arrived before it
250+
// was marked running in scheduler_run0, so there can
251+
// be several messages in the mailbox. Yet, the handler
252+
// may have processed only one message. So we rechedule
253+
// to make sure the handler process all messages.
254+
if (mailbox_has_next(&result->mailbox)) {
255+
scheduler_make_ready(result);
256+
}
257+
} else {
258+
scheduler_terminate(result);
244259
}
245-
context_update_flags(result, ~Running, NoFlags);
246260
} else {
247-
scheduler_terminate(result);
261+
// Don't call the native handler if the mailbox is empty
262+
// so native handlers can safely expect the first call to
263+
// `mailbox_first` will return a message.
264+
//
265+
// Indeed, a native process can be signaled and be made
266+
// ready, and in this case the mailbox may only contain
267+
// signal messages that are processed and removed by
268+
// `scheduler_process_native_signal_messages`.
269+
context_update_flags(result, ~Running, NoFlags);
248270
}
249271
}
250272
result = NULL; // Schedule next process (native or not)

0 commit comments

Comments
 (0)