Cysharp / YetAnotherHttpHandler

YetAnotherHttpHandler brings the power of HTTP/2 (and gRPC) to Unity and .NET Standard.
MIT License
350 stars 31 forks source link

Different behavior when canceling a token #88

Closed mo3ayka closed 2 months ago

mo3ayka commented 2 months ago

Hey @mayuki

We encountered another issue after updating YetAnotherHttpHandler. When canceling a token, an exception occurs on the client with different contents, depending on the environment

This is a client-side code:

private async UniTask UpdateCameraPreviewModeAsync(CancellationToken cancellationToken, CameraModeParams nextParams)
{
    try
    {
        var parameters = new CameraPreviewParametersDto
        {
            ScanMode = nextParams.CurrentScanMode,
            CameraPositionType = nextParams.CurrentCameraPositionType
        };
        await grpcClientService.Settings_UpdateCameraPreviewModeAsync(parameters, cancellationToken);
    }
    catch (RpcException exception) when (exception.StatusCode == StatusCode.Cancelled)
    {
        logger.LogTrace("Update of camera preview mode was canceled");
    }
    catch (RpcException exception)
    {
        logger.LogErrorWithException("Cannot update the camera preview mode", exception);
    }
    catch (Exception exception)
    {
        logger.LogErrorWithException("Error when trying to update the camera preview mode", exception);
    }
}

And a server-side code:

public async UnaryResult<Nil> Settings_UpdateCameraPreviewModeAsync(CameraPreviewParametersDto previewParametersDto)
{
    var request = new UpdateCameraPreviewCommand(previewParametersDto);
    await mediator.Send(request, Context.CallContext.CancellationToken);
    return Nil.Default;
}

The error we get when canceling the token on the client if we test through Unity Editor: unityEditorException.txt

The error we get when canceling the token on the client if we test the application on Android: androidException.txt

The main difference is that on Android, the TaskCanceledExceptio turns into an HttpRequestException and we receive the status code not Grpc.Core.StatusCode.Cancelled, but Grpc.Core.StatusCode.Internal Is this behavior expected? Do we need to handle this behavior for Android additionally?

Also, for the sake of the experiment, I tried to change the server code and throw the exception myself. In this case, both in Unity editor and on Android, we receive the status Grpc.Core.StatusCode.Cancelled:

public async UnaryResult<Nil> Settings_UpdateCameraPreviewModeAsync(CameraPreviewParametersDto previewParametersDto)
{
    throw new TaskCanceledException();
    var request = new UpdateCameraPreviewCommand(previewParametersDto);
    await mediator.Send(request, Context.CallContext.CancellationToken);
    return Nil.Default;
}

For easier testing on the client, I used a token with cancellation after 100 milliseconds:

private async UniTask UpdateCameraPreviewModeAsync(CancellationToken cancellationToken, CameraModeParams nextParams)
{
    var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));

    try
    {
        var parameters = new CameraPreviewParametersDto
        {
            ScanMode = nextParams.CurrentScanMode,
            CameraPositionType = nextParams.CurrentCameraPositionType
        };
        await grpcClientService.Settings_UpdateCameraPreviewModeAsync(parameters, cts.Token);
    }
    ...
}
mayuki commented 2 months ago

Which version of YetAnotherHttpHandler and Unity is causing this problem?

VladimirRudt commented 2 months ago

@mayuki We checked that using latest 1.5.1 version of YetAnotherHttpHandler and Unity 2022.3.29f1

mayuki commented 2 months ago

Thank you for the additional information. How are you passing CancellationToken in grpcClientService.Settings_UpdateCameraPreviewModeAsync?

Normally, CancellationToken is not propagated from the client to the remote, so I'm curious about how it is handled.

mayuki commented 2 months ago

I was able to reproduce a similar problem, so I will continue to investigate.

VladimirRudt commented 2 months ago

@mayuki Oops, sorry, we forgot to describe this aspect. Actually our grpcClientService is a wrapper around the generated client. Long story short, we just call the WithCancellationToken(cancellationToken) method of the IService interface under the hood.

mayuki commented 2 months ago

We have released 1.5.2, which fixes the cancellation behavior in IL2CPP. Please try this version.

mo3ayka commented 2 months ago

@mayuki You are great, a very quick fix! Thanks!

VladimirRudt commented 2 months ago

You rock @mayuki! ❤️