mirror of https://github.com/python/cpython
gh-77377: Ensure multiprocessing SemLock is valid for spawn-based Process before serializing it (#107275)
Ensure multiprocessing SemLock is valid for spawn Process before serializing it. Creating a multiprocessing SemLock with a fork context, and then trying to pass it to a spawn-created Process, would segfault if not detected early. --------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Antoine Pitrou <pitrou@free.fr>
This commit is contained in:
parent
5d18715765
commit
1700d34d31
|
@ -50,8 +50,8 @@ class SemLock(object):
|
||||||
def __init__(self, kind, value, maxvalue, *, ctx):
|
def __init__(self, kind, value, maxvalue, *, ctx):
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
ctx = context._default_context.get_context()
|
ctx = context._default_context.get_context()
|
||||||
name = ctx.get_start_method()
|
self.is_fork_ctx = ctx.get_start_method() == 'fork'
|
||||||
unlink_now = sys.platform == 'win32' or name == 'fork'
|
unlink_now = sys.platform == 'win32' or self.is_fork_ctx
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
try:
|
try:
|
||||||
sl = self._semlock = _multiprocessing.SemLock(
|
sl = self._semlock = _multiprocessing.SemLock(
|
||||||
|
@ -103,6 +103,11 @@ class SemLock(object):
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
h = context.get_spawning_popen().duplicate_for_child(sl.handle)
|
h = context.get_spawning_popen().duplicate_for_child(sl.handle)
|
||||||
else:
|
else:
|
||||||
|
if self.is_fork_ctx:
|
||||||
|
raise RuntimeError('A SemLock created in a fork context is being '
|
||||||
|
'shared with a process in a spawn context. This is '
|
||||||
|
'not supported. Please use the same context to create '
|
||||||
|
'multiprocessing objects and Process.')
|
||||||
h = sl.handle
|
h = sl.handle
|
||||||
return (h, sl.kind, sl.maxvalue, sl.name)
|
return (h, sl.kind, sl.maxvalue, sl.name)
|
||||||
|
|
||||||
|
|
|
@ -5421,6 +5421,28 @@ class TestStartMethod(unittest.TestCase):
|
||||||
print(err)
|
print(err)
|
||||||
self.fail("failed spawning forkserver or grandchild")
|
self.fail("failed spawning forkserver or grandchild")
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.platform == "win32",
|
||||||
|
"Only Spawn on windows so no risk of mixing")
|
||||||
|
@only_run_in_spawn_testsuite("avoids redundant testing.")
|
||||||
|
def test_mixed_startmethod(self):
|
||||||
|
# Fork-based locks cannot be used with spawned process
|
||||||
|
for process_method in ["spawn", "forkserver"]:
|
||||||
|
queue = multiprocessing.get_context("fork").Queue()
|
||||||
|
process_ctx = multiprocessing.get_context(process_method)
|
||||||
|
p = process_ctx.Process(target=close_queue, args=(queue,))
|
||||||
|
err_msg = "A SemLock created in a fork"
|
||||||
|
with self.assertRaisesRegex(RuntimeError, err_msg):
|
||||||
|
p.start()
|
||||||
|
|
||||||
|
# non-fork-based locks can be used with all other start methods
|
||||||
|
for queue_method in ["spawn", "forkserver"]:
|
||||||
|
for process_method in multiprocessing.get_all_start_methods():
|
||||||
|
queue = multiprocessing.get_context(queue_method).Queue()
|
||||||
|
process_ctx = multiprocessing.get_context(process_method)
|
||||||
|
p = process_ctx.Process(target=close_queue, args=(queue,))
|
||||||
|
p.start()
|
||||||
|
p.join()
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.platform == "win32",
|
@unittest.skipIf(sys.platform == "win32",
|
||||||
"test semantics don't make sense on Windows")
|
"test semantics don't make sense on Windows")
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Ensure that multiprocessing synchronization objects created in a fork context are not sent to a different process created in a spawn context. This changes a segfault into an actionable RuntimeError in the parent process.
|
Loading…
Reference in New Issue