@samitouri / QOSAMI-WSL / commits / 2e96d726

Avoid distro zombie state when wsl init dies in systemd mode (#40433)

This PR adds a init-watcher process to monitor the wsl init when systemd mode is enabled. --------- Co-authored-by: Copilot <copilot@github.com>

Feng Wang committed May 13, 2026 at 13:34 UTC 2e96d726a7fff0a056cb8ccf9894162b18651f2f
3 files changed +89 -17
src/linux/init/init.cpp
+50 -1
@@ -160,6 +160,8 @@ void WaitForBootProcess(wsl::linux::WslDistributionConfig& Config);
160
161 wil::unique_fd UnmarshalConsoleFromServer(int MessageFd, LXBUS_IPC_CONSOLE_ID ConsoleId);
162
163 +int WslInitWatcher(int Argc, char** Argv);
164 +
165 int WslEntryPoint(int Argc, char* Argv[])
166 {
167 //
@@ -222,6 +224,10 @@ int WslEntryPoint(int Argc, char* Argv[])
224 {
225 ExitCode = GenerateUserSystemdUnits(Argc, Argv);
226 }
227 + else if (strcmp(BaseName, LX_INIT_WSL_INIT_WATCHER) == 0)
228 + {
229 + ExitCode = WslInitWatcher(Argc, Argv);
230 + }
231 else
232 {
233 // Handle the special case for import result messages, everything else is sent to the binfmt interpreter.
@@ -2396,6 +2402,16 @@ Return Value:
2402 _exit(1);
2403 }
2404
2405 + //
2406 + // Fork a watcher process that monitors WSL init and tears down
2407 + // the PID namespace if it exits unexpectedly.
2408 + //
2409 +
2410 + UtilCreateChildProcess(LX_INIT_WSL_INIT_WATCHER, [&]() {
2411 + execl(LX_INIT_PATH, LX_INIT_WSL_INIT_WATCHER, static_cast<char*>(nullptr));
2412 + LOG_ERROR("execl({}) failed {}", LX_INIT_WSL_INIT_WATCHER, errno);
2413 + });
2414 +
2415 //
2416 // Keep track of the new pid for WSL init.
2417 //
@@ -3488,4 +3504,37 @@ void WaitForBootProcess(wsl::linux::WslDistributionConfig& Config)
3504 LOG_ERROR("{} failed to start within {}ms", INIT_PATH, Config.BootInitTimeout);
3505 }
3506 }
3491 -}
\ No newline at end of file
3507 +}
3508 +
3509 +int WslInitWatcher(int Argc, char** Argv)
3510 +{
3511 + // Ignore log initialization failure. Not critical.
3512 + InitializeLogging(false);
3513 +
3514 + UtilSetThreadName(LX_INIT_WSL_INIT_WATCHER);
3515 +
3516 + const pid_t wslInitPid = getppid();
3517 + const int pidfd = syscall(SYS_pidfd_open, wslInitPid, 0);
3518 + if (pidfd < 0)
3519 + {
3520 + LOG_ERROR("pidfd_open failed {}", errno);
3521 + _exit(1);
3522 + }
3523 +
3524 + pollfd pfd{pidfd, POLLIN, 0};
3525 + int rc;
3526 + while ((rc = poll(&pfd, 1, -1)) < 0 && errno == EINTR)
3527 + {
3528 + }
3529 + if (rc <= 0 || (pfd.revents & POLLIN) == 0)
3530 + {
3531 + LOG_ERROR("poll failed {} {}", rc, errno);
3532 + _exit(1);
3533 + }
3534 +
3535 + LOG_ERROR("wsl init has exited, shutting down the distro");
3536 +
3537 + // Teardown the current PID namespace. Not shutting down the VM.
3538 + reboot(RB_POWER_OFF);
3539 + _exit(1);
3540 +}
src/shared/inc/lxinitshared.h
+2
@@ -245,6 +245,8 @@ Abstract:
245
246 #define LX_INIT_WSL_USER_GENERATOR "wsl-user-generator"
247
248 +#define LX_INIT_WSL_INIT_WATCHER "init-watcher"
249 +
250 //
251 // WSL2-specific environment variables.
252 //
test/windows/UnitTests.cpp
+37 -16
@@ -393,6 +393,26 @@ class UnitTests
393 }
394 }
395
396 + WSL2_TEST_METHOD(SystemdKillInitTerminatesDistro)
397 + {
398 + WslConfigChange config(LxssGenerateTestConfig() + L"[general]\ninstanceIdleTimeout=-1");
399 + auto revert = EnableSystemd("initTimeout=0");
400 + // Wait for systemd to start
401 + VERIFY_NO_THROW(wsl::shared::retry::RetryWithTimeout<void>(
402 + [&]() { THROW_HR_IF(E_UNEXPECTED, !IsSystemdRunning(L"--system")); }, std::chrono::seconds(1), std::chrono::minutes(1)));
403 +
404 + // Kill the WSL init process
405 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"kill -9 2"), 0L);
406 +
407 + // Wait for the distro to exit.
408 + VERIFY_NO_THROW(wsl::shared::retry::RetryWithTimeout<void>(
409 + [&]() { THROW_HR_IF(E_ABORT, GetDistroState() == LxssDistributionStateRunning); }, std::chrono::seconds(1), std::chrono::seconds(30)));
410 +
411 + // Verify that a new WSL command succeeds (the distro restarts cleanly).
412 + auto [out, err] = LxsstuLaunchWslAndCaptureOutput(L"echo hello");
413 + VERIFY_ARE_EQUAL(out, L"hello\n");
414 + }
415 +
416 TEST_METHOD(Dup)
417 {
418 VERIFY_NO_THROW(LxsstuRunTest(L"/data/test/wsl_unit_tests dup", L"Dup"));
@@ -6152,31 +6172,32 @@ Error code: Wsl/InstallDistro/WSL_E_INVALID_JSON\r\n",
6172 }
6173 }
6174
6155 - TEST_METHOD(DistroTimeout)
6175 + static LxssDistributionState GetDistroState()
6176 {
6157 - WslConfigChange config(LxssGenerateTestConfig() + L"[general]\ninstanceIdleTimeout=-1");
6158 - auto distroId = GetDistributionId(LXSS_DISTRO_NAME_TEST_L);
6159 -
6160 - auto getDistroState = [&]() {
6161 - wsl::windows::common::SvcComm service;
6177 + wsl::windows::common::SvcComm service;
6178
6163 - for (const auto& e : service.EnumerateDistributions())
6179 + for (const auto& e : service.EnumerateDistributions())
6180 + {
6181 + if (wsl::shared::string::IsEqual(e.DistroName, LXSS_DISTRO_NAME_TEST_L))
6182 {
6165 - if (wsl::shared::string::IsEqual(e.DistroName, LXSS_DISTRO_NAME_TEST_L))
6166 - {
6167 - return e.State;
6168 - }
6183 + return e.State;
6184 }
6185 + }
6186
6171 - return LxssDistributionStateInvalid;
6172 - };
6187 + return LxssDistributionStateInvalid;
6188 + }
6189 +
6190 + TEST_METHOD(DistroTimeout)
6191 + {
6192 + WslConfigChange config(LxssGenerateTestConfig() + L"[general]\ninstanceIdleTimeout=-1");
6193 + auto distroId = GetDistributionId(LXSS_DISTRO_NAME_TEST_L);
6194
6195 // Validate that distributions don't time out when timeout is -1
6196 {
6197 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"echo OK"), 0L);
6198
6199 std::this_thread::sleep_for(std::chrono::seconds(20));
6179 - VERIFY_ARE_EQUAL(getDistroState(), LxssDistributionStateRunning);
6200 + VERIFY_ARE_EQUAL(GetDistroState(), LxssDistributionStateRunning);
6201 }
6202
6203 // Validate that distributions time out when timeout value is > 0
@@ -6190,7 +6211,7 @@ Error code: Wsl/InstallDistro/WSL_E_INVALID_JSON\r\n",
6211 unsigned long iterations = 0;
6212 while (std::chrono::steady_clock::now() < deadline)
6213 {
6193 - if (getDistroState() == LxssDistributionStateInstalled)
6214 + if (GetDistroState() == LxssDistributionStateInstalled)
6215 {
6216 LogInfo("Distribution stopped after %lu iterations", iterations);
6217 return;
@@ -6200,7 +6221,7 @@ Error code: Wsl/InstallDistro/WSL_E_INVALID_JSON\r\n",
6221 iterations++;
6222 }
6223
6203 - LogError("Distribution failed to time out after %lu iterations. State: %i", iterations, getDistroState());
6224 + LogError("Distribution failed to time out after %lu iterations. State: %i", iterations, GetDistroState());
6225 VERIFY_FAIL();
6226 }
6227 }