Skip to content

Commit 322aa68

Browse files
authored
add support for os.fork and related functions (RustPython#4877)
1 parent 4e0890a commit 322aa68

File tree

14 files changed

+213
-12
lines changed

14 files changed

+213
-12
lines changed

Lib/concurrent/futures/thread.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def _python_exit():
3737
threading._register_atexit(_python_exit)
3838

3939
# At fork, reinitialize the `_global_shutdown_lock` lock in the child process
40-
if hasattr(os, 'register_at_fork'):
40+
# TODO RUSTPYTHON - _at_fork_reinit is not implemented yet
41+
if hasattr(os, 'register_at_fork') and hasattr(_global_shutdown_lock, '_at_fork_reinit'):
4142
os.register_at_fork(before=_global_shutdown_lock.acquire,
4243
after_in_child=_global_shutdown_lock._at_fork_reinit,
4344
after_in_parent=_global_shutdown_lock.release)

Lib/test/test_builtin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
except ImportError:
3838
pty = signal = None
3939

40+
import threading # XXX: RUSTPYTHON; to skip _at_fork_reinit
41+
4042

4143
class Squares:
4244

@@ -2269,6 +2271,7 @@ def test_input_tty_non_ascii_unicode_errors(self):
22692271
# Check stdin/stdout error handler is used when invoking PyOS_Readline()
22702272
self.check_input_tty("prompté", b"quux\xe9", "ascii")
22712273

2274+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
22722275
def test_input_no_stdout_fileno(self):
22732276
# Issue #24402: If stdin is the original terminal but stdout.fileno()
22742277
# fails, do not use the original stdout file descriptor

Lib/test/test_fcntl.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import struct
66
import sys
7+
import threading # XXX: RUSTPYTHON
78
import unittest
89
from multiprocessing import Process
910
from test.support import verbose, cpython_only
@@ -155,8 +156,9 @@ def test_flock(self):
155156
self.assertRaises(ValueError, fcntl.flock, -1, fcntl.LOCK_SH)
156157
self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH)
157158

158-
# TODO: RUSTPYTHON, AttributeError: module 'os' has no attribute 'fork'
159+
# TODO: RUSTPYTHON
159160
@unittest.expectedFailure
161+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
160162
@unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
161163
def test_lockf_exclusive(self):
162164
self.f = open(TESTFN, 'wb+')
@@ -167,9 +169,9 @@ def test_lockf_exclusive(self):
167169
p.join()
168170
fcntl.lockf(self.f, fcntl.LOCK_UN)
169171
self.assertEqual(p.exitcode, 0)
170-
171-
# TODO: RUSTPYTHON, AttributeError: module 'os' has no attribute 'fork'
172+
# TODO: RUSTPYTHON
172173
@unittest.expectedFailure
174+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
173175
@unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
174176
def test_lockf_share(self):
175177
self.f = open(TESTFN, 'wb+')

Lib/test/test_httpservers.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,8 @@ def test_html_escape_filename(self):
607607
print("</pre>")
608608
"""
609609

610-
610+
@unittest.skipIf(not hasattr(os, '_exit'),
611+
"TODO: RUSTPYTHON, run_cgi in http/server.py gets stuck as os._exit(127) doesn't currently kill forked processes")
611612
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
612613
"This test can't be run reliably as root (issue #13308).")
613614
class CGIHTTPServerTestCase(BaseTestCase):
@@ -765,6 +766,7 @@ def test_url_collapse_path(self):
765766
msg='path = %r\nGot: %r\nWanted: %r' %
766767
(path, actual, expected))
767768

769+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
768770
def test_headers_and_content(self):
769771
res = self.request('/cgi-bin/file1.py')
770772
self.assertEqual(
@@ -775,6 +777,7 @@ def test_issue19435(self):
775777
res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh')
776778
self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
777779

780+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
778781
def test_post(self):
779782
params = urllib.parse.urlencode(
780783
{'spam' : 1, 'eggs' : 'python', 'bacon' : 123456})
@@ -788,6 +791,7 @@ def test_invaliduri(self):
788791
res.read()
789792
self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
790793

794+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
791795
def test_authorization(self):
792796
headers = {b'Authorization' : b'Basic ' +
793797
base64.b64encode(b'username:pass')}
@@ -796,13 +800,15 @@ def test_authorization(self):
796800
(b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
797801
(res.read(), res.getheader('Content-type'), res.status))
798802

803+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
799804
def test_no_leading_slash(self):
800805
# http://bugs.python.org/issue2254
801806
res = self.request('cgi-bin/file1.py')
802807
self.assertEqual(
803808
(b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
804809
(res.read(), res.getheader('Content-type'), res.status))
805810

811+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
806812
def test_os_environ_is_not_altered(self):
807813
signature = "Test CGI Server"
808814
os.environ['SERVER_SOFTWARE'] = signature
@@ -812,31 +818,36 @@ def test_os_environ_is_not_altered(self):
812818
(res.read(), res.getheader('Content-type'), res.status))
813819
self.assertEqual(os.environ['SERVER_SOFTWARE'], signature)
814820

821+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
815822
def test_urlquote_decoding_in_cgi_check(self):
816823
res = self.request('/cgi-bin%2ffile1.py')
817824
self.assertEqual(
818825
(b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
819826
(res.read(), res.getheader('Content-type'), res.status))
820827

828+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
821829
def test_nested_cgi_path_issue21323(self):
822830
res = self.request('/cgi-bin/child-dir/file3.py')
823831
self.assertEqual(
824832
(b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
825833
(res.read(), res.getheader('Content-type'), res.status))
826834

835+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
827836
def test_query_with_multiple_question_mark(self):
828837
res = self.request('/cgi-bin/file4.py?a=b?c=d')
829838
self.assertEqual(
830839
(b'a=b?c=d' + self.linesep, 'text/html', HTTPStatus.OK),
831840
(res.read(), res.getheader('Content-type'), res.status))
832841

842+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
833843
def test_query_with_continuous_slashes(self):
834844
res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//')
835845
self.assertEqual(
836846
(b'k=aa%2F%2Fbb&//q//p//=//a//b//' + self.linesep,
837847
'text/html', HTTPStatus.OK),
838848
(res.read(), res.getheader('Content-type'), res.status))
839849

850+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
840851
def test_cgi_path_in_sub_directories(self):
841852
try:
842853
CGIHTTPRequestHandler.cgi_directories.append('/sub/dir/cgi-bin')
@@ -847,6 +858,7 @@ def test_cgi_path_in_sub_directories(self):
847858
finally:
848859
CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin')
849860

861+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
850862
def test_accept(self):
851863
browser_accept = \
852864
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'

Lib/test/test_os.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@
6969
except ImportError:
7070
INT_MAX = PY_SSIZE_T_MAX = sys.maxsize
7171

72+
try:
73+
import _testcapi
74+
except ImportError:
75+
_testcapi = None
7276

7377
from test.support.script_helper import assert_python_ok
7478
from test.support import unix_shell
@@ -3067,11 +3071,13 @@ def check_waitpid(self, code, exitcode, callback=None):
30673071
self.assertEqual(pid2, pid)
30683072

30693073
# TODO: RUSTPYTHON (AttributeError: module 'os' has no attribute 'spawnv')
3074+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
30703075
@unittest.expectedFailure
30713076
def test_waitpid(self):
30723077
self.check_waitpid(code='pass', exitcode=0)
30733078

30743079
# TODO: RUSTPYTHON (AttributeError: module 'os' has no attribute 'spawnv')
3080+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
30753081
@unittest.expectedFailure
30763082
def test_waitstatus_to_exitcode(self):
30773083
exitcode = 23
@@ -3103,7 +3109,8 @@ def test_waitstatus_to_exitcode_windows(self):
31033109
os.waitstatus_to_exitcode((max_exitcode + 1) << 8)
31043110
with self.assertRaises(OverflowError):
31053111
os.waitstatus_to_exitcode(-1)
3106-
3112+
3113+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
31073114
# TODO: RUSTPYTHON (AttributeError: module 'os' has no attribute 'spawnv')
31083115
@unittest.expectedFailure
31093116
# Skip the test on Windows
@@ -3146,31 +3153,36 @@ def create_args(self, *, with_env=False, use_bytes=False):
31463153
for k, v in self.env.items()}
31473154

31483155
return args
3149-
3156+
3157+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
31503158
@requires_os_func('spawnl')
31513159
def test_spawnl(self):
31523160
args = self.create_args()
31533161
exitcode = os.spawnl(os.P_WAIT, args[0], *args)
31543162
self.assertEqual(exitcode, self.exitcode)
31553163

3164+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
31563165
@requires_os_func('spawnle')
31573166
def test_spawnle(self):
31583167
args = self.create_args(with_env=True)
31593168
exitcode = os.spawnle(os.P_WAIT, args[0], *args, self.env)
31603169
self.assertEqual(exitcode, self.exitcode)
31613170

3171+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
31623172
@requires_os_func('spawnlp')
31633173
def test_spawnlp(self):
31643174
args = self.create_args()
31653175
exitcode = os.spawnlp(os.P_WAIT, args[0], *args)
31663176
self.assertEqual(exitcode, self.exitcode)
31673177

3178+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
31683179
@requires_os_func('spawnlpe')
31693180
def test_spawnlpe(self):
31703181
args = self.create_args(with_env=True)
31713182
exitcode = os.spawnlpe(os.P_WAIT, args[0], *args, self.env)
31723183
self.assertEqual(exitcode, self.exitcode)
31733184

3185+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
31743186
@requires_os_func('spawnv')
31753187
def test_spawnv(self):
31763188
args = self.create_args()
@@ -3181,30 +3193,35 @@ def test_spawnv(self):
31813193
exitcode = os.spawnv(os.P_WAIT, FakePath(args[0]), args)
31823194
self.assertEqual(exitcode, self.exitcode)
31833195

3196+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
31843197
@requires_os_func('spawnve')
31853198
def test_spawnve(self):
31863199
args = self.create_args(with_env=True)
31873200
exitcode = os.spawnve(os.P_WAIT, args[0], args, self.env)
31883201
self.assertEqual(exitcode, self.exitcode)
31893202

3203+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
31903204
@requires_os_func('spawnvp')
31913205
def test_spawnvp(self):
31923206
args = self.create_args()
31933207
exitcode = os.spawnvp(os.P_WAIT, args[0], args)
31943208
self.assertEqual(exitcode, self.exitcode)
31953209

3210+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
31963211
@requires_os_func('spawnvpe')
31973212
def test_spawnvpe(self):
31983213
args = self.create_args(with_env=True)
31993214
exitcode = os.spawnvpe(os.P_WAIT, args[0], args, self.env)
32003215
self.assertEqual(exitcode, self.exitcode)
32013216

3217+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
32023218
@requires_os_func('spawnv')
32033219
def test_nowait(self):
32043220
args = self.create_args()
32053221
pid = os.spawnv(os.P_NOWAIT, args[0], args)
32063222
support.wait_process(pid, exitcode=self.exitcode)
32073223

3224+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
32083225
@requires_os_func('spawnve')
32093226
def test_spawnve_bytes(self):
32103227
# Test bytes handling in parse_arglist and parse_envlist (#28114)
@@ -3286,10 +3303,12 @@ def _test_invalid_env(self, spawn):
32863303
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
32873304
self.assertEqual(exitcode, 0)
32883305

3306+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
32893307
@requires_os_func('spawnve')
32903308
def test_spawnve_invalid_env(self):
32913309
self._test_invalid_env(os.spawnve)
32923310

3311+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
32933312
@requires_os_func('spawnvpe')
32943313
def test_spawnvpe_invalid_env(self):
32953314
self._test_invalid_env(os.spawnvpe)
@@ -4660,6 +4679,35 @@ def test_fork(self):
46604679
assert_python_ok("-c", code)
46614680
assert_python_ok("-c", code, PYTHONMALLOC="malloc_debug")
46624681

4682+
@unittest.skipIf(_testcapi is None, 'TODO: RUSTPYTHON; needs _testcapi')
4683+
@unittest.skipUnless(sys.platform in ("linux", "darwin"),
4684+
"Only Linux and macOS detect this today.")
4685+
def test_fork_warns_when_non_python_thread_exists(self):
4686+
code = """if 1:
4687+
import os, threading, warnings
4688+
from _testcapi import _spawn_pthread_waiter, _end_spawned_pthread
4689+
_spawn_pthread_waiter()
4690+
try:
4691+
with warnings.catch_warnings(record=True) as ws:
4692+
warnings.filterwarnings(
4693+
"always", category=DeprecationWarning)
4694+
if os.fork() == 0:
4695+
assert not ws, f"unexpected warnings in child: {ws}"
4696+
os._exit(0) # child
4697+
else:
4698+
assert ws[0].category == DeprecationWarning, ws[0]
4699+
assert 'fork' in str(ws[0].message), ws[0]
4700+
# Waiting allows an error in the child to hit stderr.
4701+
exitcode = os.wait()[1]
4702+
assert exitcode == 0, f"child exited {exitcode}"
4703+
assert threading.active_count() == 1, threading.enumerate()
4704+
finally:
4705+
_end_spawned_pthread()
4706+
"""
4707+
_, out, err = assert_python_ok("-c", code, PYTHONOPTIMIZE='0')
4708+
self.assertEqual(err.decode("utf-8"), "")
4709+
self.assertEqual(out.decode("utf-8"), "")
4710+
46634711

46644712
# Only test if the C version is provided, otherwise TestPEP519 already tested
46654713
# the pure Python implementation.

Lib/test/test_platform.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,6 @@ def test_mac_ver(self):
346346
else:
347347
self.assertEqual(res[2], 'PowerPC')
348348

349-
# TODO: RUSTPYTHON
350-
@unittest.expectedFailure
351349
@unittest.skipUnless(sys.platform == 'darwin', "OSX only test")
352350
def test_mac_ver_with_fork(self):
353351
# Issue7895: platform.mac_ver() crashes when using fork without exec

Lib/test/test_pty.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import threading # XXX: RUSTPYTHON
12
from test.support import verbose, reap_children
23
from test.support.import_helper import import_module
34

@@ -211,6 +212,7 @@ def test_openpty(self):
211212
self.assertEqual(b'For my pet fish, Eric.\n', normalize_output(s2))
212213

213214
# TODO: RUSTPYTHON
215+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
214216
@unittest.expectedFailure
215217
def test_fork(self):
216218
debug("calling pty.fork()")
@@ -314,6 +316,7 @@ def test_master_read(self):
314316
self.assertEqual(data, b"")
315317

316318
# TODO: RUSTPYTHON; no os.fork
319+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
317320
@unittest.expectedFailure
318321
def test_spawn_doesnt_hang(self):
319322
pty.spawn([sys.executable, '-c', 'print("hi there")'])

Lib/test/test_socketserver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class ForkingUnixDatagramServer(socketserver.ForkingMixIn,
5555
socketserver.UnixDatagramServer):
5656
pass
5757

58-
58+
@test.support.requires_fork() # TODO: RUSTPYTHON, os.fork is currently only supported on Unix-based systems
5959
@contextlib.contextmanager
6060
def simple_subprocess(testcase):
6161
"""Tests that a custom child process is not waited on (Issue 1540386)"""

Lib/test/test_support.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import sys
1010
import tempfile
1111
import textwrap
12+
import threading # XXX: RUSTPYTHON
1213
import time
1314
import unittest
1415
import warnings
@@ -453,6 +454,7 @@ def test_check__all__(self):
453454

454455
# TODO: RUSTPYTHON
455456
@unittest.expectedFailure
457+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
456458
@unittest.skipUnless(hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG'),
457459
'need os.waitpid() and os.WNOHANG')
458460
@support.requires_fork()

Lib/test/test_tempfile.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pathlib
77
import sys
88
import re
9+
import threading # XXX: RUSTPYTHON; to check `_at_fork_reinit`
910
import warnings
1011
import contextlib
1112
import stat
@@ -198,6 +199,7 @@ def supports_iter(self):
198199
if i == 20:
199200
break
200201

202+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
201203
@unittest.skipUnless(hasattr(os, 'fork'),
202204
"os.fork is required for this test")
203205
def test_process_awareness(self):
@@ -465,6 +467,7 @@ def test_file_mode(self):
465467
expected = user * (1 + 8 + 64)
466468
self.assertEqual(mode, expected)
467469

470+
@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit')
468471
@unittest.skipUnless(has_spawnl, 'os.spawnl not available')
469472
def test_noinherit(self):
470473
# _mkstemp_inner file handles are not inherited by child processes

0 commit comments

Comments
 (0)