From 98e3bdc2034d7a1ef7710781d2448f015018b71c Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 2 Dec 2019 07:01:03 -0700 Subject: [PATCH 1/8] Avoid using `AsSpan` on mono On mono, the `MethodInfo.GetParametres()` method returns a `RuntimeParameterInfo[]` through its `ParameterInfo[]` return type. This works in general due to .NET array covariance. But apparently on mono it breaks when you try to get a slice of the array as a `Span`. We avoid using slice and instead make a copy of the array as a cheap workaround. Fixes #387 --- src/StreamJsonRpc/Reflection/TargetMethod.cs | 6 ++++-- src/StreamJsonRpc/Utilities.cs | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/StreamJsonRpc/Reflection/TargetMethod.cs b/src/StreamJsonRpc/Reflection/TargetMethod.cs index 18469aee..cbb88ca0 100644 --- a/src/StreamJsonRpc/Reflection/TargetMethod.cs +++ b/src/StreamJsonRpc/Reflection/TargetMethod.cs @@ -126,7 +126,9 @@ private bool TryGetArguments(JsonRpcRequest request, MethodSignature method, Spa } // When there is a CancellationToken parameter, we require that it always be the last parameter. - Span methodParametersExcludingCancellationToken = method.Parameters.AsSpan(0, method.TotalParamCountExcludingCancellationToken); + Span methodParametersExcludingCancellationToken = Utilities.IsRunningOnMono + ? method.Parameters.Take(method.TotalParamCountExcludingCancellationToken).ToArray() + : method.Parameters.AsSpan(0, method.TotalParamCountExcludingCancellationToken); Span argumentsExcludingCancellationToken = arguments.Slice(0, method.TotalParamCountExcludingCancellationToken); if (method.HasCancellationTokenParameter) { @@ -158,4 +160,4 @@ private bool TryGetArguments(JsonRpcRequest request, MethodSignature method, Spa } } } -} \ No newline at end of file +} diff --git a/src/StreamJsonRpc/Utilities.cs b/src/StreamJsonRpc/Utilities.cs index 168a49cd..108e1ad1 100644 --- a/src/StreamJsonRpc/Utilities.cs +++ b/src/StreamJsonRpc/Utilities.cs @@ -9,6 +9,11 @@ namespace StreamJsonRpc internal static class Utilities { + /// + /// Gets a value indicating whether the mono runtime is executing this code. + /// + internal static bool IsRunningOnMono => Type.GetType("Mono.Runtime") != null; + /// /// Reads an value from a buffer using big endian. /// From df26a8cf0783ddd5ae7af3df3822bfd0f2d92c76 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 2 Dec 2019 07:59:23 -0700 Subject: [PATCH 2/8] Run tests on mono in Azure Pipelines --- azure-pipelines/dotnet.yml | 4 ++++ src/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj | 1 + 2 files changed, 5 insertions(+) diff --git a/azure-pipelines/dotnet.yml b/azure-pipelines/dotnet.yml index 3ed05413..624a7336 100644 --- a/azure-pipelines/dotnet.yml +++ b/azure-pipelines/dotnet.yml @@ -43,6 +43,10 @@ steps: testRunTitle: netcoreapp2.2-$(Agent.JobName) workingDirectory: src +- powershell: mono $(NUGET_PACKAGES)/xunit.runner.console/2.4.1/tools/net472/xunit.console.exe bin/StreamJsonRpc.Tests/$(BuildConfiguration)/net472/StreamJsonRpc.Tests.dll -vsts -notrait "TestCategory=FailsInCloudTest" -noclass PerfTests + displayName: Run tests on mono + condition: ne(variables['Agent.OS'], 'Windows_NT') # The Windows Hosted agent doesn't include mono + # We have to artifically run this script so that the extra .nupkg is produced for variables/InsertConfigValues.ps1 to notice. - powershell: azure-pipelines\artifacts\VSInsertion.ps1 displayName: Prepare VSInsertion artifact diff --git a/src/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj b/src/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj index 5e754032..68aa76e9 100644 --- a/src/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj +++ b/src/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj @@ -24,6 +24,7 @@ + From ef7ee7bd1721da1d533010b4136d17368747aff6 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 2 Dec 2019 13:45:04 -0700 Subject: [PATCH 3/8] Update package description --- src/StreamJsonRpc/StreamJsonRpc.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StreamJsonRpc/StreamJsonRpc.csproj b/src/StreamJsonRpc/StreamJsonRpc.csproj index 00323377..71af923f 100644 --- a/src/StreamJsonRpc/StreamJsonRpc.csproj +++ b/src/StreamJsonRpc/StreamJsonRpc.csproj @@ -6,7 +6,7 @@ 4 true - A cross-platform .NET portable library that implements the JSON-RPC wire protocol and can use System.IO.Stream or WebSocket so you can use it with any transport. + A cross-platform .NETStandard library that implements the JSON-RPC wire protocol and can use System.IO.Stream, System.IO.Pipelines or WebSocket so you can use it with any transport. visualstudio stream json rpc jsonrpc From aaff2774f1943bf0ca9735b4614728a2a80db97d Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 2 Dec 2019 13:55:39 -0700 Subject: [PATCH 4/8] Run OptProf on v2.2 --- azure-pipelines/OptProf.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines/OptProf.yml b/azure-pipelines/OptProf.yml index c98ca17c..03a94919 100644 --- a/azure-pipelines/OptProf.yml +++ b/azure-pipelines/OptProf.yml @@ -5,7 +5,7 @@ schedules: displayName: Weekly OptProf run branches: include: - - v2.1 + - v2.2 - master always: true # we must keep data fresh since optimizationdata drops are purged after 30 days From 1512b566113e8b563d13a6e4548f9ce99bb3454d Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 2 Dec 2019 14:03:50 -0700 Subject: [PATCH 5/8] Fix build break on mac/linux for optprof runs --- azure-pipelines/OptProf.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure-pipelines/OptProf.yml b/azure-pipelines/OptProf.yml index 03a94919..373af7af 100644 --- a/azure-pipelines/OptProf.yml +++ b/azure-pipelines/OptProf.yml @@ -25,6 +25,8 @@ variables: value: Release - name: BuildPlatform value: Any CPU +- name: NUGET_PACKAGES + value: $(Agent.TempDirectory)/.nuget/packages - name: push_to_ci value: true - name: PublicRelease From 0e9695931d121a1cfe83a321d5149bfc3ebfc22a Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 2 Dec 2019 21:10:07 -0700 Subject: [PATCH 6/8] Skip PerfTests using a more generic mechanism --- azure-pipelines/dotnet.yml | 2 +- src/StreamJsonRpc.Tests/PerfTests.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines/dotnet.yml b/azure-pipelines/dotnet.yml index 624a7336..f402ff75 100644 --- a/azure-pipelines/dotnet.yml +++ b/azure-pipelines/dotnet.yml @@ -43,7 +43,7 @@ steps: testRunTitle: netcoreapp2.2-$(Agent.JobName) workingDirectory: src -- powershell: mono $(NUGET_PACKAGES)/xunit.runner.console/2.4.1/tools/net472/xunit.console.exe bin/StreamJsonRpc.Tests/$(BuildConfiguration)/net472/StreamJsonRpc.Tests.dll -vsts -notrait "TestCategory=FailsInCloudTest" -noclass PerfTests +- powershell: mono $(NUGET_PACKAGES)/xunit.runner.console/2.4.1/tools/net472/xunit.console.exe bin/StreamJsonRpc.Tests/$(BuildConfiguration)/net472/StreamJsonRpc.Tests.dll -vsts -notrait "TestCategory=FailsInCloudTest" -notrait "FailsOnMono=true" displayName: Run tests on mono condition: ne(variables['Agent.OS'], 'Windows_NT') # The Windows Hosted agent doesn't include mono diff --git a/src/StreamJsonRpc.Tests/PerfTests.cs b/src/StreamJsonRpc.Tests/PerfTests.cs index 72fd5564..ff708f33 100644 --- a/src/StreamJsonRpc.Tests/PerfTests.cs +++ b/src/StreamJsonRpc.Tests/PerfTests.cs @@ -15,6 +15,7 @@ using Xunit.Abstractions; [Trait("Category", "SkipWhenLiveUnitTesting")] // very slow test +[Trait("FailsOnMono", "true")] public class PerfTests { private readonly ITestOutputHelper logger; From b9817125d221fc819433841ae49774fb1150c87e Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 2 Dec 2019 21:25:19 -0700 Subject: [PATCH 7/8] Adapt to mono's lack of support for IgnoresAccessChecksToAttribute --- src/StreamJsonRpc/ProxyGeneration.cs | 10 ++++---- .../Reflection/CodeGenHelpers.cs | 23 +++++++++++++++++++ .../netcoreapp2.1/PublicAPI.Unshipped.txt | 2 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 2 ++ 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 src/StreamJsonRpc/Reflection/CodeGenHelpers.cs diff --git a/src/StreamJsonRpc/ProxyGeneration.cs b/src/StreamJsonRpc/ProxyGeneration.cs index f063c335..b256eed7 100644 --- a/src/StreamJsonRpc/ProxyGeneration.cs +++ b/src/StreamJsonRpc/ProxyGeneration.cs @@ -19,6 +19,7 @@ namespace StreamJsonRpc using System.Threading.Tasks; using Microsoft; using Microsoft.VisualStudio.Threading; + using CodeGenHelpers = StreamJsonRpc.Reflection.CodeGenHelpers; internal static class ProxyGeneration { @@ -398,9 +399,10 @@ private static void AdaptReturnType(MethodInfo method, bool returnTypeIsValueTas il.Emit(OpCodes.Ldloc, local); } - Type proxyEnumerableType = typeof(AsyncEnumerableProxy<>).MakeGenericType(method.ReturnType.GenericTypeArguments[0]); - ConstructorInfo ctor = proxyEnumerableType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single(); - il.Emit(OpCodes.Newobj, ctor); +#pragma warning disable CS0618 + MethodInfo createProxyEnumerableMethod = typeof(CodeGenHelpers).GetMethod(nameof(CodeGenHelpers.CreateAsyncEnumerableProxy), BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(method.ReturnType.GenericTypeArguments[0]); +#pragma warning restore CS0618 + il.Emit(OpCodes.Call, createProxyEnumerableMethod); } } @@ -607,7 +609,7 @@ private static IEnumerable FindAllOnThisAndOtherInterfaces(TypeInfo interf /// A synthesized that makes a promise for such a value look like the actual value. /// /// The type of element produced by the enumerable. - private class AsyncEnumerableProxy : IAsyncEnumerable + internal class AsyncEnumerableProxy : IAsyncEnumerable { private readonly Task> enumerableTask; private readonly CancellationToken defaultCancellationToken; diff --git a/src/StreamJsonRpc/Reflection/CodeGenHelpers.cs b/src/StreamJsonRpc/Reflection/CodeGenHelpers.cs new file mode 100644 index 00000000..853f762c --- /dev/null +++ b/src/StreamJsonRpc/Reflection/CodeGenHelpers.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace StreamJsonRpc.Reflection +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Helper methods for dynamically generated proxies to invoke. + /// This type is only public because mono does not support IgnoresAccessChecksToAttribute. Do not call directly. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This class is only for invoking from dynamically generated code.")] + public static class CodeGenHelpers + { + /// + public static IAsyncEnumerable CreateAsyncEnumerableProxy(Task> enumerableTask, CancellationToken defaultCancellationToken) => new ProxyGeneration.AsyncEnumerableProxy(enumerableTask, defaultCancellationToken); + } +} diff --git a/src/StreamJsonRpc/netcoreapp2.1/PublicAPI.Unshipped.txt b/src/StreamJsonRpc/netcoreapp2.1/PublicAPI.Unshipped.txt index cb2e4bec..daac84fb 100644 --- a/src/StreamJsonRpc/netcoreapp2.1/PublicAPI.Unshipped.txt +++ b/src/StreamJsonRpc/netcoreapp2.1/PublicAPI.Unshipped.txt @@ -29,6 +29,7 @@ StreamJsonRpc.Protocol.JsonRpcRequest.RequestId.get -> StreamJsonRpc.RequestId StreamJsonRpc.Protocol.JsonRpcRequest.RequestId.set -> void StreamJsonRpc.Protocol.JsonRpcResult.RequestId.get -> StreamJsonRpc.RequestId StreamJsonRpc.Protocol.JsonRpcResult.RequestId.set -> void +StreamJsonRpc.Reflection.CodeGenHelpers StreamJsonRpc.Reflection.IJsonRpcFormatterCallbacks StreamJsonRpc.Reflection.IJsonRpcFormatterCallbacks.RequestTransmissionAborted -> System.EventHandler StreamJsonRpc.Reflection.IJsonRpcFormatterCallbacks.ResponseReceived -> System.EventHandler @@ -81,6 +82,7 @@ static StreamJsonRpc.JsonRpcExtensions.AsAsyncEnumerable(this System.Collecti static StreamJsonRpc.JsonRpcExtensions.AsAsyncEnumerable(this System.Collections.Generic.IEnumerable enumerable, System.Threading.CancellationToken cancellationToken) -> System.Collections.Generic.IAsyncEnumerable static StreamJsonRpc.JsonRpcExtensions.WithJsonRpcSettings(this System.Collections.Generic.IAsyncEnumerable enumerable, StreamJsonRpc.JsonRpcEnumerableSettings settings) -> System.Collections.Generic.IAsyncEnumerable static StreamJsonRpc.JsonRpcExtensions.WithPrefetchAsync(this System.Collections.Generic.IAsyncEnumerable enumerable, int count, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static StreamJsonRpc.Reflection.CodeGenHelpers.CreateAsyncEnumerableProxy(System.Threading.Tasks.Task> enumerableTask, System.Threading.CancellationToken defaultCancellationToken) -> System.Collections.Generic.IAsyncEnumerable static StreamJsonRpc.Reflection.MessageFormatterEnumerableTracker.CanDeserialize(System.Type objectType) -> bool static StreamJsonRpc.Reflection.MessageFormatterEnumerableTracker.CanSerialize(System.Type objectType) -> bool static StreamJsonRpc.RequestId.NotSpecified.get -> StreamJsonRpc.RequestId diff --git a/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt b/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt index cb2e4bec..daac84fb 100644 --- a/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt @@ -29,6 +29,7 @@ StreamJsonRpc.Protocol.JsonRpcRequest.RequestId.get -> StreamJsonRpc.RequestId StreamJsonRpc.Protocol.JsonRpcRequest.RequestId.set -> void StreamJsonRpc.Protocol.JsonRpcResult.RequestId.get -> StreamJsonRpc.RequestId StreamJsonRpc.Protocol.JsonRpcResult.RequestId.set -> void +StreamJsonRpc.Reflection.CodeGenHelpers StreamJsonRpc.Reflection.IJsonRpcFormatterCallbacks StreamJsonRpc.Reflection.IJsonRpcFormatterCallbacks.RequestTransmissionAborted -> System.EventHandler StreamJsonRpc.Reflection.IJsonRpcFormatterCallbacks.ResponseReceived -> System.EventHandler @@ -81,6 +82,7 @@ static StreamJsonRpc.JsonRpcExtensions.AsAsyncEnumerable(this System.Collecti static StreamJsonRpc.JsonRpcExtensions.AsAsyncEnumerable(this System.Collections.Generic.IEnumerable enumerable, System.Threading.CancellationToken cancellationToken) -> System.Collections.Generic.IAsyncEnumerable static StreamJsonRpc.JsonRpcExtensions.WithJsonRpcSettings(this System.Collections.Generic.IAsyncEnumerable enumerable, StreamJsonRpc.JsonRpcEnumerableSettings settings) -> System.Collections.Generic.IAsyncEnumerable static StreamJsonRpc.JsonRpcExtensions.WithPrefetchAsync(this System.Collections.Generic.IAsyncEnumerable enumerable, int count, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> +static StreamJsonRpc.Reflection.CodeGenHelpers.CreateAsyncEnumerableProxy(System.Threading.Tasks.Task> enumerableTask, System.Threading.CancellationToken defaultCancellationToken) -> System.Collections.Generic.IAsyncEnumerable static StreamJsonRpc.Reflection.MessageFormatterEnumerableTracker.CanDeserialize(System.Type objectType) -> bool static StreamJsonRpc.Reflection.MessageFormatterEnumerableTracker.CanSerialize(System.Type objectType) -> bool static StreamJsonRpc.RequestId.NotSpecified.get -> StreamJsonRpc.RequestId From d8eeacce107e4b493d1b1c66afa6e684a58e8251 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 2 Dec 2019 21:10:17 -0700 Subject: [PATCH 8/8] Skip tests that fail on mono --- src/StreamJsonRpc.Tests/AsyncEnumerableTests.cs | 3 +++ src/StreamJsonRpc.Tests/JsonRpcTests.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/StreamJsonRpc.Tests/AsyncEnumerableTests.cs b/src/StreamJsonRpc.Tests/AsyncEnumerableTests.cs index 7492c964..e33ebc1f 100644 --- a/src/StreamJsonRpc.Tests/AsyncEnumerableTests.cs +++ b/src/StreamJsonRpc.Tests/AsyncEnumerableTests.cs @@ -433,6 +433,7 @@ public async Task ArgumentEnumerable_ReleasedOnErrorResponse() [SkippableFact] [Trait("GC", "")] + [Trait("FailsOnMono", "true")] public async Task ArgumentEnumerable_ReleasedOnErrorInSubsequentArgumentSerialization() { WeakReference enumerable = await this.ArgumentEnumerable_ReleasedOnErrorInSubsequentArgumentSerialization_Helper(); @@ -441,6 +442,7 @@ public async Task ArgumentEnumerable_ReleasedOnErrorInSubsequentArgumentSerializ [SkippableFact] [Trait("GC", "")] + [Trait("FailsOnMono", "true")] public async Task ArgumentEnumerable_ReleasedWhenIgnoredBySuccessfulRpcCall() { WeakReference enumerable = await this.ArgumentEnumerable_ReleasedWhenIgnoredBySuccessfulRpcCall_Helper(); @@ -449,6 +451,7 @@ public async Task ArgumentEnumerable_ReleasedWhenIgnoredBySuccessfulRpcCall() [SkippableFact] [Trait("GC", "")] + [Trait("FailsOnMono", "true")] public async Task ArgumentEnumerable_ForciblyDisposedAndReleasedWhenNotDisposedWithinRpcCall() { WeakReference enumerable = await this.ArgumentEnumerable_ForciblyDisposedAndReleasedWhenNotDisposedWithinRpcCall_Helper(); diff --git a/src/StreamJsonRpc.Tests/JsonRpcTests.cs b/src/StreamJsonRpc.Tests/JsonRpcTests.cs index 11e396af..7c68cbf5 100644 --- a/src/StreamJsonRpc.Tests/JsonRpcTests.cs +++ b/src/StreamJsonRpc.Tests/JsonRpcTests.cs @@ -290,6 +290,7 @@ public async Task ThrowsIfTargetNotSet() [SkippableFact] [Trait("GC", "")] [Trait("TestCategory", "FailsInCloudTest")] // Test showing unstability on Azure Pipelines, but always succeeds locally. + [Trait("FailsOnMono", "true")] public async Task InvokeWithProgressParameter_NoMemoryLeakConfirm() { Skip.If(IsRunningUnderLiveUnitTest); @@ -299,6 +300,7 @@ public async Task InvokeWithProgressParameter_NoMemoryLeakConfirm() } [Fact] + [Trait("FailsOnMono", "true")] public async Task NotifyWithProgressParameter_NoMemoryLeakConfirm() { WeakReference weakRef = await this.NotifyAsyncWithProgressParameter_NoMemoryLeakConfirm_Helper();