Commit d756f901 authored by shrabbit's avatar shrabbit
Browse files

feat(SubProgram): 添加 SubProgram 游乐场并使用 Job Object 管理 worker 子进程

parent 9d978fad
Loading
Loading
Loading
Loading
+131 −0
Original line number Diff line number Diff line
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace TestV.Playgrounds;

public class SubProgram : IPlayground
{
    public string LaunchCmd { get; } = "subp";
    public string? Name { get; } = "sub program";
    public string? Description { get; } = "Create sub program.";

    // Job Object 句柄,生命周期应跟随父进程
    private static IntPtr _jobHandle;
    private static readonly object _lock = new();

    public async Task Run(string[] args)
    {
        EnsureJobObject();

        var psi = new ProcessStartInfo("worker.exe")
        {
            UseShellExecute = false, // 必须为 false 才能获取进程句柄
        };

        using var process = Process.Start(psi);
        if (process == null)
            throw new InvalidOperationException("Failed to start worker.exe");

        // 将子进程分配到 Job Object
        if (!AssignProcessToJobObject(_jobHandle, process.Handle))
        {
            var error = Marshal.GetLastWin32Error();
            process.Kill();
            throw new Win32Exception(error, "AssignProcessToJobObject failed");
        }

        // 注意:不要在这里 Dispose process,否则句柄关闭后 Job 关联可能失效
        // 如果需要等待子进程结束,可以 await process.WaitForExitAsync()
    }

    private static void EnsureJobObject()
    {
        if (_jobHandle != IntPtr.Zero) return;
        lock (_lock)
        {
            if (_jobHandle != IntPtr.Zero) return;

            // 创建 Job Object,设置 KILL_ON_JOB_CLOSE 标志
            _jobHandle = CreateJobObject(IntPtr.Zero, null);
            if (_jobHandle == IntPtr.Zero)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            var info = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
            {
                BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION
                {
                    LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
                }
            };

            if (!SetInformationJobObject(
                    _jobHandle,
                    JobObjectExtendedLimitInformation,
                    ref info,
                    Marshal.SizeOf<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>()))
            {
                CloseHandle(_jobHandle);
                _jobHandle = IntPtr.Zero;
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
    }

    #region P/Invoke

    private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
    private const int JobObjectExtendedLimitInformation = 9;

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string? name);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool SetInformationJobObject(
        IntPtr hJob, int infoClass,
        ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInfo, int cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    [StructLayout(LayoutKind.Sequential)]
    private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
    {
        public long PerProcessUserTimeLimit;
        public long PerJobUserTimeLimit;
        public uint LimitFlags;
        public UIntPtr MinimumWorkingSetSize;
        public UIntPtr MaximumWorkingSetSize;
        public uint ActiveProcessLimit;
        public UIntPtr Affinity;
        public uint PriorityClass;
        public uint SchedulingClass;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct IO_COUNTERS
    {
        public ulong ReadOperationCount;
        public ulong WriteOperationCount;
        public ulong OtherOperationCount;
        public ulong ReadTransferCount;
        public ulong WriteTransferCount;
        public ulong OtherTransferCount;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
    {
        public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
        public IO_COUNTERS IoInfo;
        public UIntPtr ProcessMemoryLimit;
        public UIntPtr JobMemoryLimit;
        public UIntPtr PeakProcessMemoryUsed;
        public UIntPtr PeakJobMemoryUsed;
    }

    #endregion
}
 No newline at end of file
+4 −0
Original line number Diff line number Diff line
@@ -43,4 +43,8 @@
      </None>
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\Worker\Worker.csproj" />
    </ItemGroup>

</Project>
+1 −0
Original line number Diff line number Diff line
d4f9ca4888caa2bfb80e0513552d203ddb461e3d36d30352663c8a7b6898ceb5