2 // System.Net.Sockets.SafeSocketHandle
5 // Marcos Henrich <marcos.henrich@xamarin.com>
11 using System.Threading;
12 using System.Diagnostics;
13 using System.Collections.Generic;
14 using Microsoft.Win32.SafeHandles;
16 namespace System.Net.Sockets {
18 sealed class SafeSocketHandle : SafeHandleZeroOrMinusOneIsInvalid {
20 List<Thread> blocking_threads;
21 Dictionary<Thread, StackTrace> threads_stacktraces;
25 const int SOCKET_CLOSED = 10004;
27 const int ABORT_RETRIES = 10;
28 static bool THROW_ON_ABORT_RETRIES = Environment.GetEnvironmentVariable("MONO_TESTS_IN_PROGRESS") == "yes";
30 public SafeSocketHandle (IntPtr preexistingHandle, bool ownsHandle) : base (ownsHandle)
32 SetHandle (preexistingHandle);
34 if (THROW_ON_ABORT_RETRIES)
35 threads_stacktraces = new Dictionary<Thread, StackTrace> ();
38 // This is just for marshalling
39 internal SafeSocketHandle () : base (true)
43 protected override bool ReleaseHandle ()
47 Socket.Blocking_internal (handle, false, out error);
49 /* It's only for platforms that do not have working syscall abort mechanism, like WatchOS and TvOS */
50 Socket.Shutdown_internal (handle, SocketShutdown.Both, out error);
53 if (blocking_threads != null) {
54 lock (blocking_threads) {
55 int abort_attempts = 0;
56 while (blocking_threads.Count > 0) {
57 if (abort_attempts++ >= ABORT_RETRIES) {
58 if (THROW_ON_ABORT_RETRIES) {
59 StringBuilder sb = new StringBuilder ();
60 sb.AppendLine ("Could not abort registered blocking threads before closing socket.");
61 foreach (var thread in blocking_threads) {
62 sb.AppendLine ("Thread StackTrace:");
63 sb.AppendLine (threads_stacktraces[thread].ToString ());
67 throw new Exception (sb.ToString ());
70 // Attempts to close the socket safely failed.
71 // We give up, and close the socket with pending blocking system calls.
72 // This should not occur, nonetheless if it does this avoids an endless loop.
77 * This method can be called by the DangerousRelease inside RegisterForBlockingSyscall
78 * When this happens blocking_threads contains the current thread.
79 * We can safely close the socket and throw SocketException in RegisterForBlockingSyscall
80 * before the blocking system call.
82 if (blocking_threads.Count == 1 && blocking_threads[0] == Thread.CurrentThread)
85 // abort registered threads
86 foreach (var t in blocking_threads)
87 Socket.cancel_blocking_socket_operation (t);
89 // Sleep so other threads can resume
91 Monitor.Wait (blocking_threads, 100);
96 Socket.Close_internal (handle, out error);
101 public void RegisterForBlockingSyscall ()
103 if (blocking_threads == null)
104 Interlocked.CompareExchange (ref blocking_threads, new List<Thread> (), null);
106 bool release = false;
108 DangerousAddRef (ref release);
110 /* We must use a finally block here to make this atomic. */
111 lock (blocking_threads) {
112 blocking_threads.Add (Thread.CurrentThread);
113 if (THROW_ON_ABORT_RETRIES)
114 threads_stacktraces.Add (Thread.CurrentThread, new StackTrace (true));
119 // Handle can be closed by DangerousRelease
121 throw new SocketException (SOCKET_CLOSED);
125 /* This must be called from a finally block! */
126 public void UnRegisterForBlockingSyscall ()
128 //If this NRE, we're in deep problems because Register Must have
129 lock (blocking_threads) {
130 blocking_threads.Remove (Thread.CurrentThread);
131 if (THROW_ON_ABORT_RETRIES)
132 threads_stacktraces.Remove (Thread.CurrentThread);
134 if (in_cleanup && blocking_threads.Count == 0)
135 Monitor.Pulse (blocking_threads);