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
3 changes: 2 additions & 1 deletion src/Grpc.Core.Api/AsyncClientStreamingCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,13 @@ public AsyncClientStreamingCallDebugView(AsyncClientStreamingCall<TRequest, TRes
_call = call;
}

public CallDebuggerMethodDebugView? Method => _call.callState.State is IMethod method ? new CallDebuggerMethodDebugView(method) : null;
public bool IsComplete => CallDebuggerHelpers.GetStatus(_call.callState) != null;
public Status? Status => CallDebuggerHelpers.GetStatus(_call.callState);
public Metadata? ResponseHeaders => _call.ResponseHeadersAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseHeadersAsync.GetAwaiter().GetResult() : null;
public Metadata? Trailers => CallDebuggerHelpers.GetTrailers(_call.callState);
public IClientStreamWriter<TRequest> RequestStream => _call.RequestStream;
public TResponse? Response => _call.ResponseAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseAsync.Result : default;
public CallDebuggerMethodDebugView? Method => CallDebuggerHelpers.GetDebugValue<IMethod>(_call.callState, CallDebuggerHelpers.MethodKey) is { } method ? new CallDebuggerMethodDebugView(method) : null;
public ChannelBase? Channel => CallDebuggerHelpers.GetDebugValue<ChannelBase>(_call.callState, CallDebuggerHelpers.ChannelKey);
}
}
3 changes: 2 additions & 1 deletion src/Grpc.Core.Api/AsyncDuplexStreamingCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,13 @@ public AsyncDuplexStreamingCallDebugView(AsyncDuplexStreamingCall<TRequest, TRes
_call = call;
}

public CallDebuggerMethodDebugView? Method => _call.callState.State is IMethod method ? new CallDebuggerMethodDebugView(method) : null;
public bool IsComplete => CallDebuggerHelpers.GetStatus(_call.callState) != null;
public Status? Status => CallDebuggerHelpers.GetStatus(_call.callState);
public Metadata? ResponseHeaders => _call.ResponseHeadersAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseHeadersAsync.Result : null;
public Metadata? Trailers => CallDebuggerHelpers.GetTrailers(_call.callState);
public IAsyncStreamReader<TResponse> ResponseStream => _call.ResponseStream;
public IClientStreamWriter<TRequest> RequestStream => _call.RequestStream;
public CallDebuggerMethodDebugView? Method => CallDebuggerHelpers.GetDebugValue<IMethod>(_call.callState, CallDebuggerHelpers.MethodKey) is { } method ? new CallDebuggerMethodDebugView(method) : null;
public ChannelBase? Channel => CallDebuggerHelpers.GetDebugValue<ChannelBase>(_call.callState, CallDebuggerHelpers.ChannelKey);
}
}
4 changes: 3 additions & 1 deletion src/Grpc.Core.Api/AsyncServerStreamingCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,13 @@ public AsyncServerStreamingCallDebugView(AsyncServerStreamingCall<TResponse> cal
_call = call;
}

public CallDebuggerMethodDebugView? Method => _call.callState.State is IMethod method ? new CallDebuggerMethodDebugView(method) : null;
public bool IsComplete => CallDebuggerHelpers.GetStatus(_call.callState) != null;
public Status? Status => CallDebuggerHelpers.GetStatus(_call.callState);
public Metadata? ResponseHeaders => _call.ResponseHeadersAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseHeadersAsync.Result : null;
public Metadata? Trailers => CallDebuggerHelpers.GetTrailers(_call.callState);
public IAsyncStreamReader<TResponse> ResponseStream => _call.ResponseStream;
public CallDebuggerMethodDebugView? Method => CallDebuggerHelpers.GetDebugValue<IMethod>(_call.callState, CallDebuggerHelpers.MethodKey) is { } method ? new CallDebuggerMethodDebugView(method) : null;
public ChannelBase? Channel => CallDebuggerHelpers.GetDebugValue<ChannelBase>(_call.callState, CallDebuggerHelpers.ChannelKey);
public object? Request => CallDebuggerHelpers.GetDebugValue<object>(_call.callState, CallDebuggerHelpers.RequestKey);
}
}
4 changes: 3 additions & 1 deletion src/Grpc.Core.Api/AsyncUnaryCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,13 @@ public AsyncUnaryCallDebugView(AsyncUnaryCall<TResponse> call)
_call = call;
}

public CallDebuggerMethodDebugView? Method => _call.callState.State is IMethod method ? new CallDebuggerMethodDebugView(method) : null;
public bool IsComplete => CallDebuggerHelpers.GetStatus(_call.callState) != null;
public Status? Status => CallDebuggerHelpers.GetStatus(_call.callState);
public Metadata? ResponseHeaders => _call.ResponseHeadersAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseHeadersAsync.Result : null;
public Metadata? Trailers => CallDebuggerHelpers.GetTrailers(_call.callState);
public TResponse? Response => _call.ResponseAsync.Status == TaskStatus.RanToCompletion ? _call.ResponseAsync.Result : default;
public CallDebuggerMethodDebugView? Method => CallDebuggerHelpers.GetDebugValue<IMethod>(_call.callState, CallDebuggerHelpers.MethodKey) is { } method ? new CallDebuggerMethodDebugView(method) : null;
public ChannelBase? Channel => CallDebuggerHelpers.GetDebugValue<ChannelBase>(_call.callState, CallDebuggerHelpers.ChannelKey);
public object? Request => CallDebuggerHelpers.GetDebugValue<object>(_call.callState, CallDebuggerHelpers.RequestKey);
}
}
38 changes: 36 additions & 2 deletions src/Grpc.Core.Api/Internal/CallDebuggerHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,28 @@
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace Grpc.Core.Internal;

internal static class CallDebuggerHelpers
{
public const string MethodKey = "Method";
public const string ChannelKey = "Channel";
public const string RequestKey = "Request";

public static string DebuggerToString(AsyncCallState callState)
{
string debugText = string.Empty;
if (callState.State is IMethod method)
if (GetDebugValue<ChannelBase>(callState, ChannelKey) is { } channel)
{
debugText = $"Channel = {channel.Target}, ";
}
if (GetDebugValue<IMethod>(callState, MethodKey) is { } method)
{
debugText = $"Method = {method.FullName}, ";
debugText += $"Method = {method.FullName}, ";
}

var status = GetStatus(callState);
Expand All @@ -40,6 +50,30 @@ public static string DebuggerToString(AsyncCallState callState)
return debugText;
}

public static T? GetDebugValue<T>(AsyncCallState callState, string key) where T : class
{
// We want to get information about a call to display during debugging, but Grpc.Core.Api does
// doesn't have access to the implementation's internal fields.
// GetDebugValue accesses values by IEnumerable + key from the implementation state.
if (callState.State is IEnumerable<KeyValuePair<string, object>> enumerable)
{
foreach (var entry in enumerable)
{
if (entry.Key == key)
{
if (entry.Value is T t)
{
return t;
}

return null;
}
}
}

return null;
}

public static Status? GetStatus(AsyncCallState callState)
{
// This is the only public API to get this value and there is no way to check if it's available.
Expand Down
16 changes: 10 additions & 6 deletions src/Grpc.Net.Client/Internal/GrpcCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#endregion

using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
Expand Down Expand Up @@ -53,6 +54,7 @@ internal sealed partial class GrpcCall<TRequest, TResponse> : GrpcCall, IGrpcCal

// These are set depending on the type of gRPC call
private TaskCompletionSource<TResponse>? _responseTcs;
private TRequest? _request;

public int MessagesWritten { get; private set; }
public int MessagesRead { get; private set; }
Expand Down Expand Up @@ -99,12 +101,11 @@ private void ValidateDeadline(DateTime? deadline)

public object? CallWrapper { get; set; }

MethodType IMethod.Type => Method.Type;
string IMethod.ServiceName => Method.ServiceName;
string IMethod.Name => Method.Name;
string IMethod.FullName => Method.FullName;

public void StartUnary(TRequest request) => StartUnaryCore(CreatePushUnaryContent(request));
public void StartUnary(TRequest request)
{
_request = request;
StartUnaryCore(CreatePushUnaryContent(request));
}

public void StartClientStreaming()
{
Expand Down Expand Up @@ -1161,4 +1162,7 @@ private static void WriteDiagnosticEvent<
{
diagnosticSource.Write(name, value);
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => GrpcProtocolConstants.GetDebugEnumerator(Channel, Method, _request);
}
19 changes: 19 additions & 0 deletions src/Grpc.Net.Client/Internal/GrpcProtocolConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#endregion

using System.Collections.Generic;
using System.Net.Http.Headers;
using Grpc.Core;
using Grpc.Net.Compression;
Expand Down Expand Up @@ -83,6 +84,24 @@ internal static string GetMessageAcceptEncoding(Dictionary<string, ICompressionP
#endif
}

/// <summary>
/// Gets key value pairs used by debugging. These are provided as an enumerator instead of a dictionary
/// because it's one method to implement an enumerator on gRPC calls compared to a dozen members for a dictionary.
/// </summary>
public static IEnumerator<KeyValuePair<string, object>> GetDebugEnumerator(ChannelBase channel, IMethod method, object? request)
{
const string MethodKey = "Method";
const string ChannelKey = "Channel";
const string RequestKey = "Request";

yield return new KeyValuePair<string, object>(ChannelKey, channel);
yield return new KeyValuePair<string, object>(MethodKey, method);
if (request != null)
{
yield return new KeyValuePair<string, object>(RequestKey, request);
}
}

static GrpcProtocolConstants()
{
UserAgentHeader = "User-Agent";
Expand Down
2 changes: 1 addition & 1 deletion src/Grpc.Net.Client/Internal/IGrpcCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

namespace Grpc.Net.Client.Internal;

internal interface IGrpcCall<TRequest, TResponse> : IDisposable, IMethod
internal interface IGrpcCall<TRequest, TResponse> : IDisposable, IEnumerable<KeyValuePair<string, object>>
where TRequest : class
where TResponse : class
{
Expand Down
13 changes: 7 additions & 6 deletions src/Grpc.Net.Client/Internal/Retry/RetryCallBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#endregion

using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Grpc.Core;
Expand All @@ -35,6 +36,7 @@ internal abstract partial class RetryCallBase<TRequest, TResponse> : IGrpcCall<T
private RetryCallBaseClientStreamWriter<TRequest, TResponse>? _retryBaseClientStreamWriter;
private Task<TResponse>? _responseTask;
private Task<Metadata>? _responseHeadersTask;
private TRequest? _request;

// Internal for unit testing.
internal CancellationTokenRegistration? _ctsRegistration;
Expand Down Expand Up @@ -64,11 +66,6 @@ internal abstract partial class RetryCallBase<TRequest, TResponse> : IGrpcCall<T
protected long CurrentCallBufferSize { get; set; }
protected bool BufferedCurrentMessage { get; set; }

MethodType IMethod.Type => Method.Type;
string IMethod.ServiceName => Method.ServiceName;
string IMethod.Name => Method.Name;
string IMethod.FullName => Method.FullName;

protected RetryCallBase(GrpcChannel channel, Method<TRequest, TResponse> method, CallOptions options, string loggerName, int retryAttempts)
{
Logger = channel.LoggerFactory.CreateLogger(loggerName);
Expand Down Expand Up @@ -170,6 +167,7 @@ public Metadata GetTrailers()

public void StartUnary(TRequest request)
{
_request = request;
StartCore(call => call.StartUnaryCore(CreatePushUnaryContent(request, call)));
}

Expand Down Expand Up @@ -520,7 +518,7 @@ internal void ClearRetryBuffer()

protected StatusGrpcCall<TRequest, TResponse> CreateStatusCall(Status status)
{
var call = new StatusGrpcCall<TRequest, TResponse>(status, Channel, Method, MessagesRead);
var call = new StatusGrpcCall<TRequest, TResponse>(status, Channel, Method, MessagesRead, _request);
call.CallWrapper = CallWrapper;
return call;
}
Expand Down Expand Up @@ -595,4 +593,7 @@ public Exception CreateFailureStatusException(Status status)
{
throw new NotSupportedException();
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => GrpcProtocolConstants.GetDebugEnumerator(Channel, Method, _request);
}
13 changes: 7 additions & 6 deletions src/Grpc.Net.Client/Internal/Retry/StatusGrpcCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#endregion

using System.Collections;
using System.Diagnostics.CodeAnalysis;
using Grpc.Core;

Expand All @@ -28,6 +29,7 @@ internal sealed class StatusGrpcCall<TRequest, TResponse> : IGrpcCall<TRequest,
private readonly Status _status;
private readonly GrpcChannel _channel;
private readonly Method<TRequest, TResponse> _method;
private readonly TRequest? _request;
private IClientStreamWriter<TRequest>? _clientStreamWriter;
private IAsyncStreamReader<TResponse>? _clientStreamReader;

Expand All @@ -39,17 +41,13 @@ internal sealed class StatusGrpcCall<TRequest, TResponse> : IGrpcCall<TRequest,

public object? CallWrapper { get; set; }

MethodType IMethod.Type => _method.Type;
string IMethod.ServiceName => _method.ServiceName;
string IMethod.Name => _method.Name;
string IMethod.FullName => _method.FullName;

public StatusGrpcCall(Status status, GrpcChannel channel, Method<TRequest, TResponse> method, int messagesRead)
public StatusGrpcCall(Status status, GrpcChannel channel, Method<TRequest, TResponse> method, int messagesRead, TRequest? request)
{
_status = status;
_channel = channel;
_method = method;
MessagesRead = messagesRead;
_request = request;
}

public void Dispose()
Expand Down Expand Up @@ -124,6 +122,9 @@ public Exception CreateFailureStatusException(Status status)
}
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => GrpcProtocolConstants.GetDebugEnumerator(_channel, _method, _request);

private sealed class StatusClientStreamWriter : IClientStreamWriter<TRequest>
{
private readonly Status _status;
Expand Down