Skip to content

Commit 3fa1785

Browse files
committed
obtain SeLockMemoryPrivilege on startup so that huge pages
can be used on Windows
1 parent 8e211b1 commit 3fa1785

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

Doc/using/cmdline.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,14 @@ conflict.
11131113
and kill the process. Only enable this in environments where the
11141114
huge-page pool is properly sized and fork-safety is not a concern.
11151115

1116+
On Windows you need a special privilege. See the
1117+
`Windows documentation for large pages
1118+
<https://learn.microsoft.com/windows/win32/memory/large-page-support>`_
1119+
for details. Python will fail on startup if the required privilege
1120+
`SeLockMemoryPrivilege
1121+
<https://learn.microsoft.com/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/lock-pages-in-memory>`_
1122+
is not held by the user.
1123+
11161124
.. versionadded:: 3.15
11171125

11181126

Doc/using/configure.rst

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -792,9 +792,18 @@ also be used to improve performance.
792792

793793
Even when compiled with this option, huge pages are **not** used at runtime
794794
unless the :envvar:`PYTHON_PYMALLOC_HUGEPAGES` environment variable is set
795-
to ``1``. This opt-in is required because huge pages carry risks on Linux:
796-
if the huge-page pool is exhausted, page faults (including copy-on-write
797-
faults after :func:`os.fork`) deliver ``SIGBUS`` and kill the process.
795+
to ``1``. This opt-in is required because huge pages
796+
797+
* carry risks on Linux: if the huge-page pool is exhausted, page faults
798+
(including copy-on-write faults after :func:`os.fork`) deliver ``SIGBUS``
799+
and kill the process.
800+
801+
* need a special privilege on Windows. See the `Windows documentation for large pages
802+
<https://learn.microsoft.com/windows/win32/memory/large-page-support>`_
803+
for details. Python will fail on startup if the required privilege
804+
`SeLockMemoryPrivilege
805+
<https://learn.microsoft.com/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/lock-pages-in-memory>`_
806+
is not held by the user.
798807

799808
The configure script checks that the platform supports ``MAP_HUGETLB``
800809
and emits a warning if it is not available.

Python/pylifecycle.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
#include "pycore_jit.h" // _PyJIT_Fini()
4141
#endif
4242

43+
#if defined(PYMALLOC_USE_HUGEPAGES) && defined(MS_WINDOWS)
44+
#include <Windows.h>
45+
#endif
46+
4347
#include "opcode.h"
4448

4549
#include <locale.h> // setlocale()
@@ -485,6 +489,39 @@ pyinit_core_reconfigure(_PyRuntimeState *runtime,
485489
return _PyStatus_OK();
486490
}
487491

492+
#if defined(PYMALLOC_USE_HUGEPAGES) && defined(MS_WINDOWS)
493+
static PyStatus
494+
get_huge_pages_privilege(void)
495+
{
496+
HANDLE hToken;
497+
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
498+
{
499+
return _PyStatus_ERR("failed to open process token");
500+
}
501+
TOKEN_PRIVILEGES tp;
502+
if (!LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &tp.Privileges[0].Luid))
503+
{
504+
CloseHandle(hToken);
505+
return _PyStatus_ERR("failed to lookup SeLockMemoryPrivilege for huge pages");
506+
}
507+
tp.PrivilegeCount = 1;
508+
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
509+
// AdjustTokenPrivileges can return with nonzero status (i.e. success)
510+
// but without having all privileges adjusted (ERROR_NOT_ALL_ASSIGNED).
511+
BOOL status = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
512+
DWORD error = GetLastError();
513+
if (!status || (error != ERROR_SUCCESS))
514+
{
515+
CloseHandle(hToken);
516+
return _PyStatus_ERR("failed to obtain SeLockMemoryPrivilege for huge pages");
517+
}
518+
if (!CloseHandle(hToken))
519+
{
520+
return _PyStatus_ERR("failed to close process token handle");
521+
}
522+
return _PyStatus_OK();
523+
}
524+
#endif
488525

489526
static PyStatus
490527
pycore_init_runtime(_PyRuntimeState *runtime,
@@ -499,6 +536,15 @@ pycore_init_runtime(_PyRuntimeState *runtime,
499536
return status;
500537
}
501538

539+
#if defined(PYMALLOC_USE_HUGEPAGES) && defined(MS_WINDOWS)
540+
if (runtime->allocators.use_hugepages) {
541+
status = get_huge_pages_privilege();
542+
if (_PyStatus_EXCEPTION(status)) {
543+
return status;
544+
}
545+
}
546+
#endif
547+
502548
/* Py_Finalize leaves _Py_Finalizing set in order to help daemon
503549
* threads behave a little more gracefully at interpreter shutdown.
504550
* We clobber it here so the new interpreter can start with a clean

0 commit comments

Comments
 (0)