Skip to content

Commit 87032fa

Browse files
committed
Add cache generation tracking for main interpreter contexts
flush_imports now works correctly for all context types: - Subinterpreter pool: slots marked stale and replaced - OWN_GIL: each has own subinterpreter destroyed with context - Main interpreter: cache generation checked on module access Added unconditional import_cache_get_generation() and import_cache_flush_generation() functions that work regardless of subinterpreter support.
1 parent 07fd494 commit 87032fa

4 files changed

Lines changed: 74 additions & 5 deletions

File tree

c_src/py_nif.c

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4124,6 +4124,7 @@ static ERL_NIF_TERM nif_context_create(ErlNifEnv *env, int argc, const ERL_NIF_T
41244124
ctx->globals = NULL;
41254125
ctx->locals = NULL;
41264126
ctx->module_cache = NULL;
4127+
ctx->cache_generation = 0;
41274128

41284129
/* Create callback pipe for blocking callback responses */
41294130
if (pipe(ctx->callback_pipe) < 0) {
@@ -4236,6 +4237,7 @@ static ERL_NIF_TERM nif_context_create(ErlNifEnv *env, int argc, const ERL_NIF_T
42364237
ctx->globals = PyDict_New();
42374238
ctx->locals = PyDict_New();
42384239
ctx->module_cache = PyDict_New();
4240+
ctx->cache_generation = import_cache_get_generation();
42394241

42404242
/* Import __builtins__ into globals */
42414243
PyObject *builtins = PyEval_GetBuiltins();
@@ -4368,6 +4370,25 @@ static ERL_NIF_TERM nif_context_destroy(ErlNifEnv *env, int argc, const ERL_NIF_
43684370
* Helper function - no mutex needed since context is process-owned.
43694371
*/
43704372
static PyObject *context_get_module(py_context_t *ctx, const char *module_name) {
4373+
/* Check for stale cache (main interpreter contexts only) */
4374+
if (ctx->module_cache != NULL) {
4375+
#ifdef HAVE_SUBINTERPRETERS
4376+
/* Pool-backed contexts get fresh interpreters on flush, so only check
4377+
* for non-pool contexts (main interpreter or OWN_GIL) */
4378+
bool is_main_interp = (ctx->pool_slot < 0 && !ctx->uses_own_gil);
4379+
#else
4380+
bool is_main_interp = true; /* All contexts use main interpreter */
4381+
#endif
4382+
if (is_main_interp) {
4383+
uint64_t current_gen = import_cache_get_generation();
4384+
if (ctx->cache_generation != current_gen) {
4385+
/* Cache is stale - clear it and update generation */
4386+
PyDict_Clear(ctx->module_cache);
4387+
ctx->cache_generation = current_gen;
4388+
}
4389+
}
4390+
}
4391+
43714392
/* Check cache first */
43724393
if (ctx->module_cache != NULL) {
43734394
PyObject *cached = PyDict_GetItemString(ctx->module_cache, module_name);
@@ -5103,12 +5124,11 @@ static ERL_NIF_TERM nif_subinterp_pool_flush_generation(ErlNifEnv *env, int argc
51035124
(void)argc;
51045125
(void)argv;
51055126

5106-
#ifdef HAVE_SUBINTERPRETERS
5107-
uint64_t new_gen = subinterp_pool_flush_generation();
5127+
/* Use the unconditional version that works with or without subinterpreters.
5128+
* This increments the generation counter (used by main interpreter contexts)
5129+
* and also marks pool slots stale when subinterpreters are available. */
5130+
uint64_t new_gen = import_cache_flush_generation();
51085131
return enif_make_tuple2(env, ATOM_OK, enif_make_uint64(env, new_gen));
5109-
#else
5110-
return enif_make_tuple2(env, ATOM_OK, enif_make_uint64(env, 0));
5111-
#endif
51125132
}
51135133

51145134
/**

c_src/py_nif.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,9 @@ typedef struct {
888888

889889
/** @brief Module cache (Dict: module_name -> PyModule) */
890890
PyObject *module_cache;
891+
892+
/** @brief Cache generation for staleness detection (main interpreter contexts) */
893+
uint64_t cache_generation;
891894
} py_context_t;
892895

893896
/* ============================================================================

c_src/py_subinterp_pool.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,4 +540,30 @@ uint64_t subinterp_pool_get_generation(void) {
540540
return atomic_load(&g_import_generation);
541541
}
542542

543+
#else /* !HAVE_SUBINTERPRETERS */
544+
545+
/* For systems without subinterpreter support, we still need the generation
546+
* counter for main interpreter context cache invalidation */
547+
static _Atomic uint64_t g_import_generation_no_subinterp = 0;
548+
543549
#endif /* HAVE_SUBINTERPRETERS */
550+
551+
/* ============================================================================
552+
* Unconditional generation functions (work with or without subinterpreters)
553+
* ============================================================================ */
554+
555+
uint64_t import_cache_get_generation(void) {
556+
#ifdef HAVE_SUBINTERPRETERS
557+
return atomic_load(&g_import_generation);
558+
#else
559+
return atomic_load(&g_import_generation_no_subinterp);
560+
#endif
561+
}
562+
563+
uint64_t import_cache_flush_generation(void) {
564+
#ifdef HAVE_SUBINTERPRETERS
565+
return subinterp_pool_flush_generation();
566+
#else
567+
return atomic_fetch_add(&g_import_generation_no_subinterp, 1) + 1;
568+
#endif
569+
}

c_src/py_subinterp_pool.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,4 +211,24 @@ uint64_t subinterp_pool_get_generation(void);
211211

212212
#endif /* HAVE_SUBINTERPRETERS */
213213

214+
/**
215+
* @brief Get the current import generation (always available)
216+
*
217+
* This function is available regardless of subinterpreter support.
218+
* It's used by main interpreter contexts to detect cache staleness.
219+
*
220+
* @return Current generation number (0 if never flushed)
221+
*/
222+
uint64_t import_cache_get_generation(void);
223+
224+
/**
225+
* @brief Increment the import generation counter (always available)
226+
*
227+
* This function is available regardless of subinterpreter support.
228+
* For systems with subinterpreters, it also marks pool slots stale.
229+
*
230+
* @return The new generation number
231+
*/
232+
uint64_t import_cache_flush_generation(void);
233+
214234
#endif /* PY_SUBINTERP_POOL_H */

0 commit comments

Comments
 (0)