From 9ba441024aa4127bc49ddcdd220f4f6471f5406f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:26:25 +0100 Subject: [PATCH 1/4] gh-143698: correctly check `scheduler` type for `os.posix_spawn[p]` --- Lib/test/test_os/test_posix.py | 6 ++++++ .../Library/2026-01-11-16-59-22.gh-issue-143698.b-Cpeb.rst | 3 +++ Modules/posixmodule.c | 7 +++++++ 3 files changed, 16 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-01-11-16-59-22.gh-issue-143698.b-Cpeb.rst diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py index 37da293a441e46..f221f75f29003e 100644 --- a/Lib/test/test_os/test_posix.py +++ b/Lib/test/test_os/test_posix.py @@ -2096,6 +2096,12 @@ def test_setsigdef_wrong_type(self): [sys.executable, "-c", "pass"], os.environ, setsigdef=[signal.NSIG, signal.NSIG+1]) + @support.subTests("scheduler", [None, 1, [1, 2]]) + def test_invalid_scheduler_param(self, scheduler): + path, args = self.NOOP_PROGRAM[0], self.NOOP_PROGRAM + with self.assertRaisesRegex(TypeError, "scheduler must be a tuple"): + self.spawn_func(path, args, os.environ, scheduler=scheduler) + @requires_sched @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), "bpo-34685: test can fail on BSD") diff --git a/Misc/NEWS.d/next/Library/2026-01-11-16-59-22.gh-issue-143698.b-Cpeb.rst b/Misc/NEWS.d/next/Library/2026-01-11-16-59-22.gh-issue-143698.b-Cpeb.rst new file mode 100644 index 00000000000000..05dc4941c98a83 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-11-16-59-22.gh-issue-143698.b-Cpeb.rst @@ -0,0 +1,3 @@ +Raise :exc:`TypeError` instead of :exc:`SystemError` when the *scheduler* +in :func:`os.posix_spawn` or :func:`os.posix_spawnp` is not a tuple. +Patch by Bénédikt Tran. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 49214d57a2e362..3b3113deb25ca4 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7611,6 +7611,7 @@ parse_posix_spawn_flags(PyObject *module, const char *func_name, PyObject *setpg PyObject *setsigdef, PyObject *scheduler, posix_spawnattr_t *attrp) { + assert(scheduler == NULL || PyTuple_Check(scheduler)); long all_flags = 0; errno = posix_spawnattr_init(attrp); @@ -7917,6 +7918,12 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a goto exit; } + if (scheduler && !PyTuple_Check(scheduler)) { + PyErr_Format(PyExc_TypeError, + "%s: scheduler must be a tuple", func_name); + goto exit; + } + argvlist = parse_arglist(argv, &argc); if (argvlist == NULL) { goto exit; From 1ad5f6d7ff7881ddd13c1349cf4f437a05f3d826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 11 Jan 2026 18:16:14 +0100 Subject: [PATCH 2/4] explicitly accept `scheduler=None` --- Lib/test/test_os/test_posix.py | 7 +++++-- Modules/clinic/posixmodule.c.h | 6 +++--- Modules/posixmodule.c | 14 +++++++------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py index f221f75f29003e..6b2002076e3b0c 100644 --- a/Lib/test/test_os/test_posix.py +++ b/Lib/test/test_os/test_posix.py @@ -2096,10 +2096,13 @@ def test_setsigdef_wrong_type(self): [sys.executable, "-c", "pass"], os.environ, setsigdef=[signal.NSIG, signal.NSIG+1]) - @support.subTests("scheduler", [None, 1, [1, 2]]) + @support.subTests("scheduler", [object(), 1, [1, 2]]) def test_invalid_scheduler_param(self, scheduler): path, args = self.NOOP_PROGRAM[0], self.NOOP_PROGRAM - with self.assertRaisesRegex(TypeError, "scheduler must be a tuple"): + with self.assertRaisesRegex( + TypeError, + "scheduler must be a tuple or None", + ): self.spawn_func(path, args, os.environ, scheduler=scheduler) @requires_sched diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 03c0f221ba98b3..f622b25e04e1be 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3911,7 +3911,7 @@ os_execve(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k PyDoc_STRVAR(os_posix_spawn__doc__, "posix_spawn($module, path, argv, env, /, *, file_actions=(),\n" " setpgroup=, resetids=False, setsid=False,\n" -" setsigmask=(), setsigdef=(), scheduler=)\n" +" setsigmask=(), setsigdef=(), scheduler=None)\n" "--\n" "\n" "Execute the program specified by path in a new process.\n" @@ -4064,7 +4064,7 @@ os_posix_spawn(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje PyDoc_STRVAR(os_posix_spawnp__doc__, "posix_spawnp($module, path, argv, env, /, *, file_actions=(),\n" " setpgroup=, resetids=False, setsid=False,\n" -" setsigmask=(), setsigdef=(), scheduler=)\n" +" setsigmask=(), setsigdef=(), scheduler=None)\n" "--\n" "\n" "Execute the program specified by path in a new process.\n" @@ -13611,4 +13611,4 @@ os__emscripten_log(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py #ifndef OS__EMSCRIPTEN_LOG_METHODDEF #define OS__EMSCRIPTEN_LOG_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */ -/*[clinic end generated code: output=5fd2aeb6ba9a5df8 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=57e10a4363feb170 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 3b3113deb25ca4..52601d71622834 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7693,7 +7693,7 @@ parse_posix_spawn_flags(PyObject *module, const char *func_name, PyObject *setpg } #endif - if (scheduler) { + if (scheduler && scheduler != Py_None) { #ifdef POSIX_SPAWN_SETSCHEDULER PyObject *py_schedpolicy; PyObject *schedparam_obj; @@ -7918,9 +7918,9 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a goto exit; } - if (scheduler && !PyTuple_Check(scheduler)) { + if (!PyTuple_Check(scheduler) && scheduler != Py_None) { PyErr_Format(PyExc_TypeError, - "%s: scheduler must be a tuple", func_name); + "%s: scheduler must be a tuple or None", func_name); goto exit; } @@ -8045,7 +8045,7 @@ os.posix_spawn The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag. setsigdef: object(c_default='NULL') = () The sigmask to use with the POSIX_SPAWN_SETSIGDEF flag. - scheduler: object = NULL + scheduler: object(c_default='NULL') = None A tuple with the scheduler policy (optional) and parameters. Execute the program specified by path in a new process. @@ -8057,7 +8057,7 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv, PyObject *setpgroup, int resetids, int setsid, PyObject *setsigmask, PyObject *setsigdef, PyObject *scheduler) -/*[clinic end generated code: output=14a1098c566bc675 input=808aed1090d84e33]*/ +/*[clinic end generated code: output=14a1098c566bc675 input=242939f993243c45]*/ { return py_posix_spawn(0, module, path, argv, env, file_actions, setpgroup, resetids, setsid, setsigmask, setsigdef, @@ -8091,7 +8091,7 @@ os.posix_spawnp The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag. setsigdef: object(c_default='NULL') = () The sigmask to use with the POSIX_SPAWN_SETSIGDEF flag. - scheduler: object = NULL + scheduler: object(c_default='NULL') = None A tuple with the scheduler policy (optional) and parameters. Execute the program specified by path in a new process. @@ -8103,7 +8103,7 @@ os_posix_spawnp_impl(PyObject *module, path_t *path, PyObject *argv, PyObject *setpgroup, int resetids, int setsid, PyObject *setsigmask, PyObject *setsigdef, PyObject *scheduler) -/*[clinic end generated code: output=7b9aaefe3031238d input=9e89e616116752a1]*/ +/*[clinic end generated code: output=7b9aaefe3031238d input=e77db185549d088c]*/ { return py_posix_spawn(1, module, path, argv, env, file_actions, setpgroup, resetids, setsid, setsigmask, setsigdef, From 34cd6557b99ba74bedcd601eae97e9d3bba454eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 11 Jan 2026 18:33:59 +0100 Subject: [PATCH 3/4] explicitly accept `setpgroup=None` --- Lib/test/test_os/test_posix.py | 12 +++++++++++- Modules/clinic/posixmodule.c.h | 6 +++--- Modules/posixmodule.c | 14 +++++++------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py index 6b2002076e3b0c..e1e2ebfdf0eed7 100644 --- a/Lib/test/test_os/test_posix.py +++ b/Lib/test/test_os/test_posix.py @@ -1996,6 +1996,11 @@ def test_setpgroup(self): ) support.wait_process(pid, exitcode=0) + def test_setpgroup_allow_none(self): + path, args = self.NOOP_PROGRAM[0], self.NOOP_PROGRAM + pid = self.spawn_func(path, args, os.environ, setpgroup=None) + support.wait_process(pid, exitcode=0) + def test_setpgroup_wrong_type(self): with self.assertRaises(TypeError): self.spawn_func(sys.executable, @@ -2096,8 +2101,13 @@ def test_setsigdef_wrong_type(self): [sys.executable, "-c", "pass"], os.environ, setsigdef=[signal.NSIG, signal.NSIG+1]) + def test_scheduler_allow_none(self): + path, args = self.NOOP_PROGRAM[0], self.NOOP_PROGRAM + pid = self.spawn_func(path, args, os.environ, scheduler=None) + support.wait_process(pid, exitcode=0) + @support.subTests("scheduler", [object(), 1, [1, 2]]) - def test_invalid_scheduler_param(self, scheduler): + def test_scheduler_wrong_type(self, scheduler): path, args = self.NOOP_PROGRAM[0], self.NOOP_PROGRAM with self.assertRaisesRegex( TypeError, diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index f622b25e04e1be..f58848922f57c9 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -3910,7 +3910,7 @@ os_execve(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k PyDoc_STRVAR(os_posix_spawn__doc__, "posix_spawn($module, path, argv, env, /, *, file_actions=(),\n" -" setpgroup=, resetids=False, setsid=False,\n" +" setpgroup=None, resetids=False, setsid=False,\n" " setsigmask=(), setsigdef=(), scheduler=None)\n" "--\n" "\n" @@ -4063,7 +4063,7 @@ os_posix_spawn(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje PyDoc_STRVAR(os_posix_spawnp__doc__, "posix_spawnp($module, path, argv, env, /, *, file_actions=(),\n" -" setpgroup=, resetids=False, setsid=False,\n" +" setpgroup=None, resetids=False, setsid=False,\n" " setsigmask=(), setsigdef=(), scheduler=None)\n" "--\n" "\n" @@ -13611,4 +13611,4 @@ os__emscripten_log(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py #ifndef OS__EMSCRIPTEN_LOG_METHODDEF #define OS__EMSCRIPTEN_LOG_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */ -/*[clinic end generated code: output=57e10a4363feb170 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=d63fe97ccc3b1792 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 52601d71622834..eb67b83899a01f 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7611,7 +7611,7 @@ parse_posix_spawn_flags(PyObject *module, const char *func_name, PyObject *setpg PyObject *setsigdef, PyObject *scheduler, posix_spawnattr_t *attrp) { - assert(scheduler == NULL || PyTuple_Check(scheduler)); + assert(scheduler == NULL || scheduler == Py_None || PyTuple_Check(scheduler)); long all_flags = 0; errno = posix_spawnattr_init(attrp); @@ -7620,7 +7620,7 @@ parse_posix_spawn_flags(PyObject *module, const char *func_name, PyObject *setpg return -1; } - if (setpgroup) { + if (setpgroup && setpgroup != Py_None) { pid_t pgid = PyLong_AsPid(setpgroup); if (pgid == (pid_t)-1 && PyErr_Occurred()) { goto fail; @@ -7918,7 +7918,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a goto exit; } - if (!PyTuple_Check(scheduler) && scheduler != Py_None) { + if (scheduler && !PyTuple_Check(scheduler) && scheduler != Py_None) { PyErr_Format(PyExc_TypeError, "%s: scheduler must be a tuple or None", func_name); goto exit; @@ -8035,7 +8035,7 @@ os.posix_spawn * file_actions: object(c_default='NULL') = () A sequence of file action tuples. - setpgroup: object = NULL + setpgroup: object(c_default='NULL') = None The pgroup to use with the POSIX_SPAWN_SETPGROUP flag. resetids: bool = False If the value is `true` the POSIX_SPAWN_RESETIDS will be activated. @@ -8057,7 +8057,7 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv, PyObject *setpgroup, int resetids, int setsid, PyObject *setsigmask, PyObject *setsigdef, PyObject *scheduler) -/*[clinic end generated code: output=14a1098c566bc675 input=242939f993243c45]*/ +/*[clinic end generated code: output=14a1098c566bc675 input=69e7c9ebbdcf94a5]*/ { return py_posix_spawn(0, module, path, argv, env, file_actions, setpgroup, resetids, setsid, setsigmask, setsigdef, @@ -8081,7 +8081,7 @@ os.posix_spawnp * file_actions: object(c_default='NULL') = () A sequence of file action tuples. - setpgroup: object = NULL + setpgroup: object(c_default='NULL') = None The pgroup to use with the POSIX_SPAWN_SETPGROUP flag. resetids: bool = False If the value is `True` the POSIX_SPAWN_RESETIDS will be activated. @@ -8103,7 +8103,7 @@ os_posix_spawnp_impl(PyObject *module, path_t *path, PyObject *argv, PyObject *setpgroup, int resetids, int setsid, PyObject *setsigmask, PyObject *setsigdef, PyObject *scheduler) -/*[clinic end generated code: output=7b9aaefe3031238d input=e77db185549d088c]*/ +/*[clinic end generated code: output=7b9aaefe3031238d input=a5c057527c6881a5]*/ { return py_posix_spawn(1, module, path, argv, env, file_actions, setpgroup, resetids, setsid, setsigmask, setsigdef, From 0724a806db5b5e8239cc5276c3fa736c40375c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 11 Jan 2026 18:42:15 +0100 Subject: [PATCH 4/4] add NEWS for setpgroup behavior --- .../Library/2026-01-11-18-35-52.gh-issue-143698.gXDzsJ.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-01-11-18-35-52.gh-issue-143698.gXDzsJ.rst diff --git a/Misc/NEWS.d/next/Library/2026-01-11-18-35-52.gh-issue-143698.gXDzsJ.rst b/Misc/NEWS.d/next/Library/2026-01-11-18-35-52.gh-issue-143698.gXDzsJ.rst new file mode 100644 index 00000000000000..5f95b0de7d8895 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-11-18-35-52.gh-issue-143698.gXDzsJ.rst @@ -0,0 +1,3 @@ +Allow *scheduler* and *setpgroup* arguments to be explicitly :const:`None` +when calling :func:`os.posix_spawn` or :func:`os.posix_spawnp`. Patch by +Bénédikt Tran.