diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ba33ffca..2ba4e233 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,7 +59,7 @@ jobs: "swift": "5.7" }, { - "os": "ubuntu-latest", + "os": "ubuntu-20.04", "swift": "5.6" } ] diff --git a/AsyncObjects.podspec b/AsyncObjects.podspec index 36a3193f..6da26b1f 100644 --- a/AsyncObjects.podspec +++ b/AsyncObjects.podspec @@ -38,9 +38,31 @@ Pod::Spec.new do |s| } s.dependency 'OrderedCollections', '~> 1.0.0' + s.default_subspecs = :none + + s.subspec 'Checked' do |ss| + ss.pod_target_xcconfig = { + 'OTHER_SWIFT_FLAGS' => '-D ASYNCOBJECTS_USE_CHECKEDCONTINUATION' + } + end + + s.subspec 'Logging' do |ss| + ss.dependency 'Logging', '~> 1.0.0' + # ss.default_subspec = 'Info' + + for level in ['Debug', 'Info', 'Trace'] do + ss.subspec level do |sss| + sss.pod_target_xcconfig = { + 'OTHER_SWIFT_FLAGS' => "-D ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_#{level.upcase}" + } + end + end + end s.test_spec do |ts| ts.source_files = "Tests/#{s.name}Tests/**/*.swift" + ts.dependency "#{s.name}/Checked" + ts.dependency "#{s.name}/Logging" ts.scheme = { :parallelizable => true } end end diff --git a/AsyncObjects.xcodeproj/project.pbxproj b/AsyncObjects.xcodeproj/project.pbxproj index 33d87af6..e96681eb 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_150 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */; + buildConfigurationList = OBJ_153 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */; buildPhases = ( ); dependencies = ( - OBJ_153 /* PBXTargetDependency */, + OBJ_156 /* PBXTargetDependency */, ); name = AsyncObjectsPackageTests; productName = AsyncObjectsPackageTests; @@ -21,109 +21,112 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 8BB988CB9A701C7EE03686DC /* AsyncObjects.docc in Sources */ = {isa = PBXBuildFile; fileRef = AFECD0135D28B7BE3712A6C7 /* AsyncObjects.docc */; }; - OBJ_119 /* AsyncCountdownEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* AsyncCountdownEvent.swift */; }; - OBJ_120 /* AsyncEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* AsyncEvent.swift */; }; - OBJ_121 /* AsyncSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* AsyncSemaphore.swift */; }; - OBJ_122 /* AsyncObject+Clock.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* AsyncObject+Clock.swift */; }; - OBJ_123 /* AsyncObject+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* AsyncObject+Duration.swift */; }; - OBJ_124 /* AsyncObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* AsyncObject.swift */; }; - OBJ_125 /* CancellationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* CancellationSource.swift */; }; - OBJ_126 /* Continuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* Continuable.swift */; }; - OBJ_127 /* ContinuableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* ContinuableCollection.swift */; }; - OBJ_128 /* GlobalContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* GlobalContinuation.swift */; }; - OBJ_129 /* SafeContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* SafeContinuation.swift */; }; - OBJ_130 /* SynchronizedContinuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* SynchronizedContinuable.swift */; }; - OBJ_131 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* Task.swift */; }; - OBJ_132 /* TaskGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* TaskGroup.swift */; }; - OBJ_133 /* Future.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* Future.swift */; }; - OBJ_134 /* Exclusible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* Exclusible.swift */; }; - OBJ_135 /* Locker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Locker.swift */; }; - OBJ_136 /* TaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* TaskOperation.swift */; }; - OBJ_137 /* TaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* TaskQueue.swift */; }; - OBJ_138 /* TaskTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* TaskTracker.swift */; }; - OBJ_140 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; - OBJ_148 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; - OBJ_159 /* AsyncCountdownEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* AsyncCountdownEventTests.swift */; }; - OBJ_160 /* AsyncEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* AsyncEventTests.swift */; }; - OBJ_161 /* AsyncObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_39 /* AsyncObjectTests.swift */; }; - OBJ_162 /* AsyncSemaphoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_40 /* AsyncSemaphoreTests.swift */; }; - OBJ_163 /* CancellationSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_41 /* CancellationSourceTests.swift */; }; - OBJ_164 /* LockerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_42 /* LockerTests.swift */; }; - OBJ_165 /* NonThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_43 /* NonThrowingFutureTests.swift */; }; - OBJ_166 /* SafeContinuationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_44 /* SafeContinuationTests.swift */; }; - OBJ_167 /* StandardLibraryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_45 /* StandardLibraryTests.swift */; }; - OBJ_168 /* TaskOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_46 /* TaskOperationTests.swift */; }; - OBJ_169 /* TaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_47 /* TaskQueueTests.swift */; }; - OBJ_170 /* ThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_48 /* ThrowingFutureTests.swift */; }; - OBJ_171 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_49 /* XCTestCase.swift */; }; - OBJ_173 /* AsyncObjects.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */; }; - OBJ_174 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; - OBJ_181 /* _HashTable+Bucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_56 /* _HashTable+Bucket.swift */; }; - OBJ_182 /* _HashTable+BucketIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_57 /* _HashTable+BucketIterator.swift */; }; - OBJ_183 /* _HashTable+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* _HashTable+Constants.swift */; }; - OBJ_184 /* _HashTable+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_59 /* _HashTable+CustomStringConvertible.swift */; }; - OBJ_185 /* _HashTable+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_60 /* _HashTable+Testing.swift */; }; - OBJ_186 /* _HashTable+UnsafeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_61 /* _HashTable+UnsafeHandle.swift */; }; - OBJ_187 /* _HashTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_62 /* _HashTable.swift */; }; - OBJ_188 /* _Hashtable+Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_63 /* _Hashtable+Header.swift */; }; - OBJ_189 /* OrderedDictionary+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_65 /* OrderedDictionary+Codable.swift */; }; - OBJ_190 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_66 /* OrderedDictionary+CustomDebugStringConvertible.swift */; }; - OBJ_191 /* OrderedDictionary+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_67 /* OrderedDictionary+CustomReflectable.swift */; }; - OBJ_192 /* OrderedDictionary+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_68 /* OrderedDictionary+CustomStringConvertible.swift */; }; - OBJ_193 /* OrderedDictionary+Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_69 /* OrderedDictionary+Deprecations.swift */; }; - OBJ_194 /* OrderedDictionary+Elements+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_70 /* OrderedDictionary+Elements+SubSequence.swift */; }; - OBJ_195 /* OrderedDictionary+Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_71 /* OrderedDictionary+Elements.swift */; }; - OBJ_196 /* OrderedDictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_72 /* OrderedDictionary+Equatable.swift */; }; - OBJ_197 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_73 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */; }; - OBJ_198 /* OrderedDictionary+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_74 /* OrderedDictionary+Hashable.swift */; }; - OBJ_199 /* OrderedDictionary+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_75 /* OrderedDictionary+Initializers.swift */; }; - OBJ_200 /* OrderedDictionary+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_76 /* OrderedDictionary+Invariants.swift */; }; - OBJ_201 /* OrderedDictionary+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_77 /* OrderedDictionary+Partial MutableCollection.swift */; }; - OBJ_202 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_78 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */; }; - OBJ_203 /* OrderedDictionary+Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_79 /* OrderedDictionary+Sequence.swift */; }; - OBJ_204 /* OrderedDictionary+Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_80 /* OrderedDictionary+Values.swift */; }; - OBJ_205 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_81 /* OrderedDictionary.swift */; }; - OBJ_206 /* OrderedSet+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_83 /* OrderedSet+Codable.swift */; }; - OBJ_207 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_84 /* OrderedSet+CustomDebugStringConvertible.swift */; }; - OBJ_208 /* OrderedSet+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_85 /* OrderedSet+CustomReflectable.swift */; }; - OBJ_209 /* OrderedSet+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_86 /* OrderedSet+CustomStringConvertible.swift */; }; - OBJ_210 /* OrderedSet+Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_87 /* OrderedSet+Diffing.swift */; }; - OBJ_211 /* OrderedSet+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_88 /* OrderedSet+Equatable.swift */; }; - OBJ_212 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_89 /* OrderedSet+ExpressibleByArrayLiteral.swift */; }; - OBJ_213 /* OrderedSet+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_90 /* OrderedSet+Hashable.swift */; }; - OBJ_214 /* OrderedSet+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_91 /* OrderedSet+Initializers.swift */; }; - OBJ_215 /* OrderedSet+Insertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_92 /* OrderedSet+Insertions.swift */; }; - OBJ_216 /* OrderedSet+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_93 /* OrderedSet+Invariants.swift */; }; - OBJ_217 /* OrderedSet+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_94 /* OrderedSet+Partial MutableCollection.swift */; }; - OBJ_218 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_95 /* OrderedSet+Partial RangeReplaceableCollection.swift */; }; - OBJ_219 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_96 /* OrderedSet+Partial SetAlgebra+Basics.swift */; }; - OBJ_220 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_97 /* OrderedSet+Partial SetAlgebra+Operations.swift */; }; - OBJ_221 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_98 /* OrderedSet+Partial SetAlgebra+Predicates.swift */; }; - OBJ_222 /* OrderedSet+RandomAccessCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_99 /* OrderedSet+RandomAccessCollection.swift */; }; - OBJ_223 /* OrderedSet+ReserveCapacity.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_100 /* OrderedSet+ReserveCapacity.swift */; }; - OBJ_224 /* OrderedSet+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_101 /* OrderedSet+SubSequence.swift */; }; - OBJ_225 /* OrderedSet+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_102 /* OrderedSet+Testing.swift */; }; - OBJ_226 /* OrderedSet+UnorderedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_103 /* OrderedSet+UnorderedView.swift */; }; - OBJ_227 /* OrderedSet+UnstableInternals.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_104 /* OrderedSet+UnstableInternals.swift */; }; - OBJ_228 /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_105 /* OrderedSet.swift */; }; - OBJ_229 /* RandomAccessCollection+Offsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_107 /* RandomAccessCollection+Offsets.swift */; }; - OBJ_230 /* _UnsafeBitset.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_108 /* _UnsafeBitset.swift */; }; - OBJ_237 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_109 /* Package.swift */; }; + BB028CDED6BCD928C0AAF339 /* AsyncObjects.docc in Sources */ = {isa = PBXBuildFile; fileRef = ED492FF628B26125C8C62E41 /* AsyncObjects.docc */; }; + OBJ_121 /* AsyncCountdownEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* AsyncCountdownEvent.swift */; }; + OBJ_122 /* AsyncEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* AsyncEvent.swift */; }; + OBJ_123 /* AsyncSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* AsyncSemaphore.swift */; }; + OBJ_124 /* AsyncObject+Clock.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* AsyncObject+Clock.swift */; }; + OBJ_125 /* AsyncObject+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* AsyncObject+Duration.swift */; }; + OBJ_126 /* AsyncObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* AsyncObject.swift */; }; + OBJ_127 /* CancellationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* CancellationSource.swift */; }; + OBJ_128 /* Continuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* Continuable.swift */; }; + OBJ_129 /* ContinuableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* ContinuableCollection.swift */; }; + OBJ_130 /* GlobalContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* GlobalContinuation.swift */; }; + OBJ_131 /* TrackableContinuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* TrackableContinuable.swift */; }; + OBJ_132 /* TrackedContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* TrackedContinuation.swift */; }; + OBJ_133 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* Task.swift */; }; + OBJ_134 /* TaskGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* TaskGroup.swift */; }; + OBJ_135 /* Future.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* Future.swift */; }; + OBJ_136 /* Exclusible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* Exclusible.swift */; }; + OBJ_137 /* Locker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Locker.swift */; }; + OBJ_138 /* Loggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* Loggable.swift */; }; + OBJ_139 /* TaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* TaskOperation.swift */; }; + OBJ_140 /* TaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* TaskQueue.swift */; }; + OBJ_141 /* TaskTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* TaskTracker.swift */; }; + OBJ_143 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; + OBJ_151 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; + OBJ_162 /* AsyncCountdownEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_39 /* AsyncCountdownEventTests.swift */; }; + OBJ_163 /* AsyncEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_40 /* AsyncEventTests.swift */; }; + OBJ_164 /* AsyncObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_41 /* AsyncObjectTests.swift */; }; + OBJ_165 /* AsyncSemaphoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_42 /* AsyncSemaphoreTests.swift */; }; + OBJ_166 /* CancellationSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_43 /* CancellationSourceTests.swift */; }; + OBJ_167 /* LockerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_44 /* LockerTests.swift */; }; + OBJ_168 /* NonThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_45 /* NonThrowingFutureTests.swift */; }; + OBJ_169 /* StandardLibraryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_46 /* StandardLibraryTests.swift */; }; + OBJ_170 /* TaskOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_47 /* TaskOperationTests.swift */; }; + OBJ_171 /* TaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_48 /* TaskQueueTests.swift */; }; + OBJ_172 /* ThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_49 /* ThrowingFutureTests.swift */; }; + OBJ_173 /* TrackedContinuationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_50 /* TrackedContinuationTests.swift */; }; + OBJ_174 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_51 /* XCTestCase.swift */; }; + OBJ_176 /* AsyncObjects.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */; }; + OBJ_177 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; + OBJ_184 /* _HashTable+Bucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* _HashTable+Bucket.swift */; }; + OBJ_185 /* _HashTable+BucketIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_59 /* _HashTable+BucketIterator.swift */; }; + OBJ_186 /* _HashTable+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_60 /* _HashTable+Constants.swift */; }; + OBJ_187 /* _HashTable+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_61 /* _HashTable+CustomStringConvertible.swift */; }; + OBJ_188 /* _HashTable+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_62 /* _HashTable+Testing.swift */; }; + OBJ_189 /* _HashTable+UnsafeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_63 /* _HashTable+UnsafeHandle.swift */; }; + OBJ_190 /* _HashTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_64 /* _HashTable.swift */; }; + OBJ_191 /* _Hashtable+Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_65 /* _Hashtable+Header.swift */; }; + OBJ_192 /* OrderedDictionary+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_67 /* OrderedDictionary+Codable.swift */; }; + OBJ_193 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_68 /* OrderedDictionary+CustomDebugStringConvertible.swift */; }; + OBJ_194 /* OrderedDictionary+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_69 /* OrderedDictionary+CustomReflectable.swift */; }; + OBJ_195 /* OrderedDictionary+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_70 /* OrderedDictionary+CustomStringConvertible.swift */; }; + OBJ_196 /* OrderedDictionary+Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_71 /* OrderedDictionary+Deprecations.swift */; }; + OBJ_197 /* OrderedDictionary+Elements+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_72 /* OrderedDictionary+Elements+SubSequence.swift */; }; + OBJ_198 /* OrderedDictionary+Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_73 /* OrderedDictionary+Elements.swift */; }; + OBJ_199 /* OrderedDictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_74 /* OrderedDictionary+Equatable.swift */; }; + OBJ_200 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_75 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */; }; + OBJ_201 /* OrderedDictionary+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_76 /* OrderedDictionary+Hashable.swift */; }; + OBJ_202 /* OrderedDictionary+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_77 /* OrderedDictionary+Initializers.swift */; }; + OBJ_203 /* OrderedDictionary+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_78 /* OrderedDictionary+Invariants.swift */; }; + OBJ_204 /* OrderedDictionary+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_79 /* OrderedDictionary+Partial MutableCollection.swift */; }; + OBJ_205 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_80 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */; }; + OBJ_206 /* OrderedDictionary+Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_81 /* OrderedDictionary+Sequence.swift */; }; + OBJ_207 /* OrderedDictionary+Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_82 /* OrderedDictionary+Values.swift */; }; + OBJ_208 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_83 /* OrderedDictionary.swift */; }; + OBJ_209 /* OrderedSet+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_85 /* OrderedSet+Codable.swift */; }; + OBJ_210 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_86 /* OrderedSet+CustomDebugStringConvertible.swift */; }; + OBJ_211 /* OrderedSet+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_87 /* OrderedSet+CustomReflectable.swift */; }; + OBJ_212 /* OrderedSet+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_88 /* OrderedSet+CustomStringConvertible.swift */; }; + OBJ_213 /* OrderedSet+Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_89 /* OrderedSet+Diffing.swift */; }; + OBJ_214 /* OrderedSet+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_90 /* OrderedSet+Equatable.swift */; }; + OBJ_215 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_91 /* OrderedSet+ExpressibleByArrayLiteral.swift */; }; + OBJ_216 /* OrderedSet+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_92 /* OrderedSet+Hashable.swift */; }; + OBJ_217 /* OrderedSet+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_93 /* OrderedSet+Initializers.swift */; }; + OBJ_218 /* OrderedSet+Insertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_94 /* OrderedSet+Insertions.swift */; }; + OBJ_219 /* OrderedSet+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_95 /* OrderedSet+Invariants.swift */; }; + OBJ_220 /* OrderedSet+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_96 /* OrderedSet+Partial MutableCollection.swift */; }; + OBJ_221 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_97 /* OrderedSet+Partial RangeReplaceableCollection.swift */; }; + OBJ_222 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_98 /* OrderedSet+Partial SetAlgebra+Basics.swift */; }; + OBJ_223 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_99 /* OrderedSet+Partial SetAlgebra+Operations.swift */; }; + OBJ_224 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_100 /* OrderedSet+Partial SetAlgebra+Predicates.swift */; }; + OBJ_225 /* OrderedSet+RandomAccessCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_101 /* OrderedSet+RandomAccessCollection.swift */; }; + OBJ_226 /* OrderedSet+ReserveCapacity.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_102 /* OrderedSet+ReserveCapacity.swift */; }; + OBJ_227 /* OrderedSet+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_103 /* OrderedSet+SubSequence.swift */; }; + OBJ_228 /* OrderedSet+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_104 /* OrderedSet+Testing.swift */; }; + OBJ_229 /* OrderedSet+UnorderedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_105 /* OrderedSet+UnorderedView.swift */; }; + OBJ_230 /* OrderedSet+UnstableInternals.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_106 /* OrderedSet+UnstableInternals.swift */; }; + OBJ_231 /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_107 /* OrderedSet.swift */; }; + OBJ_232 /* RandomAccessCollection+Offsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_109 /* RandomAccessCollection+Offsets.swift */; }; + OBJ_233 /* _UnsafeBitset.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_110 /* _UnsafeBitset.swift */; }; + OBJ_240 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_111 /* Package.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - AFECD0135D28B7BE3712A6C7 /* AsyncObjects.docc */ = {isa = PBXFileReference; includeInIndex = 1; path = AsyncObjects.docc; sourceTree = ""; }; - OBJ_100 /* OrderedSet+ReserveCapacity.swift */ = {isa = PBXFileReference; path = "OrderedSet+ReserveCapacity.swift"; sourceTree = ""; }; - OBJ_101 /* OrderedSet+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedSet+SubSequence.swift"; sourceTree = ""; }; - OBJ_102 /* OrderedSet+Testing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Testing.swift"; sourceTree = ""; }; - OBJ_103 /* OrderedSet+UnorderedView.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnorderedView.swift"; sourceTree = ""; }; - OBJ_104 /* OrderedSet+UnstableInternals.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnstableInternals.swift"; sourceTree = ""; }; - OBJ_105 /* OrderedSet.swift */ = {isa = PBXFileReference; path = OrderedSet.swift; sourceTree = ""; }; - OBJ_107 /* RandomAccessCollection+Offsets.swift */ = {isa = PBXFileReference; path = "RandomAccessCollection+Offsets.swift"; sourceTree = ""; }; - OBJ_108 /* _UnsafeBitset.swift */ = {isa = PBXFileReference; path = _UnsafeBitset.swift; sourceTree = ""; }; - OBJ_109 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; name = Package.swift; path = "/Users/soumyaranjanmahunt/Documents/personal_projs/AsyncObjects/.build/checkouts/swift-collections/Package.swift"; sourceTree = ""; }; + ED492FF628B26125C8C62E41 /* AsyncObjects.docc */ = {isa = PBXFileReference; includeInIndex = 1; path = AsyncObjects.docc; sourceTree = ""; }; + OBJ_100 /* OrderedSet+Partial SetAlgebra+Predicates.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Predicates.swift"; sourceTree = ""; }; + OBJ_101 /* OrderedSet+RandomAccessCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+RandomAccessCollection.swift"; sourceTree = ""; }; + OBJ_102 /* OrderedSet+ReserveCapacity.swift */ = {isa = PBXFileReference; path = "OrderedSet+ReserveCapacity.swift"; sourceTree = ""; }; + OBJ_103 /* OrderedSet+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedSet+SubSequence.swift"; sourceTree = ""; }; + OBJ_104 /* OrderedSet+Testing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Testing.swift"; sourceTree = ""; }; + OBJ_105 /* OrderedSet+UnorderedView.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnorderedView.swift"; sourceTree = ""; }; + OBJ_106 /* OrderedSet+UnstableInternals.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnstableInternals.swift"; sourceTree = ""; }; + OBJ_107 /* OrderedSet.swift */ = {isa = PBXFileReference; path = OrderedSet.swift; sourceTree = ""; }; + OBJ_109 /* RandomAccessCollection+Offsets.swift */ = {isa = PBXFileReference; path = "RandomAccessCollection+Offsets.swift"; sourceTree = ""; }; OBJ_11 /* AsyncCountdownEvent.swift */ = {isa = PBXFileReference; path = AsyncCountdownEvent.swift; sourceTree = ""; }; + OBJ_110 /* _UnsafeBitset.swift */ = {isa = PBXFileReference; path = _UnsafeBitset.swift; sourceTree = ""; }; + OBJ_111 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; name = Package.swift; path = "/Users/soumya.mahunt/Documents/projects/AsyncObjects/.build/checkouts/swift-collections/Package.swift"; sourceTree = ""; }; OBJ_12 /* AsyncEvent.swift */ = {isa = PBXFileReference; path = AsyncEvent.swift; sourceTree = ""; }; OBJ_13 /* AsyncSemaphore.swift */ = {isa = PBXFileReference; path = AsyncSemaphore.swift; sourceTree = ""; }; OBJ_15 /* AsyncObject+Clock.swift */ = {isa = PBXFileReference; path = "AsyncObject+Clock.swift"; sourceTree = ""; }; @@ -133,93 +136,92 @@ OBJ_20 /* Continuable.swift */ = {isa = PBXFileReference; path = Continuable.swift; sourceTree = ""; }; OBJ_21 /* ContinuableCollection.swift */ = {isa = PBXFileReference; path = ContinuableCollection.swift; sourceTree = ""; }; OBJ_22 /* GlobalContinuation.swift */ = {isa = PBXFileReference; path = GlobalContinuation.swift; sourceTree = ""; }; - OBJ_23 /* SafeContinuation.swift */ = {isa = PBXFileReference; path = SafeContinuation.swift; sourceTree = ""; }; - OBJ_24 /* SynchronizedContinuable.swift */ = {isa = PBXFileReference; path = SynchronizedContinuable.swift; sourceTree = ""; }; + OBJ_23 /* TrackableContinuable.swift */ = {isa = PBXFileReference; path = TrackableContinuable.swift; sourceTree = ""; }; + OBJ_24 /* TrackedContinuation.swift */ = {isa = PBXFileReference; path = TrackedContinuation.swift; sourceTree = ""; }; OBJ_26 /* Task.swift */ = {isa = PBXFileReference; path = Task.swift; sourceTree = ""; }; OBJ_27 /* TaskGroup.swift */ = {isa = PBXFileReference; path = TaskGroup.swift; sourceTree = ""; }; OBJ_28 /* Future.swift */ = {isa = PBXFileReference; path = Future.swift; sourceTree = ""; }; OBJ_30 /* Exclusible.swift */ = {isa = PBXFileReference; path = Exclusible.swift; sourceTree = ""; }; OBJ_31 /* Locker.swift */ = {isa = PBXFileReference; path = Locker.swift; sourceTree = ""; }; - OBJ_32 /* TaskOperation.swift */ = {isa = PBXFileReference; path = TaskOperation.swift; sourceTree = ""; }; - OBJ_33 /* TaskQueue.swift */ = {isa = PBXFileReference; path = TaskQueue.swift; sourceTree = ""; }; - OBJ_34 /* TaskTracker.swift */ = {isa = PBXFileReference; path = TaskTracker.swift; sourceTree = ""; }; - OBJ_37 /* AsyncCountdownEventTests.swift */ = {isa = PBXFileReference; path = AsyncCountdownEventTests.swift; sourceTree = ""; }; - OBJ_38 /* AsyncEventTests.swift */ = {isa = PBXFileReference; path = AsyncEventTests.swift; sourceTree = ""; }; - OBJ_39 /* AsyncObjectTests.swift */ = {isa = PBXFileReference; path = AsyncObjectTests.swift; sourceTree = ""; }; - OBJ_40 /* AsyncSemaphoreTests.swift */ = {isa = PBXFileReference; path = AsyncSemaphoreTests.swift; sourceTree = ""; }; - OBJ_41 /* CancellationSourceTests.swift */ = {isa = PBXFileReference; path = CancellationSourceTests.swift; sourceTree = ""; }; - OBJ_42 /* LockerTests.swift */ = {isa = PBXFileReference; path = LockerTests.swift; sourceTree = ""; }; - OBJ_43 /* NonThrowingFutureTests.swift */ = {isa = PBXFileReference; path = NonThrowingFutureTests.swift; sourceTree = ""; }; - OBJ_44 /* SafeContinuationTests.swift */ = {isa = PBXFileReference; path = SafeContinuationTests.swift; sourceTree = ""; }; - OBJ_45 /* StandardLibraryTests.swift */ = {isa = PBXFileReference; path = StandardLibraryTests.swift; sourceTree = ""; }; - OBJ_46 /* TaskOperationTests.swift */ = {isa = PBXFileReference; path = TaskOperationTests.swift; sourceTree = ""; }; - OBJ_47 /* TaskQueueTests.swift */ = {isa = PBXFileReference; path = TaskQueueTests.swift; sourceTree = ""; }; - OBJ_48 /* ThrowingFutureTests.swift */ = {isa = PBXFileReference; path = ThrowingFutureTests.swift; sourceTree = ""; }; - OBJ_49 /* XCTestCase.swift */ = {isa = PBXFileReference; path = XCTestCase.swift; sourceTree = ""; }; - OBJ_56 /* _HashTable+Bucket.swift */ = {isa = PBXFileReference; path = "_HashTable+Bucket.swift"; sourceTree = ""; }; - OBJ_57 /* _HashTable+BucketIterator.swift */ = {isa = PBXFileReference; path = "_HashTable+BucketIterator.swift"; sourceTree = ""; }; - OBJ_58 /* _HashTable+Constants.swift */ = {isa = PBXFileReference; path = "_HashTable+Constants.swift"; sourceTree = ""; }; - OBJ_59 /* _HashTable+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "_HashTable+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_33 /* Loggable.swift */ = {isa = PBXFileReference; path = Loggable.swift; sourceTree = ""; }; + OBJ_34 /* TaskOperation.swift */ = {isa = PBXFileReference; path = TaskOperation.swift; sourceTree = ""; }; + OBJ_35 /* TaskQueue.swift */ = {isa = PBXFileReference; path = TaskQueue.swift; sourceTree = ""; }; + OBJ_36 /* TaskTracker.swift */ = {isa = PBXFileReference; path = TaskTracker.swift; sourceTree = ""; }; + OBJ_39 /* AsyncCountdownEventTests.swift */ = {isa = PBXFileReference; path = AsyncCountdownEventTests.swift; sourceTree = ""; }; + OBJ_40 /* AsyncEventTests.swift */ = {isa = PBXFileReference; path = AsyncEventTests.swift; sourceTree = ""; }; + OBJ_41 /* AsyncObjectTests.swift */ = {isa = PBXFileReference; path = AsyncObjectTests.swift; sourceTree = ""; }; + OBJ_42 /* AsyncSemaphoreTests.swift */ = {isa = PBXFileReference; path = AsyncSemaphoreTests.swift; sourceTree = ""; }; + OBJ_43 /* CancellationSourceTests.swift */ = {isa = PBXFileReference; path = CancellationSourceTests.swift; sourceTree = ""; }; + OBJ_44 /* LockerTests.swift */ = {isa = PBXFileReference; path = LockerTests.swift; sourceTree = ""; }; + OBJ_45 /* NonThrowingFutureTests.swift */ = {isa = PBXFileReference; path = NonThrowingFutureTests.swift; sourceTree = ""; }; + OBJ_46 /* StandardLibraryTests.swift */ = {isa = PBXFileReference; path = StandardLibraryTests.swift; sourceTree = ""; }; + OBJ_47 /* TaskOperationTests.swift */ = {isa = PBXFileReference; path = TaskOperationTests.swift; sourceTree = ""; }; + OBJ_48 /* TaskQueueTests.swift */ = {isa = PBXFileReference; path = TaskQueueTests.swift; sourceTree = ""; }; + OBJ_49 /* ThrowingFutureTests.swift */ = {isa = PBXFileReference; path = ThrowingFutureTests.swift; sourceTree = ""; }; + OBJ_50 /* TrackedContinuationTests.swift */ = {isa = PBXFileReference; path = TrackedContinuationTests.swift; sourceTree = ""; }; + OBJ_51 /* XCTestCase.swift */ = {isa = PBXFileReference; path = XCTestCase.swift; sourceTree = ""; }; + OBJ_58 /* _HashTable+Bucket.swift */ = {isa = PBXFileReference; path = "_HashTable+Bucket.swift"; sourceTree = ""; }; + OBJ_59 /* _HashTable+BucketIterator.swift */ = {isa = PBXFileReference; path = "_HashTable+BucketIterator.swift"; sourceTree = ""; }; OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; - OBJ_60 /* _HashTable+Testing.swift */ = {isa = PBXFileReference; path = "_HashTable+Testing.swift"; sourceTree = ""; }; - OBJ_61 /* _HashTable+UnsafeHandle.swift */ = {isa = PBXFileReference; path = "_HashTable+UnsafeHandle.swift"; sourceTree = ""; }; - OBJ_62 /* _HashTable.swift */ = {isa = PBXFileReference; path = _HashTable.swift; sourceTree = ""; }; - OBJ_63 /* _Hashtable+Header.swift */ = {isa = PBXFileReference; path = "_Hashtable+Header.swift"; sourceTree = ""; }; - OBJ_65 /* OrderedDictionary+Codable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Codable.swift"; sourceTree = ""; }; - OBJ_66 /* OrderedDictionary+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomDebugStringConvertible.swift"; sourceTree = ""; }; - OBJ_67 /* OrderedDictionary+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomReflectable.swift"; sourceTree = ""; }; - OBJ_68 /* OrderedDictionary+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomStringConvertible.swift"; sourceTree = ""; }; - OBJ_69 /* OrderedDictionary+Deprecations.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Deprecations.swift"; sourceTree = ""; }; - OBJ_70 /* OrderedDictionary+Elements+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements+SubSequence.swift"; sourceTree = ""; }; - OBJ_71 /* OrderedDictionary+Elements.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements.swift"; sourceTree = ""; }; - OBJ_72 /* OrderedDictionary+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Equatable.swift"; sourceTree = ""; }; - OBJ_73 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+ExpressibleByDictionaryLiteral.swift"; sourceTree = ""; }; - OBJ_74 /* OrderedDictionary+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Hashable.swift"; sourceTree = ""; }; - OBJ_75 /* OrderedDictionary+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Initializers.swift"; sourceTree = ""; }; - OBJ_76 /* OrderedDictionary+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Invariants.swift"; sourceTree = ""; }; - OBJ_77 /* OrderedDictionary+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial MutableCollection.swift"; sourceTree = ""; }; - OBJ_78 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; - OBJ_79 /* OrderedDictionary+Sequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Sequence.swift"; sourceTree = ""; }; + OBJ_60 /* _HashTable+Constants.swift */ = {isa = PBXFileReference; path = "_HashTable+Constants.swift"; sourceTree = ""; }; + OBJ_61 /* _HashTable+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "_HashTable+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_62 /* _HashTable+Testing.swift */ = {isa = PBXFileReference; path = "_HashTable+Testing.swift"; sourceTree = ""; }; + OBJ_63 /* _HashTable+UnsafeHandle.swift */ = {isa = PBXFileReference; path = "_HashTable+UnsafeHandle.swift"; sourceTree = ""; }; + OBJ_64 /* _HashTable.swift */ = {isa = PBXFileReference; path = _HashTable.swift; sourceTree = ""; }; + OBJ_65 /* _Hashtable+Header.swift */ = {isa = PBXFileReference; path = "_Hashtable+Header.swift"; sourceTree = ""; }; + OBJ_67 /* OrderedDictionary+Codable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Codable.swift"; sourceTree = ""; }; + OBJ_68 /* OrderedDictionary+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomDebugStringConvertible.swift"; sourceTree = ""; }; + OBJ_69 /* OrderedDictionary+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomReflectable.swift"; sourceTree = ""; }; + OBJ_70 /* OrderedDictionary+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_71 /* OrderedDictionary+Deprecations.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Deprecations.swift"; sourceTree = ""; }; + OBJ_72 /* OrderedDictionary+Elements+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements+SubSequence.swift"; sourceTree = ""; }; + OBJ_73 /* OrderedDictionary+Elements.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements.swift"; sourceTree = ""; }; + OBJ_74 /* OrderedDictionary+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Equatable.swift"; sourceTree = ""; }; + OBJ_75 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+ExpressibleByDictionaryLiteral.swift"; sourceTree = ""; }; + OBJ_76 /* OrderedDictionary+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Hashable.swift"; sourceTree = ""; }; + OBJ_77 /* OrderedDictionary+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Initializers.swift"; sourceTree = ""; }; + OBJ_78 /* OrderedDictionary+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Invariants.swift"; sourceTree = ""; }; + OBJ_79 /* OrderedDictionary+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial MutableCollection.swift"; sourceTree = ""; }; OBJ_8 /* AsyncObjects.xcconfig */ = {isa = PBXFileReference; name = AsyncObjects.xcconfig; path = Helpers/AsyncObjects.xcconfig; sourceTree = ""; }; - OBJ_80 /* OrderedDictionary+Values.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Values.swift"; sourceTree = ""; }; - OBJ_81 /* OrderedDictionary.swift */ = {isa = PBXFileReference; path = OrderedDictionary.swift; sourceTree = ""; }; - OBJ_83 /* OrderedSet+Codable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Codable.swift"; sourceTree = ""; }; - OBJ_84 /* OrderedSet+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomDebugStringConvertible.swift"; sourceTree = ""; }; - OBJ_85 /* OrderedSet+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomReflectable.swift"; sourceTree = ""; }; - OBJ_86 /* OrderedSet+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomStringConvertible.swift"; sourceTree = ""; }; - OBJ_87 /* OrderedSet+Diffing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Diffing.swift"; sourceTree = ""; }; - OBJ_88 /* OrderedSet+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Equatable.swift"; sourceTree = ""; }; - OBJ_89 /* OrderedSet+ExpressibleByArrayLiteral.swift */ = {isa = PBXFileReference; path = "OrderedSet+ExpressibleByArrayLiteral.swift"; sourceTree = ""; }; - OBJ_90 /* OrderedSet+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Hashable.swift"; sourceTree = ""; }; - OBJ_91 /* OrderedSet+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedSet+Initializers.swift"; sourceTree = ""; }; - OBJ_92 /* OrderedSet+Insertions.swift */ = {isa = PBXFileReference; path = "OrderedSet+Insertions.swift"; sourceTree = ""; }; - OBJ_93 /* OrderedSet+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedSet+Invariants.swift"; sourceTree = ""; }; - OBJ_94 /* OrderedSet+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial MutableCollection.swift"; sourceTree = ""; }; - OBJ_95 /* OrderedSet+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; - OBJ_96 /* OrderedSet+Partial SetAlgebra+Basics.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Basics.swift"; sourceTree = ""; }; - OBJ_97 /* OrderedSet+Partial SetAlgebra+Operations.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Operations.swift"; sourceTree = ""; }; - OBJ_98 /* OrderedSet+Partial SetAlgebra+Predicates.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Predicates.swift"; sourceTree = ""; }; - OBJ_99 /* OrderedSet+RandomAccessCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+RandomAccessCollection.swift"; sourceTree = ""; }; + OBJ_80 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; + OBJ_81 /* OrderedDictionary+Sequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Sequence.swift"; sourceTree = ""; }; + OBJ_82 /* OrderedDictionary+Values.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Values.swift"; sourceTree = ""; }; + OBJ_83 /* OrderedDictionary.swift */ = {isa = PBXFileReference; path = OrderedDictionary.swift; sourceTree = ""; }; + OBJ_85 /* OrderedSet+Codable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Codable.swift"; sourceTree = ""; }; + OBJ_86 /* OrderedSet+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomDebugStringConvertible.swift"; sourceTree = ""; }; + OBJ_87 /* OrderedSet+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomReflectable.swift"; sourceTree = ""; }; + OBJ_88 /* OrderedSet+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_89 /* OrderedSet+Diffing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Diffing.swift"; sourceTree = ""; }; + OBJ_90 /* OrderedSet+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Equatable.swift"; sourceTree = ""; }; + OBJ_91 /* OrderedSet+ExpressibleByArrayLiteral.swift */ = {isa = PBXFileReference; path = "OrderedSet+ExpressibleByArrayLiteral.swift"; sourceTree = ""; }; + OBJ_92 /* OrderedSet+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Hashable.swift"; sourceTree = ""; }; + OBJ_93 /* OrderedSet+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedSet+Initializers.swift"; sourceTree = ""; }; + OBJ_94 /* OrderedSet+Insertions.swift */ = {isa = PBXFileReference; path = "OrderedSet+Insertions.swift"; sourceTree = ""; }; + OBJ_95 /* OrderedSet+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedSet+Invariants.swift"; sourceTree = ""; }; + OBJ_96 /* OrderedSet+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial MutableCollection.swift"; sourceTree = ""; }; + OBJ_97 /* OrderedSet+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; + OBJ_98 /* OrderedSet+Partial SetAlgebra+Basics.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Basics.swift"; sourceTree = ""; }; + OBJ_99 /* OrderedSet+Partial SetAlgebra+Operations.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Operations.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_139 /* Frameworks */ = { + OBJ_142 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( - OBJ_140 /* OrderedCollections.framework in Frameworks */, + OBJ_143 /* OrderedCollections.framework in Frameworks */, ); }; - OBJ_172 /* Frameworks */ = { + OBJ_175 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( - OBJ_173 /* AsyncObjects.framework in Frameworks */, - OBJ_174 /* OrderedCollections.framework in Frameworks */, + OBJ_176 /* AsyncObjects.framework in Frameworks */, + OBJ_177 /* OrderedCollections.framework in Frameworks */, ); }; - OBJ_231 /* Frameworks */ = { + OBJ_234 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( ); @@ -239,26 +241,27 @@ OBJ_25 /* Extensions */, OBJ_28 /* Future.swift */, OBJ_29 /* Locks */, - OBJ_32 /* TaskOperation.swift */, - OBJ_33 /* TaskQueue.swift */, - OBJ_34 /* TaskTracker.swift */, - AFECD0135D28B7BE3712A6C7 /* AsyncObjects.docc */, + OBJ_32 /* Logging */, + OBJ_34 /* TaskOperation.swift */, + OBJ_35 /* TaskQueue.swift */, + OBJ_36 /* TaskTracker.swift */, + ED492FF628B26125C8C62E41 /* AsyncObjects.docc */, ); name = AsyncObjects; path = Sources/AsyncObjects; sourceTree = SOURCE_ROOT; }; - OBJ_106 /* Utilities */ = { + OBJ_108 /* Utilities */ = { isa = PBXGroup; children = ( - OBJ_107 /* RandomAccessCollection+Offsets.swift */, - OBJ_108 /* _UnsafeBitset.swift */, + OBJ_109 /* RandomAccessCollection+Offsets.swift */, + OBJ_110 /* _UnsafeBitset.swift */, ); name = Utilities; path = Utilities; sourceTree = ""; }; - OBJ_110 /* Products */ = { + OBJ_112 /* Products */ = { isa = PBXGroup; children = ( asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */, @@ -286,8 +289,8 @@ OBJ_20 /* Continuable.swift */, OBJ_21 /* ContinuableCollection.swift */, OBJ_22 /* GlobalContinuation.swift */, - OBJ_23 /* SafeContinuation.swift */, - OBJ_24 /* SynchronizedContinuable.swift */, + OBJ_23 /* TrackableContinuable.swift */, + OBJ_24 /* TrackedContinuation.swift */, ); name = Continuation; path = Continuation; @@ -313,31 +316,40 @@ path = Locks; sourceTree = ""; }; - OBJ_35 /* Tests */ = { + OBJ_32 /* Logging */ = { isa = PBXGroup; children = ( - OBJ_36 /* AsyncObjectsTests */, + OBJ_33 /* Loggable.swift */, + ); + name = Logging; + path = Logging; + sourceTree = ""; + }; + OBJ_37 /* Tests */ = { + isa = PBXGroup; + children = ( + OBJ_38 /* AsyncObjectsTests */, ); name = Tests; path = ""; sourceTree = SOURCE_ROOT; }; - OBJ_36 /* AsyncObjectsTests */ = { + OBJ_38 /* AsyncObjectsTests */ = { isa = PBXGroup; children = ( - OBJ_37 /* AsyncCountdownEventTests.swift */, - OBJ_38 /* AsyncEventTests.swift */, - OBJ_39 /* AsyncObjectTests.swift */, - OBJ_40 /* AsyncSemaphoreTests.swift */, - OBJ_41 /* CancellationSourceTests.swift */, - OBJ_42 /* LockerTests.swift */, - OBJ_43 /* NonThrowingFutureTests.swift */, - OBJ_44 /* SafeContinuationTests.swift */, - OBJ_45 /* StandardLibraryTests.swift */, - OBJ_46 /* TaskOperationTests.swift */, - OBJ_47 /* TaskQueueTests.swift */, - OBJ_48 /* ThrowingFutureTests.swift */, - OBJ_49 /* XCTestCase.swift */, + OBJ_39 /* AsyncCountdownEventTests.swift */, + OBJ_40 /* AsyncEventTests.swift */, + OBJ_41 /* AsyncObjectTests.swift */, + OBJ_42 /* AsyncSemaphoreTests.swift */, + OBJ_43 /* CancellationSourceTests.swift */, + OBJ_44 /* LockerTests.swift */, + OBJ_45 /* NonThrowingFutureTests.swift */, + OBJ_46 /* StandardLibraryTests.swift */, + OBJ_47 /* TaskOperationTests.swift */, + OBJ_48 /* TaskQueueTests.swift */, + OBJ_49 /* ThrowingFutureTests.swift */, + OBJ_50 /* TrackedContinuationTests.swift */, + OBJ_51 /* XCTestCase.swift */, ); name = AsyncObjectsTests; path = Tests/AsyncObjectsTests; @@ -349,35 +361,35 @@ OBJ_6 /* Package.swift */, OBJ_7 /* Configs */, OBJ_9 /* Sources */, - OBJ_35 /* Tests */, - OBJ_50 /* Dependencies */, - OBJ_110 /* Products */, + OBJ_37 /* Tests */, + OBJ_52 /* Dependencies */, + OBJ_112 /* Products */, ); path = ""; sourceTree = ""; }; - OBJ_50 /* Dependencies */ = { + OBJ_52 /* Dependencies */ = { isa = PBXGroup; children = ( - OBJ_51 /* swift-collections 1.0.3 */, + OBJ_53 /* swift-collections 1.0.3 */, ); name = Dependencies; path = ""; sourceTree = ""; }; - OBJ_51 /* swift-collections 1.0.3 */ = { + OBJ_53 /* swift-collections 1.0.3 */ = { isa = PBXGroup; children = ( - OBJ_52 /* Collections */, - OBJ_53 /* DequeModule */, - OBJ_54 /* OrderedCollections */, - OBJ_109 /* Package.swift */, + OBJ_54 /* Collections */, + OBJ_55 /* DequeModule */, + OBJ_56 /* OrderedCollections */, + OBJ_111 /* Package.swift */, ); name = "swift-collections 1.0.3"; path = ""; sourceTree = SOURCE_ROOT; }; - OBJ_52 /* Collections */ = { + OBJ_54 /* Collections */ = { isa = PBXGroup; children = ( ); @@ -385,7 +397,7 @@ path = ".build/checkouts/swift-collections/Sources/Collections"; sourceTree = SOURCE_ROOT; }; - OBJ_53 /* DequeModule */ = { + OBJ_55 /* DequeModule */ = { isa = PBXGroup; children = ( ); @@ -393,54 +405,54 @@ path = ".build/checkouts/swift-collections/Sources/DequeModule"; sourceTree = SOURCE_ROOT; }; - OBJ_54 /* OrderedCollections */ = { + OBJ_56 /* OrderedCollections */ = { isa = PBXGroup; children = ( - OBJ_55 /* HashTable */, - OBJ_64 /* OrderedDictionary */, - OBJ_82 /* OrderedSet */, - OBJ_106 /* Utilities */, + OBJ_57 /* HashTable */, + OBJ_66 /* OrderedDictionary */, + OBJ_84 /* OrderedSet */, + OBJ_108 /* Utilities */, ); name = OrderedCollections; path = ".build/checkouts/swift-collections/Sources/OrderedCollections"; sourceTree = SOURCE_ROOT; }; - OBJ_55 /* HashTable */ = { + OBJ_57 /* HashTable */ = { isa = PBXGroup; children = ( - OBJ_56 /* _HashTable+Bucket.swift */, - OBJ_57 /* _HashTable+BucketIterator.swift */, - OBJ_58 /* _HashTable+Constants.swift */, - OBJ_59 /* _HashTable+CustomStringConvertible.swift */, - OBJ_60 /* _HashTable+Testing.swift */, - OBJ_61 /* _HashTable+UnsafeHandle.swift */, - OBJ_62 /* _HashTable.swift */, - OBJ_63 /* _Hashtable+Header.swift */, + OBJ_58 /* _HashTable+Bucket.swift */, + OBJ_59 /* _HashTable+BucketIterator.swift */, + OBJ_60 /* _HashTable+Constants.swift */, + OBJ_61 /* _HashTable+CustomStringConvertible.swift */, + OBJ_62 /* _HashTable+Testing.swift */, + OBJ_63 /* _HashTable+UnsafeHandle.swift */, + OBJ_64 /* _HashTable.swift */, + OBJ_65 /* _Hashtable+Header.swift */, ); name = HashTable; path = HashTable; sourceTree = ""; }; - OBJ_64 /* OrderedDictionary */ = { + OBJ_66 /* OrderedDictionary */ = { isa = PBXGroup; children = ( - OBJ_65 /* OrderedDictionary+Codable.swift */, - OBJ_66 /* OrderedDictionary+CustomDebugStringConvertible.swift */, - OBJ_67 /* OrderedDictionary+CustomReflectable.swift */, - OBJ_68 /* OrderedDictionary+CustomStringConvertible.swift */, - OBJ_69 /* OrderedDictionary+Deprecations.swift */, - OBJ_70 /* OrderedDictionary+Elements+SubSequence.swift */, - OBJ_71 /* OrderedDictionary+Elements.swift */, - OBJ_72 /* OrderedDictionary+Equatable.swift */, - OBJ_73 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */, - OBJ_74 /* OrderedDictionary+Hashable.swift */, - OBJ_75 /* OrderedDictionary+Initializers.swift */, - OBJ_76 /* OrderedDictionary+Invariants.swift */, - OBJ_77 /* OrderedDictionary+Partial MutableCollection.swift */, - OBJ_78 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */, - OBJ_79 /* OrderedDictionary+Sequence.swift */, - OBJ_80 /* OrderedDictionary+Values.swift */, - OBJ_81 /* OrderedDictionary.swift */, + OBJ_67 /* OrderedDictionary+Codable.swift */, + OBJ_68 /* OrderedDictionary+CustomDebugStringConvertible.swift */, + OBJ_69 /* OrderedDictionary+CustomReflectable.swift */, + OBJ_70 /* OrderedDictionary+CustomStringConvertible.swift */, + OBJ_71 /* OrderedDictionary+Deprecations.swift */, + OBJ_72 /* OrderedDictionary+Elements+SubSequence.swift */, + OBJ_73 /* OrderedDictionary+Elements.swift */, + OBJ_74 /* OrderedDictionary+Equatable.swift */, + OBJ_75 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */, + OBJ_76 /* OrderedDictionary+Hashable.swift */, + OBJ_77 /* OrderedDictionary+Initializers.swift */, + OBJ_78 /* OrderedDictionary+Invariants.swift */, + OBJ_79 /* OrderedDictionary+Partial MutableCollection.swift */, + OBJ_80 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */, + OBJ_81 /* OrderedDictionary+Sequence.swift */, + OBJ_82 /* OrderedDictionary+Values.swift */, + OBJ_83 /* OrderedDictionary.swift */, ); name = OrderedDictionary; path = OrderedDictionary; @@ -455,32 +467,32 @@ path = ""; sourceTree = ""; }; - OBJ_82 /* OrderedSet */ = { + OBJ_84 /* OrderedSet */ = { isa = PBXGroup; children = ( - OBJ_83 /* OrderedSet+Codable.swift */, - OBJ_84 /* OrderedSet+CustomDebugStringConvertible.swift */, - OBJ_85 /* OrderedSet+CustomReflectable.swift */, - OBJ_86 /* OrderedSet+CustomStringConvertible.swift */, - OBJ_87 /* OrderedSet+Diffing.swift */, - OBJ_88 /* OrderedSet+Equatable.swift */, - OBJ_89 /* OrderedSet+ExpressibleByArrayLiteral.swift */, - OBJ_90 /* OrderedSet+Hashable.swift */, - OBJ_91 /* OrderedSet+Initializers.swift */, - OBJ_92 /* OrderedSet+Insertions.swift */, - OBJ_93 /* OrderedSet+Invariants.swift */, - OBJ_94 /* OrderedSet+Partial MutableCollection.swift */, - OBJ_95 /* OrderedSet+Partial RangeReplaceableCollection.swift */, - OBJ_96 /* OrderedSet+Partial SetAlgebra+Basics.swift */, - OBJ_97 /* OrderedSet+Partial SetAlgebra+Operations.swift */, - OBJ_98 /* OrderedSet+Partial SetAlgebra+Predicates.swift */, - OBJ_99 /* OrderedSet+RandomAccessCollection.swift */, - OBJ_100 /* OrderedSet+ReserveCapacity.swift */, - OBJ_101 /* OrderedSet+SubSequence.swift */, - OBJ_102 /* OrderedSet+Testing.swift */, - OBJ_103 /* OrderedSet+UnorderedView.swift */, - OBJ_104 /* OrderedSet+UnstableInternals.swift */, - OBJ_105 /* OrderedSet.swift */, + OBJ_85 /* OrderedSet+Codable.swift */, + OBJ_86 /* OrderedSet+CustomDebugStringConvertible.swift */, + OBJ_87 /* OrderedSet+CustomReflectable.swift */, + OBJ_88 /* OrderedSet+CustomStringConvertible.swift */, + OBJ_89 /* OrderedSet+Diffing.swift */, + OBJ_90 /* OrderedSet+Equatable.swift */, + OBJ_91 /* OrderedSet+ExpressibleByArrayLiteral.swift */, + OBJ_92 /* OrderedSet+Hashable.swift */, + OBJ_93 /* OrderedSet+Initializers.swift */, + OBJ_94 /* OrderedSet+Insertions.swift */, + OBJ_95 /* OrderedSet+Invariants.swift */, + OBJ_96 /* OrderedSet+Partial MutableCollection.swift */, + OBJ_97 /* OrderedSet+Partial RangeReplaceableCollection.swift */, + OBJ_98 /* OrderedSet+Partial SetAlgebra+Basics.swift */, + OBJ_99 /* OrderedSet+Partial SetAlgebra+Operations.swift */, + OBJ_100 /* OrderedSet+Partial SetAlgebra+Predicates.swift */, + OBJ_101 /* OrderedSet+RandomAccessCollection.swift */, + OBJ_102 /* OrderedSet+ReserveCapacity.swift */, + OBJ_103 /* OrderedSet+SubSequence.swift */, + OBJ_104 /* OrderedSet+Testing.swift */, + OBJ_105 /* OrderedSet+UnorderedView.swift */, + OBJ_106 /* OrderedSet+UnstableInternals.swift */, + OBJ_107 /* OrderedSet.swift */, ); name = OrderedSet; path = OrderedSet; @@ -500,15 +512,15 @@ /* Begin PBXNativeTarget section */ asyncobjects::AsyncObjects /* AsyncObjects */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_115 /* Build configuration list for PBXNativeTarget "AsyncObjects" */; + buildConfigurationList = OBJ_117 /* Build configuration list for PBXNativeTarget "AsyncObjects" */; buildPhases = ( - OBJ_118 /* Sources */, - OBJ_139 /* Frameworks */, + OBJ_120 /* Sources */, + OBJ_142 /* Frameworks */, ); buildRules = ( ); dependencies = ( - OBJ_141 /* PBXTargetDependency */, + OBJ_144 /* PBXTargetDependency */, ); name = AsyncObjects; productName = AsyncObjects; @@ -517,16 +529,16 @@ }; asyncobjects::AsyncObjectsTests /* AsyncObjectsTests */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_155 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */; + buildConfigurationList = OBJ_158 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */; buildPhases = ( - OBJ_158 /* Sources */, - OBJ_172 /* Frameworks */, + OBJ_161 /* Sources */, + OBJ_175 /* Frameworks */, ); buildRules = ( ); dependencies = ( - OBJ_175 /* PBXTargetDependency */, - OBJ_176 /* PBXTargetDependency */, + OBJ_178 /* PBXTargetDependency */, + OBJ_179 /* PBXTargetDependency */, ); name = AsyncObjectsTests; productName = AsyncObjectsTests; @@ -535,9 +547,9 @@ }; asyncobjects::SwiftPMPackageDescription /* AsyncObjectsPackageDescription */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_144 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */; + buildConfigurationList = OBJ_147 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */; buildPhases = ( - OBJ_147 /* Sources */, + OBJ_150 /* Sources */, ); buildRules = ( ); @@ -549,10 +561,10 @@ }; swift-collections::OrderedCollections /* OrderedCollections */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_177 /* Build configuration list for PBXNativeTarget "OrderedCollections" */; + buildConfigurationList = OBJ_180 /* Build configuration list for PBXNativeTarget "OrderedCollections" */; buildPhases = ( - OBJ_180 /* Sources */, - OBJ_231 /* Frameworks */, + OBJ_183 /* Sources */, + OBJ_234 /* Frameworks */, ); buildRules = ( ); @@ -565,9 +577,9 @@ }; swift-collections::SwiftPMPackageDescription /* swift-collectionsPackageDescription */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_233 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */; + buildConfigurationList = OBJ_236 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */; buildPhases = ( - OBJ_236 /* Sources */, + OBJ_239 /* Sources */, ); buildRules = ( ); @@ -594,7 +606,7 @@ en, ); mainGroup = OBJ_5; - productRefGroup = OBJ_110 /* Products */; + productRefGroup = OBJ_112 /* Products */; projectDirPath = .; targets = ( asyncobjects::AsyncObjects /* AsyncObjects */, @@ -608,140 +620,141 @@ /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ - OBJ_118 /* Sources */ = { + OBJ_120 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_119 /* AsyncCountdownEvent.swift in Sources */, - OBJ_120 /* AsyncEvent.swift in Sources */, - OBJ_121 /* AsyncSemaphore.swift in Sources */, - OBJ_122 /* AsyncObject+Clock.swift in Sources */, - OBJ_123 /* AsyncObject+Duration.swift in Sources */, - OBJ_124 /* AsyncObject.swift in Sources */, - OBJ_125 /* CancellationSource.swift in Sources */, - OBJ_126 /* Continuable.swift in Sources */, - OBJ_127 /* ContinuableCollection.swift in Sources */, - OBJ_128 /* GlobalContinuation.swift in Sources */, - OBJ_129 /* SafeContinuation.swift in Sources */, - OBJ_130 /* SynchronizedContinuable.swift in Sources */, - OBJ_131 /* Task.swift in Sources */, - OBJ_132 /* TaskGroup.swift in Sources */, - OBJ_133 /* Future.swift in Sources */, - OBJ_134 /* Exclusible.swift in Sources */, - OBJ_135 /* Locker.swift in Sources */, - OBJ_136 /* TaskOperation.swift in Sources */, - OBJ_137 /* TaskQueue.swift in Sources */, - OBJ_138 /* TaskTracker.swift in Sources */, - 8BB988CB9A701C7EE03686DC /* AsyncObjects.docc in Sources */, + OBJ_121 /* AsyncCountdownEvent.swift in Sources */, + OBJ_122 /* AsyncEvent.swift in Sources */, + OBJ_123 /* AsyncSemaphore.swift in Sources */, + OBJ_124 /* AsyncObject+Clock.swift in Sources */, + OBJ_125 /* AsyncObject+Duration.swift in Sources */, + OBJ_126 /* AsyncObject.swift in Sources */, + OBJ_127 /* CancellationSource.swift in Sources */, + OBJ_128 /* Continuable.swift in Sources */, + OBJ_129 /* ContinuableCollection.swift in Sources */, + OBJ_130 /* GlobalContinuation.swift in Sources */, + OBJ_131 /* TrackableContinuable.swift in Sources */, + OBJ_132 /* TrackedContinuation.swift in Sources */, + OBJ_133 /* Task.swift in Sources */, + OBJ_134 /* TaskGroup.swift in Sources */, + OBJ_135 /* Future.swift in Sources */, + OBJ_136 /* Exclusible.swift in Sources */, + OBJ_137 /* Locker.swift in Sources */, + OBJ_138 /* Loggable.swift in Sources */, + OBJ_139 /* TaskOperation.swift in Sources */, + OBJ_140 /* TaskQueue.swift in Sources */, + OBJ_141 /* TaskTracker.swift in Sources */, + BB028CDED6BCD928C0AAF339 /* AsyncObjects.docc in Sources */, ); }; - OBJ_147 /* Sources */ = { + OBJ_150 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_148 /* Package.swift in Sources */, + OBJ_151 /* Package.swift in Sources */, ); }; - OBJ_158 /* Sources */ = { + OBJ_161 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_159 /* AsyncCountdownEventTests.swift in Sources */, - OBJ_160 /* AsyncEventTests.swift in Sources */, - OBJ_161 /* AsyncObjectTests.swift in Sources */, - OBJ_162 /* AsyncSemaphoreTests.swift in Sources */, - OBJ_163 /* CancellationSourceTests.swift in Sources */, - OBJ_164 /* LockerTests.swift in Sources */, - OBJ_165 /* NonThrowingFutureTests.swift in Sources */, - OBJ_166 /* SafeContinuationTests.swift in Sources */, - OBJ_167 /* StandardLibraryTests.swift in Sources */, - OBJ_168 /* TaskOperationTests.swift in Sources */, - OBJ_169 /* TaskQueueTests.swift in Sources */, - OBJ_170 /* ThrowingFutureTests.swift in Sources */, - OBJ_171 /* XCTestCase.swift in Sources */, + OBJ_162 /* AsyncCountdownEventTests.swift in Sources */, + OBJ_163 /* AsyncEventTests.swift in Sources */, + OBJ_164 /* AsyncObjectTests.swift in Sources */, + OBJ_165 /* AsyncSemaphoreTests.swift in Sources */, + OBJ_166 /* CancellationSourceTests.swift in Sources */, + OBJ_167 /* LockerTests.swift in Sources */, + OBJ_168 /* NonThrowingFutureTests.swift in Sources */, + OBJ_169 /* StandardLibraryTests.swift in Sources */, + OBJ_170 /* TaskOperationTests.swift in Sources */, + OBJ_171 /* TaskQueueTests.swift in Sources */, + OBJ_172 /* ThrowingFutureTests.swift in Sources */, + OBJ_173 /* TrackedContinuationTests.swift in Sources */, + OBJ_174 /* XCTestCase.swift in Sources */, ); }; - OBJ_180 /* Sources */ = { + OBJ_183 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_181 /* _HashTable+Bucket.swift in Sources */, - OBJ_182 /* _HashTable+BucketIterator.swift in Sources */, - OBJ_183 /* _HashTable+Constants.swift in Sources */, - OBJ_184 /* _HashTable+CustomStringConvertible.swift in Sources */, - OBJ_185 /* _HashTable+Testing.swift in Sources */, - OBJ_186 /* _HashTable+UnsafeHandle.swift in Sources */, - OBJ_187 /* _HashTable.swift in Sources */, - OBJ_188 /* _Hashtable+Header.swift in Sources */, - OBJ_189 /* OrderedDictionary+Codable.swift in Sources */, - OBJ_190 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */, - OBJ_191 /* OrderedDictionary+CustomReflectable.swift in Sources */, - OBJ_192 /* OrderedDictionary+CustomStringConvertible.swift in Sources */, - OBJ_193 /* OrderedDictionary+Deprecations.swift in Sources */, - OBJ_194 /* OrderedDictionary+Elements+SubSequence.swift in Sources */, - OBJ_195 /* OrderedDictionary+Elements.swift in Sources */, - OBJ_196 /* OrderedDictionary+Equatable.swift in Sources */, - OBJ_197 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */, - OBJ_198 /* OrderedDictionary+Hashable.swift in Sources */, - OBJ_199 /* OrderedDictionary+Initializers.swift in Sources */, - OBJ_200 /* OrderedDictionary+Invariants.swift in Sources */, - OBJ_201 /* OrderedDictionary+Partial MutableCollection.swift in Sources */, - OBJ_202 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */, - OBJ_203 /* OrderedDictionary+Sequence.swift in Sources */, - OBJ_204 /* OrderedDictionary+Values.swift in Sources */, - OBJ_205 /* OrderedDictionary.swift in Sources */, - OBJ_206 /* OrderedSet+Codable.swift in Sources */, - OBJ_207 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */, - OBJ_208 /* OrderedSet+CustomReflectable.swift in Sources */, - OBJ_209 /* OrderedSet+CustomStringConvertible.swift in Sources */, - OBJ_210 /* OrderedSet+Diffing.swift in Sources */, - OBJ_211 /* OrderedSet+Equatable.swift in Sources */, - OBJ_212 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */, - OBJ_213 /* OrderedSet+Hashable.swift in Sources */, - OBJ_214 /* OrderedSet+Initializers.swift in Sources */, - OBJ_215 /* OrderedSet+Insertions.swift in Sources */, - OBJ_216 /* OrderedSet+Invariants.swift in Sources */, - OBJ_217 /* OrderedSet+Partial MutableCollection.swift in Sources */, - OBJ_218 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */, - OBJ_219 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */, - OBJ_220 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */, - OBJ_221 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */, - OBJ_222 /* OrderedSet+RandomAccessCollection.swift in Sources */, - OBJ_223 /* OrderedSet+ReserveCapacity.swift in Sources */, - OBJ_224 /* OrderedSet+SubSequence.swift in Sources */, - OBJ_225 /* OrderedSet+Testing.swift in Sources */, - OBJ_226 /* OrderedSet+UnorderedView.swift in Sources */, - OBJ_227 /* OrderedSet+UnstableInternals.swift in Sources */, - OBJ_228 /* OrderedSet.swift in Sources */, - OBJ_229 /* RandomAccessCollection+Offsets.swift in Sources */, - OBJ_230 /* _UnsafeBitset.swift in Sources */, + OBJ_184 /* _HashTable+Bucket.swift in Sources */, + OBJ_185 /* _HashTable+BucketIterator.swift in Sources */, + OBJ_186 /* _HashTable+Constants.swift in Sources */, + OBJ_187 /* _HashTable+CustomStringConvertible.swift in Sources */, + OBJ_188 /* _HashTable+Testing.swift in Sources */, + OBJ_189 /* _HashTable+UnsafeHandle.swift in Sources */, + OBJ_190 /* _HashTable.swift in Sources */, + OBJ_191 /* _Hashtable+Header.swift in Sources */, + OBJ_192 /* OrderedDictionary+Codable.swift in Sources */, + OBJ_193 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */, + OBJ_194 /* OrderedDictionary+CustomReflectable.swift in Sources */, + OBJ_195 /* OrderedDictionary+CustomStringConvertible.swift in Sources */, + OBJ_196 /* OrderedDictionary+Deprecations.swift in Sources */, + OBJ_197 /* OrderedDictionary+Elements+SubSequence.swift in Sources */, + OBJ_198 /* OrderedDictionary+Elements.swift in Sources */, + OBJ_199 /* OrderedDictionary+Equatable.swift in Sources */, + OBJ_200 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */, + OBJ_201 /* OrderedDictionary+Hashable.swift in Sources */, + OBJ_202 /* OrderedDictionary+Initializers.swift in Sources */, + OBJ_203 /* OrderedDictionary+Invariants.swift in Sources */, + OBJ_204 /* OrderedDictionary+Partial MutableCollection.swift in Sources */, + OBJ_205 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */, + OBJ_206 /* OrderedDictionary+Sequence.swift in Sources */, + OBJ_207 /* OrderedDictionary+Values.swift in Sources */, + OBJ_208 /* OrderedDictionary.swift in Sources */, + OBJ_209 /* OrderedSet+Codable.swift in Sources */, + OBJ_210 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */, + OBJ_211 /* OrderedSet+CustomReflectable.swift in Sources */, + OBJ_212 /* OrderedSet+CustomStringConvertible.swift in Sources */, + OBJ_213 /* OrderedSet+Diffing.swift in Sources */, + OBJ_214 /* OrderedSet+Equatable.swift in Sources */, + OBJ_215 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */, + OBJ_216 /* OrderedSet+Hashable.swift in Sources */, + OBJ_217 /* OrderedSet+Initializers.swift in Sources */, + OBJ_218 /* OrderedSet+Insertions.swift in Sources */, + OBJ_219 /* OrderedSet+Invariants.swift in Sources */, + OBJ_220 /* OrderedSet+Partial MutableCollection.swift in Sources */, + OBJ_221 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */, + OBJ_222 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */, + OBJ_223 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */, + OBJ_224 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */, + OBJ_225 /* OrderedSet+RandomAccessCollection.swift in Sources */, + OBJ_226 /* OrderedSet+ReserveCapacity.swift in Sources */, + OBJ_227 /* OrderedSet+SubSequence.swift in Sources */, + OBJ_228 /* OrderedSet+Testing.swift in Sources */, + OBJ_229 /* OrderedSet+UnorderedView.swift in Sources */, + OBJ_230 /* OrderedSet+UnstableInternals.swift in Sources */, + OBJ_231 /* OrderedSet.swift in Sources */, + OBJ_232 /* RandomAccessCollection+Offsets.swift in Sources */, + OBJ_233 /* _UnsafeBitset.swift in Sources */, ); }; - OBJ_236 /* Sources */ = { + OBJ_239 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_237 /* Package.swift in Sources */, + OBJ_240 /* Package.swift in Sources */, ); }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - OBJ_141 /* PBXTargetDependency */ = { + OBJ_144 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = swift-collections::OrderedCollections /* OrderedCollections */; }; - OBJ_153 /* PBXTargetDependency */ = { + OBJ_156 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = asyncobjects::AsyncObjectsTests /* AsyncObjectsTests */; }; - OBJ_175 /* PBXTargetDependency */ = { + OBJ_178 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = asyncobjects::AsyncObjects /* AsyncObjects */; }; - OBJ_176 /* PBXTargetDependency */ = { + OBJ_179 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = swift-collections::OrderedCollections /* OrderedCollections */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - OBJ_116 /* Debug */ = { + OBJ_118 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -772,7 +785,7 @@ }; name = Debug; }; - OBJ_117 /* Release */ = { + OBJ_119 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -803,7 +816,7 @@ }; name = Release; }; - OBJ_145 /* Debug */ = { + OBJ_148 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -812,7 +825,7 @@ }; name = Debug; }; - OBJ_146 /* Release */ = { + OBJ_149 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -821,19 +834,19 @@ }; name = Release; }; - OBJ_151 /* Debug */ = { + OBJ_154 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Debug; }; - OBJ_152 /* Release */ = { + OBJ_155 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Release; }; - OBJ_156 /* Debug */ = { + OBJ_159 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -861,7 +874,7 @@ }; name = Debug; }; - OBJ_157 /* Release */ = { + OBJ_160 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -889,7 +902,7 @@ }; name = Release; }; - OBJ_178 /* Debug */ = { + OBJ_181 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -920,7 +933,7 @@ }; name = Debug; }; - OBJ_179 /* Release */ = { + OBJ_182 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -951,7 +964,7 @@ }; name = Release; }; - OBJ_234 /* Debug */ = { + OBJ_237 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -960,7 +973,7 @@ }; name = Debug; }; - OBJ_235 /* Release */ = { + OBJ_238 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -1025,47 +1038,47 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - OBJ_115 /* Build configuration list for PBXNativeTarget "AsyncObjects" */ = { + OBJ_117 /* Build configuration list for PBXNativeTarget "AsyncObjects" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_116 /* Debug */, - OBJ_117 /* Release */, + OBJ_118 /* Debug */, + OBJ_119 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_144 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */ = { + OBJ_147 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_145 /* Debug */, - OBJ_146 /* Release */, + OBJ_148 /* Debug */, + OBJ_149 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_150 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */ = { + OBJ_153 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_151 /* Debug */, - OBJ_152 /* Release */, + OBJ_154 /* Debug */, + OBJ_155 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_155 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */ = { + OBJ_158 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_156 /* Debug */, - OBJ_157 /* Release */, + OBJ_159 /* Debug */, + OBJ_160 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_177 /* Build configuration list for PBXNativeTarget "OrderedCollections" */ = { + OBJ_180 /* Build configuration list for PBXNativeTarget "OrderedCollections" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_178 /* Debug */, - OBJ_179 /* Release */, + OBJ_181 /* Debug */, + OBJ_182 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -1079,11 +1092,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_233 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */ = { + OBJ_236 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_234 /* Debug */, - OBJ_235 /* Release */, + OBJ_237 /* Debug */, + OBJ_238 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Package.swift b/Package.swift index e98b3e41..b4669e8c 100644 --- a/Package.swift +++ b/Package.swift @@ -3,97 +3,74 @@ import PackageDescription import class Foundation.ProcessInfo -let appleGitHub = "https://github.com/apple" -let package = Package( - name: "AsyncObjects", - platforms: [ - .macOS(.v10_15), - .iOS(.v13), - .tvOS(.v13), - .watchOS(.v6), - ], - products: [ - .library( - name: "AsyncObjects", - targets: ["AsyncObjects"] - ), - ], - 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.50700.0"), - ], - targets: [ - .target( - name: "AsyncObjects", - dependencies: [ - .product( - name: "OrderedCollections", - package: "swift-collections" - ), - ], - swiftSettings: swiftSettings - ), - .testTarget( - name: "AsyncObjectsTests", - dependencies: ["AsyncObjects"], - swiftSettings: testingSwiftSettings - ), +var dependencies: [Target.Dependency] = { + var dependencies: [Target.Dependency] = [ + .product(name: "OrderedCollections", package: "swift-collections") ] -) -var swiftSettings: [SwiftSetting] = { - var swiftSettings: [SwiftSetting] = [] + if ProcessInfo.processInfo.environment["ASYNCOBJECTS_ENABLE_LOGGING_LEVEL"] != nil { + dependencies.append(.product(name: "Logging", package: "swift-log")) + } + + return dependencies +}() + +var settings: [SwiftSetting] = { + var settings: [SwiftSetting] = [] - if ProcessInfo.processInfo.environment[ - "SWIFTCI_CONCURRENCY_CHECKS" - ] != nil { - swiftSettings.append( + if ProcessInfo.processInfo.environment["SWIFTCI_CONCURRENCY_CHECKS"] != nil { + settings.append( .unsafeFlags([ "-Xfrontend", "-warn-concurrency", "-enable-actor-data-race-checks", "-require-explicit-sendable", + "-strict-concurrency=complete" ]) ) } - if ProcessInfo.processInfo.environment[ - "SWIFTCI_WARNINGS_AS_ERRORS" - ] != nil { - swiftSettings.append( - .unsafeFlags([ - "-warnings-as-errors" - ]) - ) + if ProcessInfo.processInfo.environment["SWIFTCI_WARNINGS_AS_ERRORS"] != nil { + settings.append(.unsafeFlags(["-warnings-as-errors"])) } - if ProcessInfo.processInfo.environment[ - "ASYNCOBJECTS_USE_CHECKEDCONTINUATION" - ] != nil { - swiftSettings.append( - .define("ASYNCOBJECTS_USE_CHECKEDCONTINUATION") - ) + if ProcessInfo.processInfo.environment["ASYNCOBJECTS_USE_CHECKEDCONTINUATION"] != nil { + settings.append(.define("ASYNCOBJECTS_USE_CHECKEDCONTINUATION")) } - return swiftSettings -}() - -var testingSwiftSettings: [SwiftSetting] = { - var swiftSettings: [SwiftSetting] = [] - - if ProcessInfo.processInfo.environment[ - "SWIFTCI_CONCURRENCY_CHECKS" - ] != nil { - swiftSettings.append( - .unsafeFlags([ - "-Xfrontend", - "-warn-concurrency", - "-enable-actor-data-race-checks", - "-require-explicit-sendable", - ]) - ) + if let level = ProcessInfo.processInfo.environment["ASYNCOBJECTS_ENABLE_LOGGING_LEVEL"] { + if level.caseInsensitiveCompare("TRACE") == .orderedSame { + settings.append(.define("ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE")) + } else if level.caseInsensitiveCompare("DEBUG") == .orderedSame { + settings.append(.define("ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG")) + } else { + settings.append(.define("ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_INFO")) + } } - return swiftSettings + return settings }() + +let appleGitHub = "https://github.com/apple" +let package = Package( + name: "AsyncObjects", + platforms: [ + .macOS(.v10_15), + .iOS(.v13), + .tvOS(.v13), + .watchOS(.v6), + ], + products: [ + .library(name: "AsyncObjects", targets: ["AsyncObjects"]), + ], + 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.50700.0"), + .package(url: "\(appleGitHub)/swift-log.git", from: "1.0.0"), + ], + targets: [ + .target(name: "AsyncObjects", dependencies: dependencies, swiftSettings: settings), + .testTarget(name: "AsyncObjectsTests", dependencies: ["AsyncObjects"]), + ] +) diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index 0de1058b..456cf0df 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -37,15 +37,15 @@ import OrderedCollections /// /// 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, ContinuableCollection { +public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection, + LoggableActor +{ /// The suspended tasks continuation type. @usableFromInline - internal typealias Continuation = SafeContinuation< + internal typealias Continuation = TrackedContinuation< GlobalContinuation > - /// The platform dependent lock used to synchronize continuations tracking. - @usableFromInline - internal let locker: Locker = .init() + /// The continuations stored with an associated key for all the suspended task that are waiting to be resumed. @usableFromInline internal private(set) var continuations: @@ -95,67 +95,166 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// - Parameters: /// - continuation: The `continuation` to add. /// - key: The key in the map. + /// - file: The file add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function add request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// - preinit: The pre-initialization handler to run + /// in the beginning of this method. + /// + /// - Important: The pre-initialization handler must run + /// before any logic in this method. @inlinable internal func addContinuation( _ continuation: Continuation, - withKey key: UUID + withKey key: UUID, + file: String, function: String, line: UInt, + preinit: @Sendable () -> Void ) { - guard !continuation.resumed else { return } - guard shouldWait() else { resumeContinuation(continuation); return } + preinit() + log("Adding", id: key, file: file, function: function, line: line) + guard !continuation.resumed else { + log( + "Already resumed, not tracking", id: key, + file: file, function: function, line: line + ) + return + } + + guard shouldWait() else { + resumeContinuation(continuation) + log("Resumed", id: key, file: file, function: function, line: line) + return + } + continuations[key] = continuation + log("Tracking", id: key, file: file, function: function, line: line) } /// Remove continuation associated with provided key /// from `continuations` map and resumes with `CancellationError`. /// - /// - Parameter key: The key in the map. + /// - Parameters: + /// - continuation: The continuation to remove and cancel. + /// - key: The key in the map. + /// - file: The file remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function remove request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func removeContinuation(withKey key: UUID) { + internal func removeContinuation( + _ continuation: Continuation, + withKey key: UUID, + file: String, function: String, line: UInt + ) { + log("Removing", id: key, file: file, function: function, line: line) continuations.removeValue(forKey: key) + guard !continuation.resumed else { + log( + "Already resumed, not cancelling", id: key, + file: file, function: function, line: line + ) + return + } + + continuation.cancel() + log("Cancelled", id: key, file: file, function: function, line: line) } /// Decrements countdown count by the provided number. /// - /// - Parameter number: The number to decrement count by. + /// - Parameters: + /// - number: The number to decrement count by. + /// - 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`). @inlinable - internal func decrementCount(by number: UInt = 1) { - defer { resumeContinuations() } - guard currentCount > 0 else { return } + internal func decrementCount( + by number: UInt = 1, + file: String, function: String, line: UInt + ) { + defer { + resumeContinuations(file: file, function: function, line: line) + } + + guard currentCount > 0 else { + log("Least count", file: file, function: function, line: line) + return + } + currentCount -= number + log("Decremented", file: file, function: function, line: line) } /// Resume previously waiting continuations for countdown event. + /// + /// - Parameters: + /// - file: The file resume originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function resume originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line resume originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func resumeContinuations() { + internal func resumeContinuations( + file: String, function: String, line: UInt + ) { while !continuations.isEmpty && isSet { - let (_, continuation) = continuations.removeFirst() + let (key, continuation) = continuations.removeFirst() resumeContinuation(continuation) + log("Resumed", id: key, file: file, function: function, line: line) } } /// Increments the countdown event current count by the specified value. /// - /// - Parameter count: The value by which to increase ``currentCount``. + /// - Parameters: + /// - count: The value by which to increase ``currentCount``. + /// - file: The file increment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function increment originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line increment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func incrementCount(by count: UInt = 1) { + internal func incrementCount( + by count: UInt = 1, + file: String, function: String, line: UInt + ) { self.currentCount += count - } - - /// Resets current count to initial count. - @inlinable - internal func resetCount() { - self.currentCount = initialCount - resumeContinuations() + log("Incremented", file: file, function: function, line: line) } /// Resets initial count and current count to specified value. /// - /// - Parameter count: The new initial count. + /// - 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`). @inlinable - internal func resetCount(to count: UInt) { + internal func resetCount( + to count: UInt?, + file: String, function: String, line: UInt + ) { + defer { + resumeContinuations(file: file, function: function, line: line) + } + + let count = count ?? initialCount initialCount = count self.currentCount = count - resumeContinuations() + log("Reset", file: file, function: function, line: line) } // MARK: Public @@ -178,7 +277,8 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { self.currentCount = initial } - deinit { self.continuations.forEach { $0.value.cancel() } } + // TODO: Explore alternative cleanup for actor + // deinit { self.continuations.forEach { $1.cancel() } } /// Increments the countdown event current count by the specified value. /// @@ -186,34 +286,26 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// count is reflected immediately. Use this to indicate usage of /// resource from high priority tasks. /// - /// - Parameter count: The value by which to increase ``currentCount``. - 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. - /// /// - Parameters: - /// - file: The file reset originates from (there's usually no need to pass it + /// - count: The value by which to increase ``currentCount``. + /// - file: The file increment 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 + /// - function: The function increment 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 + /// - line: The line increment originates from (there's usually no need to pass it /// explicitly as it defaults to `#line`). - public nonisolated func reset( + public nonisolated func increment( + by count: UInt = 1, file: String = #fileID, function: String = #function, line: UInt = #line ) { - Task { await resetCount() } + Task { + await incrementCount( + by: count, + file: file, function: function, line: line + ) + } } /// Resets initial count and current count to specified value. @@ -230,12 +322,17 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// - 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, + to count: UInt? = nil, file: String = #fileID, function: String = #function, line: UInt = #line ) { - Task { await resetCount(to: count) } + Task { + await resetCount( + to: count, + file: file, function: function, line: line + ) + } } /// Registers a signal (decrements) with the countdown event. @@ -255,7 +352,12 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await decrementCount(by: 1) } + Task { + await decrementCount( + by: 1, + file: file, function: function, line: line + ) + } } /// Registers multiple signals (decrements by provided count) with the countdown event. @@ -277,7 +379,12 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await decrementCount(by: count) } + Task { + await decrementCount( + by: count, + file: file, function: function, line: line + ) + } } /// Waits for, or increments, a countdown event. @@ -302,7 +409,35 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) async throws { - guard shouldWait() else { currentCount += 1; return } - try await withPromisedContinuation() + guard shouldWait() else { + currentCount += 1 + log("Acquired", file: file, function: function, line: line) + return + } + + let key = UUID() + log("Waiting", id: key, file: file, function: function, line: line) + try await withPromisedContinuation( + withKey: key, + file: file, function: function, line: line + ) + log("Received", id: key, file: file, function: function, line: line) } } + +#if canImport(Logging) +import Logging + +extension AsyncCountdownEvent { + /// Type specific metadata to attach to all log messages. + @usableFromInline + var metadata: Logger.Metadata { + return [ + "obj": "\(self)(\(Unmanaged.passUnretained(self).toOpaque()))", + "limit": "\(limit)", + "current_count": "\(currentCount)", + "initial_count": "\(initialCount)", + ] + } +} +#endif diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index ef20111c..e3e019de 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -25,15 +25,13 @@ import Foundation /// // signal event after completing some task /// event.signal() /// ``` -public actor AsyncEvent: AsyncObject, ContinuableCollection { +public actor AsyncEvent: AsyncObject, ContinuableCollection, LoggableActor { /// The suspended tasks continuation type. @usableFromInline - internal typealias Continuation = SafeContinuation< + internal typealias Continuation = TrackedContinuation< GlobalContinuation > - /// The platform dependent lock used to synchronize continuations tracking. - @usableFromInline - internal let locker: Locker = .init() + /// The continuations stored with an associated key for all the suspended task that are waiting for event signal. @usableFromInline internal private(set) var continuations: [UUID: Continuation] = [:] @@ -48,38 +46,111 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { /// - Parameters: /// - continuation: The `continuation` to add. /// - key: The key in the map. + /// - file: The file add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function add request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// - preinit: The pre-initialization handler to run + /// in the beginning of this method. + /// + /// - Important: The pre-initialization handler must run + /// before any logic in this method. @inlinable internal func addContinuation( _ continuation: Continuation, - withKey key: UUID + withKey key: UUID, + file: String, function: String, line: UInt, + preinit: @Sendable () -> Void ) { - guard !continuation.resumed else { return } - guard !signalled else { continuation.resume(); return } + preinit() + log("Adding", id: key, file: file, function: function, line: line) + guard !continuation.resumed else { + log( + "Already resumed, not tracking", id: key, + file: file, function: function, line: line + ) + return + } + + guard !signalled else { + continuation.resume() + log("Resumed", id: key, file: file, function: function, line: line) + return + } + continuations[key] = continuation + log("Tracking", id: key, file: file, function: function, line: line) } /// Remove continuation associated with provided key /// from `continuations` map and resumes with `CancellationError`. /// - /// - Parameter key: The key in the map. + /// - Parameters: + /// - continuation: The continuation to remove and cancel. + /// - key: The key in the map. + /// - file: The file remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function remove request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func removeContinuation(withKey key: UUID) { + internal func removeContinuation( + _ continuation: Continuation, + withKey key: UUID, + file: String, function: String, line: UInt + ) { + log("Removing", id: key, file: file, function: function, line: line) continuations.removeValue(forKey: key) + guard !continuation.resumed else { + log( + "Already resumed, not cancelling", id: key, + file: file, function: function, line: line + ) + return + } + + continuation.cancel() + log("Cancelled", id: key, file: file, function: function, line: line) } /// Resets signal of event. + /// + /// - 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`). @inlinable - internal func resetEvent() { + internal func resetEvent(file: String, function: String, line: UInt) { signalled = false + log("Reset", file: file, function: function, line: line) } /// Signals the event and resumes all the tasks /// suspended and waiting for signal. + /// + /// - 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`). @inlinable - internal func signalEvent() { - continuations.forEach { $0.value.resume() } + internal func signalEvent(file: String, function: String, line: UInt) { + log("Signalling", file: file, function: function, line: line) + continuations.forEach { key, value in + value.resume() + log("Resumed", id: key, file: file, function: function, line: line) + } continuations = [:] signalled = true + log("Signalled", file: file, function: function, line: line) } // MARK: Public @@ -93,7 +164,8 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { self.signalled = signalled } - deinit { self.continuations.forEach { $0.value.cancel() } } + // TODO: Explore alternative cleanup for actor + // deinit { self.continuations.forEach { $1.cancel() } } /// Resets signal of event. /// @@ -112,7 +184,7 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await resetEvent() } + Task { await resetEvent(file: file, function: function, line: line) } } /// Signals the event. @@ -132,7 +204,7 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await signalEvent() } + Task { await signalEvent(file: file, function: function, line: line) } } /// Waits for event signal, or proceeds if already signalled. @@ -155,7 +227,32 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) async throws { - guard !signalled else { return } - try await withPromisedContinuation() + guard !signalled else { + log("Acquired", file: file, function: function, line: line) + return + } + + let key = UUID() + log("Waiting", id: key, file: file, function: function, line: line) + try await withPromisedContinuation( + withKey: key, + file: file, function: function, line: line + ) + log("Received", id: key, file: file, function: function, line: line) } } + +#if canImport(Logging) +import Logging + +extension AsyncEvent { + /// Type specific metadata to attach to all log messages. + @usableFromInline + var metadata: Logger.Metadata { + return [ + "obj": "\(self)(\(Unmanaged.passUnretained(self).toOpaque()))", + "signalled": "\(signalled)", + ] + } +} +#endif diff --git a/Sources/AsyncObjects/AsyncSemaphore.swift b/Sources/AsyncObjects/AsyncSemaphore.swift index c4228d55..9753bf3d 100644 --- a/Sources/AsyncObjects/AsyncSemaphore.swift +++ b/Sources/AsyncObjects/AsyncSemaphore.swift @@ -26,15 +26,13 @@ import OrderedCollections /// // release after executing critical async tasks /// defer { semaphore.signal() } /// ``` -public actor AsyncSemaphore: AsyncObject, ContinuableCollection { +public actor AsyncSemaphore: AsyncObject, ContinuableCollection, LoggableActor { /// The suspended tasks continuation type. @usableFromInline - internal typealias Continuation = SafeContinuation< + internal typealias Continuation = TrackedContinuation< GlobalContinuation > - /// The platform dependent lock used to synchronize continuations tracking. - @usableFromInline - 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 internal private(set) var continuations: @@ -58,25 +56,76 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { /// - Parameters: /// - continuation: The `continuation` to add. /// - key: The key in the map. + /// - file: The file add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function add request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// - preinit: The pre-initialization handler to run + /// in the beginning of this method. + /// + /// - Important: The pre-initialization handler must run + /// before any logic in this method. @inlinable internal func addContinuation( _ continuation: Continuation, - withKey key: UUID + withKey key: UUID, + file: String, function: String, line: UInt, + preinit: @Sendable () -> Void ) { + preinit() count -= 1 - guard !continuation.resumed else { return } - guard count <= 0 else { continuation.resume(); return } + log("Adding", id: key, file: file, function: function, line: line) + guard !continuation.resumed else { + log( + "Already resumed, not tracking", id: key, + file: file, function: function, line: line + ) + return + } + + guard count <= 0 else { + continuation.resume() + log("Resumed", id: key, file: file, function: function, line: line) + return + } + continuations[key] = continuation + log("Tracking", id: key, file: file, function: function, line: line) } /// Remove continuation associated with provided key /// from `continuations` map and resumes with `CancellationError`. /// - /// - Parameter key: The key in the map. + /// - Parameters: + /// - continuation: The continuation to remove and cancel. + /// - key: The key in the map. + /// - file: The file remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function remove request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func removeContinuation(withKey key: UUID) { - continuations.removeValue(forKey: key) + internal func removeContinuation( + _ continuation: Continuation, + withKey key: UUID, + file: String, function: String, line: UInt + ) { + log("Removing", id: key, file: file, function: function, line: line) incrementCount() + continuations.removeValue(forKey: key) + guard !continuation.resumed else { + log( + "Already resumed, not cancelling", id: key, + file: file, function: function, line: line + ) + return + } + + continuation.cancel() + log("Cancelled", id: key, file: file, function: function, line: line) } /// Increments semaphore count within limit provided. @@ -87,12 +136,22 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { } /// Signals (increments) and releases a semaphore. + /// + /// - 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`). @inlinable - internal func signalSemaphore() { + internal func signalSemaphore(file: String, function: String, line: UInt) { incrementCount() guard !continuations.isEmpty else { return } - let (_, continuation) = continuations.removeFirst() + log("Signalling", file: file, function: function, line: line) + let (key, continuation) = continuations.removeFirst() continuation.resume() + log("Resumed", id: key, file: file, function: function, line: line) } // MARK: Public @@ -110,7 +169,8 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { self.count = Int(limit) } - deinit { self.continuations.forEach { $0.value.cancel() } } + // TODO: Explore alternative cleanup for actor + // deinit { self.continuations.forEach { $1.cancel() } } /// Signals (increments) a semaphore. /// @@ -131,7 +191,9 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await signalSemaphore() } + Task { + await signalSemaphore(file: file, function: function, line: line) + } } /// Waits for, or decrements, a semaphore. @@ -154,7 +216,34 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) async throws { - guard count <= 1 else { count -= 1; return } - try await withPromisedContinuation() + guard count <= 1 else { + count -= 1 + log("Acquired", file: file, function: function, line: line) + return + } + + let key = UUID() + log("Waiting", id: key, file: file, function: function, line: line) + try await withPromisedContinuation( + withKey: key, + file: file, function: function, line: line + ) + log("Received", id: key, file: file, function: function, line: line) + } +} + +#if canImport(Logging) +import Logging + +extension AsyncSemaphore { + /// Type specific metadata to attach to all log messages. + @usableFromInline + var metadata: Logger.Metadata { + return [ + "obj": "\(self)(\(Unmanaged.passUnretained(self).toOpaque()))", + "limit": "\(limit)", + "count": "\(count)", + ] } } +#endif diff --git a/Sources/AsyncObjects/Continuation/Continuable.swift b/Sources/AsyncObjects/Continuation/Continuable.swift index d8267812..ea9cec0d 100644 --- a/Sources/AsyncObjects/Continuation/Continuable.swift +++ b/Sources/AsyncObjects/Continuation/Continuable.swift @@ -40,17 +40,20 @@ where Failure == Error { /// subsequent resumes have different behaviors depending on type implementing. /// /// - Parameters: + /// - file: The file from which suspension requested. /// - 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. + /// - line: The line from which suspension requested. /// - 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. + @_unsafeInheritExecutor static func with( - function: String, + file: String, function: String, line: UInt, _ body: (Self) -> Void ) async throws -> Success } @@ -69,16 +72,19 @@ where Failure == Never { /// subsequent resumes have different behavior depending on type implementing. /// /// - Parameters: + /// - file: The file from which suspension requested. /// - 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. + /// - line: The line from which suspension requested. /// - 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. + @_unsafeInheritExecutor static func with( - function: String, + file: String, function: String, line: UInt, _ body: (Self) -> Void ) async -> Success } @@ -123,17 +129,19 @@ internal protocol ThrowingContinuable: Continuable where Failure == Error { /// subsequent resumes have different behaviors depending on type implementing. /// /// - Parameters: + /// - file: The file from which suspension requested. /// - 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. + /// - line: The line from which suspension requested. /// - 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. static func with( - function: String, + file: String, function: String, line: UInt, _ body: (Self) -> Void ) async throws -> Success } @@ -151,16 +159,18 @@ internal protocol NonThrowingContinuable: Continuable where Failure == Never { /// subsequent resumes have different behavior depending on type implementing. /// /// - Parameters: + /// - file: The file from which suspension requested. /// - 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. + /// - line: The line from which suspension requested. /// - 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. static func with( - function: String, + file: String, function: String, line: UInt, _ body: (Self) -> Void ) async -> Success } @@ -180,6 +190,7 @@ public extension Continuable { /// The task continues executing when its executor schedules it. /// /// - Parameter value: The value to return from the continuation. + @inlinable func resume(returning value: Success) { self.resume(with: .success(value)) } @@ -190,8 +201,9 @@ public extension Continuable { /// /// 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)) + @inlinable + func resume() where Success == Void { + self.resume(with: .success(())) } /// Resume the task awaiting the continuation by having it throw an error from its suspension point. @@ -202,6 +214,7 @@ public extension Continuable { /// The task continues executing when its executor schedules it. /// /// - Parameter error: The error to throw from the continuation. + @inlinable func resume(throwing error: Failure) { self.resume(with: .failure(error)) } diff --git a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift index 14a14375..fec00010 100644 --- a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift +++ b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift @@ -6,42 +6,65 @@ import Foundation /// A type that manages a collection of continuations with an associated key. /// -/// A MUTual EXclusion object is used to synchronize continuations state. +/// While removing continuation, the continuation should be cancelled @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 + /// - continuation: The continuation value to add. /// - key: The key to associate continuation with. - func addContinuation(_ continuation: Continuation, withKey key: Key) async + /// - file: The file add request originates from. + /// - function: The function add request originates from. + /// - line: The line add request originates from. + /// - preinit: The pre-initialization handler to run + /// in the beginning of this method. + /// + /// - Important: The pre-initialization handler must run + /// before any logic in this method. + func addContinuation( + _ continuation: Continuation, withKey key: Key, + file: String, function: String, line: UInt, + preinit: @Sendable () -> Void + ) 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 + /// - Parameters: + /// - continuation: The continuation value to remove and cancel. + /// - key: The key for continuation to remove. + /// - file: The file remove request originates from. + /// - function: The function remove request originates from. + /// - line: The line remove request originates from. + func removeContinuation( + _ continuation: Continuation, withKey key: Key, + file: String, function: String, line: UInt + ) async /// Suspends the current task, then calls the given closure with a continuation for the current task. /// + /// - Parameters: + /// - key: The key associated to task, that requested suspension. + /// - file: The file wait request originates from. + /// - function: The function wait request originates from. + /// - line: The line wait request originates from. + /// /// - 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 + func withPromisedContinuation( + withKey key: Key, + file: String, function: String, line: UInt + ) async rethrows -> Continuation.Success } extension ContinuableCollection where - Self: AnyObject, Self: Sendable, Continuation: SynchronizedContinuable, - Continuation: Sendable, Continuation.Value: ThrowingContinuable, - Continuation.Lock == Lock, Key == UUID + Self: AnyObject & Sendable, Continuation: TrackableContinuable & Sendable, + Continuation.Value: Sendable & ThrowingContinuable, Key: Sendable, + Key == Continuation.ID { /// 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`. @@ -50,20 +73,37 @@ where /// 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. /// + /// - Parameters: + /// - key: The key associated to task, that requested suspension. + /// - 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`). + /// /// - 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 - ) { + nonisolated func withPromisedContinuation( + withKey key: Key, + file: String, function: String, line: UInt + ) async rethrows -> Continuation.Success { + return try await Continuation.withCancellation(id: key) { + continuation in Task { [weak self] in - await self?.removeContinuation(withKey: key) + await self?.removeContinuation( + continuation, withKey: key, + file: file, function: function, line: line + ) } - } operation: { continuation in + } operation: { continuation, preinit in Task { [weak self] in - await self?.addContinuation(continuation, withKey: key) + await self?.addContinuation( + continuation, withKey: key, + file: file, function: function, line: line, + preinit: preinit + ) } } } diff --git a/Sources/AsyncObjects/Continuation/GlobalContinuation.swift b/Sources/AsyncObjects/Continuation/GlobalContinuation.swift index 96a4ee82..e93a7685 100644 --- a/Sources/AsyncObjects/Continuation/GlobalContinuation.swift +++ b/Sources/AsyncObjects/Continuation/GlobalContinuation.swift @@ -1,3 +1,4 @@ +#if swift(>=5.7) #if DEBUG || ASYNCOBJECTS_USE_CHECKEDCONTINUATION /// The continuation type used in ``AsyncObjects`` package. /// @@ -20,18 +21,23 @@ extension CheckedContinuation: ThrowingContinuable where E == Error { /// at the loss of additional runtime checks. /// /// - Parameters: + /// - file: The file from which suspension requested. /// - 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. + /// - line: The line from which suspension requested. /// - 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 + @_unsafeInheritExecutor public static func with( + file: String = #fileID, function: String = #function, + line: UInt = #line, _ body: (Self) -> Void ) async throws -> T { return try await withCheckedThrowingContinuation( @@ -51,17 +57,22 @@ extension CheckedContinuation: NonThrowingContinuable where E == Never { /// at the loss of additional runtime checks. /// /// - Parameters: + /// - file: The file from which suspension requested. /// - 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. + /// - line: The line from which suspension requested. /// - 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 + @_unsafeInheritExecutor public static func with( + file: String = #fileID, function: String = #function, + line: UInt = #line, _ body: (Self) -> Void ) async -> T { return await withCheckedContinuation(function: function, body) @@ -87,18 +98,23 @@ extension UnsafeContinuation: ThrowingContinuable where E == Error { /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. /// /// - Parameters: + /// - file: The file from which suspension requested. /// - 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. + /// - line: The line from which suspension requested. /// - 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 + @_unsafeInheritExecutor public static func with( + file: String = #fileID, function: String = #function, + line: UInt = #line, _ body: (Self) -> Void ) async throws -> T { return try await withUnsafeThrowingContinuation(body) @@ -113,20 +129,175 @@ extension UnsafeContinuation: NonThrowingContinuable where E == Never { /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. /// /// - Parameters: + /// - file: The file from which suspension requested. /// - 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. + /// - line: The line from which suspension requested. /// - 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 + @_unsafeInheritExecutor public static func with( + file: String = #fileID, function: String = #function, + line: UInt = #line, _ body: (Self) -> Void ) async -> T { return await withUnsafeContinuation(body) } } #endif +#else +#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: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - 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( + file: String = #fileID, + function: String = #function, + line: UInt = #line, + _ 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: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - 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( + file: String = #fileID, + function: String = #function, + line: UInt = #line, + _ 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: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - 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( + file: String = #fileID, + function: String = #function, + line: UInt = #line, + _ 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: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - 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( + file: String = #fileID, + function: String = #function, + line: UInt = #line, + _ body: (Self) -> Void + ) async -> T { + return await withUnsafeContinuation(body) + } +} +#endif +#endif diff --git a/Sources/AsyncObjects/Continuation/SafeContinuation.swift b/Sources/AsyncObjects/Continuation/SafeContinuation.swift deleted file mode 100644 index 97794903..00000000 --- a/Sources/AsyncObjects/Continuation/SafeContinuation.swift +++ /dev/null @@ -1,212 +0,0 @@ -/// A safer mechanism to interface between synchronous and asynchronous code, -/// forgiving correctness violations. -/// -/// Resuming from a standard continuation more than once is undefined behavior. -/// Never resuming leaves the task in a suspended state indefinitely, -/// and leaks any associated resources. Use `SafeContinuation` if you are accessing -/// the same continuations in concurrent code and continuations have chance to be resumed -/// multiple times. -/// -/// `SafeContinuation` performs runtime checks for multiple resume operations. -/// 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. -@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. - enum Status { - /// Indicates continuation is waiting to be resumed. - /// - /// Resuming ``SafeContinuation`` with this status returns control immediately to the caller. - /// The task continues executing when its executor schedules it. - case waiting - /// Indicates continuation is waiting to be resumed with provided value. - /// - /// Resuming ``SafeContinuation`` with this status has no effect. - case willResume(Result) - /// Indicates continuation is already resumed. - /// - /// Resuming ``SafeContinuation`` with this status has no effect. - case resumed - } - - /// The platform dependent lock used to synchronize continuation resuming. - private let locker: Locker - /// The current status for continuation resumption. - private var status: Status - /// The actual continuation value. - private var value: C? - - /// Check if externally provided continuation status valid considering current status. - /// - /// - Parameter status: The provided status that current status should be updated to. - /// - 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), - (.resumed, .waiting), (.resumed, .willResume): - return false - default: return true - } - } - - /// Checks whether continuation is already resumed - /// or to be resumed with provided value. - @usableFromInline - var resumed: Bool { - return locker.perform { - switch status { - case .waiting: - return false - default: - break - } - return true - } - } - - /// 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 - /// 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. - init( - status: Status = .waiting, - with value: C? = nil, - synchronizedWith locker: Locker = .init() - ) { - self.value = value - self.locker = locker - switch status { - case .willResume(let result) where value != nil: - value!.resume(with: result) - self.status = .resumed - default: - self.status = status - } - } - - /// 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. - /// If continuation provided already during initialization, invoking this method will cause runtime exception. - /// - /// - Parameters: - /// - continuation: The continuation value to store. After passing the continuation - /// with this method, don’t use it outside of this object. - /// - status: The status to which current status should be updated. - /// Pass the ``Status`` of provided continuation. - /// - file: The file name to pass to the precondition. - /// The default is the file where method is called. - /// - line: The line number to pass to the precondition. - /// The default is the line where method is called. - /// - /// - Important: After passing the continuation with this method, - /// don’t use it outside of this object. - func add( - continuation: C, - status: Status = .waiting, - file: StaticString = #file, - line: UInt = #line - ) { - locker.perform { - precondition( - value == nil, - "Continuation can be provided only once", - file: file, line: line - ) - value = continuation - let isValidStatus = validateStatus(status) - switch isValidStatus ? status : self.status { - case .willResume(let result): - continuation.resume(with: result) - self.status = .resumed - case _ where isValidStatus: - self.status = status - default: - break - } - } - } - - /// 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. - /// If continuation provided already during initialization, invoking this method will cause runtime exception. - /// - /// - 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. - @usableFromInline - func add(continuation: C) { - self.add(continuation: continuation, status: .waiting) - } - - /// Resume the task awaiting the continuation by having it either return normally - /// or throw an error based on the state of the given `Result` value. - /// - /// 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 result: A value to either return or throw from the continuation. - @usableFromInline - func resume(with result: Result) { - locker.perform { - let finalResult: Result - switch (status, value) { - case (.waiting, .some): - finalResult = result - case (.willResume(let result), .some): - finalResult = result - case (.waiting, .none): - status = .willResume(result) - return - default: - return - } - value!.resume(with: finalResult) - status = .resumed - } - } -} - -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 deleted file mode 100644 index be3fe55d..00000000 --- a/Sources/AsyncObjects/Continuation/SynchronizedContinuable.swift +++ /dev/null @@ -1,189 +0,0 @@ -/// 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/Continuation/TrackableContinuable.swift b/Sources/AsyncObjects/Continuation/TrackableContinuable.swift new file mode 100644 index 00000000..8bc77552 --- /dev/null +++ b/Sources/AsyncObjects/Continuation/TrackableContinuable.swift @@ -0,0 +1,449 @@ +#if swift(>=5.7) +/// A type that allows to interface between synchronous and asynchronous code, +/// by binding to a base ``Continuable`` value and tracking its state. +/// +/// Use `TrackableContinuable` for interfacing Swift tasks with event loops, +/// delegate methods, callbacks, and other non-async scheduling mechanisms +/// where continuations resumption status need to be checked. +@rethrows +@usableFromInline +internal protocol TrackableContinuable: Continuable { + associatedtype ID + associatedtype Value: Continuable + where Value.Success == Success, Value.Failure == Failure + + /// Creates a trackable continuation from provided continuation. + /// + /// The provided continuation is bound and resumption state is tracked. + /// - Parameters: + /// - value: The continuation value to store. After passing the continuation + /// with this method, don’t use it outside of this object. + /// - id: Optional id to associate new instance with. + /// - file: The file where track continuation requested. + /// - function: The function where track continuation requested. + /// - line: The line where track continuation requested. + /// + /// - Returns: The newly created trackable continuation. + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + init( + with value: Value?, id: ID?, + file: String, function: String, line: UInt + ) + /// 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 TrackableContinuable +where Self: Sendable, Value: Sendable & ThrowingContinuable { + /// Suspends the current task, then calls the given operation with a `TrackableContinuable` + /// 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 with the created continuation when the task is canceled. + /// For example, even if the operation is running code that never checks for cancellation, a cancellation handler + /// still runs to allow cancellation of the continuation and to run some cleanup code. + /// + /// - Parameters: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - handler: A handler immediately invoked if task is cancelled. + /// - operation: A closure that takes an `TrackableContinuable` parameter and + /// a pre-initialization handler that needs to run before managing continuation. + /// 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 + @_unsafeInheritExecutor + static func withCancellation( + id: ID, + file: String = #fileID, + function: String = #function, + line: UInt = #line, + handler: @Sendable (Self) -> Void, + operation: (Self, @escaping @Sendable () -> Void) -> Void + ) async rethrows -> Success { + let cancellable = Self( + with: nil, id: id, + file: file, function: function, line: line + ) + return try await withTaskCancellationHandler { + return try await Value.with( + file: file, function: function, line: line + ) { continuation in + operation(cancellable) { + cancellable.add(continuation: continuation) + } + } + } onCancel: { + handler(cancellable) + } + } + + /// Suspends the current task, then calls the given closure with a `TrackableContinuable` for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `TrackableContinuable` allows accessing and resuming the same continuations in concurrent code + /// by keeping track of underlying continuation value state. + /// + /// - Parameters: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - body: A closure that takes a `TrackableContinuable` 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 + @_unsafeInheritExecutor + static func with( + file: String = #fileID, + function: String = #function, + line: UInt = #line, + _ body: (Self) -> Void + ) async rethrows -> Success { + return try await Value.with( + file: file, function: function, line: line + ) { continuation in + body( + Self( + with: continuation, id: nil, + file: file, function: function, line: line + ) + ) + } + } +} + +extension TrackableContinuable +where Self: Sendable, Value: Sendable & NonThrowingContinuable { + /// Suspends the current task, then calls the given operation with a `TrackableContinuable` + /// 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 with the created continuation when the task is canceled. + /// For example, even if the operation is running code that never checks for cancellation, a cancellation handler + /// still runs to allow cancellation of the continuation and to run some cleanup code. + /// + /// - Parameters: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - handler: A handler immediately invoked if task is cancelled. + /// - operation: A closure that takes an `TrackableContinuable` parameter and + /// a pre-initialization handler that needs to run before managing continuation. + /// 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 + @_unsafeInheritExecutor + internal static func withCancellation( + id: ID, + file: String = #fileID, + function: String = #function, + line: UInt = #line, + handler: @Sendable (Self) -> Void, + operation: (Self, @escaping @Sendable () -> Void) -> Void + ) async -> Success { + let cancellable = Self( + with: nil, id: id, + file: file, function: function, line: line + ) + return await withTaskCancellationHandler { + return await Value.with( + file: file, function: function, line: line + ) { continuation in + operation(cancellable) { + cancellable.add(continuation: continuation) + } + } + } onCancel: { + handler(cancellable) + } + } + + /// Suspends the current task, then calls the given closure with a `TrackableContinuable` for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `TrackableContinuable` allows accessing and resuming the same continuations in concurrent code + /// by keeping track of underlying continuation value state. + /// + /// - Parameters: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - body: A closure that takes a `TrackableContinuable` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + @usableFromInline + @_unsafeInheritExecutor + internal static func with( + file: String = #fileID, + function: String = #function, + line: UInt = #line, + _ body: (Self) -> Void + ) async -> Success { + return await Value.with( + file: file, function: function, line: line + ) { continuation in + body( + Self( + with: continuation, id: nil, + file: file, function: function, line: line + ) + ) + } + } +} +#else +/// A type that allows to interface between synchronous and asynchronous code, +/// by binding to a base ``Continuable`` value and tracking its state. +/// +/// Use `TrackableContinuable` for interfacing Swift tasks with event loops, +/// delegate methods, callbacks, and other non-async scheduling mechanisms +/// where continuations resumption status need to be checked. +@rethrows +@usableFromInline +internal protocol TrackableContinuable: Continuable { + associatedtype ID + associatedtype Value: Continuable + where Value.Success == Success, Value.Failure == Failure + + /// Creates a trackable continuation from provided continuation. + /// + /// The provided continuation is bound and resumption state is tracked. + /// - Parameters: + /// - value: The continuation value to store. After passing the continuation + /// with this method, don’t use it outside of this object. + /// - id: Optional id to associate new instance with. + /// - file: The file where track continuation requested. + /// - function: The function where track continuation requested. + /// - line: The line where track continuation requested. + /// + /// - Returns: The newly created trackable continuation. + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + init( + with value: Value?, id: ID?, + file: String, function: String, line: UInt + ) + /// 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 TrackableContinuable +where Self: Sendable, Value: Sendable & ThrowingContinuable { + /// Suspends the current task, then calls the given operation with a `TrackableContinuable` + /// 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 with the created continuation when the task is canceled. + /// For example, even if the operation is running code that never checks for cancellation, a cancellation handler + /// still runs to allow cancellation of the continuation and to run some cleanup code. + /// + /// - Parameters: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - handler: A handler immediately invoked if task is cancelled. + /// - operation: A closure that takes an `TrackableContinuable` parameter and + /// a pre-initialization handler that needs to run before managing continuation. + /// 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( + id: ID, + file: String = #fileID, + function: String = #function, + line: UInt = #line, + handler: @Sendable (Self) -> Void, + operation: (Self, @escaping @Sendable () -> Void) -> Void + ) async rethrows -> Success { + let cancellable = Self( + with: nil, id: id, + file: file, function: function, line: line + ) + return try await withTaskCancellationHandler { + return try await Value.with( + file: file, function: function, line: line + ) { continuation in + operation(cancellable) { + cancellable.add(continuation: continuation) + } + } + } onCancel: { + handler(cancellable) + } + } + + /// Suspends the current task, then calls the given closure with a `TrackableContinuable` for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `TrackableContinuable` allows accessing and resuming the same continuations in concurrent code + /// by keeping track of underlying continuation value state. + /// + /// - Parameters: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - body: A closure that takes a `TrackableContinuable` 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( + file: String = #fileID, + function: String = #function, + line: UInt = #line, + _ body: (Self) -> Void + ) async rethrows -> Success { + return try await Value.with( + file: file, function: function, line: line + ) { continuation in + body( + Self( + with: continuation, id: nil, + file: file, function: function, line: line + ) + ) + } + } +} + +extension TrackableContinuable +where Self: Sendable, Value: Sendable & NonThrowingContinuable { + /// Suspends the current task, then calls the given operation with a `TrackableContinuable` + /// 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 with the created continuation when the task is canceled. + /// For example, even if the operation is running code that never checks for cancellation, a cancellation handler + /// still runs to allow cancellation of the continuation and to run some cleanup code. + /// + /// - Parameters: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - handler: A handler immediately invoked if task is cancelled. + /// - operation: A closure that takes an `TrackableContinuable` parameter and + /// a pre-initialization handler that needs to run before managing continuation. + /// 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( + id: ID, + file: String = #fileID, + function: String = #function, + line: UInt = #line, + handler: @Sendable (Self) -> Void, + operation: (Self, @escaping @Sendable () -> Void) -> Void + ) async -> Success { + let cancellable = Self( + with: nil, id: id, + file: file, function: function, line: line + ) + return await withTaskCancellationHandler { + return await Value.with( + file: file, function: function, line: line + ) { continuation in + operation(cancellable) { + cancellable.add(continuation: continuation) + } + } + } onCancel: { + handler(cancellable) + } + } + + /// Suspends the current task, then calls the given closure with a `TrackableContinuable` for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `TrackableContinuable` allows accessing and resuming the same continuations in concurrent code + /// by keeping track of underlying continuation value state. + /// + /// - Parameters: + /// - file: The file from which suspension requested. + /// - 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. + /// - line: The line from which suspension requested. + /// - body: A closure that takes a `TrackableContinuable` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + @usableFromInline + internal static func with( + file: String = #fileID, + function: String = #function, + line: UInt = #line, + _ body: (Self) -> Void + ) async -> Success { + return await Value.with( + file: file, function: function, line: line + ) { continuation in + body( + Self( + with: continuation, id: nil, + file: file, function: function, line: line + ) + ) + } + } +} +#endif diff --git a/Sources/AsyncObjects/Continuation/TrackedContinuation.swift b/Sources/AsyncObjects/Continuation/TrackedContinuation.swift new file mode 100644 index 00000000..f9b27564 --- /dev/null +++ b/Sources/AsyncObjects/Continuation/TrackedContinuation.swift @@ -0,0 +1,229 @@ +#if swift(>=5.7) +import Foundation +#else +@preconcurrency import Foundation +#endif + +/// A mechanism to interface between synchronous and asynchronous code, +/// with tracking state data. +/// +/// Resuming from a standard continuation more than once is undefined behavior. +/// Never resuming leaves the task in a suspended state indefinitely, +/// and leaks any associated resources. Use `TrackedContinuation` +/// if you want to check whether continuation can be resumed. +/// +/// `TrackedContinuation` stores continuation state, +/// and allows continuation value to be provided at a later state, +/// where it can be resumed with available result. +/// +/// While there is no checks for missing resume operations, +/// `CheckedContinuation` can be used as underlying +/// continuation value for additional runtime checks. +/// +/// - Important: The continuation stored mustn't be +/// resumed or used outside of this object. +@usableFromInline +internal final class TrackedContinuation: TrackableContinuable, + Loggable +{ + /// Tracks the status of continuation resuming for ``TrackedContinuation``. + /// + /// Depending upon ``TrackedContinuation`` status the ``TrackedContinuation/resume(with:)`` + /// invocation effect is determined. + enum Status { + /// Indicates continuation is waiting to be resumed. + /// + /// Resuming ``TrackedContinuation`` with this status returns control immediately to the caller. + /// The task continues executing when its executor schedules it. + case waiting + /// Indicates continuation is waiting to be resumed with provided value. + /// + /// This happens when ``TrackedContinuation/resume(with:)`` + /// invoked without binding ``TrackedContinuation`` with a base continuation + /// + /// Resuming ``TrackedContinuation`` with this status will trap. + case willResume(Result) + /// Indicates continuation is already resumed. + /// + /// Resuming ``TrackedContinuation`` with this status will trap. + case resumed + } + + /// The invocation context from which continuation tracked. + @usableFromInline + struct Context: Sendable { + /// Optional id to associated to the continuation instance. + @usableFromInline + let id: UUID? + /// The file where track continuation requested. + @usableFromInline + let file: String + /// The function where track continuation requested. + @usableFromInline + let function: String + /// The line where track continuation requested. + @usableFromInline + let line: UInt + } + + /// The current status for continuation resumption. + private var status: Status = .waiting + /// The actual continuation for which state is tracked. + private var value: C? + /// Invocation context for the tracked continuation. + @usableFromInline + let context: Context + + /// Checks whether continuation is already resumed + /// or to be resumed with provided value. + @usableFromInline + var resumed: Bool { + switch status { + case .waiting: + return false + default: + break + } + return true + } + + /// Tracks the provided continuation state. + /// + /// 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. + /// - id: Optional id to associate new instance with. + /// - file: The file where track continuation requested. + /// - function: The function where track continuation requested. + /// - line: The line where track continuation requested. + /// + /// - Returns: The newly created tracked continuation. + /// - Important: The continuation passed mustn't be resumed before. + /// After passing the continuation with this method, + /// don’t use it outside of this object. + @usableFromInline + init( + with value: C? = nil, + id: UUID? = nil, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.value = value + self.context = .init(id: id, file: file, function: function, line: line) + log("Initialized") + } + + /// 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: The continuation passed mustn't be resumed before. + /// After passing the continuation with this method, + /// don’t use it outside of this object. + /// + /// - Important: If continuation provided already during initialization, + /// invoking this method will cause runtime exception. + @usableFromInline + func add(continuation: C) { + log("Adding continuation") + precondition( + value == nil, + "Continuation can be provided only once" + ) + value = continuation + switch self.status { + case .willResume(let result): + continuation.resume(with: result) + self.status = .resumed + case .resumed: + fatalError("Invalid move to resumed state") + default: + break + } + log("Added continuation") + } + + /// Resume the task awaiting the continuation by having it either return normally + /// or throw an error based on the state of the given `Result` value. + /// + /// A continuation must be resumed at least once. If the continuation has already resumed, + /// then the attempt to resume the continuation will trap. + /// + /// After calling this method, control immediately returns to the caller. + /// The task continues executing when its executor schedules it. + /// + /// - Parameter result: A value to either return or throw from the continuation. + @usableFromInline + func resume(with result: Result) { + log("Resuming with \(result)") + switch (status, value) { + case (_, .some(let value)): + value.resume(with: result) + status = .resumed + case (.waiting, .none): + status = .willResume(result) + default: + fatalError("Multiple resume invoked") + } + log("Resumed with \(result)") + } +} + +extension TrackedContinuation: @unchecked Sendable where C.Success: Sendable {} +extension TrackedContinuation.Status: Sendable where C.Success: Sendable {} + +#if canImport(Logging) +import Logging + +extension TrackedContinuation { + /// Type specific metadata to attach to all log messages. + @usableFromInline + var metadata: Logger.Metadata { + return [ + "obj": "\(self)", + "id": "\(context.id != nil ? "\(context.id!)" : "nil")", + "status": "\(status)", + "value": value != nil ? "some" : "nil", + ] + } + + /// Log a message attaching the default type specific metadata + /// and optional identifier. + /// + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE` is set log level is set to `trace`. + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG` is set log level is set to `debug`. + /// Otherwise log level is set to `info`. + /// + /// - Parameters: + /// - message: The message to be logged. + @inlinable + func log(_ message: @autoclosure () -> Logger.Message) { + logger.log( + level: level, message(), metadata: self.metadata, + file: context.file, function: context.function, line: context.line + ) + } +} +#else +extension TrackedContinuation { + /// Log a message attaching the default type specific metadata + /// and optional identifier. + /// + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE` is set log level is set to `trace`. + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG` is set log level is set to `debug`. + /// Otherwise log level is set to `info`. + /// + /// - Parameters: + /// - message: The message to be logged. + @inlinable + func log(_ message: @autoclosure () -> String) { /* Do nothing */ } +} +#endif diff --git a/Sources/AsyncObjects/Extensions/Task.swift b/Sources/AsyncObjects/Extensions/Task.swift index 2a030f56..98d81021 100644 --- a/Sources/AsyncObjects/Extensions/Task.swift +++ b/Sources/AsyncObjects/Extensions/Task.swift @@ -1,4 +1,5 @@ public extension Task { + #if swift(>=5.7) /// Runs the given operation asynchronously as part of /// a new top-level cancellable task on behalf of the current actor. /// @@ -14,6 +15,7 @@ public extension Task { /// - Throws: Error from the given operation. @inlinable @discardableResult + @_unsafeInheritExecutor static func withCancellableTask( priority: TaskPriority?, operation: @Sendable @escaping () async throws -> Success @@ -24,6 +26,33 @@ public extension Task { onCancel: { task.cancel() } ) } + #else + /// Runs the given operation asynchronously as part of + /// a new top-level cancellable task on behalf of the current actor. + /// + /// Use this function to perform asynchronous work as part of a top-level task + /// while being able to automatically cancel top-level task when the current task is cancelled. + /// + /// - Parameters: + /// - priority: The priority of the task. Pass `nil` to use + /// the priority from `Task.currentPriority`. + /// - operation: The operation to perform. + /// + /// - Returns: The result from the given operation, after it completes. + /// - Throws: Error from the given operation. + @inlinable + @discardableResult + static func withCancellableTask( + priority: TaskPriority?, + operation: @Sendable @escaping () async throws -> Success + ) async rethrows -> Success where Failure == Error { + let task = Self.init(priority: priority, operation: operation) + return try await withTaskCancellationHandler( + operation: { try await task.value }, + onCancel: { task.cancel() } + ) + } + #endif /// Runs the given operation asynchronously as part of /// a new top-level cancellable task. diff --git a/Sources/AsyncObjects/Extensions/TaskGroup.swift b/Sources/AsyncObjects/Extensions/TaskGroup.swift index 26017beb..d5a65dbf 100644 --- a/Sources/AsyncObjects/Extensions/TaskGroup.swift +++ b/Sources/AsyncObjects/Extensions/TaskGroup.swift @@ -1,3 +1,4 @@ +#if swift(>=5.7) public extension TaskGroup { /// Adds a child task to the group and starts the task. /// @@ -8,6 +9,7 @@ public extension TaskGroup { /// 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 + @_unsafeInheritExecutor mutating func addTaskAndStart( priority: TaskPriority? = nil, operation: @escaping @Sendable () async -> ChildTaskResult @@ -34,6 +36,7 @@ public extension ThrowingTaskGroup { /// 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 + @_unsafeInheritExecutor mutating func addTaskAndStart( priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> ChildTaskResult @@ -47,3 +50,54 @@ public extension ThrowingTaskGroup { } } } +#else +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() + } + } + } +} +#endif diff --git a/Sources/AsyncObjects/Future.swift b/Sources/AsyncObjects/Future.swift index 55dda398..3317147c 100644 --- a/Sources/AsyncObjects/Future.swift +++ b/Sources/AsyncObjects/Future.swift @@ -39,7 +39,7 @@ import Foundation /// // or cancel future with error /// await future.fulfill(throwing: CancellationError()) /// ``` -public actor Future { +public actor Future: LoggableActor { /// A type that represents a closure to invoke in the future, when an element or error is available. /// /// The promise closure receives one parameter: a `Result` that contains @@ -49,12 +49,10 @@ public actor Future { public typealias FutureResult = Result /// The suspended tasks continuation type. @usableFromInline - internal typealias Continuation = SafeContinuation< + internal typealias Continuation = TrackedContinuation< GlobalContinuation > - /// The platform dependent lock used to synchronize continuations tracking. - @usableFromInline - 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 @@ -69,14 +67,42 @@ public actor Future { /// - Parameters: /// - continuation: The `continuation` to add. /// - key: The key in the map. + /// - file: The file add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function add request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// - preinit: The pre-initialization handler to run + /// in the beginning of this method. + /// + /// - Important: The pre-initialization handler must run + /// before any logic in this method. @inlinable internal func addContinuation( _ continuation: Continuation, - withKey key: UUID = .init() + withKey key: UUID, + file: String, function: String, line: UInt, + preinit: @Sendable () -> Void ) { - guard !continuation.resumed else { return } - if let result = result { continuation.resume(with: result); return } + preinit() + log("Adding", id: key, file: file, function: function, line: line) + guard !continuation.resumed else { + log( + "Already resumed, not tracking", id: key, + file: file, function: function, line: line + ) + return + } + + if let result = result { + continuation.resume(with: result) + log("Resumed", id: key, file: file, function: function, line: line) + return + } + continuations[key] = continuation + log("Tracking", id: key, file: file, function: function, line: line) } /// Creates a future that can be fulfilled later by ``fulfill(with:file:function:line:)`` or @@ -168,11 +194,12 @@ public actor Future { } #endif - deinit { - guard Failure.self is Error.Protocol else { return } - (continuations as! [UUID: GlobalContinuation]) - .forEach { $0.value.cancel() } - } + // TODO: Explore alternative cleanup for actor + // deinit { + // guard Failure.self is Error.Protocol else { return } + // (continuations as! [UUID: GlobalContinuation]) + // .forEach { $1.cancel() } + // } /// Fulfill the future by producing the given value and notify subscribers. /// @@ -249,10 +276,21 @@ public actor Future { function: String = #function, line: UInt = #line ) { - guard self.result == nil else { return } + guard self.result == nil else { + log("Already fulfilled", file: file, function: function, line: line) + return + } + self.result = result - continuations.forEach { $0.value.resume(with: result) } + continuations.forEach { key, value in + value.resume(with: result) + log( + "Fulfilled", id: key, + file: file, function: function, line: line + ) + } continuations = [:] + log("Fulfilled", file: file, function: function, line: line) } } @@ -263,12 +301,28 @@ extension Future where Failure == Never { /// Spins up a new continuation and requests to track it with key by invoking `addContinuation`. /// This operation doesn't check for cancellation. /// + /// - Parameters: + /// - key: The key associated to task, that requested suspension. + /// - 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`). + /// /// - Returns: The value continuation is resumed with. @inlinable - internal nonisolated func withPromisedContinuation() async -> Output { + internal nonisolated func withPromisedContinuation( + withKey key: UUID, + file: String, function: String, line: UInt + ) async -> Output { return await Continuation.with { continuation in Task { [weak self] in - await self?.addContinuation(continuation) + await self?.addContinuation( + continuation, withKey: key, + file: file, function: function, line: line, + preinit: { /* Do nothing */ } + ) } } } @@ -291,8 +345,20 @@ extension Future where Failure == Never { function: String = #function, line: UInt = #line ) async -> Output { - if let result = result { return try! result.get() } - return await withPromisedContinuation() + if let result = result { + log("Received", file: file, function: function, line: line) + return try! result.get() + } + + let key = UUID() + log("Waiting", id: key, file: file, function: function, line: line) + defer { + log("Received", id: key, file: file, function: function, line: line) + } + return await withPromisedContinuation( + withKey: key, + file: file, function: function, line: line + ) } /// Combines into a single future, for all futures to be fulfilled. @@ -574,10 +640,33 @@ extension Future where Failure == Error { /// Remove continuation associated with provided key /// from `continuations` map and resumes with `CancellationError`. /// - /// - Parameter key: The key in the map. + /// - Parameters: + /// - continuation: The continuation to remove and cancel. + /// - key: The key in the map. + /// - file: The file remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function remove request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func removeContinuation(withKey key: UUID) { + internal func removeContinuation( + _ continuation: Continuation, + withKey key: UUID, + file: String, function: String, line: UInt + ) { + log("Removing", id: key, file: file, function: function, line: line) continuations.removeValue(forKey: key) + guard !continuation.resumed else { + log( + "Already resumed, not cancelling", id: key, + file: file, function: function, line: line + ) + return + } + + continuation.cancel() + log("Cancelled", id: key, file: file, function: function, line: line) } /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. @@ -587,22 +676,37 @@ extension Future where Failure == Error { /// 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. + /// - Parameters: + /// - key: The key associated to task, that requested suspension. + /// - 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`). /// + /// - Returns: The value continuation is resumed with. /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - internal nonisolated func withPromisedContinuation() async throws -> Output - { - let key = UUID() - return try await Continuation.withCancellation( - synchronizedWith: locker - ) { + internal nonisolated func withPromisedContinuation( + withKey key: UUID, + file: String, function: String, line: UInt + ) async throws -> Output { + return try await Continuation.withCancellation(id: key) { + continuation in Task { [weak self] in - await self?.removeContinuation(withKey: key) + await self?.removeContinuation( + continuation, withKey: key, + file: file, function: function, line: line + ) } - } operation: { continuation in + } operation: { continuation, preinit in Task { [weak self] in - await self?.addContinuation(continuation, withKey: key) + await self?.addContinuation( + continuation, withKey: key, + file: file, function: function, line: line, + preinit: preinit + ) } } } @@ -628,8 +732,20 @@ extension Future where Failure == Error { function: String = #function, line: UInt = #line ) async throws -> Output { - if let result = result { return try result.get() } - return try await withPromisedContinuation() + if let result = result { + log("Received", file: file, function: function, line: line) + return try result.get() + } + + let key = UUID() + log("Waiting", id: key, file: file, function: function, line: line) + defer { + log("Received", id: key, file: file, function: function, line: line) + } + return try await withPromisedContinuation( + withKey: key, + file: file, function: function, line: line + ) } /// Combines into a single future, for all futures to be fulfilled, or for any to be rejected. @@ -966,3 +1082,18 @@ private extension Result where Failure == Error { /// The cancelled error result. static var cancelled: Self { .failure(CancellationError()) } } + +#if canImport(Logging) +import Logging + +extension Future { + /// Type specific metadata to attach to all log messages. + @usableFromInline + var metadata: Logger.Metadata { + return [ + "obj": "\(self)(\(Unmanaged.passUnretained(self).toOpaque()))", + "result": "\(result != nil ? "\(result!)" : "nil")", + ] + } +} +#endif diff --git a/Sources/AsyncObjects/Logging/Loggable.swift b/Sources/AsyncObjects/Logging/Loggable.swift new file mode 100644 index 00000000..728d96e3 --- /dev/null +++ b/Sources/AsyncObjects/Logging/Loggable.swift @@ -0,0 +1,166 @@ +import Foundation + +#if canImport(Logging) +import Logging + +/// A type that emits log messages with specific metadata. +@usableFromInline +protocol Loggable { + /// Metadata to attach to all log messages for this type. + var metadata: Logger.Metadata { get } +} + +/// An actor type that emits log messages with specific metadata. +@usableFromInline +protocol LoggableActor: Actor { + /// Metadata to attach to all log messages for this type. + var metadata: Logger.Metadata { get } +} + +@usableFromInline +let logger = Logger(label: "com.SwiftyLab.AsyncObjects") + +#if ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_INFO +@usableFromInline +let level: Logger.Level = .info +#elseif ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE +@usableFromInline +let level: Logger.Level = .trace +#elseif ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG +@usableFromInline +let level: Logger.Level = .debug +#else +@usableFromInline +let level: Logger.Level = .info +#endif + +extension Loggable { + /// Log a message attaching the default type specific metadata + /// and optional identifier. + /// + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE` is set log level is set to `trace`. + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG` is set log level is set to `debug`. + /// Otherwise log level is set to `info`. + /// + /// - Parameters: + /// - message: The message to be logged. + /// - id: Optional identifier associated with message. + /// - file: The file this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#fileID`). + /// - function: The function this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#function`). + /// - line: The line this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#line`). + @inlinable + func log( + _ message: @autoclosure () -> Logger.Message, + id: UUID? = nil, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + var metadata = metadata + if let id = id { metadata["id"] = "\(id)" } + logger.log( + level: level, message(), metadata: metadata, + file: file, function: function, line: line + ) + } +} + +extension LoggableActor { + /// Log a message attaching the default type specific metadata + /// and optional identifier. + /// + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE` is set log level is set to `trace`. + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG` is set log level is set to `debug`. + /// Otherwise log level is set to `info`. + /// + /// - Parameters: + /// - message: The message to be logged. + /// - id: Optional identifier associated with message. + /// - file: The file this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#fileID`). + /// - function: The function this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#function`). + /// - line: The line this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#line`). + @inlinable + func log( + _ message: @autoclosure () -> Logger.Message, + id: UUID? = nil, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + var metadata = metadata + if let id = id { metadata["id"] = "\(id)" } + logger.log( + level: level, message(), metadata: metadata, + file: file, function: function, line: line + ) + } +} +#else +/// A type that emits log messages with specific metadata. +@usableFromInline +protocol Loggable {} + +/// An actor type that emits log messages with specific metadata. +@usableFromInline +protocol LoggableActor: Actor {} + +extension Loggable { + /// Log a message attaching the default type specific metadata + /// and optional identifier. + /// + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE` is set log level is set to `trace`. + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG` is set log level is set to `debug`. + /// Otherwise log level is set to `info`. + /// + /// - Parameters: + /// - message: The message to be logged. + /// - id: Optional identifier associated with message. + /// - file: The file this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#fileID`). + /// - function: The function this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#function`). + /// - line: The line this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#line`). + @inlinable + func log( + _ message: @autoclosure () -> String, + id: UUID? = nil, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { /* Do nothing */ } +} + +extension LoggableActor { + /// Log a message attaching the default type specific metadata + /// and optional identifier. + /// + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE` is set log level is set to `trace`. + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG` is set log level is set to `debug`. + /// Otherwise log level is set to `info`. + /// + /// - Parameters: + /// - message: The message to be logged. + /// - id: Optional identifier associated with message. + /// - file: The file this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#fileID`). + /// - function: The function this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#function`). + /// - line: The line this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#line`). + @inlinable + nonisolated func log( + _ message: @autoclosure () -> String, + id: UUID? = nil, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { /* Do nothing */ } +} +#endif diff --git a/Sources/AsyncObjects/TaskOperation.swift b/Sources/AsyncObjects/TaskOperation.swift index de2e6ca4..5b25a00e 100644 --- a/Sources/AsyncObjects/TaskOperation.swift +++ b/Sources/AsyncObjects/TaskOperation.swift @@ -29,7 +29,7 @@ import Dispatch /// operation.waitUntilFinished() /// ``` public final class TaskOperation: Operation, AsyncObject, - ContinuableCollection, @unchecked Sendable + ContinuableCollection, Loggable, @unchecked Sendable { /// The asynchronous action to perform as part of the operation.. private let underlyingAction: @Sendable () async throws -> R @@ -117,7 +117,7 @@ public final class TaskOperation: Operation, AsyncObject, locker.perform { _isFinished = newValue guard newValue, !continuations.isEmpty else { return } - continuations.forEach { $0.value.resume() } + continuations.forEach { $1.resume() } continuations = [:] } didChangeValue(forKey: "isFinished") @@ -170,7 +170,7 @@ public final class TaskOperation: Operation, AsyncObject, deinit { execTask?.cancel() - locker.perform { self.continuations.forEach { $0.value.cancel() } } + locker.perform { self.continuations.forEach { $1.cancel() } } } /// Begins the execution of the operation. @@ -224,7 +224,7 @@ public final class TaskOperation: Operation, AsyncObject, // MARK: AsyncObject /// The suspended tasks continuation type. @usableFromInline - internal typealias Continuation = SafeContinuation< + internal typealias Continuation = TrackedContinuation< GlobalContinuation > /// The continuations stored with an associated key for all the suspended task that are waiting for operation completion. @@ -236,25 +236,84 @@ public final class TaskOperation: Operation, AsyncObject, /// - Parameters: /// - continuation: The `continuation` to add. /// - key: The key in the map. + /// - file: The file add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function add request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line add request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// - preinit: The pre-initialization handler to run + /// in the beginning of this method. + /// + /// - Important: The pre-initialization handler must run + /// before any logic in this method. @inlinable internal func addContinuation( _ continuation: Continuation, - withKey key: UUID + withKey key: UUID, + file: String, function: String, line: UInt, + preinit: @Sendable () -> Void ) { locker.perform { - guard !continuation.resumed else { return } - if isFinished { continuation.resume(); return } + preinit() + log("Adding", id: key, file: file, function: function, line: line) + guard !continuation.resumed else { + log( + "Already resumed, not tracking", id: key, + file: file, function: function, line: line + ) + return + } + + guard !isFinished else { + continuation.resume() + log( + "Resumed", id: key, + file: file, function: function, line: line + ) + return + } + continuations[key] = continuation + log("Tracking", id: key, file: file, function: function, line: line) } } /// Remove continuation associated with provided key /// from `continuations` map. /// - /// - Parameter key: The key in the map. + /// - Parameters: + /// - continuation: The continuation to remove and cancel. + /// - key: The key in the map. + /// - file: The file remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function remove request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func removeContinuation(withKey key: UUID) { - locker.perform { continuations.removeValue(forKey: key) } + internal func removeContinuation( + _ continuation: Continuation, + withKey key: UUID, + file: String, function: String, line: UInt + ) { + locker.perform { + log("Removing", id: key, file: file, function: function, line: line) + continuations.removeValue(forKey: key) + guard !continuation.resumed else { + log( + "Already resumed, not cancelling", id: key, + file: file, function: function, line: line + ) + return + } + + continuation.cancel() + log( + "Cancelled", id: key, + file: file, function: function, line: line + ) + } } /// Starts operation asynchronously @@ -274,6 +333,7 @@ public final class TaskOperation: Operation, AsyncObject, line: UInt = #line ) { self.start() + log("Started", file: file, function: function, line: line) } /// Waits for operation to complete successfully or cancelled. @@ -296,8 +356,18 @@ public final class TaskOperation: Operation, AsyncObject, function: String = #function, line: UInt = #line ) async throws { - guard !isFinished else { return } - try await withPromisedContinuation() + guard !isFinished else { + log("Finished", file: file, function: function, line: line) + return + } + + let key = UUID() + log("Waiting", id: key, file: file, function: function, line: line) + try await withPromisedContinuation( + withKey: key, + file: file, function: function, line: line + ) + log("Finished", id: key, file: file, function: function, line: line) } } @@ -394,3 +464,22 @@ public struct TaskOperationFlags: OptionSet, Sendable { self.rawValue = rawValue } } + +#if canImport(Logging) +import Logging + +extension TaskOperation { + /// Type specific metadata to attach to all log messages. + @usableFromInline + var metadata: Logger.Metadata { + return [ + "obj": "\(self)", + "priority": "\(priority != nil ? "\(priority!)" : "nil")", + "flags": "\(flags)", + "executing": "\(isExecuting)", + "cancelled": "\(isCancelled)", + "finished": "\(isFinished)", + ] + } +} +#endif diff --git a/Sources/AsyncObjects/TaskQueue.swift b/Sources/AsyncObjects/TaskQueue.swift index 233c72b3..84e6dc07 100644 --- a/Sources/AsyncObjects/TaskQueue.swift +++ b/Sources/AsyncObjects/TaskQueue.swift @@ -40,7 +40,7 @@ import OrderedCollections /// try await Task.sleep(nanoseconds: 1_000_000_000) /// } /// ``` -public actor TaskQueue: AsyncObject { +public actor TaskQueue: AsyncObject, LoggableActor { /// A set of behaviors for operations, such as its priority and whether to create a barrier /// or spawn a new detached task. /// @@ -161,16 +161,14 @@ public actor TaskQueue: AsyncObject { /// The suspended tasks continuation type. @usableFromInline - internal typealias Continuation = SafeContinuation< + internal typealias Continuation = TrackedContinuation< GlobalContinuation > /// A mechanism to queue tasks in ``TaskQueue``, to be resumed when queue is freed /// and provided flags are satisfied. @usableFromInline internal typealias QueuedContinuation = (value: Continuation, flags: Flags) - /// The platform dependent lock used to synchronize continuations tracking. - @usableFromInline - 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 = [:] @@ -198,13 +196,31 @@ public actor TaskQueue: AsyncObject { /// Resume provided continuation with additional changes based on the associated flags. /// - /// - Parameter continuation: The queued continuation to resume. + /// - Parameters: + /// - continuation: The queued continuation to resume. + /// - key: The key in the continuation queue. + /// - file: The file resume request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function resume request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line resume request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Returns: Whether queue is free to proceed scheduling other tasks. @inlinable @discardableResult internal func resumeQueuedContinuation( - _ continuation: QueuedContinuation + _ continuation: QueuedContinuation, + atKey key: UUID, + file: String, function: String, line: UInt ) -> Bool { + defer { + log( + "Resumed", flags: continuation.flags, id: key, + file: file, function: function, line: line + ) + } + currentRunning += 1 continuation.value.resume() guard continuation.flags.isBlockEnabled else { return true } @@ -215,27 +231,94 @@ public actor TaskQueue: AsyncObject { /// Add continuation with the provided key and associated flags to queue. /// /// - Parameters: - /// - key: The key in the continuation queue. /// - continuation: The continuation and flags to add to queue. + /// - key: The key in the continuation queue. + /// - file: The file queue request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function queue request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line queue request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// - preinit: The pre-initialization handler to run + /// in the beginning of this method. + /// + /// - Important: The pre-initialization handler must run + /// before any logic in this method. @inlinable internal func queueContinuation( - atKey key: UUID = .init(), - _ continuation: QueuedContinuation + _ continuation: QueuedContinuation, + atKey key: UUID, + file: String, function: String, line: UInt, + preinit: @escaping @Sendable () -> Void ) { - guard !continuation.value.resumed else { return } + preinit() + log( + "Adding", flags: continuation.flags, id: key, + file: file, function: function, line: line + ) + + guard !continuation.value.resumed else { + log( + "Already resumed, not tracking", + flags: continuation.flags, id: key, + file: file, function: function, line: line + ) + return + } + guard shouldWait(whenFlags: continuation.flags) else { - resumeQueuedContinuation(continuation) + resumeQueuedContinuation( + continuation, atKey: key, + file: file, function: function, line: line + ) return } + queue[key] = continuation + log( + "Tracking", flags: continuation.flags, id: key, + file: file, function: function, line: line + ) } /// Remove continuation associated with provided key from queue. /// - /// - Parameter key: The key in the continuation queue. + /// - Parameters: + /// - continuation: The continuation and flags to remove and cancel. + /// - flags: The flags associated that determine the execution behavior of task. + /// - key: The key in the continuation queue. + /// - file: The file remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function remove request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line remove request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func dequeueContinuation(withKey key: UUID) { + internal func dequeueContinuation( + _ continuation: QueuedContinuation, + withKey key: UUID, + file: String, function: String, line: UInt + ) { + let (continuation, flags) = continuation + log( + "Removing", flags: flags, id: key, + file: file, function: function, line: line + ) + queue.removeValue(forKey: key) + guard !continuation.resumed else { + log( + "Already resumed, not cancelling", flags: flags, id: key, + file: file, function: function, line: line + ) + return + } + + continuation.cancel() + log( + "Cancelled", flags: flags, id: key, + file: file, function: function, line: line + ) } /// Unblock queue allowing other queued tasks to run @@ -243,10 +326,18 @@ public actor TaskQueue: AsyncObject { /// /// Updates the ``blocked`` flag and starts queued tasks /// in order of their addition if any tasks are queued. + /// + /// - Parameters: + /// - file: The file unblock request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The unblock resume request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line unblock request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func unblockQueue() { + internal func unblockQueue(file: String, function: String, line: UInt) { blocked = false - resumeQueuedTasks() + resumeQueuedTasks(file: file, function: function, line: line) } /// Signals completion of operation to the queue @@ -254,23 +345,46 @@ public actor TaskQueue: AsyncObject { /// /// Updates the ``currentRunning`` count and starts /// queued tasks in order of their addition if any queued. + /// + /// - Parameters: + /// - file: The file signal request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func signalCompletion() { - defer { resumeQueuedTasks() } + internal func signalCompletion(file: String, function: String, line: UInt) { + defer { resumeQueuedTasks(file: file, function: function, line: line) } guard currentRunning > 0 else { return } currentRunning -= 1 } /// Resumes queued tasks when queue isn't blocked /// and operation flags preconditions satisfied. + /// + /// - Parameters: + /// - file: The file resume request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function resume request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line resume request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @inlinable - internal func resumeQueuedTasks() { - while let (_, continuation) = queue.elements.first, + internal func resumeQueuedTasks( + file: String, function: String, line: UInt + ) { + while let (key, continuation) = queue.elements.first, !blocked, !continuation.flags.wait(forCurrent: currentRunning) { queue.removeFirst() - guard resumeQueuedContinuation(continuation) else { break } + guard + resumeQueuedContinuation( + continuation, atKey: key, + file: file, function: function, line: line + ) + else { break } } } @@ -281,25 +395,36 @@ public actor TaskQueue: AsyncObject { /// 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. + /// - Parameters: + /// - flags: The flags associated that determine the execution behavior of task. + /// - key: The key associated to task, that requested suspension. + /// - 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: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable internal nonisolated func withPromisedContinuation( - flags: Flags = [] + flags: Flags = [], + withKey key: UUID, + file: String, function: String, line: UInt ) async throws { - let key = UUID() - try await Continuation.withCancellation( - synchronizedWith: locker - ) { + try await Continuation.withCancellation(id: key) { continuation in Task { [weak self] in - await self?.dequeueContinuation(withKey: key) + await self?.dequeueContinuation( + (value: continuation, flags: flags), withKey: key, + file: file, function: function, line: line + ) } - } operation: { continuation in + } operation: { continuation, preinit in Task { [weak self] in await self?.queueContinuation( - atKey: key, - (value: continuation, flags: flags) + (value: continuation, flags: flags), atKey: key, + file: file, function: function, line: line, + preinit: preinit ) } } @@ -310,6 +435,13 @@ public actor TaskQueue: AsyncObject { /// - Parameters: /// - priority: The priority with which operation executed. /// - flags: The flags associated that determine the execution behavior of task. + /// - key: Optional key associated with task. + /// - 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 operation to perform. /// /// - Returns: The result from provided operation. @@ -318,15 +450,21 @@ public actor TaskQueue: AsyncObject { internal func run( with priority: TaskPriority?, flags: Flags, + withKey key: UUID?, + file: String, function: String, line: UInt, operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { - defer { signalCompletion() } + defer { signalCompletion(file: file, function: function, line: line) } typealias LocalTask = Task let taskPriority = flags.choosePriority( fromContext: self.priority, andWork: priority ) + log( + "Executing", flags: flags, id: key, + file: file, function: function, line: line + ) return flags.contains(.detached) ? try await LocalTask.withCancellableDetachedTask( priority: taskPriority, @@ -344,6 +482,13 @@ public actor TaskQueue: AsyncObject { /// - Parameters: /// - priority: The priority with which operation executed. /// - flags: The flags associated that determine the execution behavior of task. + /// - key: Optional key associated with task. + /// - 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 operation to perform. /// /// - Returns: The result from provided operation. @@ -352,13 +497,15 @@ public actor TaskQueue: AsyncObject { internal func runBlocking( with priority: TaskPriority?, flags: Flags, + withKey key: UUID?, + file: String, function: String, line: UInt, operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { - defer { unblockQueue() } + defer { unblockQueue(file: file, function: function, line: line) } blocked = true return try await run( - with: priority, - flags: flags, + with: priority, flags: flags, withKey: key, + file: file, function: function, line: line, operation: operation ) } @@ -376,7 +523,8 @@ public actor TaskQueue: AsyncObject { self.priority = priority } - deinit { self.queue.forEach { $1.value.cancel() } } + // TODO: Explore alternative cleanup for actor + // deinit { self.queue.forEach { $1.value.cancel() } } /// Executes the given operation asynchronously based on the priority and flags. /// @@ -390,6 +538,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 operation to perform. /// - cancellation: The cancellation handler invoked if continuation is cancelled. /// @@ -400,21 +554,23 @@ public actor TaskQueue: AsyncObject { internal func execHelper( priority: TaskPriority? = nil, flags: Flags = [], + file: String, function: String, line: UInt, operation: @Sendable @escaping () async throws -> T, cancellation: (Error) throws -> Void ) async rethrows -> T { func runTask( + withKey key: UUID? = nil, _ operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { return flags.isBlockEnabled ? try await runBlocking( - with: priority, - flags: flags, + with: priority, flags: flags, withKey: key, + file: file, function: function, line: line, operation: operation ) : try await run( - with: priority, - flags: flags, + with: priority, flags: flags, withKey: key, + file: file, function: function, line: line, operation: operation ) } @@ -424,13 +580,28 @@ public actor TaskQueue: AsyncObject { return try await runTask(operation) } + let key = UUID() + log( + "Waiting", flags: flags, id: key, + file: file, function: function, line: line + ) + defer { + log( + "Executed", flags: flags, id: key, + file: file, function: function, line: line + ) + } + do { - try await withPromisedContinuation(flags: flags) + try await withPromisedContinuation( + flags: flags, withKey: key, + file: file, function: function, line: line + ) } catch { try cancellation(error) } - return try await runTask(operation) + return try await runTask(withKey: key, operation) } /// Executes the given throwing operation asynchronously based on the priority and flags. @@ -468,8 +639,8 @@ public actor TaskQueue: AsyncObject { operation: @Sendable @escaping () async throws -> T ) async throws -> T { return try await execHelper( - priority: priority, - flags: flags, + priority: priority, flags: flags, + file: file, function: function, line: line, operation: operation ) { throw $0 } } @@ -504,8 +675,8 @@ public actor TaskQueue: AsyncObject { operation: @Sendable @escaping () async -> T ) async -> T { return await execHelper( - priority: priority, - flags: flags, + priority: priority, flags: flags, + file: file, function: function, line: line, operation: operation ) { _ in withUnsafeCurrentTask { $0?.cancel() } @@ -541,11 +712,8 @@ public actor TaskQueue: AsyncObject { ) { Task { try await exec( - priority: priority, - flags: flags, - file: file, - function: function, - line: line, + priority: priority, flags: flags, + file: file, function: function, line: line, operation: operation ) } @@ -580,11 +748,8 @@ public actor TaskQueue: AsyncObject { ) { Task { await exec( - priority: priority, - flags: flags, - file: file, - function: function, - line: line, + priority: priority, flags: flags, + file: file, function: function, line: line, operation: operation ) } @@ -632,3 +797,84 @@ public actor TaskQueue: AsyncObject { ) { try await Task.sleep(nanoseconds: 0) } } } + +#if canImport(Logging) +import Logging + +extension TaskQueue { + /// Type specific metadata to attach to all log messages. + @usableFromInline + var metadata: Logger.Metadata { + return [ + "obj": "\(self)(\(Unmanaged.passUnretained(self).toOpaque()))", + "blocked": "\(blocked)", + "current_running": "\(currentRunning)", + "priority": "\(priority != nil ? "\(priority!)" : "nil")", + ] + } + + /// Log a message attaching the default type specific metadata + /// and optional identifier. + /// + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE` is set log level is set to `trace`. + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG` is set log level is set to `debug`. + /// Otherwise log level is set to `info`. + /// + /// - Parameters: + /// - message: The message to be logged. + /// - flags: The flags associated that determine the execution behavior of task. + /// - id: Optional identifier associated with message. + /// - file: The file this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#fileID`). + /// - function: The function this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#function`). + /// - line: The line this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#line`). + @inlinable + func log( + _ message: @autoclosure () -> Logger.Message, + flags: Flags, + id: UUID? = nil, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + var metadata = self.metadata + metadata["flags"] = "\(flags)" + if let id = id { metadata["id"] = "\(id)" } + logger.log( + level: level, message(), metadata: metadata, + file: file, function: function, line: line + ) + } +} +#else +extension TaskQueue { + /// Log a message attaching the default type specific metadata + /// and optional identifier. + /// + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_TRACE` is set log level is set to `trace`. + /// If `ASYNCOBJECTS_ENABLE_LOGGING_LEVEL_DEBUG` is set log level is set to `debug`. + /// Otherwise log level is set to `info`. + /// + /// - Parameters: + /// - message: The message to be logged. + /// - flags: The flags associated that determine the execution behavior of task. + /// - id: Optional identifier associated with message. + /// - file: The file this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#fileID`). + /// - function: The function this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#function`). + /// - line: The line this log message originates from (there's usually + /// no need to pass it explicitly as it defaults to `#line`). + @inlinable + nonisolated func log( + _ message: @autoclosure () -> String, + flags: Flags, + id: UUID? = nil, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { /* Do nothing */ } +} +#endif diff --git a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift index 448da435..003b080f 100644 --- a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift +++ b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift @@ -117,13 +117,6 @@ class AsyncCountdownEventTests: XCTestCase { @MainActor class AsyncCountdownEventTimeoutTests: XCTestCase { - func testWaitZeroTimeoutWithoutIncrement() async throws { - let event = AsyncCountdownEvent() - try await Self.checkExecInterval(durationInSeconds: 0) { - try await event.wait(forSeconds: 0) - } - } - func testWaitTimeoutWithIncrement() async throws { let event = AsyncCountdownEvent() event.increment(by: 10) @@ -211,19 +204,6 @@ class AsyncCountdownEventTimeoutTests: XCTestCase { @MainActor class AsyncCountdownEventClockTimeoutTests: XCTestCase { - func testWaitZeroTimeoutWithoutIncrement() async throws { - guard - #available(macOS 13, iOS 16, macCatalyst 16, tvOS 16, watchOS 9, *) - else { - throw XCTSkip("Clock API not available") - } - let clock: ContinuousClock = .continuous - let event = AsyncCountdownEvent() - try await Self.checkExecInterval(duration: .seconds(0), clock: clock) { - try await event.wait(forSeconds: 0, clock: clock) - } - } - func testWaitTimeoutWithIncrement() async throws { guard #available(macOS 13, iOS 16, macCatalyst 16, tvOS 16, watchOS 9, *) diff --git a/Tests/AsyncObjectsTests/AsyncEventTests.swift b/Tests/AsyncObjectsTests/AsyncEventTests.swift index 7dea19d5..06fe4336 100644 --- a/Tests/AsyncObjectsTests/AsyncEventTests.swift +++ b/Tests/AsyncObjectsTests/AsyncEventTests.swift @@ -85,13 +85,6 @@ class AsyncEventTimeoutTests: XCTestCase { } } } - - func testWaitZeroTimeout() async throws { - let event = AsyncEvent(signaledInitially: true) - try await Self.checkExecInterval(durationInSeconds: 0) { - try await event.wait(forNanoseconds: 0) - } - } } #if swift(>=5.7) @@ -147,19 +140,6 @@ class AsyncEventClockTimeoutTests: XCTestCase { } } } - - func testWaitZeroTimeout() async throws { - guard - #available(macOS 13, iOS 16, macCatalyst 16, tvOS 16, watchOS 9, *) - else { - throw XCTSkip("Clock API not available") - } - let clock: ContinuousClock = .continuous - let event = AsyncEvent(signaledInitially: true) - try await Self.checkExecInterval(duration: .seconds(0), clock: clock) { - try await event.wait(forSeconds: 0, clock: clock) - } - } } #endif @@ -204,14 +184,18 @@ fileprivate extension XCTestCase { static func checkWait( for event: AsyncEvent, signalIn interval: UInt64 = 1, - durationInSeconds seconds: Int = 1 + durationInSeconds seconds: Int = 1, + file: StaticString = #filePath, + function: StaticString = #function, + line: UInt = #line ) async throws { Task.detached { try await Self.sleep(seconds: interval) event.signal() } try await Self.checkExecInterval( - durationInSeconds: seconds + durationInSeconds: seconds, + file: file, function: function, line: line ) { try await event.wait() } } } diff --git a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift index 66160ed0..1df716d6 100644 --- a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift +++ b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift @@ -91,10 +91,16 @@ class AsyncSemaphoreTimeoutTests: XCTestCase { taskCount count: Int = 1, withDelay delay: UInt64 = 2, timeout: UInt64 = 1, - durationInSeconds seconds: Int = 0 + durationInSeconds seconds: Int = 0, + file: StaticString = #filePath, + function: StaticString = #function, + line: UInt = #line ) async throws { let semaphore = AsyncSemaphore(value: value) - try await Self.checkExecInterval(durationInSeconds: seconds) { + try await Self.checkExecInterval( + durationInSeconds: seconds, + file: file, function: function, line: line + ) { try await withThrowingTaskGroup(of: Bool.self) { group in for _ in 0...with { c in - let safe = SafeContinuation(status: .waiting, with: c) - XCTAssertFalse(safe.resumed) - safe.resume(returning: 3) - XCTAssertTrue(safe.resumed) - } - XCTAssertEqual(value, 3) - } - - func testResumingWithInitializedStatusResuming() async throws { - let value = await GlobalContinuation.with { c in - let safe = SafeContinuation( - status: .willResume(.success(5)), - with: c - ) - XCTAssertTrue(safe.resumed) - safe.resume(returning: 3) - } - XCTAssertEqual(value, 5) - } - - func testResumingWithInitializedStatusResumed() async throws { - let value = try await GlobalContinuation.with { c in - c.resume(returning: 3) - let safe = SafeContinuation(status: .resumed, with: c) - XCTAssertTrue(safe.resumed) - safe.resume(throwing: CancellationError()) - } - XCTAssertEqual(value, 3) - } - - func testDirectResumeWithSuccess() async throws { - await Self.checkExecInterval(durationInSeconds: 0) { - await SafeContinuation>.with { c in - XCTAssertFalse(c.resumed) - c.resume() - XCTAssertTrue(c.resumed) - } - } - } - - func testDirectResumeWithError() async throws { - typealias C = GlobalContinuation - await Self.checkExecInterval(durationInSeconds: 0) { - do { - try await SafeContinuation.with { c in - XCTAssertFalse(c.resumed) - c.resume(throwing: CancellationError()) - XCTAssertTrue(c.resumed) - } - XCTFail("Unexpected task progression") - } catch { - XCTAssertTrue(type(of: error) == CancellationError.self) - } - } - } - - func testInitializedWithoutContinuationWithStatusWaiting() async throws { - typealias C = GlobalContinuation - let value = await C.with { c in - let safe = SafeContinuation(status: .waiting) - XCTAssertFalse(safe.resumed) - safe.resume(returning: 3) - XCTAssertTrue(safe.resumed) - safe.add(continuation: c) - } - XCTAssertEqual(value, 3) - } - - func testInitializedWithoutContinuationWithStatusResuming() async throws { - typealias C = GlobalContinuation - let value = await C.with { c in - let safe = SafeContinuation(status: .willResume(.success(5))) - XCTAssertTrue(safe.resumed) - safe.resume(returning: 3) - safe.add(continuation: c) - } - XCTAssertEqual(value, 5) - } - - func testStatusUpdateFromWaitingToResuming() async throws { - typealias C = GlobalContinuation - let value = await C.with { c in - let safe = SafeContinuation(status: .waiting) - XCTAssertFalse(safe.resumed) - safe.add(continuation: c, status: .willResume(.success(5))) - XCTAssertTrue(safe.resumed) - } - XCTAssertEqual(value, 5) - } - - func testStatusUpdateFromWaitingToResumed() async throws { - typealias C = GlobalContinuation - let value = await C.with { c in - let safe = SafeContinuation(status: .waiting) - XCTAssertFalse(safe.resumed) - c.resume(returning: 5) - safe.add(continuation: c, status: .resumed) - XCTAssertTrue(safe.resumed) - } - XCTAssertEqual(value, 5) - } - - func testStatusUpdateFromResumedToWaiting() async throws { - typealias C = GlobalContinuation - let value = await C.with { c in - c.resume(returning: 5) - let safe = SafeContinuation(status: .resumed) - XCTAssertTrue(safe.resumed) - safe.add(continuation: c, status: .waiting) - XCTAssertTrue(safe.resumed) - } - XCTAssertEqual(value, 5) - } - - func testCancellationHandlerWhenTaskCancelled() async throws { - typealias C = GlobalContinuation - let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { - do { - try await SafeContinuation.withCancellation { - // Do nothing - } operation: { _ in - // Do nothing - } - XCTFail("Unexpected task progression") - } catch { - XCTAssertTrue(type(of: error) == CancellationError.self) - } - } - } - task.cancel() - await task.value - } - - func testCancellationHandlerForAlreadyCancelledTask() async throws { - typealias C = GlobalContinuation - let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { - do { - try await Self.sleep(seconds: 5) - XCTFail("Unexpected task progression") - } catch {} - XCTAssertTrue(Task.isCancelled) - do { - try await SafeContinuation.withCancellation { - } operation: { _ in - } - XCTFail("Unexpected task progression") - } catch { - XCTAssertTrue(type(of: error) == CancellationError.self) - } - } - } - 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/TaskOperationTests.swift b/Tests/AsyncObjectsTests/TaskOperationTests.swift index a11a456f..4d47d61e 100644 --- a/Tests/AsyncObjectsTests/TaskOperationTests.swift +++ b/Tests/AsyncObjectsTests/TaskOperationTests.swift @@ -125,9 +125,7 @@ class TaskOperationTimeoutTests: XCTestCase { func testFinishedWait() async throws { let operation = TaskOperation { /* Do nothing */ } operation.signal() - try await Self.checkExecInterval(durationInSeconds: 0) { - try await operation.wait(forNanoseconds: 2) - } + try await operation.wait(forSeconds: 1) } func testWaitTimeout() async throws { @@ -144,14 +142,6 @@ class TaskOperationTimeoutTests: XCTestCase { } } } - - func testWaitZeroTimeout() async throws { - let operation = TaskOperation { /* Do nothing */ } - operation.signal() - try await Self.checkExecInterval(durationInSeconds: 0) { - try await operation.wait(forNanoseconds: 0) - } - } } #if swift(>=5.7) @@ -210,20 +200,6 @@ class TaskOperationClockTimeoutTests: XCTestCase { } } } - - func testWaitZeroTimeout() async throws { - guard - #available(macOS 13, iOS 16, macCatalyst 16, tvOS 16, watchOS 9, *) - else { - throw XCTSkip("Clock API not available") - } - let clock: ContinuousClock = .continuous - let operation = TaskOperation { /* Do nothing */ } - operation.signal() - try await Self.checkExecInterval(duration: .seconds(0), clock: clock) { - try await operation.wait(forSeconds: 0, clock: clock) - } - } } #endif diff --git a/Tests/AsyncObjectsTests/TaskQueueTests.swift b/Tests/AsyncObjectsTests/TaskQueueTests.swift index 3953c042..8d211b05 100644 --- a/Tests/AsyncObjectsTests/TaskQueueTests.swift +++ b/Tests/AsyncObjectsTests/TaskQueueTests.swift @@ -80,14 +80,18 @@ class TaskQueueTests: XCTestCase { class TaskQueueTimeoutTests: XCTestCase { private static func checkWaitTimeoutOnQueue( - option: TaskOption + option: TaskOption, + file: StaticString = #filePath, + function: StaticString = #function, + line: UInt = #line ) async throws { let queue = TaskQueue(priority: option.queue) try await Self.checkExecInterval( name: "For queue priority: \(option.queue.str), " + "task priority: \(option.task.str) " + "and flags: \(option.flags.rawValue)", - durationInSeconds: 1 + durationInSeconds: 1, + file: file, function: function, line: line ) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -103,10 +107,14 @@ class TaskQueueTimeoutTests: XCTestCase { group.addTask { do { try await queue.wait(forSeconds: 1) - XCTFail("Unexpected task progression") + XCTFail( + "Unexpected task progression", + file: file, line: line + ) } catch { XCTAssertTrue( - type(of: error) == DurationTimeoutError.self + type(of: error) == DurationTimeoutError.self, + file: file, line: line ) } } @@ -144,14 +152,18 @@ class TaskQueueTimeoutTests: XCTestCase { @available(macOS 13, iOS 16, macCatalyst 16, tvOS 16, watchOS 9, *) private static func checkWaitTimeoutOnQueue( option: TaskOption, - clock: C + clock: C, + file: StaticString = #filePath, + function: StaticString = #function, + line: UInt = #line ) async throws where C.Duration == Duration { let queue = TaskQueue(priority: option.queue) try await Self.checkExecInterval( name: "For queue priority: \(option.queue.str), " + "task priority: \(option.task.str) " + "and flags: \(option.flags.rawValue)", - durationInSeconds: 1 + durationInSeconds: 1, + file: file, function: function, line: line ) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -167,11 +179,15 @@ class TaskQueueTimeoutTests: XCTestCase { group.addTask { do { try await queue.wait(forSeconds: 1, clock: clock) - XCTFail("Unexpected task progression") + XCTFail( + "Unexpected task progression", + file: file, line: line + ) } catch { XCTAssertTrue( type(of: error) - == TimeoutError.self + == TimeoutError.self, + file: file, line: line ) } } @@ -944,13 +960,19 @@ fileprivate extension XCTestCase { queue: TaskPriority?, task: TaskPriority?, flags: TaskQueue.Flags ) - static func checkWaitOnQueue(option: TaskOption) async throws { + static func checkWaitOnQueue( + option: TaskOption, + file: StaticString = #filePath, + function: StaticString = #function, + line: UInt = #line + ) async throws { let queue = TaskQueue(priority: option.queue) try await Self.checkExecInterval( name: "For queue priority: \(option.queue.str), " + "task priority: \(option.task.str) " + "and flags: \(option.flags.rawValue)", - durationInSeconds: 1 + durationInSeconds: 1, + file: file, function: function, line: line ) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { diff --git a/Tests/AsyncObjectsTests/TrackedContinuationTests.swift b/Tests/AsyncObjectsTests/TrackedContinuationTests.swift new file mode 100644 index 00000000..845a7159 --- /dev/null +++ b/Tests/AsyncObjectsTests/TrackedContinuationTests.swift @@ -0,0 +1,126 @@ +import XCTest +@testable import AsyncObjects + +@MainActor +class TrackedContinuationTests: XCTestCase { + + func testResumingWithInitializedStatusWaiting() async throws { + let value = await GlobalContinuation.with { c in + let safe = TrackedContinuation(with: c) + XCTAssertFalse(safe.resumed) + safe.resume(returning: 3) + XCTAssertTrue(safe.resumed) + } + XCTAssertEqual(value, 3) + } + + func testDirectResumeWithSuccess() async throws { + await Self.checkExecInterval(durationInSeconds: 0) { + await TrackedContinuation>.with { + XCTAssertFalse($0.resumed) + $0.resume() + XCTAssertTrue($0.resumed) + } + } + } + + func testDirectResumeWithError() async throws { + typealias C = GlobalContinuation + await Self.checkExecInterval(durationInSeconds: 0) { + do { + try await TrackedContinuation.with { c in + XCTAssertFalse(c.resumed) + c.resume(throwing: CancellationError()) + XCTAssertTrue(c.resumed) + } + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } + } + } + + func testInitializedWithoutContinuationWithStatusWaiting() async throws { + typealias C = GlobalContinuation + let value = await C.with { c in + let safe = TrackedContinuation() + XCTAssertFalse(safe.resumed) + safe.resume(returning: 3) + XCTAssertTrue(safe.resumed) + safe.add(continuation: c) + } + XCTAssertEqual(value, 3) + } + + func testCancellationHandlerWhenTaskCancelled() async throws { + typealias C = GlobalContinuation + let task = Task.detached { + await Self.checkExecInterval(durationInSeconds: 0) { + do { + try await TrackedContinuation + .withCancellation(id: .init()) { + $0.cancel() + } operation: { _, preinit in + preinit() + } + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } + } + } + task.cancel() + await task.value + } + + func testCancellationHandlerForAlreadyCancelledTask() async throws { + typealias C = GlobalContinuation + let task = Task.detached { + await Self.checkExecInterval(durationInSeconds: 0) { + do { + try await Self.sleep(seconds: 5) + XCTFail("Unexpected task progression") + } catch {} + XCTAssertTrue(Task.isCancelled) + do { + try await TrackedContinuation + .withCancellation(id: .init()) { + $0.cancel() + } operation: { _, preinit in + preinit() + } + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } + } + } + 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 TrackedContinuation + .withCancellation(id: .init()) { _ in + // Do nothing + } operation: { continuation, preinit in + preinit() + Task { + defer { continuation.resume() } + try await Self.sleep(seconds: 1) + } + } + } + } + task.cancel() + await task.value + } +} diff --git a/Tests/AsyncObjectsTests/XCTestCase.swift b/Tests/AsyncObjectsTests/XCTestCase.swift index c4ea0fc1..3419cf2f 100644 --- a/Tests/AsyncObjectsTests/XCTestCase.swift +++ b/Tests/AsyncObjectsTests/XCTestCase.swift @@ -27,6 +27,9 @@ extension XCTestCase { static func checkExecInterval( name: String? = nil, durationInSeconds seconds: T = .zero, + file: StaticString = #filePath, + function: StaticString = #function, + line: UInt = #line, for task: () async throws -> Void ) async rethrows where T: Comparable { let second: T = 1_000_000_000 @@ -35,10 +38,20 @@ extension XCTestCase { guard let span = T(exactly: DispatchTime.now().uptimeNanoseconds - time), case let duration = span / second - else { XCTFail("Invalid number type: \(T.self)"); return } + else { + XCTFail("Invalid number type: \(T.self)", file: file, line: line) + return + } + let assertions = { - XCTAssertLessThanOrEqual(duration, seconds + 1) - XCTAssertGreaterThanOrEqual(duration, seconds - 1) + XCTAssertLessThanOrEqual( + duration, seconds + 1, + file: file, line: line + ) + XCTAssertGreaterThanOrEqual( + duration, seconds - 1, + file: file, line: line + ) } runAssertions(with: name, assertions) } @@ -46,6 +59,9 @@ extension XCTestCase { static func checkExecInterval( name: String? = nil, durationInRange range: R, + file: StaticString = #filePath, + function: StaticString = #function, + line: UInt = #line, for task: () async throws -> Void ) async rethrows where R.Bound: DivisiveArithmetic { let second: R.Bound = 1_000_000_000 @@ -56,11 +72,16 @@ extension XCTestCase { exactly: DispatchTime.now().uptimeNanoseconds - time ), case let duration = span / second - else { XCTFail("Invalid range type: \(R.self)"); return } + else { + XCTFail("Invalid range type: \(R.self)", file: file, line: line) + return + } + let assertions = { XCTAssertTrue( range.contains(duration), - "\(duration) not present in \(range)" + "\(duration) not present in \(range)", + file: file, line: line ) } runAssertions(with: name, assertions) @@ -94,13 +115,16 @@ extension XCTestCase { name: String? = nil, duration: C.Instant.Duration = .zero, clock: C, + file: StaticString = #filePath, + function: StaticString = #function, + line: UInt = #line, for task: () async throws -> Void ) async rethrows where C.Duration == Duration { let result = try await clock.measure { try await task() } let assertions = { XCTAssertLessThanOrEqual( - abs(duration.components.seconds - result.components.seconds), - 1 + abs(duration.components.seconds - result.components.seconds), 1, + file: file, line: line ) } runAssertions(with: name, assertions) diff --git a/package.json b/package.json index c4fddf1a..9016b5b4 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "scripts": { "build": "npm exec --package=swiftylab-ci -- build.js", "xcodebuild": "npm exec --package=swiftylab-ci -- xcodebuild.js", - "test": "npm exec --package=swiftylab-ci -- test.js --parallel", + "test": "ASYNCOBJECTS_ENABLE_LOGGING_LEVEL=info npm exec --package=swiftylab-ci -- test.js --parallel", "archive": "npm exec --package=swiftylab-ci -- archive.js", "pod-lint": "npm exec --package=swiftylab-ci -- pod-lint.js", "generate": "npm exec --package=swiftylab-ci -- generate.js",