Launch an apphost directly instead of through the dotnet muxer - #34
Conversation
75af418 to
b2f4395
Compare
The muxer cannot execute an apphost, which is the only artefact a self-contained or single-file publish produces. Worse than failing, it starts the muxer itself, so the debugger attaches to dotnet rather than to the program. LaunchInfo.GetCommandLine decides: a .dll goes through dotnet, anything else runs directly. Both PerformLaunch and the runInTerminal request use it. Covers launch with tests for the first time - one through the assembly, one through the apphost. fix startinfo in launch request
ca7a5e1 to
efe7104
Compare
|
FYI I've committed the fix to main separately, and will review the new tests that remain in this PR when I get a chance. |
|
Thanks for rebasing it. The tests are byte-identical to what I ran, and they pass against your version, 2 of 2 — the apphost one still fails on the muxer path, so it pins the behaviour rather than just exercising it. One small thing about normalising Unrelated, while I was running the suite: |
Fixed in 5c2552f :) Ah, thanks for reporting that EvalTests failure, looks like a macos/unix specific failure - I am primarily on Windows. I shall investigate and fix :) I probably should add some CI for running the tests on all platforms at some point too 😅 |
Fixes #33.
PerformLaunchand therunInTerminalrequest both hardcode the muxer:That is right for a managed assembly and wrong for an apphost, which is the only artefact a self-contained or single-file publish produces.
It is worse than a failed launch.
dotnet <apphost>does not fail fast - the muxer starts, fails to resolve the path as a command, and the debugger attaches to the muxer rather than to the program. The session looks healthy, and the top frame is:LaunchInfo.GetCommandLine()now decides: a.dllgoes throughdotnet, anything else is run directly. Both call sites use it.Tests
LaunchTestslaunchesDebuggableConsoleAppand stops at a breakpoint, once through the assembly and once through the apphost - the framework-dependent build produces both, so nothing new has to be published. Onmainthe apphost case attaches to the muxer and never hits the breakpoint.There was no launch coverage before, so this needed a little scaffolding:
GetRunningDebugProtocolHostForLaunchInProcdoes not start a debuggee, and gives the first stop its ownTaskCompletionSource. A launch binds its breakpoints and hits one within milliseconds, and the sharedTcsContaineronly holds the latest event, so the stop was getting dropped.WithConfigurationDoneRequestAsyncbounds the request. The launch happens insideconfigurationDone, so a program the debugger cannot start leaves it unanswered - without a bound the whole run hangs instead of failing one test.LaunchedDebuggeeKillerkills what the launch started.Disposereleases theProcessobject without killing the process, anddisconnectwithterminateDebuggee: truedoes not return at all (below), so the test cannot hand cleanup back to the adapter.Unrelated, found on the way
disconnectwithterminateDebuggee: true, sent while stopped at a breakpoint, never returns - 3 of 3 onmain. WithterminateDebuggee: falsethe same sequence disconnects cleanly, 2 of 2. It looks like the same lock as #32, but deterministic rather than 1-in-12, so it may be the easier way in. I have not touched it here.Verified on macOS arm64 with .NET 10.