Deadlock when calling LoadIntoBufferAsync in DelegatingHandler

1k Views Asked by At

To only zip / deflate WebAPI responses bigger than a given size limit, I have to find out the size of the content to be returned. However the following line:

 response.Content.LoadIntoBufferAsync()

seems to cause a deadlock later in the API response-cycle. The size is determined correctly and the handler executes fine, but afterwards the request is pending forever.

This is the SendAsync-method of my DelegatingHandler.

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith(
            task =>
            {
                HttpResponseMessage response = task.Result;

                if (response.RequestMessage != null && response.RequestMessage.Headers.AcceptEncoding != null && response.RequestMessage.Headers.AcceptEncoding.Count > 0)
                {
                    if (response.Content != null)
                    {
                        response.Content.LoadIntoBufferAsync(); // when I remove this line the request finishes, but then ContentLength = 0

                        if (response.Content.Headers.ContentLength > 4000)
                        {
                            string encodingType = GetEncodingType(response.RequestMessage.Headers.AcceptEncoding); // use deflate if possible
                            if (encodingType == "deflate" || encodingType == "gzip")
                            {
                                response.Content = new CompressedContent(response.Content, encodingType);
                            }
                        }
                    }
                }

                return response;
            }, cancellationToken);
    }

I tried different combinations with Wait(..), ConfigureAwait(..) and ContinueWith(..). With the async / await syntax I had the same problem.

EDIT: SerializeToStreamAsync of Compressor (responsible for compression the content stream):

protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        Stream compressedStream = null;

        if (m_EncodingType == "gzip")
        {
            compressedStream = new GZipStream(stream, CompressionMode.Compress, true);
        }
        else if (m_EncodingType == "deflate")
        {
            compressedStream = new DeflateStream(stream, CompressionMode.Compress, true);
        }

        return m_OriginalContent.CopyToAsync(compressedStream).ContinueWith(task =>
        {
            if (compressedStream != null)
            {
                compressedStream.Dispose();
            }
        });
    }

=> Apperently the pipeline is broken when this is called after:

response.Content.LoadIntoBufferAsync()

Calling either one of the two alone works, so their must be a problem in reading / writing the content stream.

1

There are 1 best solutions below

1
On

I was trying to do exactly the same thing. It appears that you are missing await in the following statement:

await response.Content.LoadIntoBufferAsync();

I found that the above has nothing to do with the deadlock. The problem comes when you try to get the content length using:

response.Content.Headers.ContentLength

and subsequently try to access the Header, for example:

    private void AddHeaders()
    {
        foreach (var header in content.Headers)
        {
            Headers.TryAddWithoutValidation(header.Key, header.Value);
        }
        Headers.ContentEncoding.Add(compressor.EncodingType);
    }

To fix this you need to add the header selectivly rather than using foreach loop. For example:

    private void AddHeaders()
    {
        //foreach (var header in content.Headers)
        //{
        //    Headers.TryAddWithoutValidation(header.Key, header.Value);
        //}
        Headers.ContentType = new MediaTypeHeaderValue("application/json");
        Headers.ContentEncoding.Add(compressor.EncodingType);
    }

Other option to use the compression selectively is to use action filters. You could find more info about it in the following post:

http://blog.developers.ba/asp-net-web-api-gzip-compression-actionfilter/