Skip to content

Commit d416d90

Browse files
committed
Merge pull request atomvm#1603 from pguyot/w13/fix-resource-monitor-concurrency-issue
Use ref count and lock on list of monitors for a resource type to ensure that resource still exists when calling the monitor handler. Should fix atomvm#1589 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
2 parents b134785 + 4f168b7 commit d416d90

File tree

4 files changed

+53
-6
lines changed

4 files changed

+53
-6
lines changed

src/libAtomVM/context.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,7 @@ void context_destroy(Context *ctx)
180180
struct Monitor *monitor = GET_LIST_ENTRY(item, struct Monitor, monitor_list_head);
181181
void *resource = term_to_term_ptr(monitor->monitor_obj);
182182
struct RefcBinary *refc = refc_binary_from_data(resource);
183-
resource_type_demonitor(refc->resource_type, monitor->ref_ticks);
184-
refc->resource_type->down(&env, resource, &ctx->process_id, &monitor->ref_ticks);
183+
resource_type_fire_monitor(refc->resource_type, &env, resource, ctx->process_id, monitor->ref_ticks);
185184
free(monitor);
186185
}
187186
}

src/libAtomVM/refc_binary.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,20 @@ void refc_binary_increment_refcount(struct RefcBinary *refc)
6666
bool refc_binary_decrement_refcount(struct RefcBinary *refc, struct GlobalContext *global)
6767
{
6868
if (--refc->ref_count == 0) {
69+
if (refc->resource_type && refc->resource_type->down) {
70+
// There may be monitors associated with this resource.
71+
destroy_resource_monitors(refc, global);
72+
// After this point, the resource can no longer be found by
73+
// resource_type_fire_monitor
74+
// However, resource_type_fire_monitor may have incremented ref_count
75+
// to call the monitor handler.
76+
// So we check ref_count again. We're not affected by the ABA problem
77+
// here as the resource cannot (should not) be monitoring while it is
78+
// being destroyed, i.e. no resource monitor will be created now
79+
if (UNLIKELY(refc->ref_count != 0)) {
80+
return false;
81+
}
82+
}
6983
synclist_remove(&global->refc_binaries, &refc->head);
7084
refc_binary_destroy(refc, global);
7185
return true;
@@ -78,10 +92,6 @@ void refc_binary_destroy(struct RefcBinary *refc, struct GlobalContext *global)
7892
UNUSED(global);
7993

8094
if (refc->resource_type) {
81-
if (refc->resource_type->down) {
82-
// There may be monitors associated with this resource.
83-
destroy_resource_monitors(refc, global);
84-
}
8595
if (refc->resource_type->dtor) {
8696
ErlNifEnv env;
8797
erl_nif_env_partial_init_from_globalcontext(&env, global);

src/libAtomVM/resources.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,31 @@ int enif_monitor_process(ErlNifEnv *env, void *obj, const ErlNifPid *target_pid,
360360
return 0;
361361
}
362362

363+
void resource_type_fire_monitor(struct ResourceType *resource_type, ErlNifEnv *env, void *resource, int32_t process_id, uint64_t ref_ticks)
364+
{
365+
struct RefcBinary *refc = NULL;
366+
struct ListHead *monitors = synclist_wrlock(&resource_type->monitors);
367+
struct ListHead *item;
368+
LIST_FOR_EACH (item, monitors) {
369+
struct ResourceMonitor *monitor = GET_LIST_ENTRY(item, struct ResourceMonitor, resource_list_head);
370+
if (monitor->ref_ticks == ref_ticks) {
371+
// Resource still exists.
372+
refc = refc_binary_from_data(resource);
373+
refc_binary_increment_refcount(refc);
374+
list_remove(&monitor->resource_list_head);
375+
free(monitor);
376+
break;
377+
}
378+
}
379+
380+
synclist_unlock(&resource_type->monitors);
381+
382+
if (refc) {
383+
resource_type->down(env, resource, &process_id, &ref_ticks);
384+
refc_binary_decrement_refcount(refc, env->global);
385+
}
386+
}
387+
363388
void resource_type_demonitor(struct ResourceType *resource_type, uint64_t ref_ticks)
364389
{
365390
struct ListHead *monitors = synclist_wrlock(&resource_type->monitors);

src/libAtomVM/resources.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ void destroy_resource_monitors(struct RefcBinary *resource, GlobalContext *globa
149149
*/
150150
term select_event_make_notification(void *rsrc_obj, uint64_t ref_ticks, bool is_write, Heap *heap);
151151

152+
/**
153+
* @brief Call down handler for a given resource and remove monitor from list.
154+
* @details handler is called while holding lock on the list of monitors and
155+
* if monitor is still in the list of resource monitors, thus ensuring that
156+
* the resource still exists.
157+
* @param resource_type type holding the list of monitors
158+
* @param env environment for calling the down handler
159+
* @param resource resource that monitored the process
160+
* @param process_id id of the process monitored
161+
* @param ref_ticks reference of the monitor
162+
*/
163+
void resource_type_fire_monitor(struct ResourceType *resource_type, ErlNifEnv *env, void *resource, int32_t process_id, uint64_t ref_ticks);
164+
152165
/**
153166
* @brief Remove monitor from list of monitors.
154167
* @param resource_type type holding the list of monitors

0 commit comments

Comments
 (0)