Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 47d7f73

Browse files
committed
Fix SocketOutput so that it can now complete large writes
- Complete in this context means that the callback gets invoked. - Previously, calls to write would never complete if the buffer contained more than 64 KB (_maxBytesPreCompleted). - This is tested by SocketOutputTests.CanWrite1MB.
1 parent cce9d8f commit 47d7f73

File tree

1 file changed

+26
-8
lines changed

1 file changed

+26
-8
lines changed

src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Threading;
8+
using System.Diagnostics;
89

910
namespace Microsoft.AspNet.Server.Kestrel.Http
1011
{
1112
public class SocketOutput : ISocketOutput
1213
{
1314
private const int _maxPendingWrites = 3;
14-
private const int _maxBytesBufferedBeforeThrottling = 65536;
15+
private const int _maxBytesPreCompleted = 65536;
1516

1617
private readonly KestrelThread _thread;
1718
private readonly UvStreamHandle _socket;
@@ -23,7 +24,7 @@ public class SocketOutput : ISocketOutput
2324
// but have not completed.
2425
private int _writesPending = 0;
2526

26-
private int _numBytesBuffered = 0;
27+
private int _numBytesPreCompleted = 0;
2728
private Exception _lastWriteError;
2829
private WriteContext _nextWriteContext;
2930
private readonly Queue<CallbackContext> _callbacksPending;
@@ -54,14 +55,17 @@ public void Write(ArraySegment<byte> buffer, Action<Exception, object> callback,
5455
}
5556

5657
_nextWriteContext.Buffers.Enqueue(buffer);
57-
_numBytesBuffered += buffer.Count;
5858

5959
// Complete the write task immediately if all previous write tasks have been completed,
6060
// the buffers haven't grown too large, and the last write to the socket succeeded.
6161
triggerCallbackNow = _lastWriteError == null &&
6262
_callbacksPending.Count == 0 &&
63-
_numBytesBuffered <= _maxBytesBufferedBeforeThrottling;
64-
if (!triggerCallbackNow)
63+
_numBytesPreCompleted + buffer.Count <= _maxBytesPreCompleted;
64+
if (triggerCallbackNow)
65+
{
66+
_numBytesPreCompleted += buffer.Count;
67+
}
68+
else
6569
{
6670
_callbacksPending.Enqueue(new CallbackContext
6771
{
@@ -78,6 +82,7 @@ public void Write(ArraySegment<byte> buffer, Action<Exception, object> callback,
7882
}
7983
}
8084

85+
// Make sure we call user code outside of the lock.
8186
if (triggerCallbackNow)
8287
{
8388
callback(null, state);
@@ -164,15 +169,28 @@ private void OnWriteCompleted(Queue<ArraySegment<byte>> writtenBuffers, UvWriteR
164169

165170
foreach (var writeBuffer in writtenBuffers)
166171
{
167-
_numBytesBuffered -= writeBuffer.Count;
172+
// _numBytesPreCompleted can temporarily go negative in the event there are
173+
// completed writes that we haven't triggered callbacks for yet.
174+
_numBytesPreCompleted -= writeBuffer.Count;
168175
}
169176

170-
var bytesLeftToBuffer = _maxBytesBufferedBeforeThrottling - _numBytesBuffered;
177+
178+
// bytesLeftToBuffer can be greater than _maxBytesPreCompleted
179+
// This allows large writes to complete once they've actually finished.
180+
var bytesLeftToBuffer = _maxBytesPreCompleted - _numBytesPreCompleted;
171181
while (_callbacksPending.Count > 0 &&
172182
_callbacksPending.Peek().BytesToWrite <= bytesLeftToBuffer)
173183
{
174-
TriggerCallback(_callbacksPending.Dequeue());
184+
var callbackContext = _callbacksPending.Dequeue();
185+
186+
_numBytesPreCompleted += callbackContext.BytesToWrite;
187+
188+
TriggerCallback(callbackContext);
175189
}
190+
191+
// Now that the while loop has completed the following invariants should hold true:
192+
Trace.Assert(_numBytesPreCompleted >= 0);
193+
Trace.Assert(_numBytesPreCompleted <= _maxBytesPreCompleted);
176194
}
177195

178196
req.Dispose();

0 commit comments

Comments
 (0)