threading: fix race conditions in CLOS cache

If a thread is interrupted after a call to fill_spec_vector, but
    before it can call ecl_search_cache, the cache may change during
    the interrupt, leading to crashes. We can't use
    env->disable_interrupts since fill_spec_vector calls methods which
    write in the thread-local environment. Disabling interrupts in
    ecl_search_cache and clear_list_from_cache is now redundant and
    has been removed.
This commit is contained in:
Marius Gerbershagen 2018-02-20 20:24:08 +01:00
parent 25ec43b498
commit f9630fa8b3
4 changed files with 42 additions and 27 deletions

View file

@ -71,8 +71,12 @@ static ecl_cache_record_ptr
search_slot_index(const cl_env_ptr env, cl_object gfun, cl_object instance)
{
ecl_cache_ptr cache = env->slot_cache;
fill_spec_vector(cache->keys, gfun, instance);
return ecl_search_cache(cache);
ecl_cache_record_ptr ret;
ECL_WITHOUT_INTERRUPTS_BEGIN(env) {
fill_spec_vector(cache->keys, gfun, instance);
ret = ecl_search_cache(cache);
} ECL_WITHOUT_INTERRUPTS_END;
return ret;
}
static ecl_cache_record_ptr
@ -89,10 +93,12 @@ add_new_index(const cl_env_ptr env, cl_object gfun, cl_object instance, cl_objec
{
ecl_cache_record_ptr e;
ecl_cache_ptr cache = env->slot_cache;
fill_spec_vector(cache->keys, gfun, instance);
e = ecl_search_cache(cache);
e->key = cl_copy_seq(cache->keys);
e->value = index;
ECL_WITHOUT_INTERRUPTS_BEGIN(env) {
fill_spec_vector(cache->keys, gfun, instance);
e = ecl_search_cache(cache);
e->key = cl_copy_seq(cache->keys);
e->value = index;
} ECL_WITHOUT_INTERRUPTS_END;
return e;
}
}

View file

@ -56,7 +56,6 @@ clear_one_from_cache(ecl_cache_ptr cache, cl_object target)
static void
clear_list_from_cache(ecl_cache_ptr cache)
{
ecl_disable_interrupts();
cl_object list = ecl_atomic_get(&cache->clear_list);
cl_object table = cache->table;
cl_index i, total_size = table->vector.dim;
@ -69,7 +68,6 @@ clear_list_from_cache(ecl_cache_ptr cache)
}
}
}
ecl_enable_interrupts();
}
#endif
@ -128,6 +126,7 @@ vector_hash_key(cl_object keys)
/*
* variation of ecl_gethash from hash.d, which takes an array of objects as key
* It also assumes that entries are never removed except by clrhash.
* This method must be called with interrupts disabled!
*/
ecl_cache_record_ptr
@ -139,7 +138,6 @@ ecl_search_cache(ecl_cache_ptr cache)
}
#endif
{
ecl_disable_interrupts();
cl_object table = cache->table;
cl_object keys = cache->keys;
cl_index argno = keys->vector.fillp;
@ -216,7 +214,6 @@ ecl_search_cache(ecl_cache_ptr cache)
RECORD_GEN_SET(e, g);
}
}
ecl_enable_interrupts();
return (ecl_cache_record_ptr)min_e;
}
}

View file

@ -232,25 +232,27 @@ _ecl_standard_dispatch(cl_object frame, cl_object gf)
}
#endif
vector = fill_spec_vector(cache->keys, frame, gf);
e = ecl_search_cache(cache);
if (e->key != OBJNULL) {
func = e->value;
} else {
/* The keys and the cache may change while we
* compute the applicable methods. We must save
* the keys and recompute the cache location if
* it was filled. */
cl_object keys = cl_copy_seq(vector);
func = compute_applicable_method(env, frame, gf);
if (env->values[1] != ECL_NIL) {
if (e->key != OBJNULL) {
e = ecl_search_cache(cache);
ECL_WITHOUT_INTERRUPTS_BEGIN(env) {
vector = fill_spec_vector(cache->keys, frame, gf);
e = ecl_search_cache(cache);
if (e->key != OBJNULL) {
func = e->value;
} else {
/* The keys and the cache may change while we
* compute the applicable methods. We must save
* the keys and recompute the cache location if
* it was filled. */
cl_object keys = cl_copy_seq(vector);
func = compute_applicable_method(env, frame, gf);
if (env->values[1] != ECL_NIL) {
if (e->key != OBJNULL) {
e = ecl_search_cache(cache);
}
e->key = keys;
e->value = func;
}
e->key = keys;
e->value = func;
}
}
} ECL_WITHOUT_INTERRUPTS_END;
if (func == ECL_NIL)
func = cl_apply(3, @'no-applicable-method', gf, frame);
else

View file

@ -522,6 +522,16 @@ extern cl_object mp_get_rwlock_write_wait(cl_object lock);
extern void ecl_interrupt_process(cl_object process, cl_object function);
/* disabling interrupts on the lisp side */
#define ECL_WITHOUT_INTERRUPTS_BEGIN(the_env) do { \
cl_env_ptr __the_env = (the_env); \
ecl_bds_bind(__the_env, ECL_INTERRUPTS_ENABLED, ECL_NIL);
#define ECL_WITHOUT_INTERRUPTS_END \
ecl_bds_unwind1(__the_env); \
ecl_check_pending_interrupts(__the_env); } while(0)
/* unixsys.d */
/* Some old BSD systems don't have WCONTINUED / WIFCONTINUED */