This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author eryksun
Recipients Arfrever, ZackerySpytz, asvetlov, bra, christian.heimes, eryksun, flox, jcea, kovid, pitrou, r.david.murray, socketpair, vstinner
Date 2021-11-15.02:57:39
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1636945060.06.0.558699921751.issue15500@roundup.psfhosted.org>
In-reply-to
Content
Zackery, here's an initial draft implementation for Windows 10+ that's based on the interface you created in PR 14578. It calls WinAPI SetThreadDescription(), which sets the thread's name directly in the kernel thread object (i.e. ETHREAD.ThreadName). This function was added to the API in Windows 10 version 1607 (10.0.14393), so Windows 8.1 systems and older Windows 10 systems aren't supported. I'd rather not implement the exception-based approach for older Windows systems. It doesn't really name the thread and only works when a debugger is attached to the process.

This initial draft blindly assumes that the filesystem encoding is UTF-8. That's been the case in Windows since Python 3.6, unless configured to use the legacy ANSI encoding, e.g. by setting PYTHONLEGACYWINDOWSFSENCODING. PyThread_set_thread_name() could be changed to use CP_ACP instead of CP_UTF8 when legacy mode is enabled.

I set the truncation limit to 64 characters. It could be lowered to 15 to match what you implemented for POSIX, but I think that's too short. The limit could also effectively be removed by truncating to the system limit of 32766 characters. That would be simpler since the code wouldn't have to worry about surrogate pairs.

Python/thread_nt.h:

    typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR);
    static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL;

    static void
    PyThread__init_thread(void)
    {
        // Initialize pSetThreadDescription.
        // Minimum supported Windows version: 10.0.14393 (1607)
        int i = 0;
        LPCWSTR module_names[2] = {
            L"api-ms-win-core-processthreads-l1-1-3", // i.e. kernel32
            L"kernelbase",
        };
        // Most "ms-win-core" API sets (except for COM/WinRT) are implemented
        // by modules that are always loaded, including ntdll, kernelbase, and
        // kernel32, so it's safe to use GetModuleHandleW().
        do {
            pSetThreadDescription = (PF_SET_THREAD_DESCRIPTION)GetProcAddress(
                GetModuleHandleW(module_names[i]), "SetThreadDescription");
        } while (pSetThreadDescription == NULL &&
                 ++i < Py_ARRAY_LENGTH(module_names));
    }

    int
    PyThread_set_thread_name(const char *name)
    {
        HRESULT hr = 0;
        wchar_t *wname = NULL;

        if (!initialized) {
            PyThread_init_thread();
        }
        if (name == NULL || *name == '\0') {
            hr = E_INVALIDARG;
            goto exit;
        }
        if (pSetThreadDescription == NULL) {
            hr = E_NOTIMPL;
            goto exit;
        }

        // cch includes the terminating null character.
        int cch = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0);
        if (cch > 0) {
            wname = PyMem_RawMalloc(cch * sizeof(wchar_t));
            if (wname == NULL) {
                hr = E_OUTOFMEMORY;
                goto exit;
            }
            cch = MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, cch);
        }
        if (cch == 0) {
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto exit;
        }

        // Truncate the name to 64 characters, accounting for surrogate pairs.
        // The OS limit is 32766 wide characters, but long names aren't of
        // practical use.
        int i = 0;
        for (int len = 0; i < (cch - 1) && len < 64; i++, len++) {
            if (i < (cch - 2) && IS_SURROGATE_PAIR(wname[i], wname[i + 1])) {
                i++; // Skip the trailing surrogate.
            }
        }
        wname[i] = L'\0';

        hr = pSetThreadDescription(GetCurrentThread(), wname);

    exit:
        if (wname != NULL) {
            PyMem_RawFree(wname);
        }
        if (FAILED(hr)) {
            return (int)hr;
        }
        return 0;
    }


Modules/_threadmodule.c:

    static PyObject *
    _thread__set_thread_name_impl(PyObject *module, PyObject *name)
    {
        int error = PyThread_set_thread_name(PyBytes_AS_STRING(name));
    #ifdef MS_WINDOWS
        // For Python code, ignore a not-implemented error, which means
        // it's a Windows 8.1 system or older Windows 10 system.
        if (error == (int)E_NOTIMPL) {
            error = 0;
        }
    #endif
        if (error) {
            Py_DECREF(name);
            PyErr_SetString(ThreadError, "setting the thread name failed");
            return NULL;
        }
        Py_DECREF(name);
        Py_RETURN_NONE;
    }
History
Date User Action Args
2021-11-15 02:57:40eryksunsetrecipients: + eryksun, jcea, pitrou, vstinner, christian.heimes, Arfrever, r.david.murray, asvetlov, flox, bra, kovid, socketpair, ZackerySpytz
2021-11-15 02:57:40eryksunsetmessageid: <1636945060.06.0.558699921751.issue15500@roundup.psfhosted.org>
2021-11-15 02:57:40eryksunlinkissue15500 messages
2021-11-15 02:57:39eryksuncreate