@@ -752,31 +752,156 @@ extension Optional where Wrapped == String {
752
752
}
753
753
}
754
754
755
- internal func withAsyncTaskCleanupHandler< Result > (
756
- _ body: ( ) async throws -> Result ,
755
+ internal func withAsyncTaskCleanupHandler< Success > (
756
+ _ body: ( ) async throws -> Success ,
757
757
onCleanup handler: @Sendable @escaping ( ) async -> Void ,
758
758
isolation: isolated ( any Actor ) ? = #isolation
759
- ) async rethrows -> Result {
759
+ ) async rethrows -> Success {
760
+ let runCancellationHandlerPromise = Promise < Bool , Never > ( )
760
761
return try await withThrowingTaskGroup (
761
762
of: Void . self,
762
- returning: Result . self
763
+ returning: Success . self
763
764
) { group in
764
765
group. addTask {
765
766
// Keep this task sleep indefinitely until the parent task is cancelled.
766
767
// `Task.sleep` throws `CancellationError` when the task is canceled
767
768
// before the time ends. We then run the cancel handler.
768
769
do { while true { try await Task . sleep ( nanoseconds: 1_000_000_000 ) } } catch { }
769
770
// Run task cancel handler
770
- await handler ( )
771
+ runCancellationHandlerPromise. fulfill ( with: true )
772
+ }
773
+
774
+ group. addTask {
775
+ if await runCancellationHandlerPromise. value {
776
+ await handler ( )
777
+ }
778
+ }
779
+
780
+ defer {
781
+ group. cancelAll ( )
771
782
}
772
783
773
784
do {
774
785
let result = try await body ( )
775
- group . cancelAll ( )
786
+ runCancellationHandlerPromise . fulfill ( with : false )
776
787
return result
777
788
} catch {
778
- await handler ( )
789
+ runCancellationHandlerPromise . fulfill ( with : true )
779
790
throw error
780
791
}
781
792
}
782
793
}
794
+
795
+ #if canImport(Darwin)
796
+ import os
797
+ #else
798
+ import Synchronization
799
+ #endif
800
+
801
+ /// Represents a placeholder for a value which will be provided by synchronous code running in another execution context.
802
+ internal final class Promise < Success: Sendable , Failure: Swift . Error > : Sendable {
803
+ private struct State {
804
+ var waiters : [ CheckedContinuation < Success , Failure > ] = [ ]
805
+ var value : Result < Success , Failure > ?
806
+ }
807
+
808
+ #if canImport(Darwin)
809
+ private let state : OSAllocatedUnfairLock < State >
810
+ #else
811
+ private let state : Mutex < State >
812
+ #endif
813
+
814
+ /// Creates a new promise in the unfulfilled state.
815
+ public init ( ) {
816
+ #if canImport(Darwin)
817
+ state = OSAllocatedUnfairLock ( initialState: State ( value: nil ) )
818
+ #else
819
+ state = Mutex ( State ( value: nil ) )
820
+ #endif
821
+ }
822
+
823
+ deinit {
824
+ state. withLock { state in
825
+ precondition ( state. waiters. isEmpty, " Deallocated with remaining waiters " )
826
+ }
827
+ }
828
+
829
+ /// Fulfills the promise with the specified result.
830
+ ///
831
+ /// - returns: Whether the promise was already fulfilled.
832
+ @discardableResult
833
+ public func fulfill( with result: Result < Success , Failure > ) -> Bool {
834
+ let ( waiters, alreadyFulfilled) : ( [ CheckedContinuation < Success , Failure > ] , Bool ) = state. withLock { state in
835
+ if state. value != nil {
836
+ return ( [ ] , true )
837
+ }
838
+ state. value = result
839
+ let waiters = state. waiters
840
+ state. waiters. removeAll ( )
841
+ return ( waiters, false )
842
+ }
843
+
844
+ // Resume the continuations outside the lock to avoid potential deadlock if invoked in a cancellation handler.
845
+ for waiter in waiters {
846
+ waiter. resume ( with: result)
847
+ }
848
+
849
+ return alreadyFulfilled
850
+ }
851
+
852
+ /// Fulfills the promise with the specified value.
853
+ ///
854
+ /// - returns: Whether the promise was already fulfilled.
855
+ @discardableResult
856
+ public func fulfill( with value: Success ) -> Bool {
857
+ fulfill ( with: . success( value) )
858
+ }
859
+
860
+ /// Fulfills the promise with the specified error.
861
+ ///
862
+ /// - returns: Whether the promise was already fulfilled.
863
+ @discardableResult
864
+ public func fail( throwing error: Failure ) -> Bool {
865
+ fulfill ( with: . failure( error) )
866
+ }
867
+ }
868
+
869
+ extension Promise where Success == Void {
870
+ /// Fulfills the promise.
871
+ ///
872
+ /// - returns: Whether the promise was already fulfilled.
873
+ @discardableResult
874
+ internal func fulfill( ) -> Bool {
875
+ fulfill ( with: ( ) )
876
+ }
877
+ }
878
+
879
+ extension Promise where Failure == Never {
880
+ /// Suspends if the promise is not yet fulfilled, and returns the value once it is.
881
+ internal var value : Success {
882
+ get async {
883
+ await withCheckedContinuation { continuation in
884
+ let value : Result < Success , Never > ? = state. withLock { state in
885
+ if let value = state. value {
886
+ return value
887
+ } else {
888
+ state. waiters. append ( continuation)
889
+ return nil
890
+ }
891
+ }
892
+
893
+ // Resume the continuations outside the lock to avoid potential deadlock if invoked in a cancellation handler.
894
+ if let value {
895
+ continuation. resume ( with: value)
896
+ }
897
+ }
898
+ }
899
+ }
900
+
901
+ /// Suspends if the promise is not yet fulfilled, and returns the result once it is.
902
+ internal var result : Result < Success , Failure > {
903
+ get async {
904
+ await . success( value)
905
+ }
906
+ }
907
+ }
0 commit comments