Uploaded image for project: 'C# Driver'
  1. C# Driver
  2. CSHARP-3194

Remove blocking code in StreamExtensionMethods

    • Type: Icon: Improvement Improvement
    • Resolution: Won't Fix
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: 2.11.0
    • Component/s: API
    • Labels:
      None
    • Environment:
      Windows, Linux, Mac
      .net core 3.1

      StreamExtensionMethods.WriteAsync() and StreamExtensionMethods.ReadAsync() often block concurrent reads and writes.

      This can lead to threadpool starvation, which in turn takes down any application using the driver.

       

      The two methods use a System.Threading.Timer for detecting timeout.
      The Timer's constructor will block because of a lock on TimerQueue.Instance in TimerQueueTimer:

      https://github.com/microsoft/referencesource/blob/master/mscorlib/system/threading/timer.cs#L565

       

      Our blocking detector reports these stacktraces:

       

      Blocking method has been invoked and blocked, this can lead to threadpool starvation. at System.Threading.TimerQueueTimer.Change(UInt32 dueTime, UInt32 period) at System.Threading.TimerQueueTimer..ctor(TimerCallback timerCallback, Object state, UInt32 dueTime, UInt32 period, Boolean flowExecutionContext) at System.Threading.Timer.TimerSetup(TimerCallback callback, Object state, UInt32 dueTime, UInt32 period, Boolean flowExecutionContext) at System.Threading.Timer..ctor(TimerCallback callback, Object state, TimeSpan dueTime, TimeSpan period) at MongoDB.Driver.Core.Misc.StreamExtensionMethods.WriteAsync(Stream stream, Byte[] buffer, Int32 offset, Int32 count, TimeSpan timeout, CancellationToken cancellationToken)

       

      Blocking method has been invoked and blocked, this can lead to threadpool starvation. at System.Threading.TimerQueueTimer.Change(UInt32 dueTime, UInt32 period) at System.Threading.TimerQueueTimer..ctor(TimerCallback timerCallback, Object state, UInt32 dueTime, UInt32 period, Boolean flowExecutionContext) at System.Threading.Timer.TimerSetup(TimerCallback callback, Object state, UInt32 dueTime, UInt32 period, Boolean flowExecutionContext) at System.Threading.Timer..ctor(TimerCallback callback, Object state, TimeSpan dueTime, TimeSpan period) at MongoDB.Driver.Core.Misc.StreamExtensionMethods.ReadAsync(Stream stream, Byte[] buffer, Int32 offset, Int32 count, TimeSpan timeout, CancellationToken cancellationToken)

       

      An alternative pattern to handle timeouts (parameters stripped for brevity):

       

      public static async Task<int> ReadAsync(..., CancellationToken cancellationToken)

      {

        var readTask = DoReadAsync(..., cancellationToken);

        var timeoutTask = Task.Delay(timeout, cancellationToken);

        if (await Task.WhenAny(readTask, timeoutTask) == timeoutTask)

       

      {     // Handle cancellation of read (and close stream)     // Perhaps throw exception   }

        else

       

      {     return readTask.Result;   }

      }

            Assignee:
            robert@mongodb.com Robert Stam
            Reporter:
            loftum@gmail.com Hans Olav Loftum
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: