Skip to content
This repository has been archived by the owner on Aug 2, 2019. It is now read-only.

Commit

Permalink
Implemented timeouts.
Browse files Browse the repository at this point in the history
Implemented timeouts on reading from and writing to a TCP connection.

Bug: #5
  • Loading branch information
pvginkel committed Dec 5, 2015
1 parent bd8ef57 commit 5faf814
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 4 deletions.
19 changes: 17 additions & 2 deletions NHttp/HttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ private void BeginRead()

try
{
ReadBuffer.BeginRead(_stream, ReadCallback, null);
Server.TimeoutManager.ReadQueue.Add(
ReadBuffer.BeginRead(_stream, ReadCallback, null),
this
);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -154,6 +157,12 @@ private void ReadCallback(IAsyncResult asyncResult)
else
Dispose();
}
catch (ObjectDisposedException ex)
{
Log.Info("Failed to read", ex);

Dispose();
}
catch (Exception ex)
{
Log.Info("Failed to read from the HTTP connection", ex);
Expand Down Expand Up @@ -395,7 +404,10 @@ private void BeginWrite()

int read = _writeStream.Read(_writeBuffer, 0, _writeBuffer.Length);

_stream.BeginWrite(_writeBuffer, 0, read, WriteCallback, null);
Server.TimeoutManager.WriteQueue.Add(
_stream.BeginWrite(_writeBuffer, 0, read, WriteCallback, null),
this
);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -619,6 +631,9 @@ public void UnsetParser()

private void ProcessException(Exception exception)
{
if (_disposed)
return;

_errored = true;

// If there is no request available, the error didn't occur as part
Expand Down
4 changes: 2 additions & 2 deletions NHttp/HttpReadBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public bool CopyToStream(Stream stream, int maximum, byte[] boundary)
return true;
}

public void BeginRead(Stream stream, AsyncCallback callback, object state)
public IAsyncResult BeginRead(Stream stream, AsyncCallback callback, object state)
{
// A new read was requested. Reset the flag.

Expand Down Expand Up @@ -215,7 +215,7 @@ public void BeginRead(Stream stream, AsyncCallback callback, object state)

int bufferAvailable = Math.Min(_buffer.Length - _available, _bufferSize);

stream.BeginRead(_buffer, _available, bufferAvailable, callback, state);
return stream.BeginRead(_buffer, _available, bufferAvailable, callback, state);
}

public void EndRead(Stream stream, IAsyncResult asyncResult)
Expand Down
16 changes: 16 additions & 0 deletions NHttp/HttpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,25 @@ protected virtual void OnStateChanged(EventArgs e)

public string ServerBanner { get; set; }

public TimeSpan ReadTimeout { get; set; }

public TimeSpan WriteTimeout { get; set; }

public TimeSpan ShutdownTimeout { get; set; }

internal HttpServerUtility ServerUtility { get; private set; }

internal HttpTimeoutManager TimeoutManager { get; private set; }

public HttpServer()
{
EndPoint = new IPEndPoint(IPAddress.Loopback, 0);

ReadBufferSize = 4096;
WriteBufferSize = 4096;
ShutdownTimeout = TimeSpan.FromSeconds(30);
ReadTimeout = TimeSpan.FromSeconds(90);
WriteTimeout = TimeSpan.FromSeconds(90);

ServerBanner = String.Format("NHttp/{0}", GetType().Assembly.GetName().Version);
}
Expand All @@ -94,6 +102,8 @@ public void Start()

Log.Debug(String.Format("Starting HTTP server at {0}", EndPoint));

TimeoutManager = new HttpTimeoutManager(this);

// Start the listener.

var listener = new TcpListener(EndPoint);
Expand Down Expand Up @@ -322,6 +332,12 @@ public void Dispose()
_clientsChangedEvent = null;
}

if (TimeoutManager != null)
{
TimeoutManager.Dispose();
TimeoutManager = null;
}

_disposed = true;
}
}
Expand Down
134 changes: 134 additions & 0 deletions NHttp/HttpTimeoutManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace NHttp
{
internal class HttpTimeoutManager : IDisposable
{
private Thread _thread;
private ManualResetEvent _closeEvent = new ManualResetEvent(false);

public TimeoutQueue ReadQueue { get; private set; }
public TimeoutQueue WriteQueue { get; private set; }

public HttpTimeoutManager(HttpServer server)
{
if (server == null)
throw new ArgumentNullException(nameof(server));

ReadQueue = new TimeoutQueue(server.ReadTimeout);
WriteQueue = new TimeoutQueue(server.WriteTimeout);

_thread = new Thread(ThreadProc);
_thread.Start();
}

private void ThreadProc()
{
while (!_closeEvent.WaitOne(TimeSpan.FromSeconds(1)))
{
ProcessQueue(ReadQueue);
ProcessQueue(WriteQueue);
}
}

private void ProcessQueue(TimeoutQueue queue)
{
while (true)
{
var item = queue.DequeueExpired();
if (item == null)
return;

if (!item.AsyncResult.IsCompleted)
{
try
{
item.Disposable.Dispose();
}
catch
{
// Ignore exceptions.
}
}
}
}

public void Dispose()
{
if (_thread != null)
{
_closeEvent.Set();
_thread.Join();
_thread = null;
}
if (_closeEvent != null)
{
_closeEvent.Close();
_closeEvent = null;
}
}

public class TimeoutQueue
{
private readonly object _syncRoot = new object();
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
private readonly long _timeout;
private readonly Queue<TimeoutItem> _items = new Queue<TimeoutItem>();

public TimeoutQueue(TimeSpan timeout)
{
_timeout = (long)(timeout.TotalSeconds * Stopwatch.Frequency);
}

public void Add(IAsyncResult asyncResult, IDisposable disposable)
{
if (asyncResult == null)
throw new ArgumentNullException(nameof(asyncResult));
if (disposable == null)
throw new ArgumentNullException(nameof(disposable));

lock (_syncRoot)
{
_items.Enqueue(new TimeoutItem(_stopwatch.ElapsedTicks + _timeout, asyncResult, disposable));
}
}

public TimeoutItem DequeueExpired()
{
lock (_syncRoot)
{
if (_items.Count == 0)
return null;

var item = _items.Peek();
if (item.Expires < _stopwatch.ElapsedTicks)
return _items.Dequeue();

return null;
}
}
}

public class TimeoutItem
{
public long Expires { get; private set; }
public IAsyncResult AsyncResult { get; private set; }
public IDisposable Disposable { get; private set; }

public TimeoutItem(long expires, IAsyncResult asyncResult, IDisposable disposable)
{
if (asyncResult == null)
throw new ArgumentNullException(nameof(asyncResult));

Expires = expires;
AsyncResult = asyncResult;
Disposable = disposable;
}
}
}
}
1 change: 1 addition & 0 deletions NHttp/NHttp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
<Compile Include="HttpServer.cs" />
<Compile Include="HttpServerState.cs" />
<Compile Include="HttpServerUtility.cs" />
<Compile Include="HttpTimeoutManager.cs" />
<Compile Include="HttpUnknownRequestParser.cs" />
<Compile Include="HttpUrlEncodedRequestParser.cs" />
<Compile Include="HttpUtil.cs" />
Expand Down

0 comments on commit 5faf814

Please sign in to comment.