Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="LanguageServer\References.cs" />
<Compile Include="Server\LanguageServerSettings.cs" />
<Compile Include="Server\OutputDebouncer.cs" />
<Compile Include="Server\PromptHandlers.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@
[assembly: AssemblyFileVersion("0.0.0.0")]
[assembly: AssemblyInformationalVersion("0.0.0.0")]

[assembly: InternalsVisibleTo("Microsoft.PowerShell.EditorServices.Test.Protocol")]
20 changes: 12 additions & 8 deletions src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter;
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
using Microsoft.PowerShell.EditorServices.Protocol.Server;
using Microsoft.PowerShell.EditorServices.Utility;
using System;
using System.Collections.Generic;
Expand All @@ -19,6 +18,7 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.Server
public class DebugAdapter : DebugAdapterBase
{
private EditorSession editorSession;
private OutputDebouncer outputDebouncer;

public DebugAdapter() : this(new StdioServerChannel())
{
Expand All @@ -30,6 +30,9 @@ public DebugAdapter(ChannelBase serverChannel) : base(serverChannel)
this.editorSession.StartSession();
this.editorSession.DebugService.DebuggerStopped += this.DebugService_DebuggerStopped;
this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten;

// Set up the output debouncer to throttle output event writes
this.outputDebouncer = new OutputDebouncer(this);
}

protected override void Initialize()
Expand Down Expand Up @@ -59,6 +62,9 @@ protected override void Initialize()

protected override void Shutdown()
{
// Make sure remaining output is flushed before exiting
this.outputDebouncer.Flush().Wait();

Logger.Write(LogLevel.Normal, "Debug adapter is shutting down...");

if (this.editorSession != null)
Expand Down Expand Up @@ -359,6 +365,9 @@ await requestContext.SendResult(

async void DebugService_DebuggerStopped(object sender, DebuggerStopEventArgs e)
{
// Flush pending output before sending the event
await this.outputDebouncer.Flush();

await this.SendEvent(
StoppedEvent.Type,
new StoppedEventBody
Expand All @@ -376,13 +385,8 @@ await this.SendEvent(

async void powerShellContext_OutputWritten(object sender, OutputWrittenEventArgs e)
{
await this.SendEvent(
OutputEvent.Type,
new OutputEventBody
{
Output = e.OutputText + (e.IncludeNewLine ? "\r\n" : string.Empty),
Category = (e.OutputType == OutputType.Error) ? "stderr" : "stdout"
});
// Queue the output for writing
await this.outputDebouncer.Invoke(e);
}

#endregion
Expand Down
16 changes: 9 additions & 7 deletions src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class LanguageServer : LanguageServerBase
private static CancellationTokenSource existingRequestCancellation;

private EditorSession editorSession;
private OutputDebouncer outputDebouncer;
private LanguageServerSettings currentSettings = new LanguageServerSettings();

public LanguageServer() : this(new StdioServerChannel())
Expand All @@ -44,6 +45,9 @@ public LanguageServer(ChannelBase serverChannel) : base(serverChannel)
new ProtocolPromptHandlerContext(
this,
this.editorSession.ConsoleService));

// Set up the output debouncer to throttle output event writes
this.outputDebouncer = new OutputDebouncer(this);
}

protected override void Initialize()
Expand Down Expand Up @@ -78,6 +82,9 @@ protected override void Initialize()

protected override void Shutdown()
{
// Make sure remaining output is flushed before exiting
this.outputDebouncer.Flush().Wait();

Logger.Write(LogLevel.Normal, "Language service is shutting down...");

if (this.editorSession != null)
Expand Down Expand Up @@ -757,13 +764,8 @@ protected Task HandleEvaluateRequest(

async void powerShellContext_OutputWritten(object sender, OutputWrittenEventArgs e)
{
await this.SendEvent(
DebugAdapterMessages.OutputEvent.Type,
new DebugAdapterMessages.OutputEventBody
{
Output = e.OutputText + (e.IncludeNewLine ? "\r\n" : string.Empty),
Category = (e.OutputType == OutputType.Error) ? "stderr" : "stdout"
});
// Queue the output for writing
await this.outputDebouncer.Invoke(e);
}

#endregion
Expand Down
102 changes: 102 additions & 0 deletions src/PowerShellEditorServices.Protocol/Server/OutputDebouncer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter;
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
using Microsoft.PowerShell.EditorServices.Utility;
using System.Threading.Tasks;

namespace Microsoft.PowerShell.EditorServices.Protocol.Server
{
/// <summary>
/// Throttles output written via OutputEvents by batching all output
/// written within a short time window and writing it all out at once.
/// </summary>
internal class OutputDebouncer : AsyncDebouncer<OutputWrittenEventArgs>
{
#region Private Fields

private IMessageSender messageSender;
private bool currentOutputIsError = false;
private string currentOutputString = null;

#endregion

#region Constants

// Set a really short window for output flushes. This
// gives the appearance of fast output without the crushing
// overhead of sending an OutputEvent for every single line
// written. At this point it seems that around 10-20 lines get
// batched for each flush when Get-Process is called.
public const int OutputFlushInterval = 200;

#endregion

#region Constructors

public OutputDebouncer(IMessageSender messageSender)
: base(OutputFlushInterval, false)
{
this.messageSender = messageSender;
}

#endregion

#region Private Methods

protected override async Task OnInvoke(OutputWrittenEventArgs output)
{
bool outputIsError = output.OutputType == OutputType.Error;

if (this.currentOutputIsError != outputIsError)
{
if (this.currentOutputString != null)
{
// Flush the output
await this.OnFlush();
}

this.currentOutputString = string.Empty;
this.currentOutputIsError = outputIsError;
}

// Output string could be null if the last output was already flushed
if (this.currentOutputString == null)
{
this.currentOutputString = string.Empty;
}

// Add to string (and include newline)
this.currentOutputString +=
output.OutputText +
(output.IncludeNewLine ?
System.Environment.NewLine :
string.Empty);
}

protected override async Task OnFlush()
{
// Only flush output if there is some to flush
if (this.currentOutputString != null)
{
// Send an event for the current output
await this.messageSender.SendEvent(
OutputEvent.Type,
new OutputEventBody
{
Output = this.currentOutputString,
Category = (this.currentOutputIsError) ? "stderr" : "stdout"
});

// Clear the output string for the next batch
this.currentOutputString = null;
}
}

#endregion
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
<Compile Include="Session\SessionPSHostUserInterface.cs" />
<Compile Include="Session\SessionStateChangedEventArgs.cs" />
<Compile Include="Utility\AsyncContextThread.cs" />
<Compile Include="Utility\AsyncDebouncer.cs" />
<Compile Include="Utility\AsyncLock.cs" />
<Compile Include="Utility\AsyncQueue.cs" />
<Compile Include="Utility\AsyncContext.cs" />
Expand Down
Loading