Skip to content

Commit 2eae994

Browse files
overhaul the outputhandler
1 parent 314eba8 commit 2eae994

13 files changed

+185
-158
lines changed

Directory.Build.targets

Lines changed: 53 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,53 @@
1-
<?xml version="1.0" encoding="utf-8"?>
2-
<Project>
3-
<PropertyGroup>
4-
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
5-
</PropertyGroup>
6-
<ItemGroup>
7-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
8-
<PackageReference Include="Rocket.Surgery.MSBuild.CI" Version="1.1.0" PrivateAssets="All" />
9-
<PackageReference Include="Rocket.Surgery.MSBuild.Metadata" Version="1.1.0" PrivateAssets="All" />
10-
<PackageReference Include="Rocket.Surgery.MSBuild.SourceLink" Version="1.1.0" PrivateAssets="All" />
11-
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
12-
</ItemGroup>
13-
<ItemGroup>
14-
<PackageReference Update="GitVersion.Tool" Version="5.6.6" />
15-
<PackageReference Update="JetBrains.ReSharper.CommandLineTools" Version="2020.3.3" />
16-
<PackageReference Update="ReportGenerator" Version="4.8.6" />
17-
<PackageReference Update="Rocket.Surgery.Nuke" Version="0.14.3" />
18-
</ItemGroup>
19-
<ItemGroup>
20-
<PackageReference Update="Microsoft.Extensions.Logging" Version="2.0.0" />
21-
<PackageReference Update="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
22-
<PackageReference Update="Microsoft.Extensions.Configuration" Version="2.0.0" />
23-
<PackageReference Update="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" />
24-
<PackageReference Update="Microsoft.Extensions.Options" Version="2.0.0" />
25-
<PackageReference Update="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
26-
<PackageReference Update="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
27-
<PackageReference Update="Newtonsoft.Json" Version="11.0.2" />
28-
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.9.1" />
29-
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.3" />
30-
<PackageReference Update="xunit" Version="2.4.1" />
31-
<PackageReference Update="FluentAssertions" Version="5.10.3" />
32-
<PackageReference Update="NSubstitute" Version="4.2.2" />
33-
<PackageReference Update="Serilog.Extensions.Logging" Version="2.0.2" />
34-
<PackageReference Update="Serilog.Sinks.Observable" Version="2.0.2" />
35-
<PackageReference Update="Serilog.Sinks.XUnit" Version="2.0.4" />
36-
<PackageReference Update="XunitXml.TestLogger" Version="3.0.62" />
37-
<PackageReference Update="coverlet.collector" Version="3.0.3" />
38-
<PackageReference Update="coverlet.msbuild" Version="3.0.3" />
39-
<PackageReference Update="System.Reactive" Version="4.4.1" />
40-
<PackageReference Update="System.Collections.Immutable" Version="1.7.1" />
41-
<PackageReference Update="Microsoft.Reactive.Testing" Version="4.4.1" />
42-
<PackageReference Update="MediatR" Version="8.1.0" />
43-
<PackageReference Update="Bogus" Version="33.0.2" />
44-
<PackageReference Update="Snapper" Version="2.3.0" />
45-
<PackageReference Update="Xunit.SkippableFact" Version="1.4.13" />
46-
<PackageReference Update="System.IO.Pipelines" Version="4.7.3" />
47-
<PackageReference Update="Nerdbank.Streams" Version="2.6.81" />
48-
<PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
49-
<PackageReference Update="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" />
50-
<PackageReference Update="DryIoc.Internal" Version="4.7.3" />
51-
</ItemGroup>
52-
</Project>
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project>
3+
<PropertyGroup>
4+
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
8+
<PackageReference Include="Rocket.Surgery.MSBuild.CI" Version="1.1.0" PrivateAssets="All"/>
9+
<PackageReference Include="Rocket.Surgery.MSBuild.Metadata" Version="1.1.0" PrivateAssets="All"/>
10+
<PackageReference Include="Rocket.Surgery.MSBuild.SourceLink" Version="1.1.0" PrivateAssets="All"/>
11+
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All"/>
12+
</ItemGroup>
13+
<ItemGroup>
14+
<PackageReference Update="GitVersion.Tool" Version="5.6.6"/>
15+
<PackageReference Update="JetBrains.ReSharper.CommandLineTools" Version="2020.3.3"/>
16+
<PackageReference Update="ReportGenerator" Version="4.8.6"/>
17+
<PackageReference Update="Rocket.Surgery.Nuke" Version="0.14.3"/>
18+
</ItemGroup>
19+
<ItemGroup>
20+
<PackageReference Update="Microsoft.Extensions.Logging" Version="2.0.0"/>
21+
<PackageReference Update="Microsoft.Extensions.Logging.Debug" Version="2.0.0"/>
22+
<PackageReference Update="Microsoft.Extensions.Configuration" Version="2.0.0"/>
23+
<PackageReference Update="Microsoft.Extensions.Configuration.Binder" Version="2.0.0"/>
24+
<PackageReference Update="Microsoft.Extensions.Options" Version="2.0.0"/>
25+
<PackageReference Update="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0"/>
26+
<PackageReference Update="Microsoft.Extensions.DependencyInjection" Version="2.0.0"/>
27+
<PackageReference Update="Newtonsoft.Json" Version="11.0.2"/>
28+
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.9.1"/>
29+
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.3"/>
30+
<PackageReference Update="xunit" Version="2.4.1"/>
31+
<PackageReference Update="FluentAssertions" Version="5.10.3"/>
32+
<PackageReference Update="NSubstitute" Version="4.2.2"/>
33+
<PackageReference Update="Serilog.Extensions.Logging" Version="2.0.2"/>
34+
<PackageReference Update="Serilog.Sinks.Observable" Version="2.0.2"/>
35+
<PackageReference Update="Serilog.Sinks.XUnit" Version="2.0.4"/>
36+
<PackageReference Update="XunitXml.TestLogger" Version="3.0.62"/>
37+
<PackageReference Update="coverlet.collector" Version="3.0.3"/>
38+
<PackageReference Update="coverlet.msbuild" Version="3.0.3"/>
39+
<PackageReference Update="System.Reactive" Version="4.4.1"/>
40+
<PackageReference Update="System.Collections.Immutable" Version="1.7.1"/>
41+
<PackageReference Update="System.Threading.Channels" Version="4.7.1"/>
42+
<PackageReference Update="Microsoft.Reactive.Testing" Version="4.4.1"/>
43+
<PackageReference Update="MediatR" Version="8.1.0"/>
44+
<PackageReference Update="Bogus" Version="33.0.2"/>
45+
<PackageReference Update="Snapper" Version="2.3.0"/>
46+
<PackageReference Update="Xunit.SkippableFact" Version="1.4.13"/>
47+
<PackageReference Update="System.IO.Pipelines" Version="4.7.3"/>
48+
<PackageReference Update="Nerdbank.Streams" Version="2.6.81"/>
49+
<PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0"/>
50+
<PackageReference Update="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2"/>
51+
<PackageReference Update="DryIoc.Internal" Version="4.7.3"/>
52+
</ItemGroup>
53+
</Project>

src/JsonRpc/JsonRpc.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<PackageReference Include="MediatR" />
1616
<PackageReference Include="System.IO.Pipelines" />
1717
<PackageReference Include="Nerdbank.Streams" />
18+
<PackageReference Include="System.Threading.Channels" />
1819
<PackageReference Include="DryIoc.Internal" PrivateAssets="All" />
1920
<ProjectReference Include="..\JsonRpc.Generators\JsonRpc.Generators.csproj" IncludeAssets="analyzers" ExcludeAssets="compile;runtime;native" PrivateAssets="contentfiles;build;buildMultitargeting;buildTransitive" OutputItemType="Analyzer" />
2021
</ItemGroup>

src/JsonRpc/OutputHandler.cs

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
using System.ComponentModel;
44
using System.IO.Pipelines;
55
using System.Linq;
6+
using System.Reactive;
67
using System.Reactive.Concurrency;
78
using System.Reactive.Disposables;
89
using System.Reactive.Linq;
910
using System.Reactive.Subjects;
1011
using System.Text;
1112
using System.Threading;
13+
using System.Threading.Channels;
1214
using System.Threading.Tasks;
1315
using Microsoft.Extensions.Logging;
1416

@@ -20,11 +22,16 @@ public class OutputHandler : IOutputHandler
2022
private readonly ISerializer _serializer;
2123
private readonly IEnumerable<IOutputFilter> _outputFilters;
2224
private readonly ILogger<OutputHandler> _logger;
23-
private readonly Subject<object> _queue;
24-
private readonly ReplaySubject<object> _delayedQueue;
25+
26+
27+
private readonly ChannelReader<object> _queue;
28+
private readonly Queue<object> _delayedQueue;
2529
private readonly TaskCompletionSource<object?> _outputIsFinished;
2630
private readonly CompositeDisposable _disposable;
2731
private bool _delayComplete;
32+
private readonly CancellationTokenSource _stopProcessing;
33+
private readonly Channel<object> _channel;
34+
private readonly ChannelWriter<object> _writer;
2835

2936
public OutputHandler(
3037
PipeWriter pipeWriter,
@@ -38,22 +45,26 @@ ILogger<OutputHandler> logger
3845
_serializer = serializer;
3946
_outputFilters = outputFilters.ToArray();
4047
_logger = logger;
41-
_queue = new Subject<object>();
42-
_delayedQueue = new ReplaySubject<object>();
48+
_delayedQueue = new Queue<object>();
4349
_outputIsFinished = new TaskCompletionSource<object?>();
4450

51+
_channel = Channel.CreateUnbounded<object>(
52+
new UnboundedChannelOptions() {
53+
AllowSynchronousContinuations = true,
54+
SingleReader = true,
55+
SingleWriter = false
56+
}
57+
);
58+
_queue = _channel.Reader;
59+
_writer = _channel.Writer;
60+
61+
_stopProcessing = new CancellationTokenSource();
4562
_disposable = new CompositeDisposable {
46-
_queue
47-
.ObserveOn(scheduler)
48-
.Select(value => Observable.FromAsync(ct => ProcessOutputStream(value, ct)))
49-
.Concat()
50-
.Subscribe(),
51-
_delayedQueue
52-
.ToArray()
53-
.SelectMany(z => z)
54-
.Subscribe(_queue.OnNext),
55-
_queue,
56-
_delayedQueue
63+
Disposable.Create(() => _stopProcessing.Cancel()),
64+
_stopProcessing,
65+
Observable.FromAsync(() => ProcessOutputStream(_stopProcessing.Token))
66+
.Do(_ => { }, e => _logger.LogCritical(e, "unhandled exception"))
67+
.Subscribe()
5768
};
5869
}
5970

@@ -64,34 +75,39 @@ private bool ShouldSend(object value)
6475

6576
public void Send(object? value)
6677
{
67-
// _logger.LogTrace("Writing out value {@Value} ({Type})", value, value?.GetType().FullName);
68-
6978
try
7079
{
71-
if (_queue.IsDisposed || _disposable.IsDisposed || value == null) return;
72-
if (!ShouldSend(value))
80+
if (_disposable.IsDisposed || value == null) return;
81+
if (!ShouldSend(value) && !_delayComplete)
7382
{
74-
if (_delayComplete || _delayedQueue.IsDisposed || !_delayedQueue.HasObservers) return;
75-
_delayedQueue.OnNext(value);
83+
_delayedQueue.Enqueue(value);
7684
}
7785
else
7886
{
79-
_queue.OnNext(value);
87+
_writer.TryWrite(value);
8088
}
8189
}
82-
catch (ObjectDisposedException) { }
90+
catch (ObjectDisposedException)
91+
{
92+
}
8393
}
8494

8595
public void Initialized()
8696
{
87-
if (_delayComplete || _delayedQueue.IsDisposed || !_delayedQueue.HasObservers) return;
88-
_delayedQueue.OnCompleted();
97+
if (_delayComplete) return;
98+
while (_delayedQueue.Count > 0)
99+
{
100+
var item = _delayedQueue.Dequeue();
101+
_writer.TryWrite(item);
102+
}
103+
89104
_delayComplete = true;
90-
_delayedQueue.Dispose();
105+
_delayedQueue.Clear();
91106
}
92107

93108
public async Task StopAsync()
94109
{
110+
_channel.Writer.TryComplete();
95111
await _pipeWriter.CompleteAsync().ConfigureAwait(false);
96112
_disposable.Dispose();
97113
}
@@ -107,17 +123,21 @@ internal async Task WriteAndFlush()
107123
await _pipeWriter.CompleteAsync().ConfigureAwait(false);
108124
}
109125

110-
private async Task ProcessOutputStream(object value, CancellationToken cancellationToken)
126+
private async Task ProcessOutputStream(CancellationToken cancellationToken)
111127
{
112128
try
113129
{
130+
do
131+
{
132+
var value = await _queue.ReadAsync(cancellationToken);
114133
// _logger.LogTrace("Writing out {@Value}", value);
115-
// TODO: this will be part of the serialization refactor to make streaming first class
116-
var content = _serializer.SerializeObject(value);
117-
var contentBytes = Encoding.UTF8.GetBytes(content).AsMemory();
118-
await _pipeWriter.WriteAsync(Encoding.UTF8.GetBytes($"Content-Length: {contentBytes.Length}\r\n\r\n"), cancellationToken).ConfigureAwait(false);
119-
await _pipeWriter.WriteAsync(contentBytes, cancellationToken).ConfigureAwait(false);
120-
await _pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false);
134+
// TODO: this will be part of the serialization refactor to make streaming first class
135+
var content = _serializer.SerializeObject(value);
136+
var contentBytes = Encoding.UTF8.GetBytes(content).AsMemory();
137+
await _pipeWriter.WriteAsync(Encoding.UTF8.GetBytes($"Content-Length: {contentBytes.Length}\r\n\r\n"), cancellationToken).ConfigureAwait(false);
138+
await _pipeWriter.WriteAsync(contentBytes, cancellationToken).ConfigureAwait(false);
139+
await _pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false);
140+
} while (true);
121141
}
122142
catch (OperationCanceledException ex) when (ex.CancellationToken != cancellationToken)
123143
{
@@ -136,12 +156,14 @@ private async Task ProcessOutputStream(object value, CancellationToken cancellat
136156
private void Error(Exception ex)
137157
{
138158
_outputIsFinished.TrySetResult(ex);
159+
_writer.TryComplete();
139160
_disposable.Dispose();
140161
}
141162

142163
public void Dispose()
143164
{
144165
_outputIsFinished.TrySetResult(null);
166+
_writer.TryComplete();
145167
_disposable.Dispose();
146168
}
147169
}

test/Lsp.Tests/Integration/LanguageServerLoggingTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public LanguageServerLoggingTests(ITestOutputHelper outputHelper) : base(new Jso
2525
{
2626
}
2727

28-
[RetryFact]
28+
[Fact]//[RetryFact]
2929
public async Task Logs_Are_Sent_To_Client_From_Server()
3030
{
3131
var logs = new ConcurrentBag<LogMessageParams>();
@@ -63,7 +63,7 @@ public async Task Logs_Are_Sent_To_Client_From_Server()
6363
items.Where(z => z.Type == MessageType.Log).Should().HaveCount(2);
6464
}
6565

66-
[RetryFact]
66+
[Fact]//[RetryFact]
6767
public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_SetMinimumLevel()
6868
{
6969
var logs = new ConcurrentBag<LogMessageParams>();
@@ -101,7 +101,7 @@ public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_SetMinimumLevel
101101
items.Where(z => z.Type == MessageType.Log).Should().HaveCount(0);
102102
}
103103

104-
[RetryFact]
104+
[Fact]//[RetryFact]
105105
public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_TraceLevel()
106106
{
107107
var logs = new ConcurrentBag<LogMessageParams>();
@@ -139,7 +139,7 @@ public async Task Logs_Are_Sent_To_Client_From_Server_Respecting_TraceLevel()
139139
items.Where(z => z.Type == MessageType.Log).Should().HaveCount(0);
140140
}
141141

142-
[RetryFact]
142+
[Fact]//[RetryFact]
143143
public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Off_To_Verbose()
144144
{
145145
var logs = new ConcurrentBag<LogMessageParams>();
@@ -204,7 +204,7 @@ public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Off_To_Verbos
204204
}
205205
}
206206

207-
[RetryFact]
207+
[Fact]//[RetryFact]
208208
public async Task Client_Can_Dynamically_Change_Server_Trace_Level_Verbose_To_Off()
209209
{
210210
var logs = new ConcurrentBag<LogMessageParams>();

test/Lsp.Tests/Integration/PartialItemTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public Delegates(ITestOutputHelper testOutputHelper, LanguageProtocolFixture<Def
2424
{
2525
}
2626

27-
[RetryFact]
27+
[Fact]//[RetryFact]
2828
public async Task Should_Behave_Like_A_Task()
2929
{
3030
var result = await Client.TextDocument.RequestSemanticTokens(
@@ -34,7 +34,7 @@ public async Task Should_Behave_Like_A_Task()
3434
result!.Data.Should().HaveCount(3);
3535
}
3636

37-
[RetryFact]
37+
[Fact]//[RetryFact]
3838
public async Task Should_Behave_Like_An_Observable()
3939
{
4040
var items = await Client.TextDocument
@@ -53,7 +53,7 @@ public async Task Should_Behave_Like_An_Observable()
5353
items.Select(z => z.Data.Length).Should().ContainInOrder(1, 2, 3);
5454
}
5555

56-
[RetryFact]
56+
[Fact]//[RetryFact]
5757
public async Task Should_Behave_Like_An_Observable_Without_Progress_Support()
5858
{
5959
var response = await Client.SendRequest(new SemanticTokensParams { TextDocument = new TextDocumentIdentifier(@"c:\test.cs") }, CancellationToken);

test/Lsp.Tests/Integration/PartialItemsTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public Delegates(ITestOutputHelper testOutputHelper, LanguageProtocolFixture<Def
2929
{
3030
}
3131

32-
[RetryFact]
32+
[Fact]//[RetryFact]
3333
public async Task Should_Behave_Like_A_Task()
3434
{
3535
var result = await Client.TextDocument.RequestCodeLens(
@@ -42,7 +42,7 @@ public async Task Should_Behave_Like_A_Task()
4242
result.Select(z => z.Command!.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3");
4343
}
4444

45-
[RetryFact]
45+
[Fact]//[RetryFact]
4646
public async Task Should_Behave_Like_An_Observable()
4747
{
4848
var items = await Client.TextDocument
@@ -63,7 +63,7 @@ public async Task Should_Behave_Like_An_Observable()
6363
items.Select(z => z.Command!.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3");
6464
}
6565

66-
[RetryFact]
66+
[Fact]//[RetryFact]
6767
public async Task Should_Behave_Like_An_Observable_Without_Progress_Support()
6868
{
6969
var response = await Client.SendRequest(
@@ -120,7 +120,7 @@ public Handlers(ITestOutputHelper testOutputHelper, LanguageProtocolFixture<Defa
120120
{
121121
}
122122

123-
[RetryFact]
123+
[Fact]//[RetryFact]
124124
public async Task Should_Behave_Like_An_Observable_With_WorkDone()
125125
{
126126
var items = new List<CodeLens>();

0 commit comments

Comments
 (0)