Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b2ddf79

Browse files
committed
Issue #9573: os.fork now works when triggered as a side effect of import (the wisdom of actually relying on this remains questionable!)
1 parent d2bb830 commit b2ddf79

3 files changed

Lines changed: 57 additions & 5 deletions

File tree

Lib/test/test_fork1.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
import time
99

1010
from test.fork_wait import ForkWait
11-
from test.support import run_unittest, reap_children, get_attribute, import_module
11+
from test.support import (run_unittest, reap_children, get_attribute,
12+
import_module, verbose)
13+
1214
threading = import_module('threading')
1315

1416
# Skip test if fork does not exist.
1517
get_attribute(os, 'fork')
1618

17-
1819
class ForkTest(ForkWait):
1920
def wait_impl(self, cpid):
2021
for i in range(10):
@@ -28,7 +29,8 @@ def wait_impl(self, cpid):
2829
self.assertEqual(spid, cpid)
2930
self.assertEqual(status, 0, "cause = %d, exit = %d" % (status&0xff, status>>8))
3031

31-
def test_import_lock_fork(self):
32+
def test_threaded_import_lock_fork(self):
33+
"""Check fork() in main thread works while a subthread is doing an import"""
3234
import_started = threading.Event()
3335
fake_module_name = "fake test module"
3436
partial_module = "partial"
@@ -45,11 +47,16 @@ def importer():
4547
import_started.wait()
4648
pid = os.fork()
4749
try:
50+
# PyOS_BeforeFork should have waited for the import to complete
51+
# before forking, so the child can recreate the import lock
52+
# correctly, but also won't see a partially initialised module
4853
if not pid:
4954
m = __import__(fake_module_name)
5055
if m == complete_module:
5156
os._exit(0)
5257
else:
58+
if verbose > 1:
59+
print("Child encountered partial module")
5360
os._exit(1)
5461
else:
5562
t.join()
@@ -63,6 +70,39 @@ def importer():
6370
except OSError:
6471
pass
6572

73+
74+
def test_nested_import_lock_fork(self):
75+
"""Check fork() in main thread works while the main thread is doing an import"""
76+
# Issue 9573: this used to trigger RuntimeError in the child process
77+
def fork_with_import_lock(level):
78+
release = 0
79+
in_child = False
80+
try:
81+
try:
82+
for i in range(level):
83+
imp.acquire_lock()
84+
release += 1
85+
pid = os.fork()
86+
in_child = not pid
87+
finally:
88+
for i in range(release):
89+
imp.release_lock()
90+
except RuntimeError:
91+
if in_child:
92+
if verbose > 1:
93+
print("RuntimeError in child")
94+
os._exit(1)
95+
raise
96+
if in_child:
97+
os._exit(0)
98+
self.wait_impl(pid)
99+
100+
# Check this works with various levels of nested
101+
# import in the main thread
102+
for level in range(5):
103+
fork_with_import_lock(level)
104+
105+
66106
def test_main():
67107
run_unittest(ForkTest)
68108
reap_children()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ Core and Builtins
4646
Library
4747
-------
4848

49+
- Issue #9573: os.fork() now works correctly when triggered as a side effect
50+
of a module import
51+
4952
- Issue #10464: netrc now correctly handles lines with embedded '#' characters.
5053

5154
- Added itertools.accumulate().

Python/import.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,17 @@ _PyImport_ReInitLock(void)
325325
{
326326
if (import_lock != NULL)
327327
import_lock = PyThread_allocate_lock();
328-
import_lock_thread = -1;
329-
import_lock_level = 0;
328+
if (import_lock_level > 1) {
329+
/* Forked as a side effect of import */
330+
long me = PyThread_get_thread_ident();
331+
PyThread_acquire_lock(import_lock, 0);
332+
/* XXX: can the previous line fail? */
333+
import_lock_thread = me;
334+
import_lock_level--;
335+
} else {
336+
import_lock_thread = -1;
337+
import_lock_level = 0;
338+
}
330339
}
331340

332341
#endif

0 commit comments

Comments
 (0)