Merge pull request #2720 from mono/fix-39325
[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.Threading;
11 using System.Collections.Generic;
12 using Microsoft.Win32.SafeHandles;
13
14 namespace System.Net.Sockets {
15
16         sealed class SafeSocketHandle : SafeHandleZeroOrMinusOneIsInvalid {
17
18                 List<Thread> blocking_threads;
19                 bool in_cleanup;
20
21                 const int SOCKET_CLOSED = 10004;
22
23                 const int ABORT_RETRIES = 10;
24                 static bool THROW_ON_ABORT_RETRIES = Environment.GetEnvironmentVariable("MONO_TESTS_IN_PROGRESS") == "yes";
25
26                 public SafeSocketHandle (IntPtr preexistingHandle, bool ownsHandle) : base (ownsHandle)
27                 {
28                         SetHandle (preexistingHandle);
29                 }
30
31                 // This is just for marshalling
32                 internal SafeSocketHandle () : base (true)
33                 {
34                 }
35
36                 protected override bool ReleaseHandle ()
37                 {
38                         int error = 0;
39
40                         Socket.Blocking_internal (handle, false, out error);
41 #if MOBILE_STATIC
42                         /* It's only for platforms that do not have working syscall abort mechanism, like WatchOS and TvOS */
43                         Socket.Shutdown_internal (handle, SocketShutdown.Both, out error);
44 #endif
45
46                         if (blocking_threads != null) {
47                                 lock (blocking_threads) {
48                                         int abort_attempts = 0;
49                                         while (blocking_threads.Count > 0) {
50                                                 if (abort_attempts++ >= ABORT_RETRIES) {
51                                                         if (THROW_ON_ABORT_RETRIES)
52                                                                 throw new Exception ("Could not abort registered blocking threads before closing socket.");
53
54                                                         // Attempts to close the socket safely failed.
55                                                         // We give up, and close the socket with pending blocking system calls.
56                                                         // This should not occur, nonetheless if it does this avoids an endless loop.
57                                                         break;
58                                                 }
59
60                                                 /*
61                                                 * This method can be called by the DangerousRelease inside RegisterForBlockingSyscall
62                                                 * When this happens blocking_threads contains the current thread.
63                                                 * We can safely close the socket and throw SocketException in RegisterForBlockingSyscall
64                                                 * before the blocking system call.
65                                                 */
66                                                 if (blocking_threads.Count == 1 && blocking_threads[0] == Thread.CurrentThread)
67                                                         break;
68
69                                                 // abort registered threads
70                                                 foreach (var t in blocking_threads)
71                                                         Socket.cancel_blocking_socket_operation (t);
72
73                                                 // Sleep so other threads can resume
74                                                 in_cleanup = true;
75                                                 Monitor.Wait (blocking_threads, 100);
76                                         }
77                                 }
78                         }
79
80                         Socket.Close_internal (handle, out error);
81
82                         return error == 0;
83                 }
84
85                 public void RegisterForBlockingSyscall ()
86                 {
87                         if (blocking_threads == null)
88                                 Interlocked.CompareExchange (ref blocking_threads, new List<Thread> (), null);
89                         
90                         bool release = false;
91                         try {
92                                 DangerousAddRef (ref release);
93                         } finally {
94                                 /* We must use a finally block here to make this atomic. */
95                                 lock (blocking_threads) {
96                                         blocking_threads.Add (Thread.CurrentThread);
97                                 }
98                                 if (release)
99                                         DangerousRelease ();
100
101                                 // Handle can be closed by DangerousRelease
102                                 if (IsClosed)
103                                         throw new SocketException (SOCKET_CLOSED);
104                         }
105                 }
106
107                 /* This must be called from a finally block! */
108                 public void UnRegisterForBlockingSyscall ()
109                 {
110                         //If this NRE, we're in deep problems because Register Must have
111                         lock (blocking_threads) {
112                                 blocking_threads.Remove (Thread.CurrentThread);
113                                 if (in_cleanup && blocking_threads.Count == 0)
114                                         Monitor.Pulse (blocking_threads);
115                         }
116                 }
117         }
118 }
119