-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Add per-interpreter storage for gil_safe_call_once_and_store
#5933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
58834ac to
a46973b
Compare
a46973b to
e741760
Compare
8e5bb6e to
d5b8813
Compare
3a6ec0e to
0830872
Compare
0830872 to
ac02a32
Compare
|
I have updated the approach to move the storage map as a separate The CI tests are looking good so far. But I found some concurrency bugs for this patch in my downstream PR metaopt/optree#245. I'm not sure if it is coming from I will do further investigation about these. Bug 1: Duplicate native enum registration. ImportError: pybind11::native_enum<...>("PyTreeKind") is already registered!Test Casedef test_import_in_subinterpreter_before_main():
# OK
check_script_in_subprocess(
textwrap.dedent(
"""
import contextlib
import gc
from concurrent import interpreters
subinterpreter = None
with contextlib.closing(interpreters.create()) as subinterpreter:
subinterpreter.exec('import optree')
import optree
del optree, subinterpreter
for _ in range(10):
gc.collect()
""",
).strip(),
output='',
rerun=NUM_FLAKY_RERUNS,
)
# FAIL
check_script_in_subprocess(
textwrap.dedent(
f"""
import contextlib
import gc
import random
from concurrent import interpreters
subinterpreter = subinterpreters = stack = None
with contextlib.ExitStack() as stack:
subinterpreters = [
stack.enter_context(contextlib.closing(interpreters.create()))
for _ in range({NUM_FUTURES})
]
random.shuffle(subinterpreters)
for subinterpreter in subinterpreters:
subinterpreter.exec('import optree')
import optree
del optree, subinterpreter, subinterpreters, stack
for _ in range(10):
gc.collect()
""",
).strip(),
output='',
rerun=NUM_FLAKY_RERUNS,
)
# FAIL
check_script_in_subprocess(
textwrap.dedent(
f"""
import contextlib
import gc
import random
from concurrent import interpreters
subinterpreter = subinterpreters = stack = None
with contextlib.ExitStack() as stack:
subinterpreters = [
stack.enter_context(contextlib.closing(interpreters.create()))
for _ in range({NUM_FUTURES})
]
random.shuffle(subinterpreters)
for subinterpreter in subinterpreters:
subinterpreter.exec('import optree')
import optree
del optree, subinterpreter, subinterpreters, stack
for _ in range(10):
gc.collect()
""",
).strip(),
output='',
rerun=NUM_FLAKY_RERUNS,
)Traceback__________________________________________________________________ test_import_in_subinterpreter_before_main ___________________________________________________________________
def test_import_in_subinterpreter_before_main():
check_script_in_subprocess(
textwrap.dedent(
"""
import contextlib
import gc
from concurrent import interpreters
subinterpreter = None
with contextlib.closing(interpreters.create()) as subinterpreter:
subinterpreter.exec('import optree')
import optree
del optree, subinterpreter
for _ in range(10):
gc.collect()
""",
).strip(),
output='',
rerun=NUM_FLAKY_RERUNS,
)
> check_script_in_subprocess(
textwrap.dedent(
f"""
import contextlib
import gc
import random
from concurrent import interpreters
subinterpreter = subinterpreters = stack = None
with contextlib.ExitStack() as stack:
subinterpreters = [
stack.enter_context(contextlib.closing(interpreters.create()))
for _ in range({NUM_FUTURES})
]
random.shuffle(subinterpreters)
for subinterpreter in subinterpreters:
subinterpreter.exec('import optree')
import optree
del optree, subinterpreter, subinterpreters, stack
for _ in range(10):
gc.collect()
""",
).strip(),
output='',
rerun=NUM_FLAKY_RERUNS,
)
concurrent/test_subinterpreters.py:267:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
script = "import contextlib\nimport gc\nimport random\nfrom concurrent import interpreters\n\nsubinterpreter = subinterpreters ...optree')\n\nimport optree\n\ndel optree, subinterpreter, subinterpreters, stack\nfor _ in range(10):\n gc.collect()"
def check_script_in_subprocess(script, /, *, output, env=None, cwd=TEST_ROOT, rerun=1):
result = ''
for _ in range(rerun):
try:
result = subprocess.check_output(
[sys.executable, '-Walways', '-Werror', '-c', script],
stderr=subprocess.STDOUT,
text=True,
encoding='utf-8',
cwd=cwd,
env={
key: value
for key, value in (env if env is not None else os.environ).items()
if not key.startswith(('PYTHON', 'PYTEST', 'COV_'))
},
)
except subprocess.CalledProcessError as ex:
> raise CalledProcessError(ex.returncode, ex.cmd, ex.output, ex.stderr) from None
E helpers.CalledProcessError: Command '['/home/PanXuehai/Projects/optree/venv/bin/python3', '-Walways', '-Werror', '-c', "import contextlib\nimport gc\nimport random\nfrom concurrent import interpreters\n\nsubinterpreter = subinterpreters = stack = None\nwith contextlib.ExitStack() as stack:\n subinterpreters = [\n stack.enter_context(contextlib.closing(interpreters.create()))\n for _ in range(16)\n ]\n random.shuffle(subinterpreters)\n for subinterpreter in subinterpreters:\n subinterpreter.exec('import optree')\n\nimport optree\n\ndel optree, subinterpreter, subinterpreters, stack\nfor _ in range(10):\n gc.collect()"]' returned non-zero exit status 1.
E Output:
E Traceback (most recent call last):
E File "<string>", line 16, in <module>
E import optree
E File "/home/PanXuehai/Projects/optree/optree/__init__.py", line 17, in <module>
E from optree import accessors, dataclasses, functools, integrations, pytree, treespec, typing
E File "/home/PanXuehai/Projects/optree/optree/accessors.py", line 25, in <module>
E import optree._C as _C
E ImportError: pybind11::native_enum<...>("PyTreeKind") is already registered!
_ = 0
cwd = PosixPath('/home/PanXuehai/Projects/optree/tests')
env = None
output = ''
rerun = 8
result = ''
script = "import contextlib\nimport gc\nimport random\nfrom concurrent import interpreters\n\nsubinterpreter = subinterpreters ...optree')\n\nimport optree\n\ndel optree, subinterpreter, subinterpreters, stack\nfor _ in range(10):\n gc.collect()"
helpers.py:187: CalledProcessErrorBug 2: Thread-safety issues for multiple-interpreters are ever created.
|
Description
Fixes #5926
For
!defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT), the previous behavior is unchanged.For
defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT), the pybind11internalsis already a per-interpreter-dependent singleton stored in the interpreter's state dict. This PR adds a new mapto the interpreter-dependentfrom theinternalsgil_safe_call_once_and_store's address to the result, where thegil_safe_call_once_and_storeis a static object that has a fixed address.UPDATE: The storage map is moved to a separate
capsulein the per-interpreter state dict instead of putting it as a member ofinternals. Because the per-interpreterinternalsare leaked in the program, the storage map is never GC-ed on (sub-)interpreter shutdown.Suggested changelog entry:
gil_safe_call_once_and_storeto make it safe under multi-interpreters.