SafeSocketHandle.cs 4.0 KB

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