diff --git a/.gitignore b/.gitignore
index 04d1209d3..97caab569 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,7 @@ nuget.exe
debugSettings.json
buildlog
/.vs
+osharp_pack/
# NuGet
*.nuget.targets
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 46eeba140..b8e0bffbf 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,52 +1,53 @@
-
-
-
- $(BaseIntermediateOutputPath)\GeneratedFiles
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+ $(BaseIntermediateOutputPath)\GeneratedFiles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LSP.sln b/LSP.sln
index 1a5d0701e..7ca6d6531 100644
--- a/LSP.sln
+++ b/LSP.sln
@@ -77,6 +77,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dap.Protocol.Proposals", "s
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Protocol.Proposals", "src\Protocol.Proposals\Protocol.Proposals.csproj", "{201B1CA7-AB12-41AD-9246-BC30F2EBE2DF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lsp.Integration.Tests", "test\Lsp.Integration.Tests\Lsp.Integration.Tests.csproj", "{72A74595-A278-46F0-9C8B-3151C9681B91}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -357,6 +359,18 @@ Global
{201B1CA7-AB12-41AD-9246-BC30F2EBE2DF}.Release|x64.Build.0 = Release|Any CPU
{201B1CA7-AB12-41AD-9246-BC30F2EBE2DF}.Release|x86.ActiveCfg = Release|Any CPU
{201B1CA7-AB12-41AD-9246-BC30F2EBE2DF}.Release|x86.Build.0 = Release|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|x64.Build.0 = Debug|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Debug|x86.Build.0 = Debug|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|x64.ActiveCfg = Release|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|x64.Build.0 = Release|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|x86.ActiveCfg = Release|Any CPU
+ {72A74595-A278-46F0-9C8B-3151C9681B91}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -385,6 +399,7 @@ Global
{58E83291-1ED9-4921-A12F-F2450AB17F47} = {2F323ED5-EBF8-45E1-B9D3-C014561B3DDA}
{D43637CC-94E6-4ED4-BAA3-E5D1AD3285F5} = {D764E024-3D3F-4112-B932-2DB722A1BACC}
{201B1CA7-AB12-41AD-9246-BC30F2EBE2DF} = {D764E024-3D3F-4112-B932-2DB722A1BACC}
+ {72A74595-A278-46F0-9C8B-3151C9681B91} = {2F323ED5-EBF8-45E1-B9D3-C014561B3DDA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D38DD0EC-D095-4BCD-B8AF-2D788AF3B9AE}
diff --git a/build.sh b/build.sh
old mode 100644
new mode 100755
diff --git a/package-lock.json b/package-lock.json
index c55d02cb2..bc8cb820c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -344,9 +344,9 @@
"dev": true
},
"husky": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/husky/-/husky-5.1.1.tgz",
- "integrity": "sha512-80LZ736V0Nr4/st0c2COYaMbEQhHNmAbLMN8J/kLk7/mo0QdUkUGNDjv/7jVkhug377Wh8wfbWyaVXEJ/h2B/Q==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz",
+ "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==",
"dev": true
},
"import-fresh": {
diff --git a/package.json b/package.json
index 6d664e7cf..31a2396f6 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"devDependencies": {
- "husky": "^5.1.1",
+ "husky": "^6.0.0",
"lint-staged": "^10.5.4",
"prettier": "^2.2.1"
}
diff --git a/src/Client/LanguageClient.cs b/src/Client/LanguageClient.cs
index e2304fb25..0d5ba8b19 100644
--- a/src/Client/LanguageClient.cs
+++ b/src/Client/LanguageClient.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
@@ -48,6 +49,7 @@ public class LanguageClient : JsonRpcServerBase, ILanguageClient
private readonly IEnumerable _initializedHandlers;
private readonly LspSerializer _serializer;
private readonly InstanceHasStarted _instanceHasStarted;
+ private readonly IScheduler _scheduler;
private readonly IResponseRouter _responseRouter;
private readonly ISubject _initializeComplete = new AsyncSubject();
private readonly CompositeDisposable _disposable = new CompositeDisposable();
@@ -150,7 +152,8 @@ internal LanguageClient(
IEnumerable initializedDelegates,
IEnumerable initializedHandlers,
LspSerializer serializer,
- InstanceHasStarted instanceHasStarted
+ InstanceHasStarted instanceHasStarted,
+ IScheduler scheduler
) : base(handlerCollection, responseRouter)
{
_connection = connection;
@@ -179,6 +182,7 @@ InstanceHasStarted instanceHasStarted
_initializedHandlers = initializedHandlers;
_serializer = serializer;
_instanceHasStarted = instanceHasStarted;
+ _scheduler = scheduler;
_concurrency = options.Value.Concurrency;
// We need to at least create Window here in case any handler does loggin in their constructor
@@ -262,6 +266,7 @@ await LanguageProtocolEventingHelper.Run(
_initializeHandlers.Union(_collection.Select(z => z.Handler).OfType()),
(handler, ct) => handler.OnInitialize(this, @params, ct),
_concurrency,
+ _scheduler,
token
).ConfigureAwait(false);
@@ -281,6 +286,7 @@ await LanguageProtocolEventingHelper.Run(
_initializedHandlers.Union(_collection.Select(z => z.Handler).OfType()),
(handler, ct) => handler.OnInitialized(this, @params, serverParams, ct),
_concurrency,
+ _scheduler,
token
).ConfigureAwait(false);
@@ -299,6 +305,7 @@ await LanguageProtocolEventingHelper.Run(
_startedHandlers.Union(_collection.Select(z => z.Handler).OfType()),
(handler, ct) => handler.OnStarted(this, ct),
_concurrency,
+ _scheduler,
token
).ConfigureAwait(false);
@@ -395,7 +402,7 @@ private Supports UseOrTryAndFindCapability(Supports supports) where T :
bool IResponseRouter.TryGetRequest(long id, [NotNullWhen(true)] out string method, [NotNullWhen(true)] out TaskCompletionSource pendingTask) =>
_responseRouter.TryGetRequest(id, out method, out pendingTask);
- public Task WasStarted => _initializeComplete.ToTask();
+ public Task WasStarted => _initializeComplete.ToTask(_scheduler);
public void Dispose()
{
diff --git a/src/Client/LanguageClientOptionsExtensions.cs b/src/Client/LanguageClientOptionsExtensions.cs
index 2c10ae955..32fe472e1 100644
--- a/src/Client/LanguageClientOptionsExtensions.cs
+++ b/src/Client/LanguageClientOptionsExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reactive.Concurrency;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -87,6 +88,54 @@ public static LanguageClientOptions WithClientCapabilities(this LanguageClientOp
return options;
}
+ ///
+ /// Sets both input and output schedulers to the same scheduler
+ ///
+ ///
+ ///
+ ///
+ public static LanguageClientOptions WithScheduler(this LanguageClientOptions options, IScheduler inputScheduler)
+ {
+ options.InputScheduler = options.OutputScheduler = options.DefaultScheduler = inputScheduler;
+ return options;
+ }
+
+ ///
+ /// Sets the scheduler used during reading input
+ ///
+ ///
+ ///
+ ///
+ public static LanguageClientOptions WithInputScheduler(this LanguageClientOptions options, IScheduler inputScheduler)
+ {
+ options.InputScheduler = inputScheduler;
+ return options;
+ }
+
+ ///
+ /// Sets the default scheduler to be used when scheduling other tasks
+ ///
+ ///
+ ///
+ ///
+ public static LanguageClientOptions WithDefaultScheduler(this LanguageClientOptions options, IScheduler defaultScheduler)
+ {
+ options.DefaultScheduler = defaultScheduler;
+ return options;
+ }
+
+ ///
+ /// Sets the scheduler use during writing output
+ ///
+ ///
+ ///
+ ///
+ public static LanguageClientOptions WithOutputScheduler(this LanguageClientOptions options, IScheduler outputScheduler)
+ {
+ options.OutputScheduler = outputScheduler;
+ return options;
+ }
+
public static LanguageClientOptions OnInitialize(this LanguageClientOptions options, OnLanguageClientInitializeDelegate @delegate)
{
options.Services.AddSingleton(@delegate);
diff --git a/src/Client/LanguageClientRegistrationManager.cs b/src/Client/LanguageClientRegistrationManager.cs
index 828a7b596..b6703e4c3 100644
--- a/src/Client/LanguageClientRegistrationManager.cs
+++ b/src/Client/LanguageClientRegistrationManager.cs
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
@@ -26,7 +27,7 @@ internal class LanguageClientRegistrationManager : IRegisterCapabilityHandler, I
private readonly ILspHandlerTypeDescriptorProvider _handlerTypeDescriptorProvider;
private readonly ILogger _logger;
private readonly ConcurrentDictionary _registrations;
- private readonly ReplaySubject> _registrationSubject = new ReplaySubject>(1);
+ private readonly ReplaySubject> _registrationSubject = new ReplaySubject>(1, Scheduler.Immediate);
public LanguageClientRegistrationManager(
ISerializer serializer,
diff --git a/src/Client/LanguageClientWorkspaceFoldersManager.cs b/src/Client/LanguageClientWorkspaceFoldersManager.cs
index c768335bc..1397dad26 100644
--- a/src/Client/LanguageClientWorkspaceFoldersManager.cs
+++ b/src/Client/LanguageClientWorkspaceFoldersManager.cs
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
@@ -25,7 +26,7 @@ public LanguageClientWorkspaceFoldersManager(IWorkspaceLanguageClient client, IE
{
_client = client;
_workspaceFolders = new ConcurrentDictionary(DocumentUri.Comparer);
- _workspaceFoldersSubject = new ReplaySubject>(1);
+ _workspaceFoldersSubject = new ReplaySubject>(1, Scheduler.Immediate);
foreach (var folder in workspaceFolders)
{
diff --git a/src/Dap.Client/DebugAdapterClient.cs b/src/Dap.Client/DebugAdapterClient.cs
index 5d8854b95..f2ec06604 100644
--- a/src/Dap.Client/DebugAdapterClient.cs
+++ b/src/Dap.Client/DebugAdapterClient.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Reactive.Threading.Tasks;
@@ -30,6 +31,7 @@ public class DebugAdapterClient : JsonRpcServerBase, IDebugAdapterClient, IDebug
private readonly IEnumerable _startedDelegates;
private readonly IEnumerable _startedHandlers;
private readonly InstanceHasStarted _instanceHasStarted;
+ private readonly IScheduler _scheduler;
private readonly CompositeDisposable _disposable = new CompositeDisposable();
private readonly Connection _connection;
private readonly DapReceiver _receiver;
@@ -97,7 +99,8 @@ internal DebugAdapterClient(
IEnumerable initializedDelegates,
IEnumerable initializedHandlers,
IEnumerable startedHandlers,
- InstanceHasStarted instanceHasStarted
+ InstanceHasStarted instanceHasStarted,
+ IScheduler scheduler
) : base(collection, responseRouter)
{
_settingsBag = settingsBag;
@@ -114,6 +117,7 @@ InstanceHasStarted instanceHasStarted
_initializedHandlers = initializedHandlers;
_startedHandlers = startedHandlers;
_instanceHasStarted = instanceHasStarted;
+ _scheduler = scheduler;
_concurrency = options.Value.Concurrency;
_disposable.Add(collection.Add(this));
@@ -127,6 +131,7 @@ await DebugAdapterEventingHelper.Run(
_initializeHandlers.Union(_collection.Select(z => z.Handler).OfType()),
(handler, ct) => handler.OnInitialize(this, ClientSettings, ct),
_concurrency,
+ _scheduler,
token
).ConfigureAwait(false);
@@ -145,10 +150,11 @@ await DebugAdapterEventingHelper.Run(
_initializedHandlers.Union(_collection.Select(z => z.Handler).OfType()),
(handler, ct) => handler.OnInitialized(this, ClientSettings, ServerSettings, ct),
_concurrency,
+ _scheduler,
token
).ConfigureAwait(false);
- await _initializedComplete.ToTask(token);
+ await _initializedComplete.ToTask(token, _scheduler);
await DebugAdapterEventingHelper.Run(
_startedDelegates,
@@ -156,6 +162,7 @@ await DebugAdapterEventingHelper.Run(
_startedHandlers.Union(_collection.Select(z => z.Handler).OfType()),
(handler, ct) => handler.OnStarted(this, ct),
_concurrency,
+ _scheduler,
token
).ConfigureAwait(false);
diff --git a/src/Dap.Client/DebugAdapterClientOptionsExtensions.cs b/src/Dap.Client/DebugAdapterClientOptionsExtensions.cs
index 4b3f8236c..6c10f6646 100644
--- a/src/Dap.Client/DebugAdapterClientOptionsExtensions.cs
+++ b/src/Dap.Client/DebugAdapterClientOptionsExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reactive.Concurrency;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -21,6 +22,54 @@ public static DebugAdapterClientOptions WithRequestProcessIdentifier(this DebugA
return options;
}
+ ///
+ /// Sets both input and output schedulers to the same scheduler
+ ///
+ ///
+ ///
+ ///
+ public static DebugAdapterClientOptions WithScheduler(this DebugAdapterClientOptions options, IScheduler inputScheduler)
+ {
+ options.InputScheduler = options.OutputScheduler = options.DefaultScheduler = inputScheduler;
+ return options;
+ }
+
+ ///
+ /// Sets the scheduler used during reading input
+ ///
+ ///
+ ///
+ ///
+ public static DebugAdapterClientOptions WithInputScheduler(this DebugAdapterClientOptions options, IScheduler inputScheduler)
+ {
+ options.InputScheduler = inputScheduler;
+ return options;
+ }
+
+ ///
+ /// Sets the default scheduler to be used when scheduling other tasks
+ ///
+ ///
+ ///
+ ///
+ public static DebugAdapterClientOptions WithDefaultScheduler(this DebugAdapterClientOptions options, IScheduler defaultScheduler)
+ {
+ options.DefaultScheduler = defaultScheduler;
+ return options;
+ }
+
+ ///
+ /// Sets the scheduler use during writing output
+ ///
+ ///
+ ///
+ ///
+ public static DebugAdapterClientOptions WithOutputScheduler(this DebugAdapterClientOptions options, IScheduler outputScheduler)
+ {
+ options.OutputScheduler = outputScheduler;
+ return options;
+ }
+
public static DebugAdapterClientOptions OnInitialize(this DebugAdapterClientOptions options, OnDebugAdapterClientInitializeDelegate @delegate)
{
options.Services.AddSingleton(@delegate);
diff --git a/src/Dap.Client/ProgressObservable.cs b/src/Dap.Client/ProgressObservable.cs
index c961689b3..8860ba5c9 100644
--- a/src/Dap.Client/ProgressObservable.cs
+++ b/src/Dap.Client/ProgressObservable.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using OmniSharp.Extensions.DebugAdapter.Protocol;
@@ -14,7 +15,7 @@ internal class ProgressObservable : IProgressObservable, IObserver(1);
+ _dataSubject = new ReplaySubject(1, Scheduler.Immediate);
_disposable = new CompositeDisposable { Disposable.Create(_dataSubject.OnCompleted) };
ProgressToken = token;
diff --git a/src/Dap.Protocol/DapReceiver.cs b/src/Dap.Protocol/DapReceiver.cs
index 709070725..c7555e100 100644
--- a/src/Dap.Protocol/DapReceiver.cs
+++ b/src/Dap.Protocol/DapReceiver.cs
@@ -52,6 +52,8 @@ protected virtual IEnumerable GetRenor(JToken @object)
var sequence = id.Value();
var messageType = type.Value();
+ var properties = request.Properties().ToLookup(z => z.Name, StringComparer.OrdinalIgnoreCase);
+
if (messageType == "event")
{
if (!request.TryGetValue("event", out var @event))
@@ -60,7 +62,10 @@ protected virtual IEnumerable GetRenor(JToken @object)
yield break;
}
- yield return new Notification(@event.Value(), request.TryGetValue("body", out var body) ? body : null);
+ yield return new Notification(@event.Value(), request.TryGetValue("body", out var body) ? body : null) {
+ TraceState = properties["tracestate"].FirstOrDefault()?.Value(),
+ TraceParent = properties["traceparent"].FirstOrDefault()?.Value()
+ };
yield break;
}
@@ -81,16 +86,29 @@ protected virtual IEnumerable GetRenor(JToken @object)
// This makes it so that the cancel handler implementer must still return a positive response even if the request didn't make it through.
if (ro.TryGetValue("requestId", out var requestId))
{
- yield return new Notification(JsonRpcNames.CancelRequest, JObject.FromObject(new { id = requestId }));
+ yield return new Notification(JsonRpcNames.CancelRequest, JObject.FromObject(new { id = requestId })) {
+ TraceState = properties["tracestate"].FirstOrDefault()?.Value(),
+ TraceParent = properties["traceparent"].FirstOrDefault()?.Value()
+ };
ro.Remove("requestId");
}
+ else
+ {
+ yield return new Request(sequence, RequestNames.Cancel, ro) {
+ TraceState = properties["tracestate"].FirstOrDefault()?.Value(),
+ TraceParent = properties["traceparent"].FirstOrDefault()?.Value()
+ };
+ yield break;
+ }
+ }
- yield return new Request(sequence, RequestNames.Cancel, ro);
+ {
+ yield return new Request(sequence, requestName, requestObject) {
+ TraceState = properties["tracestate"].FirstOrDefault()?.Value(),
+ TraceParent = properties["traceparent"].FirstOrDefault()?.Value()
+ };
yield break;
}
-
- yield return new Request(sequence, requestName, requestObject);
- yield break;
}
if (messageType == "response")
diff --git a/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs b/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs
index c4bd824c9..17daf699a 100644
--- a/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs
+++ b/src/Dap.Protocol/DebugAdapterConverters/DapClientNotificationConverter.cs
@@ -33,6 +33,16 @@ public override void WriteJson(JsonWriter writer, OutgoingNotification value, Js
writer.WritePropertyName("body");
serializer.Serialize(writer, value.Params);
}
+ if (value.TraceParent != null)
+ {
+ writer.WritePropertyName("traceparent");
+ writer.WriteValue(value.TraceParent);
+ if (!string.IsNullOrWhiteSpace(value.TraceState))
+ {
+ writer.WritePropertyName("tracestate");
+ writer.WriteValue(value.TraceState);
+ }
+ }
writer.WriteEndObject();
}
diff --git a/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs b/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs
index 1b92d21c1..d69f82722 100644
--- a/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs
+++ b/src/Dap.Protocol/DebugAdapterConverters/DapClientRequestConverter.cs
@@ -28,6 +28,16 @@ public override void WriteJson(JsonWriter writer, OutgoingRequest value, JsonSer
writer.WritePropertyName("arguments");
serializer.Serialize(writer, value.Params);
}
+ if (value.TraceParent != null)
+ {
+ writer.WritePropertyName("traceparent");
+ writer.WriteValue(value.TraceParent);
+ if (!string.IsNullOrWhiteSpace(value.TraceState))
+ {
+ writer.WritePropertyName("tracestate");
+ writer.WriteValue(value.TraceState);
+ }
+ }
writer.WriteEndObject();
}
diff --git a/src/Dap.Protocol/Feature/Events/ProgressFeature.cs b/src/Dap.Protocol/Feature/Events/ProgressFeature.cs
index f0df93eac..16ebd60a5 100644
--- a/src/Dap.Protocol/Feature/Events/ProgressFeature.cs
+++ b/src/Dap.Protocol/Feature/Events/ProgressFeature.cs
@@ -23,7 +23,7 @@ public abstract record ProgressEvent
public string? Message { get; init; }
}
- [Parallel]
+ [Serial]
[Method(EventNames.ProgressStart, Direction.ServerToClient)]
[
GenerateHandler,
@@ -60,7 +60,7 @@ public record ProgressStartEvent : ProgressEvent, IRequest
public int? Percentage { get; init; }
}
- [Parallel]
+ [Serial]
[Method(EventNames.ProgressUpdate, Direction.ServerToClient)]
[
GenerateHandler,
@@ -76,7 +76,7 @@ public record ProgressUpdateEvent : ProgressEvent, IRequest
public double? Percentage { get; init; }
}
- [Parallel]
+ [Serial]
[Method(EventNames.ProgressEnd, Direction.ServerToClient)]
[
GenerateHandler,
diff --git a/src/Dap.Protocol/Feature/Requests/ThreadsFeature.cs b/src/Dap.Protocol/Feature/Requests/ThreadsFeature.cs
index 165077bea..1975a2b2e 100644
--- a/src/Dap.Protocol/Feature/Requests/ThreadsFeature.cs
+++ b/src/Dap.Protocol/Feature/Requests/ThreadsFeature.cs
@@ -2,7 +2,6 @@
using OmniSharp.Extensions.DebugAdapter.Protocol.Models;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.JsonRpc.Generation;
-using Thread = System.Threading.Thread;
// ReSharper disable once CheckNamespace
namespace OmniSharp.Extensions.DebugAdapter.Protocol
diff --git a/src/Dap.Server/DebugAdapterServer.cs b/src/Dap.Server/DebugAdapterServer.cs
index 880b6e7db..7e6cde49a 100644
--- a/src/Dap.Server/DebugAdapterServer.cs
+++ b/src/Dap.Server/DebugAdapterServer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Reactive.Threading.Tasks;
@@ -30,6 +31,7 @@ public class DebugAdapterServer : JsonRpcServerBase, IDebugAdapterServer, IDebug
private readonly IEnumerable _startedDelegates;
private readonly IEnumerable _startedHandlers;
private readonly InstanceHasStarted _instanceHasStarted;
+ private readonly IScheduler _scheduler;
private readonly IServiceProvider _serviceProvider;
private readonly CompositeDisposable _disposable = new CompositeDisposable();
private readonly Connection _connection;
@@ -100,7 +102,8 @@ internal DebugAdapterServer(
IEnumerable initializeHandlers,
IEnumerable initializedHandlers,
IEnumerable startedHandlers,
- InstanceHasStarted instanceHasStarted
+ InstanceHasStarted instanceHasStarted,
+ IScheduler scheduler
) : base(collection, responseRouter)
{
_capabilities = capabilities;
@@ -117,6 +120,7 @@ InstanceHasStarted instanceHasStarted
_initializedHandlers = initializedHandlers;
_startedHandlers = startedHandlers;
_instanceHasStarted = instanceHasStarted;
+ _scheduler = scheduler;
_concurrency = options.Value.Concurrency;
_disposable.Add(collection.Add(this));
@@ -142,7 +146,7 @@ public async Task Initialize(CancellationToken token)
_connection.Open();
try
{
- _initializingTask = _initializeComplete.ToTask(token);
+ _initializingTask = _initializeComplete.ToTask(token, _scheduler);
await _initializingTask.ConfigureAwait(false);
await DebugAdapterEventingHelper.Run(
_startedDelegates,
@@ -150,6 +154,7 @@ await DebugAdapterEventingHelper.Run(
_startedHandlers.Union(_collection.Select(z => z.Handler).OfType()),
(handler, ct) => handler.OnStarted(this, ct),
_concurrency,
+ _scheduler,
token
).ConfigureAwait(false);
_instanceHasStarted.Started = true;
@@ -181,6 +186,7 @@ await DebugAdapterEventingHelper.Run(
_initializeHandlers.Union(_collection.Select(z => z.Handler).OfType()),
(handler, ct) => handler.OnInitialize(this, request, ct),
_concurrency,
+ _scheduler,
cancellationToken
).ConfigureAwait(false);
@@ -231,6 +237,7 @@ await DebugAdapterEventingHelper.Run(
_initializedHandlers.Union(_collection.Select(z => z.Handler).OfType()),
(handler, ct) => handler.OnInitialized(this, request, response, ct),
_concurrency,
+ _scheduler,
cancellationToken
).ConfigureAwait(false);
diff --git a/src/Dap.Server/DebugAdapterServerOptionsExtensions.cs b/src/Dap.Server/DebugAdapterServerOptionsExtensions.cs
index 89aa784bb..f25ed04e0 100644
--- a/src/Dap.Server/DebugAdapterServerOptionsExtensions.cs
+++ b/src/Dap.Server/DebugAdapterServerOptionsExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reactive.Concurrency;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -21,6 +22,54 @@ public static DebugAdapterServerOptions WithRequestProcessIdentifier(this DebugA
return options;
}
+ ///
+ /// Sets both input and output schedulers to the same scheduler
+ ///
+ ///
+ ///
+ ///
+ public static DebugAdapterServerOptions WithScheduler(this DebugAdapterServerOptions options, IScheduler inputScheduler)
+ {
+ options.InputScheduler = options.OutputScheduler = options.DefaultScheduler = inputScheduler;
+ return options;
+ }
+
+ ///
+ /// Sets the scheduler used during reading input
+ ///
+ ///
+ ///
+ ///
+ public static DebugAdapterServerOptions WithInputScheduler(this DebugAdapterServerOptions options, IScheduler inputScheduler)
+ {
+ options.InputScheduler = inputScheduler;
+ return options;
+ }
+
+ ///
+ /// Sets the default scheduler to be used when scheduling other tasks
+ ///
+ ///
+ ///
+ ///
+ public static DebugAdapterServerOptions WithDefaultScheduler(this DebugAdapterServerOptions options, IScheduler defaultScheduler)
+ {
+ options.DefaultScheduler = defaultScheduler;
+ return options;
+ }
+
+ ///
+ /// Sets the scheduler use during writing output
+ ///
+ ///
+ ///
+ ///
+ public static DebugAdapterServerOptions WithOutputScheduler(this DebugAdapterServerOptions options, IScheduler outputScheduler)
+ {
+ options.OutputScheduler = outputScheduler;
+ return options;
+ }
+
public static DebugAdapterServerOptions OnInitialize(this DebugAdapterServerOptions options, OnDebugAdapterServerInitializeDelegate @delegate)
{
options.Services.AddSingleton(@delegate);
diff --git a/src/Dap.Shared/DebugAdapterEventingHelper.cs b/src/Dap.Shared/DebugAdapterEventingHelper.cs
index 60176311f..4db62e462 100644
--- a/src/Dap.Shared/DebugAdapterEventingHelper.cs
+++ b/src/Dap.Shared/DebugAdapterEventingHelper.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading;
@@ -16,6 +17,7 @@ public static Task Run(
IEnumerable handlers,
Func executeHandler,
int? concurrency,
+ IScheduler scheduler,
CancellationToken cancellationToken
)
{
@@ -28,13 +30,13 @@ CancellationToken cancellationToken
{
return events.Merge(concurrency.Value)
.LastOrDefaultAsync()
- .ToTask(cancellationToken);
+ .ToTask(cancellationToken, scheduler);
}
return events
.Merge()
.LastOrDefaultAsync()
- .ToTask(cancellationToken);
+ .ToTask(cancellationToken, scheduler);
}
}
}
diff --git a/src/Dap.Shared/DebugAdapterRequestRouter.cs b/src/Dap.Shared/DebugAdapterRequestRouter.cs
index 8b9e939bc..0cc429eda 100644
--- a/src/Dap.Shared/DebugAdapterRequestRouter.cs
+++ b/src/Dap.Shared/DebugAdapterRequestRouter.cs
@@ -12,9 +12,10 @@ internal class DebugAdapterRequestRouter : RequestRouterBase
private readonly DebugAdapterHandlerCollection _collection;
public DebugAdapterRequestRouter(
- DebugAdapterHandlerCollection collection, ISerializer serializer, IServiceScopeFactory serviceScopeFactory, ILogger logger
+ DebugAdapterHandlerCollection collection, ISerializer serializer, IServiceScopeFactory serviceScopeFactory, ILogger logger,
+ IActivityTracingStrategy? activityTracingStrategy = null
)
- : base(serializer, serviceScopeFactory, logger) =>
+ : base(serializer, serviceScopeFactory, logger, activityTracingStrategy) =>
_collection = collection;
public IDisposable Add(IJsonRpcHandler handler) => _collection.Add(handler);
diff --git a/src/Dap.Testing/DebugAdapterProtocolTestBase.cs b/src/Dap.Testing/DebugAdapterProtocolTestBase.cs
index 92eabb3bf..2cbe87d4e 100644
--- a/src/Dap.Testing/DebugAdapterProtocolTestBase.cs
+++ b/src/Dap.Testing/DebugAdapterProtocolTestBase.cs
@@ -45,10 +45,11 @@ Action serverOptionsAction
options
.WithLoggerFactory(TestOptions.ClientLoggerFactory)
.ConfigureLogging(
- x => {
- x.SetMinimumLevel(LogLevel.Trace);
- }
+ x => { x.SetMinimumLevel(LogLevel.Trace); }
)
+ .WithInputScheduler(options.InputScheduler)
+ .WithOutputScheduler(options.OutputScheduler)
+ .WithDefaultScheduler(options.DefaultScheduler)
.Services
.AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>))
.AddSingleton(ClientEvents as IRequestSettler);
@@ -61,11 +62,10 @@ Action serverOptionsAction
options => {
options
.WithLoggerFactory(TestOptions.ServerLoggerFactory)
- .ConfigureLogging(
- x => {
- x.SetMinimumLevel(LogLevel.Trace);
- }
- )
+ .ConfigureLogging(x => { x.SetMinimumLevel(LogLevel.Trace); })
+ .WithInputScheduler(options.InputScheduler)
+ .WithOutputScheduler(options.OutputScheduler)
+ .WithDefaultScheduler(options.DefaultScheduler)
.Services
.AddTransient(typeof(IPipelineBehavior<,>), typeof(SettlePipeline<,>))
.AddSingleton(ServerEvents as IRequestSettler);
@@ -77,10 +77,9 @@ Action serverOptionsAction
Disposable.Add(_client);
Disposable.Add(_server);
- return await Observable.FromAsync(_client.Initialize).ForkJoin(
- Observable.FromAsync(_server.Initialize),
- (_, _) => ( _client, _server )
- ).ToTask(CancellationToken).ConfigureAwait(false);
+ return await Observable.FromAsync(_client.Initialize)
+ .ForkJoin(Observable.FromAsync(_server.Initialize), (_, _) => ( _client, _server ))
+ .ToTask(CancellationToken);
}
}
}
diff --git a/src/Dap.Testing/DebugAdapterServerTestBase.cs b/src/Dap.Testing/DebugAdapterServerTestBase.cs
index 7654f2cf1..1c53d4354 100644
--- a/src/Dap.Testing/DebugAdapterServerTestBase.cs
+++ b/src/Dap.Testing/DebugAdapterServerTestBase.cs
@@ -37,6 +37,9 @@ protected virtual async Task InitializeClient(Action), typeof(SettlePipeline<,>))
.AddSingleton(Events as IRequestSettler);
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 48b9b1adf..727ebafee 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,33 +1,36 @@
-
-
-
- $(GitVersion_NuGetVersion)
- 0.0.9.9
- $(GitVersion_Major).$(GitVersion_Minor).0.0
- $(GitVersion_AssemblySemVer)
- $(GitVersion_InformationalVersion)
- true
- enable
-
-
-
-
- <_Parameter1>Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
-
-
- <_Parameter1>JsonRpc.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
-
-
- <_Parameter1>Dap.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
-
-
- <_Parameter1>Lsp.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
-
-
- <_Parameter1>TestingUtils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
-
-
- <_Parameter1>DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7
-
-
-
+
+
+
+ $(GitVersion_NuGetVersion)
+ 0.0.9.9
+ $(GitVersion_Major).$(GitVersion_Minor).0.0
+ $(GitVersion_AssemblySemVer)
+ $(GitVersion_InformationalVersion)
+ true
+ enable
+
+
+
+
+ <_Parameter1>Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
+
+
+ <_Parameter1>JsonRpc.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
+
+
+ <_Parameter1>Dap.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
+
+
+ <_Parameter1>Lsp.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
+
+
+ <_Parameter1>Lsp.Integration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
+
+
+ <_Parameter1>TestingUtils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f
+
+
+ <_Parameter1>DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7
+
+
+
diff --git a/src/JsonRpc.Generators/Helpers.cs b/src/JsonRpc.Generators/Helpers.cs
index ebed8a799..f7559140d 100644
--- a/src/JsonRpc.Generators/Helpers.cs
+++ b/src/JsonRpc.Generators/Helpers.cs
@@ -802,8 +802,40 @@ public static ArrowExpressionClauseSyntax GetRequestInvokeExpression() =>
)
);
- public static ArrowExpressionClauseSyntax GetPartialInvokeExpression(TypeSyntax responseType) =>
- ArrowExpressionClause(
+ public static ArrowExpressionClauseSyntax GetPartialInvokeExpression(TypeSyntax responseType, TypeSyntax? partialItemType)
+ {
+ var realResponseType = responseType is NullableTypeSyntax nts ? nts.ElementType : responseType;
+ var factoryArgument = Argument(
+ SimpleLambdaExpression(
+ Parameter(Identifier("value")),
+ ObjectCreationExpression(realResponseType)
+ .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("value")))))
+ )
+ );
+ var arguments = new[] {
+ Argument(
+ IdentifierName(@"request")
+ ),
+ factoryArgument,
+ Argument(IdentifierName("cancellationToken"))
+ };
+ if (partialItemType is {})
+ {
+ var realPartialItemType = partialItemType is NullableTypeSyntax nts2 ? nts2.ElementType : partialItemType;
+ arguments = new[] {
+ arguments[0],
+ arguments[1],
+ Argument(
+ SimpleLambdaExpression(
+ Parameter(Identifier("value")),
+ ObjectCreationExpression(realPartialItemType)
+ .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("value")))))
+ )
+ ),
+ arguments[2]
+ };
+ }
+ return ArrowExpressionClause(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
@@ -815,26 +847,9 @@ public static ArrowExpressionClauseSyntax GetPartialInvokeExpression(TypeSyntax
IdentifierName("MonitorUntil")
)
)
- .WithArgumentList(
- ArgumentList(
- SeparatedList(
- new[] {
- Argument(
- IdentifierName(@"request")
- ),
- Argument(
- SimpleLambdaExpression(
- Parameter(Identifier("value")),
- ObjectCreationExpression(responseType is NullableTypeSyntax nts ? nts.ElementType : responseType)
- .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("value")))))
- )
- ),
- Argument(IdentifierName("cancellationToken"))
- }
- )
- )
- )
+ .WithArgumentList(ArgumentList(SeparatedList(arguments)))
);
+ }
public static string GetExtensionClassName(INamedTypeSymbol symbol) => SpecialCasedHandlerFullName(symbol).Split('.').Last() + "Extensions";
diff --git a/src/JsonRpc.Generators/Strategies/SendMethodRequestStrategy.cs b/src/JsonRpc.Generators/Strategies/SendMethodRequestStrategy.cs
index 47eb046d8..fdcf99139 100644
--- a/src/JsonRpc.Generators/Strategies/SendMethodRequestStrategy.cs
+++ b/src/JsonRpc.Generators/Strategies/SendMethodRequestStrategy.cs
@@ -59,7 +59,7 @@ public IEnumerable Apply(ExtensionMethodContext extensi
)
)
.WithParameterList(parameterList)
- .WithExpressionBody(Helpers.GetPartialInvokeExpression(request.Response.Syntax))
+ .WithExpressionBody(Helpers.GetPartialInvokeExpression(request.Response.Syntax, request.PartialItem.Syntax))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
yield break;
}
@@ -91,7 +91,7 @@ public IEnumerable Apply(ExtensionMethodContext extensi
)
)
.WithParameterList(parameterList)
- .WithExpressionBody(Helpers.GetPartialInvokeExpression(request.Response.Syntax))
+ .WithExpressionBody(Helpers.GetPartialInvokeExpression(request.Response.Syntax, default))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
yield break;
}
diff --git a/src/JsonRpc.Testing/JsonRpcTestBase.cs b/src/JsonRpc.Testing/JsonRpcTestBase.cs
index 4b0accc07..991ca6165 100644
--- a/src/JsonRpc.Testing/JsonRpcTestBase.cs
+++ b/src/JsonRpc.Testing/JsonRpcTestBase.cs
@@ -1,7 +1,9 @@
using System;
using System.Diagnostics;
using System.Reactive;
+using System.Reactive.Concurrency;
using System.Reactive.Disposables;
+using System.Reactive.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
diff --git a/src/JsonRpc.Testing/JsonRpcTestOptions.cs b/src/JsonRpc.Testing/JsonRpcTestOptions.cs
index 13df29487..1b998acab 100644
--- a/src/JsonRpc.Testing/JsonRpcTestOptions.cs
+++ b/src/JsonRpc.Testing/JsonRpcTestOptions.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq;
+using System.Reactive.Concurrency;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@@ -24,6 +25,8 @@ public JsonRpcTestOptions(ILoggerFactory clientLoggerFactory, ILoggerFactory ser
public ILoggerFactory ClientLoggerFactory { get; internal set; } = NullLoggerFactory.Instance;
public ILoggerFactory ServerLoggerFactory { get; internal set; } = NullLoggerFactory.Instance;
+ public IScheduler ClientScheduler { get; internal set; } = TaskPoolScheduler.Default;
+ public IScheduler ServerScheduler { get; internal set; } = TaskPoolScheduler.Default;
public TimeSpan WaitTime { get; internal set; } = TimeSpan.FromMilliseconds(50);
public TimeSpan Timeout { get; internal set; } = TimeSpan.FromMilliseconds(500);
public TimeSpan CancellationTimeout { get; internal set; } = TimeSpan.FromSeconds(50);
diff --git a/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs b/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs
index 1fa284b70..b367ff631 100644
--- a/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs
+++ b/src/JsonRpc.Testing/JsonRpcTestOptionsExtensions.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq;
+using System.Reactive.Concurrency;
using System.Reflection;
using Microsoft.Extensions.Logging;
@@ -21,6 +22,30 @@ public static JsonRpcTestOptions WithClientLoggerFactory(this JsonRpcTestOptions
return options;
}
+ public static JsonRpcTestOptions WithLoggerFactory(this JsonRpcTestOptions options, ILoggerFactory loggerFactory)
+ {
+ options.ClientLoggerFactory = options.ServerLoggerFactory = loggerFactory;
+ return options;
+ }
+
+ public static JsonRpcTestOptions WithServerScheduler(this JsonRpcTestOptions options, IScheduler scheduler)
+ {
+ options.ServerScheduler = scheduler;
+ return options;
+ }
+
+ public static JsonRpcTestOptions WithClientScheduler(this JsonRpcTestOptions options, IScheduler scheduler)
+ {
+ options.ClientScheduler = scheduler;
+ return options;
+ }
+
+ public static JsonRpcTestOptions WithScheduler(this JsonRpcTestOptions options, IScheduler scheduler)
+ {
+ options.ClientScheduler = options.ServerScheduler = scheduler;
+ return options;
+ }
+
public static JsonRpcTestOptions WithWaitTime(this JsonRpcTestOptions options, TimeSpan waitTime)
{
options.WaitTime = waitTime;
diff --git a/src/JsonRpc.Testing/Settler.cs b/src/JsonRpc.Testing/Settler.cs
index 2fc758d70..f25ee60e3 100644
--- a/src/JsonRpc.Testing/Settler.cs
+++ b/src/JsonRpc.Testing/Settler.cs
@@ -60,7 +60,7 @@ public Settler(JsonRpcTestOptions options, CancellationToken cancellationToken,
_requester = subject.AsObserver();
}
- public Task SettleNext() => SettleNextInternal().ToTask(_cancellationToken);
+ public Task SettleNext() => SettleNextInternal().ToTask(_cancellationToken, _scheduler);
public IObservable SettleNextInternal() => _settle
.Catch(_ => _timeoutValue)
diff --git a/src/JsonRpc/Client/OutgoingNotification.cs b/src/JsonRpc/Client/OutgoingNotification.cs
index 750249a02..9f5696be9 100644
--- a/src/JsonRpc/Client/OutgoingNotification.cs
+++ b/src/JsonRpc/Client/OutgoingNotification.cs
@@ -1,9 +1,23 @@
+using Newtonsoft.Json;
+
namespace OmniSharp.Extensions.JsonRpc.Client
{
- public class OutgoingNotification
+ public record OutgoingNotification : ITraceData
{
public string Method { get; set; } = null!;
public object? Params { get; set; }
+
+ ///
+ /// Gets or sets the data for the W3C Trace Context traceparent value.
+ ///
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string? TraceParent { get; set; }
+
+ ///
+ /// Gets or sets the data for the W3C Trace Context tracestate value.
+ ///
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string? TraceState { get; set; }
}
}
diff --git a/src/JsonRpc/Client/OutgoingRequest.cs b/src/JsonRpc/Client/OutgoingRequest.cs
index 6252de1aa..e639e8435 100644
--- a/src/JsonRpc/Client/OutgoingRequest.cs
+++ b/src/JsonRpc/Client/OutgoingRequest.cs
@@ -2,7 +2,7 @@
namespace OmniSharp.Extensions.JsonRpc.Client
{
- public class OutgoingRequest
+ public record OutgoingRequest : ITraceData
{
public object? Id { get; set; }
@@ -10,5 +10,17 @@ public class OutgoingRequest
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public object? Params { get; set; }
+
+ ///
+ /// Gets or sets the data for the W3C Trace Context traceparent value.
+ ///
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string? TraceParent { get; set; }
+
+ ///
+ /// Gets or sets the data for the W3C Trace Context tracestate value.
+ ///
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string? TraceState { get; set; }
}
}
diff --git a/src/JsonRpc/Client/OutgoingResponse.cs b/src/JsonRpc/Client/OutgoingResponse.cs
index 0b4dfa3fe..582e53c58 100644
--- a/src/JsonRpc/Client/OutgoingResponse.cs
+++ b/src/JsonRpc/Client/OutgoingResponse.cs
@@ -2,7 +2,7 @@
namespace OmniSharp.Extensions.JsonRpc.Client
{
- public class OutgoingResponse
+ public record OutgoingResponse
{
public OutgoingResponse(object id, ServerRequest request)
{
diff --git a/src/JsonRpc/IActivityTracingStrategy.cs b/src/JsonRpc/IActivityTracingStrategy.cs
new file mode 100644
index 000000000..6a8837a81
--- /dev/null
+++ b/src/JsonRpc/IActivityTracingStrategy.cs
@@ -0,0 +1,353 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft;
+using OmniSharp.Extensions.JsonRpc.Server;
+
+namespace OmniSharp.Extensions.JsonRpc
+{
+ ///
+ /// Based on https://github.com/microsoft/vs-streamjsonrpc IActivityTracingStrategy
+ ///
+ public interface IActivityTracingStrategy
+ {
+ void ApplyOutgoing(ITraceData data);
+ IDisposable? ApplyInbound(ITraceData data);
+ }
+ internal static class Hex
+ {
+ private static readonly byte[] HexBytes = new byte[] { (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' };
+ private static readonly byte[] ReverseHexDigits = BuildReverseHexDigits();
+
+ internal static void Encode(ReadOnlySpan src, ref Span dest)
+ {
+ Span bytes = MemoryMarshal.Cast(dest);
+
+ // Inspired by http://stackoverflow.com/questions/623104/c-byte-to-hex-string/3974535#3974535
+ int lengthInNibbles = src.Length * 2;
+
+ for (int i = 0; i < (lengthInNibbles & -2); i++)
+ {
+ int index0 = +i >> 1;
+ var b = (byte)(src[index0] >> 4);
+ bytes[(2 * i) + 1] = 0;
+ bytes[2 * i++] = HexBytes[b];
+
+ b = (byte)(src[index0] & 0x0F);
+ bytes[(2 * i) + 1] = 0;
+ bytes[2 * i] = HexBytes[b];
+ }
+
+ dest = dest.Slice(lengthInNibbles);
+ }
+
+ internal static void Decode(ReadOnlySpan value, Span bytes)
+ {
+ for (int i = 0; i < value.Length; i++)
+ {
+ int c1 = ReverseHexDigits[value[i++] - '0'] << 4;
+ int c2 = ReverseHexDigits[value[i] - '0'];
+
+ bytes[i >> 1] = (byte)(c1 + c2);
+ }
+ }
+
+ private static byte[] BuildReverseHexDigits()
+ {
+ var bytes = new byte['f' - '0' + 1];
+
+ for (int i = 0; i < 10; i++)
+ {
+ bytes[i] = (byte)i;
+ }
+
+ for (int i = 10; i < 16; i++)
+ {
+ bytes[i + 'a' - '0' - 0x0a] = (byte)i;
+ bytes[i + 'A' - '0' - 0x0a] = (byte)i;
+ }
+
+ return bytes;
+ }
+ }
+
+ internal unsafe struct TraceParent
+ {
+ internal const int VersionByteCount = 1;
+ internal const int ParentIdByteCount = 8;
+ internal const int TraceIdByteCount = 16;
+ internal const int FlagsByteCount = 1;
+
+ internal byte Version;
+
+ internal fixed byte TraceId[TraceIdByteCount];
+
+ internal fixed byte ParentId[ParentIdByteCount];
+
+ internal TraceFlags Flags;
+
+ internal TraceParent(string? traceparent)
+ {
+ if (traceparent is null)
+ {
+ this.Version = 0;
+ this.Flags = TraceFlags.None;
+ return;
+ }
+
+ ReadOnlySpan traceparentChars = traceparent.AsSpan();
+
+ // Decode version
+ ReadOnlySpan slice = Consume(ref traceparentChars, VersionByteCount * 2);
+ fixed (byte* pVersion = &this.Version)
+ {
+ Hex.Decode(slice, new Span(pVersion, 1));
+ }
+
+ ConsumeHyphen(ref traceparentChars);
+
+ // Decode traceid
+ slice = Consume(ref traceparentChars, TraceIdByteCount * 2);
+ fixed (byte* pTraceId = this.TraceId)
+ {
+ Hex.Decode(slice, new Span(pTraceId, TraceIdByteCount));
+ }
+
+ ConsumeHyphen(ref traceparentChars);
+
+ // Decode parentid
+ slice = Consume(ref traceparentChars, ParentIdByteCount * 2);
+ fixed (byte* pParentId = this.ParentId)
+ {
+ Hex.Decode(slice, new Span(pParentId, ParentIdByteCount));
+ }
+
+ ConsumeHyphen(ref traceparentChars);
+
+ // Decode flags
+ slice = Consume(ref traceparentChars, FlagsByteCount * 2);
+ fixed (TraceFlags* pFlags = &this.Flags)
+ {
+ Hex.Decode(slice, new Span(pFlags, 1));
+ }
+
+ static void ConsumeHyphen(ref ReadOnlySpan value)
+ {
+ if (value[0] != '-')
+ {
+ Requires.Fail("Invalid format.");
+ }
+
+ value = value.Slice(1);
+ }
+
+ ReadOnlySpan Consume(ref ReadOnlySpan buffer, int length)
+ {
+ ReadOnlySpan result = buffer.Slice(0, length);
+ buffer = buffer.Slice(length);
+ return result;
+ }
+ }
+
+ [Flags]
+ internal enum TraceFlags : byte
+ {
+ ///
+ /// No flags.
+ ///
+ None = 0x0,
+
+ ///
+ /// The parent is tracing their action.
+ ///
+ Sampled = 0x1,
+ }
+
+ internal Guid TraceIdGuid
+ {
+ get
+ {
+ fixed (byte* pTraceId = this.TraceId)
+ {
+ return CopyBufferToGuid(new ReadOnlySpan(pTraceId, TraceIdByteCount));
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ // When calculating the number of characters required, double each 'byte' we have to encode since we're using hex.
+ Span traceparent = stackalloc char[(VersionByteCount * 2) + 1 + (TraceIdByteCount * 2) + 1 + (ParentIdByteCount * 2) + 1 + (FlagsByteCount * 2)];
+ Span traceParentRemaining = traceparent;
+
+ fixed (byte* pVersion = &this.Version)
+ {
+ Hex.Encode(new ReadOnlySpan(pVersion, 1), ref traceParentRemaining);
+ }
+
+ AddHyphen(ref traceParentRemaining);
+
+ fixed (byte* pTraceId = this.TraceId)
+ {
+ Hex.Encode(new ReadOnlySpan(pTraceId, TraceIdByteCount), ref traceParentRemaining);
+ }
+
+ AddHyphen(ref traceParentRemaining);
+
+ fixed (byte* pParentId = this.ParentId)
+ {
+ Hex.Encode(new ReadOnlySpan(pParentId, ParentIdByteCount), ref traceParentRemaining);
+ }
+
+ AddHyphen(ref traceParentRemaining);
+
+ fixed (TraceFlags* pFlags = &this.Flags)
+ {
+ Hex.Encode(new ReadOnlySpan(pFlags, 1), ref traceParentRemaining);
+ }
+
+ Debug.Assert(traceParentRemaining.Length == 0, "Characters were not initialized.");
+
+ fixed (char* pValue = traceparent)
+ {
+ return new string(pValue, 0, traceparent.Length);
+ }
+
+ static void AddHyphen(ref Span value)
+ {
+ value[0] = '-';
+ value = value.Slice(1);
+ }
+ }
+
+ private static unsafe Guid CopyBufferToGuid(ReadOnlySpan buffer)
+ {
+ Debug.Assert(buffer.Length == 16, "Guid buffer length mismatch.");
+ fixed (byte* pBuffer = buffer)
+ {
+ return *(Guid*)pBuffer;
+ }
+ }
+ }
+
+ public sealed class CorrelationManagerTracingStrategy : IActivityTracingStrategy
+ {
+ private static readonly AsyncLocal TraceStateAsyncLocal = new AsyncLocal();
+
+ ///
+ /// Gets or sets the contextual tracestate value.
+ ///
+ public static string? TraceState
+ {
+ get => TraceStateAsyncLocal.Value;
+ set => TraceStateAsyncLocal.Value = value;
+ }
+
+ ///
+ /// Gets or sets the that will receive the activity transfer, start and stop events .
+ ///
+ public TraceSource? TraceSource { get; set; }
+
+ public unsafe void ApplyOutgoing(ITraceData data)
+ {
+ if (Trace.CorrelationManager.ActivityId != Guid.Empty)
+ {
+ var traceparent = default(TraceParent);
+
+ FillRandomBytes(new Span(traceparent.ParentId, TraceParent.ParentIdByteCount));
+ CopyGuidToBuffer(Trace.CorrelationManager.ActivityId, new Span(traceparent.TraceId, TraceParent.TraceIdByteCount));
+
+ if (this.TraceSource is object && (this.TraceSource.Switch.Level & SourceLevels.ActivityTracing) == SourceLevels.ActivityTracing && this.TraceSource.Listeners.Count > 0)
+ {
+ traceparent.Flags |= TraceParent.TraceFlags.Sampled;
+ }
+
+ data.TraceParent = traceparent.ToString();
+ data.TraceState = TraceState;
+ }
+ }
+
+ ///
+ public unsafe IDisposable? ApplyInbound(ITraceData request)
+ {
+ var traceparent = new TraceParent(request.TraceParent);
+ Guid childActivityId = Guid.NewGuid();
+ string? activityName = request is IMethodWithParams p ? p.Method : null;
+
+ return new ActivityState(request, this.TraceSource, activityName, traceparent.TraceIdGuid, childActivityId);
+ }
+
+ private static void FillRandomBytes(Span buffer) => CopyGuidToBuffer(Guid.NewGuid(), buffer);
+
+ private unsafe static void CopyGuidToBuffer(Guid guid, Span buffer)
+ {
+ ReadOnlySpan guidBytes = new ReadOnlySpan(&guid, sizeof(Guid));
+ guidBytes.Slice(0, buffer.Length).CopyTo(buffer);
+ }
+
+ private class ActivityState : IDisposable
+ {
+ private readonly TraceSource? traceSource;
+ private readonly Guid originalActivityId;
+ private readonly string? originalTraceState;
+ private readonly string? activityName;
+ private readonly Guid parentTraceId;
+
+ internal ActivityState(ITraceData request, TraceSource? traceSource, string? activityName, Guid parentTraceId, Guid childTraceId)
+ {
+ this.originalActivityId = Trace.CorrelationManager.ActivityId;
+ this.originalTraceState = TraceState;
+ this.activityName = activityName;
+ this.parentTraceId = parentTraceId;
+
+ if (traceSource is object && parentTraceId != Guid.Empty)
+ {
+ // We set ActivityId to a short-lived value here for the sake of the TraceTransfer call that comes next.
+ // TraceTransfer goes from the current activity to the one passed as an argument.
+ // Without a traceSource object, there's no transfer and thus no need to set this temporary ActivityId.
+ Trace.CorrelationManager.ActivityId = parentTraceId;
+ traceSource.TraceTransfer(0, nameof(TraceEventType.Transfer), childTraceId);
+ }
+
+ Trace.CorrelationManager.ActivityId = childTraceId;
+ TraceState = request.TraceState;
+
+ traceSource?.TraceEvent(TraceEventType.Start, 0, this.activityName);
+
+ this.traceSource = traceSource;
+ }
+
+ public void Dispose()
+ {
+ this.traceSource?.TraceEvent(TraceEventType.Stop, 0, this.activityName);
+
+ if (this.parentTraceId != Guid.Empty)
+ {
+ this.traceSource?.TraceTransfer(0, nameof(TraceEventType.Transfer), this.parentTraceId);
+ }
+
+ Trace.CorrelationManager.ActivityId = this.originalActivityId;
+ TraceState = this.originalTraceState;
+ }
+ }
+ }
+
+ public interface ITraceData
+ {
+
+
+ ///
+ /// Gets or sets the data for the W3C Trace Context traceparent value.
+ ///
+ string? TraceParent { get; set; }
+
+ ///
+ /// Gets or sets the data for the W3C Trace Context tracestate value.
+ ///
+ string? TraceState { get; set; }
+ }
+}
diff --git a/src/JsonRpc/JsonRpc.csproj b/src/JsonRpc/JsonRpc.csproj
index 81d2c2328..f62bc92b6 100644
--- a/src/JsonRpc/JsonRpc.csproj
+++ b/src/JsonRpc/JsonRpc.csproj
@@ -5,18 +5,20 @@
OmniSharp.Extensions.JsonRpc
OmniSharp.Extensions.JsonRpc
Primitives for working with JsonRpc. This library is used as the base for communication with language servers
+ true
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/JsonRpc/JsonRpcServerOptionsBase.cs b/src/JsonRpc/JsonRpcServerOptionsBase.cs
index 6a513ba3e..22847aec2 100644
--- a/src/JsonRpc/JsonRpcServerOptionsBase.cs
+++ b/src/JsonRpc/JsonRpcServerOptionsBase.cs
@@ -37,7 +37,8 @@ public ILoggerFactory LoggerFactory
public IRequestProcessIdentifier? RequestProcessIdentifier { get; set; }
public int? Concurrency { get; set; }
public IScheduler InputScheduler { get; set; } = TaskPoolScheduler.Default;
- public IScheduler OutputScheduler { get; set; } = TaskPoolScheduler.Default;
+ public IScheduler OutputScheduler { get; set; } = Scheduler.Immediate;
+ public IScheduler DefaultScheduler { get; set; } = TaskPoolScheduler.Default;
public CreateResponseExceptionHandler? CreateResponseException { get; set; }
public OnUnhandledExceptionHandler? OnUnhandledException { get; set; }
public bool SupportsContentModified { get; set; } = true;
@@ -143,5 +144,12 @@ public T WithLink(string fromMethod, string toMethod)
Handlers.Add(JsonRpcHandlerDescription.Link(fromMethod, toMethod));
return (T) (object) this;
}
+
+ public T WithActivityTracingStrategy(IActivityTracingStrategy activityTracingStrategy)
+ {
+ Services.RemoveAll(typeof(IActivityTracingStrategy));
+ Services.AddSingleton(activityTracingStrategy);
+ return (T) (object) this;
+ }
}
}
diff --git a/src/JsonRpc/JsonRpcServerOptionsExtensions.cs b/src/JsonRpc/JsonRpcServerOptionsExtensions.cs
index b2a821b75..6c341cc7a 100644
--- a/src/JsonRpc/JsonRpcServerOptionsExtensions.cs
+++ b/src/JsonRpc/JsonRpcServerOptionsExtensions.cs
@@ -24,7 +24,7 @@ public static JsonRpcServerOptions WithAssemblyAttributeScanning(this JsonRpcSer
///
public static JsonRpcServerOptions WithScheduler(this JsonRpcServerOptions options, IScheduler inputScheduler)
{
- options.InputScheduler = options.OutputScheduler = inputScheduler;
+ options.InputScheduler = options.OutputScheduler = options.DefaultScheduler = inputScheduler;
return options;
}
@@ -40,6 +40,18 @@ public static JsonRpcServerOptions WithInputScheduler(this JsonRpcServerOptions
return options;
}
+ ///
+ /// Sets the default scheduler to be used when scheduling other tasks
+ ///
+ ///
+ ///
+ ///
+ public static JsonRpcServerOptions WithDefaultScheduler(this JsonRpcServerOptions options, IScheduler defaultScheduler)
+ {
+ options.DefaultScheduler = defaultScheduler;
+ return options;
+ }
+
///
/// Sets the scheduler use during writing output
///
diff --git a/src/JsonRpc/JsonRpcServerServiceCollectionExtensions.cs b/src/JsonRpc/JsonRpcServerServiceCollectionExtensions.cs
index 35538dfab..1859fd078 100644
--- a/src/JsonRpc/JsonRpcServerServiceCollectionExtensions.cs
+++ b/src/JsonRpc/JsonRpcServerServiceCollectionExtensions.cs
@@ -58,6 +58,8 @@ internal static IContainer AddJsonRpcServerCore(this IContainer container, Js
reuse: Reuse.Singleton
);
+ container.RegisterInstance(options.DefaultScheduler);
+
container.RegisterMany(
serviceTypeCondition: type => type.IsInterface,
reuse: Reuse.Singleton
diff --git a/src/JsonRpc/OutputHandler.cs b/src/JsonRpc/OutputHandler.cs
index 2b167bdc9..f20d06d3b 100644
--- a/src/JsonRpc/OutputHandler.cs
+++ b/src/JsonRpc/OutputHandler.cs
@@ -3,12 +3,14 @@
using System.ComponentModel;
using System.IO.Pipelines;
using System.Linq;
+using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
+using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -20,40 +22,52 @@ public class OutputHandler : IOutputHandler
private readonly ISerializer _serializer;
private readonly IEnumerable _outputFilters;
private readonly ILogger _logger;
- private readonly Subject