WaitHandle.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Diagnostics;
  5. using System.Diagnostics.CodeAnalysis;
  6. using Microsoft.Win32.SafeHandles;
  7. namespace System.Threading
  8. {
  9. public abstract partial class WaitHandle : MarshalByRefObject, IDisposable
  10. {
  11. internal const int MaxWaitHandles = 64;
  12. protected static readonly IntPtr InvalidHandle = new IntPtr(-1);
  13. // IMPORTANT:
  14. // - Do not add or rearrange fields as the EE depends on this layout.
  15. private SafeWaitHandle? _waitHandle;
  16. [ThreadStatic]
  17. private static SafeWaitHandle?[]? t_safeWaitHandlesForRent;
  18. private protected enum OpenExistingResult
  19. {
  20. Success,
  21. NameNotFound,
  22. PathNotFound,
  23. NameInvalid
  24. }
  25. // The wait result values below match Win32 wait result codes (WAIT_OBJECT_0,
  26. // WAIT_ABANDONED, WAIT_TIMEOUT).
  27. // Successful wait on first object. When waiting for multiple objects the
  28. // return value is (WaitSuccess + waitIndex).
  29. internal const int WaitSuccess = 0;
  30. // The specified object is a mutex object that was not released by the
  31. // thread that owned the mutex object before the owning thread terminated.
  32. // When waiting for multiple objects the return value is (WaitAbandoned +
  33. // waitIndex).
  34. internal const int WaitAbandoned = 0x80;
  35. public const int WaitTimeout = 0x102;
  36. protected WaitHandle()
  37. {
  38. }
  39. [Obsolete("Use the SafeWaitHandle property instead.")]
  40. public virtual IntPtr Handle
  41. {
  42. get
  43. {
  44. return _waitHandle == null ? InvalidHandle : _waitHandle.DangerousGetHandle();
  45. }
  46. set
  47. {
  48. if (value == InvalidHandle)
  49. {
  50. // This line leaks a handle. However, it's currently
  51. // not perfectly clear what the right behavior is here
  52. // anyways. This preserves Everett behavior. We should
  53. // ideally do these things:
  54. // *) Expose a settable SafeHandle property on WaitHandle.
  55. // *) Expose a settable OwnsHandle property on SafeHandle.
  56. if (_waitHandle != null)
  57. {
  58. _waitHandle.SetHandleAsInvalid();
  59. _waitHandle = null;
  60. }
  61. }
  62. else
  63. {
  64. _waitHandle = new SafeWaitHandle(value, true);
  65. }
  66. }
  67. }
  68. [AllowNull]
  69. public SafeWaitHandle SafeWaitHandle
  70. {
  71. get
  72. {
  73. if (_waitHandle == null)
  74. {
  75. _waitHandle = new SafeWaitHandle(InvalidHandle, false);
  76. }
  77. return _waitHandle;
  78. }
  79. set
  80. {
  81. _waitHandle = value;
  82. }
  83. }
  84. internal static int ToTimeoutMilliseconds(TimeSpan timeout)
  85. {
  86. var timeoutMilliseconds = (long)timeout.TotalMilliseconds;
  87. if (timeoutMilliseconds < -1)
  88. {
  89. throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  90. }
  91. if (timeoutMilliseconds > int.MaxValue)
  92. {
  93. throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
  94. }
  95. return (int)timeoutMilliseconds;
  96. }
  97. public virtual void Close() => Dispose();
  98. protected virtual void Dispose(bool explicitDisposing)
  99. {
  100. _waitHandle?.Close();
  101. }
  102. public void Dispose()
  103. {
  104. Dispose(true);
  105. GC.SuppressFinalize(this);
  106. }
  107. public virtual bool WaitOne(int millisecondsTimeout)
  108. {
  109. if (millisecondsTimeout < -1)
  110. {
  111. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  112. }
  113. return WaitOneNoCheck(millisecondsTimeout);
  114. }
  115. private bool WaitOneNoCheck(int millisecondsTimeout)
  116. {
  117. Debug.Assert(millisecondsTimeout >= -1);
  118. // The field value is modifiable via the public <see cref="WaitHandle.SafeWaitHandle"/> property, save it locally
  119. // to ensure that one instance is used in all places in this method
  120. SafeWaitHandle waitHandle = _waitHandle ?? throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
  121. bool success = false;
  122. try
  123. {
  124. int waitResult;
  125. waitHandle.DangerousAddRef(ref success);
  126. SynchronizationContext? context = SynchronizationContext.Current;
  127. if (context != null && context.IsWaitNotificationRequired())
  128. {
  129. waitResult = context.Wait(new[] { waitHandle.DangerousGetHandle() }, false, millisecondsTimeout);
  130. }
  131. else
  132. {
  133. waitResult = WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout);
  134. }
  135. if (waitResult == WaitAbandoned)
  136. {
  137. throw new AbandonedMutexException();
  138. }
  139. return waitResult != WaitTimeout;
  140. }
  141. finally
  142. {
  143. if (success)
  144. waitHandle.DangerousRelease();
  145. }
  146. }
  147. // Returns an array for storing SafeWaitHandles in WaitMultiple calls. The array
  148. // is reused for subsequent calls to reduce GC pressure.
  149. private static SafeWaitHandle?[] RentSafeWaitHandleArray(int capacity)
  150. {
  151. SafeWaitHandle?[]? safeWaitHandles = t_safeWaitHandlesForRent;
  152. t_safeWaitHandlesForRent = null;
  153. // t_safeWaitHandlesForRent can be null when it was not initialized yet or
  154. // if a re-entrant wait is performed and the array is already rented. In
  155. // that case we just allocate a new one and reuse it as necessary.
  156. int currentLength = (safeWaitHandles != null) ? safeWaitHandles.Length : 0;
  157. if (currentLength < capacity)
  158. {
  159. safeWaitHandles = new SafeWaitHandle[Math.Max(capacity,
  160. Math.Min(MaxWaitHandles, 2 * currentLength))];
  161. }
  162. return safeWaitHandles;
  163. }
  164. private static void ReturnSafeWaitHandleArray(SafeWaitHandle?[]? safeWaitHandles)
  165. => t_safeWaitHandlesForRent = safeWaitHandles;
  166. /// <summary>
  167. /// Obtains all of the corresponding safe wait handles and adds a ref to each. Since the <see cref="SafeWaitHandle"/>
  168. /// property is publically modifiable, this makes sure that we add and release refs one the same set of safe wait
  169. /// handles to keep them alive during a multi-wait operation.
  170. /// </summary>
  171. private static void ObtainSafeWaitHandles(
  172. ReadOnlySpan<WaitHandle> waitHandles,
  173. Span<SafeWaitHandle?> safeWaitHandles,
  174. Span<IntPtr> unsafeWaitHandles)
  175. {
  176. Debug.Assert(waitHandles != null);
  177. Debug.Assert(waitHandles.Length > 0);
  178. Debug.Assert(waitHandles.Length <= MaxWaitHandles);
  179. bool lastSuccess = true;
  180. SafeWaitHandle? lastSafeWaitHandle = null;
  181. try
  182. {
  183. for (int i = 0; i < waitHandles.Length; ++i)
  184. {
  185. WaitHandle waitHandle = waitHandles[i];
  186. if (waitHandle == null)
  187. {
  188. throw new ArgumentNullException("waitHandles[" + i + ']', SR.ArgumentNull_ArrayElement);
  189. }
  190. SafeWaitHandle safeWaitHandle = waitHandle._waitHandle ??
  191. // Throw ObjectDisposedException for backward compatibility even though it is not representative of the issue
  192. throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
  193. lastSafeWaitHandle = safeWaitHandle;
  194. lastSuccess = false;
  195. safeWaitHandle.DangerousAddRef(ref lastSuccess);
  196. safeWaitHandles[i] = safeWaitHandle;
  197. unsafeWaitHandles[i] = safeWaitHandle.DangerousGetHandle();
  198. }
  199. }
  200. catch
  201. {
  202. for (int i = 0; i < waitHandles.Length; ++i)
  203. {
  204. SafeWaitHandle? safeWaitHandle = safeWaitHandles[i];
  205. if (safeWaitHandle == null)
  206. {
  207. break;
  208. }
  209. safeWaitHandle.DangerousRelease();
  210. safeWaitHandles[i] = null;
  211. if (safeWaitHandle == lastSafeWaitHandle)
  212. {
  213. lastSafeWaitHandle = null;
  214. lastSuccess = true;
  215. }
  216. }
  217. if (!lastSuccess)
  218. {
  219. Debug.Assert(lastSafeWaitHandle != null);
  220. lastSafeWaitHandle.DangerousRelease();
  221. }
  222. throw;
  223. }
  224. }
  225. private static int WaitMultiple(WaitHandle[] waitHandles, bool waitAll, int millisecondsTimeout)
  226. {
  227. if (waitHandles == null)
  228. {
  229. throw new ArgumentNullException(nameof(waitHandles), SR.ArgumentNull_Waithandles);
  230. }
  231. return WaitMultiple(new ReadOnlySpan<WaitHandle>(waitHandles), waitAll, millisecondsTimeout);
  232. }
  233. private static int WaitMultiple(ReadOnlySpan<WaitHandle> waitHandles, bool waitAll, int millisecondsTimeout)
  234. {
  235. if (waitHandles.Length == 0)
  236. {
  237. //
  238. // Some history: in CLR 1.0 and 1.1, we threw ArgumentException in this case, which was correct.
  239. // Somehow, in 2.0, this became ArgumentNullException. This was not fixed until Silverlight 2,
  240. // which went back to ArgumentException.
  241. //
  242. // Now we're in a bit of a bind. Backward-compatibility requires us to keep throwing ArgumentException
  243. // in CoreCLR, and ArgumentNullException in the desktop CLR. This is ugly, but so is breaking
  244. // user code.
  245. //
  246. throw new ArgumentException(SR.Argument_EmptyWaithandleArray, nameof(waitHandles));
  247. }
  248. if (waitHandles.Length > MaxWaitHandles)
  249. {
  250. throw new NotSupportedException(SR.NotSupported_MaxWaitHandles);
  251. }
  252. if (millisecondsTimeout < -1)
  253. {
  254. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  255. }
  256. SynchronizationContext? context = SynchronizationContext.Current;
  257. bool useWaitContext = context != null && context.IsWaitNotificationRequired();
  258. SafeWaitHandle?[]? safeWaitHandles = RentSafeWaitHandleArray(waitHandles.Length);
  259. try
  260. {
  261. int waitResult;
  262. if (useWaitContext)
  263. {
  264. IntPtr[] unsafeWaitHandles = new IntPtr[waitHandles.Length];
  265. ObtainSafeWaitHandles(waitHandles, safeWaitHandles, unsafeWaitHandles);
  266. waitResult = context!.Wait(unsafeWaitHandles, waitAll, millisecondsTimeout);
  267. }
  268. else
  269. {
  270. Span<IntPtr> unsafeWaitHandles = stackalloc IntPtr[waitHandles.Length];
  271. ObtainSafeWaitHandles(waitHandles, safeWaitHandles, unsafeWaitHandles);
  272. waitResult = WaitMultipleIgnoringSyncContext(unsafeWaitHandles, waitAll, millisecondsTimeout);
  273. }
  274. if (waitResult >= WaitAbandoned && waitResult < WaitAbandoned + waitHandles.Length)
  275. {
  276. if (waitAll)
  277. {
  278. // In the case of WaitAll the OS will only provide the information that mutex was abandoned.
  279. // It won't tell us which one. So we can't set the Index or provide access to the Mutex
  280. throw new AbandonedMutexException();
  281. }
  282. waitResult -= WaitAbandoned;
  283. throw new AbandonedMutexException(waitResult, waitHandles[waitResult]);
  284. }
  285. return waitResult;
  286. }
  287. finally
  288. {
  289. for (int i = 0; i < waitHandles.Length; ++i)
  290. {
  291. if (safeWaitHandles[i] != null)
  292. {
  293. safeWaitHandles[i]!.DangerousRelease(); // TODO-NULLABLE: Indexer nullability tracked (https://github.com/dotnet/roslyn/issues/34644)
  294. safeWaitHandles[i] = null;
  295. }
  296. }
  297. ReturnSafeWaitHandleArray(safeWaitHandles);
  298. }
  299. }
  300. private static bool SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn, int millisecondsTimeout)
  301. {
  302. if (toSignal == null)
  303. {
  304. throw new ArgumentNullException(nameof(toSignal));
  305. }
  306. if (toWaitOn == null)
  307. {
  308. throw new ArgumentNullException(nameof(toWaitOn));
  309. }
  310. if (millisecondsTimeout < -1)
  311. {
  312. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  313. }
  314. // The field value is modifiable via the public <see cref="WaitHandle.SafeWaitHandle"/> property, save it locally
  315. // to ensure that one instance is used in all places in this method
  316. SafeWaitHandle? safeWaitHandleToSignal = toSignal._waitHandle;
  317. SafeWaitHandle? safeWaitHandleToWaitOn = toWaitOn._waitHandle;
  318. if (safeWaitHandleToSignal == null || safeWaitHandleToWaitOn == null)
  319. {
  320. // Throw ObjectDisposedException for backward compatibility even though it is not be representative of the issue
  321. throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
  322. }
  323. bool successSignal = false, successWait = false;
  324. try
  325. {
  326. safeWaitHandleToSignal.DangerousAddRef(ref successSignal);
  327. safeWaitHandleToWaitOn.DangerousAddRef(ref successWait);
  328. int ret = SignalAndWaitCore(
  329. safeWaitHandleToSignal.DangerousGetHandle(),
  330. safeWaitHandleToWaitOn.DangerousGetHandle(),
  331. millisecondsTimeout);
  332. if (ret == WaitAbandoned)
  333. {
  334. throw new AbandonedMutexException();
  335. }
  336. return ret != WaitTimeout;
  337. }
  338. finally
  339. {
  340. if (successWait)
  341. {
  342. safeWaitHandleToWaitOn.DangerousRelease();
  343. }
  344. if (successSignal)
  345. {
  346. safeWaitHandleToSignal.DangerousRelease();
  347. }
  348. }
  349. }
  350. public virtual bool WaitOne(TimeSpan timeout) => WaitOneNoCheck(ToTimeoutMilliseconds(timeout));
  351. public virtual bool WaitOne() => WaitOneNoCheck(-1);
  352. public virtual bool WaitOne(int millisecondsTimeout, bool exitContext) => WaitOne(millisecondsTimeout);
  353. public virtual bool WaitOne(TimeSpan timeout, bool exitContext) => WaitOneNoCheck(ToTimeoutMilliseconds(timeout));
  354. public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout) =>
  355. WaitMultiple(waitHandles, true, millisecondsTimeout) != WaitTimeout;
  356. public static bool WaitAll(WaitHandle[] waitHandles, TimeSpan timeout) =>
  357. WaitMultiple(waitHandles, true, ToTimeoutMilliseconds(timeout)) != WaitTimeout;
  358. public static bool WaitAll(WaitHandle[] waitHandles) =>
  359. WaitMultiple(waitHandles, true, -1) != WaitTimeout;
  360. public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) =>
  361. WaitMultiple(waitHandles, true, millisecondsTimeout) != WaitTimeout;
  362. public static bool WaitAll(WaitHandle[] waitHandles, TimeSpan timeout, bool exitContext) =>
  363. WaitMultiple(waitHandles, true, ToTimeoutMilliseconds(timeout)) != WaitTimeout;
  364. public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout) =>
  365. WaitMultiple(waitHandles, false, millisecondsTimeout);
  366. public static int WaitAny(WaitHandle[] waitHandles, TimeSpan timeout) =>
  367. WaitMultiple(waitHandles, false, ToTimeoutMilliseconds(timeout));
  368. public static int WaitAny(WaitHandle[] waitHandles) =>
  369. WaitMultiple(waitHandles, false, -1);
  370. public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) =>
  371. WaitMultiple(waitHandles, false, millisecondsTimeout);
  372. public static int WaitAny(WaitHandle[] waitHandles, TimeSpan timeout, bool exitContext) =>
  373. WaitMultiple(waitHandles, false, ToTimeoutMilliseconds(timeout));
  374. public static bool SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn) =>
  375. SignalAndWait(toSignal, toWaitOn, -1);
  376. public static bool SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn, TimeSpan timeout, bool exitContext) =>
  377. SignalAndWait(toSignal, toWaitOn, ToTimeoutMilliseconds(timeout));
  378. public static bool SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn, int millisecondsTimeout, bool exitContext) =>
  379. SignalAndWait(toSignal, toWaitOn, millisecondsTimeout);
  380. }
  381. }