Skip to content

Commit 24f6f64

Browse files
Fix Windows PowerShell host-start hang in SetCorrectExecutionPolicy (#2328)
* Fix Windows PowerShell host-start hang in `SetCorrectExecutionPolicy` On recent in-box Windows PowerShell 5.1 servicing builds, the language and debug servers intermittently hang on startup and ride the CI job timeout (#2323). The wedge is `SetCorrectExecutionPolicy`: it calls `Microsoft.PowerShell.Security\Get-ExecutionPolicy -List`, which autoloads `Microsoft.PowerShell.Security` into the freshly created runspace. That runspace's `InitialSessionState` is reused from the host runspace and already carries the module's `ObjectSecurity` type data, so re-binding it throws "The member `AuditToString` is already present". `PsesInternalHost.Run()` catches it, faults `_started`, and the pipeline thread exits — but `TryStartAsync` is still awaiting queued startup tasks that now never run, so it hangs forever. Configuring the execution policy is best-effort, so we wrap the `Get-ExecutionPolicy` query in try/catch (mirroring the existing `Set-ExecutionPolicy` handling just below it) and skip policy configuration when it fails, rather than letting a type-data hiccup abort host startup. I also guard the subsequent indexing against a short result list. With the hang fixed we no longer need the in-box Windows PowerShell E2E skips added in #2318, so this reverts them: the `SkippableFactOnWindowsPowerShell` attributes, the `LSPTestsFixture` early-return, and the two attribute files. The DAP suite passes clean under `powershell.exe`. Three LSP help tests (`Expand-Archive` synopsis) still fail locally on my older servicing build (.8655); they pass under `pwsh`, so I'm letting CI judge them on its build rather than re-skipping. The `ci-test.yml` job timeout from #2318 stays as a backstop. Drafted by Copilot (Claude Opus 4.8). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Restore E2E test hardening reverted with the Windows PowerShell skips PR #2328 reset the three E2E test files to their pre-#2318 state (`b57653c40`) to undo the Windows PowerShell skips we no longer want now that the host-start hang is actually fixed. But #2318 was a squash that bundled genuine harness hardening *alongside* those skips, so reverting wholesale quietly dropped the good parts too. Restore just those, keeping the skips reverted: - `ReadScriptLogLineAsync` now yields with `await Task.Delay(100)` at EOF instead of busy-spinning. At EOF `ReadLineAsync` completes synchronously with `null`, so the old `while`/`await` loop never released its thread-pool thread and could starve the scheduler on constrained CI runners. - The child-process `Debug-Runspace` readiness poll in `CanAttachScriptWithPathMappings` sleeps 100ms per iteration so it can't peg a core during the attach handshake. - `LSPTestsFixture.DisposeAsync` guards against a null `PsesLanguageClient` so a startup failure isn't masked by a `NullReferenceException` during teardown. These are defense-in-depth independent of the skips, and they matter more now that we un-skip: a Windows PowerShell server that fails to start shouldn't busy-spin or NRE on teardown. Drafted by Copilot (Claude Opus 4.8). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fail fast when the debug adapter's `OnInitialize` delegate throws OmniSharp's `DebugAdapterServer.From()` (0.19.9) awaits an internal `AsyncSubject` that its `InitializeRequest` handler only signals on the success path. If an `OnInitialize` delegate throws, the handler faults before signaling, nothing errors the subject, and `From()` -- and thus `PsesDebugServer.StartAsync()` -- awaits it forever. So a startup failure wedges the entire debug server and rides the CI/job timeout instead of failing fast. This is a library limitation, not our bug: the same code is present on the library's `master`, so we can't fix it upstream without a fork or upgrade. #2328 fixes the specific trigger we hit on in-box Windows PowerShell (the `Get-ExecutionPolicy -List` type-data conflict), but the wedge mechanism is generic. Guard against any future `OnInitialize` failure: wrap the delegate body, log the exception, and signal `_serverStopped` so `WaitForShutdown` unblocks and the process exits cleanly. `Dispose`'s `_serverStopped` completion is now idempotent (`TrySetResult`) since the catch may have already completed it. This converts a silent multi-hour hang into a clean termination with a logged error -- the client sees the session end instead of waiting forever. See #2323. Drafted by Copilot (Claude Opus 4.8). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Increase `CanAttachScriptWithPathMappings` timeouts on slow CI runners This PR un-skips `CanAttachScriptWithPathMappings` on Windows PowerShell (reverting the #2318 skips now that the host-start hang is fixed), and the un-skipped test promptly failed on the WinPS 5.1 CI leg with "Attached process exited before the script could start" -- at exactly 10 seconds. The test spawns a child `powershell.exe`, then waits up to 10s for `Debug-Runspace` to subscribe to the child's runspace as the marker that the attach handshake completed, before driving a full breakpoint/continue interaction under a 15s xUnit timeout. On the 2-vCPU CI runners the WinPS attach alone exceeds that 10s budget, so the child throws its internal timeout and exits before the debug session attaches. It isn't even close to comfortable locally: xUnit flags the run as a long-running test at the 10s mark on a fast dev box, so the old budget was always on a knife's edge for the slower WinPS path. Rather than re-skip it, give the slow-but-correct path room: - The child's `Debug-Runspace` subscription poll goes 10s -> 30s. - The outer process-watch cancellation goes 30s -> 60s. - The xUnit `Timeout` goes 15s -> 60s. This continues 81b273b (which already bumped the xUnit timeout 10s -> 15s for the same flakiness) and keeps real coverage of the attach path on Windows PowerShell instead of skipping it. See #2323 and #2318. Drafted by Copilot (Claude Opus 4.8). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Skip the attach and prefixed-completion E2E tests on Windows PowerShell Un-skipping the entire Windows PowerShell E2E suite (now that the host-start hang is fixed) gave us real WinPS coverage for the first time since #2318, but it also surfaced two pre-existing WinPS-specific failures that have nothing to do with host startup: - `CanAttachScriptWithPathMappings` wedges on the in-box, cross-process `Debug-Runspace` attach handshake and rides whatever timeout we set. CI failed it at 10s, then again at exactly 30s after I bumped the budgets, so it genuinely never completes rather than merely running slow. This is the in-box attach deadlock tracked by #2323. - `CanSendCompletionResolveWithModulePrefixRequestAsync` gets an empty completion list from the Windows PowerShell server for a prefix-imported command (it fails before any help assertion, so it's help-independent). That's the same "completion works in PS7 but not WinPS" family as #1355. Because startup no longer hangs, we don't need #2318's discovery-time `[SkippableFactOnWindowsPowerShell]` attribute anymore: an in-body `Skip.If(IsWindowsPowerShell, ...)` runs after `InitializeAsync` (which now starts the server fine) and skips before the broken code, so both tests skip cleanly in ~1ms under `powershell.exe` instead of hanging or failing. This also reverts my earlier timeout bump on `CanAttachScriptWithPathMappings` (back to the 15s/10s/30s budgets from `main`) since the bigger budgets didn't help and the test no longer runs on Windows PowerShell anyway. Everything else stays un-skipped. Drafted by Copilot (Claude Opus 4.8). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Skip the flaky child-attach-session E2E test on Windows PowerShell Un-skipping the Windows PowerShell E2E suite surfaced one more attach-family flake: `CanLaunchScriptWithNewChildAttachSession`. It passed on the prior CI run (`a4e8a823e`) but timed out (`TaskCanceledException` at its 30s budget) on the next (`25d9e58dd`) with no relevant code change in between, so it's genuinely flaky on the slow in-box Windows PowerShell CI runners rather than broken. The test runs `Start-DebugAttachSession` and waits for the server's `startDebugging` reverse-request round-trip; on in-box Windows PowerShell that round-trip is slow enough to intermittently miss the timeout. That's the same in-box attach-E2E reliability bucket as #2323, and its two siblings are already skipped there: `CanAttachScriptWithPathMappings` (the cross-process `Debug-Runspace` wedge) and `CanLaunchScriptWithNewChildAttachSessionAsJob` (no `ThreadJob` on Windows PowerShell). So skip this one on Windows PowerShell too, keeping the rest of the now-un-skipped DAP suite running. The flake only reproduces on the constrained CI runner, not on a fast dev box, so I'm matching how the sibling attach tests are handled rather than chasing a timeout bump I can't validate locally. Drafted by Copilot (Claude Opus 4.8). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0515a5f commit 24f6f64

7 files changed

Lines changed: 119 additions & 179 deletions

File tree

src/PowerShellEditorServices/Server/PsesDebugServer.cs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.IO;
66
using System.Threading.Tasks;
77
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
89
using Microsoft.PowerShell.EditorServices.Handlers;
10+
using Microsoft.PowerShell.EditorServices.Logging;
911
using Microsoft.PowerShell.EditorServices.Services;
1012
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
1113
using OmniSharp.Extensions.DebugAdapter.Server;
@@ -89,23 +91,40 @@ public async Task StartAsync()
8991
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
9092
.OnInitialize(async (server, _, cancellationToken) =>
9193
{
92-
// Start the host if not already started, and enable debug mode (required
93-
// for remote debugging).
94-
//
95-
// TODO: We might need to fill in HostStartOptions here.
96-
_startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), cancellationToken).ConfigureAwait(false);
97-
_psesHost.DebugContext.EnableDebugMode();
98-
99-
// We need to give the host a handle to the DAP so it can register
100-
// notifications (specifically for sendKeyPress).
101-
if (_isTemp)
94+
try
10295
{
103-
_psesHost.DebugServer = server;
96+
// Start the host if not already started, and enable debug mode (required
97+
// for remote debugging).
98+
//
99+
// TODO: We might need to fill in HostStartOptions here.
100+
_startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), cancellationToken).ConfigureAwait(false);
101+
_psesHost.DebugContext.EnableDebugMode();
102+
103+
// We need to give the host a handle to the DAP so it can register
104+
// notifications (specifically for sendKeyPress).
105+
if (_isTemp)
106+
{
107+
_psesHost.DebugServer = server;
108+
}
109+
110+
// Clear any existing breakpoints before proceeding.
111+
BreakpointService breakpointService = server.GetService<BreakpointService>();
112+
await breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false);
113+
}
114+
catch (Exception e)
115+
{
116+
// Never let an exception escape this delegate. OmniSharp's
117+
// DebugAdapterServer only signals its internal initialize-complete
118+
// subject on the success path of the InitializeRequest handler, so a
119+
// throw here leaves DebugAdapterServer.From() (and thus StartAsync)
120+
// awaiting that subject forever -- wedging startup and riding the job
121+
// timeout instead of failing fast. Log the failure and signal shutdown
122+
// so WaitForShutdown unblocks and the process can exit cleanly.
123+
ServiceProvider.GetService<ILoggerFactory>()?
124+
.CreateLogger<PsesDebugServer>()
125+
.LogException("Failed to start the debug server; terminating the debug session.", e);
126+
_serverStopped.TrySetResult(true);
104127
}
105-
106-
// Clear any existing breakpoints before proceeding.
107-
BreakpointService breakpointService = server.GetService<BreakpointService>();
108-
await breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false);
109128
})
110129
// The OnInitialized delegate gets run right before the server responds to the _Initialize_ request:
111130
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
@@ -134,7 +153,7 @@ public void Dispose()
134153
_debugAdapterServer?.Dispose();
135154
_inputStream.Dispose();
136155
_outputStream.Dispose();
137-
_serverStopped.SetResult(true);
156+
_serverStopped.TrySetResult(true);
138157
// TODO: If the debugger has stopped, should we clear the breakpoints?
139158
}
140159

src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,31 @@ public static void SetCorrectExecutionPolicy(this PowerShell pwsh, ILogger logge
138138
{
139139
// We want to get the list hierarchy of execution policies
140140
// Calling the cmdlet is the simplest way to do that
141-
IReadOnlyList<PSObject> policies = pwsh
142-
.AddCommand(@"Microsoft.PowerShell.Security\Get-ExecutionPolicy")
143-
.AddParameter("List")
144-
.InvokeAndClear<PSObject>();
141+
IReadOnlyList<PSObject> policies;
142+
try
143+
{
144+
policies = pwsh
145+
.AddCommand(@"Microsoft.PowerShell.Security\Get-ExecutionPolicy")
146+
.AddParameter("List")
147+
.InvokeAndClear<PSObject>();
148+
}
149+
catch (Exception e)
150+
{
151+
// Some Windows PowerShell servicing builds throw a type-data conflict
152+
// ("The member ... is already present" on ObjectSecurity) when
153+
// autoloading Microsoft.PowerShell.Security into a runspace whose
154+
// InitialSessionState already carries that module's type data.
155+
// Configuring the execution policy is best-effort, so log and skip
156+
// rather than letting it abort host startup (which manifests as a hang).
157+
logger.LogError(e, "Failed to query the execution policy; skipping execution policy configuration.");
158+
return;
159+
}
160+
161+
// We need at least the CurrentUser and LocalMachine scopes to proceed.
162+
if (policies is null || policies.Count < 2)
163+
{
164+
return;
165+
}
145166

146167
// The policies come out in the following order:
147168
// - MachinePolicy

test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,6 @@
2424

2525
namespace PowerShellEditorServices.Test.E2E
2626
{
27-
/// <remarks>
28-
/// Every test in this class is skipped at discovery time on in-box Windows
29-
/// PowerShell (via <see cref="SkippableFactOnWindowsPowerShellAttribute"/> and
30-
/// <see cref="SkippableTheoryOnWindowsPowerShellAttribute"/>) because the shared
31-
/// <see cref="InitializeAsync"/> debug-adapter startup can wedge there since the
32-
/// 20260614 runner image, riding the job timeout. See
33-
/// https://github.com/PowerShell/PowerShellEditorServices/issues/2323.
34-
/// </remarks>
3527
[Trait("Category", "DAP")]
3628
// ITestOutputHelper is injected by XUnit
3729
// https://xunit.net/docs/capturing-output
@@ -264,7 +256,7 @@ private async Task<string> ReadScriptLogLineAsync()
264256
}
265257
}
266258

267-
[SkippableFactOnWindowsPowerShell]
259+
[Fact]
268260
public void CanInitializeWithCorrectServerSettings()
269261
{
270262
Assert.True(client.ServerSettings.SupportsConditionalBreakpoints);
@@ -276,7 +268,7 @@ public void CanInitializeWithCorrectServerSettings()
276268
Assert.True(client.ServerSettings.SupportsDelayedStackTraceLoading);
277269
}
278270

279-
[SkippableFactOnWindowsPowerShell]
271+
[Fact]
280272
public async Task UsesDotSourceOperatorAndQuotesAsync()
281273
{
282274
string filePath = NewTestFile(GenerateLoggingScript("$($MyInvocation.Line)"));
@@ -288,7 +280,7 @@ public async Task UsesDotSourceOperatorAndQuotesAsync()
288280
Assert.StartsWith(". '", actual);
289281
}
290282

291-
[SkippableFactOnWindowsPowerShell]
283+
[Fact]
292284
public async Task UsesCallOperatorWithSettingAsync()
293285
{
294286
string filePath = NewTestFile(GenerateLoggingScript("$($MyInvocation.Line)"));
@@ -300,7 +292,7 @@ public async Task UsesCallOperatorWithSettingAsync()
300292
Assert.StartsWith("& '", actual);
301293
}
302294

303-
[SkippableFactOnWindowsPowerShell]
295+
[Fact]
304296
public async Task CanLaunchScriptWithNoBreakpointsAsync()
305297
{
306298
string filePath = NewTestFile(GenerateLoggingScript("works"));
@@ -314,7 +306,7 @@ public async Task CanLaunchScriptWithNoBreakpointsAsync()
314306
Assert.Equal("works", actual);
315307
}
316308

317-
[SkippableFactOnWindowsPowerShell]
309+
[SkippableFact]
318310
public async Task CanSetBreakpointsAsync()
319311
{
320312
Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode,
@@ -365,7 +357,7 @@ public async Task CanSetBreakpointsAsync()
365357
Assert.Equal("after breakpoint", afterBreakpointActual);
366358
}
367359

368-
[SkippableFactOnWindowsPowerShell]
360+
[SkippableFact]
369361
public async Task FailsIfStacktraceRequestedWhenNotPaused()
370362
{
371363
Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode,
@@ -396,7 +388,7 @@ await Assert.ThrowsAsync<JsonRpcException>(() => client.RequestStackTrace(
396388
));
397389
}
398390

399-
[SkippableFactOnWindowsPowerShell]
391+
[SkippableFact]
400392
public async Task SendsInitialLabelBreakpointForPerformanceReasons()
401393
{
402394
Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode,
@@ -454,7 +446,7 @@ public async Task SendsInitialLabelBreakpointForPerformanceReasons()
454446
// PowerShell, we avoid all issues with our test project (and the xUnit executable) not
455447
// having System.Windows.Forms deployed, and can instead rely on the Windows Global Assembly
456448
// Cache (GAC) to find it.
457-
[SkippableFactOnWindowsPowerShell]
449+
[SkippableFact]
458450
public async Task CanStepPastSystemWindowsForms()
459451
{
460452
Skip.IfNot(PsesStdioLanguageServerProcessHost.IsWindowsPowerShell,
@@ -497,7 +489,7 @@ public async Task CanStepPastSystemWindowsForms()
497489
// commented. Since in some cases (such as Windows PowerShell, or the script not having a
498490
// backing ScriptFile) we just wrap the script with braces, we had a bug where the last
499491
// brace would be after the comment. We had to ensure we wrapped with newlines instead.
500-
[SkippableFactOnWindowsPowerShell]
492+
[Fact]
501493
public async Task CanLaunchScriptWithCommentedLastLineAsync()
502494
{
503495
string script = GenerateLoggingScript("$($MyInvocation.Line)", "$(1+1)") + "# a comment at the end";
@@ -521,7 +513,7 @@ public async Task CanLaunchScriptWithCommentedLastLineAsync()
521513
Assert.Equal("2", await ReadScriptLogLineAsync());
522514
}
523515

524-
[SkippableFactOnWindowsPowerShell]
516+
[SkippableFact]
525517
public async Task CanRunPesterTestFile()
526518
{
527519
Skip.If(true, "Pester test is broken.");
@@ -566,7 +558,7 @@ public async Task CanRunPesterTestFile()
566558
[InlineData("-ProcessId 1234 -RunspaceId 5678", null, null, 1234, 5678, null)]
567559
[InlineData("-ProcessId 1234 -RunspaceId 5678 -ComputerName comp", "comp", null, 1234, 5678, null)]
568560
[InlineData("-CustomPipeName testpipe -RunspaceName rs-name", null, "testpipe", 0, 0, "rs-name")]
569-
[SkippableTheoryOnWindowsPowerShell]
561+
[SkippableTheory]
570562
public async Task CanLaunchScriptWithNewChildAttachSession(
571563
string paramString,
572564
string? expectedComputerName,
@@ -578,6 +570,9 @@ public async Task CanLaunchScriptWithNewChildAttachSession(
578570
Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode,
579571
"PowerShellEditorServices.Command is not signed to run FLM in Constrained Language Mode.");
580572

573+
Skip.If(PsesStdioLanguageServerProcessHost.IsWindowsPowerShell,
574+
"The Start-DebugAttachSession reverse-request round-trip is flaky on in-box Windows PowerShell CI runners (see #2323).");
575+
581576
string script = NewTestFile($"Start-DebugAttachSession {paramString}");
582577

583578
using CancellationTokenSource timeoutCts = new(30000);
@@ -604,7 +599,7 @@ public async Task CanLaunchScriptWithNewChildAttachSession(
604599
await terminatedTcs.Task;
605600
}
606601

607-
[SkippableFactOnWindowsPowerShell]
602+
[SkippableFact]
608603
public async Task CanLaunchScriptWithNewChildAttachSessionAsJob()
609604
{
610605
Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode,
@@ -638,14 +633,15 @@ public async Task CanLaunchScriptWithNewChildAttachSessionAsJob()
638633
await terminatedTcs.Task;
639634
}
640635

641-
// Timeout is a per-test backstop; the Windows PowerShell skip happens at
642-
// discovery time via the attribute (see the class remarks).
643-
[SkippableFactOnWindowsPowerShell(Timeout = 15000)]
636+
[SkippableFact(Timeout = 15000)]
644637
public async Task CanAttachScriptWithPathMappings()
645638
{
646639
Skip.If(PsesStdioLanguageServerProcessHost.RunningInConstrainedLanguageMode,
647640
"Breakpoints can't be set in Constrained Language Mode.");
648641

642+
Skip.If(PsesStdioLanguageServerProcessHost.IsWindowsPowerShell,
643+
"In-box Windows PowerShell wedges on the cross-process Debug-Runspace attach handshake (see #2323).");
644+
649645
string[] logStatements = ["$PSCommandPath", "after breakpoint"];
650646

651647
await RunWithAttachableProcess(logStatements, async (filePath, processId, runspaceId) =>

test/PowerShellEditorServices.Test.E2E/Hosts/SkippableFactOnWindowsPowerShellAttribute.cs

Lines changed: 0 additions & 56 deletions
This file was deleted.

test/PowerShellEditorServices.Test.E2E/Hosts/SkippableTheoryOnWindowsPowerShellAttribute.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

test/PowerShellEditorServices.Test.E2E/LSPTestsFixtures.cs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,6 @@ public class LSPTestsFixture : IAsyncLifetime
4242

4343
public async Task InitializeAsync()
4444
{
45-
// All LSP end-to-end tests are skipped at discovery time on Windows
46-
// PowerShell (see SkippableFactOnWindowsPowerShell), but xUnit still
47-
// creates this class fixture even when every test method is skipped.
48-
// The in-box Windows PowerShell server can wedge during startup on the
49-
// current windows-latest runner image (a runner-image regression, not
50-
// our code); see https://github.com/PowerShell/PowerShellEditorServices/issues/2323.
51-
// So we must not start the server here on Windows PowerShell.
52-
if (PsesStdioLanguageServerProcessHost.IsWindowsPowerShell)
53-
{
54-
return;
55-
}
56-
5745
(StreamReader stdout, StreamWriter stdin) = await _psesHost.Start();
5846

5947
// Splice the streams together and enable debug logging of all messages sent and received
@@ -112,8 +100,11 @@ public async Task InitializeAsync()
112100

113101
public async Task DisposeAsync()
114102
{
115-
// The server is never started on Windows PowerShell (see
116-
// InitializeAsync), so there is nothing to shut down there.
103+
// If InitializeAsync failed before the client connected (e.g. the
104+
// server never finished starting), PsesLanguageClient was never
105+
// assigned, so there is nothing to shut down. Guarding here keeps a
106+
// startup failure from being masked by a NullReferenceException
107+
// during teardown.
117108
if (PsesLanguageClient is null)
118109
{
119110
return;

0 commit comments

Comments
 (0)