Skip to content

Commit c758da1

Browse files
committed
Make erlang.atom() available in OWN_GIL subinterpreters
Add atom() wrapper with caching directly in C module creation, so it's available in OWN_GIL contexts where _erlang_impl package isn't imported. - Add atom_wrapper_code in create_erlang_module() with: - _atom_cache dict for memoization - _MAX_USER_ATOMS limit (10000) to prevent unbounded growth - atom() function that wraps erlang._atom() with caching - Add 3 new tests: atom_create, atom_create_different, atom_cache - Add Python helpers for testing erlang.atom() in OWN_GIL mode
1 parent b227062 commit c758da1

3 files changed

Lines changed: 139 additions & 0 deletions

File tree

c_src/py_callback.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3958,6 +3958,63 @@ static int create_erlang_module(void) {
39583958
Py_DECREF(ext_globals);
39593959
}
39603960

3961+
/* Add atom() wrapper with caching for OWN_GIL subinterpreters.
3962+
* In OWN_GIL mode, the Python package (_erlang_impl) is not imported,
3963+
* so erlang.atom() isn't available. This adds it directly to the C module.
3964+
*/
3965+
const char *atom_wrapper_code =
3966+
"_atom_cache = {}\n"
3967+
"_MAX_USER_ATOMS = 10000\n"
3968+
"def atom(name):\n"
3969+
" '''Create or retrieve a cached atom.\n"
3970+
" \n"
3971+
" Args:\n"
3972+
" name: String name for the atom\n"
3973+
" \n"
3974+
" Returns:\n"
3975+
" An erlang.Atom object\n"
3976+
" \n"
3977+
" Raises:\n"
3978+
" RuntimeError: If atom limit (10000) is reached\n"
3979+
" '''\n"
3980+
" if name in _atom_cache:\n"
3981+
" return _atom_cache[name]\n"
3982+
" if len(_atom_cache) >= _MAX_USER_ATOMS:\n"
3983+
" raise RuntimeError('Atom limit reached')\n"
3984+
" import erlang\n"
3985+
" result = erlang._atom(name)\n"
3986+
" _atom_cache[name] = result\n"
3987+
" return result\n"
3988+
"\n"
3989+
"import erlang\n"
3990+
"erlang.atom = atom\n"
3991+
"erlang._atom_cache = _atom_cache\n";
3992+
3993+
PyObject *atom_globals = PyDict_New();
3994+
if (atom_globals != NULL) {
3995+
PyObject *builtins = PyEval_GetBuiltins();
3996+
PyDict_SetItemString(atom_globals, "__builtins__", builtins);
3997+
3998+
/* Import erlang module into globals so the code can reference it */
3999+
PyObject *sys_modules = PySys_GetObject("modules");
4000+
if (sys_modules != NULL) {
4001+
PyObject *erlang_mod = PyDict_GetItemString(sys_modules, "erlang");
4002+
if (erlang_mod != NULL) {
4003+
PyDict_SetItemString(atom_globals, "erlang", erlang_mod);
4004+
}
4005+
}
4006+
4007+
PyObject *result = PyRun_String(atom_wrapper_code, Py_file_input, atom_globals, atom_globals);
4008+
if (result == NULL) {
4009+
/* Non-fatal - atom() just won't be available */
4010+
PyErr_Print();
4011+
PyErr_Clear();
4012+
} else {
4013+
Py_DECREF(result);
4014+
}
4015+
Py_DECREF(atom_globals);
4016+
}
4017+
39614018
return 0;
39624019
}
39634020

test/py_owngil_features_SUITE.erl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@
104104
owngil_whereis_parallel_test/1,
105105
owngil_atom_basic_test/1,
106106
owngil_atom_roundtrip_test/1,
107+
owngil_atom_create_test/1,
108+
owngil_atom_create_different_test/1,
109+
owngil_atom_cache_test/1,
107110
owngil_ref_roundtrip_test/1,
108111
owngil_pid_operations_test/1
109112
]).
@@ -187,6 +190,9 @@ groups() ->
187190
owngil_whereis_parallel_test,
188191
owngil_atom_basic_test,
189192
owngil_atom_roundtrip_test,
193+
owngil_atom_create_test,
194+
owngil_atom_create_different_test,
195+
owngil_atom_cache_test,
190196
owngil_ref_roundtrip_test,
191197
owngil_pid_operations_test
192198
]}].
@@ -1660,6 +1666,49 @@ owngil_atom_roundtrip_test(Config) ->
16601666

16611667
py_context:stop(Ctx).
16621668

1669+
%% @doc Test erlang.atom() creates equal atoms in owngil context
1670+
owngil_atom_create_test(Config) ->
1671+
{ok, Ctx} = py_context:start_link(1, owngil),
1672+
TestDir = proplists:get_value(test_dir, Config),
1673+
1674+
ok = py_context:exec(Ctx, iolist_to_binary(io_lib:format(
1675+
"import sys; sys.path.insert(0, '~s')", [TestDir]))),
1676+
1677+
%% Test that erlang.atom() creates equal atoms for same name
1678+
{ok, true} = py_context:call(Ctx, py_test_pid_send, atom_create_test, [], #{}),
1679+
1680+
py_context:stop(Ctx).
1681+
1682+
%% @doc Test erlang.atom() creates unequal atoms for different names
1683+
owngil_atom_create_different_test(Config) ->
1684+
{ok, Ctx} = py_context:start_link(1, owngil),
1685+
TestDir = proplists:get_value(test_dir, Config),
1686+
1687+
ok = py_context:exec(Ctx, iolist_to_binary(io_lib:format(
1688+
"import sys; sys.path.insert(0, '~s')", [TestDir]))),
1689+
1690+
%% Test that erlang.atom() creates different atoms for different names
1691+
{ok, true} = py_context:call(Ctx, py_test_pid_send, atom_create_different_test, [], #{}),
1692+
1693+
py_context:stop(Ctx).
1694+
1695+
%% @doc Test erlang.atom() caching - same name returns same object
1696+
owngil_atom_cache_test(Config) ->
1697+
{ok, Ctx} = py_context:start_link(1, owngil),
1698+
TestDir = proplists:get_value(test_dir, Config),
1699+
1700+
ok = py_context:exec(Ctx, iolist_to_binary(io_lib:format(
1701+
"import sys; sys.path.insert(0, '~s')", [TestDir]))),
1702+
1703+
%% Test atom caching - same object returned for same name
1704+
{ok, true} = py_context:call(Ctx, py_test_pid_send, atom_cache_test, [], #{}),
1705+
1706+
%% Also verify we can get the type name
1707+
{ok, TypeName} = py_context:call(Ctx, py_test_pid_send, atom_type_name, [], #{}),
1708+
ct:pal("Atom type name from erlang.atom(): ~p", [TypeName]),
1709+
1710+
py_context:stop(Ctx).
1711+
16631712
%% @doc Ref type check and uniqueness in owngil context
16641713
owngil_ref_roundtrip_test(Config) ->
16651714
{ok, Ctx} = py_context:start_link(1, owngil),

test/py_test_pid_send.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,36 @@ def pid_in_set(pid):
249249
"""Test using PID in a set."""
250250
s = {pid}
251251
return pid in s
252+
253+
254+
# OWN_GIL test helpers for erlang.atom() creation
255+
256+
def atom_create_test():
257+
"""Create atom using erlang.atom() in OWN_GIL and test equality."""
258+
import erlang
259+
a1 = erlang.atom('test_owngil_atom')
260+
a2 = erlang.atom('test_owngil_atom')
261+
return a1 == a2
262+
263+
264+
def atom_create_different_test():
265+
"""Test different atoms created with erlang.atom() are not equal."""
266+
import erlang
267+
a1 = erlang.atom('atom_x')
268+
a2 = erlang.atom('atom_y')
269+
return a1 != a2
270+
271+
272+
def atom_cache_test():
273+
"""Test atom caching - same name returns same object."""
274+
import erlang
275+
a1 = erlang.atom('cached_atom_owngil')
276+
a2 = erlang.atom('cached_atom_owngil')
277+
return a1 is a2 # Should be same object due to caching
278+
279+
280+
def atom_type_name():
281+
"""Get type name of atom created with erlang.atom()."""
282+
import erlang
283+
a = erlang.atom('type_check_atom')
284+
return type(a).__name__

0 commit comments

Comments
 (0)