You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows进程退出后资源未释放:JOB_OBJECT_MSG_EXIT_PROCESS通知问题

构建系统进程退出后的资源竞争问题

问题流程

  • 父进程创建子进程;
  • 子进程创建文件后未关闭句柄即退出;
  • 父进程等待JOB_OBJECT_MSG_EXIT_PROCESS通知以确认子进程退出;
  • 父进程尝试打开子进程创建的文件时,出现ERROR_SHARING_VIOLATION错误。

现象分析

通过Windows Performance Analyzer追踪发现,文件打开操作发生在子进程生命周期结束后,但文件清理操作仍在进行。此时进程处于僵尸状态,进程句柄尚未被触发,使用WaitForSingleObject进行阻塞等待可解决该竞争问题。

该现象在多台Windows 10/11系统中均一致,即使禁用杀毒软件也是如此。这一情况超出预期,似乎降低了JOB_OBJECT_MSG_EXIT_PROCESS通知的实用性。

疑问

Windows是否存在进程已退出但API可见的副作用仍在持续的状态?若存在,是否必须等待进程句柄被触发,才能可靠地等待该“僵尸状态”结束?

(注:等待JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO似乎也能解决问题,但在本应用场景中并不适用。)

复现代码

/**
 * Repro case demonstrating resources held after JOB_OBJECT_MSG_EXIT_PROCESS
 */
#include <stdio.h>
#include <windows.h>

// Test condition and panic out after tracing the offending line number on error
#define CHECK(cond) (check_at((cond), __LINE__))
static void check_at(_Bool cond, unsigned int line) {
    if(!cond) {
        fprintf(stderr, "%s:%u: failed\n", __FILE__, line);
        ExitProcess(1);
    }
}

// Operate as child process create the file and leave cleaning up to the OS
static void child(const WCHAR filename[]) {
    HANDLE handle = CreateFileW(filename, GENERIC_WRITE, FILE_SHARE_READ, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    CHECK(handle != INVALID_HANDLE_VALUE);
    static const char payload[0x10000 /* ≥5 bytes required */];
    DWORD written;
    CHECK(WriteFile(handle, payload, sizeof payload, &written, NULL));
    CHECK(written == sizeof payload);
}

// Operate as parent executing the child and waiting for completion before
// accessing the file
static const char *parent(const WCHAR filename[], WCHAR app_name[], BOOL poll) {
    // Use job object reporting status to an I/O completion port to wait for exit.
    // Plain WaitForSingleObject on the child fails to reproduce the issue
    HANDLE job = CreateJobObjectW(NULL, NULL);
    CHECK(job);
    HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    CHECK(iocp);
    JOBOBJECT_ASSOCIATE_COMPLETION_PORT assoc = { .CompletionPort = iocp };
    CHECK(SetInformationJobObject(job,
        JobObjectAssociateCompletionPortInformation, &assoc, sizeof assoc));

    // Associate job with queue immediately from startup (Windows ≥10 required)
    STARTUPINFOEXW si = { .StartupInfo.cb = sizeof si };
    size_t space = 0;
    while(!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &space))
        si.lpAttributeList = _alloca(space);
    DWORD proc_attrib = 0x0002000DU /* PROC_THREAD_ATTRIBUTE_JOB_LIST */;
    CHECK(UpdateProcThreadAttribute(si.lpAttributeList, 0, proc_attrib, &job,
        sizeof job, NULL, NULL));

    // Pass a second dummy command line argument to trigger child behavior
    WCHAR command_line[] = L"dummy recurse";
    PROCESS_INFORMATION pi;
    CHECK(CreateProcessW(app_name, command_line, NULL, NULL, TRUE,
        EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi));

    // Wait for successful process to exit
    DWORD event;
    OVERLAPPED *overlapped;
    do {
        ULONG_PTR key;
        CHECK(GetQueuedCompletionStatus(iocp, &event, &key, &overlapped, INFINITE));
    } while(event != JOB_OBJECT_MSG_EXIT_PROCESS &&
        event != JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS);
    CHECK((ULONG_PTR) overlapped == pi.dwProcessId);

    // Poll and verify yet to be signaled or block until signaled
    if(poll)
        CHECK(WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);
    else
        CHECK(WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_OBJECT_0);

    // Verify successful exit
    DWORD exit_code;
    CHECK(GetExitCodeProcess(pi.hProcess, &exit_code));
    CHECK(exit_code == 0);

    // Try to read the expected output file
    HANDLE handle = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(handle != INVALID_HANDLE_VALUE) {
        CHECK(CloseHandle(handle));
        return "PASS";
    } else {
        CHECK(GetLastError() == ERROR_SHARING_VIOLATION);
        return "FAIL";
    }
}

int wmain(int argc, WCHAR *argv[]) {
    const WCHAR *filename = L"shared_file";
    if(argc == 1) {
        printf("polling:  %s\n", parent(filename, argv[0], TRUE));
        printf("blocking: %s\n", parent(filename, argv[0], FALSE));
    } else {
        child(filename);
    }
    return 0;
}

内容的提问来源于stack exchange,提问作者doynax

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 03:58:10