Progress on flow control
* Dealing with race conditions * Reworking iterator methods * Shutdown and Close need to be passed throught the write-behind mechanism
This commit is contained in:
parent
4193c92ba7
commit
e5a3bda3a2
|
|
@ -82,15 +82,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
SocketInput.RemoteIntakeFin = true;
|
SocketInput.RemoteIntakeFin = true;
|
||||||
_socket.ReadStop();
|
_socket.ReadStop();
|
||||||
|
Log.ConnectionReadFin(_connectionId);
|
||||||
if (errorDone && error != null)
|
|
||||||
{
|
|
||||||
Log.LogError("Connection.OnRead", error);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.ConnectionReadFin(_connectionId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketInput.SetCompleted(errorDone ? error : null);
|
SocketInput.SetCompleted(errorDone ? error : null);
|
||||||
|
|
@ -122,19 +114,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
_connectionState = ConnectionState.Shutdown;
|
_connectionState = ConnectionState.Shutdown;
|
||||||
|
|
||||||
Log.ConnectionWriteFin(_connectionId);
|
Log.ConnectionWriteFin(_connectionId);
|
||||||
Thread.Post(
|
SocketOutput.End(endType);
|
||||||
_this =>
|
|
||||||
{
|
|
||||||
var shutdown = new UvShutdownReq(_this.Log);
|
|
||||||
shutdown.Init(_this.Thread.Loop);
|
|
||||||
shutdown.Shutdown(_this._socket, (req, status, state2) =>
|
|
||||||
{
|
|
||||||
var __this = (Connection)state2;
|
|
||||||
__this.Log.ConnectionWroteFin(__this._connectionId, status);
|
|
||||||
req.Dispose();
|
|
||||||
}, _this);
|
|
||||||
},
|
|
||||||
this);
|
|
||||||
break;
|
break;
|
||||||
case ProduceEndType.ConnectionKeepAlive:
|
case ProduceEndType.ConnectionKeepAlive:
|
||||||
if (_connectionState != ConnectionState.Open)
|
if (_connectionState != ConnectionState.Open)
|
||||||
|
|
@ -152,13 +132,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
_connectionState = ConnectionState.Disconnected;
|
_connectionState = ConnectionState.Disconnected;
|
||||||
|
|
||||||
Log.ConnectionDisconnect(_connectionId);
|
Log.ConnectionDisconnect(_connectionId);
|
||||||
Thread.Post(
|
SocketOutput.End(endType);
|
||||||
_this =>
|
|
||||||
{
|
|
||||||
Log.ConnectionStop(_this._connectionId);
|
|
||||||
_this._socket.Dispose();
|
|
||||||
},
|
|
||||||
this);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,23 +113,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
await SocketInput;
|
await SocketInput;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var x = 5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!terminated && !TakeMessageHeader2(SocketInput))
|
while (!terminated && !TakeMessageHeaders(SocketInput))
|
||||||
{
|
{
|
||||||
terminated = SocketInput.RemoteIntakeFin;
|
terminated = SocketInput.RemoteIntakeFin;
|
||||||
if (!terminated)
|
if (!terminated)
|
||||||
{
|
{
|
||||||
await SocketInput;
|
await SocketInput;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var x = 5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!terminated)
|
if (!terminated)
|
||||||
|
|
@ -171,6 +163,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
// Connection Terminated!
|
// Connection Terminated!
|
||||||
ConnectionControl.End(ProduceEndType.SocketShutdownSend);
|
ConnectionControl.End(ProduceEndType.SocketShutdownSend);
|
||||||
|
|
||||||
|
// Wait for client to disconnect, or to receive unexpected data
|
||||||
|
await SocketInput;
|
||||||
|
|
||||||
ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -407,13 +403,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
WriteChunkedResponseSuffix();
|
WriteChunkedResponseSuffix();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_keepAlive)
|
ConnectionControl.End(_keepAlive ? ProduceEndType.ConnectionKeepAlive : ProduceEndType.SocketShutdownSend);
|
||||||
{
|
|
||||||
ConnectionControl.End(ProduceEndType.SocketShutdownSend);
|
|
||||||
}
|
|
||||||
|
|
||||||
//NOTE: must finish reading request body
|
|
||||||
ConnectionControl.End(_keepAlive ? ProduceEndType.ConnectionKeepAlive : ProduceEndType.SocketDisconnect);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tuple<ArraySegment<byte>, IDisposable> CreateResponseHeader(
|
private Tuple<ArraySegment<byte>, IDisposable> CreateResponseHeader(
|
||||||
|
|
@ -515,36 +505,51 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
private bool TakeStartLine(SocketInput input)
|
private bool TakeStartLine(SocketInput input)
|
||||||
{
|
{
|
||||||
var begin = input.GetIterator();
|
var scan = input.GetIterator();
|
||||||
if (begin.IsDefault) return false;
|
|
||||||
|
|
||||||
var end = begin.IndexOf(' ');
|
var begin = scan;
|
||||||
var method = begin.GetString(end);
|
if (scan.Seek(' ') == -1)
|
||||||
|
|
||||||
char chFound;
|
|
||||||
begin = end.Add(1);
|
|
||||||
end = begin.IndexOfAny(' ', '?', out chFound);
|
|
||||||
var requestUri = begin.GetString(end);
|
|
||||||
|
|
||||||
begin = end;
|
|
||||||
end = chFound == '?' ? begin.IndexOf(' ') : begin;
|
|
||||||
var queryString = begin.GetString(end);
|
|
||||||
|
|
||||||
begin = end.Add(1);
|
|
||||||
end = begin.IndexOf('\r');
|
|
||||||
var httpVersion = begin.GetString(end);
|
|
||||||
|
|
||||||
end = end.Add(1);
|
|
||||||
if (end.Peek() != '\n')
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
var method = begin.GetString(scan);
|
||||||
|
|
||||||
|
scan.Take();
|
||||||
|
begin = scan;
|
||||||
|
var chFound = scan.Seek(' ', '?');
|
||||||
|
if (chFound == -1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var requestUri = begin.GetString(scan);
|
||||||
|
|
||||||
|
var queryString = "";
|
||||||
|
if (chFound == '?')
|
||||||
|
{
|
||||||
|
begin = scan;
|
||||||
|
if (scan.Seek(' ') != ' ')
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
queryString = begin.GetString(scan);
|
||||||
|
}
|
||||||
|
|
||||||
|
scan.Take();
|
||||||
|
begin = scan;
|
||||||
|
if (scan.Seek('\r') == -1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var httpVersion = begin.GetString(scan);
|
||||||
|
|
||||||
|
scan.Take();
|
||||||
|
if (scan.Take() != '\n') return false;
|
||||||
|
|
||||||
Method = method;
|
Method = method;
|
||||||
RequestUri = requestUri;
|
RequestUri = requestUri;
|
||||||
QueryString = queryString;
|
QueryString = queryString;
|
||||||
HttpVersion = httpVersion;
|
HttpVersion = httpVersion;
|
||||||
input.JumpTo(end.Add(1));
|
input.JumpTo(scan);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -553,150 +558,98 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
return Encoding.UTF8.GetString(range.Array, range.Offset + startIndex, endIndex - startIndex);
|
return Encoding.UTF8.GetString(range.Array, range.Offset + startIndex, endIndex - startIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TakeMessageHeader2(SocketInput baton)
|
private bool TakeMessageHeaders(SocketInput input)
|
||||||
{
|
{
|
||||||
char chFirst;
|
int chFirst;
|
||||||
char chSecond;
|
int chSecond;
|
||||||
var scan = baton.GetIterator();
|
var scan = input.GetIterator();
|
||||||
while (!scan.IsDefault)
|
var consumed = scan;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var beginName = scan;
|
while (!scan.IsEnd)
|
||||||
scan = scan.IndexOfAny(':', '\r', out chFirst);
|
|
||||||
|
|
||||||
var endName = scan;
|
|
||||||
chSecond = scan.MoveNext();
|
|
||||||
|
|
||||||
if (chFirst == '\r' && chSecond == '\n')
|
|
||||||
{
|
{
|
||||||
baton.JumpTo(scan.Add(1));
|
var beginName = scan;
|
||||||
return true;
|
scan.Seek(':', '\r');
|
||||||
}
|
var endName = scan;
|
||||||
if (chFirst == char.MinValue)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (
|
chFirst = scan.Take();
|
||||||
chSecond == ' ' ||
|
var beginValue = scan;
|
||||||
chSecond == '\t' ||
|
chSecond = scan.Take();
|
||||||
chSecond == '\r' ||
|
|
||||||
chSecond == '\n')
|
|
||||||
{
|
|
||||||
chSecond = scan.MoveNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
var beginValue = scan;
|
if (chFirst == -1 || chSecond == -1)
|
||||||
var wrapping = false;
|
|
||||||
while (!scan.IsDefault)
|
|
||||||
{
|
|
||||||
var endValue = scan = scan.IndexOf('\r');
|
|
||||||
chFirst = scan.MoveNext();
|
|
||||||
if (chFirst != '\n')
|
|
||||||
{
|
{
|
||||||
continue;
|
return false;
|
||||||
}
|
}
|
||||||
chSecond = scan.MoveNext();
|
if (chFirst == '\r')
|
||||||
if (chSecond == ' ' || chSecond == '\t')
|
|
||||||
{
|
{
|
||||||
wrapping = true;
|
if (chSecond == '\n')
|
||||||
continue;
|
{
|
||||||
}
|
consumed = scan;
|
||||||
var name = beginName.GetArraySegment(endName);
|
return true;
|
||||||
var value = beginValue.GetString(endValue);
|
}
|
||||||
if (wrapping)
|
throw new Exception("Malformed request");
|
||||||
{
|
|
||||||
value = value.Replace("\r\n", " ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_requestHeaders.Append(name.Array, name.Offset, name.Count, value);
|
while (
|
||||||
break;
|
chSecond == ' ' ||
|
||||||
|
chSecond == '\t' ||
|
||||||
|
chSecond == '\r' ||
|
||||||
|
chSecond == '\n')
|
||||||
|
{
|
||||||
|
beginValue = scan;
|
||||||
|
chSecond = scan.Take();
|
||||||
|
}
|
||||||
|
scan = beginValue;
|
||||||
|
|
||||||
|
var wrapping = false;
|
||||||
|
while (!scan.IsEnd)
|
||||||
|
{
|
||||||
|
if (scan.Seek('\r') == -1)
|
||||||
|
{
|
||||||
|
// no "\r" in sight, burn used bytes and go back to await more data
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endValue = scan;
|
||||||
|
chFirst = scan.Take(); // expecting: /r
|
||||||
|
chSecond = scan.Take(); // expecting: /n
|
||||||
|
|
||||||
|
if (chSecond == '\r')
|
||||||
|
{
|
||||||
|
// special case, "\r\r". move to the 2nd "\r" and try again
|
||||||
|
scan = endValue;
|
||||||
|
scan.Take();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chThird = scan.Peek();
|
||||||
|
if (chThird == ' ' || chThird == '\t')
|
||||||
|
{
|
||||||
|
// special case, "\r\n " or "\r\n\t".
|
||||||
|
// this is considered wrapping"linear whitespace" and is actually part of the header value
|
||||||
|
// continue past this for the next
|
||||||
|
wrapping = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = beginName.GetArraySegment(endName);
|
||||||
|
var value = beginValue.GetString(endValue);
|
||||||
|
if (wrapping)
|
||||||
|
{
|
||||||
|
value = value.Replace("\r\n", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestHeaders.Append(name.Array, name.Offset, name.Count, value);
|
||||||
|
consumed = scan;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TakeMessageHeader(SocketInput baton, out bool endOfHeaders)
|
|
||||||
{
|
|
||||||
var remaining = baton.Buffer;
|
|
||||||
endOfHeaders = false;
|
|
||||||
if (remaining.Count < 2)
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var ch0 = remaining.Array[remaining.Offset];
|
finally
|
||||||
var ch1 = remaining.Array[remaining.Offset + 1];
|
|
||||||
if (ch0 == '\r' && ch1 == '\n')
|
|
||||||
{
|
{
|
||||||
endOfHeaders = true;
|
input.JumpTo(consumed);
|
||||||
baton.Skip(2);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remaining.Count < 3)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var wrappedHeaders = false;
|
|
||||||
var colonIndex = -1;
|
|
||||||
var valueStartIndex = -1;
|
|
||||||
var valueEndIndex = -1;
|
|
||||||
for (var index = 0; index != remaining.Count - 2; ++index)
|
|
||||||
{
|
|
||||||
var ch2 = remaining.Array[remaining.Offset + index + 2];
|
|
||||||
if (ch0 == '\r' &&
|
|
||||||
ch1 == '\n' &&
|
|
||||||
ch2 != ' ' &&
|
|
||||||
ch2 != '\t')
|
|
||||||
{
|
|
||||||
var value = "";
|
|
||||||
if (valueEndIndex != -1)
|
|
||||||
{
|
|
||||||
value = _ascii.GetString(
|
|
||||||
remaining.Array, remaining.Offset + valueStartIndex, valueEndIndex - valueStartIndex);
|
|
||||||
}
|
|
||||||
if (wrappedHeaders)
|
|
||||||
{
|
|
||||||
value = value.Replace("\r\n", " ");
|
|
||||||
}
|
|
||||||
AddRequestHeader(remaining.Array, remaining.Offset, colonIndex, value);
|
|
||||||
baton.Skip(index + 2);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (colonIndex == -1 && ch0 == ':')
|
|
||||||
{
|
|
||||||
colonIndex = index;
|
|
||||||
}
|
|
||||||
else if (colonIndex != -1 &&
|
|
||||||
ch0 != ' ' &&
|
|
||||||
ch0 != '\t' &&
|
|
||||||
ch0 != '\r' &&
|
|
||||||
ch0 != '\n')
|
|
||||||
{
|
|
||||||
if (valueStartIndex == -1)
|
|
||||||
{
|
|
||||||
valueStartIndex = index;
|
|
||||||
}
|
|
||||||
valueEndIndex = index + 1;
|
|
||||||
}
|
|
||||||
else if (!wrappedHeaders &&
|
|
||||||
ch0 == '\r' &&
|
|
||||||
ch1 == '\n' &&
|
|
||||||
(ch2 == ' ' ||
|
|
||||||
ch2 == '\t'))
|
|
||||||
{
|
|
||||||
wrappedHeaders = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ch0 = ch1;
|
|
||||||
ch1 = ch2;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddRequestHeader(byte[] keyBytes, int keyOffset, int keyLength, string value)
|
|
||||||
{
|
|
||||||
_requestHeaders.Append(keyBytes, keyOffset, keyLength, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool StatusCanHaveBody(int statusCode)
|
public bool StatusCanHaveBody(int statusCode)
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
void Write(ArraySegment<byte> buffer, bool immediate = true);
|
void Write(ArraySegment<byte> buffer, bool immediate = true);
|
||||||
Task WriteAsync(ArraySegment<byte> buffer, bool immediate = true, CancellationToken cancellationToken = default(CancellationToken));
|
Task WriteAsync(ArraySegment<byte> buffer, bool immediate = true, CancellationToken cancellationToken = default(CancellationToken));
|
||||||
|
void End(ProduceEndType endType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
|
|
@ -15,6 +13,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
private ConcurrentStack<MemoryPoolBlock2> _blocks = new ConcurrentStack<MemoryPoolBlock2>();
|
private ConcurrentStack<MemoryPoolBlock2> _blocks = new ConcurrentStack<MemoryPoolBlock2>();
|
||||||
private ConcurrentStack<MemoryPoolSlab2> _slabs = new ConcurrentStack<MemoryPoolSlab2>();
|
private ConcurrentStack<MemoryPoolSlab2> _slabs = new ConcurrentStack<MemoryPoolSlab2>();
|
||||||
|
private bool disposedValue = false; // To detect redundant calls
|
||||||
|
|
||||||
public MemoryPoolBlock2 Lease(int minimumSize)
|
public MemoryPoolBlock2 Lease(int minimumSize)
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +26,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
slab: null);
|
slab: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;)
|
while (true)
|
||||||
{
|
{
|
||||||
MemoryPoolBlock2 block;
|
MemoryPoolBlock2 block;
|
||||||
if (_blocks.TryPop(out block))
|
if (_blocks.TryPop(out block))
|
||||||
|
|
@ -66,7 +65,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDisposable Support
|
#region IDisposable Support
|
||||||
private bool disposedValue = false; // To detect redundant calls
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -135,246 +135,284 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
public bool IsDefault => _block == null;
|
public bool IsDefault => _block == null;
|
||||||
|
|
||||||
|
public bool IsEnd
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_block == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (_index < _block.End)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var block = _block.Next; block != null; block = block.Next)
|
||||||
|
{
|
||||||
|
if (block.Start < block.End)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public MemoryPoolBlock2 Block => _block;
|
public MemoryPoolBlock2 Block => _block;
|
||||||
|
|
||||||
public int Index => _index;
|
public int Index => _index;
|
||||||
|
|
||||||
public bool HasAtLeast(int count)
|
public int Take()
|
||||||
{
|
{
|
||||||
var scan = _block;
|
if (_block == null)
|
||||||
var index = _index;
|
|
||||||
while (scan != null)
|
|
||||||
{
|
{
|
||||||
if (count <= scan.End - index)
|
return -1;
|
||||||
{
|
}
|
||||||
return true;
|
else if (_index < _block.End)
|
||||||
}
|
{
|
||||||
count -= scan.End - index;
|
return _block.Array[_index++];
|
||||||
scan = scan.Next;
|
|
||||||
index = scan?.Start ?? 0;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator Add(int count)
|
|
||||||
{
|
|
||||||
var block = _block;
|
var block = _block;
|
||||||
var index = _index;
|
var index = _index;
|
||||||
while (block != null)
|
while (true)
|
||||||
{
|
{
|
||||||
var tailCount = block.End - index;
|
if (index < block.End)
|
||||||
if (count < tailCount)
|
|
||||||
{
|
{
|
||||||
return new Iterator(block, index + count);
|
_block = block;
|
||||||
|
_index = index + 1;
|
||||||
|
return block.Array[index];
|
||||||
}
|
}
|
||||||
count -= tailCount;
|
else if (block.Next == null)
|
||||||
block = block.Next;
|
{
|
||||||
index = block?.Start ?? 0;
|
return -1;
|
||||||
}
|
}
|
||||||
return new Iterator(block, index + count);
|
else
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator CopyTo(byte[] array, int offset, int count, out int actual)
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
var index = _index;
|
|
||||||
var remaining = count;
|
|
||||||
while (block != null && remaining != 0)
|
|
||||||
{
|
|
||||||
var copyLength = Math.Min(remaining, block.End - index);
|
|
||||||
Buffer.BlockCopy(block.Array, index, array, offset, copyLength);
|
|
||||||
index += copyLength;
|
|
||||||
offset += copyLength;
|
|
||||||
remaining -= copyLength;
|
|
||||||
if (index == block.End)
|
|
||||||
{
|
{
|
||||||
block = block.Next;
|
block = block.Next;
|
||||||
index = block?.Start ?? 0;
|
index = block.Start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actual = count - remaining;
|
|
||||||
return new Iterator(block, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public char MoveNext()
|
|
||||||
{
|
|
||||||
var block = _block;
|
|
||||||
var index = _index;
|
|
||||||
while (block != null && index == block.End)
|
|
||||||
{
|
|
||||||
block = block.Next;
|
|
||||||
index = block?.Start ?? 0;
|
|
||||||
}
|
|
||||||
if (block != null)
|
|
||||||
{
|
|
||||||
++index;
|
|
||||||
}
|
|
||||||
while (block != null && index == block.End)
|
|
||||||
{
|
|
||||||
block = block.Next;
|
|
||||||
index = block?.Start ?? 0;
|
|
||||||
}
|
|
||||||
_block = block;
|
|
||||||
_index = index;
|
|
||||||
return block != null ? (char)block.Array[index] : char.MinValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Peek()
|
public int Peek()
|
||||||
{
|
{
|
||||||
while (_block != null)
|
if (_block == null)
|
||||||
{
|
{
|
||||||
if (_index < _block.End)
|
return -1;
|
||||||
{
|
}
|
||||||
return _block.Data.Array[_index];
|
else if (_index < _block.End)
|
||||||
}
|
{
|
||||||
_block = _block.Next;
|
return _block.Array[_index];
|
||||||
_index = _block.Start;
|
}
|
||||||
|
else if (_block.Next == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var block = _block.Next;
|
||||||
|
var index = block.Start;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (index < block.End)
|
||||||
|
{
|
||||||
|
return block.Array[index];
|
||||||
|
}
|
||||||
|
else if (block.Next == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
block = block.Next;
|
||||||
|
index = block.Start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator IndexOf(char char0)
|
public int Seek(int char0)
|
||||||
{
|
{
|
||||||
|
if (IsDefault)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
var byte0 = (byte)char0;
|
var byte0 = (byte)char0;
|
||||||
var vectorStride = Vector<byte>.Count;
|
var vectorStride = Vector<byte>.Count;
|
||||||
var ch0Vector = new Vector<byte>(byte0);
|
var ch0Vector = new Vector<byte>(byte0);
|
||||||
|
|
||||||
var scanBlock = _block;
|
var block = _block;
|
||||||
var scanArray = scanBlock?.Array;
|
var index = _index;
|
||||||
var scanIndex = _index;
|
var array = block.Array;
|
||||||
while (scanBlock != null)
|
while (true)
|
||||||
{
|
{
|
||||||
var tailCount = scanBlock.End - scanIndex;
|
while (block.End == index)
|
||||||
if (tailCount == 0)
|
|
||||||
{
|
{
|
||||||
scanBlock = scanBlock.Next;
|
if (block.Next == null)
|
||||||
scanArray = scanBlock?.Array;
|
{
|
||||||
scanIndex = scanBlock?.Start ?? 0;
|
_block = block;
|
||||||
continue;
|
_index = index;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
block = block.Next;
|
||||||
|
index = block.Start;
|
||||||
|
array = block.Array;
|
||||||
}
|
}
|
||||||
if (tailCount >= vectorStride)
|
while (block.End != index)
|
||||||
{
|
{
|
||||||
var data = new Vector<byte>(scanBlock.Array, scanIndex);
|
var following = block.End - index;
|
||||||
var ch0Equals = Vector.Equals(data, ch0Vector);
|
if (following >= vectorStride)
|
||||||
var ch0Count = Vector.Dot(ch0Equals, _dotCount);
|
{
|
||||||
|
var data = new Vector<byte>(array, index);
|
||||||
|
var ch0Equals = Vector.Equals(data, ch0Vector);
|
||||||
|
var ch0Count = Vector.Dot(ch0Equals, _dotCount);
|
||||||
|
|
||||||
if (ch0Count == 0)
|
if (ch0Count == 0)
|
||||||
{
|
{
|
||||||
scanIndex += vectorStride;
|
index += vectorStride;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
else if (ch0Count == 1)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index + Vector.Dot(ch0Equals, _dotIndex);
|
||||||
|
return char0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
following = vectorStride;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (ch0Count == 1)
|
for (; following != 0; following--, index++)
|
||||||
{
|
{
|
||||||
return new Iterator(scanBlock, scanIndex + Vector.Dot(ch0Equals, _dotIndex));
|
if (block.Array[index] == byte0)
|
||||||
}
|
{
|
||||||
else
|
_block = block;
|
||||||
{
|
_index = index;
|
||||||
tailCount = vectorStride;
|
return char0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (; tailCount != 0; tailCount--, scanIndex++)
|
|
||||||
{
|
|
||||||
var ch = scanBlock.Array[scanIndex];
|
|
||||||
if (ch == byte0)
|
|
||||||
{
|
|
||||||
return new Iterator(scanBlock, scanIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Iterator(null, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator IndexOfAny(char char0, char char1, out char chFound)
|
public int Seek(int char0, int char1)
|
||||||
{
|
{
|
||||||
|
if (IsDefault)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
var byte0 = (byte)char0;
|
var byte0 = (byte)char0;
|
||||||
var byte1 = (byte)char1;
|
var byte1 = (byte)char1;
|
||||||
var vectorStride = Vector<byte>.Count;
|
var vectorStride = Vector<byte>.Count;
|
||||||
var ch0Vector = new Vector<byte>(byte0);
|
var ch0Vector = new Vector<byte>(byte0);
|
||||||
var ch1Vector = new Vector<byte>(byte1);
|
var ch1Vector = new Vector<byte>(byte1);
|
||||||
|
|
||||||
var scanBlock = _block;
|
var block = _block;
|
||||||
var scanArray = scanBlock?.Array;
|
var index = _index;
|
||||||
var scanIndex = _index;
|
var array = block.Array;
|
||||||
while (scanBlock != null)
|
while (true)
|
||||||
{
|
{
|
||||||
var tailCount = scanBlock.End - scanIndex;
|
while (block.End == index)
|
||||||
if (tailCount == 0)
|
|
||||||
{
|
{
|
||||||
scanBlock = scanBlock.Next;
|
if (block.Next == null)
|
||||||
scanArray = scanBlock?.Array;
|
|
||||||
scanIndex = scanBlock?.Start ?? 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (tailCount >= vectorStride)
|
|
||||||
{
|
|
||||||
var data = new Vector<byte>(scanBlock.Array, scanIndex);
|
|
||||||
var ch0Equals = Vector.Equals(data, ch0Vector);
|
|
||||||
var ch0Count = Vector.Dot(ch0Equals, _dotCount);
|
|
||||||
var ch1Equals = Vector.Equals(data, ch1Vector);
|
|
||||||
var ch1Count = Vector.Dot(ch1Equals, _dotCount);
|
|
||||||
|
|
||||||
if (ch0Count == 0 && ch1Count == 0)
|
|
||||||
{
|
{
|
||||||
scanIndex += vectorStride;
|
_block = block;
|
||||||
continue;
|
_index = index;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
else if (ch0Count < 2 && ch1Count < 2)
|
block = block.Next;
|
||||||
|
index = block.Start;
|
||||||
|
array = block.Array;
|
||||||
|
}
|
||||||
|
while (block.End != index)
|
||||||
|
{
|
||||||
|
var following = block.End - index;
|
||||||
|
if (following >= vectorStride)
|
||||||
{
|
{
|
||||||
var ch0Index = ch0Count == 1 ? Vector.Dot(ch0Equals, _dotIndex) : byte.MaxValue;
|
var data = new Vector<byte>(array, index);
|
||||||
var ch1Index = ch1Count == 1 ? Vector.Dot(ch1Equals, _dotIndex) : byte.MaxValue;
|
var ch0Equals = Vector.Equals(data, ch0Vector);
|
||||||
if (ch0Index < ch1Index)
|
var ch0Count = Vector.Dot(ch0Equals, _dotCount);
|
||||||
|
var ch1Equals = Vector.Equals(data, ch1Vector);
|
||||||
|
var ch1Count = Vector.Dot(ch1Equals, _dotCount);
|
||||||
|
|
||||||
|
if (ch0Count == 0 && ch1Count == 0)
|
||||||
{
|
{
|
||||||
chFound = char0;
|
index += vectorStride;
|
||||||
return new Iterator(scanBlock, scanIndex + ch0Index);
|
continue;
|
||||||
|
}
|
||||||
|
else if (ch0Count < 2 && ch1Count < 2)
|
||||||
|
{
|
||||||
|
var ch0Index = ch0Count == 1 ? Vector.Dot(ch0Equals, _dotIndex) : byte.MaxValue;
|
||||||
|
var ch1Index = ch1Count == 1 ? Vector.Dot(ch1Equals, _dotIndex) : byte.MaxValue;
|
||||||
|
if (ch0Index < ch1Index)
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index + ch0Index;
|
||||||
|
return char0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_block = block;
|
||||||
|
_index = index + ch1Index;
|
||||||
|
return char1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
chFound = char1;
|
following = vectorStride;
|
||||||
return new Iterator(scanBlock, scanIndex + ch1Index);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
for (; following != 0; following--, index++)
|
||||||
{
|
{
|
||||||
tailCount = vectorStride;
|
var byteIndex = block.Array[index];
|
||||||
}
|
if (byteIndex == byte0)
|
||||||
}
|
{
|
||||||
for (; tailCount != 0; tailCount--, scanIndex++)
|
_block = block;
|
||||||
{
|
_index = index;
|
||||||
var chIndex = scanBlock.Array[scanIndex];
|
return char0;
|
||||||
if (chIndex == byte0)
|
}
|
||||||
{
|
else if (byteIndex == byte1)
|
||||||
chFound = char0;
|
{
|
||||||
return new Iterator(scanBlock, scanIndex);
|
_block = block;
|
||||||
}
|
_index = index;
|
||||||
else if (chIndex == byte1)
|
return char1;
|
||||||
{
|
}
|
||||||
chFound = char1;
|
|
||||||
return new Iterator(scanBlock, scanIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chFound = char.MinValue;
|
|
||||||
return new Iterator(null, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetLength(Iterator end)
|
public int GetLength(Iterator end)
|
||||||
{
|
{
|
||||||
var length = 0;
|
if (IsDefault || end.IsDefault)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
var block = _block;
|
var block = _block;
|
||||||
var index = _index;
|
var index = _index;
|
||||||
for (;;)
|
var length = 0;
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
if (block == end._block)
|
if (block == end._block)
|
||||||
{
|
{
|
||||||
return length + end._index - index;
|
return length + end._index - index;
|
||||||
}
|
}
|
||||||
if (block == null)
|
else if (block.Next == null)
|
||||||
{
|
{
|
||||||
throw new Exception("end was not after iterator");
|
throw new Exception("end did not follow iterator");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
length += block.End - index;
|
||||||
|
block = block.Next;
|
||||||
|
index = block.Start;
|
||||||
}
|
}
|
||||||
length += block.End - index;
|
|
||||||
block = block.Next;
|
|
||||||
index = block?.Start ?? 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,53 +424,74 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
if (end._block == _block)
|
if (end._block == _block)
|
||||||
{
|
{
|
||||||
return Encoding.ASCII.GetString(_block.Array, _index, end._index - _index);
|
return Encoding.UTF8.GetString(_block.Array, _index, end._index - _index);
|
||||||
}
|
|
||||||
if (end._block == _block.Next && end._index == end._block.Start)
|
|
||||||
{
|
|
||||||
return Encoding.ASCII.GetString(_block.Array, _index, _block.End - _index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var length = GetLength(end);
|
|
||||||
var result = new char[length];
|
|
||||||
var offset = 0;
|
|
||||||
var decoder = Encoding.ASCII.GetDecoder();
|
var decoder = Encoding.ASCII.GetDecoder();
|
||||||
|
|
||||||
|
var length = GetLength(end);
|
||||||
|
var charLength = length * 2;
|
||||||
|
var chars = new char[charLength];
|
||||||
|
var charIndex = 0;
|
||||||
|
|
||||||
var block = _block;
|
var block = _block;
|
||||||
var index = _index;
|
var index = _index;
|
||||||
while (length != 0)
|
var remaining = length;
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
if (block == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Unexpected end of data");
|
|
||||||
}
|
|
||||||
|
|
||||||
var count = Math.Min(block.End - index, length);
|
|
||||||
|
|
||||||
int bytesUsed;
|
int bytesUsed;
|
||||||
int textAdded;
|
int charsUsed;
|
||||||
bool completed;
|
bool completed;
|
||||||
decoder.Convert(
|
var following = block.End - index;
|
||||||
block.Array,
|
if (remaining <= following)
|
||||||
index,
|
{
|
||||||
count,
|
decoder.Convert(
|
||||||
result,
|
block.Array,
|
||||||
offset,
|
index,
|
||||||
length,
|
remaining,
|
||||||
count == length,
|
chars,
|
||||||
out bytesUsed,
|
charIndex,
|
||||||
out textAdded,
|
charLength - charIndex,
|
||||||
out completed);
|
true,
|
||||||
|
out bytesUsed,
|
||||||
Debug.Assert(bytesUsed == count);
|
out charsUsed,
|
||||||
Debug.Assert(textAdded == count);
|
out completed);
|
||||||
offset += count;
|
return new string(chars, 0, charIndex + charsUsed);
|
||||||
length -= count;
|
}
|
||||||
|
else if (block.Next == null)
|
||||||
block = block.Next;
|
{
|
||||||
index = block?.Start ?? 0;
|
decoder.Convert(
|
||||||
|
block.Array,
|
||||||
|
index,
|
||||||
|
following,
|
||||||
|
chars,
|
||||||
|
charIndex,
|
||||||
|
charLength - charIndex,
|
||||||
|
true,
|
||||||
|
out bytesUsed,
|
||||||
|
out charsUsed,
|
||||||
|
out completed);
|
||||||
|
return new string(chars, 0, charIndex + charsUsed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
decoder.Convert(
|
||||||
|
block.Array,
|
||||||
|
index,
|
||||||
|
following,
|
||||||
|
chars,
|
||||||
|
charIndex,
|
||||||
|
charLength - charIndex,
|
||||||
|
false,
|
||||||
|
out bytesUsed,
|
||||||
|
out charsUsed,
|
||||||
|
out completed);
|
||||||
|
charIndex += charsUsed;
|
||||||
|
remaining -= following;
|
||||||
|
block = block.Next;
|
||||||
|
index = block.Start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new string(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArraySegment<byte> GetArraySegment(Iterator end)
|
public ArraySegment<byte> GetArraySegment(Iterator end)
|
||||||
|
|
@ -445,34 +504,47 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
return new ArraySegment<byte>(_block.Array, _index, end._index - _index);
|
return new ArraySegment<byte>(_block.Array, _index, end._index - _index);
|
||||||
}
|
}
|
||||||
if (end._block == _block.Next && end._index == end._block.Start)
|
|
||||||
{
|
|
||||||
return new ArraySegment<byte>(_block.Array, _index, _block.End - _index);
|
|
||||||
}
|
|
||||||
|
|
||||||
var length = GetLength(end);
|
var length = GetLength(end);
|
||||||
var result = new ArraySegment<byte>(new byte[length]);
|
var array = new byte[length];
|
||||||
var offset = result.Offset;
|
CopyTo(array, 0, length, out length);
|
||||||
|
return new ArraySegment<byte>(array, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator CopyTo(byte[] array, int offset, int count, out int actual)
|
||||||
|
{
|
||||||
|
if (IsDefault)
|
||||||
|
{
|
||||||
|
actual = 0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
var block = _block;
|
var block = _block;
|
||||||
var index = _index;
|
var index = _index;
|
||||||
while (length != 0)
|
var remaining = count;
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
if (block == null)
|
var following = block.End - index;
|
||||||
|
if (remaining <= following)
|
||||||
{
|
{
|
||||||
throw new Exception("Unexpected end of data");
|
actual = count;
|
||||||
|
Buffer.BlockCopy(block.Array, index, array, offset, remaining);
|
||||||
|
return new Iterator(block, index + remaining);
|
||||||
|
}
|
||||||
|
else if (block.Next == null)
|
||||||
|
{
|
||||||
|
actual = count - remaining + following;
|
||||||
|
Buffer.BlockCopy(block.Array, index, array, offset, following);
|
||||||
|
return new Iterator(block, index + following);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Buffer.BlockCopy(block.Array, index, array, offset, following);
|
||||||
|
remaining -= following;
|
||||||
|
block = block.Next;
|
||||||
|
index = block.Start;
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = Math.Min(block.End - index, length);
|
|
||||||
Buffer.BlockCopy(block.Array, index, result.Array, offset, count);
|
|
||||||
offset += count;
|
|
||||||
length -= count;
|
|
||||||
|
|
||||||
block = block.Next;
|
|
||||||
index = block?.Start ?? 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
public byte[] Array;
|
public byte[] Array;
|
||||||
public IntPtr ArrayPtr;
|
public IntPtr ArrayPtr;
|
||||||
public bool IsActive;
|
public bool IsActive;
|
||||||
|
private bool disposedValue = false; // To detect redundant calls
|
||||||
|
|
||||||
public static MemoryPoolSlab2 Create(int length)
|
public static MemoryPoolSlab2 Create(int length)
|
||||||
{
|
{
|
||||||
|
|
@ -26,7 +27,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDisposable Support
|
#region IDisposable Support
|
||||||
private bool disposedValue = false; // To detect redundant calls
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -86,14 +86,24 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
public override async Task<int> ReadAsyncImplementation(ArraySegment<byte> buffer, CancellationToken cancellationToken)
|
public override async Task<int> ReadAsyncImplementation(ArraySegment<byte> buffer, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var input = _context.SocketInput;
|
var input = _context.SocketInput;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await input;
|
||||||
|
|
||||||
await input;
|
var begin = input.GetIterator();
|
||||||
|
int actual;
|
||||||
|
var end = begin.CopyTo(buffer.Array, buffer.Offset, buffer.Count, out actual);
|
||||||
|
input.JumpTo(end);
|
||||||
|
|
||||||
var begin = input.GetIterator();
|
if (actual != 0)
|
||||||
int actual;
|
{
|
||||||
var end = begin.CopyTo(buffer.Array, buffer.Offset, buffer.Count, out actual);
|
return actual;
|
||||||
input.JumpTo(end);
|
}
|
||||||
return actual;
|
if (input.RemoteIntakeFin)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,18 +124,30 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
var input = _context.SocketInput;
|
var input = _context.SocketInput;
|
||||||
|
|
||||||
var limit = Math.Min(buffer.Count, _inputLength);
|
while (true)
|
||||||
if (limit != 0)
|
|
||||||
{
|
{
|
||||||
await input;
|
var limit = Math.Min(buffer.Count, _inputLength);
|
||||||
}
|
if (limit == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
var begin = input.GetIterator();
|
await input;
|
||||||
int actual;
|
|
||||||
var end = begin.CopyTo(buffer.Array, buffer.Offset, limit, out actual);
|
var begin = input.GetIterator();
|
||||||
_inputLength -= actual;
|
int actual;
|
||||||
input.JumpTo(end);
|
var end = begin.CopyTo(buffer.Array, buffer.Offset, limit, out actual);
|
||||||
return actual;
|
_inputLength -= actual;
|
||||||
|
input.JumpTo(end);
|
||||||
|
if (actual != 0)
|
||||||
|
{
|
||||||
|
return actual;
|
||||||
|
}
|
||||||
|
if (input.RemoteIntakeFin)
|
||||||
|
{
|
||||||
|
throw new Exception("Unexpected end of request content");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,21 +208,23 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
_mode = Mode.ChunkSuffix;
|
_mode = Mode.ChunkSuffix;
|
||||||
}
|
}
|
||||||
|
if (actual != 0)
|
||||||
return actual;
|
{
|
||||||
|
return actual;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
while (_mode == Mode.ChunkSuffix)
|
while (_mode == Mode.ChunkSuffix)
|
||||||
{
|
{
|
||||||
var begin = input.GetIterator();
|
var scan = input.GetIterator();
|
||||||
var ch1 = begin.Peek();
|
var ch1 = scan.Take();
|
||||||
var ch2 = begin.MoveNext();
|
var ch2 = scan.Take();
|
||||||
if (ch1 == char.MinValue || ch2 == char.MinValue)
|
if (ch1 == -1 || ch2 == -1)
|
||||||
{
|
{
|
||||||
await input;
|
await input;
|
||||||
}
|
}
|
||||||
else if (ch1 == '\r' && ch2 == '\n')
|
else if (ch1 == '\r' && ch2 == '\n')
|
||||||
{
|
{
|
||||||
input.JumpTo(begin.Add(1));
|
input.JumpTo(scan);
|
||||||
_mode = Mode.ChunkPrefix;
|
_mode = Mode.ChunkPrefix;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -215,13 +239,17 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
private static bool TakeChunkedLine(SocketInput baton, ref int chunkSizeOut)
|
private static bool TakeChunkedLine(SocketInput baton, ref int chunkSizeOut)
|
||||||
{
|
{
|
||||||
var remaining = baton.GetIterator();
|
var scan = baton.GetIterator();
|
||||||
var ch0 = remaining.Peek();
|
var ch0 = scan.Take();
|
||||||
var chunkSize = 0;
|
var chunkSize = 0;
|
||||||
var mode = 0;
|
var mode = 0;
|
||||||
while(ch0 != -1)
|
while (ch0 != -1)
|
||||||
{
|
{
|
||||||
var ch1 = remaining.MoveNext();
|
var ch1 = scan.Take();
|
||||||
|
if (ch1 == -1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (mode == 0)
|
if (mode == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -263,7 +291,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
else if (ch0 == '\r' && ch1 == '\n')
|
else if (ch0 == '\r' && ch1 == '\n')
|
||||||
{
|
{
|
||||||
baton.JumpTo(remaining.Add(1));
|
baton.JumpTo(scan);
|
||||||
chunkSizeOut = chunkSize;
|
chunkSizeOut = chunkSize;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +304,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
{
|
{
|
||||||
if (ch0 == '\r' && ch1 == '\n')
|
if (ch0 == '\r' && ch1 == '\n')
|
||||||
{
|
{
|
||||||
baton.JumpTo(remaining.Add(1));
|
baton.JumpTo(scan);
|
||||||
chunkSizeOut = chunkSize;
|
chunkSizeOut = chunkSize;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,19 +206,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
lock (_syncHeadAndTail)
|
lock (_syncHeadAndTail)
|
||||||
{
|
{
|
||||||
// TODO: leave _pinned intact
|
// TODO: leave _pinned intact
|
||||||
|
// TODO: return when empty
|
||||||
|
|
||||||
returnStart = _head;
|
returnStart = _head;
|
||||||
returnEnd = iterator.Block;
|
returnEnd = iterator.Block;
|
||||||
_head = iterator.Block;
|
_head = iterator.Block;
|
||||||
if (_head == null)
|
_head.Start = iterator.Index;
|
||||||
|
if (iterator.IsEnd)
|
||||||
{
|
{
|
||||||
_tail = null;
|
|
||||||
SetNotCompleted();
|
SetNotCompleted();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_head.Start = iterator.Index;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
while (returnStart != returnEnd)
|
while (returnStart != returnEnd)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -42,14 +42,22 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
_callbacksPending = new Queue<CallbackContext>();
|
_callbacksPending = new Queue<CallbackContext>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(ArraySegment<byte> buffer, Action<Exception, object> callback, object state, bool immediate = true)
|
public void Write(
|
||||||
|
ArraySegment<byte> buffer,
|
||||||
|
Action<Exception, object> callback,
|
||||||
|
object state,
|
||||||
|
bool immediate = true,
|
||||||
|
bool socketShutdownSend = false,
|
||||||
|
bool socketDisconnect = false)
|
||||||
{
|
{
|
||||||
//TODO: need buffering that works
|
//TODO: need buffering that works
|
||||||
var copy = new byte[buffer.Count];
|
if (buffer.Array != null)
|
||||||
Array.Copy(buffer.Array, buffer.Offset, copy, 0, buffer.Count);
|
{
|
||||||
buffer = new ArraySegment<byte>(copy);
|
var copy = new byte[buffer.Count];
|
||||||
|
Array.Copy(buffer.Array, buffer.Offset, copy, 0, buffer.Count);
|
||||||
_log.ConnectionWrite(_connectionId, buffer.Count);
|
buffer = new ArraySegment<byte>(copy);
|
||||||
|
_log.ConnectionWrite(_connectionId, buffer.Count);
|
||||||
|
}
|
||||||
|
|
||||||
bool triggerCallbackNow = false;
|
bool triggerCallbackNow = false;
|
||||||
|
|
||||||
|
|
@ -60,8 +68,18 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
_nextWriteContext = new WriteContext(this);
|
_nextWriteContext = new WriteContext(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_nextWriteContext.Buffers.Enqueue(buffer);
|
if (buffer.Array != null)
|
||||||
|
{
|
||||||
|
_nextWriteContext.Buffers.Enqueue(buffer);
|
||||||
|
}
|
||||||
|
if (socketShutdownSend)
|
||||||
|
{
|
||||||
|
_nextWriteContext.SocketShutdownSend = true;
|
||||||
|
}
|
||||||
|
if (socketDisconnect)
|
||||||
|
{
|
||||||
|
_nextWriteContext.SocketDisconnect = true;
|
||||||
|
}
|
||||||
// Complete the write task immediately if all previous write tasks have been completed,
|
// Complete the write task immediately if all previous write tasks have been completed,
|
||||||
// the buffers haven't grown too large, and the last write to the socket succeeded.
|
// the buffers haven't grown too large, and the last write to the socket succeeded.
|
||||||
triggerCallbackNow = _lastWriteError == null &&
|
triggerCallbackNow = _lastWriteError == null &&
|
||||||
|
|
@ -121,22 +139,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var buffers = new ArraySegment<byte>[writingContext.Buffers.Count];
|
writingContext.Execute();
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
foreach (var buffer in writingContext.Buffers)
|
|
||||||
{
|
|
||||||
buffers[i++] = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
var writeReq = new UvWriteReq(_log);
|
|
||||||
writeReq.Init(_thread.Loop);
|
|
||||||
|
|
||||||
writeReq.Write(_socket, new ArraySegment<ArraySegment<byte>>(buffers), (r, status, error, state) =>
|
|
||||||
{
|
|
||||||
var writtenContext = (WriteContext)state;
|
|
||||||
writtenContext.Self.OnWriteCompleted(writtenContext.Buffers, r, status, error);
|
|
||||||
}, writingContext);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
@ -152,7 +155,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is called on the libuv event loop
|
// This is called on the libuv event loop
|
||||||
private void OnWriteCompleted(Queue<ArraySegment<byte>> writtenBuffers, UvWriteReq req, int status, Exception error)
|
private void OnWriteCompleted(Queue<ArraySegment<byte>> writtenBuffers, int status, Exception error)
|
||||||
{
|
{
|
||||||
_log.ConnectionWriteCallback(_connectionId, status);
|
_log.ConnectionWriteCallback(_connectionId, status);
|
||||||
|
|
||||||
|
|
@ -194,8 +197,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
Trace.Assert(_numBytesPreCompleted >= 0);
|
Trace.Assert(_numBytesPreCompleted >= 0);
|
||||||
Trace.Assert(_numBytesPreCompleted <= _maxBytesPreCompleted);
|
Trace.Assert(_numBytesPreCompleted <= _maxBytesPreCompleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TriggerCallback(CallbackContext context)
|
private void TriggerCallback(CallbackContext context)
|
||||||
|
|
@ -237,6 +238,25 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
return tcs.Task;
|
return tcs.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ISocketOutput.End(ProduceEndType endType)
|
||||||
|
{
|
||||||
|
switch (endType)
|
||||||
|
{
|
||||||
|
case ProduceEndType.SocketShutdownSend:
|
||||||
|
Write(default(ArraySegment<byte>), (error, state) => { }, null,
|
||||||
|
immediate: true,
|
||||||
|
socketShutdownSend: true,
|
||||||
|
socketDisconnect: false);
|
||||||
|
break;
|
||||||
|
case ProduceEndType.SocketDisconnect:
|
||||||
|
Write(default(ArraySegment<byte>), (error, state) => { }, null,
|
||||||
|
immediate: true,
|
||||||
|
socketShutdownSend: false,
|
||||||
|
socketDisconnect: true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class CallbackContext
|
private class CallbackContext
|
||||||
{
|
{
|
||||||
public Exception Error;
|
public Exception Error;
|
||||||
|
|
@ -248,13 +268,89 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
private class WriteContext
|
private class WriteContext
|
||||||
{
|
{
|
||||||
public SocketOutput Self;
|
public SocketOutput Self;
|
||||||
|
|
||||||
public Queue<ArraySegment<byte>> Buffers;
|
public Queue<ArraySegment<byte>> Buffers;
|
||||||
|
public bool SocketShutdownSend;
|
||||||
|
public bool SocketDisconnect;
|
||||||
|
|
||||||
|
public int WriteStatus;
|
||||||
|
public Exception WriteError;
|
||||||
|
|
||||||
|
public int ShutdownSendStatus;
|
||||||
|
|
||||||
public WriteContext(SocketOutput self)
|
public WriteContext(SocketOutput self)
|
||||||
{
|
{
|
||||||
Self = self;
|
Self = self;
|
||||||
Buffers = new Queue<ArraySegment<byte>>();
|
Buffers = new Queue<ArraySegment<byte>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
if (Buffers.Count == 0 || Self._socket.IsClosed)
|
||||||
|
{
|
||||||
|
StageTwo();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffers = new ArraySegment<byte>[Buffers.Count];
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
foreach (var buffer in Buffers)
|
||||||
|
{
|
||||||
|
buffers[i++] = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
var writeReq = new UvWriteReq(Self._log);
|
||||||
|
writeReq.Init(Self._thread.Loop);
|
||||||
|
writeReq.Write(Self._socket, new ArraySegment<ArraySegment<byte>>(buffers), (_writeReq, status, error, state) =>
|
||||||
|
{
|
||||||
|
_writeReq.Dispose();
|
||||||
|
var _this = (WriteContext)state;
|
||||||
|
_this.WriteStatus = status;
|
||||||
|
_this.WriteError = error;
|
||||||
|
StageTwo();
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StageTwo()
|
||||||
|
{
|
||||||
|
if (SocketShutdownSend == false || Self._socket.IsClosed)
|
||||||
|
{
|
||||||
|
StageThree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shutdownReq = new UvShutdownReq(Self._log);
|
||||||
|
shutdownReq.Init(Self._thread.Loop);
|
||||||
|
shutdownReq.Shutdown(Self._socket, (_shutdownReq, status, state) =>
|
||||||
|
{
|
||||||
|
_shutdownReq.Dispose();
|
||||||
|
var _this = (WriteContext)state;
|
||||||
|
_this.ShutdownSendStatus = status;
|
||||||
|
|
||||||
|
Self._log.ConnectionWroteFin(Self._connectionId, status);
|
||||||
|
|
||||||
|
StageThree();
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StageThree()
|
||||||
|
{
|
||||||
|
if (SocketDisconnect == false || Self._socket.IsClosed)
|
||||||
|
{
|
||||||
|
Complete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self._socket.Dispose();
|
||||||
|
Self._log.ConnectionStop(Self._connectionId);
|
||||||
|
Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Complete()
|
||||||
|
{
|
||||||
|
Self.OnWriteCompleted(Buffers, WriteStatus, WriteError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ using Microsoft.AspNet.Server.Kestrel.Http;
|
||||||
using Microsoft.Dnx.Runtime;
|
using Microsoft.Dnx.Runtime;
|
||||||
using Microsoft.Dnx.Runtime.Infrastructure;
|
using Microsoft.Dnx.Runtime.Infrastructure;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.KestrelTests
|
namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
{
|
{
|
||||||
|
|
@ -23,7 +24,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
private async Task App(Frame frame)
|
private async Task App(Frame frame)
|
||||||
{
|
{
|
||||||
frame.ResponseHeaders.Clear();
|
frame.ResponseHeaders.Clear();
|
||||||
for (; ;)
|
while (true)
|
||||||
{
|
{
|
||||||
var buffer = new byte[8192];
|
var buffer = new byte[8192];
|
||||||
var count = await frame.RequestBody.ReadAsync(buffer, 0, buffer.Length);
|
var count = await frame.RequestBody.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
|
@ -60,17 +61,17 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
|
|
||||||
private async Task AppChunked(Frame frame)
|
private async Task AppChunked(Frame frame)
|
||||||
{
|
{
|
||||||
|
foreach (var h in frame.RequestHeaders)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{h.Key}: {h.Value}");
|
||||||
|
}
|
||||||
|
Console.WriteLine($"");
|
||||||
|
|
||||||
frame.ResponseHeaders.Clear();
|
frame.ResponseHeaders.Clear();
|
||||||
var data = new MemoryStream();
|
var data = new MemoryStream();
|
||||||
for (; ;)
|
while(true)
|
||||||
{
|
{
|
||||||
var buffer = new byte[8192];
|
await frame.RequestBody.CopyToAsync(data);
|
||||||
var count = await frame.RequestBody.ReadAsync(buffer, 0, buffer.Length);
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
data.Write(buffer, 0, count);
|
|
||||||
}
|
}
|
||||||
var bytes = data.ToArray();
|
var bytes = data.ToArray();
|
||||||
frame.ResponseHeaders["Content-Length"] = new[] { bytes.Length.ToString() };
|
frame.ResponseHeaders["Content-Length"] = new[] { bytes.Length.ToString() };
|
||||||
|
|
@ -115,7 +116,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World"));
|
socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World"));
|
||||||
socket.Shutdown(SocketShutdown.Send);
|
socket.Shutdown(SocketShutdown.Send);
|
||||||
var buffer = new byte[8192];
|
var buffer = new byte[8192];
|
||||||
for (; ;)
|
for (;;)
|
||||||
{
|
{
|
||||||
var length = socket.Receive(buffer);
|
var length = socket.Receive(buffer);
|
||||||
if (length == 0) { break; }
|
if (length == 0) { break; }
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
public class MemoryPoolBlock2Tests
|
public class MemoryPoolBlock2Tests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IndexOfAnyWorks()
|
public void SeekWorks()
|
||||||
{
|
{
|
||||||
using (var pool = new MemoryPool2())
|
using (var pool = new MemoryPool2())
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +22,16 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
var iterator = block.GetIterator();
|
var iterator = block.GetIterator();
|
||||||
foreach (var ch in Enumerable.Range(0, 256).Select(x => (char)x))
|
foreach (var ch in Enumerable.Range(0, 256).Select(x => (char)x))
|
||||||
{
|
{
|
||||||
var hit = iterator.IndexOf(ch);
|
var hit = iterator;
|
||||||
|
hit.Seek(ch);
|
||||||
|
Assert.Equal(ch, iterator.GetLength(hit));
|
||||||
|
|
||||||
|
hit = iterator;
|
||||||
|
hit.Seek(ch, byte.MaxValue);
|
||||||
|
Assert.Equal(ch, iterator.GetLength(hit));
|
||||||
|
|
||||||
|
hit = iterator;
|
||||||
|
hit.Seek(byte.MaxValue, ch);
|
||||||
Assert.Equal(ch, iterator.GetLength(hit));
|
Assert.Equal(ch, iterator.GetLength(hit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,5 +81,49 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddDoesNotAdvanceAtEndOfCurrentBlock()
|
||||||
|
{
|
||||||
|
using (var pool = new MemoryPool2())
|
||||||
|
{
|
||||||
|
var block1 = pool.Lease(256);
|
||||||
|
var block2 = block1.Next = pool.Lease(256);
|
||||||
|
|
||||||
|
block1.End += 100;
|
||||||
|
block2.End += 200;
|
||||||
|
|
||||||
|
var iter0 = block1.GetIterator();
|
||||||
|
var iter100 = iter0.Add(100);
|
||||||
|
|
||||||
|
var iter200a = iter0.Add(200);
|
||||||
|
var iter200b = iter100.Add(100);
|
||||||
|
|
||||||
|
var iter300a = iter0.Add(300);
|
||||||
|
var iter300b = iter100.Add(200);
|
||||||
|
var iter300c = iter200a.Add(100);
|
||||||
|
|
||||||
|
var iter300a2 = iter300a.Add(1);
|
||||||
|
var iter300b2 = iter300b.Add(1);
|
||||||
|
var iter300c2 = iter300c.Add(1);
|
||||||
|
|
||||||
|
AssertIterator(iter0, block1, block1.Start);
|
||||||
|
AssertIterator(iter100, block1, block1.End);
|
||||||
|
AssertIterator(iter200a, block2, block2.Start+100);
|
||||||
|
AssertIterator(iter200b, block2, block2.Start + 100);
|
||||||
|
AssertIterator(iter300a, block2, block2.End);
|
||||||
|
AssertIterator(iter300b, block2, block2.End);
|
||||||
|
AssertIterator(iter300c, block2, block2.End);
|
||||||
|
AssertIterator(iter300a2, block2, block2.End);
|
||||||
|
AssertIterator(iter300b2, block2, block2.End);
|
||||||
|
AssertIterator(iter300c2, block2, block2.End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertIterator(MemoryPoolBlock2.Iterator iter, MemoryPoolBlock2 block, int index)
|
||||||
|
{
|
||||||
|
Assert.Same(block, iter.Block);
|
||||||
|
Assert.Equal(index, iter.Index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Microsoft.AspNet.Server.Kestrel.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
|
{
|
||||||
|
public static class MemoryPoolExtensions
|
||||||
|
{
|
||||||
|
public static MemoryPoolBlock2.Iterator Add(this MemoryPoolBlock2.Iterator iterator, int count)
|
||||||
|
{
|
||||||
|
int actual;
|
||||||
|
return iterator.CopyTo(new byte[count], 0, count, out actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -71,8 +71,8 @@ namespace Microsoft.StandardsPolice
|
||||||
acceptableOrder = true;
|
acceptableOrder = true;
|
||||||
}
|
}
|
||||||
if (!acceptableOrder &&
|
if (!acceptableOrder &&
|
||||||
priorUsingDirective.Name.ToString().StartsWith("System.") &&
|
priorUsingDirective.Name.ToString().StartsWith("System") &&
|
||||||
!usingDirective.Name.ToString().StartsWith("System."))
|
!usingDirective.Name.ToString().StartsWith("System"))
|
||||||
{
|
{
|
||||||
acceptableOrder = true;
|
acceptableOrder = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue