diff --git a/AsyncObjects.xcodeproj/project.pbxproj b/AsyncObjects.xcodeproj/project.pbxproj index 9995bf5b..2e33008b 100644 --- a/AsyncObjects.xcodeproj/project.pbxproj +++ b/AsyncObjects.xcodeproj/project.pbxproj @@ -9,11 +9,11 @@ /* Begin PBXAggregateTarget section */ asyncobjects::AsyncObjectsPackageTests::ProductTarget /* AsyncObjectsPackageTests */ = { isa = PBXAggregateTarget; - buildConfigurationList = OBJ_132 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */; + buildConfigurationList = OBJ_145 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */; buildPhases = ( ); dependencies = ( - OBJ_135 /* PBXTargetDependency */, + OBJ_148 /* PBXTargetDependency */, ); name = AsyncObjectsPackageTests; productName = AsyncObjectsPackageTests; @@ -21,191 +21,201 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 0663FA4A7B71E49448BE9547 /* AsyncObjects.docc in Sources */ = {isa = PBXBuildFile; fileRef = B7A072784A06B1A1C700CC40 /* AsyncObjects.docc */; }; - OBJ_108 /* AsyncCountdownEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* AsyncCountdownEvent.swift */; }; - OBJ_109 /* AsyncEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* AsyncEvent.swift */; }; - OBJ_110 /* AsyncObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* AsyncObject.swift */; }; - OBJ_111 /* AsyncSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* AsyncSemaphore.swift */; }; - OBJ_112 /* CancellationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* CancellationSource.swift */; }; - OBJ_113 /* Continuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* Continuable.swift */; }; - OBJ_114 /* Future.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Future.swift */; }; - OBJ_115 /* Locker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* Locker.swift */; }; - OBJ_116 /* SafeContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* SafeContinuation.swift */; }; - OBJ_117 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* Task.swift */; }; - OBJ_118 /* TaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* TaskOperation.swift */; }; - OBJ_119 /* TaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* TaskQueue.swift */; }; - OBJ_120 /* TaskTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* TaskTracker.swift */; }; - OBJ_122 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; - OBJ_130 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; - OBJ_141 /* AsyncCountdownEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* AsyncCountdownEventTests.swift */; }; - OBJ_142 /* AsyncEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* AsyncEventTests.swift */; }; - OBJ_143 /* AsyncObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* AsyncObjectTests.swift */; }; - OBJ_144 /* AsyncSemaphoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* AsyncSemaphoreTests.swift */; }; - OBJ_145 /* CancellationSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* CancellationSourceTests.swift */; }; - OBJ_146 /* LockerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* LockerTests.swift */; }; - OBJ_147 /* NonThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* NonThrowingFutureTests.swift */; }; - OBJ_148 /* SafeContinuationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* SafeContinuationTests.swift */; }; - OBJ_149 /* StandardLibraryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* StandardLibraryTests.swift */; }; - OBJ_150 /* TaskOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* TaskOperationTests.swift */; }; - OBJ_151 /* TaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* TaskQueueTests.swift */; }; - OBJ_152 /* ThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* ThrowingFutureTests.swift */; }; - OBJ_153 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* XCTestCase.swift */; }; - OBJ_155 /* AsyncObjects.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */; }; - OBJ_156 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; - OBJ_163 /* _HashTable+Bucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_45 /* _HashTable+Bucket.swift */; }; - OBJ_164 /* _HashTable+BucketIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_46 /* _HashTable+BucketIterator.swift */; }; - OBJ_165 /* _HashTable+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_47 /* _HashTable+Constants.swift */; }; - OBJ_166 /* _HashTable+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_48 /* _HashTable+CustomStringConvertible.swift */; }; - OBJ_167 /* _HashTable+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_49 /* _HashTable+Testing.swift */; }; - OBJ_168 /* _HashTable+UnsafeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_50 /* _HashTable+UnsafeHandle.swift */; }; - OBJ_169 /* _HashTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_51 /* _HashTable.swift */; }; - OBJ_170 /* _Hashtable+Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_52 /* _Hashtable+Header.swift */; }; - OBJ_171 /* OrderedDictionary+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_54 /* OrderedDictionary+Codable.swift */; }; - OBJ_172 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_55 /* OrderedDictionary+CustomDebugStringConvertible.swift */; }; - OBJ_173 /* OrderedDictionary+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_56 /* OrderedDictionary+CustomReflectable.swift */; }; - OBJ_174 /* OrderedDictionary+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_57 /* OrderedDictionary+CustomStringConvertible.swift */; }; - OBJ_175 /* OrderedDictionary+Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* OrderedDictionary+Deprecations.swift */; }; - OBJ_176 /* OrderedDictionary+Elements+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_59 /* OrderedDictionary+Elements+SubSequence.swift */; }; - OBJ_177 /* OrderedDictionary+Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_60 /* OrderedDictionary+Elements.swift */; }; - OBJ_178 /* OrderedDictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_61 /* OrderedDictionary+Equatable.swift */; }; - OBJ_179 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_62 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */; }; - OBJ_180 /* OrderedDictionary+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_63 /* OrderedDictionary+Hashable.swift */; }; - OBJ_181 /* OrderedDictionary+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_64 /* OrderedDictionary+Initializers.swift */; }; - OBJ_182 /* OrderedDictionary+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_65 /* OrderedDictionary+Invariants.swift */; }; - OBJ_183 /* OrderedDictionary+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_66 /* OrderedDictionary+Partial MutableCollection.swift */; }; - OBJ_184 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_67 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */; }; - OBJ_185 /* OrderedDictionary+Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_68 /* OrderedDictionary+Sequence.swift */; }; - OBJ_186 /* OrderedDictionary+Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_69 /* OrderedDictionary+Values.swift */; }; - OBJ_187 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_70 /* OrderedDictionary.swift */; }; - OBJ_188 /* OrderedSet+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_72 /* OrderedSet+Codable.swift */; }; - OBJ_189 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_73 /* OrderedSet+CustomDebugStringConvertible.swift */; }; - OBJ_190 /* OrderedSet+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_74 /* OrderedSet+CustomReflectable.swift */; }; - OBJ_191 /* OrderedSet+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_75 /* OrderedSet+CustomStringConvertible.swift */; }; - OBJ_192 /* OrderedSet+Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_76 /* OrderedSet+Diffing.swift */; }; - OBJ_193 /* OrderedSet+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_77 /* OrderedSet+Equatable.swift */; }; - OBJ_194 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_78 /* OrderedSet+ExpressibleByArrayLiteral.swift */; }; - OBJ_195 /* OrderedSet+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_79 /* OrderedSet+Hashable.swift */; }; - OBJ_196 /* OrderedSet+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_80 /* OrderedSet+Initializers.swift */; }; - OBJ_197 /* OrderedSet+Insertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_81 /* OrderedSet+Insertions.swift */; }; - OBJ_198 /* OrderedSet+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_82 /* OrderedSet+Invariants.swift */; }; - OBJ_199 /* OrderedSet+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_83 /* OrderedSet+Partial MutableCollection.swift */; }; - OBJ_200 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_84 /* OrderedSet+Partial RangeReplaceableCollection.swift */; }; - OBJ_201 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_85 /* OrderedSet+Partial SetAlgebra+Basics.swift */; }; - OBJ_202 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_86 /* OrderedSet+Partial SetAlgebra+Operations.swift */; }; - OBJ_203 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_87 /* OrderedSet+Partial SetAlgebra+Predicates.swift */; }; - OBJ_204 /* OrderedSet+RandomAccessCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_88 /* OrderedSet+RandomAccessCollection.swift */; }; - OBJ_205 /* OrderedSet+ReserveCapacity.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_89 /* OrderedSet+ReserveCapacity.swift */; }; - OBJ_206 /* OrderedSet+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_90 /* OrderedSet+SubSequence.swift */; }; - OBJ_207 /* OrderedSet+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_91 /* OrderedSet+Testing.swift */; }; - OBJ_208 /* OrderedSet+UnorderedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_92 /* OrderedSet+UnorderedView.swift */; }; - OBJ_209 /* OrderedSet+UnstableInternals.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_93 /* OrderedSet+UnstableInternals.swift */; }; - OBJ_210 /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_94 /* OrderedSet.swift */; }; - OBJ_211 /* RandomAccessCollection+Offsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_96 /* RandomAccessCollection+Offsets.swift */; }; - OBJ_212 /* _UnsafeBitset.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_97 /* _UnsafeBitset.swift */; }; - OBJ_219 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_98 /* Package.swift */; }; + DFEF4D3A44700284C230A854 /* AsyncObjects.docc in Sources */ = {isa = PBXBuildFile; fileRef = EB6E2F9D4FEF6DDD65D0B251 /* AsyncObjects.docc */; }; + OBJ_116 /* AsyncCountdownEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* AsyncCountdownEvent.swift */; }; + OBJ_117 /* AsyncEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* AsyncEvent.swift */; }; + OBJ_118 /* AsyncObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* AsyncObject.swift */; }; + OBJ_119 /* AsyncSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* AsyncSemaphore.swift */; }; + OBJ_120 /* CancellationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* CancellationSource.swift */; }; + OBJ_121 /* Continuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Continuable.swift */; }; + OBJ_122 /* ContinuableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* ContinuableCollection.swift */; }; + OBJ_123 /* GlobalContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* GlobalContinuation.swift */; }; + OBJ_124 /* SafeContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* SafeContinuation.swift */; }; + OBJ_125 /* SynchronizedContinuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* SynchronizedContinuable.swift */; }; + OBJ_126 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* Task.swift */; }; + OBJ_127 /* TaskGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* TaskGroup.swift */; }; + OBJ_128 /* Future.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* Future.swift */; }; + OBJ_129 /* Exclusible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* Exclusible.swift */; }; + OBJ_130 /* Locker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* Locker.swift */; }; + OBJ_131 /* TaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* TaskOperation.swift */; }; + OBJ_132 /* TaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* TaskQueue.swift */; }; + OBJ_133 /* TaskTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* TaskTracker.swift */; }; + OBJ_135 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; + OBJ_143 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; + OBJ_154 /* AsyncCountdownEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* AsyncCountdownEventTests.swift */; }; + OBJ_155 /* AsyncEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* AsyncEventTests.swift */; }; + OBJ_156 /* AsyncObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* AsyncObjectTests.swift */; }; + OBJ_157 /* AsyncSemaphoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* AsyncSemaphoreTests.swift */; }; + OBJ_158 /* CancellationSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* CancellationSourceTests.swift */; }; + OBJ_159 /* LockerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_39 /* LockerTests.swift */; }; + OBJ_160 /* NonThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_40 /* NonThrowingFutureTests.swift */; }; + OBJ_161 /* SafeContinuationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_41 /* SafeContinuationTests.swift */; }; + OBJ_162 /* StandardLibraryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_42 /* StandardLibraryTests.swift */; }; + OBJ_163 /* TaskOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_43 /* TaskOperationTests.swift */; }; + OBJ_164 /* TaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_44 /* TaskQueueTests.swift */; }; + OBJ_165 /* ThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_45 /* ThrowingFutureTests.swift */; }; + OBJ_166 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_46 /* XCTestCase.swift */; }; + OBJ_168 /* AsyncObjects.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */; }; + OBJ_169 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; + OBJ_176 /* _HashTable+Bucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_53 /* _HashTable+Bucket.swift */; }; + OBJ_177 /* _HashTable+BucketIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_54 /* _HashTable+BucketIterator.swift */; }; + OBJ_178 /* _HashTable+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_55 /* _HashTable+Constants.swift */; }; + OBJ_179 /* _HashTable+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_56 /* _HashTable+CustomStringConvertible.swift */; }; + OBJ_180 /* _HashTable+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_57 /* _HashTable+Testing.swift */; }; + OBJ_181 /* _HashTable+UnsafeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* _HashTable+UnsafeHandle.swift */; }; + OBJ_182 /* _HashTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_59 /* _HashTable.swift */; }; + OBJ_183 /* _Hashtable+Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_60 /* _Hashtable+Header.swift */; }; + OBJ_184 /* OrderedDictionary+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_62 /* OrderedDictionary+Codable.swift */; }; + OBJ_185 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_63 /* OrderedDictionary+CustomDebugStringConvertible.swift */; }; + OBJ_186 /* OrderedDictionary+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_64 /* OrderedDictionary+CustomReflectable.swift */; }; + OBJ_187 /* OrderedDictionary+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_65 /* OrderedDictionary+CustomStringConvertible.swift */; }; + OBJ_188 /* OrderedDictionary+Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_66 /* OrderedDictionary+Deprecations.swift */; }; + OBJ_189 /* OrderedDictionary+Elements+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_67 /* OrderedDictionary+Elements+SubSequence.swift */; }; + OBJ_190 /* OrderedDictionary+Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_68 /* OrderedDictionary+Elements.swift */; }; + OBJ_191 /* OrderedDictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_69 /* OrderedDictionary+Equatable.swift */; }; + OBJ_192 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_70 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */; }; + OBJ_193 /* OrderedDictionary+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_71 /* OrderedDictionary+Hashable.swift */; }; + OBJ_194 /* OrderedDictionary+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_72 /* OrderedDictionary+Initializers.swift */; }; + OBJ_195 /* OrderedDictionary+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_73 /* OrderedDictionary+Invariants.swift */; }; + OBJ_196 /* OrderedDictionary+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_74 /* OrderedDictionary+Partial MutableCollection.swift */; }; + OBJ_197 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_75 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */; }; + OBJ_198 /* OrderedDictionary+Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_76 /* OrderedDictionary+Sequence.swift */; }; + OBJ_199 /* OrderedDictionary+Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_77 /* OrderedDictionary+Values.swift */; }; + OBJ_200 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_78 /* OrderedDictionary.swift */; }; + OBJ_201 /* OrderedSet+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_80 /* OrderedSet+Codable.swift */; }; + OBJ_202 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_81 /* OrderedSet+CustomDebugStringConvertible.swift */; }; + OBJ_203 /* OrderedSet+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_82 /* OrderedSet+CustomReflectable.swift */; }; + OBJ_204 /* OrderedSet+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_83 /* OrderedSet+CustomStringConvertible.swift */; }; + OBJ_205 /* OrderedSet+Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_84 /* OrderedSet+Diffing.swift */; }; + OBJ_206 /* OrderedSet+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_85 /* OrderedSet+Equatable.swift */; }; + OBJ_207 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_86 /* OrderedSet+ExpressibleByArrayLiteral.swift */; }; + OBJ_208 /* OrderedSet+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_87 /* OrderedSet+Hashable.swift */; }; + OBJ_209 /* OrderedSet+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_88 /* OrderedSet+Initializers.swift */; }; + OBJ_210 /* OrderedSet+Insertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_89 /* OrderedSet+Insertions.swift */; }; + OBJ_211 /* OrderedSet+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_90 /* OrderedSet+Invariants.swift */; }; + OBJ_212 /* OrderedSet+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_91 /* OrderedSet+Partial MutableCollection.swift */; }; + OBJ_213 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_92 /* OrderedSet+Partial RangeReplaceableCollection.swift */; }; + OBJ_214 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_93 /* OrderedSet+Partial SetAlgebra+Basics.swift */; }; + OBJ_215 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_94 /* OrderedSet+Partial SetAlgebra+Operations.swift */; }; + OBJ_216 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_95 /* OrderedSet+Partial SetAlgebra+Predicates.swift */; }; + OBJ_217 /* OrderedSet+RandomAccessCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_96 /* OrderedSet+RandomAccessCollection.swift */; }; + OBJ_218 /* OrderedSet+ReserveCapacity.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_97 /* OrderedSet+ReserveCapacity.swift */; }; + OBJ_219 /* OrderedSet+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_98 /* OrderedSet+SubSequence.swift */; }; + OBJ_220 /* OrderedSet+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_99 /* OrderedSet+Testing.swift */; }; + OBJ_221 /* OrderedSet+UnorderedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_100 /* OrderedSet+UnorderedView.swift */; }; + OBJ_222 /* OrderedSet+UnstableInternals.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_101 /* OrderedSet+UnstableInternals.swift */; }; + OBJ_223 /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_102 /* OrderedSet.swift */; }; + OBJ_224 /* RandomAccessCollection+Offsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_104 /* RandomAccessCollection+Offsets.swift */; }; + OBJ_225 /* _UnsafeBitset.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_105 /* _UnsafeBitset.swift */; }; + OBJ_232 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_106 /* Package.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - B7A072784A06B1A1C700CC40 /* AsyncObjects.docc */ = {isa = PBXFileReference; includeInIndex = 1; path = AsyncObjects.docc; sourceTree = ""; }; + EB6E2F9D4FEF6DDD65D0B251 /* AsyncObjects.docc */ = {isa = PBXFileReference; includeInIndex = 1; path = AsyncObjects.docc; sourceTree = ""; }; + OBJ_100 /* OrderedSet+UnorderedView.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnorderedView.swift"; sourceTree = ""; }; + OBJ_101 /* OrderedSet+UnstableInternals.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnstableInternals.swift"; sourceTree = ""; }; + OBJ_102 /* OrderedSet.swift */ = {isa = PBXFileReference; path = OrderedSet.swift; sourceTree = ""; }; + OBJ_104 /* RandomAccessCollection+Offsets.swift */ = {isa = PBXFileReference; path = "RandomAccessCollection+Offsets.swift"; sourceTree = ""; }; + OBJ_105 /* _UnsafeBitset.swift */ = {isa = PBXFileReference; path = _UnsafeBitset.swift; sourceTree = ""; }; + OBJ_106 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; name = Package.swift; path = "/Users/soumyaranjanmahunt/Documents/personal_projs/AsyncObjects/.build/checkouts/swift-collections/Package.swift"; sourceTree = ""; }; OBJ_11 /* AsyncCountdownEvent.swift */ = {isa = PBXFileReference; path = AsyncCountdownEvent.swift; sourceTree = ""; }; OBJ_12 /* AsyncEvent.swift */ = {isa = PBXFileReference; path = AsyncEvent.swift; sourceTree = ""; }; OBJ_13 /* AsyncObject.swift */ = {isa = PBXFileReference; path = AsyncObject.swift; sourceTree = ""; }; OBJ_14 /* AsyncSemaphore.swift */ = {isa = PBXFileReference; path = AsyncSemaphore.swift; sourceTree = ""; }; OBJ_15 /* CancellationSource.swift */ = {isa = PBXFileReference; path = CancellationSource.swift; sourceTree = ""; }; - OBJ_16 /* Continuable.swift */ = {isa = PBXFileReference; path = Continuable.swift; sourceTree = ""; }; - OBJ_17 /* Future.swift */ = {isa = PBXFileReference; path = Future.swift; sourceTree = ""; }; - OBJ_18 /* Locker.swift */ = {isa = PBXFileReference; path = Locker.swift; sourceTree = ""; }; - OBJ_19 /* SafeContinuation.swift */ = {isa = PBXFileReference; path = SafeContinuation.swift; sourceTree = ""; }; - OBJ_20 /* Task.swift */ = {isa = PBXFileReference; path = Task.swift; sourceTree = ""; }; - OBJ_21 /* TaskOperation.swift */ = {isa = PBXFileReference; path = TaskOperation.swift; sourceTree = ""; }; - OBJ_22 /* TaskQueue.swift */ = {isa = PBXFileReference; path = TaskQueue.swift; sourceTree = ""; }; - OBJ_23 /* TaskTracker.swift */ = {isa = PBXFileReference; path = TaskTracker.swift; sourceTree = ""; }; - OBJ_26 /* AsyncCountdownEventTests.swift */ = {isa = PBXFileReference; path = AsyncCountdownEventTests.swift; sourceTree = ""; }; - OBJ_27 /* AsyncEventTests.swift */ = {isa = PBXFileReference; path = AsyncEventTests.swift; sourceTree = ""; }; - OBJ_28 /* AsyncObjectTests.swift */ = {isa = PBXFileReference; path = AsyncObjectTests.swift; sourceTree = ""; }; - OBJ_29 /* AsyncSemaphoreTests.swift */ = {isa = PBXFileReference; path = AsyncSemaphoreTests.swift; sourceTree = ""; }; - OBJ_30 /* CancellationSourceTests.swift */ = {isa = PBXFileReference; path = CancellationSourceTests.swift; sourceTree = ""; }; - OBJ_31 /* LockerTests.swift */ = {isa = PBXFileReference; path = LockerTests.swift; sourceTree = ""; }; - OBJ_32 /* NonThrowingFutureTests.swift */ = {isa = PBXFileReference; path = NonThrowingFutureTests.swift; sourceTree = ""; }; - OBJ_33 /* SafeContinuationTests.swift */ = {isa = PBXFileReference; path = SafeContinuationTests.swift; sourceTree = ""; }; - OBJ_34 /* StandardLibraryTests.swift */ = {isa = PBXFileReference; path = StandardLibraryTests.swift; sourceTree = ""; }; - OBJ_35 /* TaskOperationTests.swift */ = {isa = PBXFileReference; path = TaskOperationTests.swift; sourceTree = ""; }; - OBJ_36 /* TaskQueueTests.swift */ = {isa = PBXFileReference; path = TaskQueueTests.swift; sourceTree = ""; }; - OBJ_37 /* ThrowingFutureTests.swift */ = {isa = PBXFileReference; path = ThrowingFutureTests.swift; sourceTree = ""; }; - OBJ_38 /* XCTestCase.swift */ = {isa = PBXFileReference; path = XCTestCase.swift; sourceTree = ""; }; - OBJ_45 /* _HashTable+Bucket.swift */ = {isa = PBXFileReference; path = "_HashTable+Bucket.swift"; sourceTree = ""; }; - OBJ_46 /* _HashTable+BucketIterator.swift */ = {isa = PBXFileReference; path = "_HashTable+BucketIterator.swift"; sourceTree = ""; }; - OBJ_47 /* _HashTable+Constants.swift */ = {isa = PBXFileReference; path = "_HashTable+Constants.swift"; sourceTree = ""; }; - OBJ_48 /* _HashTable+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "_HashTable+CustomStringConvertible.swift"; sourceTree = ""; }; - OBJ_49 /* _HashTable+Testing.swift */ = {isa = PBXFileReference; path = "_HashTable+Testing.swift"; sourceTree = ""; }; - OBJ_50 /* _HashTable+UnsafeHandle.swift */ = {isa = PBXFileReference; path = "_HashTable+UnsafeHandle.swift"; sourceTree = ""; }; - OBJ_51 /* _HashTable.swift */ = {isa = PBXFileReference; path = _HashTable.swift; sourceTree = ""; }; - OBJ_52 /* _Hashtable+Header.swift */ = {isa = PBXFileReference; path = "_Hashtable+Header.swift"; sourceTree = ""; }; - OBJ_54 /* OrderedDictionary+Codable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Codable.swift"; sourceTree = ""; }; - OBJ_55 /* OrderedDictionary+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomDebugStringConvertible.swift"; sourceTree = ""; }; - OBJ_56 /* OrderedDictionary+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomReflectable.swift"; sourceTree = ""; }; - OBJ_57 /* OrderedDictionary+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomStringConvertible.swift"; sourceTree = ""; }; - OBJ_58 /* OrderedDictionary+Deprecations.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Deprecations.swift"; sourceTree = ""; }; - OBJ_59 /* OrderedDictionary+Elements+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements+SubSequence.swift"; sourceTree = ""; }; + OBJ_17 /* Continuable.swift */ = {isa = PBXFileReference; path = Continuable.swift; sourceTree = ""; }; + OBJ_18 /* ContinuableCollection.swift */ = {isa = PBXFileReference; path = ContinuableCollection.swift; sourceTree = ""; }; + OBJ_19 /* GlobalContinuation.swift */ = {isa = PBXFileReference; path = GlobalContinuation.swift; sourceTree = ""; }; + OBJ_20 /* SafeContinuation.swift */ = {isa = PBXFileReference; path = SafeContinuation.swift; sourceTree = ""; }; + OBJ_21 /* SynchronizedContinuable.swift */ = {isa = PBXFileReference; path = SynchronizedContinuable.swift; sourceTree = ""; }; + OBJ_23 /* Task.swift */ = {isa = PBXFileReference; path = Task.swift; sourceTree = ""; }; + OBJ_24 /* TaskGroup.swift */ = {isa = PBXFileReference; path = TaskGroup.swift; sourceTree = ""; }; + OBJ_25 /* Future.swift */ = {isa = PBXFileReference; path = Future.swift; sourceTree = ""; }; + OBJ_27 /* Exclusible.swift */ = {isa = PBXFileReference; path = Exclusible.swift; sourceTree = ""; }; + OBJ_28 /* Locker.swift */ = {isa = PBXFileReference; path = Locker.swift; sourceTree = ""; }; + OBJ_29 /* TaskOperation.swift */ = {isa = PBXFileReference; path = TaskOperation.swift; sourceTree = ""; }; + OBJ_30 /* TaskQueue.swift */ = {isa = PBXFileReference; path = TaskQueue.swift; sourceTree = ""; }; + OBJ_31 /* TaskTracker.swift */ = {isa = PBXFileReference; path = TaskTracker.swift; sourceTree = ""; }; + OBJ_34 /* AsyncCountdownEventTests.swift */ = {isa = PBXFileReference; path = AsyncCountdownEventTests.swift; sourceTree = ""; }; + OBJ_35 /* AsyncEventTests.swift */ = {isa = PBXFileReference; path = AsyncEventTests.swift; sourceTree = ""; }; + OBJ_36 /* AsyncObjectTests.swift */ = {isa = PBXFileReference; path = AsyncObjectTests.swift; sourceTree = ""; }; + OBJ_37 /* AsyncSemaphoreTests.swift */ = {isa = PBXFileReference; path = AsyncSemaphoreTests.swift; sourceTree = ""; }; + OBJ_38 /* CancellationSourceTests.swift */ = {isa = PBXFileReference; path = CancellationSourceTests.swift; sourceTree = ""; }; + OBJ_39 /* LockerTests.swift */ = {isa = PBXFileReference; path = LockerTests.swift; sourceTree = ""; }; + OBJ_40 /* NonThrowingFutureTests.swift */ = {isa = PBXFileReference; path = NonThrowingFutureTests.swift; sourceTree = ""; }; + OBJ_41 /* SafeContinuationTests.swift */ = {isa = PBXFileReference; path = SafeContinuationTests.swift; sourceTree = ""; }; + OBJ_42 /* StandardLibraryTests.swift */ = {isa = PBXFileReference; path = StandardLibraryTests.swift; sourceTree = ""; }; + OBJ_43 /* TaskOperationTests.swift */ = {isa = PBXFileReference; path = TaskOperationTests.swift; sourceTree = ""; }; + OBJ_44 /* TaskQueueTests.swift */ = {isa = PBXFileReference; path = TaskQueueTests.swift; sourceTree = ""; }; + OBJ_45 /* ThrowingFutureTests.swift */ = {isa = PBXFileReference; path = ThrowingFutureTests.swift; sourceTree = ""; }; + OBJ_46 /* XCTestCase.swift */ = {isa = PBXFileReference; path = XCTestCase.swift; sourceTree = ""; }; + OBJ_53 /* _HashTable+Bucket.swift */ = {isa = PBXFileReference; path = "_HashTable+Bucket.swift"; sourceTree = ""; }; + OBJ_54 /* _HashTable+BucketIterator.swift */ = {isa = PBXFileReference; path = "_HashTable+BucketIterator.swift"; sourceTree = ""; }; + OBJ_55 /* _HashTable+Constants.swift */ = {isa = PBXFileReference; path = "_HashTable+Constants.swift"; sourceTree = ""; }; + OBJ_56 /* _HashTable+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "_HashTable+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_57 /* _HashTable+Testing.swift */ = {isa = PBXFileReference; path = "_HashTable+Testing.swift"; sourceTree = ""; }; + OBJ_58 /* _HashTable+UnsafeHandle.swift */ = {isa = PBXFileReference; path = "_HashTable+UnsafeHandle.swift"; sourceTree = ""; }; + OBJ_59 /* _HashTable.swift */ = {isa = PBXFileReference; path = _HashTable.swift; sourceTree = ""; }; OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; - OBJ_60 /* OrderedDictionary+Elements.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements.swift"; sourceTree = ""; }; - OBJ_61 /* OrderedDictionary+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Equatable.swift"; sourceTree = ""; }; - OBJ_62 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+ExpressibleByDictionaryLiteral.swift"; sourceTree = ""; }; - OBJ_63 /* OrderedDictionary+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Hashable.swift"; sourceTree = ""; }; - OBJ_64 /* OrderedDictionary+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Initializers.swift"; sourceTree = ""; }; - OBJ_65 /* OrderedDictionary+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Invariants.swift"; sourceTree = ""; }; - OBJ_66 /* OrderedDictionary+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial MutableCollection.swift"; sourceTree = ""; }; - OBJ_67 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; - OBJ_68 /* OrderedDictionary+Sequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Sequence.swift"; sourceTree = ""; }; - OBJ_69 /* OrderedDictionary+Values.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Values.swift"; sourceTree = ""; }; - OBJ_70 /* OrderedDictionary.swift */ = {isa = PBXFileReference; path = OrderedDictionary.swift; sourceTree = ""; }; - OBJ_72 /* OrderedSet+Codable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Codable.swift"; sourceTree = ""; }; - OBJ_73 /* OrderedSet+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomDebugStringConvertible.swift"; sourceTree = ""; }; - OBJ_74 /* OrderedSet+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomReflectable.swift"; sourceTree = ""; }; - OBJ_75 /* OrderedSet+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomStringConvertible.swift"; sourceTree = ""; }; - OBJ_76 /* OrderedSet+Diffing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Diffing.swift"; sourceTree = ""; }; - OBJ_77 /* OrderedSet+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Equatable.swift"; sourceTree = ""; }; - OBJ_78 /* OrderedSet+ExpressibleByArrayLiteral.swift */ = {isa = PBXFileReference; path = "OrderedSet+ExpressibleByArrayLiteral.swift"; sourceTree = ""; }; - OBJ_79 /* OrderedSet+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Hashable.swift"; sourceTree = ""; }; + OBJ_60 /* _Hashtable+Header.swift */ = {isa = PBXFileReference; path = "_Hashtable+Header.swift"; sourceTree = ""; }; + OBJ_62 /* OrderedDictionary+Codable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Codable.swift"; sourceTree = ""; }; + OBJ_63 /* OrderedDictionary+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomDebugStringConvertible.swift"; sourceTree = ""; }; + OBJ_64 /* OrderedDictionary+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomReflectable.swift"; sourceTree = ""; }; + OBJ_65 /* OrderedDictionary+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_66 /* OrderedDictionary+Deprecations.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Deprecations.swift"; sourceTree = ""; }; + OBJ_67 /* OrderedDictionary+Elements+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements+SubSequence.swift"; sourceTree = ""; }; + OBJ_68 /* OrderedDictionary+Elements.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements.swift"; sourceTree = ""; }; + OBJ_69 /* OrderedDictionary+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Equatable.swift"; sourceTree = ""; }; + OBJ_70 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+ExpressibleByDictionaryLiteral.swift"; sourceTree = ""; }; + OBJ_71 /* OrderedDictionary+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Hashable.swift"; sourceTree = ""; }; + OBJ_72 /* OrderedDictionary+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Initializers.swift"; sourceTree = ""; }; + OBJ_73 /* OrderedDictionary+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Invariants.swift"; sourceTree = ""; }; + OBJ_74 /* OrderedDictionary+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial MutableCollection.swift"; sourceTree = ""; }; + OBJ_75 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; + OBJ_76 /* OrderedDictionary+Sequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Sequence.swift"; sourceTree = ""; }; + OBJ_77 /* OrderedDictionary+Values.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Values.swift"; sourceTree = ""; }; + OBJ_78 /* OrderedDictionary.swift */ = {isa = PBXFileReference; path = OrderedDictionary.swift; sourceTree = ""; }; OBJ_8 /* AsyncObjects.xcconfig */ = {isa = PBXFileReference; name = AsyncObjects.xcconfig; path = Helpers/AsyncObjects.xcconfig; sourceTree = ""; }; - OBJ_80 /* OrderedSet+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedSet+Initializers.swift"; sourceTree = ""; }; - OBJ_81 /* OrderedSet+Insertions.swift */ = {isa = PBXFileReference; path = "OrderedSet+Insertions.swift"; sourceTree = ""; }; - OBJ_82 /* OrderedSet+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedSet+Invariants.swift"; sourceTree = ""; }; - OBJ_83 /* OrderedSet+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial MutableCollection.swift"; sourceTree = ""; }; - OBJ_84 /* OrderedSet+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; - OBJ_85 /* OrderedSet+Partial SetAlgebra+Basics.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Basics.swift"; sourceTree = ""; }; - OBJ_86 /* OrderedSet+Partial SetAlgebra+Operations.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Operations.swift"; sourceTree = ""; }; - OBJ_87 /* OrderedSet+Partial SetAlgebra+Predicates.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Predicates.swift"; sourceTree = ""; }; - OBJ_88 /* OrderedSet+RandomAccessCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+RandomAccessCollection.swift"; sourceTree = ""; }; - OBJ_89 /* OrderedSet+ReserveCapacity.swift */ = {isa = PBXFileReference; path = "OrderedSet+ReserveCapacity.swift"; sourceTree = ""; }; - OBJ_90 /* OrderedSet+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedSet+SubSequence.swift"; sourceTree = ""; }; - OBJ_91 /* OrderedSet+Testing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Testing.swift"; sourceTree = ""; }; - OBJ_92 /* OrderedSet+UnorderedView.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnorderedView.swift"; sourceTree = ""; }; - OBJ_93 /* OrderedSet+UnstableInternals.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnstableInternals.swift"; sourceTree = ""; }; - OBJ_94 /* OrderedSet.swift */ = {isa = PBXFileReference; path = OrderedSet.swift; sourceTree = ""; }; - OBJ_96 /* RandomAccessCollection+Offsets.swift */ = {isa = PBXFileReference; path = "RandomAccessCollection+Offsets.swift"; sourceTree = ""; }; - OBJ_97 /* _UnsafeBitset.swift */ = {isa = PBXFileReference; path = _UnsafeBitset.swift; sourceTree = ""; }; - OBJ_98 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; name = Package.swift; path = "/Users/soumyaranjanmahunt/Documents/personal_projs/AsyncObjects/.build/checkouts/swift-collections/Package.swift"; sourceTree = ""; }; + OBJ_80 /* OrderedSet+Codable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Codable.swift"; sourceTree = ""; }; + OBJ_81 /* OrderedSet+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomDebugStringConvertible.swift"; sourceTree = ""; }; + OBJ_82 /* OrderedSet+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomReflectable.swift"; sourceTree = ""; }; + OBJ_83 /* OrderedSet+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_84 /* OrderedSet+Diffing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Diffing.swift"; sourceTree = ""; }; + OBJ_85 /* OrderedSet+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Equatable.swift"; sourceTree = ""; }; + OBJ_86 /* OrderedSet+ExpressibleByArrayLiteral.swift */ = {isa = PBXFileReference; path = "OrderedSet+ExpressibleByArrayLiteral.swift"; sourceTree = ""; }; + OBJ_87 /* OrderedSet+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Hashable.swift"; sourceTree = ""; }; + OBJ_88 /* OrderedSet+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedSet+Initializers.swift"; sourceTree = ""; }; + OBJ_89 /* OrderedSet+Insertions.swift */ = {isa = PBXFileReference; path = "OrderedSet+Insertions.swift"; sourceTree = ""; }; + OBJ_90 /* OrderedSet+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedSet+Invariants.swift"; sourceTree = ""; }; + OBJ_91 /* OrderedSet+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial MutableCollection.swift"; sourceTree = ""; }; + OBJ_92 /* OrderedSet+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; + OBJ_93 /* OrderedSet+Partial SetAlgebra+Basics.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Basics.swift"; sourceTree = ""; }; + OBJ_94 /* OrderedSet+Partial SetAlgebra+Operations.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Operations.swift"; sourceTree = ""; }; + OBJ_95 /* OrderedSet+Partial SetAlgebra+Predicates.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Predicates.swift"; sourceTree = ""; }; + OBJ_96 /* OrderedSet+RandomAccessCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+RandomAccessCollection.swift"; sourceTree = ""; }; + OBJ_97 /* OrderedSet+ReserveCapacity.swift */ = {isa = PBXFileReference; path = "OrderedSet+ReserveCapacity.swift"; sourceTree = ""; }; + OBJ_98 /* OrderedSet+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedSet+SubSequence.swift"; sourceTree = ""; }; + OBJ_99 /* OrderedSet+Testing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Testing.swift"; sourceTree = ""; }; asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */ = {isa = PBXFileReference; path = AsyncObjects.framework; sourceTree = BUILT_PRODUCTS_DIR; }; asyncobjects::AsyncObjectsTests::Product /* AsyncObjectsTests.xctest */ = {isa = PBXFileReference; path = AsyncObjectsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; swift-collections::OrderedCollections::Product /* OrderedCollections.framework */ = {isa = PBXFileReference; path = OrderedCollections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - OBJ_121 /* Frameworks */ = { + OBJ_134 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( - OBJ_122 /* OrderedCollections.framework in Frameworks */, + OBJ_135 /* OrderedCollections.framework in Frameworks */, ); }; - OBJ_154 /* Frameworks */ = { + OBJ_167 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( - OBJ_155 /* AsyncObjects.framework in Frameworks */, - OBJ_156 /* OrderedCollections.framework in Frameworks */, + OBJ_168 /* AsyncObjects.framework in Frameworks */, + OBJ_169 /* OrderedCollections.framework in Frameworks */, ); }; - OBJ_213 /* Frameworks */ = { + OBJ_226 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( ); @@ -221,72 +231,125 @@ OBJ_13 /* AsyncObject.swift */, OBJ_14 /* AsyncSemaphore.swift */, OBJ_15 /* CancellationSource.swift */, - OBJ_16 /* Continuable.swift */, - OBJ_17 /* Future.swift */, - OBJ_18 /* Locker.swift */, - OBJ_19 /* SafeContinuation.swift */, - OBJ_20 /* Task.swift */, - OBJ_21 /* TaskOperation.swift */, - OBJ_22 /* TaskQueue.swift */, - OBJ_23 /* TaskTracker.swift */, - B7A072784A06B1A1C700CC40 /* AsyncObjects.docc */, + OBJ_16 /* Continuation */, + OBJ_22 /* Extensions */, + OBJ_25 /* Future.swift */, + OBJ_26 /* Locks */, + OBJ_29 /* TaskOperation.swift */, + OBJ_30 /* TaskQueue.swift */, + OBJ_31 /* TaskTracker.swift */, + EB6E2F9D4FEF6DDD65D0B251 /* AsyncObjects.docc */, ); name = AsyncObjects; path = Sources/AsyncObjects; sourceTree = SOURCE_ROOT; }; - OBJ_24 /* Tests */ = { + OBJ_103 /* Utilities */ = { isa = PBXGroup; children = ( - OBJ_25 /* AsyncObjectsTests */, + OBJ_104 /* RandomAccessCollection+Offsets.swift */, + OBJ_105 /* _UnsafeBitset.swift */, + ); + name = Utilities; + path = Utilities; + sourceTree = ""; + }; + OBJ_107 /* Products */ = { + isa = PBXGroup; + children = ( + asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */, + asyncobjects::AsyncObjectsTests::Product /* AsyncObjectsTests.xctest */, + swift-collections::OrderedCollections::Product /* OrderedCollections.framework */, + ); + name = Products; + path = ""; + sourceTree = BUILT_PRODUCTS_DIR; + }; + OBJ_16 /* Continuation */ = { + isa = PBXGroup; + children = ( + OBJ_17 /* Continuable.swift */, + OBJ_18 /* ContinuableCollection.swift */, + OBJ_19 /* GlobalContinuation.swift */, + OBJ_20 /* SafeContinuation.swift */, + OBJ_21 /* SynchronizedContinuable.swift */, + ); + name = Continuation; + path = Continuation; + sourceTree = ""; + }; + OBJ_22 /* Extensions */ = { + isa = PBXGroup; + children = ( + OBJ_23 /* Task.swift */, + OBJ_24 /* TaskGroup.swift */, + ); + name = Extensions; + path = Extensions; + sourceTree = ""; + }; + OBJ_26 /* Locks */ = { + isa = PBXGroup; + children = ( + OBJ_27 /* Exclusible.swift */, + OBJ_28 /* Locker.swift */, + ); + name = Locks; + path = Locks; + sourceTree = ""; + }; + OBJ_32 /* Tests */ = { + isa = PBXGroup; + children = ( + OBJ_33 /* AsyncObjectsTests */, ); name = Tests; path = ""; sourceTree = SOURCE_ROOT; }; - OBJ_25 /* AsyncObjectsTests */ = { + OBJ_33 /* AsyncObjectsTests */ = { isa = PBXGroup; children = ( - OBJ_26 /* AsyncCountdownEventTests.swift */, - OBJ_27 /* AsyncEventTests.swift */, - OBJ_28 /* AsyncObjectTests.swift */, - OBJ_29 /* AsyncSemaphoreTests.swift */, - OBJ_30 /* CancellationSourceTests.swift */, - OBJ_31 /* LockerTests.swift */, - OBJ_32 /* NonThrowingFutureTests.swift */, - OBJ_33 /* SafeContinuationTests.swift */, - OBJ_34 /* StandardLibraryTests.swift */, - OBJ_35 /* TaskOperationTests.swift */, - OBJ_36 /* TaskQueueTests.swift */, - OBJ_37 /* ThrowingFutureTests.swift */, - OBJ_38 /* XCTestCase.swift */, + OBJ_34 /* AsyncCountdownEventTests.swift */, + OBJ_35 /* AsyncEventTests.swift */, + OBJ_36 /* AsyncObjectTests.swift */, + OBJ_37 /* AsyncSemaphoreTests.swift */, + OBJ_38 /* CancellationSourceTests.swift */, + OBJ_39 /* LockerTests.swift */, + OBJ_40 /* NonThrowingFutureTests.swift */, + OBJ_41 /* SafeContinuationTests.swift */, + OBJ_42 /* StandardLibraryTests.swift */, + OBJ_43 /* TaskOperationTests.swift */, + OBJ_44 /* TaskQueueTests.swift */, + OBJ_45 /* ThrowingFutureTests.swift */, + OBJ_46 /* XCTestCase.swift */, ); name = AsyncObjectsTests; path = Tests/AsyncObjectsTests; sourceTree = SOURCE_ROOT; }; - OBJ_39 /* Dependencies */ = { + OBJ_47 /* Dependencies */ = { isa = PBXGroup; children = ( - OBJ_40 /* swift-collections 1.0.2 */, + OBJ_48 /* swift-collections 1.0.2 */, ); name = Dependencies; path = ""; sourceTree = ""; }; - OBJ_40 /* swift-collections 1.0.2 */ = { + OBJ_48 /* swift-collections 1.0.2 */ = { isa = PBXGroup; children = ( - OBJ_41 /* Collections */, - OBJ_42 /* DequeModule */, - OBJ_43 /* OrderedCollections */, - OBJ_98 /* Package.swift */, + OBJ_49 /* Collections */, + OBJ_50 /* DequeModule */, + OBJ_51 /* OrderedCollections */, + OBJ_106 /* Package.swift */, ); name = "swift-collections 1.0.2"; path = ""; sourceTree = SOURCE_ROOT; }; - OBJ_41 /* Collections */ = { + OBJ_49 /* Collections */ = { isa = PBXGroup; children = ( ); @@ -294,7 +357,20 @@ path = ".build/checkouts/swift-collections/Sources/Collections"; sourceTree = SOURCE_ROOT; }; - OBJ_42 /* DequeModule */ = { + OBJ_5 = { + isa = PBXGroup; + children = ( + OBJ_6 /* Package.swift */, + OBJ_7 /* Configs */, + OBJ_9 /* Sources */, + OBJ_32 /* Tests */, + OBJ_47 /* Dependencies */, + OBJ_107 /* Products */, + ); + path = ""; + sourceTree = ""; + }; + OBJ_50 /* DequeModule */ = { isa = PBXGroup; children = ( ); @@ -302,67 +378,54 @@ path = ".build/checkouts/swift-collections/Sources/DequeModule"; sourceTree = SOURCE_ROOT; }; - OBJ_43 /* OrderedCollections */ = { + OBJ_51 /* OrderedCollections */ = { isa = PBXGroup; children = ( - OBJ_44 /* HashTable */, - OBJ_53 /* OrderedDictionary */, - OBJ_71 /* OrderedSet */, - OBJ_95 /* Utilities */, + OBJ_52 /* HashTable */, + OBJ_61 /* OrderedDictionary */, + OBJ_79 /* OrderedSet */, + OBJ_103 /* Utilities */, ); name = OrderedCollections; path = ".build/checkouts/swift-collections/Sources/OrderedCollections"; sourceTree = SOURCE_ROOT; }; - OBJ_44 /* HashTable */ = { + OBJ_52 /* HashTable */ = { isa = PBXGroup; children = ( - OBJ_45 /* _HashTable+Bucket.swift */, - OBJ_46 /* _HashTable+BucketIterator.swift */, - OBJ_47 /* _HashTable+Constants.swift */, - OBJ_48 /* _HashTable+CustomStringConvertible.swift */, - OBJ_49 /* _HashTable+Testing.swift */, - OBJ_50 /* _HashTable+UnsafeHandle.swift */, - OBJ_51 /* _HashTable.swift */, - OBJ_52 /* _Hashtable+Header.swift */, + OBJ_53 /* _HashTable+Bucket.swift */, + OBJ_54 /* _HashTable+BucketIterator.swift */, + OBJ_55 /* _HashTable+Constants.swift */, + OBJ_56 /* _HashTable+CustomStringConvertible.swift */, + OBJ_57 /* _HashTable+Testing.swift */, + OBJ_58 /* _HashTable+UnsafeHandle.swift */, + OBJ_59 /* _HashTable.swift */, + OBJ_60 /* _Hashtable+Header.swift */, ); name = HashTable; path = HashTable; sourceTree = ""; }; - OBJ_5 = { - isa = PBXGroup; - children = ( - OBJ_6 /* Package.swift */, - OBJ_7 /* Configs */, - OBJ_9 /* Sources */, - OBJ_24 /* Tests */, - OBJ_39 /* Dependencies */, - OBJ_99 /* Products */, - ); - path = ""; - sourceTree = ""; - }; - OBJ_53 /* OrderedDictionary */ = { + OBJ_61 /* OrderedDictionary */ = { isa = PBXGroup; children = ( - OBJ_54 /* OrderedDictionary+Codable.swift */, - OBJ_55 /* OrderedDictionary+CustomDebugStringConvertible.swift */, - OBJ_56 /* OrderedDictionary+CustomReflectable.swift */, - OBJ_57 /* OrderedDictionary+CustomStringConvertible.swift */, - OBJ_58 /* OrderedDictionary+Deprecations.swift */, - OBJ_59 /* OrderedDictionary+Elements+SubSequence.swift */, - OBJ_60 /* OrderedDictionary+Elements.swift */, - OBJ_61 /* OrderedDictionary+Equatable.swift */, - OBJ_62 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */, - OBJ_63 /* OrderedDictionary+Hashable.swift */, - OBJ_64 /* OrderedDictionary+Initializers.swift */, - OBJ_65 /* OrderedDictionary+Invariants.swift */, - OBJ_66 /* OrderedDictionary+Partial MutableCollection.swift */, - OBJ_67 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */, - OBJ_68 /* OrderedDictionary+Sequence.swift */, - OBJ_69 /* OrderedDictionary+Values.swift */, - OBJ_70 /* OrderedDictionary.swift */, + OBJ_62 /* OrderedDictionary+Codable.swift */, + OBJ_63 /* OrderedDictionary+CustomDebugStringConvertible.swift */, + OBJ_64 /* OrderedDictionary+CustomReflectable.swift */, + OBJ_65 /* OrderedDictionary+CustomStringConvertible.swift */, + OBJ_66 /* OrderedDictionary+Deprecations.swift */, + OBJ_67 /* OrderedDictionary+Elements+SubSequence.swift */, + OBJ_68 /* OrderedDictionary+Elements.swift */, + OBJ_69 /* OrderedDictionary+Equatable.swift */, + OBJ_70 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */, + OBJ_71 /* OrderedDictionary+Hashable.swift */, + OBJ_72 /* OrderedDictionary+Initializers.swift */, + OBJ_73 /* OrderedDictionary+Invariants.swift */, + OBJ_74 /* OrderedDictionary+Partial MutableCollection.swift */, + OBJ_75 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */, + OBJ_76 /* OrderedDictionary+Sequence.swift */, + OBJ_77 /* OrderedDictionary+Values.swift */, + OBJ_78 /* OrderedDictionary.swift */, ); name = OrderedDictionary; path = OrderedDictionary; @@ -377,32 +440,32 @@ path = ""; sourceTree = ""; }; - OBJ_71 /* OrderedSet */ = { + OBJ_79 /* OrderedSet */ = { isa = PBXGroup; children = ( - OBJ_72 /* OrderedSet+Codable.swift */, - OBJ_73 /* OrderedSet+CustomDebugStringConvertible.swift */, - OBJ_74 /* OrderedSet+CustomReflectable.swift */, - OBJ_75 /* OrderedSet+CustomStringConvertible.swift */, - OBJ_76 /* OrderedSet+Diffing.swift */, - OBJ_77 /* OrderedSet+Equatable.swift */, - OBJ_78 /* OrderedSet+ExpressibleByArrayLiteral.swift */, - OBJ_79 /* OrderedSet+Hashable.swift */, - OBJ_80 /* OrderedSet+Initializers.swift */, - OBJ_81 /* OrderedSet+Insertions.swift */, - OBJ_82 /* OrderedSet+Invariants.swift */, - OBJ_83 /* OrderedSet+Partial MutableCollection.swift */, - OBJ_84 /* OrderedSet+Partial RangeReplaceableCollection.swift */, - OBJ_85 /* OrderedSet+Partial SetAlgebra+Basics.swift */, - OBJ_86 /* OrderedSet+Partial SetAlgebra+Operations.swift */, - OBJ_87 /* OrderedSet+Partial SetAlgebra+Predicates.swift */, - OBJ_88 /* OrderedSet+RandomAccessCollection.swift */, - OBJ_89 /* OrderedSet+ReserveCapacity.swift */, - OBJ_90 /* OrderedSet+SubSequence.swift */, - OBJ_91 /* OrderedSet+Testing.swift */, - OBJ_92 /* OrderedSet+UnorderedView.swift */, - OBJ_93 /* OrderedSet+UnstableInternals.swift */, - OBJ_94 /* OrderedSet.swift */, + OBJ_80 /* OrderedSet+Codable.swift */, + OBJ_81 /* OrderedSet+CustomDebugStringConvertible.swift */, + OBJ_82 /* OrderedSet+CustomReflectable.swift */, + OBJ_83 /* OrderedSet+CustomStringConvertible.swift */, + OBJ_84 /* OrderedSet+Diffing.swift */, + OBJ_85 /* OrderedSet+Equatable.swift */, + OBJ_86 /* OrderedSet+ExpressibleByArrayLiteral.swift */, + OBJ_87 /* OrderedSet+Hashable.swift */, + OBJ_88 /* OrderedSet+Initializers.swift */, + OBJ_89 /* OrderedSet+Insertions.swift */, + OBJ_90 /* OrderedSet+Invariants.swift */, + OBJ_91 /* OrderedSet+Partial MutableCollection.swift */, + OBJ_92 /* OrderedSet+Partial RangeReplaceableCollection.swift */, + OBJ_93 /* OrderedSet+Partial SetAlgebra+Basics.swift */, + OBJ_94 /* OrderedSet+Partial SetAlgebra+Operations.swift */, + OBJ_95 /* OrderedSet+Partial SetAlgebra+Predicates.swift */, + OBJ_96 /* OrderedSet+RandomAccessCollection.swift */, + OBJ_97 /* OrderedSet+ReserveCapacity.swift */, + OBJ_98 /* OrderedSet+SubSequence.swift */, + OBJ_99 /* OrderedSet+Testing.swift */, + OBJ_100 /* OrderedSet+UnorderedView.swift */, + OBJ_101 /* OrderedSet+UnstableInternals.swift */, + OBJ_102 /* OrderedSet.swift */, ); name = OrderedSet; path = OrderedSet; @@ -417,41 +480,20 @@ path = ""; sourceTree = SOURCE_ROOT; }; - OBJ_95 /* Utilities */ = { - isa = PBXGroup; - children = ( - OBJ_96 /* RandomAccessCollection+Offsets.swift */, - OBJ_97 /* _UnsafeBitset.swift */, - ); - name = Utilities; - path = Utilities; - sourceTree = ""; - }; - OBJ_99 /* Products */ = { - isa = PBXGroup; - children = ( - asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */, - asyncobjects::AsyncObjectsTests::Product /* AsyncObjectsTests.xctest */, - swift-collections::OrderedCollections::Product /* OrderedCollections.framework */, - ); - name = Products; - path = ""; - sourceTree = BUILT_PRODUCTS_DIR; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ asyncobjects::AsyncObjects /* AsyncObjects */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_104 /* Build configuration list for PBXNativeTarget "AsyncObjects" */; + buildConfigurationList = OBJ_112 /* Build configuration list for PBXNativeTarget "AsyncObjects" */; buildPhases = ( - OBJ_107 /* Sources */, - OBJ_121 /* Frameworks */, + OBJ_115 /* Sources */, + OBJ_134 /* Frameworks */, ); buildRules = ( ); dependencies = ( - OBJ_123 /* PBXTargetDependency */, + OBJ_136 /* PBXTargetDependency */, ); name = AsyncObjects; productName = AsyncObjects; @@ -460,16 +502,16 @@ }; asyncobjects::AsyncObjectsTests /* AsyncObjectsTests */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_137 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */; + buildConfigurationList = OBJ_150 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */; buildPhases = ( - OBJ_140 /* Sources */, - OBJ_154 /* Frameworks */, + OBJ_153 /* Sources */, + OBJ_167 /* Frameworks */, ); buildRules = ( ); dependencies = ( - OBJ_157 /* PBXTargetDependency */, - OBJ_158 /* PBXTargetDependency */, + OBJ_170 /* PBXTargetDependency */, + OBJ_171 /* PBXTargetDependency */, ); name = AsyncObjectsTests; productName = AsyncObjectsTests; @@ -478,9 +520,9 @@ }; asyncobjects::SwiftPMPackageDescription /* AsyncObjectsPackageDescription */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_126 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */; + buildConfigurationList = OBJ_139 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */; buildPhases = ( - OBJ_129 /* Sources */, + OBJ_142 /* Sources */, ); buildRules = ( ); @@ -492,10 +534,10 @@ }; swift-collections::OrderedCollections /* OrderedCollections */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_159 /* Build configuration list for PBXNativeTarget "OrderedCollections" */; + buildConfigurationList = OBJ_172 /* Build configuration list for PBXNativeTarget "OrderedCollections" */; buildPhases = ( - OBJ_162 /* Sources */, - OBJ_213 /* Frameworks */, + OBJ_175 /* Sources */, + OBJ_226 /* Frameworks */, ); buildRules = ( ); @@ -508,9 +550,9 @@ }; swift-collections::SwiftPMPackageDescription /* swift-collectionsPackageDescription */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_215 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */; + buildConfigurationList = OBJ_228 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */; buildPhases = ( - OBJ_218 /* Sources */, + OBJ_231 /* Sources */, ); buildRules = ( ); @@ -537,7 +579,7 @@ en, ); mainGroup = OBJ_5; - productRefGroup = OBJ_99 /* Products */; + productRefGroup = OBJ_107 /* Products */; projectDirPath = .; targets = ( asyncobjects::AsyncObjects /* AsyncObjects */, @@ -551,133 +593,138 @@ /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ - OBJ_107 /* Sources */ = { + OBJ_115 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_108 /* AsyncCountdownEvent.swift in Sources */, - OBJ_109 /* AsyncEvent.swift in Sources */, - OBJ_110 /* AsyncObject.swift in Sources */, - OBJ_111 /* AsyncSemaphore.swift in Sources */, - OBJ_112 /* CancellationSource.swift in Sources */, - OBJ_113 /* Continuable.swift in Sources */, - OBJ_114 /* Future.swift in Sources */, - OBJ_115 /* Locker.swift in Sources */, - OBJ_116 /* SafeContinuation.swift in Sources */, - OBJ_117 /* Task.swift in Sources */, - OBJ_118 /* TaskOperation.swift in Sources */, - OBJ_119 /* TaskQueue.swift in Sources */, - OBJ_120 /* TaskTracker.swift in Sources */, - 0663FA4A7B71E49448BE9547 /* AsyncObjects.docc in Sources */, + OBJ_116 /* AsyncCountdownEvent.swift in Sources */, + OBJ_117 /* AsyncEvent.swift in Sources */, + OBJ_118 /* AsyncObject.swift in Sources */, + OBJ_119 /* AsyncSemaphore.swift in Sources */, + OBJ_120 /* CancellationSource.swift in Sources */, + OBJ_121 /* Continuable.swift in Sources */, + OBJ_122 /* ContinuableCollection.swift in Sources */, + OBJ_123 /* GlobalContinuation.swift in Sources */, + OBJ_124 /* SafeContinuation.swift in Sources */, + OBJ_125 /* SynchronizedContinuable.swift in Sources */, + OBJ_126 /* Task.swift in Sources */, + OBJ_127 /* TaskGroup.swift in Sources */, + OBJ_128 /* Future.swift in Sources */, + OBJ_129 /* Exclusible.swift in Sources */, + OBJ_130 /* Locker.swift in Sources */, + OBJ_131 /* TaskOperation.swift in Sources */, + OBJ_132 /* TaskQueue.swift in Sources */, + OBJ_133 /* TaskTracker.swift in Sources */, + DFEF4D3A44700284C230A854 /* AsyncObjects.docc in Sources */, ); }; - OBJ_129 /* Sources */ = { + OBJ_142 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_130 /* Package.swift in Sources */, + OBJ_143 /* Package.swift in Sources */, ); }; - OBJ_140 /* Sources */ = { + OBJ_153 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_141 /* AsyncCountdownEventTests.swift in Sources */, - OBJ_142 /* AsyncEventTests.swift in Sources */, - OBJ_143 /* AsyncObjectTests.swift in Sources */, - OBJ_144 /* AsyncSemaphoreTests.swift in Sources */, - OBJ_145 /* CancellationSourceTests.swift in Sources */, - OBJ_146 /* LockerTests.swift in Sources */, - OBJ_147 /* NonThrowingFutureTests.swift in Sources */, - OBJ_148 /* SafeContinuationTests.swift in Sources */, - OBJ_149 /* StandardLibraryTests.swift in Sources */, - OBJ_150 /* TaskOperationTests.swift in Sources */, - OBJ_151 /* TaskQueueTests.swift in Sources */, - OBJ_152 /* ThrowingFutureTests.swift in Sources */, - OBJ_153 /* XCTestCase.swift in Sources */, + OBJ_154 /* AsyncCountdownEventTests.swift in Sources */, + OBJ_155 /* AsyncEventTests.swift in Sources */, + OBJ_156 /* AsyncObjectTests.swift in Sources */, + OBJ_157 /* AsyncSemaphoreTests.swift in Sources */, + OBJ_158 /* CancellationSourceTests.swift in Sources */, + OBJ_159 /* LockerTests.swift in Sources */, + OBJ_160 /* NonThrowingFutureTests.swift in Sources */, + OBJ_161 /* SafeContinuationTests.swift in Sources */, + OBJ_162 /* StandardLibraryTests.swift in Sources */, + OBJ_163 /* TaskOperationTests.swift in Sources */, + OBJ_164 /* TaskQueueTests.swift in Sources */, + OBJ_165 /* ThrowingFutureTests.swift in Sources */, + OBJ_166 /* XCTestCase.swift in Sources */, ); }; - OBJ_162 /* Sources */ = { + OBJ_175 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_163 /* _HashTable+Bucket.swift in Sources */, - OBJ_164 /* _HashTable+BucketIterator.swift in Sources */, - OBJ_165 /* _HashTable+Constants.swift in Sources */, - OBJ_166 /* _HashTable+CustomStringConvertible.swift in Sources */, - OBJ_167 /* _HashTable+Testing.swift in Sources */, - OBJ_168 /* _HashTable+UnsafeHandle.swift in Sources */, - OBJ_169 /* _HashTable.swift in Sources */, - OBJ_170 /* _Hashtable+Header.swift in Sources */, - OBJ_171 /* OrderedDictionary+Codable.swift in Sources */, - OBJ_172 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */, - OBJ_173 /* OrderedDictionary+CustomReflectable.swift in Sources */, - OBJ_174 /* OrderedDictionary+CustomStringConvertible.swift in Sources */, - OBJ_175 /* OrderedDictionary+Deprecations.swift in Sources */, - OBJ_176 /* OrderedDictionary+Elements+SubSequence.swift in Sources */, - OBJ_177 /* OrderedDictionary+Elements.swift in Sources */, - OBJ_178 /* OrderedDictionary+Equatable.swift in Sources */, - OBJ_179 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */, - OBJ_180 /* OrderedDictionary+Hashable.swift in Sources */, - OBJ_181 /* OrderedDictionary+Initializers.swift in Sources */, - OBJ_182 /* OrderedDictionary+Invariants.swift in Sources */, - OBJ_183 /* OrderedDictionary+Partial MutableCollection.swift in Sources */, - OBJ_184 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */, - OBJ_185 /* OrderedDictionary+Sequence.swift in Sources */, - OBJ_186 /* OrderedDictionary+Values.swift in Sources */, - OBJ_187 /* OrderedDictionary.swift in Sources */, - OBJ_188 /* OrderedSet+Codable.swift in Sources */, - OBJ_189 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */, - OBJ_190 /* OrderedSet+CustomReflectable.swift in Sources */, - OBJ_191 /* OrderedSet+CustomStringConvertible.swift in Sources */, - OBJ_192 /* OrderedSet+Diffing.swift in Sources */, - OBJ_193 /* OrderedSet+Equatable.swift in Sources */, - OBJ_194 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */, - OBJ_195 /* OrderedSet+Hashable.swift in Sources */, - OBJ_196 /* OrderedSet+Initializers.swift in Sources */, - OBJ_197 /* OrderedSet+Insertions.swift in Sources */, - OBJ_198 /* OrderedSet+Invariants.swift in Sources */, - OBJ_199 /* OrderedSet+Partial MutableCollection.swift in Sources */, - OBJ_200 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */, - OBJ_201 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */, - OBJ_202 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */, - OBJ_203 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */, - OBJ_204 /* OrderedSet+RandomAccessCollection.swift in Sources */, - OBJ_205 /* OrderedSet+ReserveCapacity.swift in Sources */, - OBJ_206 /* OrderedSet+SubSequence.swift in Sources */, - OBJ_207 /* OrderedSet+Testing.swift in Sources */, - OBJ_208 /* OrderedSet+UnorderedView.swift in Sources */, - OBJ_209 /* OrderedSet+UnstableInternals.swift in Sources */, - OBJ_210 /* OrderedSet.swift in Sources */, - OBJ_211 /* RandomAccessCollection+Offsets.swift in Sources */, - OBJ_212 /* _UnsafeBitset.swift in Sources */, + OBJ_176 /* _HashTable+Bucket.swift in Sources */, + OBJ_177 /* _HashTable+BucketIterator.swift in Sources */, + OBJ_178 /* _HashTable+Constants.swift in Sources */, + OBJ_179 /* _HashTable+CustomStringConvertible.swift in Sources */, + OBJ_180 /* _HashTable+Testing.swift in Sources */, + OBJ_181 /* _HashTable+UnsafeHandle.swift in Sources */, + OBJ_182 /* _HashTable.swift in Sources */, + OBJ_183 /* _Hashtable+Header.swift in Sources */, + OBJ_184 /* OrderedDictionary+Codable.swift in Sources */, + OBJ_185 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */, + OBJ_186 /* OrderedDictionary+CustomReflectable.swift in Sources */, + OBJ_187 /* OrderedDictionary+CustomStringConvertible.swift in Sources */, + OBJ_188 /* OrderedDictionary+Deprecations.swift in Sources */, + OBJ_189 /* OrderedDictionary+Elements+SubSequence.swift in Sources */, + OBJ_190 /* OrderedDictionary+Elements.swift in Sources */, + OBJ_191 /* OrderedDictionary+Equatable.swift in Sources */, + OBJ_192 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */, + OBJ_193 /* OrderedDictionary+Hashable.swift in Sources */, + OBJ_194 /* OrderedDictionary+Initializers.swift in Sources */, + OBJ_195 /* OrderedDictionary+Invariants.swift in Sources */, + OBJ_196 /* OrderedDictionary+Partial MutableCollection.swift in Sources */, + OBJ_197 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */, + OBJ_198 /* OrderedDictionary+Sequence.swift in Sources */, + OBJ_199 /* OrderedDictionary+Values.swift in Sources */, + OBJ_200 /* OrderedDictionary.swift in Sources */, + OBJ_201 /* OrderedSet+Codable.swift in Sources */, + OBJ_202 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */, + OBJ_203 /* OrderedSet+CustomReflectable.swift in Sources */, + OBJ_204 /* OrderedSet+CustomStringConvertible.swift in Sources */, + OBJ_205 /* OrderedSet+Diffing.swift in Sources */, + OBJ_206 /* OrderedSet+Equatable.swift in Sources */, + OBJ_207 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */, + OBJ_208 /* OrderedSet+Hashable.swift in Sources */, + OBJ_209 /* OrderedSet+Initializers.swift in Sources */, + OBJ_210 /* OrderedSet+Insertions.swift in Sources */, + OBJ_211 /* OrderedSet+Invariants.swift in Sources */, + OBJ_212 /* OrderedSet+Partial MutableCollection.swift in Sources */, + OBJ_213 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */, + OBJ_214 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */, + OBJ_215 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */, + OBJ_216 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */, + OBJ_217 /* OrderedSet+RandomAccessCollection.swift in Sources */, + OBJ_218 /* OrderedSet+ReserveCapacity.swift in Sources */, + OBJ_219 /* OrderedSet+SubSequence.swift in Sources */, + OBJ_220 /* OrderedSet+Testing.swift in Sources */, + OBJ_221 /* OrderedSet+UnorderedView.swift in Sources */, + OBJ_222 /* OrderedSet+UnstableInternals.swift in Sources */, + OBJ_223 /* OrderedSet.swift in Sources */, + OBJ_224 /* RandomAccessCollection+Offsets.swift in Sources */, + OBJ_225 /* _UnsafeBitset.swift in Sources */, ); }; - OBJ_218 /* Sources */ = { + OBJ_231 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_219 /* Package.swift in Sources */, + OBJ_232 /* Package.swift in Sources */, ); }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - OBJ_123 /* PBXTargetDependency */ = { + OBJ_136 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = swift-collections::OrderedCollections /* OrderedCollections */; }; - OBJ_135 /* PBXTargetDependency */ = { + OBJ_148 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = asyncobjects::AsyncObjectsTests /* AsyncObjectsTests */; }; - OBJ_157 /* PBXTargetDependency */ = { + OBJ_170 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = asyncobjects::AsyncObjects /* AsyncObjects */; }; - OBJ_158 /* PBXTargetDependency */ = { + OBJ_171 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = swift-collections::OrderedCollections /* OrderedCollections */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - OBJ_105 /* Debug */ = { + OBJ_113 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -695,7 +742,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xfrontend -warn-concurrency -enable-actor-data-race-checks -require-explicit-sendable"; PRODUCT_BUNDLE_IDENTIFIER = AsyncObjects; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -708,7 +755,7 @@ }; name = Debug; }; - OBJ_106 /* Release */ = { + OBJ_114 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -726,7 +773,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xfrontend -warn-concurrency -enable-actor-data-race-checks -require-explicit-sendable"; PRODUCT_BUNDLE_IDENTIFIER = AsyncObjects; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -739,7 +786,7 @@ }; name = Release; }; - OBJ_127 /* Debug */ = { + OBJ_140 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -748,7 +795,7 @@ }; name = Debug; }; - OBJ_128 /* Release */ = { + OBJ_141 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -757,19 +804,19 @@ }; name = Release; }; - OBJ_133 /* Debug */ = { + OBJ_146 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Debug; }; - OBJ_134 /* Release */ = { + OBJ_147 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Release; }; - OBJ_138 /* Debug */ = { + OBJ_151 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -797,7 +844,7 @@ }; name = Debug; }; - OBJ_139 /* Release */ = { + OBJ_152 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -825,7 +872,7 @@ }; name = Release; }; - OBJ_160 /* Debug */ = { + OBJ_173 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -856,7 +903,7 @@ }; name = Debug; }; - OBJ_161 /* Release */ = { + OBJ_174 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -887,7 +934,7 @@ }; name = Release; }; - OBJ_216 /* Debug */ = { + OBJ_229 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -896,7 +943,7 @@ }; name = Debug; }; - OBJ_217 /* Release */ = { + OBJ_230 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -961,47 +1008,47 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - OBJ_104 /* Build configuration list for PBXNativeTarget "AsyncObjects" */ = { + OBJ_112 /* Build configuration list for PBXNativeTarget "AsyncObjects" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_105 /* Debug */, - OBJ_106 /* Release */, + OBJ_113 /* Debug */, + OBJ_114 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_126 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */ = { + OBJ_139 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_127 /* Debug */, - OBJ_128 /* Release */, + OBJ_140 /* Debug */, + OBJ_141 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_132 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */ = { + OBJ_145 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_133 /* Debug */, - OBJ_134 /* Release */, + OBJ_146 /* Debug */, + OBJ_147 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_137 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */ = { + OBJ_150 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_138 /* Debug */, - OBJ_139 /* Release */, + OBJ_151 /* Debug */, + OBJ_152 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_159 /* Build configuration list for PBXNativeTarget "OrderedCollections" */ = { + OBJ_172 /* Build configuration list for PBXNativeTarget "OrderedCollections" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_160 /* Debug */, - OBJ_161 /* Release */, + OBJ_173 /* Debug */, + OBJ_174 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -1015,11 +1062,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_215 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */ = { + OBJ_228 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_216 /* Debug */, - OBJ_217 /* Release */, + OBJ_229 /* Debug */, + OBJ_230 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Package.swift b/Package.swift index 9e2077cc..e98b3e41 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( dependencies: [ .package(url: "\(appleGitHub)/swift-collections.git", from: "1.0.0"), .package(url: "\(appleGitHub)/swift-docc-plugin", from: "1.0.0"), - .package(url: "\(appleGitHub)/swift-format", from: "0.50600.1"), + .package(url: "\(appleGitHub)/swift-format", from: "0.50700.0"), ], targets: [ .target( @@ -42,7 +42,7 @@ let package = Package( ] ) -var swiftSettings: [SwiftSetting] { +var swiftSettings: [SwiftSetting] = { var swiftSettings: [SwiftSetting] = [] if ProcessInfo.processInfo.environment[ @@ -77,9 +77,9 @@ var swiftSettings: [SwiftSetting] { } return swiftSettings -} +}() -var testingSwiftSettings: [SwiftSetting] { +var testingSwiftSettings: [SwiftSetting] = { var swiftSettings: [SwiftSetting] = [] if ProcessInfo.processInfo.environment[ @@ -96,4 +96,4 @@ var testingSwiftSettings: [SwiftSetting] { } return swiftSettings -} +}() diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index 2f7795ad..813292a7 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -3,6 +3,7 @@ import Foundation #else @preconcurrency import Foundation #endif + import OrderedCollections /// An event object that controls access to a resource between high and low priority tasks @@ -15,20 +16,42 @@ import OrderedCollections /// You can indicate high priority usage of resource by using ``increment(by:)`` method, /// and indicate free of resource by calling ``signal(repeat:)`` or ``signal()`` methods. /// For low priority resource usage or detect resource idling use ``wait()`` method -/// or its timeout variation ``wait(forNanoseconds:)``. +/// or its timeout variation ``wait(forNanoseconds:)``: +/// +/// ```swift +/// // create event with initial count and count down limit +/// let event = AsyncCountdownEvent() +/// // increment countdown count from high priority tasks +/// event.increment(by: 1) +/// +/// // wait for countdown signal from low priority tasks, +/// // fails only if task cancelled +/// try await event.wait() +/// // or wait with some timeout +/// try await event.wait(forNanoseconds: 1_000_000_000) +/// +/// // signal countdown after completing high priority tasks +/// event.signal() +/// ``` /// /// Use the ``limit`` parameter to indicate concurrent low priority usage, i.e. if limit set to zero, /// only one low priority usage allowed at one time. -public actor AsyncCountdownEvent: AsyncObject { +public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The continuations stored with an associated key for all the suspended task that are waiting to be resumed. @usableFromInline - private(set) var continuations: OrderedDictionary = [:] + internal private(set) var continuations: + OrderedDictionary< + UUID, + Continuation + > = [:] /// The limit up to which the countdown counts and triggers event. /// /// By default this is set to zero and can be changed during initialization. @@ -42,7 +65,7 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// Can be changed after initialization /// by using ``reset(to:)`` method. - public private(set) var initialCount: UInt + public var initialCount: UInt /// Indicates whether countdown event current count is within ``limit``. /// /// Queued tasks are resumed from suspension when event is set and until current count exceeds limit. @@ -54,13 +77,13 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// - Returns: Whether to wait to be resumed later. @inlinable - func _wait() -> Bool { !isSet || !continuations.isEmpty } + internal func shouldWait() -> Bool { !isSet || !continuations.isEmpty } /// Resume provided continuation with additional changes based on the associated flags. /// /// - Parameter continuation: The queued continuation to resume. @inlinable - func _resumeContinuation(_ continuation: Continuation) { + internal func resumeContinuation(_ continuation: Continuation) { currentCount += 1 continuation.resume() } @@ -71,12 +94,12 @@ public actor AsyncCountdownEvent: AsyncObject { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID ) { guard !continuation.resumed else { return } - guard _wait() else { _resumeContinuation(continuation); return } + guard shouldWait() else { resumeContinuation(continuation); return } continuations[key] = continuation } @@ -85,7 +108,7 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) } @@ -93,41 +116,44 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// - Parameter number: The number to decrement count by. @inlinable - func _decrementCount(by number: UInt = 1) { - defer { _resumeContinuations() } + internal func decrementCount(by number: UInt = 1) { + defer { resumeContinuations() } guard currentCount > 0 else { return } currentCount -= number } /// Resume previously waiting continuations for countdown event. @inlinable - func _resumeContinuations() { + internal func resumeContinuations() { while !continuations.isEmpty && isSet { let (_, continuation) = continuations.removeFirst() - _resumeContinuation(continuation) + resumeContinuation(continuation) } } - /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. + /// Increments the countdown event current count by the specified value. /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. - /// Continuation can be resumed with error and some cleanup code can be run here. + /// - Parameter count: The value by which to increase ``currentCount``. + @inlinable + internal func incrementCount(by count: UInt = 1) { + self.currentCount += count + } + + /// Resets current count to initial count. + @inlinable + internal func resetCount() { + self.currentCount = initialCount + resumeContinuations() + } + + /// Resets initial count and current count to specified value. /// - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + /// - Parameter count: The new initial count. @inlinable - nonisolated func _withPromisedContinuation() async throws { - let key = UUID() - try await Continuation.withCancellation(synchronizedWith: locker) { - Task { [weak self] in - await self?._removeContinuation(withKey: key) - } - } operation: { continuation in - Task { [weak self] in - await self?._addContinuation(continuation, withKey: key) - } - } + internal func resetCount(to count: UInt) { + initialCount = count + self.currentCount = count + resumeContinuations() } // MARK: Public @@ -158,17 +184,33 @@ public actor AsyncCountdownEvent: AsyncObject { /// Use this to indicate usage of resource from high priority tasks. /// /// - Parameter count: The value by which to increase ``currentCount``. - public func increment(by count: UInt = 1) { - self.currentCount += count + public nonisolated func increment( + by count: UInt = 1, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + Task { await incrementCount(by: count) } } /// Resets current count to initial count. /// /// If the current count becomes less or equal to limit, multiple queued tasks /// are resumed from suspension until current count exceeds limit. - public func reset() { - self.currentCount = initialCount - _resumeContinuations() + /// + /// - Parameters: + /// - file: The file reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function reset originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public nonisolated func reset( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + Task { await resetCount() } } /// Resets initial count and current count to specified value. @@ -176,19 +218,41 @@ public actor AsyncCountdownEvent: AsyncObject { /// If the current count becomes less or equal to limit, multiple queued tasks /// are resumed from suspension until current count exceeds limit. /// - /// - Parameter count: The new initial count. - public func reset(to count: UInt) { - initialCount = count - self.currentCount = count - _resumeContinuations() + /// - Parameters: + /// - count: The new initial count. + /// - file: The file reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function reset originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public nonisolated func reset( + to count: UInt, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + Task { await resetCount(to: count) } } /// Registers a signal (decrements) with the countdown event. /// /// Decrement the countdown. If the current count becomes less or equal to limit, /// one queued task is resumed from suspension. - public func signal() { - signal(repeat: 1) + /// + /// - Parameters: + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public nonisolated func signal( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + Task { await decrementCount(by: 1) } } /// Registers multiple signals (decrements by provided count) with the countdown event. @@ -196,9 +260,21 @@ public actor AsyncCountdownEvent: AsyncObject { /// Decrement the countdown by the provided count. If the current count becomes less or equal to limit, /// multiple queued tasks are resumed from suspension until current count exceeds limit. /// - /// - Parameter count: The number of signals to register. - public func signal(repeat count: UInt) { - _decrementCount(by: count) + /// - Parameters: + /// - count: The number of signals to register. + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public nonisolated func signal( + repeat count: UInt, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + Task { await decrementCount(by: count) } } /// Waits for, or increments, a countdown event. @@ -207,9 +283,23 @@ public actor AsyncCountdownEvent: AsyncObject { /// Otherwise, current task is suspended until either a signal occurs or event is reset. /// /// Use this to wait for high priority tasks completion to start low priority ones. + /// + /// - Parameters: + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { - guard _wait() else { currentCount += 1; return } - try? await _withPromisedContinuation() + public func wait( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { + guard shouldWait() else { currentCount += 1; return } + try await withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index 966baf90..85fe77ac 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -9,20 +9,35 @@ import Foundation /// An async event suspends tasks if current state is non-signaled and resumes execution when event is signalled. /// /// You can signal event by calling the ``signal()`` method and reset signal by calling ``reset()``. -/// Wait for event signal by calling ``wait()`` method or its timeout variation ``wait(forNanoseconds:)``. -public actor AsyncEvent: AsyncObject { +/// Wait for event signal by calling ``wait()`` method or its timeout variation ``wait(forNanoseconds:)``: +/// +/// ```swift +/// // create event with initial state (signalled or not) +/// let event = AsyncEvent(signaledInitially: false) +/// // wait for event to be signalled, +/// // fails only if task cancelled +/// try await event.wait() +/// // or wait with some timeout +/// try await event.wait(forNanoseconds: 1_000_000_000) +/// +/// // signal event after completing some task +/// event.signal() +/// ``` +public actor AsyncEvent: AsyncObject, ContinuableCollection { /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The continuations stored with an associated key for all the suspended task that are waiting for event signal. @usableFromInline - private(set) var continuations: [UUID: Continuation] = [:] + internal private(set) var continuations: [UUID: Continuation] = [:] /// Indicates whether current state of event is signalled. @usableFromInline - var signalled: Bool + internal private(set) var signalled: Bool // MARK: Internal @@ -32,7 +47,7 @@ public actor AsyncEvent: AsyncObject { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -46,30 +61,23 @@ public actor AsyncEvent: AsyncObject { /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) } - /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. - /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. - /// Continuation can be resumed with error and some cleanup code can be run here. - /// - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + /// Resets signal of event. + @inlinable + internal func resetEvent() { + signalled = false + } + + /// Signals the event and resumes all the tasks + /// suspended and waiting for signal. @inlinable - nonisolated func _withPromisedContinuation() async throws { - let key = UUID() - try await Continuation.withCancellation(synchronizedWith: locker) { - Task { [weak self] in - await self?._removeContinuation(withKey: key) - } - } operation: { continuation in - Task { [weak self] in - await self?._addContinuation(continuation, withKey: key) - } - } + internal func signalEvent() { + continuations.forEach { $0.value.resume() } + continuations = [:] + signalled = true } // MARK: Public @@ -78,7 +86,6 @@ public actor AsyncEvent: AsyncObject { /// By default, event is initially in signalled state. /// /// - Parameter signalled: The signal state for event. - /// /// - Returns: The newly created event. public init(signaledInitially signalled: Bool = true) { self.signalled = signalled @@ -89,26 +96,60 @@ public actor AsyncEvent: AsyncObject { /// Resets signal of event. /// /// After reset, tasks have to wait for event signal to complete. - public func reset() { - signalled = false + /// + /// - Parameters: + /// - file: The file reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function reset originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + @Sendable + public nonisolated func reset() { + Task { await resetEvent() } } /// Signals the event. /// /// Resumes all the tasks suspended and waiting for signal. - public func signal() { - continuations.forEach { $0.value.resume() } - continuations = [:] - signalled = true + /// + /// - Parameters: + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + @Sendable + public nonisolated func signal( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + Task { await signalEvent() } } /// Waits for event signal, or proceeds if already signalled. /// /// Only waits asynchronously, if event is in non-signaled state, /// until event is signalled. + /// + /// - Parameters: + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { + public func wait( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { guard !signalled else { return } - try? await _withPromisedContinuation() + try await withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/AsyncObject.swift b/Sources/AsyncObjects/AsyncObject.swift index 6b5eeed8..b539de1a 100644 --- a/Sources/AsyncObjects/AsyncObject.swift +++ b/Sources/AsyncObjects/AsyncObject.swift @@ -1,68 +1,67 @@ -import Foundation - -/// A result value indicating whether a task finished before a specified time. -@frozen -public enum TaskTimeoutResult: Hashable, Sendable { - /// Indicates that a task successfully finished - /// before the specified time elapsed. - case success - /// Indicates that a task failed to finish - /// before the specified time elapsed. - case timedOut -} - /// An object type that can provide synchronization across multiple task contexts /// /// Waiting asynchronously can be done by calling ``wait()`` method, /// while object decides when to resume task. Similarly, ``signal()`` can be used /// to indicate resuming suspended tasks. +@rethrows public protocol AsyncObject: Sendable { /// Signals the object for task synchronization. /// /// Object might resume suspended tasks /// or synchronize tasks differently. + /// + /// - Parameters: + /// - file: The file signal originates from. + /// - function: The function signal originates from. + /// - line: The line signal originates from. @Sendable - func signal() async + func signal(file: String, function: String, line: UInt) /// Waits for the object to green light task execution. /// /// Waits asynchronously suspending current task, instead of blocking any thread. /// Async object has to resume the task at a later time depending on its requirement. /// - /// - Note: Method might return immediately depending upon the synchronization object requirement. - @Sendable - func wait() async - /// Waits for the object to green light task execution within the duration. + /// Might throw some error or never throws depending on implementation. /// - /// Waits asynchronously suspending current task, instead of blocking any thread within the duration. - /// Async object has to resume the task at a later time depending on its requirement. - /// Depending upon whether wait succeeds or timeout expires result is returned. + /// - Parameters: + /// - file: The file wait request originates from. + /// - function: The function wait request originates from. + /// - line: The line signal wait request originates from. /// - /// - Parameter duration: The duration in nano seconds to wait until. - /// - Returns: The result indicating whether wait completed or timed out. /// - Note: Method might return immediately depending upon the synchronization object requirement. - @discardableResult @Sendable - func wait(forNanoseconds duration: UInt64) async -> TaskTimeoutResult + func wait(file: String, function: String, line: UInt) async throws } -public extension AsyncObject where Self: AnyObject { +// TODO: add clock based timeout for Swift >=5.7 +public extension AsyncObject { /// Waits for the object to green light task execution within the duration. /// /// Waits asynchronously suspending current task, instead of blocking any thread. /// Depending upon whether wait succeeds or timeout expires result is returned. /// Async object has to resume the task at a later time depending on its requirement. /// - /// - Parameter duration: The duration in nano seconds to wait until. - /// - Returns: The result indicating whether wait completed or timed out. + /// - Parameters: + /// - duration: The duration in nano seconds to wait until. + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// + /// - Throws: `CancellationError` if cancelled or `DurationTimeoutError` if timed out. /// - Note: Method might return immediately depending upon the synchronization object requirement. - @discardableResult @Sendable - func wait(forNanoseconds duration: UInt64) async -> TaskTimeoutResult { - return await waitForTaskCompletion( + func wait( + forNanoseconds duration: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { + return try await waitForTaskCompletion( withTimeoutInNanoseconds: duration - ) { [weak self] in - await self?.wait() - } + ) { try await self.wait(file: file, function: function, line: line) } } } @@ -71,13 +70,31 @@ public extension AsyncObject where Self: AnyObject { /// Invokes ``AsyncObject/wait()`` for all objects /// and returns only when all the invocation completes. /// -/// - Parameter objects: The objects to wait for. +/// - Parameters: +/// - objects: The objects to wait for. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). +/// +/// - Throws: `CancellationError` if cancelled. @inlinable @Sendable -public func waitForAll(_ objects: [any AsyncObject]) async { - await withTaskGroup(of: Void.self) { group in - objects.forEach { group.addTask(operation: $0.wait) } - await group.waitForAll() +public func waitForAll( + _ objects: [any AsyncObject], + file: String = #fileID, + function: String = #function, + line: UInt = #line +) async throws { + try await withThrowingTaskGroup(of: Void.self) { group in + objects.forEach { obj in + group.addTask { + try await obj.wait(file: file, function: function, line: line) + } + } + try await group.waitForAll() } } @@ -86,11 +103,25 @@ public func waitForAll(_ objects: [any AsyncObject]) async { /// Invokes ``AsyncObject/wait()`` for all objects /// and returns only when all the invocation completes. /// -/// - Parameter objects: The objects to wait for. +/// - Parameters: +/// - objects: The objects to wait for. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). +/// +/// - Throws: `CancellationError` if cancelled. @inlinable @Sendable -public func waitForAll(_ objects: any AsyncObject...) async { - await waitForAll(objects) +public func waitForAll( + _ objects: any AsyncObject..., + file: String = #fileID, + function: String = #function, + line: UInt = #line +) async throws { + try await waitForAll(objects, file: file, function: function, line: line) } /// Waits for multiple objects to green light task execution @@ -103,15 +134,29 @@ public func waitForAll(_ objects: any AsyncObject...) async { /// - Parameters: /// - objects: The objects to wait for. /// - duration: The duration in nano seconds to wait until. -/// - Returns: The result indicating whether wait completed or timed out. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). +/// +/// - Throws: `CancellationError` if cancelled +/// or `DurationTimeoutError` if timed out. @inlinable @Sendable public func waitForAll( _ objects: [any AsyncObject], - forNanoseconds duration: UInt64 -) async -> TaskTimeoutResult { - return await waitForTaskCompletion(withTimeoutInNanoseconds: duration) { - await waitForAll(objects) + forNanoseconds duration: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line +) async throws { + return try await waitForTaskCompletion(withTimeoutInNanoseconds: duration) { + try await waitForAll( + objects, + file: file, function: function, line: line + ) } } @@ -125,14 +170,28 @@ public func waitForAll( /// - Parameters: /// - objects: The objects to wait for. /// - duration: The duration in nano seconds to wait until. -/// - Returns: The result indicating whether wait completed or timed out. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). +/// +/// - Throws: `CancellationError` if cancelled +/// or `DurationTimeoutError` if timed out. @inlinable @Sendable public func waitForAll( _ objects: any AsyncObject..., - forNanoseconds duration: UInt64 -) async -> TaskTimeoutResult { - return await waitForAll(objects, forNanoseconds: duration) + forNanoseconds duration: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line +) async throws { + return try await waitForAll( + objects, forNanoseconds: duration, + file: file, function: function, line: line + ) } /// Waits for multiple objects to green light task execution @@ -144,12 +203,30 @@ public func waitForAll( /// - Parameters: /// - objects: The objects to wait for. /// - count: The number of objects to wait for. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). +/// +/// - Throws: `CancellationError` if cancelled. @inlinable @Sendable -public func waitForAny(_ objects: [any AsyncObject], count: Int = 1) async { - await withTaskGroup(of: Void.self) { group in - objects.forEach { group.addTask(operation: $0.wait) } - for _ in 0.. TaskTimeoutResult { - return await waitForTaskCompletion(withTimeoutInNanoseconds: duration) { - await waitForAny(objects, count: count) + forNanoseconds duration: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line +) async throws { + return try await waitForTaskCompletion(withTimeoutInNanoseconds: duration) { + try await waitForAny( + objects, count: count, + file: file, function: function, line: line + ) } } @@ -204,15 +312,29 @@ public func waitForAny( /// - objects: The objects to wait for. /// - count: The number of objects to wait for. /// - duration: The duration in nano seconds to wait until. -/// - Returns: The result indicating whether wait completed or timed out. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). +/// +/// - Throws: `CancellationError` if cancelled +/// or `DurationTimeoutError` if timed out. @inlinable @Sendable public func waitForAny( _ objects: any AsyncObject..., count: Int = 1, - forNanoseconds duration: UInt64 -) async -> TaskTimeoutResult { - return await waitForAny(objects, count: count, forNanoseconds: duration) + forNanoseconds duration: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line +) async throws { + return try await waitForAny( + objects, count: count, forNanoseconds: duration, + file: file, function: function, line: line + ) } /// Waits for the provided task to be completed within the timeout duration. @@ -223,30 +345,65 @@ public func waitForAny( /// - Parameters: /// - task: The task to execute and wait for completion. /// - timeout: The duration in nano seconds to wait until. -/// - Returns: The result indicating whether task execution completed -/// or timed out. +/// +/// - Throws: `CancellationError` if cancelled +/// or `DurationTimeoutError` if timed out. @Sendable public func waitForTaskCompletion( withTimeoutInNanoseconds timeout: UInt64, - _ task: @escaping @Sendable () async -> Void -) async -> TaskTimeoutResult { - var timedOut = true - await withTaskGroup(of: Bool.self) { group in + _ task: @escaping @Sendable () async throws -> Void +) async throws { + try await withThrowingTaskGroup(of: Void.self) { group in await GlobalContinuation.with { continuation in group.addTask { continuation.resume() - await task() - return !Task.isCancelled + try await task() } } group.addTask { await Task.yield() - return (try? await Task.sleep(nanoseconds: timeout + 1_000)) == nil - } - if let result = await group.next() { - timedOut = !result + try await Task.sleep(nanoseconds: timeout + 1_000) + throw DurationTimeoutError(for: timeout, tolerance: 1_000) } - group.cancelAll() + defer { group.cancelAll() } + try await group.next() + } +} + +/// An error that indicates a task was timed out for provided duration +/// and task specific tolerance. +/// +/// This error is also thrown automatically by +/// ``waitForTaskCompletion(withTimeoutInNanoseconds:_:)``, +/// if the task execution exceeds provided time out duration. +/// +/// While ``duration`` is user configurable, ``tolerance`` is task specific. +@frozen +public struct DurationTimeoutError: Error, Sendable { + /// The duration in nano seconds that was provided for timeout operation. + /// + /// The total timeout duration takes consideration + /// of both provided duration and operation specific tolerance: + /// + /// total timeout duration = ``duration`` + ``tolerance`` + public let duration: UInt64 + /// The additional tolerance in nano seconds used for timeout operation. + /// + /// The total timeout duration takes consideration + /// of both provided duration and operation specific tolerance: + /// + /// total timeout duration = ``duration`` + ``tolerance`` + public let tolerance: UInt64 + + /// Creates a new timeout error based on provided duration and task specific tolerance. + /// + /// - Parameters: + /// - duration: The provided timeout duration in nano seconds. + /// - tolerance: The task specific additional margin in nano seconds. + /// + /// - Returns: The newly created timeout error. + public init(for duration: UInt64, tolerance: UInt64 = 0) { + self.duration = duration + self.tolerance = tolerance } - return timedOut ? .timedOut : .success } diff --git a/Sources/AsyncObjects/AsyncSemaphore.swift b/Sources/AsyncObjects/AsyncSemaphore.swift index de3278db..a1a6c4d9 100644 --- a/Sources/AsyncObjects/AsyncSemaphore.swift +++ b/Sources/AsyncObjects/AsyncSemaphore.swift @@ -3,6 +3,7 @@ import Foundation #else @preconcurrency import Foundation #endif + import OrderedCollections /// An object that controls access to a resource across multiple task contexts through use of a traditional counting semaphore. @@ -12,25 +13,43 @@ import OrderedCollections /// /// You increment a semaphore count by calling the ``signal()`` method /// and decrement a semaphore count by calling ``wait()`` method -/// or its timeout variation ``wait(forNanoseconds:)``. -public actor AsyncSemaphore: AsyncObject { +/// or its timeout variation ``wait(forNanoseconds:)``: +/// +/// ```swift +/// // create limiting concurrent access count +/// let semaphore = AsyncSemaphore(value: 1) +/// // wait for semaphore access, +/// // fails only if task cancelled +/// try await semaphore.wait() +/// // or wait with some timeout +/// try await semaphore.wait(forNanoseconds: 1_000_000_000) +/// // release after executing critical async tasks +/// defer { semaphore.signal() } +/// ``` +public actor AsyncSemaphore: AsyncObject, ContinuableCollection { /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The continuations stored with an associated key for all the suspended task that are waiting for access to resource. @usableFromInline - private(set) var continuations: OrderedDictionary = [:] + internal private(set) var continuations: + OrderedDictionary< + UUID, + Continuation + > = [:] /// Pool size for concurrent resource access. /// Has value provided during initialization incremented by one. @usableFromInline - let limit: UInt + internal let limit: UInt /// Current count of semaphore. /// Can have maximum value up to `limit`. @usableFromInline - private(set) var count: Int + internal private(set) var count: Int // MARK: Internal @@ -40,7 +59,7 @@ public actor AsyncSemaphore: AsyncObject { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -55,38 +74,25 @@ public actor AsyncSemaphore: AsyncObject { /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) - _incrementCount() + incrementCount() } /// Increments semaphore count within limit provided. @inlinable - func _incrementCount() { + internal func incrementCount() { guard count < limit else { return } count += 1 } - /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. - /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. - /// Continuation can be resumed with error and some cleanup code can be run here. - /// - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + /// Signals (increments) and releases a semaphore. @inlinable - nonisolated func _withPromisedContinuation() async throws { - let key = UUID() - try await Continuation.withCancellation(synchronizedWith: locker) { - Task { [weak self] in - await self?._removeContinuation(withKey: key) - } - } operation: { continuation in - Task { [weak self] in - await self?._addContinuation(continuation, withKey: key) - } - } + internal func signalSemaphore() { + incrementCount() + guard !continuations.isEmpty else { return } + let (_, continuation) = continuations.removeFirst() + continuation.resume() } // MARK: Public @@ -98,7 +104,6 @@ public actor AsyncSemaphore: AsyncObject { /// Passing a value greater than zero is useful for managing a finite pool of resources, where the pool size is equal to the value. /// /// - Parameter count: The starting value for the semaphore. - /// /// - Returns: The newly created semaphore. public init(value count: UInt = 0) { self.limit = count + 1 @@ -110,22 +115,46 @@ public actor AsyncSemaphore: AsyncObject { /// Signals (increments) a semaphore. /// /// Increment the counting semaphore. - /// If the previous value was less than zero, - /// current task is resumed from suspension. - public func signal() { - _incrementCount() - guard !continuations.isEmpty else { return } - let (_, continuation) = continuations.removeFirst() - continuation.resume() + /// If any previous task is waiting for access to semaphore, + /// then the task is resumed from suspension. + /// + /// - Parameters: + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + @Sendable + public nonisolated func signal( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + Task { await signalSemaphore() } } /// Waits for, or decrements, a semaphore. /// /// Decrement the counting semaphore. If the resulting value is less than zero, /// current task is suspended until a signal occurs. + /// + /// - Parameters: + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { + public func wait( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { guard count <= 1 else { count -= 1; return } - try? await _withPromisedContinuation() + try await withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/CancellationSource.swift b/Sources/AsyncObjects/CancellationSource.swift index f725e1ee..b174a5c6 100644 --- a/Sources/AsyncObjects/CancellationSource.swift +++ b/Sources/AsyncObjects/CancellationSource.swift @@ -1,5 +1,3 @@ -import Foundation - /// An object that controls cooperative cancellation of multiple registered tasks and linked object registered tasks. /// /// An async event suspends tasks if current state is non-signaled and resumes execution when event is signalled. @@ -10,12 +8,32 @@ import Foundation /// and the cancellation event will be propagated to linked cancellation sources, /// which in turn cancels their registered tasks and further propagates cancellation. /// +/// ```swift +/// // create a root cancellation source +/// let source = CancellationSource() +/// // or a child cancellation source linked with multiple parents +/// let childSource = CancellationSource(linkedWith: source) +/// +/// // create task registered with cancellation source +/// let task = Task(cancellationSource: source) { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// // or register already created task with cancellation source +/// source.register(task: task) +/// +/// // cancel all registered tasks and tasks registered +/// // in linked cancellation sources +/// source.cancel() +/// // or cancel after some time (fails if calling task cancelled) +/// try await source.cancel(afterNanoseconds: 1_000_000_000) +/// ``` +/// /// - Warning: Cancellation sources propagate cancellation event to other linked cancellation sources. /// In case of circular dependency between cancellation sources, app will go into infinite recursion. public actor CancellationSource { /// All the registered tasks for cooperative cancellation. @usableFromInline - private(set) var registeredTasks: [AnyHashable: () -> Void] = [:] + internal private(set) var registeredTasks: [AnyHashable: () -> Void] = [:] /// All the linked cancellation sources that cancellation event will be propagated. /// /// - TODO: Store weak reference for cancellation sources. @@ -23,7 +41,7 @@ public actor CancellationSource { /// private var linkedSources: NSHashTable = .weakObjects() /// ``` @usableFromInline - private(set) var linkedSources: [CancellationSource] = [] + internal private(set) var linkedSources: [CancellationSource] = [] // MARK: Internal @@ -31,7 +49,7 @@ public actor CancellationSource { /// /// - Parameter task: The task to register. @inlinable - func _add(task: Task) { + internal func add(task: Task) { guard !task.isCancelled else { return } registeredTasks[task] = { task.cancel() } } @@ -40,7 +58,7 @@ public actor CancellationSource { /// /// - Parameter task: The task to remove. @inlinable - func _remove(task: Task) { + internal func remove(task: Task) { registeredTasks.removeValue(forKey: task) } @@ -48,26 +66,34 @@ public actor CancellationSource { /// /// - Parameter task: The source to link. @inlinable - func _addSource(_ source: CancellationSource) { + internal func addSource(_ source: CancellationSource) { linkedSources.append(source) } /// Propagate cancellation to linked cancellation sources. @inlinable - nonisolated func _propagateCancellation() async { + internal nonisolated func propagateCancellation() async { await withTaskGroup(of: Void.self) { group in let linkedSources = await linkedSources - linkedSources.forEach { group.addTask(operation: $0.cancel) } + linkedSources.forEach { s in group.addTask { s.cancel() } } await group.waitForAll() } } + /// Trigger cancellation event, initiate cooperative cancellation of registered tasks + /// and propagate cancellation to linked cancellation sources. + internal func cancelAll() async { + registeredTasks.forEach { $1() } + registeredTasks = [:] + await propagateCancellation() + } + // MARK: Public /// Creates a new cancellation source object. /// /// - Returns: The newly created cancellation source. - public init() { } + public init() {} #if swift(>=5.7) /// Creates a new cancellation source object linking to all the provided cancellation sources. @@ -78,12 +104,15 @@ public actor CancellationSource { /// - Parameter sources: The cancellation sources the newly created object will be linked to. /// /// - Returns: The newly created cancellation source. - public nonisolated init(linkedWith sources: [CancellationSource]) async { - await withTaskGroup(of: Void.self) { group in - sources.forEach { source in - group.addTask { await source._addSource(self) } + public init(linkedWith sources: [CancellationSource]) { + self.init() + Task { + await withTaskGroup(of: Void.self) { group in + sources.forEach { source in + group.addTask { await source.addSource(self) } + } + await group.waitForAll() } - await group.waitForAll() } } @@ -95,20 +124,37 @@ public actor CancellationSource { /// - Parameter sources: The cancellation sources the newly created object will be linked to. /// /// - Returns: The newly created cancellation source. - public init(linkedWith sources: CancellationSource...) async { - await self.init(linkedWith: sources) + public init(linkedWith sources: CancellationSource...) { + self.init(linkedWith: sources) } /// Creates a new cancellation source object /// and triggers cancellation event on this object after specified timeout. /// - /// - Parameter nanoseconds: The delay after which cancellation event triggered. + /// - Parameters: + /// - nanoseconds: The delay after which cancellation event triggered. + /// - file: The file cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function cancel request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: The newly created cancellation source. - public init(cancelAfterNanoseconds nanoseconds: UInt64) { + public init( + cancelAfterNanoseconds nanoseconds: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { self.init() Task { [weak self] in - try await self?.cancel(afterNanoseconds: nanoseconds) + try await self?.cancel( + afterNanoseconds: nanoseconds, + file: file, + function: function, + line: line + ) } } #else @@ -120,12 +166,15 @@ public actor CancellationSource { /// - Parameter sources: The cancellation sources the newly created object will be linked to. /// /// - Returns: The newly created cancellation source. - public init(linkedWith sources: [CancellationSource]) async { - await withTaskGroup(of: Void.self) { group in - sources.forEach { source in - group.addTask { await source._addSource(self) } + public convenience init(linkedWith sources: [CancellationSource]) { + self.init() + Task { + await withTaskGroup(of: Void.self) { group in + sources.forEach { source in + group.addTask { await source.addSource(self) } + } + await group.waitForAll() } - await group.waitForAll() } } @@ -137,20 +186,37 @@ public actor CancellationSource { /// - Parameter sources: The cancellation sources the newly created object will be linked to. /// /// - Returns: The newly created cancellation source. - public convenience init(linkedWith sources: CancellationSource...) async { - await self.init(linkedWith: sources) + public convenience init(linkedWith sources: CancellationSource...) { + self.init(linkedWith: sources) } /// Creates a new cancellation source object /// and triggers cancellation event on this object after specified timeout. /// - /// - Parameter nanoseconds: The delay after which cancellation event triggered. + /// - Parameters: + /// - nanoseconds: The delay after which cancellation event triggered. + /// - file: The file cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function cancel request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: The newly created cancellation source. - public convenience init(cancelAfterNanoseconds nanoseconds: UInt64) { + public convenience init( + cancelAfterNanoseconds nanoseconds: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { self.init() Task { [weak self] in - try await self?.cancel(afterNanoseconds: nanoseconds) + try await self?.cancel( + afterNanoseconds: nanoseconds, + file: file, + function: function, + line: line + ) } } #endif @@ -159,146 +225,74 @@ public actor CancellationSource { /// /// If task completes before cancellation event is triggered, it is automatically unregistered. /// - /// - Parameter task: The task to register. - public func register(task: Task) { - _add(task: task) + /// - Parameters: + /// - task: The task to register. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + @Sendable + public nonisolated func register( + task: Task, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { Task { [weak self] in + await self?.add(task: task) let _ = await task.result - await self?._remove(task: task) + await self?.remove(task: task) } } /// Trigger cancellation event, initiate cooperative cancellation of registered tasks /// and propagate cancellation to linked cancellation sources. + /// + /// - Parameters: + /// - file: The file cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function cancel request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @Sendable - public func cancel() async { - registeredTasks.forEach { $1() } - registeredTasks = [:] - await _propagateCancellation() + public nonisolated func cancel( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + Task { await cancelAll() } } /// Trigger cancellation event after provided delay, /// initiate cooperative cancellation of registered tasks /// and propagate cancellation to linked cancellation sources. /// - /// - Parameter nanoseconds: The delay after which cancellation event triggered. + /// - Parameters: + /// - nanoseconds: The delay after which cancellation event triggered. + /// - file: The file cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function cancel request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Throws: `CancellationError` if cancelled. @Sendable - public func cancel(afterNanoseconds nanoseconds: UInt64) async throws { + public func cancel( + afterNanoseconds nanoseconds: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { try await Task.sleep(nanoseconds: nanoseconds) - await cancel() + await cancelAll() } } public extension Task { - /// Runs the given non-throwing operation asynchronously as part of a new task on behalf of the current actor, - /// with the provided cancellation source controlling cooperative cancellation. - /// - /// A top-level task with the provided operation is created, cancellation of which is controlled by provided cancellation source. - /// In the event of cancellation top-level task is cancelled, while returning the value in the returned task. - /// - /// - Parameters: - /// - priority: The priority of the task. Pass `nil` to use the priority from `Task.currentPriority`. - /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. - /// - operation: The operation to perform. - /// - /// - Returns: The newly created task. - /// - Note: In case you want to register and track the top-level task - /// for cancellation use the async initializer instead. - @discardableResult - init( - priority: TaskPriority? = nil, - cancellationSource: CancellationSource, - operation: @escaping @Sendable () async -> Success - ) where Failure == Never { - self.init(priority: priority) { - let task = Self.init(priority: priority, operation: operation) - await cancellationSource.register(task: task) - return await task.value - } - } - - /// Runs the given throwing operation asynchronously as part of a new task on behalf of the current actor, - /// with the provided cancellation source controlling cooperative cancellation. - /// - /// A top-level task with the provided operation is created, cancellation of which is controlled by provided cancellation source. - /// In the event of cancellation top-level task is cancelled, while propagating error in the returned task. - /// - /// - Parameters: - /// - priority: The priority of the task. Pass `nil` to use the priority from `Task.currentPriority`. - /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. - /// - operation: The operation to perform. - /// - /// - Returns: The newly created task. - /// - Note: In case you want to register and track the top-level task - /// for cancellation use the async initializer instead. - @discardableResult - init( - priority: TaskPriority? = nil, - cancellationSource: CancellationSource, - operation: @escaping @Sendable () async throws -> Success - ) where Failure == Error { - self.init(priority: priority) { - let task = Self.init(priority: priority, operation: operation) - await cancellationSource.register(task: task) - return try await task.value - } - } - - /// Runs the given non-throwing operation asynchronously as part of a new task, - /// with the provided cancellation source controlling cooperative cancellation. - /// - /// A top-level task with the provided operation is created, cancellation of which is controlled by provided cancellation source. - /// In the event of cancellation top-level task is cancelled, while returning the value in the returned task. - /// - /// - Parameters: - /// - priority: The priority of the task. - /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. - /// - operation: The operation to perform. - /// - /// - Returns: The newly created task. - /// - Note: In case you want to register and track the top-level task - /// for cancellation use the async initializer instead. - @discardableResult - static func detached( - priority: TaskPriority? = nil, - cancellationSource: CancellationSource, - operation: @escaping @Sendable () async -> Success - ) -> Self where Failure == Never { - return Task.detached(priority: priority) { - let task = Self.init(priority: priority, operation: operation) - await cancellationSource.register(task: task) - return await task.value - } - } - - /// Runs the given throwing operation asynchronously as part of a new task, - /// with the provided cancellation source controlling cooperative cancellation. - /// - /// A top-level task with the provided operation is created, cancellation of which is controlled by provided cancellation source. - /// In the event of cancellation top-level task is cancelled, while returning the value in the returned task. - /// - /// - Parameters: - /// - priority: The priority of the task. - /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. - /// - operation: The operation to perform. - /// - /// - Returns: The newly created task. - /// - Note: In case you want to register and track the top-level task - /// for cancellation use the async initializer instead. - @discardableResult - static func detached( - priority: TaskPriority? = nil, - cancellationSource: CancellationSource, - operation: @escaping @Sendable () async throws -> Success - ) -> Self where Failure == Error { - return Task.detached(priority: priority) { - let task = Self.init(priority: priority, operation: operation) - await cancellationSource.register(task: task) - return try await task.value - } - } - /// Runs the given non-throwing operation asynchronously as part of a new top-level task on behalf of the current actor, /// with the provided cancellation source controlling cooperative cancellation. /// @@ -307,6 +301,12 @@ public extension Task { /// - Parameters: /// - priority: The priority of the task. Pass `nil` to use the priority from `Task.currentPriority`. /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The operation to perform. /// /// - Returns: The newly created task. @@ -314,10 +314,18 @@ public extension Task { init( priority: TaskPriority? = nil, cancellationSource: CancellationSource, + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @escaping @Sendable () async -> Success - ) async where Failure == Never { + ) where Failure == Never { self.init(priority: priority, operation: operation) - await cancellationSource.register(task: self) + cancellationSource.register( + task: self, + file: file, + function: function, + line: line + ) } /// Runs the given throwing operation asynchronously as part of a new top-level task on behalf of the current actor, @@ -328,6 +336,12 @@ public extension Task { /// - Parameters: /// - priority: The priority of the task. Pass `nil` to use the priority from `Task.currentPriority`. /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The operation to perform. /// /// - Returns: The newly created task. @@ -335,10 +349,18 @@ public extension Task { init( priority: TaskPriority? = nil, cancellationSource: CancellationSource, + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @escaping @Sendable () async throws -> Success - ) async where Failure == Error { + ) where Failure == Error { self.init(priority: priority, operation: operation) - await cancellationSource.register(task: self) + cancellationSource.register( + task: self, + file: file, + function: function, + line: line + ) } /// Runs the given non-throwing operation asynchronously as part of a new top-level task, @@ -349,6 +371,12 @@ public extension Task { /// - Parameters: /// - priority: The priority of the task. /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The operation to perform. /// /// - Returns: The newly created task. @@ -356,10 +384,18 @@ public extension Task { static func detached( priority: TaskPriority? = nil, cancellationSource: CancellationSource, + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @escaping @Sendable () async -> Success - ) async -> Self where Failure == Never { + ) -> Self where Failure == Never { let task = Task.detached(priority: priority, operation: operation) - await cancellationSource.register(task: task) + cancellationSource.register( + task: task, + file: file, + function: function, + line: line + ) return task } @@ -371,6 +407,12 @@ public extension Task { /// - Parameters: /// - priority: The priority of the task. /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The operation to perform. /// /// - Returns: The newly created task. @@ -378,10 +420,18 @@ public extension Task { static func detached( priority: TaskPriority? = nil, cancellationSource: CancellationSource, + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @escaping @Sendable () async throws -> Success - ) async -> Self where Failure == Error { + ) -> Self where Failure == Error { let task = Task.detached(priority: priority, operation: operation) - await cancellationSource.register(task: task) + cancellationSource.register( + task: task, + file: file, + function: function, + line: line + ) return task } } diff --git a/Sources/AsyncObjects/Continuable.swift b/Sources/AsyncObjects/Continuation/Continuable.swift similarity index 51% rename from Sources/AsyncObjects/Continuable.swift rename to Sources/AsyncObjects/Continuation/Continuable.swift index b7287b46..d8267812 100644 --- a/Sources/AsyncObjects/Continuable.swift +++ b/Sources/AsyncObjects/Continuation/Continuable.swift @@ -4,7 +4,8 @@ /// /// Use continuations for interfacing Swift tasks with event loops, delegate methods, callbacks, /// and other non-async scheduling mechanisms. -public protocol Continuable { +@rethrows +public protocol Continuable { /// The type of value to resume the continuation with in case of success. associatedtype Success /// The type of error to resume the continuation with in case of failure. @@ -29,7 +30,8 @@ public protocol Continuable { /// /// Use non-throwing continuation to interface synchronous code that might fail, /// or implements task cancellation mechanism, with asynchronous code. -public protocol ThrowingContinuable: Continuable +@rethrows +internal protocol ThrowingContinuable: Continuable where Failure == Error { /// Suspends the current task, then calls the given closure /// with a throwing continuation for the current task. @@ -42,15 +44,14 @@ where Failure == Error { /// that is the notional source for the continuation, /// used to identify the continuation in runtime diagnostics /// related to misuse of this continuation. - /// - fn: A closure that takes the throwing continuation parameter. - /// You can resume the continuation exactly once. + /// - body: A closure that takes the throwing continuation parameter. + /// You can resume the continuation exactly once. /// /// - Returns: The value passed to the continuation by the closure. /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable static func with( function: String, - _ fn: (Self) -> Void + _ body: (Self) -> Void ) async throws -> Success } @@ -58,7 +59,8 @@ where Failure == Error { /// by representing task state and allowing task to be always resumed with some value. /// /// Use non-throwing continuation to interface synchronous code that never fails with asynchronous code. -public protocol NonThrowingContinuable: Continuable +@rethrows +internal protocol NonThrowingContinuable: Continuable where Failure == Never { /// Suspends the current task, then calls the given closure /// with a non-throwing continuation for the current task. @@ -71,14 +73,13 @@ where Failure == Never { /// that is the notional source for the continuation, /// used to identify the continuation in runtime diagnostics /// related to misuse of this continuation. - /// - fn: A closure that takes the non-throwing continuation parameter. - /// You can resume the continuation exactly once. + /// - body: A closure that takes the non-throwing continuation parameter. + /// You can resume the continuation exactly once. /// /// - Returns: The value passed to the continuation by the closure. - @inlinable static func with( function: String, - _ fn: (Self) -> Void + _ body: (Self) -> Void ) async -> Success } #else @@ -87,6 +88,7 @@ where Failure == Never { /// /// Use continuations for interfacing Swift tasks with event loops, delegate methods, callbacks, /// and other non-async scheduling mechanisms. +@rethrows public protocol Continuable { /// The type of value to resume the continuation with in case of success. associatedtype Success @@ -112,7 +114,8 @@ public protocol Continuable { /// /// Use non-throwing continuation to interface synchronous code that might fail, /// or implements task cancellation mechanism, with asynchronous code. -public protocol ThrowingContinuable: Continuable where Failure == Error { +@rethrows +internal protocol ThrowingContinuable: Continuable where Failure == Error { /// Suspends the current task, then calls the given closure /// with a throwing continuation for the current task. /// @@ -124,15 +127,14 @@ public protocol ThrowingContinuable: Continuable where Failure == Error { /// that is the notional source for the continuation, /// used to identify the continuation in runtime diagnostics /// related to misuse of this continuation. - /// - fn: A closure that takes the throwing continuation parameter. - /// You can resume the continuation exactly once. + /// - body: A closure that takes the throwing continuation parameter. + /// You can resume the continuation exactly once. /// /// - Returns: The value passed to the continuation by the closure. /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable static func with( function: String, - _ fn: (Self) -> Void + _ body: (Self) -> Void ) async throws -> Success } @@ -140,7 +142,8 @@ public protocol ThrowingContinuable: Continuable where Failure == Error { /// by representing task state and allowing task to be always resumed with some value. /// /// Use non-throwing continuation to interface synchronous code that never fails with asynchronous code. -public protocol NonThrowingContinuable: Continuable where Failure == Never { +@rethrows +internal protocol NonThrowingContinuable: Continuable where Failure == Never { /// Suspends the current task, then calls the given closure /// with a non-throwing continuation for the current task. /// @@ -152,153 +155,60 @@ public protocol NonThrowingContinuable: Continuable where Failure == Never { /// that is the notional source for the continuation, /// used to identify the continuation in runtime diagnostics /// related to misuse of this continuation. - /// - fn: A closure that takes the non-throwing continuation parameter. - /// You can resume the continuation exactly once. + /// - body: A closure that takes the non-throwing continuation parameter. + /// You can resume the continuation exactly once. /// /// - Returns: The value passed to the continuation by the closure. - @inlinable static func with( function: String, - _ fn: (Self) -> Void + _ body: (Self) -> Void ) async -> Success } #endif -public extension Continuable where Failure == Error { - /// Cancel continuation by resuming with cancellation error. +public extension Continuable { + /// Dummy cancellation method for continuations + /// that don't support cancellation. @inlinable - func cancel() { self.resume(throwing: CancellationError()) } -} + func cancel() { /* Do nothing */ } -#if DEBUG || ASYNCOBJECTS_USE_CHECKEDCONTINUATION -/// The continuation type used in ``AsyncObjects`` package. -/// -/// In `DEBUG` mode or if `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag turned on -/// `CheckedContinuation` is used. -/// -/// In `RELEASE` mode and in absence of `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` -/// flag `UnsafeContinuation` is used. -public typealias GlobalContinuation = CheckedContinuation - -extension CheckedContinuation: Continuable {} - -extension CheckedContinuation: ThrowingContinuable where E == Error { - /// Suspends the current task, then calls the given closure - /// with a checked throwing continuation for the current task. + /// Resume the task awaiting the continuation by having it return normally from its suspension point. /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// `CheckedContinuation` logs messages proving additional info on these errors. - /// Once all errors resolved, use `UnsafeContinuation` in release mode to benefit improved performance - /// at the loss of additional runtime checks. + /// A continuation must be resumed at least once. /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes a `CheckedContinuation` parameter. - /// You must resume the continuation exactly once. + /// After calling this method, control immediately returns to the caller. + /// The task continues executing when its executor schedules it. /// - /// - Returns: The value passed to the continuation by the closure. - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - public static func with( - function: String = #function, - _ body: (CheckedContinuation) -> Void - ) async throws -> T { - return try await withCheckedThrowingContinuation( - function: function, - body - ) + /// - Parameter value: The value to return from the continuation. + func resume(returning value: Success) { + self.resume(with: .success(value)) } -} -extension CheckedContinuation: NonThrowingContinuable where E == Never { - /// Suspends the current task, then calls the given closure - /// with a checked non-throwing continuation for the current task. - /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// `CheckedContinuation` logs messages proving additional info on these errors. - /// Once all errors resolved, use `UnsafeContinuation` in release mode to benefit improved performance - /// at the loss of additional runtime checks. + /// Resume the task that’s awaiting the continuation by returning. /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes a `CheckedContinuation` parameter. - /// You must resume the continuation exactly once. + /// A continuation must be resumed at least once. /// - /// - Returns: The value passed to the continuation by the closure. - @inlinable - public static func with( - function: String = #function, - _ body: (CheckedContinuation) -> Void - ) async -> T { - return await withCheckedContinuation(function: function, body) + /// After calling this method, control immediately returns to the caller. + /// The task continues executing when its executor schedules it. + func resume(returning value: Success = ()) where Success == Void { + self.resume(with: .success(value)) } -} -#else -/// The continuation type used in ``AsyncObjects`` package. -/// -/// In `DEBUG` mode or if `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag turned on -/// `CheckedContinuation` is used. -/// -/// In `RELEASE` mode and in absence of `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` -/// flag `UnsafeContinuation` is used. -public typealias GlobalContinuation = UnsafeContinuation - -extension UnsafeContinuation: Continuable {} -extension UnsafeContinuation: ThrowingContinuable where E == Error { - /// Suspends the current task, then calls the given closure - /// with an unsafe throwing continuation for the current task. + /// Resume the task awaiting the continuation by having it throw an error from its suspension point. /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. + /// A continuation must be resumed at least once. /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes an `UnsafeContinuation` parameter. - /// You must resume the continuation exactly once. + /// After calling this method, control immediately returns to the caller. + /// The task continues executing when its executor schedules it. /// - /// - Returns: The value passed to the continuation by the closure. - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - public static func with( - function: String = #function, - _ fn: (UnsafeContinuation) -> Void - ) async throws -> T { - return try await withUnsafeThrowingContinuation(fn) + /// - Parameter error: The error to throw from the continuation. + func resume(throwing error: Failure) { + self.resume(with: .failure(error)) } } -extension UnsafeContinuation: NonThrowingContinuable where E == Never { - /// Suspends the current task, then calls the given closure - /// with an unsafe non-throwing continuation for the current task. - /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. - /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes an `UnsafeContinuation` parameter. - /// You must resume the continuation exactly once. - /// - /// - Returns: The value passed to the continuation by the closure. +public extension Continuable where Failure == Error { + /// Cancel continuation by resuming with cancellation error. @inlinable - public static func with( - function: String = #function, - _ fn: (UnsafeContinuation) -> Void - ) async -> T { - return await withUnsafeContinuation(fn) - } + func cancel() { self.resume(throwing: CancellationError()) } } -#endif diff --git a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift new file mode 100644 index 00000000..38fb669e --- /dev/null +++ b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift @@ -0,0 +1,79 @@ +#if swift(>=5.7) +import Foundation +#else +@preconcurrency import Foundation +#endif + +/// A type that manages a collection of continuations with an associated key. +/// +/// A MUTual EXclusion object is used to synchronize continuations state. +@rethrows +internal protocol ContinuableCollection { + /// The continuation item type in collection. + associatedtype Continuation: Continuable + /// The key type that is associated with each continuation item. + associatedtype Key: Hashable + /// The MUTual EXclusion object type used + /// to synchronize continuation state. + associatedtype Lock: Exclusible + + /// The MUTual EXclusion object used + /// to synchronize continuation state. + var locker: Lock { get } + /// Add continuation with the provided key to collection for tracking. + /// + /// - Parameters: + /// - continuation: The continuation value to add + /// - key: The key to associate continuation with. + func addContinuation(_ continuation: Continuation, withKey key: Key) async + /// Remove continuation with the associated key from collection out of tracking. + /// + /// - Parameter key: The key for continuation to remove. + func removeContinuation(withKey key: Key) async + /// Suspends the current task, then calls the given closure with a continuation for the current task. + /// + /// - Returns: The value continuation is resumed with. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + func withPromisedContinuation() async rethrows -> Continuation.Success +} + +extension ContinuableCollection { + /// Remove continuation associated with provided key. + /// + /// Default implementation that does nothing. + /// + /// - Parameter key: The key for continuation to remove. + func removeContinuation(withKey key: Key) async { /* Do nothing */ } +} + +extension ContinuableCollection +where + Self: AnyObject, Self: Sendable, Continuation: SynchronizedContinuable, + Continuation: Sendable, Continuation.Value: ThrowingContinuable, + Continuation.Lock == Lock, Key == UUID +{ + /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. + /// Continuation can be cancelled with error if current task is cancelled, by invoking `removeContinuation`. + /// + /// Spins up a new continuation and requests to track it with key by invoking `addContinuation`. + /// This operation cooperatively checks for cancellation and reacting to it by invoking `removeContinuation`. + /// Continuation can be resumed with error and some cleanup code can be run here. + /// + /// - Returns: The value continuation is resumed with. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + @inlinable + func withPromisedContinuation() async rethrows -> Continuation.Success { + let key = UUID() + return try await Continuation.withCancellation( + synchronizedWith: locker + ) { + Task { [weak self] in + await self?.removeContinuation(withKey: key) + } + } operation: { continuation in + Task { [weak self] in + await self?.addContinuation(continuation, withKey: key) + } + } + } +} diff --git a/Sources/AsyncObjects/Continuation/GlobalContinuation.swift b/Sources/AsyncObjects/Continuation/GlobalContinuation.swift new file mode 100644 index 00000000..96a4ee82 --- /dev/null +++ b/Sources/AsyncObjects/Continuation/GlobalContinuation.swift @@ -0,0 +1,132 @@ +#if DEBUG || ASYNCOBJECTS_USE_CHECKEDCONTINUATION +/// The continuation type used in ``AsyncObjects`` package. +/// +/// In `DEBUG` mode or if `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag turned on +/// `CheckedContinuation` is used. +/// +/// In `RELEASE` mode and in absence of `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` +/// flag `UnsafeContinuation` is used. +public typealias GlobalContinuation = CheckedContinuation + +extension CheckedContinuation: Continuable {} + +extension CheckedContinuation: ThrowingContinuable where E == Error { + /// Suspends the current task, then calls the given closure + /// with a checked throwing continuation for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `CheckedContinuation` logs messages proving additional info on these errors. + /// Once all errors resolved, use `UnsafeContinuation` in release mode to benefit improved performance + /// at the loss of additional runtime checks. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes a `CheckedContinuation` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + @inlinable + public static func with( + function: String = #function, + _ body: (Self) -> Void + ) async throws -> T { + return try await withCheckedThrowingContinuation( + function: function, + body + ) + } +} + +extension CheckedContinuation: NonThrowingContinuable where E == Never { + /// Suspends the current task, then calls the given closure + /// with a checked non-throwing continuation for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `CheckedContinuation` logs messages proving additional info on these errors. + /// Once all errors resolved, use `UnsafeContinuation` in release mode to benefit improved performance + /// at the loss of additional runtime checks. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes a `CheckedContinuation` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + @inlinable + public static func with( + function: String = #function, + _ body: (Self) -> Void + ) async -> T { + return await withCheckedContinuation(function: function, body) + } +} +#else +/// The continuation type used in ``AsyncObjects`` package. +/// +/// In `DEBUG` mode or if `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag turned on +/// `CheckedContinuation` is used. +/// +/// In `RELEASE` mode and in absence of `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` +/// flag `UnsafeContinuation` is used. +public typealias GlobalContinuation = UnsafeContinuation + +extension UnsafeContinuation: Continuable {} + +extension UnsafeContinuation: ThrowingContinuable where E == Error { + /// Suspends the current task, then calls the given closure + /// with an unsafe throwing continuation for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes an `UnsafeContinuation` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + @inlinable + public static func with( + function: String = #function, + _ body: (Self) -> Void + ) async throws -> T { + return try await withUnsafeThrowingContinuation(body) + } +} + +extension UnsafeContinuation: NonThrowingContinuable where E == Never { + /// Suspends the current task, then calls the given closure + /// with an unsafe non-throwing continuation for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes an `UnsafeContinuation` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + @inlinable + public static func with( + function: String = #function, + _ body: (Self) -> Void + ) async -> T { + return await withUnsafeContinuation(body) + } +} +#endif diff --git a/Sources/AsyncObjects/SafeContinuation.swift b/Sources/AsyncObjects/Continuation/SafeContinuation.swift similarity index 51% rename from Sources/AsyncObjects/SafeContinuation.swift rename to Sources/AsyncObjects/Continuation/SafeContinuation.swift index 4cef6115..97794903 100644 --- a/Sources/AsyncObjects/SafeContinuation.swift +++ b/Sources/AsyncObjects/Continuation/SafeContinuation.swift @@ -11,13 +11,13 @@ /// Only first resume operation is considered and rest are all ignored. /// While there is no checks for missing resume operations, /// `CheckedContinuation` can be used as underlying continuation value for additional runtime checks. -public final class SafeContinuation: Continuable { +@usableFromInline +internal final class SafeContinuation: SynchronizedContinuable { /// Tracks the status of continuation resuming for ``SafeContinuation``. /// /// Depending upon ``SafeContinuation`` status the ``SafeContinuation/resume(with:)`` /// invocation effect is determined. Only first resume operation is considered and rest are all ignored. - @frozen - public enum Status { + enum Status { /// Indicates continuation is waiting to be resumed. /// /// Resuming ``SafeContinuation`` with this status returns control immediately to the caller. @@ -46,15 +46,17 @@ public final class SafeContinuation: Continuable { /// - Returns: Whether the current status can be updated to provided status. private func validateStatus(_ status: Status) -> Bool { switch (self.status, status) { - case (.willResume, .waiting), (.willResume, .willResume): return false - case (.resumed, .waiting), (.resumed, .willResume): return false + case (.willResume, .waiting), (.willResume, .willResume), + (.resumed, .waiting), (.resumed, .willResume): + return false default: return true } } /// Checks whether continuation is already resumed /// or to be resumed with provided value. - public var resumed: Bool { + @usableFromInline + var resumed: Bool { return locker.perform { switch status { case .waiting: @@ -68,6 +70,9 @@ public final class SafeContinuation: Continuable { /// Creates a safe continuation from provided continuation. /// + /// The provided platform lock is used to synchronize + /// continuation state. + /// /// - Parameters: /// - status: The initial ``Status`` of provided continuation. /// - value: The continuation value to store. After passing the continuation @@ -78,7 +83,7 @@ public final class SafeContinuation: Continuable { /// - Returns: The newly created safe continuation. /// - Important: After passing the continuation with this method, /// don’t use it outside of this object. - public init( + init( status: Status = .waiting, with value: C? = nil, synchronizedWith locker: Locker = .init() @@ -94,6 +99,25 @@ public final class SafeContinuation: Continuable { } } + /// Creates a safe continuation from provided continuation. + /// + /// The provided platform lock is used to synchronize + /// continuation state. + /// + /// - Parameters: + /// - value: The continuation value to store. After passing the continuation + /// with this method, don’t use it outside of this object. + /// - locker: The platform lock to use to synchronize continuation state. + /// New lock object is created in case none provided. + /// + /// - Returns: The newly created safe continuation. + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + @usableFromInline + convenience init(with value: C?, synchronizedWith locker: Locker) { + self.init(status: .waiting, with: value, synchronizedWith: locker) + } + /// Store the provided continuation if no continuation was provided during initialization. /// /// Use this method to pass continuation if continuation can't be provided during initialization. @@ -111,7 +135,7 @@ public final class SafeContinuation: Continuable { /// /// - Important: After passing the continuation with this method, /// don’t use it outside of this object. - public func add( + func add( continuation: C, status: Status = .waiting, file: StaticString = #file, @@ -137,41 +161,20 @@ public final class SafeContinuation: Continuable { } } - /// Resume the task awaiting the continuation by having it return normally from its suspension point. - /// - /// A continuation must be resumed at least once. If the continuation has already resumed, - /// then calling this method has no effect. - /// - /// After calling this method, control immediately returns to the caller. - /// The task continues executing when its executor schedules it. - /// - /// - Parameter value: The value to return from the continuation. - public func resume(returning value: C.Success) { - self.resume(with: .success(value)) - } - - /// Resume the task that’s awaiting the continuation by returning. - /// - /// A continuation must be resumed at least once. If the continuation has already resumed, - /// then calling this method has no effect. - /// - /// After calling this method, control immediately returns to the caller. - /// The task continues executing when its executor schedules it. - public func resume() where C.Success == Void { - self.resume(returning: ()) - } - - /// Resume the task awaiting the continuation by having it throw an error from its suspension point. + /// Store the provided continuation if no continuation was provided during initialization. /// - /// A continuation must be resumed at least once. If the continuation has already resumed, - /// then calling this method has no effect. + /// Use this method to pass continuation if continuation can't be provided during initialization. + /// If continuation provided already during initialization, invoking this method will cause runtime exception. /// - /// After calling this method, control immediately returns to the caller. - /// The task continues executing when its executor schedules it. + /// - Parameters: + /// - continuation: The continuation value to store. After passing the continuation + /// with this method, don’t use it outside of this object. /// - /// - Parameter error: The error to throw from the continuation. - public func resume(throwing error: C.Failure) { - self.resume(with: .failure(error)) + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + @usableFromInline + func add(continuation: C) { + self.add(continuation: continuation, status: .waiting) } /// Resume the task awaiting the continuation by having it either return normally @@ -184,7 +187,8 @@ public final class SafeContinuation: Continuable { /// The task continues executing when its executor schedules it. /// /// - Parameter result: A value to either return or throw from the continuation. - public func resume(with result: Result) { + @usableFromInline + func resume(with result: Result) { locker.perform { let finalResult: Result switch (status, value) { @@ -204,102 +208,5 @@ public final class SafeContinuation: Continuable { } } -extension SafeContinuation: ThrowingContinuable where C: ThrowingContinuable { - /// Suspends the current task, then calls the given operation with a``SafeContinuation`` - /// for the current task with a cancellation handler that’s immediately invoked if the current task is canceled. - /// - /// This differs from the operation cooperatively checking for cancellation and reacting to it in that - /// the cancellation handler is always and immediately invoked after resuming continuation with - /// `CancellationError` when the task is canceled. For example, even if the operation - /// is running code that never checks for cancellation and provided continuation to operation hasn't been resumed, - /// a cancellation handler still runs cancelling the continuation and provides a chance to run some cleanup code. - /// - /// - Parameters: - /// - locker: The platform lock to use to synchronize continuation state. - /// New lock object is created in case none provided. - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - handler: A handler immediately invoked if task is cancelled. - /// - operation: A closure that takes an ``SafeContinuation`` parameter. - /// You must resume the continuation at least once. - /// - /// - Returns: The value passed to the continuation by the operation. - /// - Throws: If cancelled or `resume(throwing:)` is called on the continuation, - /// this function throws that error. - public static func withCancellation( - synchronizedWith locker: Locker = .init(), - function: String = #function, - handler: @escaping @Sendable () -> Void, - operation: @escaping (SafeContinuation) -> Void - ) async throws -> C.Success where C.Success: Sendable { - let safeContinuation = SafeContinuation(synchronizedWith: locker) - return try await withTaskCancellationHandler { - return try await C.with(function: function) { continuation in - safeContinuation.add(continuation: continuation) - operation(safeContinuation) - } - } onCancel: { [weak safeContinuation] in - safeContinuation?.cancel() - handler() - } - } - - /// Suspends the current task, then calls the given closure with a ``SafeContinuation`` for the current task. - /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// ``SafeContinuation`` allows accessing and resuming the same continuations in concurrent code - /// by keeping track of underlying continuation value state. - /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes a ``SafeContinuation`` parameter. - /// You must resume the continuation at least once. - /// - /// - Returns: The value passed to the continuation by the closure. - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - public static func with( - function: String = #function, - _ body: (SafeContinuation) -> Void - ) async throws -> C.Success { - return try await C.with(function: function) { continuation in - body(SafeContinuation(with: continuation)) - } - } -} - -extension SafeContinuation: NonThrowingContinuable -where C: NonThrowingContinuable { - /// Suspends the current task, then calls the given closure with a ``SafeContinuation`` for the current task. - /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// ``SafeContinuation`` allows accessing and resuming the same continuations in concurrent code - /// by keeping track of underlying continuation value state. - /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes a ``SafeContinuation`` parameter. - /// You must resume the continuation exactly once. - /// - /// - Returns: The value passed to the continuation by the closure. - @inlinable - public static func with( - function: String = #function, - _ body: (SafeContinuation) -> Void - ) async -> C.Success { - return await C.with(function: function) { continuation in - body(SafeContinuation(with: continuation)) - } - } -} - extension SafeContinuation: @unchecked Sendable where C.Success: Sendable {} extension SafeContinuation.Status: Sendable where C.Success: Sendable {} diff --git a/Sources/AsyncObjects/Continuation/SynchronizedContinuable.swift b/Sources/AsyncObjects/Continuation/SynchronizedContinuable.swift new file mode 100644 index 00000000..be3fe55d --- /dev/null +++ b/Sources/AsyncObjects/Continuation/SynchronizedContinuable.swift @@ -0,0 +1,189 @@ +/// A type that allows to interface between synchronous and asynchronous code, +/// by representing synchronized task state and allowing exclusive task resuming +/// with some value or error. +/// +/// Use synchronized continuations for interfacing Swift tasks with event loops, +/// delegate methods, callbacks, and other non-async scheduling mechanisms +/// where continuations have chance to be resumed multiple times. +@rethrows +@usableFromInline +internal protocol SynchronizedContinuable: Continuable { + /// The MUTual EXclusion object type used + /// to synchronize continuation state. + associatedtype Lock: Exclusible + /// The actual continuation value type. + associatedtype Value: Continuable + where Value.Success == Success, Value.Failure == Failure + + /// Creates a synchronized continuation from provided continuation. + /// + /// The provided MUTual EXclusion object is used to synchronize + /// continuation state. + /// + /// - Parameters: + /// - value: The continuation value to store. After passing the continuation + /// with this method, don’t use it outside of this object. + /// - locker: The MUTual EXclusion object to use to synchronize continuation state. + /// + /// - Returns: The newly created synchronized continuation. + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + init(with value: Value?, synchronizedWith locker: Lock) + /// Store the provided continuation if no continuation was provided during initialization. + /// + /// Use this method to pass continuation if continuation can't be provided during initialization. + /// + /// - Parameters: + /// - continuation: The continuation value to store. + /// After passing the continuation with this method, + /// don’t use it outside of this object. + /// + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + func add(continuation: Value) +} + +extension SynchronizedContinuable +where Self: Sendable, Value: ThrowingContinuable { + /// Suspends the current task, then calls the given operation with a `SynchronizedContinuable` + /// for the current task with a cancellation handler that’s immediately invoked if the current task is canceled. + /// + /// This differs from the operation cooperatively checking for cancellation and reacting to it in that + /// the cancellation handler is always and immediately invoked after resuming continuation with + /// `CancellationError` when the task is canceled. For example, even if the operation + /// is running code that never checks for cancellation and provided continuation to operation hasn't been resumed, + /// a cancellation handler still runs cancelling the continuation and provides a chance to run some cleanup code. + /// + /// - Parameters: + /// - locker: The platform lock to use to synchronize continuation state. + /// New lock object is created in case none provided. + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - handler: A handler immediately invoked if task is cancelled. + /// - operation: A closure that takes an `SynchronizedContinuable` parameter. + /// You must resume the continuation at least once. + /// + /// - Returns: The value passed to the continuation by the operation. + /// - Throws: If cancelled or `resume(throwing:)` is called on the continuation, + /// this function throws that error. + @usableFromInline + static func withCancellation( + synchronizedWith locker: Lock = .init(), + function: String = #function, + handler: @escaping @Sendable () -> Void, + operation: @escaping (Self) -> Void + ) async rethrows -> Success { + let cancellable = Self.init(with: nil, synchronizedWith: locker) + return try await withTaskCancellationHandler { + return try await Value.with( + function: function + ) { continuation in + cancellable.add(continuation: continuation) + operation(cancellable) + } + } onCancel: { + cancellable.cancel() + handler() + } + } + + /// Suspends the current task, then calls the given closure with a `SynchronizedContinuable` for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `SynchronizedContinuable` allows accessing and resuming the same continuations in concurrent code + /// by keeping track of underlying continuation value state. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes a `SynchronizedContinuable` parameter. + /// You must resume the continuation at least once. + /// + /// - Returns: The value passed to the continuation by the closure. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + @usableFromInline + static func with( + function: String = #function, + _ body: (Self) -> Void + ) async rethrows -> Success { + return try await Value.with(function: function) { continuation in + body(Self(with: continuation, synchronizedWith: .init())) + } + } +} + +extension SynchronizedContinuable +where Self: Sendable, Value: NonThrowingContinuable { + /// Suspends the current task, then calls the given operation with a `SynchronizedContinuable` + /// for the current task with a cancellation handler that’s immediately invoked if the current task is canceled. + /// + /// This differs from the operation cooperatively checking for cancellation and reacting to it in that + /// the cancellation handler is always and immediately invoked after resuming continuation with + /// `CancellationError` when the task is canceled. For example, even if the operation + /// is running code that never checks for cancellation and provided continuation to operation hasn't been resumed, + /// a cancellation handler still runs cancelling the continuation and provides a chance to run some cleanup code. + /// + /// - Parameters: + /// - locker: The platform lock to use to synchronize continuation state. + /// New lock object is created in case none provided. + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - handler: A handler immediately invoked if task is cancelled. + /// - operation: A closure that takes an `SynchronizedContinuable` parameter. + /// You must resume the continuation at least once. + /// + /// - Returns: The value passed to the continuation by the operation. + /// - Throws: If cancelled or `resume(throwing:)` is called on the continuation, + /// this function throws that error. + @usableFromInline + internal static func withCancellation( + synchronizedWith locker: Lock = .init(), + function: String = #function, + handler: @escaping @Sendable () -> Void, + operation: @escaping (Self) -> Void + ) async -> Success { + let cancellable = Self.init(with: nil, synchronizedWith: locker) + return await withTaskCancellationHandler { + return await Value.with( + function: function + ) { continuation in + cancellable.add(continuation: continuation) + operation(cancellable) + } + } onCancel: { + cancellable.cancel() + handler() + } + } + + /// Suspends the current task, then calls the given closure with a `SynchronizedContinuable` for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `SynchronizedContinuable` allows accessing and resuming the same continuations in concurrent code + /// by keeping track of underlying continuation value state. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes a `SynchronizedContinuable` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + @usableFromInline + internal static func with( + function: String = #function, + _ body: (Self) -> Void + ) async -> Success { + return await Value.with(function: function) { continuation in + body(Self(with: continuation, synchronizedWith: .init())) + } + } +} diff --git a/Sources/AsyncObjects/Task.swift b/Sources/AsyncObjects/Extensions/Task.swift similarity index 52% rename from Sources/AsyncObjects/Task.swift rename to Sources/AsyncObjects/Extensions/Task.swift index 034a68e1..2a030f56 100644 --- a/Sources/AsyncObjects/Task.swift +++ b/Sources/AsyncObjects/Extensions/Task.swift @@ -1,53 +1,3 @@ -public extension TaskGroup { - /// Adds a child task to the group and starts the task. - /// - /// This method adds child task to the group and returns only after the child task is started. - /// - /// - Parameters: - /// - priority: The priority of the operation task. Omit this parameter or - /// pass `nil` to set the child task’s priority to the priority of the group. - /// - operation: The operation to execute as part of the task group. - @inlinable - mutating func addTaskAndStart( - priority: TaskPriority? = nil, - operation: @escaping @Sendable () async -> ChildTaskResult - ) async { - typealias C = UnsafeContinuation - await withUnsafeContinuation { (continuation: C) in - self.addTask { - continuation.resume() - return await operation() - } - } - } -} - -public extension ThrowingTaskGroup { - /// Adds a child task to the group and starts the task. - /// - /// This method adds child task to the group and returns only after the child task is started. - /// This method doesn’t throw an error, even if the child task does. Instead, - /// the corresponding call to `ThrowingTaskGroup.next()` rethrows that error. - /// - /// - Parameters: - /// - priority: The priority of the operation task. Omit this parameter or - /// pass `nil` to set the child task’s priority to the priority of the group. - /// - operation: The operation to execute as part of the task group. - @inlinable - mutating func addTaskAndStart( - priority: TaskPriority? = nil, - operation: @escaping @Sendable () async throws -> ChildTaskResult - ) async { - typealias C = UnsafeContinuation - await withUnsafeContinuation { (continuation: C) in - self.addTask { - continuation.resume() - return try await operation() - } - } - } -} - public extension Task { /// Runs the given operation asynchronously as part of /// a new top-level cancellable task on behalf of the current actor. diff --git a/Sources/AsyncObjects/Extensions/TaskGroup.swift b/Sources/AsyncObjects/Extensions/TaskGroup.swift new file mode 100644 index 00000000..26017beb --- /dev/null +++ b/Sources/AsyncObjects/Extensions/TaskGroup.swift @@ -0,0 +1,49 @@ +public extension TaskGroup { + /// Adds a child task to the group and starts the task. + /// + /// This method adds child task to the group and returns only after the child task is started. + /// + /// - Parameters: + /// - priority: The priority of the operation task. Omit this parameter or + /// pass `nil` to set the child task’s priority to the priority of the group. + /// - operation: The operation to execute as part of the task group. + @inlinable + mutating func addTaskAndStart( + priority: TaskPriority? = nil, + operation: @escaping @Sendable () async -> ChildTaskResult + ) async { + typealias C = UnsafeContinuation + await withUnsafeContinuation { (continuation: C) in + self.addTask { + continuation.resume() + return await operation() + } + } + } +} + +public extension ThrowingTaskGroup { + /// Adds a child task to the group and starts the task. + /// + /// This method adds child task to the group and returns only after the child task is started. + /// This method doesn’t throw an error, even if the child task does. Instead, + /// the corresponding call to `ThrowingTaskGroup.next()` rethrows that error. + /// + /// - Parameters: + /// - priority: The priority of the operation task. Omit this parameter or + /// pass `nil` to set the child task’s priority to the priority of the group. + /// - operation: The operation to execute as part of the task group. + @inlinable + mutating func addTaskAndStart( + priority: TaskPriority? = nil, + operation: @escaping @Sendable () async throws -> ChildTaskResult + ) async { + typealias C = UnsafeContinuation + await withUnsafeContinuation { (continuation: C) in + self.addTask { + continuation.resume() + return try await operation() + } + } + } +} diff --git a/Sources/AsyncObjects/Future.swift b/Sources/AsyncObjects/Future.swift index a80b08b8..f8bb626c 100644 --- a/Sources/AsyncObjects/Future.swift +++ b/Sources/AsyncObjects/Future.swift @@ -14,6 +14,31 @@ import Foundation /// by using ``fulfill(with:)`` method. In the success case, /// the future’s downstream subscriber receives the element prior to the publishing stream finishing normally. /// If the result is an error, publishing terminates with that error. +/// +/// ```swift +/// // create a new unfulfilled future that is cancellable +/// let future = Future() +/// // or create a new unfulfilled future +/// // that is assured to be fulfilled +/// let future = Future() +/// // or create a future passing callback +/// // that fulfills the future +/// let future = Future { promise in +/// DispatchQueue.global(qos: .background) +/// .asyncAfter(deadline: .now() + 2) { +/// promise(.success(5)) +/// } +/// } +/// +/// // wait for future to be fulfilled with some value +/// // or cancelled with some error +/// let value = try await future.value +/// +/// // fulfill future with some value +/// await future.fulfill(producing: 5) +/// // or cancel future with error +/// await future.fulfill(throwing: CancellationError()) +/// ``` public actor Future { /// A type that represents a closure to invoke in the future, when an element or error is available. /// @@ -24,14 +49,16 @@ public actor Future { public typealias FutureResult = Result /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The continuations stored with an associated key for all the suspended task /// that are waiting for future to be fulfilled. @usableFromInline - private(set) var continuations: [UUID: Continuation] = [:] + internal private(set) var continuations: [UUID: Continuation] = [:] /// The underlying `Result` that indicates either future fulfilled or rejected. /// /// If future isn't fulfilled or rejected, the value is `nil`. @@ -43,7 +70,7 @@ public actor Future { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID = .init() ) { @@ -56,7 +83,7 @@ public actor Future { /// any other variation of this methods. /// /// - Returns: The newly created future. - public init() { } + public init() {} /// Create an already fulfilled promise with the provided `Result`. /// @@ -67,20 +94,79 @@ public actor Future { self.result = result } + #if swift(>=5.7) /// Creates a future that invokes a promise closure when the publisher emits an element. /// /// - Parameters: + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - attemptToFulfill: A ``Future/Promise`` that the publisher invokes /// when the publisher emits an element or terminates with an error. /// /// - Returns: The newly created future. public init( + file: String = #fileID, + function: String = #function, + line: UInt = #line, attemptToFulfill: @Sendable @escaping ( - @escaping Future.Promise + @escaping Promise ) async -> Void - ) async { - Task { await attemptToFulfill(self.fulfill(with:)) } + ) { + self.init() + Task { + await attemptToFulfill { result in + Task { [weak self] in + await self?.fulfill( + with: result, + file: file, + function: function, + line: line + ) + } + } + } } + #else + /// Creates a future that invokes a promise closure when the publisher emits an element. + /// + /// - Parameters: + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// - attemptToFulfill: A ``Future/Promise`` that the publisher invokes + /// when the publisher emits an element or terminates with an error. + /// + /// - Returns: The newly created future. + public convenience init( + file: String = #fileID, + function: String = #function, + line: UInt = #line, + attemptToFulfill: @Sendable @escaping ( + @escaping Promise + ) async -> Void + ) { + self.init() + Task { + await attemptToFulfill { result in + Task { [weak self] in + await self?.fulfill( + with: result, + file: file, + function: function, + line: line + ) + } + } + } + } + #endif deinit { guard Failure.self is Error.Protocol else { return } @@ -93,9 +179,26 @@ public actor Future { /// A future must be fulfilled exactly once. If the future has already been fulfilled, /// then calling this method has no effect and returns immediately. /// - /// - Parameter value: The value to produce from the future. - public func fulfill(producing value: Output) { - self.fulfill(with: .success(value)) + /// - Parameters: + /// - value: The value to produce from the future. + /// - file: The file future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future fulfillment originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public func fulfill( + producing value: Output, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.fulfill( + with: .success(value), + file: file, + function: function, + line: line + ) } /// Terminate the future with the given error and propagate error to subscribers. @@ -103,9 +206,26 @@ public actor Future { /// A future must be fulfilled exactly once. If the future has already been fulfilled, /// then calling this method has no effect and returns immediately. /// - /// - Parameter error: The error to throw to the callers. - public func fulfill(throwing error: Failure) { - self.fulfill(with: .failure(error)) + /// - Parameters: + /// - error: The error to throw to the callers. + /// - file: The file future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future fulfillment originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public func fulfill( + throwing error: Failure, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.fulfill( + with: .failure(error), + file: file, + function: function, + line: line + ) } /// Fulfill the future by returning or throwing the given result value. @@ -113,10 +233,22 @@ public actor Future { /// A future must be fulfilled exactly once. If the future has already been fulfilled, /// then calling this method has no effect and returns immediately. /// - /// - Parameter result: The result. If it contains a `.success` value, - /// that value delivered asynchronously to callers; - /// otherwise, the awaiting caller receives the `.error` instead. - public func fulfill(with result: FutureResult) { + /// - Parameters: + /// - result: The result. If it contains a `.success` value, + /// that value delivered asynchronously to callers; + /// otherwise, the awaiting caller receives the `.error` instead. + /// - file: The file future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future fulfillment originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public func fulfill( + with result: FutureResult, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { guard self.result == nil else { return } self.result = result continuations.forEach { $0.value.resume(with: result) } @@ -128,15 +260,15 @@ public actor Future { extension Future where Failure == Never { /// Suspends the current task, then calls the given closure with a non-throwing continuation for the current task. /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. + /// Spins up a new continuation and requests to track it with key by invoking `addContinuation`. /// This operation doesn't check for cancellation. /// /// - Returns: The value continuation is resumed with. @inlinable - nonisolated func _withPromisedContinuation() async -> Output { + internal nonisolated func withPromisedContinuation() async -> Output { return await Continuation.with { continuation in Task { [weak self] in - await self?._addContinuation(continuation) + await self?.addContinuation(continuation) } } } @@ -146,11 +278,21 @@ extension Future where Failure == Never { /// This property exposes the fulfilled value for the `Future` asynchronously. /// Immediately returns if `Future` is fulfilled otherwise waits asynchronously /// for `Future` to be fulfilled. - public var value: Output { - get async { - if let result = result { return try! result.get() } - return await _withPromisedContinuation() - } + /// + /// - Parameters: + /// - file: The file value request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function value request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line value request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public func get( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async -> Output { + if let result = result { return try! result.get() } + return await withPromisedContinuation() } /// Combines into a single future, for all futures to be fulfilled. @@ -158,21 +300,40 @@ extension Future where Failure == Never { /// If the returned future fulfills, it is fulfilled with an aggregating array of the values from the fulfilled futures, /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func all( - _ futures: [Future] - ) async -> Future<[Output], Failure> { + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future<[Output], Failure> { typealias IndexedOutput = (index: Int, value: Output) guard !futures.isEmpty else { return .init(with: .success([])) } - return await .init { promise in + return .init { promise in await withTaskGroup(of: IndexedOutput.self) { group in var result: [IndexedOutput] = [] result.reserveCapacity(futures.count) for (index, future) in futures.enumerated() { - group.addTask { (index: index, value: await future.value) } + group.addTask { + return ( + index: index, + value: await future.get( + file: file, + function: function, + line: line + ) + ) + } } for await item in group { result.append(item) } promise( @@ -189,14 +350,24 @@ extension Future where Failure == Never { /// If the returned future fulfills, it is fulfilled with an aggregating array of the values from the fulfilled futures, /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func all( - _ futures: Future... - ) async -> Future<[Output], Failure> { - return await Self.all(futures) + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future<[Output], Failure> { + return Self.all(futures, file: file, function: function, line: line) } /// Combines into a single future, for all futures to have settled. @@ -205,22 +376,41 @@ extension Future where Failure == Never { /// with an array of `Result`s that each describe the outcome of each future /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func allSettled( - _ futures: [Future] - ) async -> Future<[FutureResult], Never> { + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future<[FutureResult], Never> { typealias IndexedOutput = (index: Int, value: FutureResult) guard !futures.isEmpty else { return .init(with: .success([])) } - return await .init { promise in + return .init { promise in await withTaskGroup(of: IndexedOutput.self) { group in var result: [IndexedOutput] = [] result.reserveCapacity(futures.count) for (index, future) in futures.enumerated() { group.addTask { - (index: index, value: .success(await future.value)) + return ( + index: index, + value: .success( + await future.get( + file: file, + function: function, + line: line + ) + ) + ) } } for await item in group { result.append(item) } @@ -239,14 +429,29 @@ extension Future where Failure == Never { /// with an array of `Result`s that each describe the outcome of each future /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func allSettled( - _ futures: Future... - ) async -> Future<[FutureResult], Never> { - return await Self.allSettled(futures) + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future<[FutureResult], Never> { + return Self.allSettled( + futures, + file: file, + function: function, + line: line + ) } /// Takes multiple futures and, returns a single future that fulfills with the value @@ -254,17 +459,33 @@ extension Future where Failure == Never { /// /// If the returned future fulfills, it is fulfilled with the value of the first future that fulfilled. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future combining provided futures, or a forever pending future /// if no future provided. public static func race( - _ futures: [Future] - ) async -> Future { - return await .init { promise in + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future { + return .init { promise in await withTaskGroup(of: Output.self) { group in futures.forEach { future in - group.addTask { await future.value } + group.addTask { + await future.get( + file: file, + function: function, + line: line + ) + } } if let first = await group.next() { promise(.success(first)) @@ -279,42 +500,72 @@ extension Future where Failure == Never { /// /// If the returned future fulfills, it is fulfilled with the value of the first future that fulfilled. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future combining provided futures, or a forever pending future /// if no future provided. public static func race( - _ futures: Future... - ) async -> Future { - return await Self.race(futures) + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future { + return Self.race(futures, file: file, function: function, line: line) } /// Takes multiple futures and, returns a single future that fulfills with the value as soon as one of the futures fulfills. /// /// If the returned future fulfills, it is fulfilled with the value of the first future that fulfilled. /// - /// - Parameter futures: The futures to wait for. + /// - Parameters: + /// - futures: The futures to wait for. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future waiting for first fulfilled future from provided futures, /// or a forever pending future if no future provided. public static func any( - _ futures: [Future] - ) async -> Future { - return await Self.race(futures) + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future { + return Self.race(futures, file: file, function: function, line: line) } /// Takes multiple futures and, returns a single future that fulfills with the value as soon as one of the futures fulfills. /// /// If the returned future fulfills, it is fulfilled with the value of the first future that fulfilled. /// - /// - Parameter futures: The futures to wait for. + /// - Parameters: + /// - futures: The futures to wait for. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future waiting for first fulfilled future from provided futures, /// or a forever pending future if no future provided. public static func any( - _ futures: Future... - ) async -> Future { - return await Self.any(futures) + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future { + return Self.any(futures, file: file, function: function, line: line) } } @@ -325,32 +576,33 @@ extension Future where Failure == Error { /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) } /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. + /// Continuation can be cancelled with error if current task is cancelled, by invoking `removeContinuation`. /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. + /// Spins up a new continuation and requests to track it with key by invoking `addContinuation`. + /// This operation cooperatively checks for cancellation and reacting to it by invoking `removeContinuation`. /// Continuation can be resumed with error and some cleanup code can be run here. /// /// - Returns: The value continuation is resumed with. /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - nonisolated func _withPromisedContinuation() async throws -> Output { + internal nonisolated func withPromisedContinuation() async throws -> Output + { let key = UUID() return try await Continuation.withCancellation( synchronizedWith: locker ) { Task { [weak self] in - await self?._removeContinuation(withKey: key) + await self?.removeContinuation(withKey: key) } } operation: { continuation in Task { [weak self] in - await self?._addContinuation(continuation, withKey: key) + await self?.addContinuation(continuation, withKey: key) } } } @@ -362,12 +614,22 @@ extension Future where Failure == Error { /// for `Future` to be fulfilled. If the Future terminates with an error, /// the awaiting caller receives the error instead. /// + /// - Parameters: + /// - file: The file value request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function value request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line value request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Throws: If future rejected with error or `CancellationError` if cancelled. - public var value: Output { - get async throws { - if let result = result { return try result.get() } - return try await _withPromisedContinuation() - } + public func get( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws -> Output { + if let result = result { return try result.get() } + return try await withPromisedContinuation() } /// Combines into a single future, for all futures to be fulfilled, or for any to be rejected. @@ -377,22 +639,39 @@ extension Future where Failure == Error { /// /// If it rejects, it is rejected with the error from the first future that was rejected. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func all( - _ futures: [Future] - ) async -> Future<[Output], Failure> { + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future<[Output], Failure> { typealias IndexedOutput = (index: Int, value: Output) guard !futures.isEmpty else { return .init(with: .success([])) } - return await .init { promise in + return .init { promise in await withThrowingTaskGroup(of: IndexedOutput.self) { group in var result: [IndexedOutput] = [] result.reserveCapacity(futures.count) for (index, future) in futures.enumerated() { group.addTask { - (index: index, value: try await future.value) + ( + index: index, + value: try await future.get( + file: file, + function: function, + line: line + ) + ) } } do { @@ -417,14 +696,24 @@ extension Future where Failure == Error { /// /// If it rejects, it is rejected with the error from the first future that was rejected. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func all( - _ futures: Future... - ) async -> Future<[Output], Failure> { - return await Self.all(futures) + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future<[Output], Failure> { + return Self.all(futures, file: file, function: function, line: line) } /// Combines into a single future, for all futures to have settled (each may fulfill or reject). @@ -433,23 +722,37 @@ extension Future where Failure == Error { /// with an array of `Result`s that each describe the outcome of each future /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func allSettled( - _ futures: [Future] - ) async -> Future<[FutureResult], Never> { + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future<[FutureResult], Never> { typealias IndexedOutput = (index: Int, value: FutureResult) guard !futures.isEmpty else { return .init(with: .success([])) } - return await .init { promise in + return .init { promise in await withTaskGroup(of: IndexedOutput.self) { group in var result: [IndexedOutput] = [] result.reserveCapacity(futures.count) for (index, future) in futures.enumerated() { group.addTask { do { - let value = try await future.value + let value = try await future.get( + file: file, + function: function, + line: line + ) return (index: index, value: .success(value)) } catch { return (index: index, value: .failure(error)) @@ -472,14 +775,29 @@ extension Future where Failure == Error { /// with an array of `Result`s that each describe the outcome of each future /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func allSettled( - _ futures: Future... - ) async -> Future<[FutureResult], Never> { - return await Self.allSettled(futures) + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future<[FutureResult], Never> { + return Self.allSettled( + futures, + file: file, + function: function, + line: line + ) } /// Takes multiple futures and, returns a single future that fulfills with the value @@ -489,17 +807,33 @@ extension Future where Failure == Error { /// /// If it rejects, it is rejected with the error from the first future that was rejected. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future combining provided futures, or a forever pending future /// if no future provided. public static func race( - _ futures: [Future] - ) async -> Future { - return await .init { promise in + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future { + return .init { promise in await withThrowingTaskGroup(of: Output.self) { group in futures.forEach { future in - group.addTask { try await future.value } + group.addTask { + try await future.get( + file: file, + function: function, + line: line + ) + } } do { if let first = try await group.next() { @@ -520,14 +854,24 @@ extension Future where Failure == Error { /// /// If it rejects, it is rejected with the error from the first future that was rejected. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future combining provided futures, or a forever pending future /// if no future provided. public static func race( - _ futures: Future... - ) async -> Future { - return await Self.race(futures) + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future { + return Self.race(futures, file: file, function: function, line: line) } /// Takes multiple futures and, returns a single future that fulfills with the value as soon as one of the futures fulfills. @@ -536,20 +880,34 @@ extension Future where Failure == Error { /// /// If all the provided futures are rejected, it rejects with `CancellationError`. /// - /// - Parameter futures: The futures to wait for. + /// - Parameters: + /// - futures: The futures to wait for. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future waiting for first fulfilled future from provided futures, /// or a future rejected with `CancellationError` if no future provided. public static func any( - _ futures: [Future] - ) async -> Future { + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future { guard !futures.isEmpty else { return .init(with: .cancelled) } - return await .init { promise in + return .init { promise in await withTaskGroup(of: FutureResult.self) { group in futures.forEach { future in group.addTask { do { - let value = try await future.value + let value = try await future.get( + file: file, + function: function, + line: line + ) return .success(value) } catch { return .failure(error) @@ -583,14 +941,24 @@ extension Future where Failure == Error { /// /// If all the provided futures are rejected, it rejects with `CancellationError`. /// - /// - Parameter futures: The futures to wait for. + /// - Parameters: + /// - futures: The futures to wait for. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future waiting for first fulfilled future from provided futures, /// or a future rejected with `CancellationError` if no future provided. public static func any( - _ futures: Future... - ) async -> Future { - return await Self.any(futures) + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) -> Future { + return Self.any(futures, file: file, function: function, line: line) } } diff --git a/Sources/AsyncObjects/Locks/Exclusible.swift b/Sources/AsyncObjects/Locks/Exclusible.swift new file mode 100644 index 00000000..f254db64 --- /dev/null +++ b/Sources/AsyncObjects/Locks/Exclusible.swift @@ -0,0 +1,23 @@ +/// A type that provides exclusive access to threads. +/// +/// The `perform(_:)` method executes a synchronous +/// piece of work exclusively for a single instance of this type. +@rethrows +@usableFromInline +internal protocol Exclusible { + /// Initializes a MUTual EXclusion object. + /// + /// - Returns: The newly created MUTual EXclusion object. + init() + /// Performs a critical piece of work synchronously after acquiring the MUTual + /// EXclusion object and releases MUTual EXclusion object when task completes. + /// + /// Use this to perform critical tasks or provide access to critical resource + /// that require exclusivity among other concurrent tasks. + /// + /// - Parameter critical: The critical task to perform. + /// - Returns: The result from the critical task. + /// - Throws: Error occurred running critical task. + @discardableResult + func perform(_ critical: () throws -> R) rethrows -> R +} diff --git a/Sources/AsyncObjects/Locker.swift b/Sources/AsyncObjects/Locks/Locker.swift similarity index 68% rename from Sources/AsyncObjects/Locker.swift rename to Sources/AsyncObjects/Locks/Locker.swift index 91ad8ab9..3267f33f 100644 --- a/Sources/AsyncObjects/Locker.swift +++ b/Sources/AsyncObjects/Locks/Locker.swift @@ -14,29 +14,46 @@ import Foundation /// This lock must be unlocked from the same thread that locked it, /// attempts to unlock from a different thread will cause an assertion aborting the process. /// This lock must not be accessed from multiple processes or threads via shared or multiply-mapped memory, -/// the lock implementation relies on the address of the lock value and owning process. -public final class Locker: Equatable, Hashable, NSCopying, Sendable { +/// as the lock implementation relies on the address of the lock value and owning process. +/// +/// ```swift +/// // create a lock object to provide +/// // exclusive access to critical resources +/// let lock = Locker() +/// // perform critical mutually exclusive action +/// lock.perform { /* some action */ } +/// +/// // inside the critical action provided +/// // lock.perform can be called safely +/// lock.perform { +/// lock.perform { // works and runs with current context +/// /* some action */ +/// } +/// } +/// ``` +public final class Locker: Exclusible, Equatable, Hashable, NSCopying, Sendable +{ #if canImport(Darwin) /// A type representing data for an unfair lock. - typealias Primitive = os_unfair_lock + internal typealias Primitive = os_unfair_lock #elseif canImport(Glibc) /// A type representing a MUTual EXclusion object. - typealias Primitive = pthread_mutex_t + internal typealias Primitive = pthread_mutex_t #elseif canImport(WinSDK) /// A type representing a slim reader/writer (SRW) lock. - typealias Primitive = SRWLOCK + internal typealias Primitive = SRWLOCK #endif /// Pointer type pointing to platform dependent lock primitive. - typealias PlatformLock = UnsafeMutablePointer + internal typealias PlatformLock = UnsafeMutablePointer /// Pointer to platform dependent lock primitive. - let platformLock: PlatformLock + internal let platformLock: PlatformLock /// Creates lock object with the provided pointer to platform dependent lock primitive. /// /// - Parameter platformLock: Pointer to platform dependent lock primitive. /// - Returns: The newly created lock object. - init(withLock platformLock: PlatformLock) { + internal init(withLock platformLock: PlatformLock) { self.platformLock = platformLock } @@ -62,55 +79,6 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { platformLock.deinitialize(count: 1) } - #if swift(>=5.7) - /// Acquires exclusive lock. - /// - /// If a thread has already acquired lock and hasn't released lock yet, - /// other threads will wait for lock to be released and then acquire lock - /// in order of their request. - /// - /// - Warning: This method doesn't check if current thread - /// has already acquired lock, and will cause runtime error - /// if called repeatedly from same thread without releasing - /// with ``unlock()`` beforehand. Use the ``perform(_:)`` - /// method for safer handling of locking and unlocking. - @available(*, noasync, message: "use perform(_:) instead") - public func lock() { - #if canImport(Darwin) - os_unfair_lock_lock(platformLock) - #elseif canImport(Glibc) - pthread_mutex_lock(platformLock) - #elseif canImport(WinSDK) - AcquireSRWLockExclusive(platformLock) - #endif - // Track if thread is locked - let threadDictionary = Thread.current.threadDictionary - threadDictionary.setObject(true, forKey: self) - } - - /// Releases exclusive lock. - /// - /// A lock must be unlocked only from the same thread in which it was locked. - /// Attempting to unlock from a different thread causes a runtime error. - /// - /// - Warning: This method doesn't check if current thread - /// has already acquired lock, and will cause runtime error - /// if called from a thread calling ``lock()`` beforehand. - /// Use the ``perform(_:)`` method for safer handling - /// of locking and unlocking. - @available(*, noasync, message: "use perform(_:) instead") - public func unlock() { - let threadDictionary = Thread.current.threadDictionary - threadDictionary.removeObject(forKey: self) - #if canImport(Darwin) - os_unfair_lock_unlock(platformLock) - #elseif canImport(Glibc) - pthread_mutex_unlock(platformLock) - #elseif canImport(WinSDK) - ReleaseSRWLockExclusive(platformLock) - #endif - } - #else /// Acquires exclusive lock. /// /// If a thread has already acquired lock and hasn't released lock yet, @@ -120,9 +88,9 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// - Warning: This method doesn't check if current thread /// has already acquired lock, and will cause runtime error /// if called repeatedly from same thread without releasing - /// with ``unlock()`` beforehand. Use the ``perform(_:)`` + /// with `unlock()` beforehand. Use the ``perform(_:)`` /// method for safer handling of locking and unlocking. - public func lock() { + private func lock() { #if canImport(Darwin) os_unfair_lock_lock(platformLock) #elseif canImport(Glibc) @@ -142,10 +110,10 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// /// - Warning: This method doesn't check if current thread /// has already acquired lock, and will cause runtime error - /// if called from a thread calling ``lock()`` beforehand. + /// if called from a thread calling `lock()` beforehand. /// Use the ``perform(_:)`` method for safer handling /// of locking and unlocking. - public func unlock() { + private func unlock() { let threadDictionary = Thread.current.threadDictionary threadDictionary.removeObject(forKey: self) #if canImport(Darwin) @@ -156,7 +124,6 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { ReleaseSRWLockExclusive(platformLock) #endif } - #endif /// Performs a critical piece of work synchronously after acquiring the lock /// and releases lock when task completes. @@ -166,7 +133,7 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// /// This method checks if thread has already acquired lock and performs task /// without releasing the lock. This allows safer lock management eliminating - /// potential runtime errors, than manually invoking ``lock()`` and ``unlock()``. + /// potential runtime errors. /// /// - Parameter critical: The critical task to perform. /// - Returns: The result from the critical task. diff --git a/Sources/AsyncObjects/TaskOperation.swift b/Sources/AsyncObjects/TaskOperation.swift index 5d7769e2..46acb6c2 100644 --- a/Sources/AsyncObjects/TaskOperation.swift +++ b/Sources/AsyncObjects/TaskOperation.swift @@ -1,8 +1,4 @@ -#if swift(>=5.7) import Foundation -#else -@preconcurrency import Foundation -#endif import Dispatch /// An object that bridges asynchronous work under structured concurrency @@ -14,9 +10,26 @@ import Dispatch /// You can start the operation by adding it to an `OperationQueue`, /// or by manually calling the ``signal()`` or ``start()`` method. /// Wait for operation completion asynchronously by calling ``wait()`` method -/// or its timeout variation ``wait(forNanoseconds:)``. +/// or its timeout variation ``wait(forNanoseconds:)``: +/// +/// ```swift +/// // create operation with async action +/// let operation = TaskOperation { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// // start operation to execute action +/// operation.start() // operation.signal() +/// +/// // wait for operation completion asynchronously, +/// // fails only if task cancelled +/// try await operation.wait() +/// // or wait with some timeout +/// try await operation.wait(forNanoseconds: 1_000_000_000) +/// // or wait synchronously for completion +/// operation.waitUntilFinished() +/// ``` public final class TaskOperation: Operation, AsyncObject, - @unchecked Sendable + ContinuableCollection, @unchecked Sendable { /// The asynchronous action to perform as part of the operation.. private let underlyingAction: @Sendable () async throws -> R @@ -26,7 +39,7 @@ public final class TaskOperation: Operation, AsyncObject, /// The platform dependent lock used to /// synchronize data access and modifications. @usableFromInline - let locker: Locker + internal let locker: Locker /// A type representing a set of behaviors for the executed /// task type and task completion behavior. @@ -49,16 +62,32 @@ public final class TaskOperation: Operation, AsyncObject, /// /// Always returns true, since the operation always executes its task asynchronously. public override var isAsynchronous: Bool { true } + + /// Private store for boolean value indicating whether the operation is currently cancelled. + @usableFromInline + internal var _isCancelled: Bool = false /// A Boolean value indicating whether the operation has been cancelled. /// /// Returns whether the underlying top-level task is cancelled or not. /// The default value of this property is `false`. /// Calling the ``cancel()`` method of this object sets the value of this property to `true`. - public override var isCancelled: Bool { execTask?.isCancelled ?? false } + public override internal(set) var isCancelled: Bool { + get { locker.perform { execTask?.isCancelled ?? _isCancelled } } + @usableFromInline + set { + willChangeValue(forKey: "isCancelled") + locker.perform { + _isCancelled = newValue + guard newValue else { return } + execTask?.cancel() + } + didChangeValue(forKey: "isCancelled") + } + } /// Private store for boolean value indicating whether the operation is currently executing. @usableFromInline - var _isExecuting: Bool = false + internal var _isExecuting: Bool = false /// A Boolean value indicating whether the operation is currently executing. /// /// The value of this property is true if the operation is currently executing @@ -75,7 +104,7 @@ public final class TaskOperation: Operation, AsyncObject, /// Private store for boolean value indicating whether the operation has finished executing its task. @usableFromInline - var _isFinished: Bool = false + internal var _isFinished: Bool = false /// A Boolean value indicating whether the operation has finished executing its task. /// /// The value of this property is true if the operation is finished executing or cancelled @@ -100,7 +129,12 @@ public final class TaskOperation: Operation, AsyncObject, /// Will be success if provided operation completed successfully, /// or failure returned with error. public var result: Result { - get async { (await execTask?.result) ?? .failure(EarlyInvokeError()) } + get async { + (await execTask?.result) + ?? (isCancelled + ? .failure(CancellationError()) + : .failure(EarlyInvokeError())) + } } /// Creates a new operation that executes the provided asynchronous task. @@ -157,7 +191,7 @@ public final class TaskOperation: Operation, AsyncObject, /// as part of a new top-level task on behalf of the current actor. public override func main() { guard isExecuting, execTask == nil else { return } - let final = { @Sendable[weak self] in self?._finish(); return } + let final = { @Sendable[weak self] in self?.finish(); return } execTask = flags.createTask( priority: priority, operation: underlyingAction, @@ -174,15 +208,15 @@ public final class TaskOperation: Operation, AsyncObject, /// Likewise, if the task has already run past the last point where it would stop early, /// calling this method has no effect. public override func cancel() { - execTask?.cancel() - _finish() + isCancelled = true + finish() } /// Moves this operation to finished state. /// /// Must be called either when operation completes or cancelled. @inlinable - func _finish() { + internal func finish() { isExecuting = false isFinished = true } @@ -190,10 +224,12 @@ public final class TaskOperation: Operation, AsyncObject, // MARK: AsyncObject /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The continuations stored with an associated key for all the suspended task that are waiting for operation completion. @usableFromInline - private(set) var continuations: [UUID: Continuation] = [:] + internal private(set) var continuations: [UUID: Continuation] = [:] /// Add continuation with the provided key in `continuations` map. /// @@ -201,7 +237,7 @@ public final class TaskOperation: Operation, AsyncObject, /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -217,34 +253,26 @@ public final class TaskOperation: Operation, AsyncObject, /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { locker.perform { continuations.removeValue(forKey: key) } } - /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. - /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. - /// Continuation can be resumed with error and some cleanup code can be run here. - /// - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - func _withPromisedContinuation() async throws { - let key = UUID() - try await Continuation.withCancellation(synchronizedWith: locker) { - Task { [weak self] in self?._removeContinuation(withKey: key) } - } operation: { continuation in - Task { [weak self] in - self?._addContinuation(continuation, withKey: key) - } - } - } - /// Starts operation asynchronously /// as part of a new top-level task on behalf of the current actor. + /// + /// - Parameters: + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @Sendable - public func signal() { + public func signal( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { self.start() } @@ -252,10 +280,24 @@ public final class TaskOperation: Operation, AsyncObject, /// /// Only waits asynchronously, if operation is executing, /// until it is completed or cancelled. + /// + /// - Parameters: + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { + public func wait( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { guard !isFinished else { return } - try? await _withPromisedContinuation() + try await withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/TaskQueue.swift b/Sources/AsyncObjects/TaskQueue.swift index afde3f11..4dece155 100644 --- a/Sources/AsyncObjects/TaskQueue.swift +++ b/Sources/AsyncObjects/TaskQueue.swift @@ -3,14 +3,43 @@ import Foundation #else @preconcurrency import Foundation #endif + import OrderedCollections /// An object that acts as a concurrent queue executing submitted tasks concurrently. /// -/// You can use the ``exec(priority:flags:operation:)-2ll3k`` +/// You can use the ``exec(priority:flags:operation:)-8u46i`` /// or its non-throwing/non-cancellable version to run tasks concurrently. /// Additionally, you can provide priority of task and ``Flags`` /// to customize execution of submitted operation. +/// +/// ```swift +/// // create a queue with some priority processing async actions +/// let queue = TaskQueue() +/// // add operations to queue to be executed asynchronously +/// queue.addTask { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// // or wait asynchronously for operation to be executed on queue +/// // the provided operation cancelled if invoking task cancelled +/// try await queue.exec { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// +/// // provide additional flags for added operations +/// // execute operation as a barrier +/// queue.addTask(flags: .barrier) { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// // execute operation as a detached task +/// queue.addTask(flags: .detached) { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// // combine multiple flags for operation execution +/// queue.addTask(flags: [.barrier, .detached]) { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// ``` public actor TaskQueue: AsyncObject { /// A set of behaviors for operations, such as its priority and whether to create a barrier /// or spawn a new detached task. @@ -55,7 +84,7 @@ public actor TaskQueue: AsyncObject { /// /// Returns `true` if either ``barrier`` or ``block`` flag provided. @usableFromInline - var isBlockEnabled: Bool { + internal var isBlockEnabled: Bool { return self.contains(.block) || self.contains(.barrier) } @@ -74,7 +103,7 @@ public actor TaskQueue: AsyncObject { /// - Returns: The determined priority of operation to be executed, /// based on provided flags. @usableFromInline - func choosePriority( + internal func choosePriority( fromContext context: TaskPriority?, andWork work: TaskPriority? ) -> TaskPriority? { @@ -105,7 +134,7 @@ public actor TaskQueue: AsyncObject { /// /// - Returns: Whether to suspend newly added task. @usableFromInline - func wait(forCurrent current: UInt) -> Bool { + internal func wait(forCurrent current: UInt) -> Bool { return self.contains(.barrier) ? current > 0 : false } @@ -132,14 +161,16 @@ public actor TaskQueue: AsyncObject { /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// A mechanism to queue tasks in ``TaskQueue``, to be resumed when queue is freed /// and provided flags are satisfied. @usableFromInline - typealias QueuedContinuation = (value: Continuation, flags: Flags) + internal typealias QueuedContinuation = (value: Continuation, flags: Flags) /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The list of tasks currently queued and would be resumed one by one when current barrier task ends. @usableFromInline private(set) var queue: OrderedDictionary = [:] @@ -159,7 +190,7 @@ public actor TaskQueue: AsyncObject { /// - Parameter flags: The flags provided for new task. /// - Returns: Whether to wait to be resumed later. @inlinable - func _wait(whenFlags flags: Flags) -> Bool { + internal func shouldWait(whenFlags flags: Flags) -> Bool { return blocked || !queue.isEmpty || flags.wait(forCurrent: currentRunning) @@ -171,7 +202,7 @@ public actor TaskQueue: AsyncObject { /// - Returns: Whether queue is free to proceed scheduling other tasks. @inlinable @discardableResult - func _resumeQueuedContinuation( + internal func resumeQueuedContinuation( _ continuation: QueuedContinuation ) -> Bool { currentRunning += 1 @@ -187,13 +218,13 @@ public actor TaskQueue: AsyncObject { /// - key: The key in the continuation queue. /// - continuation: The continuation and flags to add to queue. @inlinable - func _queueContinuation( + internal func queueContinuation( atKey key: UUID = .init(), _ continuation: QueuedContinuation ) { guard !continuation.value.resumed else { return } - guard _wait(whenFlags: continuation.flags) else { - _resumeQueuedContinuation(continuation) + guard shouldWait(whenFlags: continuation.flags) else { + resumeQueuedContinuation(continuation) return } queue[key] = continuation @@ -203,7 +234,7 @@ public actor TaskQueue: AsyncObject { /// /// - Parameter key: The key in the continuation queue. @inlinable - func _dequeueContinuation(withKey key: UUID) { + internal func dequeueContinuation(withKey key: UUID) { queue.removeValue(forKey: key) } @@ -213,9 +244,9 @@ public actor TaskQueue: AsyncObject { /// Updates the ``blocked`` flag and starts queued tasks /// in order of their addition if any tasks are queued. @inlinable - func _unblockQueue() { + internal func unblockQueue() { blocked = false - _resumeQueuedTasks() + resumeQueuedTasks() } /// Signals completion of operation to the queue @@ -224,8 +255,8 @@ public actor TaskQueue: AsyncObject { /// Updates the ``currentRunning`` count and starts /// queued tasks in order of their addition if any queued. @inlinable - func _signalCompletion() { - defer { _resumeQueuedTasks() } + internal func signalCompletion() { + defer { resumeQueuedTasks() } guard currentRunning > 0 else { return } currentRunning -= 1 } @@ -233,38 +264,40 @@ public actor TaskQueue: AsyncObject { /// Resumes queued tasks when queue isn't blocked /// and operation flags preconditions satisfied. @inlinable - func _resumeQueuedTasks() { + internal func resumeQueuedTasks() { while let (_, continuation) = queue.elements.first, !blocked, !continuation.flags.wait(forCurrent: currentRunning) { queue.removeFirst() - guard _resumeQueuedContinuation(continuation) else { break } + guard resumeQueuedContinuation(continuation) else { break } } } /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_dequeueContinuation`. + /// Continuation can be cancelled with error if current task is cancelled, by invoking `dequeueContinuation`. /// - /// Spins up a new continuation and requests to track it on queue with key by invoking `_queueContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_dequeueContinuation`. + /// Spins up a new continuation and requests to track it on queue with key by invoking `queueContinuation`. + /// This operation cooperatively checks for cancellation and reacting to it by invoking `dequeueContinuation`. /// Continuation can be resumed with error and some cleanup code can be run here. /// /// - Parameter flags: The flags associated that determine the execution behavior of task. /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - nonisolated func _withPromisedContinuation(flags: Flags = []) async throws { + internal nonisolated func withPromisedContinuation( + flags: Flags = [] + ) async throws { let key = UUID() try await Continuation.withCancellation( synchronizedWith: locker ) { Task { [weak self] in - await self?._dequeueContinuation(withKey: key) + await self?.dequeueContinuation(withKey: key) } } operation: { continuation in Task { [weak self] in - await self?._queueContinuation( + await self?.queueContinuation( atKey: key, (value: continuation, flags: flags) ) @@ -282,12 +315,12 @@ public actor TaskQueue: AsyncObject { /// - Returns: The result from provided operation. /// - Throws: `CancellationError` if cancelled, or error from provided operation. @inlinable - func _run( + internal func run( with priority: TaskPriority?, flags: Flags, operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { - defer { _signalCompletion() } + defer { signalCompletion() } typealias LocalTask = Task let taskPriority = flags.choosePriority( fromContext: self.priority, @@ -316,14 +349,14 @@ public actor TaskQueue: AsyncObject { /// - Returns: The result from provided operation. /// - Throws: `CancellationError` if cancelled, or error from provided operation. @inlinable - func _runBlocking( + internal func runBlocking( with priority: TaskPriority?, flags: Flags, operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { - defer { _unblockQueue() } + defer { unblockQueue() } blocked = true - return try await _run( + return try await run( with: priority, flags: flags, operation: operation @@ -364,7 +397,7 @@ public actor TaskQueue: AsyncObject { /// - Throws: Error from provided operation or the cancellation handler. @discardableResult @inlinable - public func _execHelper( + internal func execHelper( priority: TaskPriority? = nil, flags: Flags = [], operation: @Sendable @escaping () async throws -> T, @@ -374,25 +407,25 @@ public actor TaskQueue: AsyncObject { _ operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { return flags.isBlockEnabled - ? try await _runBlocking( + ? try await runBlocking( with: priority, flags: flags, operation: operation ) - : try await _run( + : try await run( with: priority, flags: flags, operation: operation ) } - guard self._wait(whenFlags: flags) else { + guard self.shouldWait(whenFlags: flags) else { currentRunning += 1 return try await runTask(operation) } do { - try await _withPromisedContinuation(flags: flags) + try await withPromisedContinuation(flags: flags) } catch { try cancellation(error) } @@ -410,6 +443,12 @@ public actor TaskQueue: AsyncObject { /// from execution context(`Task.currentPriority`) for non-detached tasks. /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. + /// - file: The file execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function execution request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The throwing operation to perform. /// /// - Returns: The result from provided operation. @@ -418,13 +457,17 @@ public actor TaskQueue: AsyncObject { /// - Note: If task that added the operation to queue is cancelled, /// the provided operation also cancelled cooperatively if already started /// or the operation execution is skipped if only queued and not started. + @Sendable @discardableResult public func exec( priority: TaskPriority? = nil, flags: Flags = [], + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @Sendable @escaping () async throws -> T ) async throws -> T { - return try await _execHelper( + return try await execHelper( priority: priority, flags: flags, operation: operation @@ -441,16 +484,26 @@ public actor TaskQueue: AsyncObject { /// from execution context(`Task.currentPriority`) for non-detached tasks. /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. + /// - file: The file execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function execution request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The non-throwing operation to perform. /// /// - Returns: The result from provided operation. + @Sendable @discardableResult public func exec( priority: TaskPriority? = nil, flags: Flags = [], + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @Sendable @escaping () async -> T ) async -> T { - return await _execHelper( + return await execHelper( priority: priority, flags: flags, operation: operation @@ -470,16 +523,29 @@ public actor TaskQueue: AsyncObject { /// from execution context(`Task.currentPriority`) for non-detached tasks. /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. + /// - file: The file execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function execution request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The throwing operation to perform. + @Sendable public nonisolated func addTask( priority: TaskPriority? = nil, flags: Flags = [], + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @Sendable @escaping () async throws -> T ) { Task { try await exec( priority: priority, flags: flags, + file: file, + function: function, + line: line, operation: operation ) } @@ -496,16 +562,29 @@ public actor TaskQueue: AsyncObject { /// from execution context(`Task.currentPriority`) for non-detached tasks. /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. + /// - file: The file execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function execution request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The non-throwing operation to perform. + @Sendable public nonisolated func addTask( priority: TaskPriority? = nil, flags: Flags = [], + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @Sendable @escaping () async -> T ) { Task { await exec( priority: priority, flags: flags, + file: file, + function: function, + line: line, operation: operation ) } @@ -513,16 +592,43 @@ public actor TaskQueue: AsyncObject { /// Signalling on queue does nothing. /// Only added to satisfy ``AsyncObject`` requirements. - public func signal() { - // Do nothing - } + /// + /// - Parameters: + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + @Sendable + public nonisolated func signal( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { /* Do nothing */ } /// Waits for execution turn on queue. /// /// Only waits asynchronously, if queue is locked by a barrier task, /// until the suspended task's turn comes to be resumed. + /// + /// - Parameters: + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { - await exec { /*Do nothing*/ } + public func wait( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { + try await exec( + file: file, function: function, line: line + ) { try await Task.sleep(nanoseconds: 0) } } } diff --git a/Sources/AsyncObjects/TaskTracker.swift b/Sources/AsyncObjects/TaskTracker.swift index db1da39e..c5e7f724 100644 --- a/Sources/AsyncObjects/TaskTracker.swift +++ b/Sources/AsyncObjects/TaskTracker.swift @@ -9,7 +9,7 @@ /// Do not keep strong reference of this object, as then object won't /// deallocate as soon asynchronous operations and their created /// unstructured tasks complete. -final class TaskTracker: Sendable { +internal final class TaskTracker: Sendable { /// The tracker associated with current task. /// /// Use the `withValue` method to assign a tracker to asynchronous operation. @@ -21,7 +21,7 @@ final class TaskTracker: Sendable { /// deallocate as soon asynchronous operations and their created /// unstructured tasks complete. @TaskLocal - static var current: TaskTracker? + internal static var current: TaskTracker? /// The action to complete when task and all its created unstructured tasks complete. private let fire: @Sendable () -> Void @@ -39,7 +39,7 @@ final class TaskTracker: Sendable { /// Do not keep strong reference of this object, as then object won't /// deallocate as soon asynchronous operations and their created /// unstructured tasks complete. - init(onComplete fire: @Sendable @escaping () -> Void) { + internal init(onComplete fire: @Sendable @escaping () -> Void) { self.fire = fire } diff --git a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift index fc0bc632..ef48e8e0 100644 --- a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift +++ b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift @@ -6,16 +6,15 @@ class AsyncCountdownEventTests: XCTestCase { func testCountdownWaitWithoutIncrement() async throws { let event = AsyncCountdownEvent() - await Self.checkExecInterval(durationInSeconds: 0) { - await event.wait() + try await Self.checkExecInterval(durationInSeconds: 0) { + try await event.wait() } } func testCountdownWaitZeroTimeoutWithoutIncrement() async throws { let event = AsyncCountdownEvent() - await Self.checkExecInterval(durationInSeconds: 0) { - let result = await event.wait(forSeconds: 0) - XCTAssertEqual(result, .success) + try await Self.checkExecInterval(durationInSeconds: 0) { + try await event.wait(forSeconds: 0) } } @@ -27,9 +26,8 @@ class AsyncCountdownEventTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in for i in 0..(with: .success(5)) - let value = await future.value + let value = await future.get() XCTAssertEqual(value, 5) } @@ -15,7 +15,7 @@ class NonThrowingFutureTests: XCTestCase { let future = Future() await withTaskGroup(of: Void.self) { group in group.addTask { - let value = await future.value + let value = await future.get() XCTAssertEqual(value, 5) } group.addTask { @@ -27,13 +27,13 @@ class NonThrowingFutureTests: XCTestCase { } func testFutureFulfilledWithAttemptClosure() async throws { - let future = await Future { promise in + let future = Future { promise in DispatchQueue.global(qos: .background) .asyncAfter(deadline: .now() + 2) { promise(.success(5)) } } - let value = await future.value + let value = await future.get() XCTAssertEqual(value, 5) } @@ -41,15 +41,15 @@ class NonThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.all(future1, future3, future2) + let allFuture = Future.all(future1, future3, future2) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { - let value = await allFuture.value + let value = await allFuture.get() XCTAssertEqual(value, [1, 3, 2]) } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -71,11 +71,11 @@ class NonThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.allSettled(future1, future2, future3) + let allFuture = Future.allSettled(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { - let values = await allFuture.value + let values = await allFuture.get() for (index, item) in values.enumerated() { switch item { case .success(let value): @@ -86,7 +86,7 @@ class NonThrowingFutureTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -108,17 +108,17 @@ class NonThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.race(future1, future2, future3) + let allFuture = Future.race(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 1) { - let value = await allFuture.value + let value = await allFuture.get() XCTAssertEqual(value, 1) } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -140,17 +140,17 @@ class NonThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.any(future1, future2, future3) + let allFuture = Future.any(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 1) { - let value = await allFuture.value + let value = await allFuture.get() XCTAssertEqual(value, 1) } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -169,27 +169,27 @@ class NonThrowingFutureTests: XCTestCase { } func testConstructingAllFutureFromZeroFutures() async { - let future = await Future.all() - let value = await future.value + let future = Future.all() + let value = await future.get() XCTAssertTrue(value.isEmpty) } func testConstructingAllSettledFutureFromZeroFutures() async { - let future = await Future.allSettled() - let value = await future.value + let future = Future.allSettled() + let value = await future.get() XCTAssertTrue(value.isEmpty) } func testMultipleTimesFutureFulfilled() async throws { let future = Future(with: .success(5)) await future.fulfill(producing: 10) - let value = await future.value + let value = await future.get() XCTAssertEqual(value, 5) } func testFutureAsyncInitializerDuration() async throws { await Self.checkExecInterval(durationInSeconds: 0) { - let _ = await Future { promise in + let _ = Future { promise in try! await Self.sleep(seconds: 1) promise(.success(5)) } @@ -202,8 +202,9 @@ class NonThrowingFutureTests: XCTestCase { try await Self.sleep(seconds: 1) await future.fulfill(producing: 5) } - let _ = await future.value + let _ = await future.get() self.addTeardownBlock { [weak future] in + try await Self.sleep(seconds: 1) XCTAssertNil(future) } } @@ -215,7 +216,7 @@ class NonThrowingFutureTests: XCTestCase { let future = Future() await Self.checkExecInterval(durationInSeconds: 0) { await withTaskGroup(of: Void.self) { group in - group.addTask { let _ = await future.value } + group.addTask { let _ = await future.get() } group.addTask { await future.fulfill(producing: i) } await group.waitForAll() } diff --git a/Tests/AsyncObjectsTests/SafeContinuationTests.swift b/Tests/AsyncObjectsTests/SafeContinuationTests.swift index e08b622c..37b390c4 100644 --- a/Tests/AsyncObjectsTests/SafeContinuationTests.swift +++ b/Tests/AsyncObjectsTests/SafeContinuationTests.swift @@ -150,10 +150,9 @@ class SafeContinuationTests: XCTestCase { } catch {} XCTAssertTrue(Task.isCancelled) do { - try await SafeContinuation - .withCancellation { - } operation: { _ in - } + try await SafeContinuation.withCancellation { + } operation: { _ in + } XCTFail("Unexpected task progression") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -163,4 +162,26 @@ class SafeContinuationTests: XCTestCase { task.cancel() await task.value } + + func testNonCancellableContinuation() async throws { + typealias C = GlobalContinuation + let task = Task.detached { + await Self.checkExecInterval(durationInSeconds: 1) { + do { + try await Self.sleep(seconds: 5) + XCTFail("Unexpected task progression") + } catch {} + XCTAssertTrue(Task.isCancelled) + await SafeContinuation.withCancellation { + } operation: { continuation in + Task { + defer { continuation.resume() } + try await Self.sleep(seconds: 1) + } + } + } + } + task.cancel() + await task.value + } } diff --git a/Tests/AsyncObjectsTests/StandardLibraryTests.swift b/Tests/AsyncObjectsTests/StandardLibraryTests.swift index b432082c..159cbfea 100644 --- a/Tests/AsyncObjectsTests/StandardLibraryTests.swift +++ b/Tests/AsyncObjectsTests/StandardLibraryTests.swift @@ -1,4 +1,5 @@ import XCTest +@testable import AsyncObjects /// Tests inner workings of structured concurrency class StandardLibraryTests: XCTestCase { @@ -47,7 +48,7 @@ class StandardLibraryTests: XCTestCase { } @TaskLocal - nonisolated static var traceID: Int = 0 + static var traceID: Int = 0 func testTaskLocalVariable() async { func call(_ value: Int) { XCTAssertEqual(Self.traceID, value) @@ -121,7 +122,7 @@ class StandardLibraryTests: XCTestCase { } @TaskLocal - nonisolated static var localRef: TaskLocalClass! + static var localRef: TaskLocalClass! func testTaskLocalVariableWithReferenceType() { @Sendable func call(label: String, fromFunction function: String = #function) { diff --git a/Tests/AsyncObjectsTests/TaskOperationTests.swift b/Tests/AsyncObjectsTests/TaskOperationTests.swift index 38e062a9..097a94c7 100644 --- a/Tests/AsyncObjectsTests/TaskOperationTests.swift +++ b/Tests/AsyncObjectsTests/TaskOperationTests.swift @@ -133,10 +133,9 @@ class TaskOperationTests: XCTestCase { (try? await Self.sleep(seconds: 3)) != nil } operation.signal() - await Self.checkExecInterval( - durationInRange: ...3, - for: operation.wait - ) + try await Self.checkExecInterval( + durationInRange: ...3 + ) { try await operation.wait() } } func testTaskOperationAsyncWaitTimeout() async throws { @@ -145,15 +144,20 @@ class TaskOperationTests: XCTestCase { } operation.signal() await Self.checkExecInterval(durationInSeconds: 1) { - await operation.wait(forSeconds: 1) + do { + try await operation.wait(forSeconds: 1) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } } func testTaskOperationAsyncWaitWithZeroTimeout() async throws { let operation = TaskOperation { /* Do nothing */ } operation.signal() - await Self.checkExecInterval(durationInSeconds: 0) { - await operation.wait(forNanoseconds: 0) + try await Self.checkExecInterval(durationInSeconds: 0) { + try await operation.wait(forNanoseconds: 0) } } @@ -168,6 +172,7 @@ class TaskOperationTests: XCTestCase { } operation.signal() self.addTeardownBlock { [weak operation] in + try await Self.sleep(seconds: 1) XCTAssertNil(operation) } } @@ -175,8 +180,9 @@ class TaskOperationTests: XCTestCase { func testDeinitWithoutCancellation() async throws { let operation = TaskOperation { try await Self.sleep(seconds: 1) } operation.signal() - await operation.wait() + try await operation.wait() self.addTeardownBlock { [weak operation] in + try await Self.sleep(seconds: 1) XCTAssertNil(operation) } } @@ -203,16 +209,16 @@ class TaskOperationTests: XCTestCase { func testOperationWithoutTrackingChildTasks() async throws { let operation = createOperationWithChildTasks(track: false) operation.signal() - await Self.checkExecInterval(durationInSeconds: 0) { - await operation.wait() + try await Self.checkExecInterval(durationInSeconds: 0) { + try await operation.wait() } } func testOperationWithTrackingChildTasks() async throws { let operation = createOperationWithChildTasks(track: true) operation.signal() - await Self.checkExecInterval(durationInSeconds: 3) { - await operation.wait() + try await Self.checkExecInterval(durationInSeconds: 3) { + try await operation.wait() } } @@ -230,47 +236,72 @@ class TaskOperationTests: XCTestCase { } } + func testNotStartedCancellationError() async throws { + let operation = TaskOperation { try await Self.sleep(seconds: 1) } + operation.cancel() + let result = await operation.result + switch result { + case .success: XCTFail("Unexpected operation result") + case .failure(let error): + XCTAssertTrue(type(of: error) == CancellationError.self) + print( + "[\(#function)] [\(type(of: error))] \(error.localizedDescription)" + ) + XCTAssertFalse(error.localizedDescription.isEmpty) + } + } + func testWaitCancellationWhenTaskCancelled() async throws { let operation = TaskOperation { try await Self.sleep(seconds: 10) } let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { - await operation.wait() + try await Self.checkExecInterval(durationInSeconds: 0) { + try await operation.wait() } } task.cancel() - await task.value + do { + try await task.value + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } } func testWaitCancellationForAlreadyCancelledTask() async throws { let operation = TaskOperation { try await Self.sleep(seconds: 10) } let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { + try await Self.checkExecInterval(durationInSeconds: 0) { do { try await Self.sleep(seconds: 5) XCTFail("Unexpected task progression") } catch {} XCTAssertTrue(Task.isCancelled) - await operation.wait() + try await operation.wait() } } task.cancel() - await task.value + do { + try await task.value + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } } - func testConcurrentAccess() async { - await withTaskGroup(of: Void.self) { group in + func testConcurrentAccess() async throws { + try await withThrowingTaskGroup(of: Void.self) { group in for _ in 0..<10 { group.addTask { let operation = TaskOperation {} - await Self.checkExecInterval(durationInSeconds: 0) { - await withTaskGroup(of: Void.self) { group in - group.addTask { await operation.wait() } - group.addTask { operation.signal() } - await group.waitForAll() + try await Self.checkExecInterval(durationInSeconds: 0) { + try await withThrowingTaskGroup(of: Void.self) { g in + g.addTask { try await operation.wait() } + g.addTask { operation.signal() } + try await g.waitForAll() } } } - await group.waitForAll() + try await group.waitForAll() } } } diff --git a/Tests/AsyncObjectsTests/TaskQueueTests.swift b/Tests/AsyncObjectsTests/TaskQueueTests.swift index db0f501d..84decc97 100644 --- a/Tests/AsyncObjectsTests/TaskQueueTests.swift +++ b/Tests/AsyncObjectsTests/TaskQueueTests.swift @@ -9,7 +9,7 @@ class TaskQueueTests: XCTestCase { func testSignalingQueueDoesNothing() async { let queue = TaskQueue() - await queue.signal() + queue.signal() let blocked = await queue.blocked XCTAssertFalse(blocked) } @@ -22,7 +22,7 @@ class TaskQueueTests: XCTestCase { } } try await Self.sleep(seconds: 1) - await queue.signal() + queue.signal() let blocked = await queue.blocked XCTAssertTrue(blocked) } @@ -45,8 +45,8 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) - group.addTask { await queue.wait() } + try await Self.sleep(seconds: 0.01) + group.addTask { try await queue.wait() } try await group.waitForAll() } } @@ -93,8 +93,17 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) - group.addTask { await queue.wait(forSeconds: 1) } + try await Self.sleep(seconds: 0.01) + group.addTask { + do { + try await queue.wait(forSeconds: 1) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue( + type(of: error) == DurationTimeoutError.self + ) + } + } for try await _ in group.prefix(1) { group.cancelAll() } @@ -154,7 +163,7 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .block) { try await Self.sleep(seconds: 1) @@ -175,7 +184,7 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec { try await Self.sleep(seconds: 1) @@ -196,7 +205,7 @@ class TaskQueueTests: XCTestCase { throw CancellationError() } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .block) { try await Self.sleep(seconds: 2) @@ -227,7 +236,7 @@ class TaskQueueTests: XCTestCase { throw CancellationError() } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .block) { try await Self.sleep(seconds: 2) @@ -264,7 +273,7 @@ class TaskQueueTests: XCTestCase { throw CancellationError() } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .block) { try await Self.sleep(seconds: 2) @@ -322,7 +331,7 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .barrier) { try await Self.sleep(seconds: 1) @@ -343,7 +352,7 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec { try await Self.sleep(seconds: 1) @@ -364,7 +373,7 @@ class TaskQueueTests: XCTestCase { throw CancellationError() } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .barrier) { try await Self.sleep(seconds: 2) @@ -395,7 +404,7 @@ class TaskQueueTests: XCTestCase { throw CancellationError() } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .barrier) { try await Self.sleep(seconds: 3) @@ -432,7 +441,7 @@ class TaskQueueTests: XCTestCase { throw CancellationError() } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .barrier) { try await Self.sleep(seconds: 3) @@ -471,7 +480,7 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .barrier) { try await Self.sleep(seconds: 1) @@ -492,7 +501,7 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec(flags: .block) { try await Self.sleep(seconds: 1) @@ -516,14 +525,14 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) await group.addTaskAndStart { try await queue.exec(flags: .block) { try await Self.sleep(seconds: 1) } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) await group.addTaskAndStart { try await queue.exec(flags: .barrier) { try await Self.sleep(seconds: 3) @@ -547,14 +556,14 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try! await Self.sleep(forSeconds: 0.01) + try! await Self.sleep(seconds: 0.01) await group.addTaskAndStart { await queue.exec(flags: .barrier) { try! await Self.sleep(seconds: 2) } } // Make sure previous tasks started - try! await Self.sleep(forSeconds: 0.01) + try! await Self.sleep(seconds: 0.01) await group.addTaskAndStart { await queue.exec(flags: .block) { try! await Self.sleep(seconds: 1) @@ -587,14 +596,14 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try! await Self.sleep(forSeconds: 0.01) + try! await Self.sleep(seconds: 0.01) await group.addTaskAndStart { await queue.exec(flags: .barrier) { try! await Self.sleep(seconds: 2) } } // Make sure previous tasks started - try! await Self.sleep(forSeconds: 0.01) + try! await Self.sleep(seconds: 0.01) group.addTask { await queue.exec { try! await Self.sleep(seconds: 1) @@ -667,14 +676,14 @@ class TaskQueueTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) await group.addTaskAndStart { try await queue.exec(flags: .barrier) { try await Self.sleep(seconds: 2) } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await queue.exec { try await Self.sleep(seconds: 2) @@ -717,10 +726,10 @@ class TaskQueueTests: XCTestCase { try await Self.sleep(seconds: 2) } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.001) - await Self.checkExecInterval(durationInSeconds: 2) { + try await Self.sleep(seconds: 0.001) + try await Self.checkExecInterval(durationInSeconds: 2) { queue.addTask { try! await Self.sleep(seconds: 2) } - await queue.wait() + try await queue.wait() } } @@ -732,8 +741,9 @@ class TaskQueueTests: XCTestCase { try await queue.exec { try await Self.sleep(seconds: 1) } - try await Self.sleep(forSeconds: 0.001) + try await Self.sleep(seconds: 0.001) self.addTeardownBlock { [weak queue] in + try await Self.sleep(seconds: 1) XCTAssertNil(queue) } } @@ -744,12 +754,17 @@ class TaskQueueTests: XCTestCase { try await Self.sleep(seconds: 10) } let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { - await queue.wait() + try await Self.checkExecInterval(durationInSeconds: 0) { + try await queue.wait() } } task.cancel() - await task.value + do { + try await task.value + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } } func testWaitCancellationForAlreadyCancelledTask() async throws { @@ -758,17 +773,22 @@ class TaskQueueTests: XCTestCase { try await Self.sleep(seconds: 10) } let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { + try await Self.checkExecInterval(durationInSeconds: 0) { do { try await Self.sleep(seconds: 5) XCTFail("Unexpected task progression") } catch {} XCTAssertTrue(Task.isCancelled) - await queue.wait() + try await queue.wait() } } task.cancel() - await task.value + do { + try await task.value + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } } func testBarrierTaskCancellationWhenTaskCancelled() async throws { @@ -777,13 +797,18 @@ class TaskQueueTests: XCTestCase { try await Self.sleep(seconds: 10) } let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { + try await Self.checkExecInterval(durationInSeconds: 0) { await queue.exec(flags: .barrier) {} - await queue.wait() + try await queue.wait() } } task.cancel() - await task.value + do { + try await task.value + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } } func testBarrierTaskCancellationForAlreadyCancelledTask() async throws { @@ -792,18 +817,23 @@ class TaskQueueTests: XCTestCase { try await Self.sleep(seconds: 10) } let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { + try await Self.checkExecInterval(durationInSeconds: 0) { do { try await Self.sleep(seconds: 5) XCTFail("Unexpected task progression") } catch {} XCTAssertTrue(Task.isCancelled) await queue.exec(flags: .barrier) {} - await queue.wait() + try await queue.wait() } } task.cancel() - await task.value + do { + try await task.value + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } } } diff --git a/Tests/AsyncObjectsTests/ThrowingFutureTests.swift b/Tests/AsyncObjectsTests/ThrowingFutureTests.swift index 7b42dd6d..b15cdafc 100644 --- a/Tests/AsyncObjectsTests/ThrowingFutureTests.swift +++ b/Tests/AsyncObjectsTests/ThrowingFutureTests.swift @@ -7,7 +7,7 @@ class ThrowingFutureTests: XCTestCase { func testFutureFulfilledInitialization() async throws { let future = Future(with: .success(5)) - let value = try await future.value + let value = try await future.get() XCTAssertEqual(value, 5) } @@ -15,7 +15,7 @@ class ThrowingFutureTests: XCTestCase { let future = Future() try await withThrowingTaskGroup(of: Void.self) { group in group.addTask { - let value = try await future.value + let value = try await future.get() XCTAssertEqual(value, 5) } group.addTask { @@ -31,7 +31,7 @@ class ThrowingFutureTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in group.addTask { do { - let _ = try await future.value + let _ = try await future.get() XCTFail("Unexpected task progression") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -49,7 +49,7 @@ class ThrowingFutureTests: XCTestCase { let future = Future() let waitTask = Task { do { - let _ = try await future.value + let _ = try await future.get() XCTFail("Future fulfillments wait not cancelled") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -72,15 +72,15 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.all(future1, future2, future3) + let allFuture = Future.all(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { - let value = try await allFuture.value + let value = try await allFuture.get() XCTAssertEqual(value, [1, 2, 3]) } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -102,13 +102,13 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.all(future1, future2, future3) + let allFuture = Future.all(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 2) { do { - let _ = try await allFuture.value + let _ = try await allFuture.get() XCTFail("Future fulfillment did not fail") } catch { XCTAssertTrue( @@ -118,7 +118,7 @@ class ThrowingFutureTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -140,11 +140,11 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.allSettled(future1, future2, future3) + let allFuture = Future.allSettled(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { - let values = await allFuture.value + let values = await allFuture.get() for (index, item) in values.enumerated() { switch item { case .success(let value): @@ -155,7 +155,7 @@ class ThrowingFutureTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -177,12 +177,12 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.allSettled(future1, future2, future3) + let allFuture = Future.allSettled(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 3) { - let values = await allFuture.value + let values = await allFuture.get() for (index, item) in values.enumerated() { switch item { case .success(let value): @@ -197,7 +197,7 @@ class ThrowingFutureTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -219,17 +219,17 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.race(future1, future2, future3) + let allFuture = Future.race(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { try await Self.checkExecInterval(durationInSeconds: 1) { - let value = try await allFuture.value + let value = try await allFuture.get() XCTAssertEqual(value, 1) } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -251,13 +251,13 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.race(future1, future2, future3) + let allFuture = Future.race(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 1) { do { - let _ = try await allFuture.value + let _ = try await allFuture.get() XCTFail("Future fulfillment did not fail") } catch { XCTAssertTrue( @@ -267,7 +267,7 @@ class ThrowingFutureTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(throwing: CancellationError()) @@ -289,17 +289,17 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.any(future1, future2, future3) + let allFuture = Future.any(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { try await Self.checkExecInterval(durationInSeconds: 1) { - let value = try await allFuture.value + let value = try await allFuture.get() XCTAssertEqual(value, 1) } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(producing: 1) @@ -321,17 +321,17 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.any(future1, future2, future3) + let allFuture = Future.any(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { try await Self.checkExecInterval(durationInSeconds: 2) { - let value = try await allFuture.value + let value = try await allFuture.get() XCTAssertEqual(value, 2) } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(throwing: CancellationError()) @@ -353,13 +353,13 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.any(future1, future2, future3) + let allFuture = Future.any(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 3) { do { - let _ = try await allFuture.value + let _ = try await allFuture.get() XCTFail("Future fulfillment did not fail") } catch { XCTAssertTrue( @@ -369,7 +369,7 @@ class ThrowingFutureTests: XCTestCase { } } // Make sure previous tasks started - try await Self.sleep(forSeconds: 0.01) + try await Self.sleep(seconds: 0.01) group.addTask { try await Self.sleep(seconds: 1) await future1.fulfill(throwing: CancellationError()) @@ -388,7 +388,7 @@ class ThrowingFutureTests: XCTestCase { } func testConstructingAnyFutureFromZeroFutures() async { - let future = await Future.any() + let future = Future.any() let result = await future.result switch result { case .failure(let error): @@ -398,21 +398,21 @@ class ThrowingFutureTests: XCTestCase { } func testConstructingAllFutureFromZeroFutures() async throws { - let future = await Future.all() - let value = try await future.value + let future = Future.all() + let value = try await future.get() XCTAssertTrue(value.isEmpty) } func testConstructingAllSettledFutureFromZeroFutures() async throws { - let future = await Future.allSettled() - let value = await future.value + let future = Future.allSettled() + let value = await future.get() XCTAssertTrue(value.isEmpty) } func testMultipleTimesFutureFulfilled() async throws { let future = Future(with: .success(5)) await future.fulfill(producing: 10) - let value = try await future.value + let value = try await future.get() XCTAssertEqual(value, 5) } @@ -422,8 +422,9 @@ class ThrowingFutureTests: XCTestCase { try await Self.sleep(seconds: 1) await future.fulfill(producing: 5) } - let _ = try await future.value + let _ = try await future.get() self.addTeardownBlock { [weak future] in + try await Self.sleep(seconds: 1) XCTAssertNil(future) } } @@ -433,7 +434,7 @@ class ThrowingFutureTests: XCTestCase { let task = Task.detached { await Self.checkExecInterval(durationInSeconds: 0) { do { - let _ = try await future.value + let _ = try await future.get() XCTFail("Unexpected task progression") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -454,7 +455,7 @@ class ThrowingFutureTests: XCTestCase { } catch {} XCTAssertTrue(Task.isCancelled) do { - let _ = try await future.value + let _ = try await future.get() XCTFail("Unexpected task progression") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -473,7 +474,7 @@ class ThrowingFutureTests: XCTestCase { try await Self.checkExecInterval(durationInSeconds: 0) { try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { let _ = try await future.value } + group.addTask { let _ = try await future.get() } group.addTask { await future.fulfill(producing: i) } try await group.waitForAll() } diff --git a/Tests/AsyncObjectsTests/XCTestCase.swift b/Tests/AsyncObjectsTests/XCTestCase.swift index 467e004f..83420f37 100644 --- a/Tests/AsyncObjectsTests/XCTestCase.swift +++ b/Tests/AsyncObjectsTests/XCTestCase.swift @@ -24,45 +24,21 @@ extension XCTestCase { #endif } - static func checkExecInterval( + static func checkExecInterval( name: String? = nil, - durationInSeconds seconds: Int = 0, + durationInSeconds seconds: T = .zero, for task: () async throws -> Void - ) async rethrows { - let time = DispatchTime.now() + ) async rethrows where T: Comparable { + let second: T = 1_000_000_000 + let time = DispatchTime.now().uptimeNanoseconds try await task() + guard + let span = T(exactly: DispatchTime.now().uptimeNanoseconds - time), + case let duration = span / second + else { XCTFail("Invalid number type: \(T.self)"); return } let assertions = { - XCTAssertEqual( - seconds, - Int( - (Double( - DispatchTime.now().uptimeNanoseconds - - time.uptimeNanoseconds - ) / 1E9).rounded(.toNearestOrAwayFromZero) - ) - ) - } - runAssertions(with: name, assertions) - } - - static func checkExecInterval( - name: String? = nil, - durationInSeconds seconds: Double = 0, - roundedUpTo digit: UInt = 1, - for task: () async throws -> Void - ) async rethrows { - let time = DispatchTime.now() - try await task() - let order = pow(10, Double(digit)) - let duration = - Double( - DispatchTime.now().uptimeNanoseconds - time.uptimeNanoseconds - ) * order - let assertions = { - XCTAssertEqual( - seconds, - (duration / 1E9).rounded() / order - ) + XCTAssertLessThanOrEqual(duration, seconds + 1) + XCTAssertGreaterThanOrEqual(duration, seconds - 1) } runAssertions(with: name, assertions) } @@ -71,14 +47,16 @@ extension XCTestCase { name: String? = nil, durationInRange range: R, for task: () async throws -> Void - ) async rethrows where R.Bound == Int { - let time = DispatchTime.now() + ) async rethrows where R.Bound: DivisiveArithmetic { + let second: R.Bound = 1_000_000_000 + let time = DispatchTime.now().uptimeNanoseconds try await task() - let duration = Int( - (Double( - DispatchTime.now().uptimeNanoseconds - time.uptimeNanoseconds - ) / 1E9).rounded(.toNearestOrAwayFromZero) - ) + guard + let span = R.Bound( + exactly: DispatchTime.now().uptimeNanoseconds - time + ), + case let duration = span / second + else { XCTFail("Invalid range type: \(R.self)"); return } let assertions = { XCTAssertTrue( range.contains(duration), @@ -88,40 +66,30 @@ extension XCTestCase { runAssertions(with: name, assertions) } - static func checkExecInterval( - name: String? = nil, - durationInRange range: R, - for task: () async throws -> Void - ) async rethrows where R.Bound == Double { - let time = DispatchTime.now() - try await task() - let duration = - Double( - DispatchTime.now().uptimeNanoseconds - time.uptimeNanoseconds - ) / 1E9 - let assertions = { - XCTAssertTrue( - range.contains(duration), - "\(duration) not present in \(range)" - ) - } - runAssertions(with: name, assertions) - } - - static func sleep(seconds: UInt64) async throws { - try await Task.sleep(nanoseconds: seconds * 1_000_000_000) + static func sleep(seconds: T) async throws { + let second: T = 1_000_000_000 + try await Task.sleep(nanoseconds: UInt64(exactly: seconds * second)!) } - static func sleep(forSeconds seconds: Double) async throws { - try await Task.sleep(nanoseconds: UInt64(seconds * 1E9)) + static func sleep(seconds: T) async throws { + let second: T = 1_000_000_000 + try await Task.sleep(nanoseconds: UInt64(exactly: seconds * second)!) } } extension AsyncObject { - @discardableResult @Sendable @inlinable - func wait(forSeconds seconds: UInt64) async -> TaskTimeoutResult { - return await self.wait(forNanoseconds: seconds * 1_000_000_000) + func wait(forSeconds seconds: UInt64) async throws { + return try await self.wait(forNanoseconds: seconds * 1_000_000_000) } } + +protocol DivisiveArithmetic: Numeric { + static func / (lhs: Self, rhs: Self) -> Self + static func /= (lhs: inout Self, rhs: Self) +} + +extension Int: DivisiveArithmetic {} +extension Double: DivisiveArithmetic {} +extension UInt64: DivisiveArithmetic {}