Kelaparan ThreadPool HttpClient GetAsync

Kami memiliki tumpukan backend berorientasi layanan mikro. Semua layanan mikro dibangun di atas Nancy dan terdaftar sebagai layanan windows dengan topshelf.

Salah satu layanan, yang menangani sebagian besar lalu lintas (~5000 req/s), mulai mengalami masalah kelaparan threadpool di 3 dari 8 server.

Ini adalah pengecualian yang kami dapatkan ketika mencapai titik akhir tertentu:

System.InvalidOperationException: There were not enough free threads in the ThreadPool to complete the operation.
   at System.Net.HttpWebRequest.BeginGetResponse(AsyncCallback callback, Object state)
   at System.Net.Http.HttpClientHandler.StartGettingResponse(RequestState state)
   at System.Net.Http.HttpClientHandler.StartRequest(Object obj)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at RandomNamedClient.<GetProductBySkuAsync>d__20.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ProductService.<GetBySkuAsync>d__3.MoveNext() in ...\ProductService.cs:line 34
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ProductModule.<>c__DisplayClass15.<<.ctor>b__b>d__1d.MoveNext() in ...\ProductModule.cs:line 32

Titik akhir ini memanggil layanan lain -yang berada di luar domain tim saya- untuk mendapatkan data produk. Implementasinya sebagai berikut:

Get["/product/sku/{sku}", true] = async (parameters, ctx) =>
{
    string sku = parameters.sku;
    var product = await productService.GetBySkuAsync(sku);
    return Response.AsJson(new ProductRepresentation(product));
};

ProductService.GetBySkuAsync(string sku) implementasi:

public async Task<Product> GetBySkuAsync(string sku)
{
    var productDto = await randomNamedClient.GetProductBySkuAsync(sku);
    if (productDto == null)
    {
        throw new ProductDtoNotFoundException("sku", sku);
    }

    var variantDto = productDto.VariantList.FirstOrDefault(v => v.Sku == sku);

    if (variantDto == null)
    {
        throw new ProductVariantDtoNotFoundException("sku", sku);
    }

    return MapVariantDtoToProduct(variantDto, productDto);
}

RandomNamedClient.GetProductBySkuAsync(string sku) implementasi (dari paket internal):

public async Task<ProductDto> GetProductBySkuAsync(string sku)
{
  HttpResponseMessage result = await this._serviceClient.GetAsync("Product?Sku=" + sku);
  return result == null || result.StatusCode != HttpStatusCode.OK ? (ProductDto) null : this.Decompress<ProductDto>(result);
}

RandomNamedClient.Decompress<T>(HttpResponseMessage response) implementasi:

private T Decompress<T>(HttpResponseMessage response)
{
  if (!response.Content.Headers.ContentEncoding.Contains("gzip"))
    return HttpContentExtensions.ReadAsAsync<T>(response.Content).Result;
  using (GZipStream gzipStream = new GZipStream((Stream) new MemoryStream(response.Content.ReadAsByteArrayAsync().Result), CompressionMode.Decompress))
  {
    byte[] buffer = new byte[8192];
    using (MemoryStream memoryStream = new MemoryStream())
    {
      int count;
      do
      {
        count = gzipStream.Read(buffer, 0, 8192);
        if (count > 0)
          memoryStream.Write(buffer, 0, count);
      }
      while (count > 0);
      return JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(memoryStream.ToArray()));
    }
  }
}

Semua layanan kami dibuat sebagai Rilis/32-bit. Kami tidak mengubah apa pun tentang penggunaan threadpool.


person onatm    schedule 09.03.2016    source sumber


Jawaban (1)


Masalah terbesar yang saya lihat dengan kode ini adalah metode Decompress<T> yang memblokir operasi async menggunakan Task.Result. Hal ini berpotensi menghentikan pengambilan thread yang sedang memproses permintaan ke threadpool, atau bahkan lebih buruk lagi menyebabkan kebuntuan dalam kode Anda (inilah tepatnya alasan Anda tidak boleh memblokir kode asinkron). Saya tidak yakin apakah Anda pernah melihat permintaan tersebut diproses secara menyeluruh, tetapi apakah NancyFX menangani penyusunan konteks sinkronisasi untuk Anda (sepertinya memang terjadi) yang mungkin menjadi penyebab utama kelaparan threadpools.

Anda dapat mengubahnya dengan membuat semua pemrosesan IO berfungsi di dalam metode tersebut async juga, dan memanfaatkan API asinkron alami yang sudah diekspos oleh kelas-kelas tersebut. Alternatifnya, dan saya sangat tidak menyarankan melakukan ini, Anda dapat menggunakan ConfigureAwait(false) di mana saja.

(Catatan tambahan - Anda dapat menyederhanakan kode Anda dengan menggunakan Stream.CopyToAsync())

Implementasi async yang tepat akan terlihat seperti ini:

private async Task<T> DecompressAsync<T>(HttpResponseMessage response)
{
    if (!response.Content.Headers.ContentEncoding.Contains("gzip"))
        return await response.Content.ReadAsAsync<T>();

    const int bufferSize = 8192;        
    using (GZipStream gzipStream = new GZipStream(
                                   new MemoryStream(
                                        await response.Content.ReadAsByteArrayAsync()), 
                                        CompressionMode.Decompress))
    using (MemoryStream memoryStream = new MemoryStream())
    {
        await gzipStream.CopyToAsync(memoryStream, bufferSize);
        return JsonConvert.DeserializeObject<T>(
                    Encoding.UTF8.GetString(memoryStream.ToArray()));
    }
}
person Yuval Itzchakov    schedule 09.03.2016
comment
terima kasih atas tanggapan rinci Anda. Saya curiga dekompresi menghalangi kami dalam eksekusi. Saya akan memberi tahu tim yang memelihara perpustakaan klien dan saya mungkin menerima ini sebagai jawaban - person onatm; 09.03.2016
comment
@onatm Semoga itu bisa membantu! - person Yuval Itzchakov; 09.03.2016
comment
@onatm Senang mendengarnya. - person Yuval Itzchakov; 14.03.2016