An answer in this post
suggests using a Timer
to time out a connection attempt using ConnectAsync
. I'm unsure how to handle disposal of the Socket
if that timeout occurs.
Consider the following example
public static void Main (string[] args)
{
IPEndPoint ep = new IPEndPoint (new IPAddress (new byte []{ 127, 0, 0, 1 }), 9042);
var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
var connectTimeoutMs = 50;
var tcs = new TaskCompletionSource<bool> ();
new Timer ((t) => {
tcs.SetException(new ConnectionTimeoutException());
((Timer) t).Dispose();
}).Change (connectTimeoutMs, Timeout.Infinite);
var eventArgs = new SocketAsyncEventArgs { RemoteEndPoint = ep };
eventArgs.Completed += Finisher (tcs);
if (socket.ConnectAsync (eventArgs)) {
// dealt synchronously
}
try {
tcs.Task.Wait (); // actually a ContinueWith
} catch (AggregateException e) {
if (e.InnerException is ConnectionTimeoutException) {
socket.Dispose (); // is this the appropriate place for this?
}
}
Thread.Sleep (10000);
}
public static EventHandler<SocketAsyncEventArgs> Finisher(TaskCompletionSource<bool> tcs) {
return (sender, e) => {
if (e.SocketError != SocketError.Success) {
tcs.TrySetException (new SocketException ((int)e.SocketError));
} else {
tcs.TrySetResult (true);
}
e.Dispose ();
};
}
class ConnectionTimeoutException : SocketException {
public ConnectionTimeoutException(): base((int) SocketError.TimedOut){}
}
On Mono-3.10.0, if the timeout occurs, this causes a fatal (uncaught) ObjectDisposedException
Unhandled Exception:
System.ObjectDisposedException: The object was used after being disposed.
at System.Net.Sockets.Socket.EndConnect (IAsyncResult result) [0x00016] in /home/user/dev/mono-3.10/mono-3.10.0/mcs/class/System/System.Net.Sockets/Socket.cs:1274
at System.Net.Sockets.SocketAsyncEventArgs.ConnectCallback (IAsyncResult ares) [0x00000] in /home/user/dev/mono-3.10/mono-3.10.0/mcs/class/System/System.Net.Sockets/SocketAsyncEventArgs.cs:260
at System.Net.Sockets.SocketAsyncEventArgs.DispatcherCB (IAsyncResult ares) [0x000aa] in /home/user/dev/mono-3.10/mono-3.10.0/mcs/class/System/System.Net.Sockets/SocketAsyncEventArgs.cs:234
because the call to
socket.Dispose()
actually completes the SocketAsyncEventArgs
, but first validates that the corresponding Socket
wasn't disposed. This was "corrected" in newer versions of Mono, but it got me thinking whether this is the appropriate pattern for timing out a connection attempt on a Socket
. Is it? And should the Completed
event be raised on Dispose
of the underlying Socket
(with SocketError.Success
, no less)?
Solution looks like
IPEndPoint ep = new IPEndPoint (new IPAddress (new byte []{ 127, 0, 0, 1 }), 9042);
var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
var connectTimeoutMs = 50;
var connectTask = Task.Factory.FromAsync(
(ipEndPoint, callback, state) => ((Socket)state).BeginConnect(ipEndPoint, callback, state),
asyncResult => ((Socket)asyncResult.AsyncState).EndConnect(asyncResult),
ep, socket);
var timeoutTask = Task.WhenAny(connectTask, Task.Delay(Options.connectTimeoutMs));
if (await timeoutTask != connectTask) {
throw new SocketException((int)SocketError.TimedOut);
}
await connectTask;