Skip to content

Commit 7322265

Browse files
committed
Fix SMP deadlock
https://ampcode.com/threads/T-019ba1c2-2a7f-77c7-bd33-ce9f303152a2 Fix SMP deadlock, due to ABBA deadlocks. Signed-off-by: Peter M <petermm@gmail.com>
1 parent fd96d95 commit 7322265

File tree

1 file changed

+40
-17
lines changed

1 file changed

+40
-17
lines changed

src/libAtomVM/resources.c

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -473,54 +473,77 @@ int enif_demonitor_process(ErlNifEnv *env, void *obj, const ErlNifMonitor *mon)
473473
return -1;
474474
}
475475

476+
int32_t target_process_id = INVALID_PROCESS_ID;
477+
uint64_t ref_ticks = 0;
478+
479+
// Phase 1: Find and remove from monitors list while holding monitors lock
476480
struct ListHead *monitors = synclist_wrlock(&resource_type->monitors);
477481
struct ListHead *item;
478482
LIST_FOR_EACH (item, monitors) {
479483
struct ResourceMonitor *monitor = GET_LIST_ENTRY(item, struct ResourceMonitor, resource_list_head);
480484
if (monitor->ref_ticks == mon->ref_ticks) {
481485
struct RefcBinary *resource = refc_binary_from_data(obj);
482486
if (resource->resource_type != mon->resource_type) {
487+
synclist_unlock(&resource_type->monitors);
483488
return -1;
484489
}
485490

486-
Context *target = globalcontext_get_process_lock(global, monitor->process_id);
487-
if (target) {
488-
mailbox_send_ref_signal(target, DemonitorSignal, monitor->ref_ticks);
489-
globalcontext_get_process_unlock(global, target);
490-
}
491-
491+
target_process_id = monitor->process_id;
492+
ref_ticks = monitor->ref_ticks;
492493
list_remove(&monitor->resource_list_head);
493494
free(monitor);
494-
synclist_unlock(&resource_type->monitors);
495-
return 0;
495+
break;
496496
}
497497
}
498-
499498
synclist_unlock(&resource_type->monitors);
500499

501-
return -1;
500+
if (target_process_id == INVALID_PROCESS_ID) {
501+
return -1;
502+
}
503+
504+
// Phase 2: Send demonitor signal without holding monitors lock
505+
// This avoids lock order inversion with processes_table
506+
Context *target = globalcontext_get_process_lock(global, target_process_id);
507+
if (target) {
508+
mailbox_send_ref_signal(target, DemonitorSignal, ref_ticks);
509+
globalcontext_get_process_unlock(global, target);
510+
}
511+
512+
return 0;
502513
}
503514

504515
void destroy_resource_monitors(struct RefcBinary *resource, GlobalContext *global)
505516
{
506517
struct ResourceType *resource_type = resource->resource_type;
518+
519+
// Phase 1: Collect monitors to destroy while holding monitors lock
520+
struct ListHead to_signal;
521+
list_init(&to_signal);
522+
507523
struct ListHead *monitors = synclist_wrlock(&resource_type->monitors);
508524
struct ListHead *item;
509525
struct ListHead *tmp;
510526
MUTABLE_LIST_FOR_EACH (item, tmp, monitors) {
511527
struct ResourceMonitor *monitor = GET_LIST_ENTRY(item, struct ResourceMonitor, resource_list_head);
512528
if (monitor->resource == resource) {
513-
Context *target = globalcontext_get_process_lock(global, monitor->process_id);
514-
if (target) {
515-
mailbox_send_ref_signal(target, DemonitorSignal, monitor->ref_ticks);
516-
globalcontext_get_process_unlock(global, target);
517-
}
518529
list_remove(&monitor->resource_list_head);
519-
free(monitor);
530+
list_append(&to_signal, &monitor->resource_list_head);
520531
}
521532
}
522-
523533
synclist_unlock(&resource_type->monitors);
534+
535+
// Phase 2: Send demonitor signals without holding monitors lock
536+
// This avoids lock order inversion with processes_table
537+
MUTABLE_LIST_FOR_EACH (item, tmp, &to_signal) {
538+
struct ResourceMonitor *monitor = GET_LIST_ENTRY(item, struct ResourceMonitor, resource_list_head);
539+
Context *target = globalcontext_get_process_lock(global, monitor->process_id);
540+
if (target) {
541+
mailbox_send_ref_signal(target, DemonitorSignal, monitor->ref_ticks);
542+
globalcontext_get_process_unlock(global, target);
543+
}
544+
list_remove(&monitor->resource_list_head);
545+
free(monitor);
546+
}
524547
}
525548

526549
int enif_compare_monitors(const ErlNifMonitor *monitor1, const ErlNifMonitor *monitor2)

0 commit comments

Comments
 (0)