Merge nucleic/lucid-river-toad-6efj into dev
This commit is contained in:
+11
-6
@@ -180,12 +180,17 @@ for, and nothing above it should move. These three are different:
|
|||||||
mounted vs. in-VM). Deliberately held back until `WslcApiDump` has run: written now, against
|
mounted vs. in-VM). Deliberately held back until `WslcApiDump` has run: written now, against
|
||||||
guessed names, it would not compile, and fixing it blind is the mistake this whole approach
|
guessed names, it would not compile, and fixing it blind is the mistake this whole approach
|
||||||
exists to avoid.
|
exists to avoid.
|
||||||
- **The internal arm itself** (`WslcInternal.cs` in the broker, not a spike) — now fully
|
- **The internal arm, Tier 1 — "recover"** (`WslcInternal.cs` in the broker, not a spike).
|
||||||
de-risked by `--internal-call`: entry point, vtable, impersonation requirement and the
|
Everything it needs is confirmed on hardware: entry point, vtable, impersonation, and
|
||||||
session→compat handoff are all confirmed on hardware. Shape is *find, then hand off*:
|
`OpenSessionByName`. On broker restart, open the orphaned session, `ListContainers` for
|
||||||
`OpenSessionByName` / `ListContainers` / `OpenContainer`, each result QI'd onto its compat
|
reporting, `Terminate` it, and create a fresh one through the compat SDK — automatic clean
|
||||||
interface and passed to `FromAbi()`, after which the existing facade code drives it. The
|
recovery instead of a manual `wsl --shutdown`. Enough for M2.
|
||||||
container-level QI is the one step still unobserved — confirm it in `WslcSpike` below.
|
- **Tier 2 — "adopt"** (keep containers running across a broker restart) is **blocked**. The
|
||||||
|
`Session.FromAbi()` handoff throws `InvalidCastException` even though the QI to
|
||||||
|
`IWSLCCompatSession` succeeds: the WinRT layer appears to be a client-side wrapper in
|
||||||
|
`wslcsdk.dll`, not the service object. `--internal-call` now compares COM identity to confirm.
|
||||||
|
If confirmed, Tier 2 means driving exec/stdio through internal COM directly
|
||||||
|
(`IWSLCContainer::Exec`, `IWSLCProcess::GetStdHandle` — handle-based, not event-based).
|
||||||
- **`GatewaySpike`** — M1 (b), and the control-plane spike that actually matters now: can a
|
- **`GatewaySpike`** — M1 (b), and the control-plane spike that actually matters now: can a
|
||||||
`Bridged` wslc container reach the host at the `vEthernet (WSL)` address `WslcFacade` returns,
|
`Bridged` wslc container reach the host at the `vEthernet (WSL)` address `WslcFacade` returns,
|
||||||
under the **default** Windows firewall, and does `control-bridge.js` complete an MCP round trip
|
under the **default** Windows firewall, and does `control-bridge.js` complete an MCP round trip
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ internal static class InternalComProbe
|
|||||||
Console.WriteLine(" → a re-adopted session can be handed to "
|
Console.WriteLine(" → a re-adopted session can be handed to "
|
||||||
+ "Session.FromAbi() and driven by the EXISTING compat facade code.");
|
+ "Session.FromAbi() and driven by the EXISTING compat facade code.");
|
||||||
Console.WriteLine(" → D13's internal arm shrinks to: find, then hand off.");
|
Console.WriteLine(" → D13's internal arm shrinks to: find, then hand off.");
|
||||||
ProbeFromAbiOwnership(projected);
|
ProbeObjectIdentity(session, projected);
|
||||||
Marshal.Release(projected);
|
Marshal.Release(projected);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -353,6 +353,92 @@ internal static class InternalComProbe
|
|||||||
|
|
||||||
private static string? sessionName;
|
private static string? sessionName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Why did `Session.FromAbi` throw `InvalidCastException` on a pointer that had just QI'd
|
||||||
|
/// successfully to `IWSLCCompatSession`?
|
||||||
|
///
|
||||||
|
/// The hypothesis is that there are **three** layers, not two: `wslc.idl` (service-internal
|
||||||
|
/// COM), `WSLCCompat.idl` (compat COM), and a **WinRT** layer implemented *client-side* in
|
||||||
|
/// `wslcsdk.dll` — which exports `DllGetActivationFactory`, so its `Session` is an in-process
|
||||||
|
/// wrapper object that holds compat COM proxies rather than being one. If so, the service's
|
||||||
|
/// object can never satisfy `FromAbi`, because the WinRT default interface only ever exists
|
||||||
|
/// on the client-side wrapper, and no API turns a compat pointer into one.
|
||||||
|
///
|
||||||
|
/// Decisive test, and it needs no knowledge of the WinRT IID: take the `Session` the SDK
|
||||||
|
/// itself created, reach through to its underlying native pointer, and compare **COM
|
||||||
|
/// identity** (QI for IUnknown — the canonical pointer) against the one `OpenSessionByName`
|
||||||
|
/// returned. Same object → the layers coincide and something else broke FromAbi. Different
|
||||||
|
/// objects → the wrapper is client-side and the handoff is impossible as designed.
|
||||||
|
/// </summary>
|
||||||
|
private static void ProbeObjectIdentity(IntPtr internalSession, IntPtr compatFace)
|
||||||
|
{
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine(" object identity — is the WinRT Session the SAME object?");
|
||||||
|
if (sdkSession is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine(" (no SDK-created session in this run; pass --session --keep)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntPtr sdkPtr;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// CsWinRT keeps the native pointer on IWinRTObject.NativeObject.ThisPtr. Reached by
|
||||||
|
// reflection so this spike keeps building even if the projection's shape changes.
|
||||||
|
var winrt = sdkSession.GetType().GetInterface("WinRT.IWinRTObject");
|
||||||
|
var native = winrt?.GetProperty("NativeObject")?.GetValue(sdkSession);
|
||||||
|
var ptr = native?.GetType().GetProperty("ThisPtr")?.GetValue(native);
|
||||||
|
if (ptr is not IntPtr value || value == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Console.WriteLine(" could not reach IWinRTObject.NativeObject.ThisPtr");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sdkPtr = value;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" reflection failed: {e.GetType().Name}: {e.Message}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// COM identity is defined as the IUnknown pointer, so canonicalise both before comparing.
|
||||||
|
var iid = IidIUnknown;
|
||||||
|
if (Marshal.QueryInterface(sdkPtr, in iid, out var sdkIdentity) < 0) return;
|
||||||
|
if (Marshal.QueryInterface(internalSession, in iid, out var internalIdentity) < 0)
|
||||||
|
{
|
||||||
|
Marshal.Release(sdkIdentity);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var same = sdkIdentity == internalIdentity;
|
||||||
|
Console.WriteLine($" SDK Session IUnknown = 0x{sdkIdentity:X}");
|
||||||
|
Console.WriteLine($" OpenSessionByName IUnknown = 0x{internalIdentity:X}");
|
||||||
|
Console.WriteLine($" → {(same ? "SAME object" : "DIFFERENT objects")}");
|
||||||
|
|
||||||
|
// The other half: does the SDK's own object even implement the compat COM interface?
|
||||||
|
var compat = IidCompatSession;
|
||||||
|
var qi = Marshal.QueryInterface(sdkPtr, in compat, out var sdkCompat);
|
||||||
|
if (qi >= 0) Marshal.Release(sdkCompat);
|
||||||
|
Console.WriteLine($" SDK Session QI IWSLCCompatSession: {(qi >= 0 ? "yes" : "no")}");
|
||||||
|
|
||||||
|
Console.WriteLine(same
|
||||||
|
? " → the layers coincide; FromAbi failed for some OTHER reason — investigate."
|
||||||
|
: " → the WinRT Session is a CLIENT-SIDE WRAPPER (wslcsdk.dll exports\n"
|
||||||
|
+ " DllGetActivationFactory), so a service-side pointer can never satisfy\n"
|
||||||
|
+ " FromAbi. The 'find, then hand off' shape does NOT work, and a\n"
|
||||||
|
+ " re-adopted session must be driven through internal COM directly.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.Release(sdkIdentity);
|
||||||
|
Marshal.Release(internalIdentity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static object? sdkSession;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does <c>Session.FromAbi(ptr)</c> take a reference, or borrow the caller's?
|
/// Does <c>Session.FromAbi(ptr)</c> take a reference, or borrow the caller's?
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -395,6 +395,10 @@ internal static class Program
|
|||||||
|
|
||||||
var first = CreateSession(settingsType, sessionType, name, dataDir, "first");
|
var first = CreateSession(settingsType, sessionType, name, dataDir, "first");
|
||||||
if (first is null) return;
|
if (first is null) return;
|
||||||
|
// Hand the live SDK-created session to the internal probe: comparing ITS underlying COM
|
||||||
|
// identity against the one OpenSessionByName returns is what settles whether the WinRT
|
||||||
|
// projection is the same object or a client-side wrapper (InternalComProbe).
|
||||||
|
InternalComProbe.sdkSession = first;
|
||||||
|
|
||||||
// Start it — construction alone may not boot the VM (`Session.Start()` is separate).
|
// Start it — construction alone may not boot the VM (`Session.Start()` is separate).
|
||||||
var start = sessionType.GetMethods(Public)
|
var start = sessionType.GetMethods(Public)
|
||||||
|
|||||||
Reference in New Issue
Block a user