Skip to content

Commit 6d8e36c

Browse files
committed
Pre-emptively check whether to use the fast way methods
1 parent df0538a commit 6d8e36c

File tree

2 files changed

+62
-50
lines changed

2 files changed

+62
-50
lines changed

Lib/subprocess.py

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -748,16 +748,63 @@ def _use_posix_spawn():
748748
# By default, assume that posix_spawn() does not properly report errors.
749749
return False
750750

751-
_CAN_USE_KQUEUE = all(
752-
hasattr(select, x)
753-
for x in (
754-
"kqueue",
755-
"KQ_EV_ADD",
756-
"KQ_EV_ONESHOT",
757-
"KQ_FILTER_PROC",
758-
"KQ_NOTE_EXIT",
759-
)
760-
)
751+
752+
def _can_use_pidfd_open():
753+
# Availability: Linux >= 5.3
754+
if not hasattr(os, "pidfd_open"):
755+
return False
756+
try:
757+
pidfd = os.pidfd_open(os.getpid(), 0)
758+
except OSError as err:
759+
if err.errno in {errno.EMFILE, errno.ENFILE}:
760+
# transitory 'too many open files'
761+
return True
762+
# likely blocked by security policy like SECCOMP (EPERM,
763+
# EACCES, ENOSYS)
764+
return False
765+
else:
766+
os.close(pidfd)
767+
return True
768+
769+
770+
def _can_use_kqueue():
771+
# Availability: macOS, BSD
772+
if not all(
773+
hasattr(select, x)
774+
for x in (
775+
"kqueue",
776+
"KQ_EV_ADD",
777+
"KQ_EV_ONESHOT",
778+
"KQ_FILTER_PROC",
779+
"KQ_NOTE_EXIT",
780+
)
781+
):
782+
return False
783+
784+
kq = None
785+
try:
786+
kq = select.kqueue()
787+
kev = select.kevent(
788+
os.getpid(),
789+
filter=select.KQ_FILTER_PROC,
790+
flags=select.KQ_EV_ADD | select.KQ_EV_ONESHOT,
791+
fflags=select.KQ_NOTE_EXIT,
792+
)
793+
events = kq.control([kev], 1, 0)
794+
return True
795+
except OSError as err:
796+
if err.errno in {errno.EMFILE, errno.ENFILE}:
797+
# transitory 'too many open files'
798+
return True
799+
return False
800+
finally:
801+
if kq is not None:
802+
kq.close()
803+
804+
805+
_CAN_USE_PIDFD_OPEN = _can_use_pidfd_open()
806+
_CAN_USE_KQUEUE = _can_use_kqueue()
807+
761808

762809
# These are primarily fail-safe knobs for negatives. A True value does not
763810
# guarantee the given libc/syscall API will be used.
@@ -2061,7 +2108,7 @@ def _wait_pidfd(self, timeout):
20612108
"""Wait for PID to terminate using pidfd_open() + poll().
20622109
Linux >= 5.3 only.
20632110
"""
2064-
if not hasattr(os, "pidfd_open"):
2111+
if not _CAN_USE_PIDFD_OPEN:
20652112
return False
20662113
try:
20672114
pidfd = os.pidfd_open(self.pid, 0)
@@ -2091,7 +2138,7 @@ def _wait_kqueue(self, timeout):
20912138
try:
20922139
kq = select.kqueue()
20932140
except OSError:
2094-
# usually EMFILE / ENFILE (too many open files)
2141+
# likely EMFILE / ENFILE (too many open files)
20952142
return False
20962143

20972144
try:
@@ -2103,7 +2150,7 @@ def _wait_kqueue(self, timeout):
21032150
)
21042151
try:
21052152
events = kq.control([kev], 1, timeout) # wait
2106-
except OSError as err:
2153+
except OSError as err: # should never happen
21072154
return False
21082155
if not events:
21092156
raise TimeoutExpired(self.args, timeout)

Lib/test/test_subprocess.py

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4096,48 +4096,13 @@ def test_broken_pipe_cleanup(self):
40964096
self.assertTrue(proc.stdin.closed)
40974097

40984098

4099-
# ---
4100-
4101-
def can_use_pidfd():
4102-
# Availability: Linux >= 5.3
4103-
if not hasattr(os, "pidfd_open"):
4104-
return False
4105-
try:
4106-
pidfd = os.pidfd_open(os.getpid(), 0)
4107-
except OSError as err:
4108-
# blocked by security policy like SECCOMP
4109-
return False
4110-
else:
4111-
os.close(pidfd)
4112-
return True
4113-
4114-
4115-
def can_use_kevent():
4116-
if not subprocess._CAN_USE_KQUEUE:
4117-
return False
4118-
kq = select.kqueue()
4119-
try:
4120-
kev = select.kevent(
4121-
os.getpid(),
4122-
filter=select.KQ_FILTER_PROC,
4123-
flags=select.KQ_EV_ADD | select.KQ_EV_ONESHOT,
4124-
fflags=select.KQ_NOTE_EXIT,
4125-
)
4126-
events = kq.control([kev], 1, 0)
4127-
return True
4128-
except OSError:
4129-
return False
4130-
finally:
4131-
kq.close()
4132-
4133-
41344099

41354100
class FastWaitTestCase(BaseTestCase):
41364101
"""Tests for efficient (pidfd_open() + poll() / kqueue()) process
41374102
waiting in subprocess.Popen.wait().
41384103
"""
4139-
CAN_USE_PIDFD_OPEN = can_use_pidfd()
4140-
CAN_USE_KQUEUE = can_use_kevent()
4104+
CAN_USE_PIDFD_OPEN = subprocess._CAN_USE_PIDFD_OPEN
4105+
CAN_USE_KQUEUE = subprocess._CAN_USE_KQUEUE
41414106

41424107
def assert_fast_waitpid_error(self, patch_point):
41434108
# Emulate a case where pidfd_open() (Linux) or kqueue()

0 commit comments

Comments
 (0)