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
}