Async batched request not returning

Hello,

I am migrating a web application from ServiceStack 4.5.x to 5.9.0 and I am encountering issues related to batched requests. My main issue with the upgrade to 5.9 is that batched requests my application are now returned as a list of Task which is very strange and different from 4.5.x.

While I believe that the issue is on our side it is difficult to figure out where the issue comes from since this application is heavily customized (custom host, service runner, security, etc). To help debugging I am building a new webapp and porting over the customization to isolate the issue.

I encountered an issue while building this new webapp which prompted this post: async batched requests seems to hang somewhere in ServiceStack’s code. The batched request are sent to my host and I can debug my service however the request never ends and my tests never ends. I tried the same code without any async/await without issues.

You can reproduce the issue with the following code. There are 2 versions of the Hello service: one sync and one async.
If you prefer, you can find the code below in this repo: https://github.com/orck-adrouin/Issues/tree/ServiceStack20200810

Can you tell my what I am missing to make the async batched queries to work without issues?
Thanks!

Service:

    public class MyServices : Service
    {
        public async Task<HelloResponse> Any(HelloAsync request)
        {
            await Task.Delay(10).ConfigureAwait(false); // simulate some async workload

            return new HelloResponse { Result = $"Hello, {request.Name}!" };
        }

        public HelloResponse Any(HelloSync request)
        {

            return new HelloResponse { Result = $"Hello, {request.Name}!" };
        }
    }

Requests:

    [Route("/helloAsync/{Name}", Verbs = "GET")]
    public class HelloAsync : IReturn<HelloResponse>
    {
        public string Name { get; set; }
    }

    [Route("/helloSync/{Name}", Verbs = "GET")]
    public class HelloSync : IReturn<HelloResponse>
    {
        public string Name { get; set; }
    }

    public class HelloResponse
    {
        public string Result { get; set; }
    }

Unit tests:

    public class UnitTest1
    {
        [Test]
        public async Task Async()
        {
            var client = new ServiceStack.JsonServiceClient("http://localhost.fiddler:26311");

            var requests = new List<HelloAsync>
            {
                new HelloAsync { Name = "roger" },
                new HelloAsync { Name = "fred" },
            };

            var response = await client.SendAllAsync<HelloResponse>(requests, CancellationToken.None).ConfigureAwait(false);

        }

        [Test]
        public void Sync()
        {
            var client = new ServiceStack.JsonServiceClient("http://localhost.fiddler:26311");

            var requests = new List<HelloSync>
            {
                new HelloSync { Name = "roger" },
                new HelloSync { Name = "fred" },
            };

            var response = client.SendAll<HelloResponse>(requests);
        }
    }

Fiddler raw request (I don’t have the output since the request never ends):

POST http://localhost:26311/json/reply/HelloAsync[] HTTP/1.1
Accept-Encoding: gzip,deflate,gzip,deflate
Accept: application/json
User-Agent: ServiceStack .NET Client 5.92
Content-Type: application/json
Host: localhost:26311
Content-Length: 34
Expect: 100-continue
Connection: Keep-Alive

[{"Name":"roger"},{"Name":"fred"}]

This was a hard one to track down, which I was able to repro in ASP .NET, ultimately I believe it was due to rewriting some async libraries to use async/await from manual ContinueWith() Task callbacks, which had a knock-on effect that resulted in an never returning async callback.

To minimize async compatibility issues like this in future I’ve ended up rewriting all async callbacks to use async/await and ConfigureAwait() where possible in this commit which resolved this issue.

This change is available from v5.9.3 that’s now available on MyGet.

I was able to confirm that the new version fixes the async issue.

Thanks!

1 Like