aboutsummaryrefslogtreecommitdiffstats
path: root/src/eval.c
diff options
context:
space:
mode:
authorPhilipp Stephani2020-11-27 19:08:55 +0100
committerPhilipp Stephani2020-11-27 20:15:33 +0100
commit23974cfa48b9245658667eff81d132b3aecd2618 (patch)
tree654441136c36d579d366f64f32907bb72e2b3b20 /src/eval.c
parente7423689208026658fbe8c75523eac6dbe022c39 (diff)
downloademacs-23974cfa48b9245658667eff81d132b3aecd2618.tar.gz
emacs-23974cfa48b9245658667eff81d132b3aecd2618.zip
Fix incorrect handling of module runtime and environment pointers.
We used to store module runtime and environment pointers in the static lists Vmodule_runtimes and Vmodule_environments. However, this is incorrect because these objects have to be kept per-thread. With this naive approach, interleaving module function calls in separate threads leads to environments being removed in the wrong order, which in turn can cause local module values to be incorrectly garbage-collected. The fix isn't completely trivial: specbinding the lists wouldn't work either, because then the garbage collector wouldn't find the environments in other threads than the current ones, again leading to objects being garbage-collected incorrectly. While introducing custom pseudovector types would fix this, it's simpler to put the runtime and environment pointers into the specbinding list as new specbinding kinds. This works since we need to unwind them anyway, and we only ever treat the lists as a stack. The thread switching machinery ensures that the specbinding lists are thread-local, and that all elements of the specbinding lists in all threads are marked during garbage collection. Module assertions now have to walk the specbinding list for the current thread, which is more correct since they now only find environments for the current thread. As a result, we can now remove the faulty Vmodule_runtimes and Vmodule_environments variables entirely. Also add a unit test that exemplifies the problem. It interleaves two module calls in two threads so that the first call ends while the second one is still active. Without this change, this test triggers an assertion failure. * src/lisp.h (enum specbind_tag): Add new tags for module runtimes and environments. * src/eval.c (record_unwind_protect_module): New function to record a module object in the specpdl list. (do_one_unbind): Unwind module objects. (backtrace_eval_unrewind, default_toplevel_binding, lexbound_p) (Fbacktrace__locals): Deal with new specbinding types. (mark_specpdl): Mark module environments as needed. * src/alloc.c (garbage_collect): Remove call to 'mark-modules'. Garbage collection of module values is now handled as part of marking the specpdl of each thread. * src/emacs-module.c (Fmodule_load, funcall_module): Use specpdl to record module runtimes and environments. (module_assert_runtime, module_assert_env, value_to_lisp): Walk through specpdl list instead of list variables. (mark_module_environment): Rename from 'mark_modules'. Don't attempt to walk though current thread's environments only, since that would miss other threads. (initialize_environment, finalize_environment): Don't change Vmodule_environments variable; environments are now in the specpdl list. (finalize_environment_unwind, finalize_runtime_unwind): Make 'extern' since do_one_unbind now calls them. (finalize_runtime_unwind): Don't change Vmodule_runtimes variable; runtimes are now in the specpdl list. (syms_of_module): Remove Vmodule_runtimes and Vmodule_environments. * test/data/emacs-module/mod-test.c (Fmod_test_funcall): New test function. (emacs_module_init): Bind it. * test/src/emacs-module-tests.el (emacs-module-tests--variable): New helper type to guard access to state in a thread-safe way. (emacs-module-tests--wait-for-variable) (emacs-module-tests--change-variable): New helper functions. (emacs-module-tests/interleaved-threads): New unit test.
Diffstat (limited to 'src/eval.c')
-rw-r--r--src/eval.c41
1 files changed, 41 insertions, 0 deletions
diff --git a/src/eval.c b/src/eval.c
index 3f9dcf6be6d..d9a424b57a9 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -681,6 +681,10 @@ default_toplevel_binding (Lisp_Object symbol)
681 case SPECPDL_UNWIND_EXCURSION: 681 case SPECPDL_UNWIND_EXCURSION:
682 case SPECPDL_UNWIND_VOID: 682 case SPECPDL_UNWIND_VOID:
683 case SPECPDL_BACKTRACE: 683 case SPECPDL_BACKTRACE:
684#ifdef HAVE_MODULES
685 case SPECPDL_MODULE_RUNTIME:
686 case SPECPDL_MODULE_ENVIRONMENT:
687#endif
684 case SPECPDL_LET_LOCAL: 688 case SPECPDL_LET_LOCAL:
685 break; 689 break;
686 690
@@ -720,6 +724,10 @@ lexbound_p (Lisp_Object symbol)
720 case SPECPDL_UNWIND_EXCURSION: 724 case SPECPDL_UNWIND_EXCURSION:
721 case SPECPDL_UNWIND_VOID: 725 case SPECPDL_UNWIND_VOID:
722 case SPECPDL_BACKTRACE: 726 case SPECPDL_BACKTRACE:
727#ifdef HAVE_MODULES
728 case SPECPDL_MODULE_RUNTIME:
729 case SPECPDL_MODULE_ENVIRONMENT:
730#endif
723 case SPECPDL_LET_LOCAL: 731 case SPECPDL_LET_LOCAL:
724 break; 732 break;
725 733
@@ -3480,6 +3488,15 @@ record_unwind_protect_void (void (*function) (void))
3480} 3488}
3481 3489
3482void 3490void
3491record_unwind_protect_module (enum specbind_tag kind, void *ptr)
3492{
3493 specpdl_ptr->kind = kind;
3494 specpdl_ptr->unwind_ptr.func = NULL;
3495 specpdl_ptr->unwind_ptr.arg = ptr;
3496 grow_specpdl ();
3497}
3498
3499void
3483rebind_for_thread_switch (void) 3500rebind_for_thread_switch (void)
3484{ 3501{
3485 union specbinding *bind; 3502 union specbinding *bind;
@@ -3529,6 +3546,14 @@ do_one_unbind (union specbinding *this_binding, bool unwinding,
3529 break; 3546 break;
3530 case SPECPDL_BACKTRACE: 3547 case SPECPDL_BACKTRACE:
3531 break; 3548 break;
3549#ifdef HAVE_MODULES
3550 case SPECPDL_MODULE_RUNTIME:
3551 finalize_runtime_unwind (this_binding->unwind_ptr.arg);
3552 break;
3553 case SPECPDL_MODULE_ENVIRONMENT:
3554 finalize_environment_unwind (this_binding->unwind_ptr.arg);
3555 break;
3556#endif
3532 case SPECPDL_LET: 3557 case SPECPDL_LET:
3533 { /* If variable has a trivial value (no forwarding), and isn't 3558 { /* If variable has a trivial value (no forwarding), and isn't
3534 trapped, we can just set it. */ 3559 trapped, we can just set it. */
@@ -3859,6 +3884,10 @@ backtrace_eval_unrewind (int distance)
3859 case SPECPDL_UNWIND_INTMAX: 3884 case SPECPDL_UNWIND_INTMAX:
3860 case SPECPDL_UNWIND_VOID: 3885 case SPECPDL_UNWIND_VOID:
3861 case SPECPDL_BACKTRACE: 3886 case SPECPDL_BACKTRACE:
3887#ifdef HAVE_MODULES
3888 case SPECPDL_MODULE_RUNTIME:
3889 case SPECPDL_MODULE_ENVIRONMENT:
3890#endif
3862 break; 3891 break;
3863 case SPECPDL_LET: 3892 case SPECPDL_LET:
3864 { /* If variable has a trivial value (no forwarding), we can 3893 { /* If variable has a trivial value (no forwarding), we can
@@ -3994,6 +4023,10 @@ NFRAMES and BASE specify the activation frame to use, as in `backtrace-frame'.
3994 case SPECPDL_UNWIND_EXCURSION: 4023 case SPECPDL_UNWIND_EXCURSION:
3995 case SPECPDL_UNWIND_VOID: 4024 case SPECPDL_UNWIND_VOID:
3996 case SPECPDL_BACKTRACE: 4025 case SPECPDL_BACKTRACE:
4026#ifdef HAVE_MODULES
4027 case SPECPDL_MODULE_RUNTIME:
4028 case SPECPDL_MODULE_ENVIRONMENT:
4029#endif
3997 break; 4030 break;
3998 4031
3999 default: 4032 default:
@@ -4040,6 +4073,14 @@ mark_specpdl (union specbinding *first, union specbinding *ptr)
4040 } 4073 }
4041 break; 4074 break;
4042 4075
4076#ifdef HAVE_MODULES
4077 case SPECPDL_MODULE_RUNTIME:
4078 break;
4079 case SPECPDL_MODULE_ENVIRONMENT:
4080 mark_module_environment (pdl->unwind_ptr.arg);
4081 break;
4082#endif
4083
4043 case SPECPDL_LET_DEFAULT: 4084 case SPECPDL_LET_DEFAULT:
4044 case SPECPDL_LET_LOCAL: 4085 case SPECPDL_LET_LOCAL:
4045 mark_object (specpdl_where (pdl)); 4086 mark_object (specpdl_where (pdl));