Skip to content

Commit fa3413a

Browse files
authored
Merge pull request #1581 from rjmholt/pt-eventing
Implement OnIdle engine events
2 parents 79a586b + 08df557 commit fa3413a

File tree

2 files changed

+45
-9
lines changed

2 files changed

+45
-9
lines changed

src/PowerShellEditorServices/Services/PowerShell/Execution/BlockingConcurrentDeque.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public BlockingConcurrentDeque()
3939
};
4040
}
4141

42-
public int Count => _queues[0].Count + _queues[1].Count;
42+
public bool IsEmpty => _queues[0].Count == 0 && _queues[1].Count == 0;
4343

4444
public void Prepend(T item)
4545
{

src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs

+44-8
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns
5757

5858
private readonly IdempotentLatch _isRunningLatch = new();
5959

60+
private EngineIntrinsics _mainRunspaceEngineIntrinsics;
61+
6062
private bool _shouldExit = false;
6163

6264
private string _localComputerName;
@@ -347,17 +349,18 @@ public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cance
347349

348350
private void Run()
349351
{
350-
(PowerShell pwsh, RunspaceInfo localRunspaceInfo) = CreateInitialPowerShellSession();
352+
(PowerShell pwsh, RunspaceInfo localRunspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession();
353+
_mainRunspaceEngineIntrinsics = engineIntrinsics;
351354
_localComputerName = localRunspaceInfo.SessionDetails.ComputerName;
352355
_runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo));
353356
PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo);
354357
}
355358

356-
private (PowerShell, RunspaceInfo) CreateInitialPowerShellSession()
359+
private (PowerShell, RunspaceInfo, EngineIntrinsics) CreateInitialPowerShellSession()
357360
{
358-
PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider);
361+
(PowerShell pwsh, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShell(_hostInfo, _readLineProvider);
359362
RunspaceInfo localRunspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh);
360-
return (pwsh, localRunspaceInfo);
363+
return (pwsh, localRunspaceInfo, engineIntrinsics);
361364
}
362365

363366
private void PushPowerShellAndRunLoop(PowerShell pwsh, PowerShellFrameType frameType, RunspaceInfo newRunspaceInfo = null)
@@ -613,7 +616,7 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace)
613616
return pwsh;
614617
}
615618

616-
public PowerShell CreateInitialPowerShell(
619+
public (PowerShell, EngineIntrinsics) CreateInitialPowerShell(
617620
HostStartupInfo hostStartupInfo,
618621
ReadLineProvider readLineProvider)
619622
{
@@ -649,7 +652,7 @@ public PowerShell CreateInitialPowerShell(
649652
}
650653
}
651654

652-
return pwsh;
655+
return (pwsh, engineIntrinsics);
653656
}
654657

655658
private Runspace CreateInitialRunspace(InitialSessionState initialSessionState)
@@ -668,7 +671,27 @@ private Runspace CreateInitialRunspace(InitialSessionState initialSessionState)
668671

669672
private void OnPowerShellIdle()
670673
{
671-
if (_taskQueue.Count == 0)
674+
IReadOnlyList<PSEventSubscriber> eventSubscribers = _mainRunspaceEngineIntrinsics.Events.Subscribers;
675+
676+
// Go through pending event subscribers and:
677+
// - if we have any subscribers, ensure we process any events
678+
// - if we have any idle events, generate an idle event and process that
679+
bool runPipelineForEventProcessing = false;
680+
foreach (PSEventSubscriber subscriber in eventSubscribers)
681+
{
682+
runPipelineForEventProcessing = true;
683+
684+
if (string.Equals(subscriber.SourceIdentifier, PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase))
685+
{
686+
// We control the pipeline thread, so it's not possible for PowerShell to generate events while we're here.
687+
// But we know we're sitting waiting for the prompt, so we generate the idle event ourselves
688+
// and that will flush idle event subscribers in PowerShell so we can service them
689+
_mainRunspaceEngineIntrinsics.Events.GenerateEvent(PSEngineEvent.OnIdle, sender: null, args: null, extraData: null);
690+
break;
691+
}
692+
}
693+
694+
if (!runPipelineForEventProcessing && _taskQueue.IsEmpty)
672695
{
673696
return;
674697
}
@@ -688,9 +711,21 @@ private void OnPowerShellIdle()
688711
return;
689712
}
690713

714+
// If we're executing a task, we don't need to run an extra pipeline later for events
715+
// TODO: This may not be a PowerShell task, so ideally we can differentiate that here.
716+
// For now it's mostly true and an easy assumption to make.
717+
runPipelineForEventProcessing = false;
691718
task.ExecuteSynchronously(cancellationScope.CancellationToken);
692719
}
693720
}
721+
722+
// We didn't end up executing anything in the background,
723+
// so we need to run a small artificial pipeline instead
724+
// to force event processing
725+
if (runPipelineForEventProcessing)
726+
{
727+
InvokePSCommand(new PSCommand().AddScript("0", useLocalScope: true), PowerShellExecutionOptions.Default, CancellationToken.None);
728+
}
694729
}
695730

696731
private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args)
@@ -771,7 +806,8 @@ private Task PopOrReinitializeRunspaceAsync()
771806
// If our main runspace was corrupted,
772807
// we must re-initialize our state.
773808
// TODO: Use runspace.ResetRunspaceState() here instead
774-
(PowerShell pwsh, RunspaceInfo runspaceInfo) = CreateInitialPowerShellSession();
809+
(PowerShell pwsh, RunspaceInfo runspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession();
810+
_mainRunspaceEngineIntrinsics = engineIntrinsics;
775811
PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, PowerShellFrameType.Normal));
776812

777813
_logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized."

0 commit comments

Comments
 (0)