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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/StreamJsonRpc/CommonMethodNameTransforms.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 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.Text.Json;

namespace StreamJsonRpc;

/// <summary>
Expand Down Expand Up @@ -45,7 +47,12 @@ public static Func<string, string> Prepend(string prefix)
{
return name => name;
}

return name => prefix + name;
else
{
// Using a local variable for the closure avoids C# from allocating the closure
// earlier in the method, which would impact even the fast path.
string localPrefix = prefix;
return name => localPrefix + name;
}
}
}
13 changes: 1 addition & 12 deletions src/StreamJsonRpc/HeaderDelimitedMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ unsafe int WriteHeaderText(string value, Span<byte> memory)
var mediaType = MediaTypeHeaderValue.Parse(contentTypeAsText);
if (mediaType.CharSet is not null)
{
// The common language server protocol accpets 'utf8' as a valid charset due to an early bug. To maintain backwards compatibility, 'utf8' will be
// The common language server protocol accepts 'utf8' as a valid charset due to an early bug. To maintain backwards compatibility, 'utf8' will be
// accepted here so StreamJsonRpc can be used to support remote language servers following common language protocol.
if (mediaType.CharSet.Equals("utf8", StringComparison.OrdinalIgnoreCase))
{
Expand All @@ -338,17 +338,6 @@ unsafe int WriteHeaderText(string value, Span<byte> memory)
}
}

private static bool IsLastFourBytesCrlfCrlf(byte[] buffer, int lastIndex)
{
const byte cr = (byte)'\r';
const byte lf = (byte)'\n';
return lastIndex >= 4
&& buffer[lastIndex - 4] == cr
&& buffer[lastIndex - 3] == lf
&& buffer[lastIndex - 2] == cr
&& buffer[lastIndex - 1] == lf;
}

private static int GetContentLength(ReadOnlySequence<byte> contentLengthValue)
{
// Ensure the length is reasonable so we don't blow the stack if we execute the stackalloc path.
Expand Down
2 changes: 1 addition & 1 deletion src/StreamJsonRpc/Hex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace StreamJsonRpc;

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[] HexBytes = "0123456789abcdef"u8.ToArray();
private static readonly byte[] ReverseHexDigits = BuildReverseHexDigits();

internal static void Encode(ReadOnlySpan<byte> src, ref Span<char> dest)
Expand Down
4 changes: 4 additions & 0 deletions src/StreamJsonRpc/JsonRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2601,10 +2601,14 @@ private async Task HandleRpcAsync(JsonRpcMessage rpc)

lock (this.dispatcherMapLock)
{
#if NET
this.resultDispatcherMap.Remove(resultOrError.RequestId, out data);
#else
if (this.resultDispatcherMap.TryGetValue(resultOrError.RequestId, out data))
{
this.resultDispatcherMap.Remove(resultOrError.RequestId);
}
#endif
}

if (this.TraceSource.Switch.ShouldTrace(TraceEventType.Information))
Expand Down
3 changes: 2 additions & 1 deletion src/StreamJsonRpc/LengthHeaderMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Buffers;
using System.Buffers.Binary;
using System.IO.Pipelines;
using Nerdbank.Streams;
using StreamJsonRpc.Protocol;
Expand Down Expand Up @@ -109,7 +110,7 @@ protected override void Write(JsonRpcMessage content, CancellationToken cancella
}

// Now go back and fill in the header with the actual content length.
Utilities.Write(this.Writer.GetSpan(sizeof(int)), checked((int)contentSequence.Length));
BinaryPrimitives.WriteInt32BigEndian(this.Writer.GetSpan(sizeof(int)), checked((int)contentSequence.Length));
Copy link
Member Author

@stephentoub stephentoub Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this another one that's going to cause you to load a library you didn't want to?

Hmm, probably not, since you're already using span.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking. Ya, we're using System.Memory heavily throughout the library.

this.Writer.Advance(sizeof(int));
contentSequence.CopyTo(this.Writer);

Expand Down
8 changes: 6 additions & 2 deletions src/StreamJsonRpc/MessagePackFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -402,13 +402,17 @@ private static void WriteProtocolVersionPropertyAndValue(ref MessagePackWriter w
}
}

private static void ReadUnknownProperty(ref MessagePackReader reader, ref Dictionary<string, ReadOnlySequence<byte>>? topLevelProperties, ReadOnlySpan<byte> stringKey)
private static unsafe void ReadUnknownProperty(ref MessagePackReader reader, ref Dictionary<string, ReadOnlySequence<byte>>? topLevelProperties, ReadOnlySpan<byte> stringKey)
{
topLevelProperties ??= new Dictionary<string, ReadOnlySequence<byte>>(StringComparer.Ordinal);
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
string name = Encoding.UTF8.GetString(stringKey);
#else
string name = Encoding.UTF8.GetString(stringKey.ToArray());
string name;
fixed (byte* stringKeyPtr = stringKey)
{
name = Encoding.UTF8.GetString(stringKeyPtr, stringKey.Length);
}
#endif
topLevelProperties.Add(name, GetSliceForNextToken(ref reader));
}
Expand Down
5 changes: 1 addition & 4 deletions src/StreamJsonRpc/Protocol/TraceParent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,7 @@ public override string ToString()

Debug.Assert(traceParentRemaining.Length == 0, "Characters were not initialized.");

fixed (char* pValue = traceparent)
{
return new string(pValue, 0, traceparent.Length);
}
return traceparent.ToString();

static void AddHyphen(ref Span<char> value)
{
Expand Down
4 changes: 2 additions & 2 deletions src/StreamJsonRpc/ProxyGeneration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,8 @@ internal static TypeInfo Get(Type contractInterface, ReadOnlySpan<Type> addition
string rpcMethodName = methodNameMap.GetRpcMethodName(method);
if (rpcInterfaceCode.HasValue)
{
methodName = rpcInterfaceCode + "." + method.Name;
rpcMethodName = rpcInterfaceCode + "." + rpcMethodName;
methodName = $"{rpcInterfaceCode.GetValueOrDefault()}.{method.Name}";
rpcMethodName = $"{rpcInterfaceCode.GetValueOrDefault()}.{rpcMethodName}";
}
Comment on lines 295 to 299

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

                    if (rpcInterfaceCode is { } localRpcInterfaceCode)
                    {
                        methodName = $"{localRpcInterfaceCode}.{method.Name}";
                        rpcMethodName = $"{localRpcInterfaceCode}.{rpcMethodName}";
                    }

/cc @stephentoub 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or why not just:

                    if (rpcInterfaceCode.HasValue)
                    {
                        methodName = $"{rpcInterfaceCode}.{method.Name}";
                        rpcMethodName = $"{rpcInterfaceCode}.{rpcMethodName}";
                    }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the latter, just because nullable value types are a bit less efficient in interpolated strings. Probably not a big deal, though.

For the former, seems like a stylistic preference.

Copy link

@paulomorgado paulomorgado Mar 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's exactly the same code, but more CSharpy. 😄

It just shows you were used to doing the right thing before C# had pattern matching. 😄


ParameterInfo[] methodParameters = method.GetParameters();
Expand Down
4 changes: 4 additions & 0 deletions src/StreamJsonRpc/StandardCancellationStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,14 @@ protected void CancelInboundRequest(RequestId id)
CancellationTokenSource? cts;
lock (this.inboundCancellationSources)
{
#if NET
this.inboundCancellationSources.Remove(id, out cts);
#else
if (this.inboundCancellationSources.TryGetValue(id, out cts))
{
this.inboundCancellationSources.Remove(id);
}
#endif
}

if (cts is object)
Expand Down
35 changes: 2 additions & 33 deletions src/StreamJsonRpc/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;

namespace StreamJsonRpc;
Expand All @@ -18,39 +19,7 @@ internal static int ReadInt32BE(ReadOnlySequence<byte> sequence)
sequence = sequence.Slice(0, 4);
Span<byte> stackSpan = stackalloc byte[4];
sequence.Slice(0, 4).CopyTo(stackSpan);
return ReadIntBE(stackSpan);
}

/// <summary>
/// Reads an <see cref="int"/> value to a buffer using big endian.
/// </summary>
/// <param name="buffer">The buffer to read from. Must be at most 4 bytes long.</param>
/// <returns>The read value.</returns>
internal static int ReadIntBE(ReadOnlySpan<byte> buffer)
{
Requires.Argument(buffer.Length <= 4, nameof(buffer), "Int32 length exceeded.");

int local = 0;
for (int offset = 0; offset < buffer.Length; offset++)
{
local <<= 8;
local |= buffer[offset];
}

return local;
}

/// <summary>
/// Writes an <see cref="int"/> value to a buffer using big endian.
/// </summary>
/// <param name="buffer">The buffer to write to. Must be at least 4 bytes long.</param>
/// <param name="value">The value to write.</param>
internal static void Write(Span<byte> buffer, int value)
{
buffer[0] = (byte)(value >> 24);
buffer[1] = (byte)(value >> 16);
buffer[2] = (byte)(value >> 8);
buffer[3] = (byte)value;
return BinaryPrimitives.ReadInt32BigEndian(stackSpan);
}

/// <summary>
Expand Down