using System;
using System.IO;
+using System.Text;
using System.Threading;
+using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Win32.SafeHandles;
sealed class SafeSocketHandle : SafeHandleZeroOrMinusOneIsInvalid {
List<Thread> blocking_threads;
+ Dictionary<Thread, StackTrace> threads_stacktraces;
+
+ bool in_cleanup;
const int SOCKET_CLOSED = 10004;
public SafeSocketHandle (IntPtr preexistingHandle, bool ownsHandle) : base (ownsHandle)
{
SetHandle (preexistingHandle);
+
+ if (THROW_ON_ABORT_RETRIES)
+ threads_stacktraces = new Dictionary<Thread, StackTrace> ();
}
// This is just for marshalling
int error = 0;
Socket.Blocking_internal (handle, false, out error);
+#if AOT_ONLY_DESKTOP
+ /* It's only for platforms that do not have working syscall abort mechanism, like WatchOS and TvOS */
Socket.Shutdown_internal (handle, SocketShutdown.Both, out error);
+#endif
if (blocking_threads != null) {
- int abort_attempts = 0;
- while (blocking_threads.Count > 0) {
- if (abort_attempts++ >= ABORT_RETRIES) {
- if (THROW_ON_ABORT_RETRIES)
- throw new Exception ("Could not abort registered blocking threads before closing socket.");
-
- // Attempts to close the socket safely failed.
- // We give up, and close the socket with pending blocking system calls.
- // This should not occur, nonetheless if it does this avoids an endless loop.
- break;
- }
-
- /*
- * This method can be called by the DangerousRelease inside RegisterForBlockingSyscall
- * When this happens blocking_threads contains the current thread.
- * We can safely close the socket and throw SocketException in RegisterForBlockingSyscall
- * before the blocking system call.
- */
- lock (blocking_threads) {
+ lock (blocking_threads) {
+ int abort_attempts = 0;
+ while (blocking_threads.Count > 0) {
+ if (abort_attempts++ >= ABORT_RETRIES) {
+ if (THROW_ON_ABORT_RETRIES) {
+ StringBuilder sb = new StringBuilder ();
+ sb.AppendLine ("Could not abort registered blocking threads before closing socket.");
+ foreach (var thread in blocking_threads) {
+ sb.AppendLine ("Thread StackTrace:");
+ sb.AppendLine (threads_stacktraces[thread].ToString ());
+ }
+ sb.AppendLine ();
+
+ throw new Exception (sb.ToString ());
+ }
+
+ // Attempts to close the socket safely failed.
+ // We give up, and close the socket with pending blocking system calls.
+ // This should not occur, nonetheless if it does this avoids an endless loop.
+ break;
+ }
+
+ /*
+ * This method can be called by the DangerousRelease inside RegisterForBlockingSyscall
+ * When this happens blocking_threads contains the current thread.
+ * We can safely close the socket and throw SocketException in RegisterForBlockingSyscall
+ * before the blocking system call.
+ */
if (blocking_threads.Count == 1 && blocking_threads[0] == Thread.CurrentThread)
break;
- }
- AbortRegisteredThreads ();
- // Sleep so other threads can resume
- Thread.Sleep (1);
+ // abort registered threads
+ foreach (var t in blocking_threads)
+ Socket.cancel_blocking_socket_operation (t);
+
+ // Sleep so other threads can resume
+ in_cleanup = true;
+ Monitor.Wait (blocking_threads, 100);
+ }
}
}
/* We must use a finally block here to make this atomic. */
lock (blocking_threads) {
blocking_threads.Add (Thread.CurrentThread);
+ if (THROW_ON_ABORT_RETRIES)
+ threads_stacktraces.Add (Thread.CurrentThread, new StackTrace (true));
}
if (release)
DangerousRelease ();
{
//If this NRE, we're in deep problems because Register Must have
lock (blocking_threads) {
- blocking_threads.Remove (Thread.CurrentThread);
- }
- }
-
- void AbortRegisteredThreads () {
- if (blocking_threads == null)
- return;
+ var current = Thread.CurrentThread;
+ blocking_threads.Remove (current);
+ if (THROW_ON_ABORT_RETRIES) {
+ if (blocking_threads.IndexOf (current) == -1)
+ threads_stacktraces.Remove (current);
+ }
- lock (blocking_threads) {
- foreach (var t in blocking_threads)
- Socket.cancel_blocking_socket_operation (t);
+ if (in_cleanup && blocking_threads.Count == 0)
+ Monitor.Pulse (blocking_threads);
}
}
}