Merge pull request #4033 from ntherning/no-stdcall-for-icalls-on-windows-32-bit
[mono.git] / mcs / class / System / System.Net.Sockets / SafeSocketHandle.cs
index 9c9d8d751280f29fb9e163b4ffdc332051ff0863..87f5e3aab3ed390bea59585f89103b17ea725c81 100644 (file)
@@ -7,7 +7,9 @@
 
 using System;
 using System.IO;
+using System.Text;
 using System.Threading;
+using System.Diagnostics;
 using System.Collections.Generic;
 using Microsoft.Win32.SafeHandles;
 
@@ -16,6 +18,9 @@ namespace System.Net.Sockets {
        sealed class SafeSocketHandle : SafeHandleZeroOrMinusOneIsInvalid {
 
                List<Thread> blocking_threads;
+               Dictionary<Thread, StackTrace> threads_stacktraces;
+
+               bool in_cleanup;
 
                const int SOCKET_CLOSED = 10004;
 
@@ -25,6 +30,9 @@ namespace System.Net.Sockets {
                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
@@ -32,57 +40,56 @@ namespace System.Net.Sockets {
                {
                }
 
-               bool closii;
-
-               /*protected override void Dispose (bool disposing)
-               {
-                       lock (this) {
-                               if (!closii) {
-
-                                       closii = true;
-                                       int error = 0;
-                                       Socket.Blocking_internal (handle, false, out error);
-                                       //AbortRegisteredThreads ();
-                                       Socket.Close_internal (handle, out error);
-                                       //Console.Error.WriteLine ("Closed "+ handle);
-                               }
-                       }
-                       base.Dispose (disposing);
-               }*/
-
                protected override bool ReleaseHandle ()
                {
                        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);
+                                       }
                                }
                        }
 
@@ -103,6 +110,8 @@ namespace System.Net.Sockets {
                                /* 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 ();
@@ -118,17 +127,15 @@ namespace System.Net.Sockets {
                {
                        //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);
                        }
                }
        }