diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 2e7ea7b4fc4cba..483490e13c5fa8 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -1113,6 +1113,14 @@ conflict. and kill the process. Only enable this in environments where the huge-page pool is properly sized and fork-safety is not a concern. + On Windows you need a special privilege. See the + `Windows documentation for large pages + `_ + for details. Python will fail on startup if the required privilege + `SeLockMemoryPrivilege + `_ + is not held by the user. + .. versionadded:: 3.15 diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index 50812358a690c3..763267c6e190e8 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -792,9 +792,18 @@ also be used to improve performance. Even when compiled with this option, huge pages are **not** used at runtime unless the :envvar:`PYTHON_PYMALLOC_HUGEPAGES` environment variable is set - to ``1``. This opt-in is required because huge pages carry risks on Linux: - if the huge-page pool is exhausted, page faults (including copy-on-write - faults after :func:`os.fork`) deliver ``SIGBUS`` and kill the process. + to ``1``. This opt-in is required because huge pages + + * carry risks on Linux: if the huge-page pool is exhausted, page faults + (including copy-on-write faults after :func:`os.fork`) deliver ``SIGBUS`` + and kill the process. + + * need a special privilege on Windows. See the `Windows documentation for large pages + `_ + for details. Python will fail on startup if the required privilege + `SeLockMemoryPrivilege + `_ + is not held by the user. The configure script checks that the platform supports ``MAP_HUGETLB`` and emits a warning if it is not available. diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 7dfeb5b847b254..f14f96c0a639e7 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -40,6 +40,10 @@ #include "pycore_jit.h" // _PyJIT_Fini() #endif +#if defined(PYMALLOC_USE_HUGEPAGES) && defined(MS_WINDOWS) +#include +#endif + #include "opcode.h" #include // setlocale() @@ -485,6 +489,39 @@ pyinit_core_reconfigure(_PyRuntimeState *runtime, return _PyStatus_OK(); } +#if defined(PYMALLOC_USE_HUGEPAGES) && defined(MS_WINDOWS) +static PyStatus +get_huge_pages_privilege(void) +{ + HANDLE hToken; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { + return _PyStatus_ERR("failed to open process token"); + } + TOKEN_PRIVILEGES tp; + if (!LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &tp.Privileges[0].Luid)) + { + CloseHandle(hToken); + return _PyStatus_ERR("failed to lookup SeLockMemoryPrivilege for huge pages"); + } + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + // AdjustTokenPrivileges can return with nonzero status (i.e. success) + // but without having all privileges adjusted (ERROR_NOT_ALL_ASSIGNED). + BOOL status = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); + DWORD error = GetLastError(); + if (!status || (error != ERROR_SUCCESS)) + { + CloseHandle(hToken); + return _PyStatus_ERR("failed to obtain SeLockMemoryPrivilege for huge pages"); + } + if (!CloseHandle(hToken)) + { + return _PyStatus_ERR("failed to close process token handle"); + } + return _PyStatus_OK(); +} +#endif static PyStatus pycore_init_runtime(_PyRuntimeState *runtime, @@ -499,6 +536,15 @@ pycore_init_runtime(_PyRuntimeState *runtime, return status; } +#if defined(PYMALLOC_USE_HUGEPAGES) && defined(MS_WINDOWS) + if (runtime->allocators.use_hugepages) { + status = get_huge_pages_privilege(); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + } +#endif + /* Py_Finalize leaves _Py_Finalizing set in order to help daemon * threads behave a little more gracefully at interpreter shutdown. * We clobber it here so the new interpreter can start with a clean