Skip to content

[WSLC] (2/2) Preserve structured errors from process waits - #1711

Open
Soham Das (SohamDas2021) wants to merge 3 commits into
mainfrom
sohamdas2021-wslc-wait-errors
Open

Soham Das (SohamDas2021) wants to merge 3 commits into
mainfrom
sohamdas2021-wslc-wait-errors

Conversation

@SohamDas2021

@SohamDas2021 Soham Das (SohamDas2021) commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

📖 Description

When a process had started and MXC then failed to get its final result, the error lost its details. For example, when WslcGetProcessExitCode failed after a WSLc process exited:

  • .NET and Node reported a generic backend_error that named neither the WSLc call nor its HRESULT, because the C wait functions return only a status code.
  • Rust run and run_in_container reported backend_error with the message prefixed by "waiting for the sandbox to complete failed:", and with operation, native_code, and details empty. The Rust process wait methods return std::io::Error, which carries only the message.
  • Waiting again on a WSLc create-and-run process queried WSLc again instead of reporting the same failure.

These errors now keep their details:

  • Rust run and run_in_container, and .NET and Node waits and captured runs, report the backend's code, message, operation, native code, and remediation. In the example: backend_error, WslcGetProcessExitCode failed: HRESULT 0x80004005 (prefixed with exec failed: for a process in an existing container), WslcGetProcessExitCode, 0x80004005.
  • Rust adds try_wait_detailed, wait_detailed, and wait_with_output_detailed, returning v1::Error with its details.
  • C adds mxc_sandbox_try_wait_with_error and mxc_sandbox_wait_with_error, which fill MxcErrorDetail.
  • Waiting again reports the same failure, and kill or dispose still try to stop the process.

This applies to processes in an existing container on any backend, and to WSLc create-and-run processes. The existing Rust and C wait functions, timeouts, and exit codes are unchanged. Without backend details, Rust run still returns backend_error, with a reworded message prefix.

Debug test-support builds can force this failure for the live WSLc tests; release builds cannot. The Windows test bundle stages those builds, and nightly WSLc validation requires the eight new tests.

No spec is required: the new APIs are additive forms of existing waits that carry existing error fields, and the .NET and Node APIs do not change. Limitation: details does not reach .NET or Node.

🔗 References

Refs #1644

Stacked on #1671, which fills operation and nativeCode on WSLc container lifecycle errors and leaves these process waits as a limitation. This branch is rebased onto current main and builds on #1539, which added v1::Error.details and caches exec wait results. Its first commit is #1671's change; until #1671 merges, review the last two commits.

🔍 Validation

Run on Windows 11 Enterprise (build 26300, x64) with WSL 3.0.5, on main at 9bc16c0. Cargo commands run from src.

  • cargo fmt --all -- --check: passed.
  • cargo clippy --workspace --all-targets -- -D warnings: passed.
  • cargo clippy -p mxc-sdk --all-targets --features wslc,test-support -- -D warnings: passed.
  • cargo clippy -p mxc_ffi --all-targets --features isolation_session,wslc,test-support -- -D warnings: passed.
  • cargo test -p mxc-sdk --lib --features wslc,test-support: 2,970 passed, 8 ignored.
  • cargo test -p mxc-sdk --bin wxc-wslc-daemon --features wslc,test-support: 176 passed, 13 ignored.
  • cargo test -p mxc_ffi --features isolation_session,wslc,test-support: 94 passed, 4 ignored.
  • The new unit tests cover the repeated failure, cleanup after a failed wait, a panicking waiter, the WSLc error conversion and test-only fault, the new C functions' outputs and ownership, and the .NET and Node decoding.
  • From the repository root, node scripts\check-dotnet-bindings-codegen.js: passed; all 43 required entry points generated.
  • From sdk\node, with Node 26.13.0: npm run typecheck passed, including the integration tests, and npm test passed 456 and skipped 22.
  • Both changed CI scripts parse in PowerShell, and git diff --check passed.

Live WSLc, with MXC_ENABLE_WSLC_TESTS=1 and debug test-support builds from cargo build -p mxc_ffi --features isolation_session,wslc,test-support --target x86_64-pc-windows-msvc and cargo build -p mxc-sdk --bin wxc-wslc-daemon --features wslc,test-support --target x86_64-pc-windows-msvc:

  • Node, from sdk\node\tests\integration, with MXC_FFI_DIR naming a directory that holds those builds and wslcsdk.dll: node --test --test-force-exit --test-reporter=spec --test-reporter-destination=stdout dist/wslc-e2e.test.js passed 11 of 11, and the same command with dist/wslc-state-aware.test.js passed 4 of 4. None were skipped.
  • .NET SDK 10 is not installed, so the tests were built for net8.0 with SDK 9.0.320, selected by a temporary global.json: dotnet build sdk\dotnet\Microsoft.Mxc.Sdk.Tests\Microsoft.Mxc.Sdk.Tests.csproj -c Debug -p:TargetFrameworks=net8.0 -p:TargetFramework=net8.0 -p:MxcWithWslc=true -p:MxcTestPrebuiltNativeLibrary=<mxc_ffi.dll>. Microsoft.Mxc.Sdk.Tests.exe -class *.MxcLifecycleWslcE2ETests -class *.MxcContainerWslcE2ETests -class *.MxcProcessCleanupTests passed 30 of 30, none skipped, including the four new live tests and the two new wait-failure tests.

Not run:

  • dotnet test for net10.0 and the Windows SDK test bundle build, which need .NET SDK 10.
  • The WSLc command-line suite, tests\scripts\run_wslc_all_tests.ps1.
  • Linux and macOS builds, and the Windows CI release tests and all-features Clippy; CI runs these.

✅ Checklist

  • Signed the Contributor License Agreement
  • Linked to an issue
  • For features or changes requiring a spec: linked the issue with maintainer-applied feature-accepted
  • For implementation: linked the required approved and merged design/API spec, or explained why no spec is required
  • Feature issue remains open unless this PR completes all agreed implementation, validation, and documentation
  • Updated documentation (if applicable)
  • Updated Copilot instructions (if build, architecture, or conventions changed)
  • If this PR changes Cargo.lock, the dependency-feed-check check passes (see pull request builds)

📋 Issue Type

  • Bug fix
  • Feature
  • Task
Microsoft Reviewers: Open in CodeFlow

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@SohamDas2021
Soham Das (SohamDas2021) requested a review from a team November 9, 2026 19:46
@SohamDas2021
Soham Das (SohamDas2021) requested a review from a team as a code owner November 9, 2026 19:46
Copilot AI balanced review requested due to automatic review settings November 9, 2026 19:46
@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.

🟡 Changes recommended

The .NET test target runs Rust commands and builds the WSLc daemon even in prebuilt mode, making managed-test CI depend on a Rust toolchain and Cargo feed it does not configure.

5 open issues
What changed in this PR

Preserves structured process-wait errors across MXC’s Rust, C, .NET, and Node layers, extending the WSLc lifecycle error work from #1671 and #1644.

Changes:

  • Adds detailed Rust and C wait APIs and updates managed bindings to retain backend error fields.
  • Carries WSLc failure details through daemon responses and caches wait failures without disabling cleanup.
  • Adds regression tests, debug-only fault injection, CI requirements, and SDK documentation.
File Description
tests/​scripts/​run_wslc_state_aware_tests.ps1 Checks structured port-collision errors.
src/​mxc-sdk/​src/​sandbox.rs Adds detailed process-wait methods.
src/​mxc-sdk/​src/​lib.rs Uses detailed waits for captured execution.
src/​mxc-sdk/​src/​core/​mxc_engine/​error.rs Tests preservation of error details.
src/​mxc-sdk/​src/​core/​mxc_common/​mxc_error.rs Carries structured errors inside I/O errors.
src/​mxc-sdk/​src/​core/​mxc_common/​exec_stream.rs Preserves cached waiter failures and tests cleanup.
src/​mxc-sdk/​src/​bin/​wslc_daemon/​session_manager.rs Retains SDK failure details through worker errors.
src/​mxc-sdk/​src/​bin/​wslc_daemon/​control_server.rs Includes failure details in daemon responses.
src/​mxc-sdk/​src/​backends/​wslc/​common/​wsl_container_runner.rs Produces structured wait errors and injects test failures.
src/​mxc-sdk/​src/​backends/​wslc/​common/​state_aware.rs Maps daemon failure details into backend errors.
src/​mxc-sdk/​src/​backends/​wslc/​common/​sandbox.rs Caches and replays WSLc wait failures.
src/​mxc-sdk/​src/​backends/​wslc/​common/​image.rs Preserves SDK details in image-operation failures.
src/​mxc-sdk/​src/​backends/​wslc/​common/​error.rs Introduces structured WSLc step errors.
src/​mxc-sdk/​src/​backends/​wslc/​common/​daemon_protocol.rs Adds failure fields and advances protocol version.
src/​mxc-sdk/​src/​backends/​wslc/​common/​daemon_client.rs Preserves structured daemon errors.
src/​mxc-sdk/​src/​backends/​wslc/​common/​container_steps.rs Attaches SDK failure metadata and test hooks.
src/​mxc-sdk/​README.md Documents detailed Rust wait APIs.
src/​ffi/​mxc_ffi/​src/​test_fake_sandbox.rs Adds structured wait-failure test doubles.
src/​ffi/​mxc_ffi/​src/​streaming.rs Adds C wait functions with error outputs.
sdk/​node/​tests/​unit/​streaming-binding.test.ts Tests error decoding and allocation cleanup.
sdk/​node/​tests/​unit/​sandbox-process.test.ts Tests shared wait rejection and cleanup.
sdk/​node/​tests/​integration/​wslc-state-aware.test.ts Tests lifecycle wait failures and recovery.
sdk/​node/​tests/​integration/​wslc-e2e.test.ts Tests create-and-run wait failures and recovery.
sdk/​node/​src/​bindings/​streaming.ts Uses detailed native waits and frees errors.
sdk/​node/​README.md Documents structured wait rejections.
sdk/​dotnet/​README.md Documents structured wait exceptions.
sdk/​dotnet/​Microsoft.Mxc.Sdk/​V1/​MxcProcess.cs Decodes and frees native wait-error details.
sdk/​dotnet/​Microsoft.Mxc.Sdk.Tests/​V1/​MxcProcessCleanupTests.cs Tests structured polling and blocking-wait failures.
sdk/​dotnet/​Microsoft.Mxc.Sdk.Tests/​V1/​MxcLifecycleWslcE2ETests.cs Tests lifecycle wait failures and recovery.
sdk/​dotnet/​Microsoft.Mxc.Sdk.Tests/​V1/​MxcContainerWslcE2ETests.cs Tests create-and-run wait failures and recovery.
sdk/​dotnet/​Microsoft.Mxc.Sdk.Tests/​Microsoft.Mxc.Sdk.Tests.csproj Builds and stages a test-support WSLc daemon.
scripts/​ci/​run_backend_validation_tests.ps1 Requires eight new WSLc regression scenarios.
scripts/​ci/​build-windows-sdk-test-bundle.ps1 Stages debug test-support binaries and checks discovery.
docs/​development/​architecture/​container-lifecycle.md Describes structured wait failures and cleanup semantics.
docs/​backends/​wslc/​wslc-state-aware.md Documents WSLc failure fields and protocol changes.
docs/​api-reference/​rust/​v1/​types.md Lists detailed wait signatures and semantics.
docs/​api-reference/​node/​v1/​types.md Describes process-wait rejection fields.
docs/​api-reference/​dotnet/​v1/​types.md Describes process-wait exception fields.

Review effort: Balanced

<Target Name="BuildMxcTestSupportNativeLibrary"
AfterTargets="Build"
Condition="'$(TargetFramework)' != '' and '$(DesignTimeBuild)' != 'true' and '$(MxcUsePrebuiltTestNativeLibrary)' != 'true'">
Condition="'$(TargetFramework)' != '' and '$(DesignTimeBuild)' != 'true'">
Comment on lines +351 to +355
'Node' = {
Invoke-NodeIntegrationSuite -Bundle $bundle -Name 'wslc' -TestFile 'wslc-state-aware.test.js' `
-RequiredTests @('V1.Dev preserves WSLC lifecycle responses, captured exec, pipes, and caller ownership',
'captured exec preserves structured post-admission wait failures',
'process wait preserves structured post-admission wait failures')
Comment on lines 1940 to +1942
let exit_event = self
.exit_event()
.map_err(|e| WslcError::Runtime(e).into_response())?;
.map_err(|e| WslcError::Runtime(e).into_step_error())?;
Comment on lines +179 to +180
#[derive(Debug)]
struct MxcIoError(MxcError);
Comment on lines +473 to +475
result after admission. The rejection preserves `code`, `message`,
`operation`, `nativeCode`, `remediation`, and `details`; it is not converted
into an exit result.

This branch has not been deployed

No deployments
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.

2 participants