-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Design: WslcGetCLISession API for inner-loop development #40133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,330 @@ | ||||||||||||||
| # WslcGetCLISession API Design | ||||||||||||||
|
|
||||||||||||||
| ## Overview | ||||||||||||||
|
|
||||||||||||||
| This document describes the design of `WslcGetCLISession`, a public API in the WSLC (WSL Containers) library that returns a reference to the active CLI session for the current process. This is primarily used during the **inner-loop development experience** — build, run, and debug flows for Windows applications that use Linux containers via WSLC. | ||||||||||||||
|
|
||||||||||||||
| ### Motivation | ||||||||||||||
|
|
||||||||||||||
| The WSLC architecture follows an **app-owns-lifecycle** model: | ||||||||||||||
|
|
||||||||||||||
| ``` | ||||||||||||||
| App → Library → Session → Container | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| During the inner-loop development flow, the WSLC toolchain (MSBuild targets, `wslc` CLI, or IDE integration) creates a `WslcSession` to manage container operations. Application code running in the same process — such as build tasks, debug launch helpers, or the app itself during F5 — needs access to this session to interact with the container (e.g., attach a debugger, inspect state, or run additional commands). | ||||||||||||||
|
|
||||||||||||||
| `WslcGetCLISession` provides a stable, public mechanism to retrieve the session that the WSLC toolchain has established for the current process. | ||||||||||||||
|
|
||||||||||||||
| ### Scope | ||||||||||||||
|
|
||||||||||||||
| - **In scope**: Retrieving the WSLC CLI session from any code running in the same process where the WSLC toolchain has published a session. This includes the `wslc` CLI process, MSBuild task host processes, Windows app processes launched with WSLC integration, and IDE extension hosts. | ||||||||||||||
| - **Out of scope**: Cross-process session sharing, remote session access, session creation. | ||||||||||||||
| - **Usage context**: Inner-loop developer experience — build, run, debug. Not a primary production API, but designed to the same quality standards as all WSLC public APIs. | ||||||||||||||
|
|
||||||||||||||
| ## API Design | ||||||||||||||
|
|
||||||||||||||
| ### Prerequisites: Existing WSLC Types | ||||||||||||||
|
|
||||||||||||||
| The following types are established in the WSLC public API surface and are referenced by this design: | ||||||||||||||
|
|
||||||||||||||
| ```c | ||||||||||||||
| // Opaque session handle (ref-counted) | ||||||||||||||
| typedef struct WslcSession_s* WslcSession; | ||||||||||||||
|
|
||||||||||||||
| // Standard WSLC lifecycle APIs (already exist) | ||||||||||||||
| STDAPI WslcCreateSession(_In_ const WslcSessionConfig* config, _Out_ WslcSession* session); | ||||||||||||||
| STDAPI WslcCloseSession(_In_ WslcSession session); | ||||||||||||||
|
|
||||||||||||||
| // Increment session reference count (already exists) | ||||||||||||||
| void WslcSessionAddRef(_In_ WslcSession session); | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| > **Note**: WSLC uses `Close` instead of `Free` (contrast with `FreeWslConfig` in the WSL Config API) to emphasize ref-counted release semantics — `WslcCloseSession` decrements the reference count and only destroys the session when it reaches zero. | ||||||||||||||
|
|
||||||||||||||
| ### New API | ||||||||||||||
|
|
||||||||||||||
| ```c | ||||||||||||||
| // --------------------------------------------------------------------------- | ||||||||||||||
| // WslcGetCLISession | ||||||||||||||
| // --------------------------------------------------------------------------- | ||||||||||||||
| // | ||||||||||||||
| // Retrieves the active WSLC CLI session for the current process. | ||||||||||||||
| // | ||||||||||||||
| // The WSLC toolchain (wslc CLI, MSBuild targets, or IDE integration) | ||||||||||||||
| // publishes a session during the build/run/debug flow. This API returns | ||||||||||||||
| // that session to any code running in the same process — including the | ||||||||||||||
| // application being developed. | ||||||||||||||
| // | ||||||||||||||
| // The returned session handle is ref-counted. The caller receives an owned | ||||||||||||||
| // reference and MUST call WslcCloseSession() when finished. Closing the | ||||||||||||||
| // returned handle only releases the caller's reference — it does NOT close | ||||||||||||||
| // or destroy the underlying CLI session. The toolchain holds its own | ||||||||||||||
| // independent reference. | ||||||||||||||
| // | ||||||||||||||
| // If the process exits or is terminated, all in-process ref counts are | ||||||||||||||
| // reclaimed by the OS. There is no leak concern. | ||||||||||||||
| // | ||||||||||||||
| // Thread safety: | ||||||||||||||
| // - This function is safe to call concurrently from multiple threads. | ||||||||||||||
| // - The returned WslcSession handle is safe for concurrent use across | ||||||||||||||
| // threads (all WslcSession operations are internally synchronized). | ||||||||||||||
| // | ||||||||||||||
| // Lifetime: | ||||||||||||||
| // - Behavior is undefined if called during CRT static destruction | ||||||||||||||
| // (DLL_PROCESS_DETACH). Callers should release their session handles | ||||||||||||||
| // before process teardown begins. | ||||||||||||||
| // | ||||||||||||||
| // Parameters: | ||||||||||||||
| // session - [out] Receives the CLI session handle. On failure, set to NULL. | ||||||||||||||
| // | ||||||||||||||
| // Return values: | ||||||||||||||
| // S_OK - Session retrieved successfully. | ||||||||||||||
| // WSLC_E_NO_CLI_SESSION - No CLI session has been published in the | ||||||||||||||
| // current process (toolchain not initialized). | ||||||||||||||
| // E_POINTER - The session parameter is NULL. | ||||||||||||||
| // | ||||||||||||||
| STDAPI WslcGetCLISession(_Out_ WslcSession* session); | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| > **Export**: `WslcGetCLISession` is exported from the WSLC library DLL via its module `.def` file. Consumers link against the WSLC import library or call `GetProcAddress` on the loaded DLL. The declaration lives in the WSLC public header (`wslc.h`). | ||||||||||||||
|
|
||||||||||||||
| ### Error Code Definition | ||||||||||||||
|
|
||||||||||||||
| ```c | ||||||||||||||
| // WSLC-specific error codes use FACILITY_ITF (standard for interface-specific errors) | ||||||||||||||
| #define WSLC_E_NO_CLI_SESSION MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x8100) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| `FACILITY_ITF` is the standard COM facility for interface-specific error codes. The CODE offset `0x8100` is well-separated from the existing `WSL_E_*` error codes, which use CODE offsets in the `0x0300–0x03xx` range within `FACILITY_ITF`. Both produce HRESULTs in the `0x8004xxxx` range, but the CODE field separation avoids collisions. | ||||||||||||||
|
|
||||||||||||||
| ## Design Decisions | ||||||||||||||
|
|
||||||||||||||
| ### 1. HRESULT Return Type | ||||||||||||||
|
|
||||||||||||||
| **Decision**: Use `HRESULT`, not a custom `WslcResult` enum. | ||||||||||||||
|
|
||||||||||||||
| **Rationale**: Every existing WSL public API uses `HRESULT` — the Plugin API, the Config API, the COM service interface, and internal helpers. Introducing a separate error type would: | ||||||||||||||
|
|
||||||||||||||
| - Fragment the error handling surface | ||||||||||||||
| - Require conversion at every WSL/WSLC boundary | ||||||||||||||
| - Lose compatibility with standard Windows tooling (`SUCCEEDED()`, `FAILED()`, `FormatMessage()`) | ||||||||||||||
|
|
||||||||||||||
| Custom WSLC-specific error conditions are expressed as custom `HRESULT` values (e.g., `WSLC_E_NO_CLI_SESSION`), which is the standard Windows pattern. | ||||||||||||||
|
|
||||||||||||||
| ### 2. Ref-Counted Owned Handle | ||||||||||||||
|
|
||||||||||||||
| **Decision**: `WslcGetCLISession` returns an **owned, ref-counted** handle. The caller must call `WslcCloseSession()` when finished. | ||||||||||||||
|
|
||||||||||||||
| **Rationale**: Returning a borrowed (non-owning) handle would be simpler but introduces safety risks: | ||||||||||||||
|
|
||||||||||||||
| | Concern | Borrowed Handle | Owned Handle | | ||||||||||||||
| |---------|----------------|--------------| | ||||||||||||||
| | Caller accidentally closes it | Use-after-free for CLI | Safe — only releases caller's ref | | ||||||||||||||
| | Toolchain tears down during async work | Dangling pointer | Caller's ref keeps session alive | | ||||||||||||||
| | Type confusion with owned handles | Same type, different rules | Same type, same rules | | ||||||||||||||
| | API surface complexity | Lower | Slightly higher (caller must close) | | ||||||||||||||
|
|
||||||||||||||
| Since `WslcSession` is already a ref-counted type in the WSLC API, returning an owned handle is consistent. The minor overhead of an `AddRef` call is negligible compared to the safety benefits. | ||||||||||||||
|
|
||||||||||||||
| > **Important**: Calling `WslcCloseSession` on a handle returned by `WslcGetCLISession` **never** closes the actual CLI session. It only decrements the caller's reference. The CLI session continues to operate normally. When the process exits or is terminated, the OS reclaims all in-process memory — including any ref counts — so there is no leak concern regardless of whether the caller remembers to call `WslcCloseSession`. | ||||||||||||||
|
|
||||||||||||||
| ### 3. Process-Scoped, Publish-Once Semantics | ||||||||||||||
|
|
||||||||||||||
| **Decision**: The CLI session is published once during CLI initialization and is never replaced or cleared during the process lifetime. | ||||||||||||||
|
|
||||||||||||||
| **Rationale**: This provides a strong invariant that simplifies reasoning about the API: | ||||||||||||||
|
|
||||||||||||||
| - **Publish-once**: After `WslcGetCLISession` returns `S_OK` for a given process, it will always return the same session handle (with a new reference) for the remainder of the process lifetime. | ||||||||||||||
| - **Never cleared**: The CLI runtime does not unpublish the session. Even during CLI shutdown, the session remains accessible (but see Lifetime note above — behavior is undefined during CRT static destruction). Ref-counting ensures the session is only destroyed when all references (including the CLI's own reference) are released. | ||||||||||||||
| - **No replacement**: Calling `WslcGetCLISession` at different times within the same process always returns the same underlying session. | ||||||||||||||
|
|
||||||||||||||
| ### 4. Internal Publication Mechanism | ||||||||||||||
|
|
||||||||||||||
| The CLI runtime publishes its session via an **internal** (non-exported) function: | ||||||||||||||
|
|
||||||||||||||
| ```c | ||||||||||||||
| // Internal — not part of the public API surface | ||||||||||||||
| void WslcPublishCLISession(_In_ WslcSession session); | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| This function: | ||||||||||||||
|
|
||||||||||||||
| 1. Stores the session in a process-global atomic pointer with release semantics. | ||||||||||||||
| 2. Calls `AddRef` on the session (the CLI retains its own reference separately). | ||||||||||||||
| 3. Silently ignores subsequent calls (the `std::call_once` guard ensures only the first invocation takes effect). Debug builds should assert that this function is not called more than once. | ||||||||||||||
|
Comment on lines
+153
to
+155
|
||||||||||||||
| 1. Stores the session in a process-global atomic pointer with release semantics. | |
| 2. Calls `AddRef` on the session (the CLI retains its own reference separately). | |
| 3. Silently ignores subsequent calls (the `std::call_once` guard ensures only the first invocation takes effect). Debug builds should assert that this function is not called more than once. | |
| 1. Attempts to publish the session into a process-global atomic pointer using a compare-and-swap operation with release semantics; only the first successful call stores the pointer. | |
| 2. Calls `AddRef` on the session only when publication succeeds (the CLI retains its own reference separately). | |
| 3. Does not replace an already-published session. Subsequent calls are ignored after the CAS fails; debug builds should assert to catch unexpected duplicate publication attempts. |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proposed implementation file paths reference src/windows/wslc/... and test/windows/wslc/..., but this repository snapshot doesn’t currently contain a src/windows/wslc tree. If those directories will be introduced later, consider calling that out explicitly (or link to the planned location) so the design doc doesn’t read as if it matches current repo structure.
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Section numbering is out of order here ("### 6. Implementation Location" appears before "### 5. Naming"). Reordering or renumbering would make the document easier to follow and reduces confusion when referencing sections in future discussions.
| ### 5. Naming: `WslcGetCLISession` | |
| ### 7. Naming: `WslcGetCLISession` |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The “Global reference lifetime” note says the global AddRef is never released (so the session won’t be destroyed until process exit), but then claims destructor-side cleanup is handled when the toolchain releases its own reference during normal shutdown. Those two statements conflict: if the global ref remains, the destructor won’t run at toolchain shutdown. Please clarify the intended cleanup model (process-exit only vs explicit shutdown/Reset API) so implementers don’t rely on destructor cleanup that won’t happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section states the WSLC public API types/functions “already exist”, but there are currently no
Wslc*symbols in this repo (e.g., noWslcCreateSession/WslcCloseSessiondefinitions undersrc/). To avoid readers assuming these are already available, either link to the existing header location or reword this block as proposed prerequisite types for the future WSLC surface.