Skip to content

Commit

Permalink
gh-116195: Implements a fast path for nt.getppid (GH-116205)
Browse files Browse the repository at this point in the history
Use the NtQueryInformationProcess system call to efficiently retrieve the parent process ID in a single step, rather than using the process snapshots API which retrieves large amounts of unnecessary information and is more prone to failure (since it makes heap allocations).

Includes a fallback to the original win32_getppid implementation in case the unstable API appears to return strange results.
  • Loading branch information
vxiiduu authored Mar 14, 2024
1 parent 7bbb9b5 commit be1c808
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improves performance of :func:`os.getppid` by using an alternate system API when available. Contributed by vxiiduu.
86 changes: 84 additions & 2 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -9115,16 +9115,98 @@ os_setpgrp_impl(PyObject *module)
#ifdef HAVE_GETPPID

#ifdef MS_WINDOWS
#include <processsnapshot.h>
#include <winternl.h>
#include <ProcessSnapshot.h>

// The structure definition in winternl.h may be incomplete.
// This structure is the full version from the MSDN documentation.
typedef struct _PROCESS_BASIC_INFORMATION_FULL {
NTSTATUS ExitStatus;
PVOID PebBaseAddress;
ULONG_PTR AffinityMask;
LONG BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION_FULL;

typedef NTSTATUS (NTAPI *PNT_QUERY_INFORMATION_PROCESS) (
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL);

// This function returns the process ID of the parent process.
// Returns 0 on failure.
static ULONG
win32_getppid_fast(void)
{
NTSTATUS status;
HMODULE ntdll;
PNT_QUERY_INFORMATION_PROCESS pNtQueryInformationProcess;
PROCESS_BASIC_INFORMATION_FULL basic_information;
static ULONG cached_ppid = 0;

if (cached_ppid) {
// No need to query the kernel again.
return cached_ppid;
}

ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) {
return 0;
}

pNtQueryInformationProcess = (PNT_QUERY_INFORMATION_PROCESS) GetProcAddress(ntdll, "NtQueryInformationProcess");
if (!pNtQueryInformationProcess) {
return 0;
}

status = pNtQueryInformationProcess(GetCurrentProcess(),
ProcessBasicInformation,
&basic_information,
sizeof(basic_information),
NULL);

if (!NT_SUCCESS(status)) {
return 0;
}

// Perform sanity check on the parent process ID we received from NtQueryInformationProcess.
// The check covers values which exceed the 32-bit range (if running on x64) as well as
// zero and (ULONG) -1.

if (basic_information.InheritedFromUniqueProcessId == 0 ||
basic_information.InheritedFromUniqueProcessId >= ULONG_MAX)
{
return 0;
}

// Now that we have reached this point, the BasicInformation.InheritedFromUniqueProcessId
// structure member contains a ULONG_PTR which represents the process ID of our parent
// process. This process ID will be correctly returned even if the parent process has
// exited or been terminated.

cached_ppid = (ULONG) basic_information.InheritedFromUniqueProcessId;
return cached_ppid;
}

static PyObject*
win32_getppid(void)
{
DWORD error;
PyObject* result = NULL;
HANDLE process = GetCurrentProcess();

HPSS snapshot = NULL;
ULONG pid;

pid = win32_getppid_fast();
if (pid != 0) {
return PyLong_FromUnsignedLong(pid);
}

// If failure occurs in win32_getppid_fast(), fall back to using the PSS API.

error = PssCaptureSnapshot(process, PSS_CAPTURE_NONE, 0, &snapshot);
if (error != ERROR_SUCCESS) {
return PyErr_SetFromWindowsErr(error);
Expand Down

0 comments on commit be1c808

Please sign in to comment.