From 1639136b1f56e9c5ba3106de67a2d34a5e776df9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 10 Aug 2026 22:04:34 +0300 Subject: [PATCH 1/2] gh-155496: Use Argument Clinic for more functions of the _thread module --- .../pycore_global_objects_fini_generated.h | 2 + Include/internal/pycore_global_strings.h | 2 + .../internal/pycore_runtime_init_generated.h | 2 + .../internal/pycore_unicodeobject_generated.h | 8 + Modules/_threadmodule.c | 504 +++++++++-------- Modules/clinic/_threadmodule.c.h | 525 +++++++++++++++++- 6 files changed, 796 insertions(+), 247 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 6df1c01f151f68e..4553ae41627efe3 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1692,6 +1692,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ctx)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cwd)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(d_parameter_type)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(daemon)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(data)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(database)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(date)); @@ -1790,6 +1791,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fset)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fullerror)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(func)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(function)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(future)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(gc)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(generation)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 873344fbdcb67b0..1be9ad997624f32 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -415,6 +415,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(ctx) STRUCT_FOR_ID(cwd) STRUCT_FOR_ID(d_parameter_type) + STRUCT_FOR_ID(daemon) STRUCT_FOR_ID(data) STRUCT_FOR_ID(database) STRUCT_FOR_ID(date) @@ -513,6 +514,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(fset) STRUCT_FOR_ID(fullerror) STRUCT_FOR_ID(func) + STRUCT_FOR_ID(function) STRUCT_FOR_ID(future) STRUCT_FOR_ID(gc) STRUCT_FOR_ID(generation) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 378f27ca17b5079..e5f2d8a2f7b1dfe 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1690,6 +1690,7 @@ extern "C" { INIT_ID(ctx), \ INIT_ID(cwd), \ INIT_ID(d_parameter_type), \ + INIT_ID(daemon), \ INIT_ID(data), \ INIT_ID(database), \ INIT_ID(date), \ @@ -1788,6 +1789,7 @@ extern "C" { INIT_ID(fset), \ INIT_ID(fullerror), \ INIT_ID(func), \ + INIT_ID(function), \ INIT_ID(future), \ INIT_ID(gc), \ INIT_ID(generation), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index daf6840aa47f328..d6c0bee3593b7d4 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1440,6 +1440,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(daemon); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(data); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -1832,6 +1836,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(function); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(future); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 199e4ac3db723bf..7b72d65e709972c 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -85,8 +85,9 @@ static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL; module _thread class _thread.lock "lockobject *" "clinic_state()->lock_type" class _thread.RLock "rlockobject *" "clinic_state()->rlock_type" +class _thread._ThreadHandle "PyObject *" "clinic_state()->thread_handle_type" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=c5a0f8c492a0c263]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=bd4eae07ae18bc2c]*/ #define clinic_state() get_thread_state_by_cls(type) #include "clinic/_threadmodule.c.h" @@ -684,36 +685,44 @@ PyThreadHandleObject_get_ident(PyObject *op, void *Py_UNUSED(closure)) return PyLong_FromUnsignedLongLong(ThreadHandle_ident(self->handle)); } +/*[clinic input] +_thread._ThreadHandle.join + timeout as timeout_obj: object = None + / +[clinic start generated code]*/ + static PyObject * -PyThreadHandleObject_join(PyObject *op, PyObject *args) +_thread__ThreadHandle_join_impl(PyObject *self, PyObject *timeout_obj) +/*[clinic end generated code: output=d3a4f0b20c21442e input=b66d17a0f77d6e9c]*/ { - PyThreadHandleObject *self = PyThreadHandleObject_CAST(op); - - PyObject *timeout_obj = NULL; - if (!PyArg_ParseTuple(args, "|O:join", &timeout_obj)) { - return NULL; - } + PyThreadHandleObject *handle = PyThreadHandleObject_CAST(self); PyTime_t timeout_ns = -1; - if (timeout_obj != NULL && timeout_obj != Py_None) { + if (timeout_obj != Py_None) { if (_PyTime_FromSecondsObject(&timeout_ns, timeout_obj, _PyTime_ROUND_TIMEOUT) < 0) { return NULL; } } - if (ThreadHandle_join(self->handle, timeout_ns) < 0) { + if (ThreadHandle_join(handle->handle, timeout_ns) < 0) { return NULL; } Py_RETURN_NONE; } +/*[clinic input] +_thread._ThreadHandle.is_done +[clinic start generated code]*/ + static PyObject * -PyThreadHandleObject_is_done(PyObject *op, PyObject *Py_UNUSED(dummy)) +_thread__ThreadHandle_is_done_impl(PyObject *self) +/*[clinic end generated code: output=f5c00923fb61967b input=dd905a1ac7fc2e7c]*/ { - PyThreadHandleObject *self = PyThreadHandleObject_CAST(op); - if (_PyEvent_IsSet(&self->handle->thread_is_exiting)) { - if (_PyOnceFlag_CallOnce(&self->handle->once, join_thread, self->handle) == -1) { + PyThreadHandleObject *handle = PyThreadHandleObject_CAST(self); + if (_PyEvent_IsSet(&handle->handle->thread_is_exiting)) { + if (_PyOnceFlag_CallOnce(&handle->handle->once, join_thread, + handle->handle) == -1) { return NULL; } Py_RETURN_TRUE; @@ -723,11 +732,16 @@ PyThreadHandleObject_is_done(PyObject *op, PyObject *Py_UNUSED(dummy)) } } +/*[clinic input] +_thread._ThreadHandle._set_done +[clinic start generated code]*/ + static PyObject * -PyThreadHandleObject_set_done(PyObject *op, PyObject *Py_UNUSED(dummy)) +_thread__ThreadHandle__set_done_impl(PyObject *self) +/*[clinic end generated code: output=709b4d7ca318bb6d input=9f0115db8d84806b]*/ { - PyThreadHandleObject *self = PyThreadHandleObject_CAST(op); - if (ThreadHandle_set_done(self->handle) < 0) { + PyThreadHandleObject *handle = PyThreadHandleObject_CAST(self); + if (ThreadHandle_set_done(handle->handle) < 0) { return NULL; } Py_RETURN_NONE; @@ -739,9 +753,9 @@ static PyGetSetDef ThreadHandle_getsetlist[] = { }; static PyMethodDef ThreadHandle_methods[] = { - {"join", PyThreadHandleObject_join, METH_VARARGS, NULL}, - {"_set_done", PyThreadHandleObject_set_done, METH_NOARGS, NULL}, - {"is_done", PyThreadHandleObject_is_done, METH_NOARGS, NULL}, + _THREAD__THREADHANDLE_JOIN_METHODDEF + _THREAD__THREADHANDLE__SET_DONE_METHODDEF + _THREAD__THREADHANDLE_IS_DONE_METHODDEF {0, 0} }; @@ -1874,8 +1888,17 @@ clear_locals(PyObject *locals_and_key, PyObject *dummyweakref) /* Module functions */ +/*[clinic input] +_thread.daemon_threads_allowed + +Return True if daemon threads are allowed in the current interpreter. + +Return False otherwise. +[clinic start generated code]*/ + static PyObject * -thread_daemon_threads_allowed(PyObject *module, PyObject *Py_UNUSED(ignored)) +_thread_daemon_threads_allowed_impl(PyObject *module) +/*[clinic end generated code: output=c82b3b9490c7e11e input=f94150f9e14b7d0c]*/ { PyInterpreterState *interp = _PyInterpreterState_GET(); if (interp->feature_flags & Py_RTFLAGS_DAEMON_THREADS) { @@ -1886,13 +1909,6 @@ thread_daemon_threads_allowed(PyObject *module, PyObject *Py_UNUSED(ignored)) } } -PyDoc_STRVAR(daemon_threads_allowed_doc, -"daemon_threads_allowed($module, /)\n\ ---\n\ -\n\ -Return True if daemon threads are allowed in the current interpreter,\n\ -and False otherwise.\n"); - static int do_start_new_thread(thread_module_state *state, PyObject *func, PyObject *args, PyObject *kwargs, ThreadHandle *handle, int daemon) @@ -1926,31 +1942,36 @@ do_start_new_thread(thread_module_state *state, PyObject *func, PyObject *args, return 0; } +/*[clinic input] +_thread.start_new_thread + + function as func: object + args: object(subclass_of='&PyTuple_Type') + kwargs: object(subclass_of='&PyDict_Type', c_default="NULL") = {} + / + +Start a new thread and return its identifier. + +The thread will call the function with positional arguments from the +tuple args and keyword arguments taken from the optional dictionary +kwargs. The thread exits when the function returns; the return value +is ignored. The thread will also exit when the function raises an +unhandled exception; a stack trace will be printed unless the exception +is SystemExit. +[clinic start generated code]*/ + static PyObject * -thread_PyThread_start_new_thread(PyObject *module, PyObject *fargs) +_thread_start_new_thread_impl(PyObject *module, PyObject *func, + PyObject *args, PyObject *kwargs) +/*[clinic end generated code: output=0c27eb61b7a7152b input=b85f6b3301d30719]*/ { - PyObject *func, *args, *kwargs = NULL; thread_module_state *state = get_thread_state(module); - if (!PyArg_UnpackTuple(fargs, "start_new_thread", 2, 3, - &func, &args, &kwargs)) - return NULL; if (!PyCallable_Check(func)) { PyErr_SetString(PyExc_TypeError, "first arg must be callable"); return NULL; } - if (!PyTuple_Check(args)) { - PyErr_SetString(PyExc_TypeError, - "2nd arg must be a tuple"); - return NULL; - } - if (kwargs != NULL && !PyDict_Check(kwargs)) { - PyErr_SetString(PyExc_TypeError, - "optional 3rd arg must be a dictionary"); - return NULL; - } - if (PySys_Audit("_thread.start_new_thread", "OOO", func, args, kwargs ? kwargs : Py_None) < 0) { return NULL; @@ -1972,39 +1993,36 @@ thread_PyThread_start_new_thread(PyObject *module, PyObject *fargs) return PyLong_FromUnsignedLongLong(ident); } -PyDoc_STRVAR(start_new_thread_doc, -"start_new_thread($module, function, args, kwargs={}, /)\n\ ---\n\ -\n\ -Start a new thread and return its identifier.\n\ -\n\ -The thread will call the function with positional arguments from the\n\ -tuple args and keyword arguments taken from the optional dictionary\n\ -kwargs. The thread exits when the function returns; the return value\n\ -is ignored. The thread will also exit when the function raises an\n\ -unhandled exception; a stack trace will be printed unless the exception\n\ -is SystemExit."); - PyDoc_STRVAR(start_new_doc, "start_new($module, function, args, kwargs={}, /)\n\ --\n\ \n\ An obsolete synonym of start_new_thread()."); +/*[clinic input] +_thread.start_joinable_thread + + function as func: object + handle as hobj: object = None + daemon: bool = True + +*For internal use only*: start a new thread. + +Like start_new_thread(), this starts a new thread calling the given +function. Unlike start_new_thread(), this returns a handle object with +methods to join or detach the given thread. +This function is not for third-party code, please use the `threading` +module instead. During finalization the runtime will not wait for the +thread to exit if daemon is True. If handle is provided it must be a +newly created thread._ThreadHandle instance. +[clinic start generated code]*/ + static PyObject * -thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs, - PyObject *fkwargs) +_thread_start_joinable_thread_impl(PyObject *module, PyObject *func, + PyObject *hobj, int daemon) +/*[clinic end generated code: output=58691769e60620cf input=e17b3aebc087952a]*/ { - static char *keywords[] = {"function", "handle", "daemon", NULL}; - PyObject *func = NULL; - int daemon = 1; thread_module_state *state = get_thread_state(module); - PyObject *hobj = NULL; - if (!PyArg_ParseTupleAndKeywords(fargs, fkwargs, - "O|Op:start_joinable_thread", keywords, - &func, &hobj, &daemon)) { - return NULL; - } if (!PyCallable_Check(func)) { PyErr_SetString(PyExc_TypeError, @@ -2012,10 +2030,7 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs, return NULL; } - if (hobj == NULL) { - hobj = Py_None; - } - else if (hobj != Py_None && !Py_IS_TYPE(hobj, state->thread_handle_type)) { + if (hobj != Py_None && !Py_IS_TYPE(hobj, state->thread_handle_type)) { PyErr_SetString(PyExc_TypeError, "'handle' must be a _ThreadHandle"); return NULL; } @@ -2049,48 +2064,49 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs, return (PyObject *) hobj; } -PyDoc_STRVAR(start_joinable_doc, -"start_joinable_thread($module, /, function, handle=None, daemon=True)\n\ ---\n\ -\n\ -*For internal use only*: start a new thread.\n\ -\n\ -Like start_new_thread(), this starts a new thread calling the given function.\n\ -Unlike start_new_thread(), this returns a handle object with methods to join\n\ -or detach the given thread.\n\ -This function is not for third-party code, please use the\n\ -`threading` module instead. During finalization the runtime will not wait for\n\ -the thread to exit if daemon is True. If handle is provided it must be a\n\ -newly created thread._ThreadHandle instance."); +/*[clinic input] +_thread.exit + +Raise SystemExit. + +It will cause the current thread to exit silently unless the exception +is caught. +[clinic start generated code]*/ static PyObject * -thread_PyThread_exit_thread(PyObject *self, PyObject *Py_UNUSED(ignored)) +_thread_exit_impl(PyObject *module) +/*[clinic end generated code: output=b03924346e743f4c input=1f3dc5da8ad48c70]*/ { PyErr_SetNone(PyExc_SystemExit); return NULL; } -PyDoc_STRVAR(exit_doc, -"exit($module, /)\n\ ---\n\ -\n\ -This is synonymous to ``raise SystemExit''. It will cause the current\n\ -thread to exit silently unless the exception is caught."); - PyDoc_STRVAR(exit_thread_doc, "exit_thread($module, /)\n\ --\n\ \n\ An obsolete synonym of exit()."); +/*[clinic input] +_thread.interrupt_main + + signum: int(c_default="SIGINT") = signal.SIGINT + / + +Simulate the arrival of the given signal in the main thread. + +The corresponding signal handler will be executed. +If *signum* is omitted, SIGINT is assumed. +A subthread can use this function to interrupt the main thread. + +Note: the default signal handler for SIGINT raises +``KeyboardInterrupt``. +[clinic start generated code]*/ + static PyObject * -thread_PyThread_interrupt_main(PyObject *self, PyObject *args) +_thread_interrupt_main_impl(PyObject *module, int signum) +/*[clinic end generated code: output=fdafa7261ceb26e9 input=a8cf392f3a7f2a84]*/ { - int signum = SIGINT; - if (!PyArg_ParseTuple(args, "|i:signum", &signum)) { - return NULL; - } - if (PyErr_SetInterruptEx(signum)) { PyErr_SetString(PyExc_ValueError, "signal number out of range"); return NULL; @@ -2098,40 +2114,44 @@ thread_PyThread_interrupt_main(PyObject *self, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(interrupt_doc, -"interrupt_main($module, signum=signal.SIGINT, /)\n\ ---\n\ -\n\ -Simulate the arrival of the given signal in the main thread,\n\ -where the corresponding signal handler will be executed.\n\ -If *signum* is omitted, SIGINT is assumed.\n\ -A subthread can use this function to interrupt the main thread.\n\ -\n\ -Note: the default signal handler for SIGINT raises ``KeyboardInterrupt``." -); +/*[clinic input] +_thread.allocate_lock + +Create a new lock object. + +See help(type(threading.Lock())) for information about locks. +[clinic start generated code]*/ static PyObject * -thread_PyThread_allocate_lock(PyObject *module, PyObject *Py_UNUSED(ignored)) +_thread_allocate_lock_impl(PyObject *module) +/*[clinic end generated code: output=884a4be6cb9ca731 input=49f5b1f966361bb6]*/ { thread_module_state *state = get_thread_state(module); return lock_new_impl(state->lock_type); } -PyDoc_STRVAR(allocate_lock_doc, -"allocate_lock($module, /)\n\ ---\n\ -\n\ -Create a new lock object. See help(type(threading.Lock())) for\n\ -information about locks."); - PyDoc_STRVAR(allocate_doc, "allocate($module, /)\n\ --\n\ \n\ An obsolete synonym of allocate_lock()."); +/*[clinic input] +_thread.get_ident + +Return a non-zero integer that uniquely identifies the current thread. + +It is unique amongst other threads that exist simultaneously. +This may be used to identify per-thread resources. +Even though on some platforms threads identities may appear to be +allocated consecutive numbers starting at 1, this behavior should not +be relied upon, and the number should be seen purely as a magic cookie. +A thread's identity may be reused for another thread after it exits. +[clinic start generated code]*/ + static PyObject * -thread_get_ident(PyObject *self, PyObject *Py_UNUSED(ignored)) +_thread_get_ident_impl(PyObject *module) +/*[clinic end generated code: output=8c3f9d8aa938b332 input=08faef37d1fd9e67]*/ { PyThread_ident_t ident = PyThread_get_thread_ident_ex(); if (ident == PYTHREAD_INVALID_THREAD_ID) { @@ -2141,64 +2161,79 @@ thread_get_ident(PyObject *self, PyObject *Py_UNUSED(ignored)) return PyLong_FromUnsignedLongLong(ident); } -PyDoc_STRVAR(get_ident_doc, -"get_ident($module, /)\n\ ---\n\ -\n\ -Return a non-zero integer that uniquely identifies the current thread\n\ -amongst other threads that exist simultaneously.\n\ -This may be used to identify per-thread resources.\n\ -Even though on some platforms threads identities may appear to be\n\ -allocated consecutive numbers starting at 1, this behavior should not\n\ -be relied upon, and the number should be seen purely as a magic cookie.\n\ -A thread's identity may be reused for another thread after it exits."); - #ifdef PY_HAVE_THREAD_NATIVE_ID +/*[clinic input] +_thread.get_native_id + +Return a non-negative integer identifying the thread. + +It is reported by the OS (kernel). This may be used to uniquely +identify a particular thread within a system. +[clinic start generated code]*/ + static PyObject * -thread_get_native_id(PyObject *self, PyObject *Py_UNUSED(ignored)) +_thread_get_native_id_impl(PyObject *module) +/*[clinic end generated code: output=d69767224b4ba922 input=454af91013e4f747]*/ { unsigned long native_id = PyThread_get_thread_native_id(); return PyLong_FromUnsignedLong(native_id); } -PyDoc_STRVAR(get_native_id_doc, -"get_native_id($module, /)\n\ ---\n\ -\n\ -Return a non-negative integer identifying the thread as reported\n\ -by the OS (kernel). This may be used to uniquely identify a\n\ -particular thread within a system."); #endif +/*[clinic input] +_thread._count + +Return the number of currently running Python threads. + +The main thread is excluded. The returned number comprises all threads +created through `start_new_thread()` as well as `threading.Thread`, and +not yet finished. + +This function is meant for internal and specialized purposes only. +In most applications `threading.enumerate()` should be used instead. +[clinic start generated code]*/ + static PyObject * -thread__count(PyObject *self, PyObject *Py_UNUSED(ignored)) +_thread__count_impl(PyObject *module) +/*[clinic end generated code: output=4cd03377c3e8521d input=bbd340043d174d48]*/ { PyInterpreterState *interp = _PyInterpreterState_GET(); return PyLong_FromSsize_t(_Py_atomic_load_ssize(&interp->threads.count)); } -PyDoc_STRVAR(_count_doc, -"_count($module, /)\n\ ---\n\ -\n\ -Return the number of currently running Python threads, excluding\n\ -the main thread. The returned number comprises all threads created\n\ -through `start_new_thread()` as well as `threading.Thread`, and not\n\ -yet finished.\n\ -\n\ -This function is meant for internal and specialized purposes only.\n\ -In most applications `threading.enumerate()` should be used instead."); +/*[clinic input] +_thread.stack_size + + size as new_size: Py_ssize_t = 0 + / + +Return the thread stack size used when creating new threads. + +The optional size argument specifies the stack size (in bytes) to be +used for subsequently created threads, and must be 0 (use platform or +configured default) or a positive integer value of at least 32,768 (32 +KiB). If changing the thread stack size is unsupported, a ThreadError +exception is raised. If the specified size is invalid, a ValueError +exception is raised, and the stack size is unmodified. 32 KiB +currently is the minimum supported stack size value to guarantee +sufficient stack space for the interpreter itself. + +Note that some systems may have particular restrictions on values for +the stack size, such as requiring a minimum stack size larger than 32 +KiB or requiring allocation in multiples of the system memory page size +- platform documentation should be referred to for more information +(4 KiB pages are common; using multiples of 4096 for the stack size is +the suggested approach in the absence of more specific information). +[clinic start generated code]*/ static PyObject * -thread_stack_size(PyObject *self, PyObject *args) +_thread_stack_size_impl(PyObject *module, Py_ssize_t new_size) +/*[clinic end generated code: output=cc7ba71bd35afc30 input=3249feb8ed2350f8]*/ { size_t old_size; - Py_ssize_t new_size = 0; int rc; - if (!PyArg_ParseTuple(args, "|n:stack_size", &new_size)) - return NULL; - Py_ssize_t min_size = _PyOS_MIN_STACK_SIZE + SYSTEM_PAGE_SIZE; if (new_size != 0 && new_size < min_size) { PyErr_Format(PyExc_ValueError, @@ -2224,27 +2259,6 @@ thread_stack_size(PyObject *self, PyObject *args) return PyLong_FromSsize_t((Py_ssize_t) old_size); } -PyDoc_STRVAR(stack_size_doc, -"stack_size($module, size=0, /)\n\ ---\n\ -\n\ -Return the thread stack size used when creating new threads. The\n\ -optional size argument specifies the stack size (in bytes) to be used\n\ -for subsequently created threads, and must be 0 (use platform or\n\ -configured default) or a positive integer value of at least 32,768 (32k).\n\ -If changing the thread stack size is unsupported, a ThreadError\n\ -exception is raised. If the specified size is invalid, a ValueError\n\ -exception is raised, and the stack size is unmodified. 32k bytes\n\ - currently the minimum supported stack size value to guarantee\n\ -sufficient stack space for the interpreter itself.\n\ -\n\ -Note that some platforms may have particular restrictions on values for\n\ -the stack size, such as requiring a minimum stack size larger than 32 KiB or\n\ -requiring allocation in multiples of the system memory page size\n\ -- platform documentation should be referred to for more information\n\ -(4 KiB pages are common; using multiples of 4096 for the stack size is\n\ -the suggested approach in the absence of more specific information)."); - static int thread_excepthook_file(PyObject *file, PyObject *exc_type, PyObject *exc_value, PyObject *exc_traceback, PyObject *thread) @@ -2323,8 +2337,18 @@ static PyStructSequence_Desc ExceptHookArgs_desc = { }; +/*[clinic input] +_thread._excepthook + + args: object + / + +Handle uncaught Thread.run() exception. +[clinic start generated code]*/ + static PyObject * -thread_excepthook(PyObject *module, PyObject *args) +_thread__excepthook(PyObject *module, PyObject *args) +/*[clinic end generated code: output=cadc54a3be0e6007 input=5ae2316c400a3497]*/ { thread_module_state *state = get_thread_state(module); @@ -2380,30 +2404,32 @@ thread_excepthook(PyObject *module, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(excepthook_doc, -"_excepthook($module, args, /)\n\ ---\n\ -\n\ -Handle uncaught Thread.run() exception."); +/*[clinic input] +_thread._is_main_interpreter + +Return True if the current interpreter is the main Python interpreter. +[clinic start generated code]*/ static PyObject * -thread__is_main_interpreter(PyObject *module, PyObject *Py_UNUSED(ignored)) +_thread__is_main_interpreter_impl(PyObject *module) +/*[clinic end generated code: output=7dd82e1728339adc input=cc1eb00fd4598915]*/ { PyInterpreterState *interp = _PyInterpreterState_GET(); return PyBool_FromLong(_Py_IsMainInterpreter(interp)); } -PyDoc_STRVAR(thread__is_main_interpreter_doc, -"_is_main_interpreter($module, /)\n\ ---\n\ -\n\ -Return True if the current interpreter is the main Python interpreter."); +/*[clinic input] +_thread._shutdown + +Wait for all non-daemon threads (other than the calling thread) to stop. +[clinic start generated code]*/ static PyObject * -thread_shutdown(PyObject *self, PyObject *args) +_thread__shutdown_impl(PyObject *module) +/*[clinic end generated code: output=a19afe92bddc1824 input=bcaeaceb3a6fa590]*/ { PyThread_ident_t ident = PyThread_get_thread_ident_ex(); - thread_module_state *state = get_thread_state(self); + thread_module_state *state = get_thread_state(module); for (;;) { ThreadHandle *handle = NULL; @@ -2439,14 +2465,21 @@ thread_shutdown(PyObject *self, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(shutdown_doc, -"_shutdown($module, /)\n\ ---\n\ -\n\ -Wait for all non-daemon threads (other than the calling thread) to stop."); +/*[clinic input] +_thread._make_thread_handle + + ident as identobj: object + / + +Internal only. + +Make a thread handle for threads not spawned by the _thread or +threading module. +[clinic start generated code]*/ static PyObject * -thread__make_thread_handle(PyObject *module, PyObject *identobj) +_thread__make_thread_handle(PyObject *module, PyObject *identobj) +/*[clinic end generated code: output=afa56c40a7e1c394 input=57bb20ebf5825dd1]*/ { thread_module_state *state = get_thread_state(module); if (!PyLong_Check(identobj)) { @@ -2469,26 +2502,22 @@ thread__make_thread_handle(PyObject *module, PyObject *identobj) return (PyObject*) hobj; } -PyDoc_STRVAR(thread__make_thread_handle_doc, -"_make_thread_handle($module, ident, /)\n\ ---\n\ -\n\ -Internal only. Make a thread handle for threads not spawned\n\ -by the _thread or threading module."); +/*[clinic input] +_thread._get_main_thread_ident + +Internal only. + +Return a non-zero integer that uniquely identifies the main thread of +the main interpreter. +[clinic start generated code]*/ static PyObject * -thread__get_main_thread_ident(PyObject *module, PyObject *Py_UNUSED(ignored)) +_thread__get_main_thread_ident_impl(PyObject *module) +/*[clinic end generated code: output=01aa07c5553458bf input=feea25c7039ba4b5]*/ { return PyLong_FromUnsignedLongLong(_PyRuntime.main_thread); } -PyDoc_STRVAR(thread__get_main_thread_ident_doc, -"_get_main_thread_ident($module, /)\n\ ---\n\ -\n\ -Internal only. Return a non-zero integer that uniquely identifies the main thread\n\ -of the main interpreter."); - #if defined(__OpenBSD__) /* pthread_*_np functions, especially pthread_{get,set}_name_np(). pthread_np.h exists on both OpenBSD and FreeBSD but the latter declares @@ -2648,44 +2677,29 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) static PyMethodDef thread_methods[] = { - {"start_new_thread", thread_PyThread_start_new_thread, - METH_VARARGS, start_new_thread_doc}, - {"start_new", thread_PyThread_start_new_thread, - METH_VARARGS, start_new_doc}, - {"start_joinable_thread", _PyCFunction_CAST(thread_PyThread_start_joinable_thread), - METH_VARARGS | METH_KEYWORDS, start_joinable_doc}, - {"daemon_threads_allowed", thread_daemon_threads_allowed, - METH_NOARGS, daemon_threads_allowed_doc}, - {"allocate_lock", thread_PyThread_allocate_lock, - METH_NOARGS, allocate_lock_doc}, - {"allocate", thread_PyThread_allocate_lock, + _THREAD_START_NEW_THREAD_METHODDEF + {"start_new", _PyCFunction_CAST(_thread_start_new_thread), + METH_FASTCALL, start_new_doc}, + _THREAD_START_JOINABLE_THREAD_METHODDEF + _THREAD_DAEMON_THREADS_ALLOWED_METHODDEF + _THREAD_ALLOCATE_LOCK_METHODDEF + {"allocate", _thread_allocate_lock, METH_NOARGS, allocate_doc}, - {"exit_thread", thread_PyThread_exit_thread, + {"exit_thread", _thread_exit, METH_NOARGS, exit_thread_doc}, - {"exit", thread_PyThread_exit_thread, - METH_NOARGS, exit_doc}, - {"interrupt_main", thread_PyThread_interrupt_main, - METH_VARARGS, interrupt_doc}, - {"get_ident", thread_get_ident, - METH_NOARGS, get_ident_doc}, + _THREAD_EXIT_METHODDEF + _THREAD_INTERRUPT_MAIN_METHODDEF + _THREAD_GET_IDENT_METHODDEF #ifdef PY_HAVE_THREAD_NATIVE_ID - {"get_native_id", thread_get_native_id, - METH_NOARGS, get_native_id_doc}, + _THREAD_GET_NATIVE_ID_METHODDEF #endif - {"_count", thread__count, - METH_NOARGS, _count_doc}, - {"stack_size", thread_stack_size, - METH_VARARGS, stack_size_doc}, - {"_excepthook", thread_excepthook, - METH_O, excepthook_doc}, - {"_is_main_interpreter", thread__is_main_interpreter, - METH_NOARGS, thread__is_main_interpreter_doc}, - {"_shutdown", thread_shutdown, - METH_NOARGS, shutdown_doc}, - {"_make_thread_handle", thread__make_thread_handle, - METH_O, thread__make_thread_handle_doc}, - {"_get_main_thread_ident", thread__get_main_thread_ident, - METH_NOARGS, thread__get_main_thread_ident_doc}, + _THREAD__COUNT_METHODDEF + _THREAD_STACK_SIZE_METHODDEF + _THREAD__EXCEPTHOOK_METHODDEF + _THREAD__IS_MAIN_INTERPRETER_METHODDEF + _THREAD__SHUTDOWN_METHODDEF + _THREAD__MAKE_THREAD_HANDLE_METHODDEF + _THREAD__GET_MAIN_THREAD_IDENT_METHODDEF _THREAD_SET_NAME_METHODDEF _THREAD__GET_NAME_METHODDEF {NULL, NULL} /* sentinel */ diff --git a/Modules/clinic/_threadmodule.c.h b/Modules/clinic/_threadmodule.c.h index 926bea8e1e419a4..13c1a64d0b456fd 100644 --- a/Modules/clinic/_threadmodule.c.h +++ b/Modules/clinic/_threadmodule.c.h @@ -6,7 +6,73 @@ preserve # include "pycore_gc.h" // PyGC_Head # include "pycore_runtime.h" // _Py_ID() #endif -#include "pycore_modsupport.h" // _PyArg_UnpackKeywords() +#include "pycore_abstract.h" // _PyNumber_Index() +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +PyDoc_STRVAR(_thread__ThreadHandle_join__doc__, +"join($self, timeout=None, /)\n" +"--\n" +"\n"); + +#define _THREAD__THREADHANDLE_JOIN_METHODDEF \ + {"join", _PyCFunction_CAST(_thread__ThreadHandle_join), METH_FASTCALL, _thread__ThreadHandle_join__doc__}, + +static PyObject * +_thread__ThreadHandle_join_impl(PyObject *self, PyObject *timeout_obj); + +static PyObject * +_thread__ThreadHandle_join(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *timeout_obj = Py_None; + + if (!_PyArg_CheckPositional("join", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + timeout_obj = args[0]; +skip_optional: + return_value = _thread__ThreadHandle_join_impl(self, timeout_obj); + +exit: + return return_value; +} + +PyDoc_STRVAR(_thread__ThreadHandle_is_done__doc__, +"is_done($self, /)\n" +"--\n" +"\n"); + +#define _THREAD__THREADHANDLE_IS_DONE_METHODDEF \ + {"is_done", (PyCFunction)_thread__ThreadHandle_is_done, METH_NOARGS, _thread__ThreadHandle_is_done__doc__}, + +static PyObject * +_thread__ThreadHandle_is_done_impl(PyObject *self); + +static PyObject * +_thread__ThreadHandle_is_done(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _thread__ThreadHandle_is_done_impl(self); +} + +PyDoc_STRVAR(_thread__ThreadHandle__set_done__doc__, +"_set_done($self, /)\n" +"--\n" +"\n"); + +#define _THREAD__THREADHANDLE__SET_DONE_METHODDEF \ + {"_set_done", (PyCFunction)_thread__ThreadHandle__set_done, METH_NOARGS, _thread__ThreadHandle__set_done__doc__}, + +static PyObject * +_thread__ThreadHandle__set_done_impl(PyObject *self); + +static PyObject * +_thread__ThreadHandle__set_done(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _thread__ThreadHandle__set_done_impl(self); +} PyDoc_STRVAR(_thread_lock_acquire__doc__, "acquire($self, /, blocking=True, timeout=-1)\n" @@ -637,6 +703,457 @@ _thread_RLock__at_fork_reinit(PyObject *self, PyObject *Py_UNUSED(ignored)) #endif /* defined(HAVE_FORK) */ +PyDoc_STRVAR(_thread_daemon_threads_allowed__doc__, +"daemon_threads_allowed($module, /)\n" +"--\n" +"\n" +"Return True if daemon threads are allowed in the current interpreter.\n" +"\n" +"Return False otherwise."); + +#define _THREAD_DAEMON_THREADS_ALLOWED_METHODDEF \ + {"daemon_threads_allowed", (PyCFunction)_thread_daemon_threads_allowed, METH_NOARGS, _thread_daemon_threads_allowed__doc__}, + +static PyObject * +_thread_daemon_threads_allowed_impl(PyObject *module); + +static PyObject * +_thread_daemon_threads_allowed(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _thread_daemon_threads_allowed_impl(module); +} + +PyDoc_STRVAR(_thread_start_new_thread__doc__, +"start_new_thread($module, function, args, kwargs={}, /)\n" +"--\n" +"\n" +"Start a new thread and return its identifier.\n" +"\n" +"The thread will call the function with positional arguments from the\n" +"tuple args and keyword arguments taken from the optional dictionary\n" +"kwargs. The thread exits when the function returns; the return value\n" +"is ignored. The thread will also exit when the function raises an\n" +"unhandled exception; a stack trace will be printed unless the exception\n" +"is SystemExit."); + +#define _THREAD_START_NEW_THREAD_METHODDEF \ + {"start_new_thread", _PyCFunction_CAST(_thread_start_new_thread), METH_FASTCALL, _thread_start_new_thread__doc__}, + +static PyObject * +_thread_start_new_thread_impl(PyObject *module, PyObject *func, + PyObject *args, PyObject *kwargs); + +static PyObject * +_thread_start_new_thread(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *func; + PyObject *__clinic_args; + PyObject *__clinic_kwargs = NULL; + + if (!_PyArg_CheckPositional("start_new_thread", nargs, 2, 3)) { + goto exit; + } + func = args[0]; + if (!PyTuple_Check(args[1])) { + _PyArg_BadArgument("start_new_thread", "argument 2", "tuple", args[1]); + goto exit; + } + __clinic_args = args[1]; + if (nargs < 3) { + goto skip_optional; + } + if (!PyDict_Check(args[2])) { + _PyArg_BadArgument("start_new_thread", "argument 3", "dict", args[2]); + goto exit; + } + __clinic_kwargs = args[2]; +skip_optional: + return_value = _thread_start_new_thread_impl(module, func, __clinic_args, __clinic_kwargs); + +exit: + return return_value; +} + +PyDoc_STRVAR(_thread_start_joinable_thread__doc__, +"start_joinable_thread($module, /, function, handle=None, daemon=True)\n" +"--\n" +"\n" +"*For internal use only*: start a new thread.\n" +"\n" +"Like start_new_thread(), this starts a new thread calling the given\n" +"function. Unlike start_new_thread(), this returns a handle object with\n" +"methods to join or detach the given thread.\n" +"This function is not for third-party code, please use the `threading`\n" +"module instead. During finalization the runtime will not wait for the\n" +"thread to exit if daemon is True. If handle is provided it must be a\n" +"newly created thread._ThreadHandle instance."); + +#define _THREAD_START_JOINABLE_THREAD_METHODDEF \ + {"start_joinable_thread", _PyCFunction_CAST(_thread_start_joinable_thread), METH_FASTCALL|METH_KEYWORDS, _thread_start_joinable_thread__doc__}, + +static PyObject * +_thread_start_joinable_thread_impl(PyObject *module, PyObject *func, + PyObject *hobj, int daemon); + +static PyObject * +_thread_start_joinable_thread(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(function), &_Py_ID(handle), &_Py_ID(daemon), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"function", "handle", "daemon", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "start_joinable_thread", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *func; + PyObject *hobj = Py_None; + int daemon = 1; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + func = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + if (args[1]) { + hobj = args[1]; + if (!--noptargs) { + goto skip_optional_pos; + } + } + daemon = PyObject_IsTrue(args[2]); + if (daemon < 0) { + goto exit; + } +skip_optional_pos: + return_value = _thread_start_joinable_thread_impl(module, func, hobj, daemon); + +exit: + return return_value; +} + +PyDoc_STRVAR(_thread_exit__doc__, +"exit($module, /)\n" +"--\n" +"\n" +"Raise SystemExit.\n" +"\n" +"It will cause the current thread to exit silently unless the exception\n" +"is caught."); + +#define _THREAD_EXIT_METHODDEF \ + {"exit", (PyCFunction)_thread_exit, METH_NOARGS, _thread_exit__doc__}, + +static PyObject * +_thread_exit_impl(PyObject *module); + +static PyObject * +_thread_exit(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _thread_exit_impl(module); +} + +PyDoc_STRVAR(_thread_interrupt_main__doc__, +"interrupt_main($module, signum=signal.SIGINT, /)\n" +"--\n" +"\n" +"Simulate the arrival of the given signal in the main thread.\n" +"\n" +"The corresponding signal handler will be executed.\n" +"If *signum* is omitted, SIGINT is assumed.\n" +"A subthread can use this function to interrupt the main thread.\n" +"\n" +"Note: the default signal handler for SIGINT raises\n" +"``KeyboardInterrupt``."); + +#define _THREAD_INTERRUPT_MAIN_METHODDEF \ + {"interrupt_main", _PyCFunction_CAST(_thread_interrupt_main), METH_FASTCALL, _thread_interrupt_main__doc__}, + +static PyObject * +_thread_interrupt_main_impl(PyObject *module, int signum); + +static PyObject * +_thread_interrupt_main(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int signum = SIGINT; + + if (!_PyArg_CheckPositional("interrupt_main", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + signum = PyLong_AsInt(args[0]); + if (signum == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional: + return_value = _thread_interrupt_main_impl(module, signum); + +exit: + return return_value; +} + +PyDoc_STRVAR(_thread_allocate_lock__doc__, +"allocate_lock($module, /)\n" +"--\n" +"\n" +"Create a new lock object.\n" +"\n" +"See help(type(threading.Lock())) for information about locks."); + +#define _THREAD_ALLOCATE_LOCK_METHODDEF \ + {"allocate_lock", (PyCFunction)_thread_allocate_lock, METH_NOARGS, _thread_allocate_lock__doc__}, + +static PyObject * +_thread_allocate_lock_impl(PyObject *module); + +static PyObject * +_thread_allocate_lock(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _thread_allocate_lock_impl(module); +} + +PyDoc_STRVAR(_thread_get_ident__doc__, +"get_ident($module, /)\n" +"--\n" +"\n" +"Return a non-zero integer that uniquely identifies the current thread.\n" +"\n" +"It is unique amongst other threads that exist simultaneously.\n" +"This may be used to identify per-thread resources.\n" +"Even though on some platforms threads identities may appear to be\n" +"allocated consecutive numbers starting at 1, this behavior should not\n" +"be relied upon, and the number should be seen purely as a magic cookie.\n" +"A thread\'s identity may be reused for another thread after it exits."); + +#define _THREAD_GET_IDENT_METHODDEF \ + {"get_ident", (PyCFunction)_thread_get_ident, METH_NOARGS, _thread_get_ident__doc__}, + +static PyObject * +_thread_get_ident_impl(PyObject *module); + +static PyObject * +_thread_get_ident(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _thread_get_ident_impl(module); +} + +#if defined(PY_HAVE_THREAD_NATIVE_ID) + +PyDoc_STRVAR(_thread_get_native_id__doc__, +"get_native_id($module, /)\n" +"--\n" +"\n" +"Return a non-negative integer identifying the thread.\n" +"\n" +"It is reported by the OS (kernel). This may be used to uniquely\n" +"identify a particular thread within a system."); + +#define _THREAD_GET_NATIVE_ID_METHODDEF \ + {"get_native_id", (PyCFunction)_thread_get_native_id, METH_NOARGS, _thread_get_native_id__doc__}, + +static PyObject * +_thread_get_native_id_impl(PyObject *module); + +static PyObject * +_thread_get_native_id(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _thread_get_native_id_impl(module); +} + +#endif /* defined(PY_HAVE_THREAD_NATIVE_ID) */ + +PyDoc_STRVAR(_thread__count__doc__, +"_count($module, /)\n" +"--\n" +"\n" +"Return the number of currently running Python threads.\n" +"\n" +"The main thread is excluded. The returned number comprises all threads\n" +"created through `start_new_thread()` as well as `threading.Thread`, and\n" +"not yet finished.\n" +"\n" +"This function is meant for internal and specialized purposes only.\n" +"In most applications `threading.enumerate()` should be used instead."); + +#define _THREAD__COUNT_METHODDEF \ + {"_count", (PyCFunction)_thread__count, METH_NOARGS, _thread__count__doc__}, + +static PyObject * +_thread__count_impl(PyObject *module); + +static PyObject * +_thread__count(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _thread__count_impl(module); +} + +PyDoc_STRVAR(_thread_stack_size__doc__, +"stack_size($module, size=0, /)\n" +"--\n" +"\n" +"Return the thread stack size used when creating new threads.\n" +"\n" +"The optional size argument specifies the stack size (in bytes) to be\n" +"used for subsequently created threads, and must be 0 (use platform or\n" +"configured default) or a positive integer value of at least 32,768 (32\n" +"KiB). If changing the thread stack size is unsupported, a ThreadError\n" +"exception is raised. If the specified size is invalid, a ValueError\n" +"exception is raised, and the stack size is unmodified. 32 KiB\n" +"currently is the minimum supported stack size value to guarantee\n" +"sufficient stack space for the interpreter itself.\n" +"\n" +"Note that some systems may have particular restrictions on values for\n" +"the stack size, such as requiring a minimum stack size larger than 32\n" +"KiB or requiring allocation in multiples of the system memory page size\n" +"- platform documentation should be referred to for more information\n" +"(4 KiB pages are common; using multiples of 4096 for the stack size is\n" +"the suggested approach in the absence of more specific information)."); + +#define _THREAD_STACK_SIZE_METHODDEF \ + {"stack_size", _PyCFunction_CAST(_thread_stack_size), METH_FASTCALL, _thread_stack_size__doc__}, + +static PyObject * +_thread_stack_size_impl(PyObject *module, Py_ssize_t new_size); + +static PyObject * +_thread_stack_size(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_ssize_t new_size = 0; + + if (!_PyArg_CheckPositional("stack_size", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[0]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + new_size = ival; + } +skip_optional: + return_value = _thread_stack_size_impl(module, new_size); + +exit: + return return_value; +} + +PyDoc_STRVAR(_thread__excepthook__doc__, +"_excepthook($module, args, /)\n" +"--\n" +"\n" +"Handle uncaught Thread.run() exception."); + +#define _THREAD__EXCEPTHOOK_METHODDEF \ + {"_excepthook", (PyCFunction)_thread__excepthook, METH_O, _thread__excepthook__doc__}, + +PyDoc_STRVAR(_thread__is_main_interpreter__doc__, +"_is_main_interpreter($module, /)\n" +"--\n" +"\n" +"Return True if the current interpreter is the main Python interpreter."); + +#define _THREAD__IS_MAIN_INTERPRETER_METHODDEF \ + {"_is_main_interpreter", (PyCFunction)_thread__is_main_interpreter, METH_NOARGS, _thread__is_main_interpreter__doc__}, + +static PyObject * +_thread__is_main_interpreter_impl(PyObject *module); + +static PyObject * +_thread__is_main_interpreter(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _thread__is_main_interpreter_impl(module); +} + +PyDoc_STRVAR(_thread__shutdown__doc__, +"_shutdown($module, /)\n" +"--\n" +"\n" +"Wait for all non-daemon threads (other than the calling thread) to stop."); + +#define _THREAD__SHUTDOWN_METHODDEF \ + {"_shutdown", (PyCFunction)_thread__shutdown, METH_NOARGS, _thread__shutdown__doc__}, + +static PyObject * +_thread__shutdown_impl(PyObject *module); + +static PyObject * +_thread__shutdown(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _thread__shutdown_impl(module); +} + +PyDoc_STRVAR(_thread__make_thread_handle__doc__, +"_make_thread_handle($module, ident, /)\n" +"--\n" +"\n" +"Internal only.\n" +"\n" +"Make a thread handle for threads not spawned by the _thread or\n" +"threading module."); + +#define _THREAD__MAKE_THREAD_HANDLE_METHODDEF \ + {"_make_thread_handle", (PyCFunction)_thread__make_thread_handle, METH_O, _thread__make_thread_handle__doc__}, + +PyDoc_STRVAR(_thread__get_main_thread_ident__doc__, +"_get_main_thread_ident($module, /)\n" +"--\n" +"\n" +"Internal only.\n" +"\n" +"Return a non-zero integer that uniquely identifies the main thread of\n" +"the main interpreter."); + +#define _THREAD__GET_MAIN_THREAD_IDENT_METHODDEF \ + {"_get_main_thread_ident", (PyCFunction)_thread__get_main_thread_ident, METH_NOARGS, _thread__get_main_thread_ident__doc__}, + +static PyObject * +_thread__get_main_thread_ident_impl(PyObject *module); + +static PyObject * +_thread__get_main_thread_ident(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _thread__get_main_thread_ident_impl(module); +} + #if (defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS)) PyDoc_STRVAR(_thread__get_name__doc__, @@ -733,6 +1250,10 @@ _thread_set_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb #define _THREAD_RLOCK__AT_FORK_REINIT_METHODDEF #endif /* !defined(_THREAD_RLOCK__AT_FORK_REINIT_METHODDEF) */ +#ifndef _THREAD_GET_NATIVE_ID_METHODDEF + #define _THREAD_GET_NATIVE_ID_METHODDEF +#endif /* !defined(_THREAD_GET_NATIVE_ID_METHODDEF) */ + #ifndef _THREAD__GET_NAME_METHODDEF #define _THREAD__GET_NAME_METHODDEF #endif /* !defined(_THREAD__GET_NAME_METHODDEF) */ @@ -740,4 +1261,4 @@ _thread_set_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb #ifndef _THREAD_SET_NAME_METHODDEF #define _THREAD_SET_NAME_METHODDEF #endif /* !defined(_THREAD_SET_NAME_METHODDEF) */ -/*[clinic end generated code: output=0f1707cbafc0e8f2 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f5b372a20b63d7d5 input=a9049054013a1b77]*/ From 45b9cf22c442ae9f3662e33670b35a2f49e4fd53 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 13 Aug 2026 21:40:17 +0300 Subject: [PATCH 2/2] Use Argument Clinic for the ident getter of _ThreadHandle --- Modules/_threadmodule.c | 14 ++++++++++---- Modules/clinic/_threadmodule.c.h | 21 ++++++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 7b72d65e709972c..e18d3671467aca5 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -678,11 +678,17 @@ PyThreadHandleObject_repr(PyObject *op) Py_TYPE(self)->tp_name, ident); } +/*[clinic input] +@getter +_thread._ThreadHandle.ident +[clinic start generated code]*/ + static PyObject * -PyThreadHandleObject_get_ident(PyObject *op, void *Py_UNUSED(closure)) +_thread__ThreadHandle_ident_get_impl(PyObject *self) +/*[clinic end generated code: output=66d40aa1a624ac23 input=2b3361e34a6ca79d]*/ { - PyThreadHandleObject *self = PyThreadHandleObject_CAST(op); - return PyLong_FromUnsignedLongLong(ThreadHandle_ident(self->handle)); + PyThreadHandleObject *handle = PyThreadHandleObject_CAST(self); + return PyLong_FromUnsignedLongLong(ThreadHandle_ident(handle->handle)); } /*[clinic input] @@ -748,7 +754,7 @@ _thread__ThreadHandle__set_done_impl(PyObject *self) } static PyGetSetDef ThreadHandle_getsetlist[] = { - {"ident", PyThreadHandleObject_get_ident, NULL, NULL}, + _THREAD__THREADHANDLE_IDENT_GETSETDEF {0}, }; diff --git a/Modules/clinic/_threadmodule.c.h b/Modules/clinic/_threadmodule.c.h index 13c1a64d0b456fd..7c95251cd8fde25 100644 --- a/Modules/clinic/_threadmodule.c.h +++ b/Modules/clinic/_threadmodule.c.h @@ -9,6 +9,25 @@ preserve #include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_modsupport.h" // _PyArg_CheckPositional() +#if !defined(_thread__ThreadHandle_ident_DOCSTR) +# define _thread__ThreadHandle_ident_DOCSTR NULL +#endif +#if defined(_THREAD__THREADHANDLE_IDENT_GETSETDEF) +# undef _THREAD__THREADHANDLE_IDENT_GETSETDEF +# define _THREAD__THREADHANDLE_IDENT_GETSETDEF {"ident", (getter)_thread__ThreadHandle_ident_get, (setter)_thread__ThreadHandle_ident_set, _thread__ThreadHandle_ident_DOCSTR}, +#else +# define _THREAD__THREADHANDLE_IDENT_GETSETDEF {"ident", (getter)_thread__ThreadHandle_ident_get, NULL, _thread__ThreadHandle_ident_DOCSTR}, +#endif + +static PyObject * +_thread__ThreadHandle_ident_get_impl(PyObject *self); + +static PyObject * +_thread__ThreadHandle_ident_get(PyObject *self, void *Py_UNUSED(context)) +{ + return _thread__ThreadHandle_ident_get_impl(self); +} + PyDoc_STRVAR(_thread__ThreadHandle_join__doc__, "join($self, timeout=None, /)\n" "--\n" @@ -1261,4 +1280,4 @@ _thread_set_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb #ifndef _THREAD_SET_NAME_METHODDEF #define _THREAD_SET_NAME_METHODDEF #endif /* !defined(_THREAD_SET_NAME_METHODDEF) */ -/*[clinic end generated code: output=f5b372a20b63d7d5 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=cd1b9c78d32ab693 input=a9049054013a1b77]*/