aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPhilipp Stephani2020-11-27 19:08:55 +0100
committerPhilipp Stephani2020-11-27 21:44:05 +0100
commitcdc632fbe6e149318147a98cccf1b7af191f2ce8 (patch)
tree152ad8041a4b7fbea2bececdae1dd129b1dacc20 /src
parentc9160bda7889d9e37a9b82ef64bf711ba7e32e41 (diff)
downloademacs-cdc632fbe6e149318147a98cccf1b7af191f2ce8.tar.gz
emacs-cdc632fbe6e149318147a98cccf1b7af191f2ce8.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. Instead, turn Vmodule_runtimes and Vmodule_environments into hashtables keyed by the thread objects. The fix is relatively localized and should therefore be safe enough for the release branch. Module assertions now have to walk the pointer list for the current thread, which is more correct since they now only find environments for the current thread. 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/emacs-module.c (Fmodule_load, initialize_environment) (finalize_environment, finalize_runtime_unwind): Store runtime and environment pointers in per-thread lists. (syms_of_module): Initialize runtimes and environments hashtables. (module_assert_runtime, module_assert_env, value_to_lisp): Consider only objects for the current thread. (module_gc_hash_table_size, module_hash_push, module_hash_pop): New generic hashtable helper functions. (module_objects, module_push_pointer, module_pop_pointer): New helper functions to main thread-specific lists of runtime and environment pointers. (mark_modules): Mark all environments in all threads. * 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')
-rw-r--r--src/emacs-module.c131
1 files changed, 109 insertions, 22 deletions
diff --git a/src/emacs-module.c b/src/emacs-module.c
index a90a9765dbf..89d96839d2f 100644
--- a/src/emacs-module.c
+++ b/src/emacs-module.c
@@ -217,6 +217,9 @@ static void module_out_of_memory (emacs_env *);
217static void module_reset_handlerlist (struct handler **); 217static void module_reset_handlerlist (struct handler **);
218static bool value_storage_contains_p (const struct emacs_value_storage *, 218static bool value_storage_contains_p (const struct emacs_value_storage *,
219 emacs_value, ptrdiff_t *); 219 emacs_value, ptrdiff_t *);
220static Lisp_Object module_objects (Lisp_Object);
221static void module_push_pointer (Lisp_Object, void *);
222static void module_pop_pointer (Lisp_Object, void *);
220 223
221static bool module_assertions = false; 224static bool module_assertions = false;
222 225
@@ -1005,7 +1008,8 @@ module_signal_or_throw (struct emacs_env_private *env)
1005 } 1008 }
1006} 1009}
1007 1010
1008/* Live runtime and environment objects, for assertions. */ 1011/* Live runtime and environment objects, for assertions. These are hashtables
1012 keyed by the thread objects. */
1009static Lisp_Object Vmodule_runtimes; 1013static Lisp_Object Vmodule_runtimes;
1010static Lisp_Object Vmodule_environments; 1014static Lisp_Object Vmodule_environments;
1011 1015
@@ -1046,7 +1050,7 @@ DEFUN ("module-load", Fmodule_load, Smodule_load, 1, 1, 0,
1046 rt->private_members = &rt_priv; 1050 rt->private_members = &rt_priv;
1047 rt->get_environment = module_get_environment; 1051 rt->get_environment = module_get_environment;
1048 1052
1049 Vmodule_runtimes = Fcons (make_mint_ptr (rt), Vmodule_runtimes); 1053 module_push_pointer (Vmodule_runtimes, rt);
1050 ptrdiff_t count = SPECPDL_INDEX (); 1054 ptrdiff_t count = SPECPDL_INDEX ();
1051 record_unwind_protect_ptr (finalize_runtime_unwind, rt); 1055 record_unwind_protect_ptr (finalize_runtime_unwind, rt);
1052 1056
@@ -1146,7 +1150,8 @@ module_assert_runtime (struct emacs_runtime *ert)
1146 if (! module_assertions) 1150 if (! module_assertions)
1147 return; 1151 return;
1148 ptrdiff_t count = 0; 1152 ptrdiff_t count = 0;
1149 for (Lisp_Object tail = Vmodule_runtimes; CONSP (tail); tail = XCDR (tail)) 1153 for (Lisp_Object tail = module_objects (Vmodule_runtimes); CONSP (tail);
1154 tail = XCDR (tail))
1150 { 1155 {
1151 if (xmint_pointer (XCAR (tail)) == ert) 1156 if (xmint_pointer (XCAR (tail)) == ert)
1152 return; 1157 return;
@@ -1162,7 +1167,7 @@ module_assert_env (emacs_env *env)
1162 if (! module_assertions) 1167 if (! module_assertions)
1163 return; 1168 return;
1164 ptrdiff_t count = 0; 1169 ptrdiff_t count = 0;
1165 for (Lisp_Object tail = Vmodule_environments; CONSP (tail); 1170 for (Lisp_Object tail = module_objects (Vmodule_environments); CONSP (tail);
1166 tail = XCDR (tail)) 1171 tail = XCDR (tail))
1167 { 1172 {
1168 if (xmint_pointer (XCAR (tail)) == env) 1173 if (xmint_pointer (XCAR (tail)) == env)
@@ -1210,6 +1215,83 @@ module_out_of_memory (emacs_env *env)
1210} 1215}
1211 1216
1212 1217
1218/* Hash table helper functions. */
1219
1220/* Like HASH_TABLE_SIZE, but also works during garbage collection. */
1221
1222static ptrdiff_t
1223module_gc_hash_table_size (const struct Lisp_Hash_Table *h)
1224{
1225 ptrdiff_t size = gc_asize (h->next);
1226 eassert (0 <= size);
1227 return size;
1228}
1229
1230/* Like (push NEWELT (gethash KEY TABLE)). */
1231
1232static void
1233module_hash_push (Lisp_Object table, Lisp_Object key, Lisp_Object newelt)
1234{
1235 /* Inline calls to Fgethash/Fputhash to avoid duplicate hash lookup. */
1236 struct Lisp_Hash_Table *h = XHASH_TABLE (table);
1237 Lisp_Object hash;
1238 ptrdiff_t i = hash_lookup (h, key, &hash);
1239 if (i >= 0)
1240 set_hash_value_slot (h, i, Fcons (newelt, HASH_VALUE (h, i)));
1241 else
1242 hash_put (h, key, list1 (newelt), hash);
1243}
1244
1245/* Like (pop (gethash KEY TABLE)), but removes KEY from TABLE if the new value
1246 is nil. */
1247
1248static Lisp_Object
1249module_hash_pop (Lisp_Object table, Lisp_Object key)
1250{
1251 /* Inline calls to Fgethash/Fputhash to avoid duplicate hash lookup. */
1252 struct Lisp_Hash_Table *h = XHASH_TABLE (table);
1253 Lisp_Object hash;
1254 ptrdiff_t i = hash_lookup (h, key, &hash);
1255 eassert (i >= 0);
1256 Lisp_Object value = HASH_VALUE (h, i);
1257 Lisp_Object rest = XCDR (value);
1258 if (NILP (rest))
1259 hash_remove_from_table(h, key);
1260 else
1261 set_hash_value_slot (h, i, rest);
1262 return XCAR (value);
1263}
1264
1265/* Returns the list of objects for the current thread in TABLE. The keys of
1266 TABLE are thread objects. */
1267
1268static Lisp_Object
1269module_objects (Lisp_Object table)
1270{
1271 return Fgethash (Fcurrent_thread (), table, Qnil);
1272}
1273
1274/* Adds PTR to the front of the list of objects for the current thread in TABLE.
1275 The keys of TABLE are thread objects. */
1276
1277static void
1278module_push_pointer (Lisp_Object table, void *ptr)
1279{
1280 module_hash_push (table, Fcurrent_thread (), make_mint_ptr (ptr));
1281}
1282
1283/* Removes the first object from the list of objects for the current thread in
1284 TABLE. The keys of TABLE are thread objects. Checks that the first object
1285 is a pointer with value PTR. */
1286
1287static void
1288module_pop_pointer (Lisp_Object table, void *ptr)
1289{
1290 Lisp_Object value = module_hash_pop (table, Fcurrent_thread ());
1291 eassert (xmint_pointer (value) == ptr);
1292}
1293
1294
1213/* Value conversion. */ 1295/* Value conversion. */
1214 1296
1215/* Convert an `emacs_value' to the corresponding internal object. 1297/* Convert an `emacs_value' to the corresponding internal object.
@@ -1226,7 +1308,7 @@ value_to_lisp (emacs_value v)
1226 environments. */ 1308 environments. */
1227 ptrdiff_t num_environments = 0; 1309 ptrdiff_t num_environments = 0;
1228 ptrdiff_t num_values = 0; 1310 ptrdiff_t num_values = 0;
1229 for (Lisp_Object environments = Vmodule_environments; 1311 for (Lisp_Object environments = module_objects (Vmodule_environments);
1230 CONSP (environments); environments = XCDR (environments)) 1312 CONSP (environments); environments = XCDR (environments))
1231 { 1313 {
1232 emacs_env *env = xmint_pointer (XCAR (environments)); 1314 emacs_env *env = xmint_pointer (XCAR (environments));
@@ -1326,16 +1408,19 @@ allocate_emacs_value (emacs_env *env, struct emacs_value_storage *storage,
1326void 1408void
1327mark_modules (void) 1409mark_modules (void)
1328{ 1410{
1329 for (Lisp_Object tem = Vmodule_environments; CONSP (tem); tem = XCDR (tem)) 1411 const struct Lisp_Hash_Table *h = XHASH_TABLE (Vmodule_environments);
1330 { 1412 /* Can't use HASH_TABLE_SIZE because we are in the mark phase of the GC. */
1331 emacs_env *env = xmint_pointer (XCAR (tem)); 1413 for (ptrdiff_t i = 0; i < module_gc_hash_table_size (h); ++i)
1332 struct emacs_env_private *priv = env->private_members; 1414 if (!EQ (HASH_KEY (h, i), Qunbound))
1333 for (struct emacs_value_frame *frame = &priv->storage.initial; 1415 for (Lisp_Object tem = HASH_VALUE (h, i); CONSP (tem); tem = XCDR (tem))
1334 frame != NULL; 1416 {
1335 frame = frame->next) 1417 emacs_env *env = xmint_pointer (XCAR (tem));
1336 for (int i = 0; i < frame->offset; ++i) 1418 struct emacs_env_private *priv = env->private_members;
1337 mark_object (frame->objects[i].v); 1419 for (struct emacs_value_frame *frame = &priv->storage.initial;
1338 } 1420 frame != NULL; frame = frame->next)
1421 for (int i = 0; i < frame->offset; ++i)
1422 mark_object (frame->objects[i].v);
1423 }
1339} 1424}
1340 1425
1341 1426
@@ -1390,7 +1475,7 @@ initialize_environment (emacs_env *env, struct emacs_env_private *priv)
1390 env->make_time = module_make_time; 1475 env->make_time = module_make_time;
1391 env->extract_big_integer = module_extract_big_integer; 1476 env->extract_big_integer = module_extract_big_integer;
1392 env->make_big_integer = module_make_big_integer; 1477 env->make_big_integer = module_make_big_integer;
1393 Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments); 1478 module_push_pointer (Vmodule_environments, env);
1394 return env; 1479 return env;
1395} 1480}
1396 1481
@@ -1400,8 +1485,7 @@ static void
1400finalize_environment (emacs_env *env) 1485finalize_environment (emacs_env *env)
1401{ 1486{
1402 finalize_storage (&env->private_members->storage); 1487 finalize_storage (&env->private_members->storage);
1403 eassert (xmint_pointer (XCAR (Vmodule_environments)) == env); 1488 module_pop_pointer (Vmodule_environments, env);
1404 Vmodule_environments = XCDR (Vmodule_environments);
1405} 1489}
1406 1490
1407static void 1491static void
@@ -1414,9 +1498,8 @@ static void
1414finalize_runtime_unwind (void *raw_ert) 1498finalize_runtime_unwind (void *raw_ert)
1415{ 1499{
1416 struct emacs_runtime *ert = raw_ert; 1500 struct emacs_runtime *ert = raw_ert;
1417 eassert (xmint_pointer (XCAR (Vmodule_runtimes)) == ert);
1418 Vmodule_runtimes = XCDR (Vmodule_runtimes);
1419 finalize_environment (ert->private_members->env); 1501 finalize_environment (ert->private_members->env);
1502 module_pop_pointer (Vmodule_runtimes, ert);
1420} 1503}
1421 1504
1422 1505
@@ -1506,10 +1589,14 @@ syms_of_module (void)
1506 Qnil, false); 1589 Qnil, false);
1507 1590
1508 staticpro (&Vmodule_runtimes); 1591 staticpro (&Vmodule_runtimes);
1509 Vmodule_runtimes = Qnil; 1592 Vmodule_runtimes
1593 = make_hash_table (hashtest_eq, DEFAULT_HASH_SIZE, DEFAULT_REHASH_SIZE,
1594 DEFAULT_REHASH_THRESHOLD, Qnil, false);
1510 1595
1511 staticpro (&Vmodule_environments); 1596 staticpro (&Vmodule_environments);
1512 Vmodule_environments = Qnil; 1597 Vmodule_environments
1598 = make_hash_table (hashtest_eq, DEFAULT_HASH_SIZE, DEFAULT_REHASH_SIZE,
1599 DEFAULT_REHASH_THRESHOLD, Qnil, false);
1513 1600
1514 DEFSYM (Qmodule_load_failed, "module-load-failed"); 1601 DEFSYM (Qmodule_load_failed, "module-load-failed");
1515 Fput (Qmodule_load_failed, Qerror_conditions, 1602 Fput (Qmodule_load_failed, Qerror_conditions,