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

Commit 769e519

Browse files
committed
Complete WriteAsync Tasks early when there are less than 64KB buffered
1 parent bf728cb commit 769e519

File tree

1 file changed

+69
-30
lines changed

1 file changed

+69
-30
lines changed

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

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,28 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
1212
public class SocketOutput : ISocketOutput
1313
{
1414
private const int _maxPendingWrites = 3;
15+
private const int _maxBytesBufferedBeforeThrottling = 65536 / 8;
1516

1617
private readonly KestrelThread _thread;
1718
private readonly UvStreamHandle _socket;
1819

19-
private WriteContext _nextWriteContext;
20+
// This locks all access to to all the below
21+
private readonly object _lockObj = new object();
2022

2123
// The number of write operations that have been scheduled so far
2224
// but have not completed.
23-
private int _writesSending = 0;
25+
private int _writesPending = 0;
2426

25-
// This locks all access to _nextWriteContext and _writesSending
26-
private readonly object _lockObj = new object();
27+
private int _numBytesBuffered = 0;
28+
private Exception _lastWriteError;
29+
private WriteContext _nextWriteContext;
30+
private readonly Queue<CallbackContext> _callbacksPending;
2731

2832
public SocketOutput(KestrelThread thread, UvStreamHandle socket)
2933
{
3034
_thread = thread;
3135
_socket = socket;
36+
_callbacksPending = new Queue<CallbackContext>();
3237
}
3338

3439
public void Write(ArraySegment<byte> buffer, Action<Exception, object> callback, object state)
@@ -40,11 +45,16 @@ public void Write(ArraySegment<byte> buffer, Action<Exception, object> callback,
4045

4146
KestrelTrace.Log.ConnectionWrite(0, buffer.Count);
4247

43-
var context = new WriteOperation
48+
var writeOp = new WriteOperation
49+
{
50+
Buffer = buffer
51+
};
52+
53+
var callbackContext = new CallbackContext
4454
{
45-
Buffer = buffer,
4655
Callback = callback,
47-
State = state
56+
State = state,
57+
BytesToWrite = buffer.Count
4858
};
4959

5060
lock (_lockObj)
@@ -54,12 +64,26 @@ public void Write(ArraySegment<byte> buffer, Action<Exception, object> callback,
5464
_nextWriteContext = new WriteContext(this);
5565
}
5666

57-
_nextWriteContext.Operations.Add(context);
67+
_nextWriteContext.Operations.Enqueue(writeOp);
68+
_numBytesBuffered += buffer.Count;
69+
70+
// Complete the write task immediately if all previous write tasks have been completed,
71+
// the buffers haven't grown too large, and the last write to the socket succeeded.
72+
if (_lastWriteError == null &&
73+
_callbacksPending.Count == 0 &&
74+
_numBytesBuffered < _maxBytesBufferedBeforeThrottling)
75+
{
76+
TriggerCallback(callbackContext);
77+
}
78+
else
79+
{
80+
_callbacksPending.Enqueue(callbackContext);
81+
}
5882

59-
if (_writesSending < _maxPendingWrites)
83+
if (_writesPending < _maxPendingWrites)
6084
{
6185
ScheduleWrite();
62-
_writesSending++;
86+
_writesPending++;
6387
}
6488
}
6589
}
@@ -87,7 +111,7 @@ private void WriteAllPending()
87111
}
88112
else
89113
{
90-
_writesSending--;
114+
_writesPending--;
91115
return;
92116
}
93117
}
@@ -115,51 +139,66 @@ private void WriteAllPending()
115139
{
116140
// Lock instead of using Interlocked.Decrement so _writesSending
117141
// doesn't change in the middle of executing other synchronized code.
118-
_writesSending--;
142+
_writesPending--;
119143
}
120144

121145
throw;
122146
}
123147
}
124148

125149
// This is called on the libuv event loop
126-
private void OnWriteCompleted(List<WriteOperation> completedWrites, UvWriteReq req, int status, Exception error)
150+
private void OnWriteCompleted(Queue<WriteOperation> completedWrites, UvWriteReq req, int status, Exception error)
127151
{
128152
lock (_lockObj)
129153
{
154+
_lastWriteError = error;
155+
130156
if (_nextWriteContext != null)
131157
{
132158
ScheduleWrite();
133159
}
134160
else
135161
{
136-
_writesSending--;
162+
_writesPending--;
163+
}
164+
165+
foreach (var writeOp in completedWrites)
166+
{
167+
_numBytesBuffered -= writeOp.Buffer.Count;
168+
}
169+
170+
var bytesLeftToBuffer = _maxBytesBufferedBeforeThrottling - _numBytesBuffered;
171+
while (_callbacksPending.Count > 0 && _callbacksPending.Peek().BytesToWrite < bytesLeftToBuffer)
172+
{
173+
var context = _callbacksPending.Dequeue();
174+
TriggerCallback(context);
137175
}
138176
}
139177

140178
req.Dispose();
179+
}
141180

142-
foreach (var writeOp in completedWrites)
181+
private void TriggerCallback(CallbackContext context)
182+
{
183+
context.Error = _lastWriteError;
184+
ThreadPool.QueueUserWorkItem(obj =>
143185
{
144-
KestrelTrace.Log.ConnectionWriteCallback(0, status);
145-
//NOTE: pool this?
146-
147-
// Get off the event loop before calling user code!
148-
writeOp.Error = error;
149-
ThreadPool.QueueUserWorkItem(obj =>
150-
{
151-
var op = (WriteOperation)obj;
152-
op.Callback(op.Error, op.State);
153-
}, writeOp);
154-
}
186+
var c = (CallbackContext)obj;
187+
c.Callback(c.Error, c.State);
188+
}, context);
155189
}
156190

157-
private class WriteOperation
191+
private class CallbackContext
158192
{
159-
public ArraySegment<byte> Buffer;
160193
public Exception Error;
161194
public Action<Exception, object> Callback;
162195
public object State;
196+
public int BytesToWrite;
197+
}
198+
199+
private class WriteOperation
200+
{
201+
public ArraySegment<byte> Buffer;
163202
}
164203

165204
private class WriteContext
@@ -171,13 +210,13 @@ public WriteContext(SocketOutput self)
171210
WriteReq = new UvWriteReq();
172211
WriteReq.Init(self._thread.Loop);
173212

174-
Operations = new List<WriteOperation>();
213+
Operations = new Queue<WriteOperation>();
175214
}
176215

177216
public SocketOutput Self;
178217

179218
public UvWriteReq WriteReq;
180-
public List<WriteOperation> Operations;
219+
public Queue<WriteOperation> Operations;
181220
}
182221
}
183222
}

0 commit comments

Comments
 (0)