Skip to content

Commit 854444c

Browse files
committed
Add thread-local namespace for reentrant calls
1 parent 06c986d commit 854444c

3 files changed

Lines changed: 61 additions & 2 deletions

File tree

c_src/py_event_loop.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ ErlNifResourceType *TIMER_RESOURCE_TYPE = NULL;
5555
static char g_priv_dir[1024] = {0};
5656
static bool g_priv_dir_set = false;
5757

58+
/**
59+
* Thread-local for current event loop namespace during task execution.
60+
* This allows reentrant calls (erlang.call -> Python) to use the same namespace.
61+
*/
62+
__thread process_namespace_t *tl_current_event_loop_namespace = NULL;
63+
5864
/** Atoms for event loop messages */
5965
ERL_NIF_TERM ATOM_SELECT;
6066
ERL_NIF_TERM ATOM_READY_INPUT;
@@ -2736,8 +2742,16 @@ ERL_NIF_TERM nif_process_ready_tasks(ErlNifEnv *env, int argc,
27362742
kwargs = term_to_py(term_env, tuple_elems[5]);
27372743
}
27382744

2745+
/* Set current namespace for reentrant calls (erlang.call -> Python) */
2746+
process_namespace_t *prev_namespace = tl_current_event_loop_namespace;
2747+
tl_current_event_loop_namespace = ns;
2748+
27392749
/* Call the function to get coroutine */
27402750
PyObject *coro = PyObject_Call(func, args, kwargs);
2751+
2752+
/* Restore previous namespace */
2753+
tl_current_event_loop_namespace = prev_namespace;
2754+
27412755
Py_DECREF(func);
27422756
Py_DECREF(args);
27432757
Py_XDECREF(kwargs);

c_src/py_event_loop.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,15 @@ extern ErlNifResourceType *FD_RESOURCE_TYPE;
387387
/** @brief Resource type for timer_resource_t */
388388
extern ErlNifResourceType *TIMER_RESOURCE_TYPE;
389389

390+
/**
391+
* @brief Current event loop namespace for reentrant calls
392+
*
393+
* Set during task execution in process_ready_tasks. Used by erlang.call()
394+
* to access the same namespace when Python calls back to Erlang and
395+
* Erlang calls back to Python.
396+
*/
397+
extern __thread process_namespace_t *tl_current_event_loop_namespace;
398+
390399
/* ============================================================================
391400
* Atom Declarations
392401
* ============================================================================ */

test/py_async_task_SUITE.erl

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
test_process_namespace_exec/1,
3737
test_process_namespace_eval/1,
3838
test_process_namespace_async_func/1,
39-
test_process_namespace_isolation/1
39+
test_process_namespace_isolation/1,
40+
test_process_namespace_reentrant/1
4041
]).
4142

4243
all() ->
@@ -72,7 +73,8 @@ all() ->
7273
test_process_namespace_exec,
7374
test_process_namespace_eval,
7475
test_process_namespace_async_func,
75-
test_process_namespace_isolation
76+
test_process_namespace_isolation,
77+
test_process_namespace_reentrant
7678
].
7779

7880
groups() -> [].
@@ -435,3 +437,37 @@ test_process_namespace_isolation(_Config) ->
435437
{ok, _} ->
436438
ct:log("isolation test: parent unexpectedly saw child_var")
437439
end.
440+
441+
test_process_namespace_reentrant(_Config) ->
442+
%% Test that namespace variables are accessible during task execution
443+
%% This verifies the thread-local namespace is set correctly
444+
445+
%% Define a variable and a function that uses it
446+
ok = py_event_loop:exec(<<"
447+
shared_value = 100
448+
449+
def use_shared():
450+
# Access shared_value from namespace
451+
return shared_value + 23
452+
">>),
453+
454+
%% Call the function via create_task - it should access the namespace
455+
Ref = py_event_loop:create_task('__main__', use_shared, []),
456+
{ok, Result} = py_event_loop:await(Ref, 5000),
457+
ct:log("reentrant test: use_shared() returned ~p (expected 123)", [Result]),
458+
123 = Result,
459+
460+
%% Test with a function that modifies namespace
461+
ok = py_event_loop:exec(<<"
462+
def increment_shared():
463+
global shared_value
464+
shared_value += 1
465+
return shared_value
466+
">>),
467+
468+
Ref2 = py_event_loop:create_task('__main__', increment_shared, []),
469+
{ok, 101} = py_event_loop:await(Ref2, 5000),
470+
471+
%% Verify the change persists in namespace
472+
{ok, 101} = py_event_loop:eval(<<"shared_value">>),
473+
ct:log("reentrant test: namespace modifications persist correctly").

0 commit comments

Comments
 (0)