/
SafeSocketHandle.cs
140 lines (114 loc) · 3.95 KB
/
SafeSocketHandle.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//
// System.Net.Sockets.SafeSocketHandle
//
// Authors:
// Marcos Henrich <marcos.henrich@xamarin.com>
//
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Win32.SafeHandles;
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;
const int ABORT_RETRIES = 10;
static bool THROW_ON_ABORT_RETRIES = Environment.GetEnvironmentVariable("MONO_TESTS_IN_PROGRESS") == "yes";
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
internal SafeSocketHandle () : base (true)
{
}
protected override bool ReleaseHandle ()
{
int error = 0;
Socket.Blocking_internal (handle, false, out error);
#if MOBILE_STATIC
/* 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) {
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;
// 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);
}
}
}
Socket.Close_internal (handle, out error);
return error == 0;
}
public void RegisterForBlockingSyscall ()
{
if (blocking_threads == null)
Interlocked.CompareExchange (ref blocking_threads, new List<Thread> (), null);
bool release = false;
try {
DangerousAddRef (ref release);
} finally {
/* 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 ();
// Handle can be closed by DangerousRelease
if (IsClosed)
throw new SocketException (SOCKET_CLOSED);
}
}
/* This must be called from a finally block! */
public void UnRegisterForBlockingSyscall ()
{
//If this NRE, we're in deep problems because Register Must have
lock (blocking_threads) {
blocking_threads.Remove (Thread.CurrentThread);
if (THROW_ON_ABORT_RETRIES)
threads_stacktraces.Remove (Thread.CurrentThread);
if (in_cleanup && blocking_threads.Count == 0)
Monitor.Pulse (blocking_threads);
}
}
}
}