GithubHelp home page GithubHelp logo

doctorseus / grpc-dotnet-unity Goto Github PK

View Code? Open in Web Editor NEW
72.0 72.0 18.0 547 KB

Custom HttpClientHandler to enable grpc-dotnet client in Unity via HTTP2.

License: MIT License

C# 86.54% Java 13.46%

grpc-dotnet-unity's People

Contributors

doctorseus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

grpc-dotnet-unity's Issues

Trailing headers/metadata support is not implemented

Metadata headers are not fully supported yet. As we currently only allow well-known trailing headers any custom headers (gRPC metadata) will not arrive on the client side. This needs fixing and test cases.

Handling disconnect from the server

When I make a plain C# console app that acts as a grpc client and I call a grpc method that returns a stream then await call.ResponseStream.MoveNext() throws the exception bellow when I kill the server:

Unhandled exception. Grpc.Core.RpcException: Status(StatusCode="Unavailable", Detail="Error reading next message. IOException: The request was aborted. IOException: Unable to read
data from the transport connection: An existing connection was forcibly closed by the remote host.. SocketException: An existing connection was forcibly closed by the remote host."
, DebugException="System.IO.IOException: The request was aborted.
 ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..
 ---> System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host.

But when I use GRPCBestHttpHandler in Unity no exception is thrown upon killing the server and the await just keeps waiting. I don't see any obvious way to detect disconnects on client side without this?

headers are not making their way back to the Grpc library

When doing a simple unary call, I can see the server sending the response correctly. I can also debug this library and see that the grpc-status is in the trailing header.

That is coming in to GRPCBestHttpHandler, and the bestRequest.OnHeadersReceived gets the trailing headers and these are set with grpcResponseMessage.TrailingHeaders.

However, Grpc library is getting:

Exception: Grpc.Core.RpcException: Status(StatusCode="Cancelled", Detail="No grpc-status found on response.")

Full stack from GRPC is:

Exception: Grpc.Core.RpcException: Status(StatusCode="Cancelled", Detail="No grpc-status found on response.")
  at Grpc.Net.Client.Internal.HttpClientCallInvoker.BlockingUnaryCall[TRequest,TResponse] (Grpc.Core.Method`2[TRequest,TResponse] method, System.String host, Grpc.Core.CallOptions options, TRequest request) [0x00016] in <d97e013995eb4d27a7e6fafbdd95d13e>:0 
  at Grpc.Core.Interceptors.InterceptingCallInvoker.<BlockingUnaryCall>b__3_0[TRequest,TResponse] (TRequest req, Grpc.Core.Interceptors.ClientInterceptorContext`2[TRequest,TResponse] ctx) [0x0001b] in <18da7575174844b1b40d2cd0a1e5ca02>:0 
  at Grpc.Core.ClientBase+ClientBaseConfiguration+ClientBaseConfigurationInterceptor.BlockingUnaryCall[TRequest,TResponse] (TRequest request, Grpc.Core.Interceptors.ClientInterceptorContext`2[TRequest,TResponse] context, Grpc.Core.Interceptors.Interceptor+BlockingUnaryCallContinuation`2[TRequest,TResponse] continuation) [0x00009] in <18da7575174844b1b40d2cd0a1e5ca02>:0 
  at Grpc.Core.Interceptors.InterceptingCallInvoker.BlockingUnaryCall[TRequest,TResponse] (Grpc.Core.Method`2[TRequest,TResponse] method, System.String host, Grpc.Core.CallOptions options, TRequest request) [0x00010] in <18da7575174844b1b40d2cd0a1e5ca02>:0 

webgl platform build fail.

error:

Assets/GRPC.NET/Scripts/GRPCBestHttpHandler.cs(10,28): error CS0234: The type or namespace name 'HTTP2' does not exist in the namespace 'BestHTTP.Connections' (are you missing an assembly reference?)

IAsyncStreamReader.MoveNext() in bidirectional stream can not be cancelled by a CancellationToken

in receive thread:

IAsyncStreamReader<ServerMsg> responseStream;
CancellationToken cancelToken; // cancelToken = source.Token
try {
   while (await responseStream.MoveNext(cancelToken)) {
      ...
   }
} catch(Exception e) {
   ...
}

in other thread

CancellationTokenSource source;
source.Cancel();

after call Cancel(), the receive thread should get an exception and quit the while loop. But actually nothing happened.

`System.IO.IOException` throws 2 times when Unity Editor stops playing

Hello!

First of all, thank you very much for building this repository. It serves a really good reference for how to bridge the gap between gRPC and Unity via BestHTTP2 layer.

However, I'm seeing a weird behavior in the Unity Editor which is that .NET backend throws 2 System.IO.IOExceptions right after I stop playing.

Here, I'm pasting the call-stacks first, then I'll try to explain repro steps better:

fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unexpected exception in TimingPipeFlusher.FlushAsync.
      System.IO.IOException: The encryption operation failed, see inner exception.
       ---> System.ComponentModel.Win32Exception (14): Bad address
         --- End of inner exception stack trace ---
         at System.Net.Security.SslStream.WriteSingleChunk[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
         at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at System.IO.Pipelines.StreamPipeWriter.FlushAsyncInternal(Boolean writeToStream, ReadOnlyMemory`1 data, CancellationToken cancellationToken)
         at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
         at System.IO.Pipelines.StreamPipeWriter.FlushAsyncInternal(Boolean writeToStream, ReadOnlyMemory`1 data, CancellationToken cancellationToken)
         at System.IO.Pipelines.StreamPipeWriter.FlushAsync(CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeWriterHelpers.ConcurrentPipeWriter.FlushAsync(CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeWriterHelpers.TimingPipeFlusher.FlushAsync(MinDataRate minRate, Int64 count, IHttpOutputAborter outputAborter, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2FrameWriter.WriteGoAwayAsync(Int32 lastStreamId, Http2ErrorCode errorCode)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Connection.ProcessRequestsAsync[TContext](IHttpApplication`1 application)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)
         at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecuteFromThreadPool(Thread threadPoolThread)
         at System.Threading.ThreadPoolWorkQueue.Dispatch()
         at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
         at System.Threading.Thread.StartCallback()
      --- End of stack trace from previous location ---
         at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at System.IO.Pipelines.StreamPipeWriter.FlushAsyncInternal(Boolean writeToStream, ReadOnlyMemory`1 data, CancellationToken cancellationToken)
         at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeWriterHelpers.ConcurrentPipeWriter.FlushAsyncAwaited(ValueTask`1 flushTask, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeWriterHelpers.TimingPipeFlusher.TimeFlushAsyncAwaited(ValueTask`1 pipeFlushTask, MinDataRate minRate, IHttpOutputAborter outputAborter, CancellationToken cancellationToken)
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HMROUKQB1N6K.
      System.IO.IOException: The encryption operation failed, see inner exception.
       ---> System.ComponentModel.Win32Exception (14): Bad address
         --- End of inner exception stack trace ---
         at System.Net.Security.SslStream.WriteSingleChunk[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
         at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at System.IO.Pipelines.StreamPipeWriter.FlushAsyncInternal(Boolean writeToStream, ReadOnlyMemory`1 data, CancellationToken cancellationToken)
         at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
         at System.IO.Pipelines.StreamPipeWriter.FlushAsyncInternal(Boolean writeToStream, ReadOnlyMemory`1 data, CancellationToken cancellationToken)
         at System.IO.Pipelines.StreamPipeWriter.CompleteAsync(Exception exception)
         at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
         at System.IO.Pipelines.StreamPipeWriter.CompleteAsync(Exception exception)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.DuplexPipeStreamAdapter`1.DisposeAsync()
         at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.DuplexPipeStreamAdapter`1.DisposeAsync()
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)
         at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext()
         at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(IAsyncStateMachineBox box, Boolean allowInlining)
         at System.Threading.Tasks.Task.RunContinuations(Object continuationObject)
         at System.Threading.Tasks.Task`1.TrySetResult(TResult result)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.SetExistingTaskResult(Task`1 task, TResult result)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.HttpConnection.ProcessRequestsAsync[TContext](IHttpApplication`1 httpApplication)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)
         at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext()
         at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(IAsyncStateMachineBox box, Boolean allowInlining)
         at System.Threading.Tasks.Task.RunContinuations(Object continuationObject)
         at System.Threading.Tasks.Task`1.TrySetResult(TResult result)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.SetExistingTaskResult(Task`1 task, TResult result)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2Connection.ProcessRequestsAsync[TContext](IHttpApplication`1 application)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)
         at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext()
         at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(IAsyncStateMachineBox box, Boolean allowInlining)
         at System.Threading.Tasks.Task.RunContinuations(Object continuationObject)
         at System.Threading.Tasks.Task`1.TrySetResult(TResult result)
         at System.Threading.Tasks.UnwrapPromise`1.TrySetFromTask(Task task, Boolean lookForOce)
         at System.Threading.Tasks.UnwrapPromise`1.InvokeCore(Task completingTask)
         at System.Threading.Tasks.Task.RunContinuations(Object continuationObject)
         at System.Threading.Tasks.Task`1.TrySetResult(TResult result)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.SetExistingTaskResult(Task`1 task, TResult result)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.Http2FrameWriter.WriteToOutputPipe()
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)
         at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
         at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext()
         at System.Threading.ThreadPoolWorkQueue.Dispatch()
         at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
         at System.Threading.Thread.StartCallback()
      --- End of stack trace from previous location ---
         at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at System.IO.Pipelines.StreamPipeWriter.FlushAsyncInternal(Boolean writeToStream, ReadOnlyMemory`1 data, CancellationToken cancellationToken)
         at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
         at System.IO.Pipelines.StreamPipeWriter.CompleteAsync(Exception exception)
         at System.IO.Pipelines.StreamPipeWriter.CompleteAsync(Exception exception)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.DuplexPipeStreamAdapter`1.DisposeAsync()
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()

I have a simple gRPC test code on the Unity side:

public class GrpcTest : MonoBehaviour
{
    private async void Start()
    {
        var grpcChannel = GrpcChannel.ForAddress("https://localhost:8888", new() { HttpHandler = new GrpcClientHttp2Handler() });
        var demoClient = new Demo.DemoClient(grpcChannel);
        var helloResponse = await demoClient.HelloAsync(new()
        {
            Name = "Unity",
            Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
        });

        // ...

The GrpcClientHttp2Handler class is basically GRPCBestHttpHandler from your repo.

What happens is that when I enter playmode, everything works perfectly and I get no errors, no exceptions or whatsoever.
However, when I stop playmode, I immediately see the call-stack above with 2 exceptions thrown.
It does not happen always but it happens quite frequently (most of the time 50/50 chance).
I suspect that gRPC initiates another request or come TCP connection via HTTP2 upon shutdown/disposal, or some stream gets closed prematurely.
But the problem is, I did wait for a full minute after getting the response from the backend for my gRPC request and then stopped playing, exceptions were still thrown.

I wonder if you bumped into this issue and/or can guide me through debugging and potentially bug fixing this for us.

I hope you don't mind me bugging you with this on your experimental repo โ€” thanks in advance! :)

missing TE header

Getting this on the server: NettyServerHandler [WARNING] (grpc-nio-worker) - Expected header TE: trailers, but null is received. This means some intermediate proxy may not support trailers

This results in the client getting: Error: One or more errors occurred. (Status(StatusCode="Cancelled", Details="No grpc-status found on response."))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.