@@ -752,31 +752,154 @@ 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
+ #endif
798
+
799
+ /// Represents a placeholder for a value which will be provided by synchronous code running in another execution context.
800
+ internal final class Promise < Success: Sendable , Failure: Swift . Error > : Sendable {
801
+ private struct State {
802
+ var waiters : [ CheckedContinuation < Success , Failure > ] = [ ]
803
+ var value : Result < Success , Failure > ?
804
+ }
805
+
806
+ #if canImport(Darwin)
807
+ private let state : OSAllocatedUnfairLock < State >
808
+ #else
809
+ private let state : Mutex < State >
810
+ #endif
811
+
812
+ /// Creates a new promise in the unfulfilled state.
813
+ public init ( ) {
814
+ #if canImport(Darwin)
815
+ state = OSAllocatedUnfairLock ( initialState: State ( value: nil ) )
816
+ #else
817
+ state = Mutex ( State ( value: nil ) )
818
+ #endif
819
+ }
820
+
821
+ deinit {
822
+ state. withLock { state in
823
+ precondition ( state. waiters. isEmpty, " Deallocated with remaining waiters " )
824
+ }
825
+ }
826
+
827
+ /// Fulfills the promise with the specified result.
828
+ ///
829
+ /// - returns: Whether the promise was already fulfilled.
830
+ @discardableResult
831
+ public func fulfill( with result: Result < Success , Failure > ) -> Bool {
832
+ let ( waiters, alreadyFulfilled) : ( [ CheckedContinuation < Success , Failure > ] , Bool ) = state. withLock { state in
833
+ if state. value != nil {
834
+ return ( [ ] , true )
835
+ }
836
+ state. value = result
837
+ let waiters = state. waiters
838
+ state. waiters. removeAll ( )
839
+ return ( waiters, false )
840
+ }
841
+
842
+ // Resume the continuations outside the lock to avoid potential deadlock if invoked in a cancellation handler.
843
+ for waiter in waiters {
844
+ waiter. resume ( with: result)
845
+ }
846
+
847
+ return alreadyFulfilled
848
+ }
849
+
850
+ /// Fulfills the promise with the specified value.
851
+ ///
852
+ /// - returns: Whether the promise was already fulfilled.
853
+ @discardableResult
854
+ public func fulfill( with value: Success ) -> Bool {
855
+ fulfill ( with: . success( value) )
856
+ }
857
+
858
+ /// Fulfills the promise with the specified error.
859
+ ///
860
+ /// - returns: Whether the promise was already fulfilled.
861
+ @discardableResult
862
+ public func fail( throwing error: Failure ) -> Bool {
863
+ fulfill ( with: . failure( error) )
864
+ }
865
+ }
866
+
867
+ extension Promise where Success == Void {
868
+ /// Fulfills the promise.
869
+ ///
870
+ /// - returns: Whether the promise was already fulfilled.
871
+ @discardableResult
872
+ internal func fulfill( ) -> Bool {
873
+ fulfill ( with: ( ) )
874
+ }
875
+ }
876
+
877
+ extension Promise where Failure == Never {
878
+ /// Suspends if the promise is not yet fulfilled, and returns the value once it is.
879
+ internal var value : Success {
880
+ get async {
881
+ await withCheckedContinuation { continuation in
882
+ let value : Result < Success , Never > ? = state. withLock { state in
883
+ if let value = state. value {
884
+ return value
885
+ } else {
886
+ state. waiters. append ( continuation)
887
+ return nil
888
+ }
889
+ }
890
+
891
+ // Resume the continuations outside the lock to avoid potential deadlock if invoked in a cancellation handler.
892
+ if let value {
893
+ continuation. resume ( with: value)
894
+ }
895
+ }
896
+ }
897
+ }
898
+
899
+ /// Suspends if the promise is not yet fulfilled, and returns the result once it is.
900
+ internal var result : Result < Success , Failure > {
901
+ get async {
902
+ await . success( value)
903
+ }
904
+ }
905
+ }
0 commit comments