Use __cdecl rather than __stdcall for icalls on Windows 32-bit
[mono.git] / mcs / class / System / System.Net.Sockets / SafeSocketHandle.cs
1 //
2 // System.Net.Sockets.SafeSocketHandle
3 //
4 // Authors:
5 //      Marcos Henrich  <marcos.henrich@xamarin.com>
6 //
7
8 using System;
9 using System.IO;
10 using System.Text;
11 using System.Threading;
12 using System.Diagnostics;
13 using System.Collections.Generic;
14 using Microsoft.Win32.SafeHandles;
15
16 namespace System.Net.Sockets {
17
18         sealed class SafeSocketHandle : SafeHandleZeroOrMinusOneIsInvalid {
19
20                 List<Thread> blocking_threads;
21                 Dictionary<Thread, StackTrace> threads_stacktraces;
22
23                 bool in_cleanup;
24
25                 const int SOCKET_CLOSED = 10004;
26
27                 const int ABORT_RETRIES = 10;
28                 static bool THROW_ON_ABORT_RETRIES = Environment.GetEnvironmentVariable("MONO_TESTS_IN_PROGRESS") == "yes";
29
30                 public SafeSocketHandle (IntPtr preexistingHandle, bool ownsHandle) : base (ownsHandle)
31                 {
32                         SetHandle (preexistingHandle);
33
34                         if (THROW_ON_ABORT_RETRIES)
35                                 threads_stacktraces = new Dictionary<Thread, StackTrace> ();
36                 }
37
38                 // This is just for marshalling
39                 internal SafeSocketHandle () : base (true)
40                 {
41                 }
42
43                 protected override bool ReleaseHandle ()
44                 {
45                         int error = 0;
46
47                         Socket.Blocking_internal (handle, false, out error);
48 #if AOT_ONLY_DESKTOP
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);
51 #endif
52
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 ());
64                                                                 }
65                                                                 sb.AppendLine ();
66
67                                                                 throw new Exception (sb.ToString ());
68                                                         }
69
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.
73                                                         break;
74                                                 }
75
76                                                 /*
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.
81                                                 */
82                                                 if (blocking_threads.Count == 1 && blocking_threads[0] == Thread.CurrentThread)
83                                                         break;
84
85                                                 // abort registered threads
86                                                 foreach (var t in blocking_threads)
87                                                         Socket.cancel_blocking_socket_operation (t);
88
89                                                 // Sleep so other threads can resume
90                                                 in_cleanup = true;
91                                                 Monitor.Wait (blocking_threads, 100);
92                                         }
93                                 }
94                         }
95
96                         Socket.Close_internal (handle, out error);
97
98                         return error == 0;
99                 }
100
101                 public void RegisterForBlockingSyscall ()
102                 {
103                         if (blocking_threads == null)
104                                 Interlocked.CompareExchange (ref blocking_threads, new List<Thread> (), null);
105                         
106                         bool release = false;
107                         try {
108                                 DangerousAddRef (ref release);
109                         } finally {
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));
115                                 }
116                                 if (release)
117                                         DangerousRelease ();
118
119                                 // Handle can be closed by DangerousRelease
120                                 if (IsClosed)
121                                         throw new SocketException (SOCKET_CLOSED);
122                         }
123                 }
124
125                 /* This must be called from a finally block! */
126                 public void UnRegisterForBlockingSyscall ()
127                 {
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);
133
134                                 if (in_cleanup && blocking_threads.Count == 0)
135                                         Monitor.Pulse (blocking_threads);
136                         }
137                 }
138         }
139 }
140