/src/System.IO.Pipes/src/System/IO/Pipes/PipeCompletionSource.cs

https://gitlab.com/0072016/0072016-corefx- · C# · 185 lines · 135 code · 25 blank · 25 comment · 31 complexity · fed8dce98a048b5bde7b49060280b2fe MD5 · raw file

  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.Security;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using System.Runtime.InteropServices;
  9. namespace System.IO.Pipes
  10. {
  11. internal abstract unsafe class PipeCompletionSource<TResult> : TaskCompletionSource<TResult>
  12. {
  13. private const int NoResult = 0;
  14. private const int ResultSuccess = 1;
  15. private const int ResultError = 2;
  16. private const int RegisteringCancellation = 4;
  17. private const int CompletedCallback = 8;
  18. private readonly CancellationToken _cancellationToken;
  19. private readonly ThreadPoolBoundHandle _threadPoolBinding;
  20. private CancellationTokenRegistration _cancellationRegistration;
  21. private int _errorCode;
  22. private NativeOverlapped* _overlapped;
  23. private int _state;
  24. #if DEBUG
  25. private bool _cancellationHasBeenRegistered;
  26. #endif
  27. // Using RunContinuationsAsynchronously for compat reasons (old API used ThreadPool.QueueUserWorkItem for continuations)
  28. protected PipeCompletionSource(ThreadPoolBoundHandle handle, CancellationToken cancellationToken, object pinData)
  29. : base(TaskCreationOptions.RunContinuationsAsynchronously)
  30. {
  31. Debug.Assert(handle != null, "handle is null");
  32. _threadPoolBinding = handle;
  33. _cancellationToken = cancellationToken;
  34. _state = NoResult;
  35. _overlapped = _threadPoolBinding.AllocateNativeOverlapped((errorCode, numBytes, pOverlapped) =>
  36. {
  37. var completionSource = (PipeCompletionSource<TResult>)ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped);
  38. Debug.Assert(completionSource.Overlapped == pOverlapped);
  39. completionSource.AsyncCallback(errorCode, numBytes);
  40. }, this, pinData);
  41. }
  42. internal NativeOverlapped* Overlapped
  43. {
  44. [SecurityCritical]get { return _overlapped; }
  45. }
  46. internal void RegisterForCancellation()
  47. {
  48. #if DEBUG
  49. Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice");
  50. _cancellationHasBeenRegistered = true;
  51. #endif
  52. // Quick check to make sure that the cancellation token supports cancellation, and that the IO hasn't completed
  53. if (_cancellationToken.CanBeCanceled && Overlapped != null)
  54. {
  55. // Register the cancellation only if the IO hasn't completed
  56. int state = Interlocked.CompareExchange(ref _state, RegisteringCancellation, NoResult);
  57. if (state == NoResult)
  58. {
  59. // Register the cancellation
  60. _cancellationRegistration = _cancellationToken.Register(thisRef => ((PipeCompletionSource<TResult>)thisRef).Cancel(), this);
  61. // Grab the state for case if IO completed while we were setting the registration.
  62. state = Interlocked.Exchange(ref _state, NoResult);
  63. }
  64. else if (state != CompletedCallback)
  65. {
  66. // IO already completed and we have grabbed result state.
  67. // Set NoResult to prevent invocation of CompleteCallback(result state) from AsyncCallback(...)
  68. state = Interlocked.Exchange(ref _state, NoResult);
  69. }
  70. // If we have the result state of completed IO call CompleteCallback(result).
  71. // Otherwise IO not completed.
  72. if ((state & (ResultSuccess | ResultError)) != 0)
  73. {
  74. CompleteCallback(state);
  75. }
  76. }
  77. }
  78. internal void ReleaseResources()
  79. {
  80. _cancellationRegistration.Dispose();
  81. // NOTE: The cancellation must *NOT* be running at this point, or it may observe freed memory
  82. // (this is why we disposed the registration above)
  83. if (Overlapped != null)
  84. {
  85. _threadPoolBinding.FreeNativeOverlapped(Overlapped);
  86. _overlapped = null;
  87. }
  88. }
  89. internal abstract void SetCompletedSynchronously();
  90. protected virtual void AsyncCallback(uint errorCode, uint numBytes)
  91. {
  92. int resultState;
  93. if (errorCode == 0)
  94. {
  95. resultState = ResultSuccess;
  96. }
  97. else
  98. {
  99. resultState = ResultError;
  100. _errorCode = (int)errorCode;
  101. }
  102. // Store the result so that other threads can observe it
  103. // and if no other thread is registering cancellation, continue.
  104. // Otherwise CompleteCallback(resultState) will be invoked by RegisterForCancellation().
  105. if (Interlocked.Exchange(ref _state, resultState) == NoResult)
  106. {
  107. // Now try to prevent invocation of CompleteCallback(resultState) from RegisterForCancellation().
  108. // Otherwise, thread responsible for registering cancellation stole the result and it will invoke CompleteCallback(resultState).
  109. if (Interlocked.Exchange(ref _state, CompletedCallback) != NoResult)
  110. {
  111. CompleteCallback(resultState);
  112. }
  113. }
  114. }
  115. protected abstract void HandleError(int errorCode);
  116. private void Cancel()
  117. {
  118. SafeHandle handle = _threadPoolBinding.Handle;
  119. NativeOverlapped* overlapped = Overlapped;
  120. // If the handle is still valid, attempt to cancel the IO
  121. if (!handle.IsInvalid && !Interop.mincore.CancelIoEx(handle, overlapped))
  122. {
  123. // This case should not have any consequences although
  124. // it will be easier to debug if there exists any special case
  125. // we are not aware of.
  126. int errorCode = Marshal.GetLastWin32Error();
  127. Debug.WriteLine("CancelIoEx finished with error code {0}.", errorCode);
  128. }
  129. }
  130. private void CompleteCallback(int resultState)
  131. {
  132. Debug.Assert(resultState == ResultSuccess || resultState == ResultError, "Unexpected result state " + resultState);
  133. ReleaseResources();
  134. if (resultState == ResultError)
  135. {
  136. if (_errorCode == Interop.mincore.Errors.ERROR_OPERATION_ABORTED)
  137. {
  138. if (_cancellationToken.CanBeCanceled && !_cancellationToken.IsCancellationRequested)
  139. {
  140. // If this is unexpected abortion
  141. TrySetException(Error.GetOperationAborted());
  142. }
  143. else
  144. {
  145. // otherwise set canceled
  146. TrySetCanceled(_cancellationToken);
  147. }
  148. }
  149. else
  150. {
  151. HandleError(_errorCode);
  152. }
  153. }
  154. else
  155. {
  156. SetCompletedSynchronously();
  157. }
  158. }
  159. }
  160. }