|
|
@ -17,6 +17,7 @@ |
|
|
|
#endregion |
|
|
|
#endregion |
|
|
|
|
|
|
|
|
|
|
|
using System; |
|
|
|
using System; |
|
|
|
|
|
|
|
using System.Threading; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using Grpc.Core.Logging; |
|
|
|
using Grpc.Core.Logging; |
|
|
|
using Grpc.Core.Profiling; |
|
|
|
using Grpc.Core.Profiling; |
|
|
@ -34,6 +35,8 @@ namespace Grpc.Core.Internal |
|
|
|
readonly CallInvocationDetails<TRequest, TResponse> details; |
|
|
|
readonly CallInvocationDetails<TRequest, TResponse> details; |
|
|
|
readonly INativeCall injectedNativeCall; // for testing |
|
|
|
readonly INativeCall injectedNativeCall; // for testing |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool registeredWithChannel; |
|
|
|
|
|
|
|
|
|
|
|
// Dispose of to de-register cancellation token registration |
|
|
|
// Dispose of to de-register cancellation token registration |
|
|
|
IDisposable cancellationTokenRegistration; |
|
|
|
IDisposable cancellationTokenRegistration; |
|
|
|
|
|
|
|
|
|
|
@ -79,6 +82,9 @@ namespace Grpc.Core.Internal |
|
|
|
{ |
|
|
|
{ |
|
|
|
byte[] payload = UnsafeSerialize(msg); |
|
|
|
byte[] payload = UnsafeSerialize(msg); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool callStartedOk = false; |
|
|
|
|
|
|
|
try |
|
|
|
|
|
|
|
{ |
|
|
|
unaryResponseTcs = new TaskCompletionSource<TResponse>(); |
|
|
|
unaryResponseTcs = new TaskCompletionSource<TResponse>(); |
|
|
|
|
|
|
|
|
|
|
|
lock (myLock) |
|
|
|
lock (myLock) |
|
|
@ -97,6 +103,8 @@ namespace Grpc.Core.Internal |
|
|
|
try |
|
|
|
try |
|
|
|
{ |
|
|
|
{ |
|
|
|
call.StartUnary(ctx, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags); |
|
|
|
call.StartUnary(ctx, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags); |
|
|
|
|
|
|
|
callStartedOk = true; |
|
|
|
|
|
|
|
|
|
|
|
var ev = cq.Pluck(ctx.Handle); |
|
|
|
var ev = cq.Pluck(ctx.Handle); |
|
|
|
bool success = (ev.success != 0); |
|
|
|
bool success = (ev.success != 0); |
|
|
|
try |
|
|
|
try |
|
|
@ -116,6 +124,18 @@ namespace Grpc.Core.Internal |
|
|
|
ctx.Recycle(); |
|
|
|
ctx.Recycle(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (!callStartedOk) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
lock (myLock) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
OnFailedToStartCallLocked(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
throw; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Once the blocking call returns, the result should be available synchronously. |
|
|
|
// Once the blocking call returns, the result should be available synchronously. |
|
|
|
// Note that GetAwaiter().GetResult() doesn't wrap exceptions in AggregateException. |
|
|
|
// Note that GetAwaiter().GetResult() doesn't wrap exceptions in AggregateException. |
|
|
@ -129,6 +149,9 @@ namespace Grpc.Core.Internal |
|
|
|
public Task<TResponse> UnaryCallAsync(TRequest msg) |
|
|
|
public Task<TResponse> UnaryCallAsync(TRequest msg) |
|
|
|
{ |
|
|
|
{ |
|
|
|
lock (myLock) |
|
|
|
lock (myLock) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
bool callStartedOk = false; |
|
|
|
|
|
|
|
try |
|
|
|
{ |
|
|
|
{ |
|
|
|
GrpcPreconditions.CheckState(!started); |
|
|
|
GrpcPreconditions.CheckState(!started); |
|
|
|
started = true; |
|
|
|
started = true; |
|
|
@ -144,9 +167,20 @@ namespace Grpc.Core.Internal |
|
|
|
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) |
|
|
|
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
call.StartUnary(UnaryResponseClientCallback, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags); |
|
|
|
call.StartUnary(UnaryResponseClientCallback, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags); |
|
|
|
|
|
|
|
callStartedOk = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return unaryResponseTcs.Task; |
|
|
|
return unaryResponseTcs.Task; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (!callStartedOk) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
OnFailedToStartCallLocked(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
throw; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// <summary> |
|
|
@ -156,6 +190,9 @@ namespace Grpc.Core.Internal |
|
|
|
public Task<TResponse> ClientStreamingCallAsync() |
|
|
|
public Task<TResponse> ClientStreamingCallAsync() |
|
|
|
{ |
|
|
|
{ |
|
|
|
lock (myLock) |
|
|
|
lock (myLock) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
bool callStartedOk = false; |
|
|
|
|
|
|
|
try |
|
|
|
{ |
|
|
|
{ |
|
|
|
GrpcPreconditions.CheckState(!started); |
|
|
|
GrpcPreconditions.CheckState(!started); |
|
|
|
started = true; |
|
|
|
started = true; |
|
|
@ -168,10 +205,20 @@ namespace Grpc.Core.Internal |
|
|
|
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) |
|
|
|
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
call.StartClientStreaming(UnaryResponseClientCallback, metadataArray, details.Options.Flags); |
|
|
|
call.StartClientStreaming(UnaryResponseClientCallback, metadataArray, details.Options.Flags); |
|
|
|
|
|
|
|
callStartedOk = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return unaryResponseTcs.Task; |
|
|
|
return unaryResponseTcs.Task; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (!callStartedOk) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
OnFailedToStartCallLocked(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
throw; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// <summary> |
|
|
@ -180,6 +227,9 @@ namespace Grpc.Core.Internal |
|
|
|
public void StartServerStreamingCall(TRequest msg) |
|
|
|
public void StartServerStreamingCall(TRequest msg) |
|
|
|
{ |
|
|
|
{ |
|
|
|
lock (myLock) |
|
|
|
lock (myLock) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
bool callStartedOk = false; |
|
|
|
|
|
|
|
try |
|
|
|
{ |
|
|
|
{ |
|
|
|
GrpcPreconditions.CheckState(!started); |
|
|
|
GrpcPreconditions.CheckState(!started); |
|
|
|
started = true; |
|
|
|
started = true; |
|
|
@ -194,9 +244,19 @@ namespace Grpc.Core.Internal |
|
|
|
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) |
|
|
|
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
call.StartServerStreaming(ReceivedStatusOnClientCallback, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags); |
|
|
|
call.StartServerStreaming(ReceivedStatusOnClientCallback, payload, GetWriteFlagsForCall(), metadataArray, details.Options.Flags); |
|
|
|
|
|
|
|
callStartedOk = true; |
|
|
|
} |
|
|
|
} |
|
|
|
call.StartReceiveInitialMetadata(ReceivedResponseHeadersCallback); |
|
|
|
call.StartReceiveInitialMetadata(ReceivedResponseHeadersCallback); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (!callStartedOk) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
OnFailedToStartCallLocked(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
throw; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// <summary> |
|
|
@ -206,6 +266,9 @@ namespace Grpc.Core.Internal |
|
|
|
public void StartDuplexStreamingCall() |
|
|
|
public void StartDuplexStreamingCall() |
|
|
|
{ |
|
|
|
{ |
|
|
|
lock (myLock) |
|
|
|
lock (myLock) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
bool callStartedOk = false; |
|
|
|
|
|
|
|
try |
|
|
|
{ |
|
|
|
{ |
|
|
|
GrpcPreconditions.CheckState(!started); |
|
|
|
GrpcPreconditions.CheckState(!started); |
|
|
|
started = true; |
|
|
|
started = true; |
|
|
@ -216,9 +279,19 @@ namespace Grpc.Core.Internal |
|
|
|
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) |
|
|
|
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
call.StartDuplexStreaming(ReceivedStatusOnClientCallback, metadataArray, details.Options.Flags); |
|
|
|
call.StartDuplexStreaming(ReceivedStatusOnClientCallback, metadataArray, details.Options.Flags); |
|
|
|
|
|
|
|
callStartedOk = true; |
|
|
|
} |
|
|
|
} |
|
|
|
call.StartReceiveInitialMetadata(ReceivedResponseHeadersCallback); |
|
|
|
call.StartReceiveInitialMetadata(ReceivedResponseHeadersCallback); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (!callStartedOk) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
OnFailedToStartCallLocked(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
throw; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// <summary> |
|
|
@ -326,8 +399,12 @@ namespace Grpc.Core.Internal |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected override void OnAfterReleaseResourcesLocked() |
|
|
|
protected override void OnAfterReleaseResourcesLocked() |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (registeredWithChannel) |
|
|
|
{ |
|
|
|
{ |
|
|
|
details.Channel.RemoveCallReference(this); |
|
|
|
details.Channel.RemoveCallReference(this); |
|
|
|
|
|
|
|
registeredWithChannel = false; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected override void OnAfterReleaseResourcesUnlocked() |
|
|
|
protected override void OnAfterReleaseResourcesUnlocked() |
|
|
@ -394,10 +471,27 @@ namespace Grpc.Core.Internal |
|
|
|
var call = CreateNativeCall(cq); |
|
|
|
var call = CreateNativeCall(cq); |
|
|
|
|
|
|
|
|
|
|
|
details.Channel.AddCallReference(this); |
|
|
|
details.Channel.AddCallReference(this); |
|
|
|
|
|
|
|
registeredWithChannel = true; |
|
|
|
InitializeInternal(call); |
|
|
|
InitializeInternal(call); |
|
|
|
|
|
|
|
|
|
|
|
RegisterCancellationCallback(); |
|
|
|
RegisterCancellationCallback(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void OnFailedToStartCallLocked() |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
ReleaseResources(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We need to execute the hook that disposes the cancellation token |
|
|
|
|
|
|
|
// registration, but it cannot be done from under a lock. |
|
|
|
|
|
|
|
// To make things simple, we just schedule the unregistering |
|
|
|
|
|
|
|
// on a threadpool. |
|
|
|
|
|
|
|
// - Once the native call is disposed, the Cancel() calls are ignored anyway |
|
|
|
|
|
|
|
// - We don't care about the overhead as OnFailedToStartCallLocked() only happens |
|
|
|
|
|
|
|
// when something goes very bad when initializing a call and that should |
|
|
|
|
|
|
|
// never happen when gRPC is used correctly. |
|
|
|
|
|
|
|
ThreadPool.QueueUserWorkItem((state) => OnAfterReleaseResourcesUnlocked()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private INativeCall CreateNativeCall(CompletionQueueSafeHandle cq) |
|
|
|
private INativeCall CreateNativeCall(CompletionQueueSafeHandle cq) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (injectedNativeCall != null) |
|
|
|
if (injectedNativeCall != null) |
|
|
|