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