@samitouri / QOSAMI-WSL / commits / 53a2e7f8

device host: use a fresh job object per device host process (#40764)

* device host: use a fresh job object per device host process DeviceHostProxy assigned every device host process to a single shared kill-on-close job object. Because the system places each newly launched process in its own job, once the shared job had been assigned to one such process it could no longer accept a process belonging to an unrelated job, so subsequent AssignProcessToJobObject calls could fail with ERROR_ACCESS_DENIED and leave that device host process out of the kill-on-close job. Create a fresh kill-on-close job per device host process, keyed by process id, so each assignment targets an empty job and always succeeds. The jobs are held for the proxy's lifetime, so device host processes are still terminated when the VM shuts down. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * device host: simplify per-process job tracking to a vector RegisterDeviceHost is a one-time init callback per device host process, so the process-id keyed map and contains() dedup were unnecessary and introduced a pid-reuse hazard (a recycled pid could skip job assignment for a new process). Hold the per-process kill-on-close jobs in a plain vector instead. Each registration assigns its process to a fresh job; the jobs live for the proxy's lifetime so the device host processes are still terminated on VM shutdown. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Ben Hillis committed Jun 11, 2026 at 08:34 UTC 53a2e7f806096c47aec88ab4599c80245ab8b5fd
2 files changed +24 -10
src/windows/common/DeviceHostProxy.cpp
+21 -9
@@ -27,10 +27,6 @@ DeviceHostProxy::DeviceHostProxy(const std::wstring& VmId, const GUID& RuntimeId
27 {
28 m_devicesShutdown = false;
29 m_git = wil::CoCreateInstance<IGlobalInterfaceTable>(CLSID_StdGlobalInterfaceTable, CLSCTX_INPROC_SERVER);
30 -
31 - // Create a job object that will terminate device host processes when this proxy is destroyed
32 - // (i.e., when the VM shuts down).
33 - m_jobObject = wsl::windows::common::helpers::CreateKillOnCloseJob();
30 }
31
32 GUID DeviceHostProxy::AddNewDevice(const GUID& Type, const wil::com_ptr<IPlan9FileSystem>& Plan9Fs, const std::wstring& VirtIoTag)
@@ -157,12 +153,28 @@ try
153 const wil::com_ptr<IUnknown> unknown = remoteHost.query<IUnknown>();
154 THROW_IF_FAILED(proxyDeviceHost(m_system.get(), unknown.get(), ProcessId, IpcSectionHandle));
155
160 - // Add the device host process to the job object so it is terminated when the VM shuts down.
161 - wil::unique_handle process(OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, FALSE, ProcessId));
162 - LOG_LAST_ERROR_IF_MSG(!process, "Failed to open device host process %u for job assignment", ProcessId);
163 - if (process)
156 + // Assign the device host process to a fresh kill-on-close job so it is terminated when the VM
157 + // shuts down. Each process needs its own job: a process the system has already placed in a job
158 + // cannot be assigned to a job that already owns a different process (ERROR_ACCESS_DENIED).
159 {
165 - LOG_IF_WIN32_BOOL_FALSE(AssignProcessToJobObject(m_jobObject.get(), process.get()));
160 + auto lock = m_devicesLock.lock_exclusive();
161 + if (!m_devicesShutdown)
162 + {
163 + wil::unique_handle process(OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, FALSE, ProcessId));
164 + LOG_LAST_ERROR_IF_MSG(!process, "Failed to open device host process %u for job assignment", ProcessId);
165 + if (process)
166 + {
167 + wil::unique_handle job = wsl::windows::common::helpers::CreateKillOnCloseJob();
168 + if (AssignProcessToJobObject(job.get(), process.get()))
169 + {
170 + m_processJobs.emplace_back(std::move(job));
171 + }
172 + else
173 + {
174 + LOG_LAST_ERROR_MSG("Failed to assign device host process %u to job object", ProcessId);
175 + }
176 + }
177 + }
178 }
179
180 return S_OK;
src/windows/common/DeviceHostProxy.h
+3 -1
@@ -114,7 +114,9 @@ private:
114 std::map<GUID, DeviceHostProxyEntry, wsl::windows::common::helpers::GuidLess> m_devices;
115 bool m_devicesShutdown;
116
117 - wil::unique_handle m_jobObject;
117 + // A kill-on-close job per device host process, held for the proxy's lifetime so the
118 + // processes are terminated when the VM shuts down. Guarded by m_devicesLock.
119 + std::vector<wil::unique_handle> m_processJobs;
120
121 static constexpr LPCWSTR c_hdvModuleName = L"vmdevicehost.dll";
122 static constexpr LPCWSTR c_vmwpctrlModuleName = L"vmwpctrl.dll";