Skip to content

Avoid holding the instance lock while waiting for OOBE - #41387

Open
Sylvain MOLINIER (SylvainM98) wants to merge 2 commits into
microsoft:masterfrom
SylvainM98:fix/wslservice-oobe-shutdown-deadlock
Open

Avoid holding the instance lock while waiting for OOBE#41387
Sylvain MOLINIER (SylvainM98) wants to merge 2 commits into
microsoft:masterfrom
SylvainM98:fix/wslservice-oobe-shutdown-deadlock

Conversation

@SylvainM98

Copy link
Copy Markdown

Summary of the Pull Request

Process creation can wait for a distribution's OOBE to complete while holding
WslCoreInstance::m_lock. Because Stop() requires the same lock before it can
signal m_destroyingEvent, terminating the distribution can deadlock until the
OOBE exits on its own.

This change releases the instance lock while waiting and waits for either OOBE
completion or instance destruction. After reacquiring the lock, it validates
that the instance is still running before continuing.

PR Checklist

  • Closes: Closes WSL2 shutdown can deadlock while process creation waits for OOBE #41386
  • Communication: I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
  • Tests: Added/updated if needed and all pass
  • Localization: All end user facing strings can be localized
  • Dev docs: Added/updated if needed
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

WslCoreInstance::CreateLxProcess() currently acquires m_lock before checking
whether OOBE is still running. If it is, the function waits indefinitely on
m_oobeCompleteEvent without releasing the lock.

WslCoreInstance::Stop() acquires m_lock before setting
m_destroyingEvent. This creates the following cycle:

  1. Process creation holds m_lock and waits for OOBE completion.
  2. Distribution termination waits for m_lock in Stop().
  3. Stop() cannot signal m_destroyingEvent while process creation holds the lock.

The wait now uses duplicated event handles captured while the instance state is
protected, releases m_lock, and waits for either OOBE completion or instance
destruction. It then reacquires the lock and revalidates m_initChannel and
m_consoleManager before using them.

Validation Steps Performed

Added ModernOOBETermination, a WSL2 regression test that:

  1. Starts an OOBE command that signals readiness and remains active.
  2. Starts a second process that waits for OOBE completion.
  3. Terminates the distribution while that process is waiting.
  4. Verifies termination completes before the OOBE command's natural exit.
  5. Verifies both waiting processes exit during cleanup.

Static inspection was performed locally. The Windows build, formatting check,
and test execution are left to repository CI because the integration tests
affect machine-wide WSL state.

@SylvainM98
Sylvain MOLINIER (SylvainM98) requested a review from a team as a code owner August 19, 2026 14:43
Copilot AI lite review requested due to automatic review settings August 19, 2026 14:43
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR improves WSL2 OOBE termination behavior by preventing a lock-held wait during OOBE completion and adding a Windows unit test to validate termination behavior during Modern OOBE.

Changes:

  • Added a WSL2 unit test that runs a long OOBE command and validates --terminate completes while another process is blocked.
  • Updated WslCoreInstance::CreateLxProcess to wait for either OOBE completion or instance destruction without holding m_lock.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
test/windows/UnitTests.cpp Adds a new unit test covering termination behavior while Modern OOBE is in progress.
src/windows/service/exe/WslCoreInstance.cpp Avoids holding m_lock while waiting for OOBE completion; also unblocks when destruction is signaled.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread test/windows/UnitTests.cpp Outdated
Comment on lines +4787 to +4795
const DWORD terminationResult = WaitForSingleObject(terminationProcess.get(), terminationTimeout);

if (terminationResult == WAIT_TIMEOUT)
{
VERIFY_ARE_EQUAL(WaitForSingleObject(terminationProcess.get(), cleanupTimeout), WAIT_OBJECT_0);
}

VERIFY_ARE_EQUAL(waitingResult, WAIT_TIMEOUT);
VERIFY_ARE_EQUAL(terminationResult, WAIT_OBJECT_0);

@OneBlue Blue (OneBlue) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for fixing this ! This approach looks good, couple minor comments

EMIT_USER_WARNING(wsl::shared::Localization::MessageWaitingForOobe(m_configuration.Name.c_str()));
m_oobeCompleteEvent.wait();

const wil::unique_handle oobeCompleteEvent{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Instead of duplicating the handles, we could switch those to be wil::shared_handle, and then just make a copy in this block

Comment thread test/windows/UnitTests.cpp Outdated
wsl::windows::common::SubProcess terminationProcessBuilder(
nullptr, LxssGenerateWslCommandLine(L"--terminate " LXSS_DISTRO_NAME_TEST_L).c_str());
const auto terminationProcess = terminationProcessBuilder.Start();
const DWORD terminationResult = WaitForSingleObject(terminationProcess.get(), terminationTimeout);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would recommend just calling terminationProcessBuilder.Run(terminationTimeout);. This will throw an exception if the process times out, which will fail the test

@OneBlue

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI review requested due to automatic review settings August 20, 2026 06:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment on lines +183 to +187
const auto oobeCompleteEvent = m_oobeCompleteEvent;
const auto destroyingEvent = m_destroyingEvent;
const HANDLE waitHandles[] = {oobeCompleteEvent.get(), destroyingEvent.get()};
lock.unlock();
const DWORD waitResult = WaitForMultipleObjects(RTL_NUMBER_OF(waitHandles), waitHandles, FALSE, INFINITE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm this is true. The easiest solution I can think of would be to only create m_oobeCompleteEvent if it isn't already created

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WSL2 shutdown can deadlock while process creation waits for OOBE

3 participants