2
0

SafeSocketHandle.cs 3.2 KB

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