support.check__all__(self, threading, ('threading', '_thread'),
extra=extra, not_exported=not_exported)
+ @unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name")
+ @unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name")
+ def test_set_name(self):
+ # set_name() limit in bytes
+ truncate = getattr(_thread, "_NAME_MAXLEN", None)
+ limit = truncate or 100
+
+ tests = [
+ # test short ASCII name
+ "CustomName",
+
+ # test short non-ASCII name
+ "namé€",
+
+ # embedded null character: name is truncated
+ # at the first null character
+ "embed\0null",
+
+ # Test long ASCII names (not truncated)
+ "x" * limit,
+
+ # Test long ASCII names (truncated)
+ "x" * (limit + 10),
+
+ # Test long non-ASCII name (truncated)
+ "x" * (limit - 1) + "é€",
+ ]
+ if os_helper.FS_NONASCII:
+ tests.append(f"nonascii:{os_helper.FS_NONASCII}")
+ if os_helper.TESTFN_UNENCODABLE:
+ tests.append(os_helper.TESTFN_UNENCODABLE)
+
+ if sys.platform.startswith("solaris"):
+ encoding = "utf-8"
+ else:
+ encoding = sys.getfilesystemencoding()
+
+ def work():
+ nonlocal work_name
+ work_name = _thread._get_name()
+
+ for name in tests:
+ encoded = name.encode(encoding, "replace")
+ if b'\0' in encoded:
+ encoded = encoded.split(b'\0', 1)[0]
+ if truncate is not None:
+ encoded = encoded[:truncate]
+ if sys.platform.startswith("solaris"):
+ expected = encoded.decode("utf-8", "surrogateescape")
+ else:
+ expected = os.fsdecode(encoded)
+
+ with self.subTest(name=name, expected=expected):
+ work_name = None
+ thread = threading.Thread(target=work, name=name)
+ thread.start()
+ thread.join()
+ self.assertEqual(work_name, expected,
+ f"{len(work_name)=} and {len(expected)=}")
+
class InterruptMainTests(unittest.TestCase):
def check_interrupt_main_with_signal_handler(self, signum):
__all__.append('get_native_id')
except AttributeError:
_HAVE_THREAD_NATIVE_ID = False
+try:
+ _set_name = _thread.set_name
+except AttributeError:
+ _set_name = None
ThreadError = _thread.error
try:
_CRLock = _thread.RLock
self._set_ident()
if _HAVE_THREAD_NATIVE_ID:
self._set_native_id()
+ if _set_name is not None and self._name:
+ try:
+ _set_name(self._name)
+ except OSError:
+ pass
self._started.set()
with _active_limbo_lock:
_active[self._ident] = self
--- /dev/null
+On Linux, :class:`threading.Thread` now sets the thread name to the
+operating system. Patch by Victor Stinner.
# include <signal.h> // SIGINT
#endif
+#include "clinic/_threadmodule.c.h"
+
// ThreadError is just an alias to PyExc_RuntimeError
#define ThreadError PyExc_RuntimeError
return (thread_module_state *)state;
}
+
+/*[clinic input]
+module _thread
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=be8dbe5cc4b16df7]*/
+
+
// _ThreadHandle type
// Handles state transitions according to the following diagram:
Internal only. Return a non-zero integer that uniquely identifies the main thread\n\
of the main interpreter.");
+
+#ifdef HAVE_PTHREAD_GETNAME_NP
+/*[clinic input]
+_thread._get_name
+
+Get the name of the current thread.
+[clinic start generated code]*/
+
+static PyObject *
+_thread__get_name_impl(PyObject *module)
+/*[clinic end generated code: output=20026e7ee3da3dd7 input=35cec676833d04c8]*/
+{
+ // Linux and macOS are limited to respectively 16 and 64 bytes
+ char name[100];
+ pthread_t thread = pthread_self();
+ int rc = pthread_getname_np(thread, name, Py_ARRAY_LENGTH(name));
+ if (rc) {
+ errno = rc;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+#ifdef __sun
+ return PyUnicode_DecodeUTF8(name, strlen(name), "surrogateescape");
+#else
+ return PyUnicode_DecodeFSDefault(name);
+#endif
+}
+#endif // HAVE_PTHREAD_GETNAME_NP
+
+
+#ifdef HAVE_PTHREAD_SETNAME_NP
+/*[clinic input]
+_thread.set_name
+
+ name as name_obj: unicode
+
+Set the name of the current thread.
+[clinic start generated code]*/
+
+static PyObject *
+_thread_set_name_impl(PyObject *module, PyObject *name_obj)
+/*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/
+{
+#ifdef __sun
+ // Solaris always uses UTF-8
+ const char *encoding = "utf-8";
+#else
+ // Encode the thread name to the filesystem encoding using the "replace"
+ // error handler
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ const char *encoding = interp->unicode.fs_codec.encoding;
+#endif
+ PyObject *name_encoded;
+ name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace");
+ if (name_encoded == NULL) {
+ return NULL;
+ }
+
+#ifdef PYTHREAD_NAME_MAXLEN
+ // Truncate to PYTHREAD_NAME_MAXLEN bytes + the NUL byte if needed
+ size_t len = PyBytes_GET_SIZE(name_encoded);
+ if (len > PYTHREAD_NAME_MAXLEN) {
+ PyObject *truncated;
+ truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded),
+ PYTHREAD_NAME_MAXLEN);
+ if (truncated == NULL) {
+ Py_DECREF(name_encoded);
+ return NULL;
+ }
+ Py_SETREF(name_encoded, truncated);
+ }
+#endif
+
+ const char *name = PyBytes_AS_STRING(name_encoded);
+#ifdef __APPLE__
+ int rc = pthread_setname_np(name);
+#else
+ pthread_t thread = pthread_self();
+ int rc = pthread_setname_np(thread, name);
+#endif
+ Py_DECREF(name_encoded);
+ if (rc) {
+ errno = rc;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+ Py_RETURN_NONE;
+}
+#endif // HAVE_PTHREAD_SETNAME_NP
+
+
static PyMethodDef thread_methods[] = {
{"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread,
METH_VARARGS, start_new_thread_doc},
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_SET_NAME_METHODDEF
+ _THREAD__GET_NAME_METHODDEF
{NULL, NULL} /* sentinel */
};
llist_init(&state->shutdown_handles);
+#ifdef PYTHREAD_NAME_MAXLEN
+ if (PyModule_AddIntConstant(module, "_NAME_MAXLEN",
+ PYTHREAD_NAME_MAXLEN) < 0) {
+ return -1;
+ }
+#endif
+
return 0;
}
--- /dev/null
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+# include "pycore_gc.h" // PyGC_Head
+# include "pycore_runtime.h" // _Py_ID()
+#endif
+#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
+
+#if defined(HAVE_PTHREAD_GETNAME_NP)
+
+PyDoc_STRVAR(_thread__get_name__doc__,
+"_get_name($module, /)\n"
+"--\n"
+"\n"
+"Get the name of the current thread.");
+
+#define _THREAD__GET_NAME_METHODDEF \
+ {"_get_name", (PyCFunction)_thread__get_name, METH_NOARGS, _thread__get_name__doc__},
+
+static PyObject *
+_thread__get_name_impl(PyObject *module);
+
+static PyObject *
+_thread__get_name(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+ return _thread__get_name_impl(module);
+}
+
+#endif /* defined(HAVE_PTHREAD_GETNAME_NP) */
+
+#if defined(HAVE_PTHREAD_SETNAME_NP)
+
+PyDoc_STRVAR(_thread_set_name__doc__,
+"set_name($module, /, name)\n"
+"--\n"
+"\n"
+"Set the name of the current thread.");
+
+#define _THREAD_SET_NAME_METHODDEF \
+ {"set_name", _PyCFunction_CAST(_thread_set_name), METH_FASTCALL|METH_KEYWORDS, _thread_set_name__doc__},
+
+static PyObject *
+_thread_set_name_impl(PyObject *module, PyObject *name_obj);
+
+static PyObject *
+_thread_set_name(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 1
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(name), },
+ };
+ #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[] = {"name", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "set_name",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[1];
+ PyObject *name_obj;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!PyUnicode_Check(args[0])) {
+ _PyArg_BadArgument("set_name", "argument 'name'", "str", args[0]);
+ goto exit;
+ }
+ name_obj = args[0];
+ return_value = _thread_set_name_impl(module, name_obj);
+
+exit:
+ return return_value;
+}
+
+#endif /* defined(HAVE_PTHREAD_SETNAME_NP) */
+
+#ifndef _THREAD__GET_NAME_METHODDEF
+ #define _THREAD__GET_NAME_METHODDEF
+#endif /* !defined(_THREAD__GET_NAME_METHODDEF) */
+
+#ifndef _THREAD_SET_NAME_METHODDEF
+ #define _THREAD_SET_NAME_METHODDEF
+#endif /* !defined(_THREAD_SET_NAME_METHODDEF) */
+/*[clinic end generated code: output=b5cb85aaccc45bf6 input=a9049054013a1b77]*/
MODULE__IO_FALSE
MODULE__IO_TRUE
MODULE_BUILDTYPE
+PYTHREAD_NAME_MAXLEN
TEST_MODULES
OPENSSL_LDFLAGS
OPENSSL_LIBS
then :
printf "%s\n" "#define HAVE_PTHREAD_KILL 1" >>confdefs.h
+fi
+ac_fn_c_check_func "$LINENO" "pthread_getname_np" "ac_cv_func_pthread_getname_np"
+if test "x$ac_cv_func_pthread_getname_np" = xyes
+then :
+ printf "%s\n" "#define HAVE_PTHREAD_GETNAME_NP 1" >>confdefs.h
+
+fi
+ac_fn_c_check_func "$LINENO" "pthread_setname_np" "ac_cv_func_pthread_setname_np"
+if test "x$ac_cv_func_pthread_setname_np" = xyes
+then :
+ printf "%s\n" "#define HAVE_PTHREAD_SETNAME_NP 1" >>confdefs.h
+
fi
ac_fn_c_check_func "$LINENO" "ptsname" "ac_cv_func_ptsname"
if test "x$ac_cv_func_ptsname" = xyes
CPPFLAGS=$save_CPPFLAGS
+# gh-59705: Maximum length in bytes of a thread name
+case "$ac_sys_system" in
+ Linux*) PYTHREAD_NAME_MAXLEN=15;; # Linux and Android
+ SunOS*) PYTHREAD_NAME_MAXLEN=31;;
+ Darwin) PYTHREAD_NAME_MAXLEN=63;;
+ iOS) PYTHREAD_NAME_MAXLEN=63;;
+ FreeBSD*) PYTHREAD_NAME_MAXLEN=98;;
+ *) PYTHREAD_NAME_MAXLEN=;;
+esac
+if test -n "$PYTHREAD_NAME_MAXLEN"; then
+
+printf "%s\n" "#define PYTHREAD_NAME_MAXLEN $PYTHREAD_NAME_MAXLEN" >>confdefs.h
+
+fi
+
+
+
# stdlib
mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \
posix_spawn_file_actions_addclosefrom_np \
- pread preadv preadv2 process_vm_readv pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \
- pthread_kill ptsname ptsname_r pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
+ pread preadv preadv2 process_vm_readv \
+ pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \
+ pthread_kill pthread_getname_np pthread_setname_np \
+ ptsname ptsname_r pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \
sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \
sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \
_RESTORE_VAR([CPPFLAGS])
+# gh-59705: Maximum length in bytes of a thread name
+case "$ac_sys_system" in
+ Linux*) PYTHREAD_NAME_MAXLEN=15;; # Linux and Android
+ SunOS*) PYTHREAD_NAME_MAXLEN=31;;
+ Darwin) PYTHREAD_NAME_MAXLEN=63;;
+ iOS) PYTHREAD_NAME_MAXLEN=63;;
+ FreeBSD*) PYTHREAD_NAME_MAXLEN=98;;
+ *) PYTHREAD_NAME_MAXLEN=;;
+esac
+if test -n "$PYTHREAD_NAME_MAXLEN"; then
+ AC_DEFINE_UNQUOTED([PYTHREAD_NAME_MAXLEN], [$PYTHREAD_NAME_MAXLEN],
+ [Maximum length in bytes of a thread name])
+fi
+AC_SUBST([PYTHREAD_NAME_MAXLEN])
+
+
# stdlib
AC_DEFUN([PY_STDLIB_MOD_SET_NA], [
m4_foreach([mod], [$@], [
/* Define to 1 if you have the `pthread_getcpuclockid' function. */
#undef HAVE_PTHREAD_GETCPUCLOCKID
+/* Define to 1 if you have the `pthread_getname_np' function. */
+#undef HAVE_PTHREAD_GETNAME_NP
+
/* Define to 1 if you have the <pthread.h> header file. */
#undef HAVE_PTHREAD_H
/* Define to 1 if you have the `pthread_kill' function. */
#undef HAVE_PTHREAD_KILL
+/* Define to 1 if you have the `pthread_setname_np' function. */
+#undef HAVE_PTHREAD_SETNAME_NP
+
/* Define to 1 if you have the `pthread_sigmask' function. */
#undef HAVE_PTHREAD_SIGMASK
/* Define as the preferred size in bits of long digits */
#undef PYLONG_BITS_IN_DIGIT
+/* Maximum length in bytes of a thread name */
+#undef PYTHREAD_NAME_MAXLEN
+
/* enabled builtin hash modules */
#undef PY_BUILTIN_HASHLIB_HASHES