From 288b633058beb82b9e0f6b81433068e5a7110ae0 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 5 Dec 2019 15:31:33 -0700 Subject: [PATCH] Fix verbose RPC message tracing We have JsonConverter and IMessagePackFormatter types that have side effects in order to support marshaling of special objects. This makes tracing the full JSON-RPC message hazardous if it means we run these custom serializers multiple times per message, and this is in fact happening lately. The fix is to be sure we only serialize/deserialize a message *once*. This means that the result of the serialization must be retained long enough to trace (if tracing is turned on). It also means we need to retain the text that was *de*serialized long enough to be traced. We also don't want to allocate a string for the entire message unless tracing requires it. The approach varies between formatters, as follows: For the `JsonMessageFormatter` we have a super-convenient `JToken` instance that represents the entire message either after serialization or before deserialization. This type knows how to render a string representation of itself. So for this formatter we simply raise the trace events with the `JToken` as a parameter, which the string formatter will call `ToString` on if a trace listener wants it as a string. For the `MessagePackFormatter` it's a little more complicated. There is no native JSON text representation at all (since msgpack is a binary encoding). But we still want to trace a JSON text representation for logging/diagnostics purposes. MessagePack has a converter that translates msgpack binary to JSON text, but this requires that we have the entire msgpack byte sequence. When deserializing this is easy enough: we have the sequence to deserialize from anyway. But when serializing, we're writing to an `IBufferWriter` which doesn't give us access to access the serialized binary later. So seeing the full msgpack that was serialized in order to convert to JSON and trace it is something the formatter needs help with. The message handlers have access to the full msgpack to be written, so we arrange for the handlers to "call back" into the formatter after the formatter is done serializing in order to say "by the way, here's the full sequence you just wrote out" which the formatter can then use to raise the trace event with an object that will convert it to JSON text if/when the object's ToString() method is called. This call back from the handler into the formatter is through an optional interface that only the `MessagePackFormatter` needs to implement. As part of this 'callback' from handler to formatter, the `LengthHeaderMessageHandler` needed a slightly revision: it didn't have a way to collect the entire serialized sequence written by the formatter because the `PrefixingBufferWriter` doesn't expose a `ReadOnlySequence` for all the written bytes like `Sequence` does. So I had to switch to `Sequence` in this handler. This means that small messages will have their buffer copied (even when not tracing) once before being transmitted where they weren't being copied before. But these are small messages so the impact is likely very small. Large messages were already getting copied anyway, so no difference there. So with this change we now have safe and complete tracing of JSON-RPC messages for all handlers and formatters, and without nasty doubling of side-effects. Fixes #386 --- .../HeaderDelimitedMessageHandler.cs | 12 +++- src/StreamJsonRpc/IJsonRpcMessageFormatter.cs | 3 + src/StreamJsonRpc/JsonMessageFormatter.cs | 29 +++++--- src/StreamJsonRpc/JsonRpc.cs | 70 ++++++++----------- .../LengthHeaderMessageHandler.cs | 37 ++++++---- src/StreamJsonRpc/MessagePackFormatter.cs | 34 ++++++++- .../IJsonRpcFormatterTracingCallbacks.cs | 21 ++++++ .../Reflection/IJsonRpcTracingCallbacks.cs | 27 +++++++ src/StreamJsonRpc/Utilities.cs | 16 +++++ src/StreamJsonRpc/WebSocketMessageHandler.cs | 14 ++-- .../netcoreapp2.1/PublicAPI.Unshipped.txt | 5 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 5 ++ 12 files changed, 199 insertions(+), 74 deletions(-) create mode 100644 src/StreamJsonRpc/Reflection/IJsonRpcFormatterTracingCallbacks.cs create mode 100644 src/StreamJsonRpc/Reflection/IJsonRpcTracingCallbacks.cs diff --git a/src/StreamJsonRpc/HeaderDelimitedMessageHandler.cs b/src/StreamJsonRpc/HeaderDelimitedMessageHandler.cs index 930cd366..7752d0ed 100644 --- a/src/StreamJsonRpc/HeaderDelimitedMessageHandler.cs +++ b/src/StreamJsonRpc/HeaderDelimitedMessageHandler.cs @@ -17,9 +17,8 @@ namespace StreamJsonRpc using System.Threading.Tasks; using Microsoft; using Nerdbank.Streams; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; using StreamJsonRpc.Protocol; + using StreamJsonRpc.Reflection; /// /// Adds headers before each text message transmitted over a stream. @@ -209,6 +208,15 @@ unsafe int WriteHeaderText(string value, Span memory) { this.Formatter.Serialize(this.contentSequenceBuilder, content); ReadOnlySequence contentSequence = this.contentSequenceBuilder.AsReadOnlySequence; + + // Some formatters (e.g. MessagePackFormatter) needs the encoded form in order to produce JSON for tracing. + // Other formatters (e.g. JsonMessageFormatter) would prefer to do its own tracing while it still has a JToken. + // We only help the formatters that need the byte-encoded form here. The rest can do it themselves. + if (this.Formatter is IJsonRpcFormatterTracingCallbacks tracer) + { + tracer.OnSerializationComplete(content, this.contentSequenceBuilder); + } + Memory headerMemory = this.Writer.GetMemory(1024); int bytesWritten = 0; diff --git a/src/StreamJsonRpc/IJsonRpcMessageFormatter.cs b/src/StreamJsonRpc/IJsonRpcMessageFormatter.cs index 0d4230c5..6a9a3a7c 100644 --- a/src/StreamJsonRpc/IJsonRpcMessageFormatter.cs +++ b/src/StreamJsonRpc/IJsonRpcMessageFormatter.cs @@ -3,8 +3,10 @@ namespace StreamJsonRpc { + using System; using System.Buffers; using StreamJsonRpc.Protocol; + using StreamJsonRpc.Reflection; /// /// An interface that offers serialization to and from a sequence of bytes. @@ -30,6 +32,7 @@ public interface IJsonRpcMessageFormatter /// /// The message to be traced. /// Any object whose method will produce a human-readable JSON string, suitable for tracing. + [Obsolete("Tracing is now done via the " + nameof(IJsonRpcTracingCallbacks) + " and " + nameof(IJsonRpcFormatterTracingCallbacks) + " interfaces. Formatters may throw NotSupportedException from this method.")] object GetJsonText(JsonRpcMessage message); } } diff --git a/src/StreamJsonRpc/JsonMessageFormatter.cs b/src/StreamJsonRpc/JsonMessageFormatter.cs index 6fb04d21..710003c7 100644 --- a/src/StreamJsonRpc/JsonMessageFormatter.cs +++ b/src/StreamJsonRpc/JsonMessageFormatter.cs @@ -234,15 +234,13 @@ JsonRpc IJsonRpcInstanceContainer.Rpc { set { + Requires.NotNull(value, nameof(value)); Verify.Operation(this.rpc == null, Resources.FormatterConfigurationLockedAfterJsonRpcAssigned); this.rpc = value; - if (value != null) - { - this.formatterProgressTracker = new MessageFormatterProgressTracker(value, this); - this.enumerableTracker = new MessageFormatterEnumerableTracker(value, this); - this.duplexPipeTracker = new MessageFormatterDuplexPipeTracker(value, this) { MultiplexingStream = this.multiplexingStream }; - } + this.formatterProgressTracker = new MessageFormatterProgressTracker(value, this); + this.enumerableTracker = new MessageFormatterEnumerableTracker(value, this); + this.duplexPipeTracker = new MessageFormatterDuplexPipeTracker(value, this) { MultiplexingStream = this.multiplexingStream }; } } @@ -300,7 +298,12 @@ public JsonRpcMessage Deserialize(ReadOnlySequence contentBuffer, Encoding Requires.NotNull(encoding, nameof(encoding)); JToken json = this.ReadJToken(contentBuffer, encoding); - return this.Deserialize(json); + JsonRpcMessage message = this.Deserialize(json); + + IJsonRpcTracingCallbacks? tracingCallbacks = this.rpc; + tracingCallbacks?.OnMessageDeserialized(message, json); + + return message; } /// @@ -313,7 +316,12 @@ public async ValueTask DeserializeAsync(PipeReader reader, Encod { this.ConfigureJsonTextReader(jsonReader); JToken json = await JToken.ReadFromAsync(jsonReader, cancellationToken).ConfigureAwait(false); - return this.Deserialize(json); + JsonRpcMessage message = this.Deserialize(json); + + IJsonRpcTracingCallbacks? tracingCallbacks = this.rpc; + tracingCallbacks?.OnMessageDeserialized(message, json); + + return message; } } @@ -325,6 +333,9 @@ public void Serialize(IBufferWriter contentBuffer, JsonRpcMessage message) { JToken json = this.Serialize(message); + IJsonRpcTracingCallbacks? tracingCallbacks = this.rpc; + tracingCallbacks?.OnMessageSerialized(message, json); + this.WriteJToken(contentBuffer, json); } @@ -407,7 +418,7 @@ public JToken Serialize(JsonRpcMessage message) /// mutates the itself by tokenizing arguments as if we were going to transmit them /// which BREAKS argument parsing for incoming named argument messages such as $/cancelRequest. /// - public object GetJsonText(JsonRpcMessage message) => JToken.FromObject(message); + public object GetJsonText(JsonRpcMessage message) => throw new NotSupportedException(); /// public void Dispose() diff --git a/src/StreamJsonRpc/JsonRpc.cs b/src/StreamJsonRpc/JsonRpc.cs index 95d679fa..a1dc9a59 100644 --- a/src/StreamJsonRpc/JsonRpc.cs +++ b/src/StreamJsonRpc/JsonRpc.cs @@ -4,6 +4,7 @@ namespace StreamJsonRpc { using System; + using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; @@ -24,7 +25,7 @@ namespace StreamJsonRpc /// /// Manages a JSON-RPC connection with another entity over a . /// - public class JsonRpc : IDisposableObservable, IJsonRpcFormatterCallbacks + public class JsonRpc : IDisposableObservable, IJsonRpcFormatterCallbacks, IJsonRpcTracingCallbacks { private const string ImpliedMethodNameAsyncSuffix = "Async"; private const string CancelRequestSpecialMethod = "$/cancelRequest"; @@ -1111,6 +1112,32 @@ public Task NotifyWithParameterObjectAsync(string targetName, object? argument = return this.InvokeCoreAsync(RequestId.NotSpecified, targetName, argumentToPass, CancellationToken.None, isParameterObject: true); } + void IJsonRpcTracingCallbacks.OnMessageSerialized(JsonRpcMessage message, object encodedMessage) + { + if (this.TraceSource.Switch.ShouldTrace(TraceEventType.Information)) + { + this.TraceSource.TraceData(TraceEventType.Information, (int)TraceEvents.MessageSent, message); + } + + if (this.TraceSource.Switch.ShouldTrace(TraceEventType.Verbose)) + { + this.TraceSource.TraceEvent(TraceEventType.Verbose, (int)TraceEvents.MessageSent, "Sent: {0}", encodedMessage); + } + } + + void IJsonRpcTracingCallbacks.OnMessageDeserialized(JsonRpcMessage message, object encodedMessage) + { + if (this.TraceSource.Switch.ShouldTrace(TraceEventType.Information)) + { + this.TraceSource.TraceData(TraceEventType.Information, (int)TraceEvents.MessageReceived, message); + } + + if (this.TraceSource.Switch.ShouldTrace(TraceEventType.Verbose)) + { + this.TraceSource.TraceEvent(TraceEventType.Verbose, (int)TraceEvents.MessageReceived, "Received: {0}", encodedMessage); + } + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -2124,8 +2151,6 @@ private async Task ReadAndHandleRequestsAsync() this.OnJsonRpcDisconnected(new JsonRpcDisconnectedEventArgs(Resources.ReachedEndOfStream, DisconnectedReason.RemotePartyTerminated)); return; } - - this.TraceMessageReceived(protocolMessage); } catch (OperationCanceledException) { @@ -2384,49 +2409,10 @@ private void TraceLocalMethodAdded(string rpcMethodName, MethodSignatureAndTarge } } - private void TraceMessageSent(JsonRpcMessage message) - { - if (this.TraceSource.Switch.ShouldTrace(TraceEventType.Information)) - { - this.TraceSource.TraceData(TraceEventType.Information, (int)TraceEvents.MessageSent, message); - } - - if (this.TraceSource.Switch.ShouldTrace(TraceEventType.Verbose)) - { - this.TraceSource.TraceEvent(TraceEventType.Verbose, (int)TraceEvents.MessageSent, "Sent: {0}", this.GetMessageJson(message)); - } - } - - private void TraceMessageReceived(JsonRpcMessage message) - { - if (this.TraceSource.Switch.ShouldTrace(TraceEventType.Information)) - { - this.TraceSource.TraceData(TraceEventType.Information, (int)TraceEvents.MessageReceived, message); - } - - if (this.TraceSource.Switch.ShouldTrace(TraceEventType.Verbose)) - { - this.TraceSource.TraceEvent(TraceEventType.Verbose, (int)TraceEvents.MessageReceived, "Received: {0}", this.GetMessageJson(message)); - } - } - - private object GetMessageJson(JsonRpcMessage message) - { - try - { - return this.MessageHandler.Formatter.GetJsonText(message); - } - catch (Exception ex) - { - return $""; - } - } - private async ValueTask TransmitAsync(JsonRpcMessage message, CancellationToken cancellationToken) { try { - this.TraceMessageSent(message); bool etwEnabled = JsonRpcEventSource.Instance.IsEnabled(System.Diagnostics.Tracing.EventLevel.Informational, System.Diagnostics.Tracing.EventKeywords.None); if (etwEnabled) { diff --git a/src/StreamJsonRpc/LengthHeaderMessageHandler.cs b/src/StreamJsonRpc/LengthHeaderMessageHandler.cs index 67f51692..479d8fc8 100644 --- a/src/StreamJsonRpc/LengthHeaderMessageHandler.cs +++ b/src/StreamJsonRpc/LengthHeaderMessageHandler.cs @@ -3,18 +3,15 @@ namespace StreamJsonRpc { - using System; using System.Buffers; - using System.Collections.Generic; using System.IO; using System.IO.Pipelines; - using System.Runtime.InteropServices; - using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft; using Nerdbank.Streams; using StreamJsonRpc.Protocol; + using StreamJsonRpc.Reflection; /// /// A minimal header for each message that simply declares content length. @@ -30,9 +27,9 @@ public class LengthHeaderMessageHandler : PipeMessageHandler private readonly IJsonRpcMessageFormatter formatter; /// - /// A wrapper to use for the when we need to count bytes written. + /// The sent to the to write the message. /// - private PrefixingBufferWriter? prefixingWriter; + private readonly Sequence contentSequenceBuilder = new Sequence(ArrayPool.Shared); /// /// Initializes a new instance of the class. @@ -94,17 +91,29 @@ protected override void Write(JsonRpcMessage content, CancellationToken cancella { Assumes.NotNull(this.Writer); - if (this.prefixingWriter == null) + try { - this.prefixingWriter = new PrefixingBufferWriter(this.Writer, sizeof(int)); - } + // Write out the actual message content. + this.formatter.Serialize(this.contentSequenceBuilder, content); + ReadOnlySequence contentSequence = this.contentSequenceBuilder.AsReadOnlySequence; - // Write out the actual message content, counting all written bytes. - this.formatter.Serialize(this.prefixingWriter, content); + // Some formatters (e.g. MessagePackFormatter) needs the encoded form in order to produce JSON for tracing. + // Other formatters (e.g. JsonMessageFormatter) would prefer to do its own tracing while it still has a JToken. + // We only help the formatters that need the byte-encoded form here. The rest can do it themselves. + if (this.Formatter is IJsonRpcFormatterTracingCallbacks tracer) + { + tracer.OnSerializationComplete(content, contentSequence); + } - // Now go back and fill in the header with the actual content length. - Utilities.Write(this.prefixingWriter.Prefix.Span, checked((int)this.prefixingWriter.Length)); - this.prefixingWriter.Commit(); + // Now go back and fill in the header with the actual content length. + Utilities.Write(this.Writer.GetSpan(sizeof(int)), checked((int)contentSequence.Length)); + this.Writer.Advance(sizeof(int)); + contentSequence.CopyTo(this.Writer); + } + finally + { + this.contentSequenceBuilder.Reset(); + } } } } diff --git a/src/StreamJsonRpc/MessagePackFormatter.cs b/src/StreamJsonRpc/MessagePackFormatter.cs index ab54b1ac..106d422b 100644 --- a/src/StreamJsonRpc/MessagePackFormatter.cs +++ b/src/StreamJsonRpc/MessagePackFormatter.cs @@ -29,7 +29,7 @@ namespace StreamJsonRpc /// The README on that project site describes use cases and its performance compared to alternative /// .NET MessagePack implementations and this one appears to be the best by far. /// - public class MessagePackFormatter : IJsonRpcMessageFormatter, IJsonRpcInstanceContainer, IJsonRpcFormatterState, IDisposable + public class MessagePackFormatter : IJsonRpcMessageFormatter, IJsonRpcInstanceContainer, IJsonRpcFormatterState, IJsonRpcFormatterTracingCallbacks, IDisposable { /// /// The constant "jsonrpc". @@ -217,7 +217,15 @@ public void SetMessagePackSerializerOptions(MessagePackSerializerOptions options } /// - public JsonRpcMessage Deserialize(ReadOnlySequence contentBuffer) => MessagePackSerializer.Deserialize(contentBuffer, this.messageSerializationOptions); + public JsonRpcMessage Deserialize(ReadOnlySequence contentBuffer) + { + JsonRpcMessage message = MessagePackSerializer.Deserialize(contentBuffer, this.messageSerializationOptions); + + IJsonRpcTracingCallbacks? tracingCallbacks = this.rpc; + tracingCallbacks?.OnMessageDeserialized(message, new ToStringHelper(contentBuffer, this.messageSerializationOptions)); + + return message; + } /// public void Serialize(IBufferWriter contentBuffer, JsonRpcMessage message) @@ -235,7 +243,13 @@ public void Serialize(IBufferWriter contentBuffer, JsonRpcMessage message) } /// - public object GetJsonText(JsonRpcMessage message) => message is IJsonRpcMessagePackRetention retainedMsgPack ? MessagePackSerializer.ConvertToJson(retainedMsgPack.OriginalMessagePack, this.messageSerializationOptions) : MessagePackSerializer.SerializeToJson(message, this.messageSerializationOptions); + public object GetJsonText(JsonRpcMessage message) => message is IJsonRpcMessagePackRetention retainedMsgPack ? MessagePackSerializer.ConvertToJson(retainedMsgPack.OriginalMessagePack, this.messageSerializationOptions) : throw new NotSupportedException(); + + void IJsonRpcFormatterTracingCallbacks.OnSerializationComplete(JsonRpcMessage message, ReadOnlySequence encodedMessage) + { + IJsonRpcTracingCallbacks? tracingCallbacks = this.rpc; + tracingCallbacks?.OnMessageSerialized(message, new ToStringHelper(encodedMessage, this.messageSerializationOptions)); + } /// public void Dispose() @@ -424,6 +438,20 @@ internal void WriteRaw(ref MessagePackWriter writer) } } + private class ToStringHelper + { + private readonly ReadOnlySequence encodedMessage; + private readonly MessagePackSerializerOptions options; + + internal ToStringHelper(ReadOnlySequence encodedMessage, MessagePackSerializerOptions options) + { + this.encodedMessage = encodedMessage; + this.options = options; + } + + public override string ToString() => MessagePackSerializer.ConvertToJson(this.encodedMessage, this.options); + } + private class RequestIdFormatter : IMessagePackFormatter { internal static readonly RequestIdFormatter Instance = new RequestIdFormatter(); diff --git a/src/StreamJsonRpc/Reflection/IJsonRpcFormatterTracingCallbacks.cs b/src/StreamJsonRpc/Reflection/IJsonRpcFormatterTracingCallbacks.cs new file mode 100644 index 00000000..9992b2d0 --- /dev/null +++ b/src/StreamJsonRpc/Reflection/IJsonRpcFormatterTracingCallbacks.cs @@ -0,0 +1,21 @@ +// 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.Buffers; + using StreamJsonRpc.Protocol; + + /// + /// Optionally implemented by a when it needs the fully serialized sequence in order to trace the JSON representation of the message. + /// + public interface IJsonRpcFormatterTracingCallbacks + { + /// + /// Invites the formatter to call with the JSON representation of the message just serialized.. + /// + /// The message that was just serialized. + /// The encoded copy of the message, as it recently came from the method. + void OnSerializationComplete(JsonRpcMessage message, ReadOnlySequence encodedMessage); + } +} diff --git a/src/StreamJsonRpc/Reflection/IJsonRpcTracingCallbacks.cs b/src/StreamJsonRpc/Reflection/IJsonRpcTracingCallbacks.cs new file mode 100644 index 00000000..be57b4c5 --- /dev/null +++ b/src/StreamJsonRpc/Reflection/IJsonRpcTracingCallbacks.cs @@ -0,0 +1,27 @@ +// 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 StreamJsonRpc.Protocol; + + /// + /// An interface implemented by for implementations to use to facilitate message tracing. + /// + public interface IJsonRpcTracingCallbacks + { + /// + /// Occurs when the has serialized a message for transmission. + /// + /// The JSON-RPC message. + /// The encoded form of the message. Calling on this should produce the JSON-RPC text of the message. + void OnMessageSerialized(JsonRpcMessage message, object encodedMessage); + + /// + /// Occurs when the has deserialized an incoming message. + /// + /// The JSON-RPC message. + /// The encoded form of the message. Calling on this should produce the JSON-RPC text of the message. + void OnMessageDeserialized(JsonRpcMessage message, object encodedMessage); + } +} diff --git a/src/StreamJsonRpc/Utilities.cs b/src/StreamJsonRpc/Utilities.cs index 108e1ad1..a8ab782e 100644 --- a/src/StreamJsonRpc/Utilities.cs +++ b/src/StreamJsonRpc/Utilities.cs @@ -58,5 +58,21 @@ internal static void Write(Span buffer, int value) buffer[2] = (byte)(value >> 8); buffer[3] = (byte)value; } + + /// + /// Copies a to an . + /// + /// The type of element to copy. + /// The sequence to read from. + /// The target to write to. + internal static void CopyTo(this in ReadOnlySequence sequence, IBufferWriter writer) + { + Requires.NotNull(writer, nameof(writer)); + + foreach (ReadOnlyMemory segment in sequence) + { + writer.Write(segment.Span); + } + } } } diff --git a/src/StreamJsonRpc/WebSocketMessageHandler.cs b/src/StreamJsonRpc/WebSocketMessageHandler.cs index afd7c0a2..a2edc7e7 100644 --- a/src/StreamJsonRpc/WebSocketMessageHandler.cs +++ b/src/StreamJsonRpc/WebSocketMessageHandler.cs @@ -5,17 +5,14 @@ namespace StreamJsonRpc { using System; using System.Buffers; - using System.IO; using System.Net.WebSockets; using System.Runtime.InteropServices; - using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft; using Nerdbank.Streams; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; using StreamJsonRpc.Protocol; + using StreamJsonRpc.Reflection; /// /// A message handler for the class @@ -122,6 +119,15 @@ protected override async ValueTask WriteCoreAsync(JsonRpcMessage content, Cancel WebSocketMessageType messageType = this.Formatter is IJsonRpcMessageTextFormatter ? WebSocketMessageType.Text : WebSocketMessageType.Binary; this.Formatter.Serialize(contentSequenceBuilder, content); cancellationToken.ThrowIfCancellationRequested(); + + // Some formatters (e.g. MessagePackFormatter) needs the encoded form in order to produce JSON for tracing. + // Other formatters (e.g. JsonMessageFormatter) would prefer to do its own tracing while it still has a JToken. + // We only help the formatters that need the byte-encoded form here. The rest can do it themselves. + if (this.Formatter is IJsonRpcFormatterTracingCallbacks tracer) + { + tracer.OnSerializationComplete(content, contentSequenceBuilder); + } + int bytesCopied = 0; ReadOnlySequence contentSequence = contentSequenceBuilder.AsReadOnlySequence; foreach (ReadOnlyMemory memory in contentSequence) diff --git a/src/StreamJsonRpc/netcoreapp2.1/PublicAPI.Unshipped.txt b/src/StreamJsonRpc/netcoreapp2.1/PublicAPI.Unshipped.txt index b64c3f41..94e8905d 100644 --- a/src/StreamJsonRpc/netcoreapp2.1/PublicAPI.Unshipped.txt +++ b/src/StreamJsonRpc/netcoreapp2.1/PublicAPI.Unshipped.txt @@ -38,8 +38,13 @@ StreamJsonRpc.Reflection.IJsonRpcFormatterState StreamJsonRpc.Reflection.IJsonRpcFormatterState.DeserializingMessageWithId.get -> StreamJsonRpc.RequestId StreamJsonRpc.Reflection.IJsonRpcFormatterState.SerializingMessageWithId.get -> StreamJsonRpc.RequestId StreamJsonRpc.Reflection.IJsonRpcFormatterState.SerializingRequest.get -> bool +StreamJsonRpc.Reflection.IJsonRpcFormatterTracingCallbacks +StreamJsonRpc.Reflection.IJsonRpcFormatterTracingCallbacks.OnSerializationComplete(StreamJsonRpc.Protocol.JsonRpcMessage message, System.Buffers.ReadOnlySequence encodedMessage) -> void StreamJsonRpc.Reflection.IJsonRpcMessageBufferManager StreamJsonRpc.Reflection.IJsonRpcMessageBufferManager.DeserializationComplete(StreamJsonRpc.Protocol.JsonRpcMessage message) -> void +StreamJsonRpc.Reflection.IJsonRpcTracingCallbacks +StreamJsonRpc.Reflection.IJsonRpcTracingCallbacks.OnMessageDeserialized(StreamJsonRpc.Protocol.JsonRpcMessage message, object encodedMessage) -> void +StreamJsonRpc.Reflection.IJsonRpcTracingCallbacks.OnMessageSerialized(StreamJsonRpc.Protocol.JsonRpcMessage message, object encodedMessage) -> void StreamJsonRpc.Reflection.JsonRpcMessageEventArgs StreamJsonRpc.Reflection.JsonRpcMessageEventArgs.JsonRpcMessageEventArgs(StreamJsonRpc.RequestId requestId) -> void StreamJsonRpc.Reflection.JsonRpcMessageEventArgs.RequestId.get -> StreamJsonRpc.RequestId diff --git a/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt b/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt index b64c3f41..94e8905d 100644 --- a/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt @@ -38,8 +38,13 @@ StreamJsonRpc.Reflection.IJsonRpcFormatterState StreamJsonRpc.Reflection.IJsonRpcFormatterState.DeserializingMessageWithId.get -> StreamJsonRpc.RequestId StreamJsonRpc.Reflection.IJsonRpcFormatterState.SerializingMessageWithId.get -> StreamJsonRpc.RequestId StreamJsonRpc.Reflection.IJsonRpcFormatterState.SerializingRequest.get -> bool +StreamJsonRpc.Reflection.IJsonRpcFormatterTracingCallbacks +StreamJsonRpc.Reflection.IJsonRpcFormatterTracingCallbacks.OnSerializationComplete(StreamJsonRpc.Protocol.JsonRpcMessage message, System.Buffers.ReadOnlySequence encodedMessage) -> void StreamJsonRpc.Reflection.IJsonRpcMessageBufferManager StreamJsonRpc.Reflection.IJsonRpcMessageBufferManager.DeserializationComplete(StreamJsonRpc.Protocol.JsonRpcMessage message) -> void +StreamJsonRpc.Reflection.IJsonRpcTracingCallbacks +StreamJsonRpc.Reflection.IJsonRpcTracingCallbacks.OnMessageDeserialized(StreamJsonRpc.Protocol.JsonRpcMessage message, object encodedMessage) -> void +StreamJsonRpc.Reflection.IJsonRpcTracingCallbacks.OnMessageSerialized(StreamJsonRpc.Protocol.JsonRpcMessage message, object encodedMessage) -> void StreamJsonRpc.Reflection.JsonRpcMessageEventArgs StreamJsonRpc.Reflection.JsonRpcMessageEventArgs.JsonRpcMessageEventArgs(StreamJsonRpc.RequestId requestId) -> void StreamJsonRpc.Reflection.JsonRpcMessageEventArgs.RequestId.get -> StreamJsonRpc.RequestId