Skip to content

Commit 5854d8e

Browse files
authored
Merge pull request #19 from uber/executor-track_task_id-master
Allow tracking task ID to support reporting timeout error details
2 parents 78fe329 + af1c389 commit 5854d8e

File tree

7 files changed

+167
-25
lines changed

7 files changed

+167
-25
lines changed

Concurrency.xcodeproj/project.pbxproj

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
41B94842210A4744007E59C8 /* Task.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
4141
41B94848210A4756007E59C8 /* ConcurrentSequenceExecutorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConcurrentSequenceExecutorTests.swift; sourceTree = "<group>"; };
4242
"Concurrency::Concurrency::Product" /* Concurrency.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Concurrency.framework; sourceTree = BUILT_PRODUCTS_DIR; };
43-
"Concurrency::ConcurrencyTests::Product" /* ConcurrencyTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = ConcurrencyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
43+
"Concurrency::ConcurrencyTests::Product" /* ConcurrencyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = ConcurrencyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4444
OBJ_10 /* AtomicInt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicInt.swift; sourceTree = "<group>"; };
4545
OBJ_11 /* AtomicReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicReference.swift; sourceTree = "<group>"; };
4646
OBJ_12 /* CountDownLatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountDownLatch.swift; sourceTree = "<group>"; };
@@ -193,7 +193,7 @@
193193
OBJ_1 /* Project object */ = {
194194
isa = PBXProject;
195195
attributes = {
196-
LastUpgradeCheck = 9999;
196+
LastUpgradeCheck = 1000;
197197
};
198198
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Concurrency" */;
199199
compatibilityVersion = "Xcode 3.2";
@@ -302,12 +302,38 @@
302302
isa = XCBuildConfiguration;
303303
buildSettings = {
304304
CLANG_ENABLE_OBJC_ARC = YES;
305+
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
306+
CLANG_WARN_BOOL_CONVERSION = YES;
307+
CLANG_WARN_COMMA = YES;
308+
CLANG_WARN_CONSTANT_CONVERSION = YES;
309+
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
310+
CLANG_WARN_EMPTY_BODY = YES;
311+
CLANG_WARN_ENUM_CONVERSION = YES;
312+
CLANG_WARN_INFINITE_RECURSION = YES;
313+
CLANG_WARN_INT_CONVERSION = YES;
314+
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
315+
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
316+
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
317+
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
318+
CLANG_WARN_STRICT_PROTOTYPES = YES;
319+
CLANG_WARN_SUSPICIOUS_MOVE = YES;
320+
CLANG_WARN_UNREACHABLE_CODE = YES;
321+
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
305322
COMBINE_HIDPI_IMAGES = YES;
306323
COPY_PHASE_STRIP = NO;
307324
DEBUG_INFORMATION_FORMAT = dwarf;
308325
DYLIB_INSTALL_NAME_BASE = "@rpath";
309326
ENABLE_NS_ASSERTIONS = YES;
327+
ENABLE_STRICT_OBJC_MSGSEND = YES;
328+
ENABLE_TESTABILITY = YES;
329+
GCC_NO_COMMON_BLOCKS = YES;
310330
GCC_OPTIMIZATION_LEVEL = 0;
331+
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
332+
GCC_WARN_ABOUT_RETURN_TYPE = YES;
333+
GCC_WARN_UNDECLARED_SELECTOR = YES;
334+
GCC_WARN_UNINITIALIZED_AUTOS = YES;
335+
GCC_WARN_UNUSED_FUNCTION = YES;
336+
GCC_WARN_UNUSED_VARIABLE = YES;
311337
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
312338
MACOSX_DEPLOYMENT_TARGET = 10.10;
313339
ONLY_ACTIVE_ARCH = YES;
@@ -325,11 +351,36 @@
325351
isa = XCBuildConfiguration;
326352
buildSettings = {
327353
CLANG_ENABLE_OBJC_ARC = YES;
354+
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
355+
CLANG_WARN_BOOL_CONVERSION = YES;
356+
CLANG_WARN_COMMA = YES;
357+
CLANG_WARN_CONSTANT_CONVERSION = YES;
358+
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
359+
CLANG_WARN_EMPTY_BODY = YES;
360+
CLANG_WARN_ENUM_CONVERSION = YES;
361+
CLANG_WARN_INFINITE_RECURSION = YES;
362+
CLANG_WARN_INT_CONVERSION = YES;
363+
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
364+
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
365+
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
366+
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
367+
CLANG_WARN_STRICT_PROTOTYPES = YES;
368+
CLANG_WARN_SUSPICIOUS_MOVE = YES;
369+
CLANG_WARN_UNREACHABLE_CODE = YES;
370+
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
328371
COMBINE_HIDPI_IMAGES = YES;
329372
COPY_PHASE_STRIP = YES;
330373
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
331374
DYLIB_INSTALL_NAME_BASE = "@rpath";
375+
ENABLE_STRICT_OBJC_MSGSEND = YES;
376+
GCC_NO_COMMON_BLOCKS = YES;
332377
GCC_OPTIMIZATION_LEVEL = s;
378+
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
379+
GCC_WARN_ABOUT_RETURN_TYPE = YES;
380+
GCC_WARN_UNDECLARED_SELECTOR = YES;
381+
GCC_WARN_UNINITIALIZED_AUTOS = YES;
382+
GCC_WARN_UNUSED_FUNCTION = YES;
383+
GCC_WARN_UNUSED_VARIABLE = YES;
333384
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
334385
MACOSX_DEPLOYMENT_TARGET = 10.10;
335386
OTHER_SWIFT_FLAGS = "-DXcode";
@@ -345,7 +396,7 @@
345396
OBJ_45 /* Debug */ = {
346397
isa = XCBuildConfiguration;
347398
buildSettings = {
348-
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
399+
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
349400
FRAMEWORK_SEARCH_PATHS = (
350401
"$(inherited)",
351402
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
@@ -364,7 +415,7 @@
364415
OBJ_46 /* Release */ = {
365416
isa = XCBuildConfiguration;
366417
buildSettings = {
367-
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
418+
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
368419
FRAMEWORK_SEARCH_PATHS = (
369420
"$(inherited)",
370421
"$(PLATFORM_DIR)/Developer/Library/Frameworks",

Concurrency.xcodeproj/xcshareddata/xcschemes/Concurrency-Package.xcscheme renamed to Concurrency.xcodeproj/xcshareddata/xcschemes/Concurrency.xcscheme

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "9999"
3+
LastUpgradeVersion = "1000"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"
@@ -28,16 +28,6 @@
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
2929
shouldUseLaunchSchemeArgsEnv = "YES">
3030
<Testables>
31-
<TestableReference
32-
skipped = "NO">
33-
<BuildableReference
34-
BuildableIdentifier = "primary"
35-
BlueprintIdentifier = "Concurrency::ConcurrencyTests"
36-
BuildableName = "ConcurrencyTests.xctest"
37-
BlueprintName = "ConcurrencyTests"
38-
ReferencedContainer = "container:Concurrency.xcodeproj">
39-
</BuildableReference>
40-
</TestableReference>
4131
</Testables>
4232
<AdditionalOptions>
4333
</AdditionalOptions>
@@ -70,6 +60,15 @@
7060
savedToolIdentifier = ""
7161
useCustomWorkingDirectory = "NO"
7262
debugDocumentVersioning = "YES">
63+
<MacroExpansion>
64+
<BuildableReference
65+
BuildableIdentifier = "primary"
66+
BlueprintIdentifier = "Concurrency::Concurrency"
67+
BuildableName = "Concurrency.framework"
68+
BlueprintName = "Concurrency"
69+
ReferencedContainer = "container:Concurrency.xcodeproj">
70+
</BuildableReference>
71+
</MacroExpansion>
7372
</ProfileAction>
7473
<AnalyzeAction
7574
buildConfiguration = "Debug">
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1000"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
</BuildAction>
9+
<TestAction
10+
buildConfiguration = "Debug"
11+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
12+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
13+
shouldUseLaunchSchemeArgsEnv = "YES">
14+
<Testables>
15+
<TestableReference
16+
skipped = "NO">
17+
<BuildableReference
18+
BuildableIdentifier = "primary"
19+
BlueprintIdentifier = "Concurrency::ConcurrencyTests"
20+
BuildableName = "ConcurrencyTests.xctest"
21+
BlueprintName = "ConcurrencyTests"
22+
ReferencedContainer = "container:Concurrency.xcodeproj">
23+
</BuildableReference>
24+
</TestableReference>
25+
</Testables>
26+
<AdditionalOptions>
27+
</AdditionalOptions>
28+
</TestAction>
29+
<LaunchAction
30+
buildConfiguration = "Debug"
31+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
32+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
33+
launchStyle = "0"
34+
useCustomWorkingDirectory = "NO"
35+
ignoresPersistentStateOnLaunch = "NO"
36+
debugDocumentVersioning = "YES"
37+
debugServiceExtension = "internal"
38+
allowLocationSimulation = "YES">
39+
<AdditionalOptions>
40+
</AdditionalOptions>
41+
</LaunchAction>
42+
<ProfileAction
43+
buildConfiguration = "Release"
44+
shouldUseLaunchSchemeArgsEnv = "YES"
45+
savedToolIdentifier = ""
46+
useCustomWorkingDirectory = "NO"
47+
debugDocumentVersioning = "YES">
48+
</ProfileAction>
49+
<AnalyzeAction
50+
buildConfiguration = "Debug">
51+
</AnalyzeAction>
52+
<ArchiveAction
53+
buildConfiguration = "Release"
54+
revealArchiveInOrganizer = "YES">
55+
</ArchiveAction>
56+
</Scheme>

Sources/Concurrency/Executor/ConcurrentSequenceExecutor.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,15 @@ public class ConcurrentSequenceExecutor: SequenceExecutor {
2727
/// - parameter name: The name of the executor.
2828
/// - parameter qos: The quality of service of this executor. This
2929
/// defaults to `userInitiated`.
30-
public init(name: String, qos: DispatchQoS = .userInitiated) {
30+
/// - parameter shouldTackTaskId: `true` if task IDs should be tracked
31+
/// as tasks are executed. `false` otherwise. By tracking the task IDs,
32+
/// if waiting on the completion of a task sequence times out, the
33+
/// reported error contains the ID of the task that was being executed
34+
/// when the timeout occurred. The tracking does incur a minor
35+
/// performance cost. This value defaults to `false`.
36+
public init(name: String, qos: DispatchQoS = .userInitiated, shouldTackTaskId: Bool = false) {
3137
taskQueue = DispatchQueue(label: "Executor.taskQueue-\(name)", qos: qos, attributes: .concurrent)
38+
self.shouldTackTaskId = shouldTackTaskId
3239
}
3340

3441
/// Execute a sequence of tasks concurrently from the given initial task.
@@ -51,13 +58,18 @@ public class ConcurrentSequenceExecutor: SequenceExecutor {
5158
// MARK: - Private
5259

5360
private let taskQueue: DispatchQueue
61+
private let shouldTackTaskId: Bool
5462

5563
private func execute<SequenceResultType>(_ task: Task, with sequenceHandle: SynchronizedSequenceExecutionHandle<SequenceResultType>, _ execution: @escaping (Task, Any) -> SequenceExecution<SequenceResultType>) {
5664
taskQueue.async {
5765
guard !sequenceHandle.isCancelled else {
5866
return
5967
}
6068

69+
if self.shouldTackTaskId {
70+
sequenceHandle.willBeginExecuting(taskId: task.id)
71+
}
72+
6173
let result = task.typeErasedExecute()
6274
let nextExecution = execution(task, result)
6375
switch nextExecution {
@@ -74,6 +86,7 @@ private class SynchronizedSequenceExecutionHandle<SequenceResultType>: SequenceE
7486

7587
private let latch = CountDownLatch(count: 1)
7688
private let didCancel = AtomicBool(initialValue: false)
89+
private let currentTaskId = AtomicInt(initialValue: nonTrackingDefaultTaskId)
7790

7891
// Use a lock to ensure result is properly accessed, since the read
7992
// `await` method may be invoked on a different thread than the write
@@ -85,10 +98,14 @@ private class SynchronizedSequenceExecutionHandle<SequenceResultType>: SequenceE
8598
return didCancel.value
8699
}
87100

101+
fileprivate func willBeginExecuting(taskId: Int) {
102+
currentTaskId.value = taskId
103+
}
104+
88105
fileprivate override func await(withTimeout timeout: TimeInterval?) throws -> SequenceResultType {
89106
let didComplete = latch.await(timeout: timeout)
90107
if !didComplete {
91-
throw SequenceExecutionError.awaitTimeout
108+
throw SequenceExecutionError.awaitTimeout(currentTaskId.value)
92109
}
93110

94111
resultLock.lock()

Sources/Concurrency/Executor/SequenceExecutor.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ import Foundation
1818

1919
/// Errors that can occur during a sequence execution.
2020
public enum SequenceExecutionError: Error {
21-
/// The waiting on sequence completion timed out.
22-
case awaitTimeout
21+
/// The waiting on sequence completion timed out. The `Int` value
22+
/// indicates the ID of the task that was being executed when the
23+
/// timeout occurred. If the value is `nonTrackingDefaultTaskId`,
24+
/// then the executor was not configured to track task IDs during
25+
/// initialization.
26+
case awaitTimeout(Int)
2327
}
2428

2529
/// The handle of the execution of a sequence of tasks, that allows control

Sources/Concurrency/Executor/Task.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616

1717
import Foundation
1818

19+
public let nonTrackingDefaultTaskId = Int.min
20+
1921
/// An individual unit of work that can be executed in a concurrent
2022
/// environment by an executor.
2123
// Task cannot be generic since it needs to be referenced by the executor
2224
// class which cannot provide type information for specific tasks.
2325
public protocol Task {
2426

27+
/// A unique ID number identifying the task.
28+
var id: Int { get }
29+
2530
/// Execute this task without any type information.
2631
///
2732
/// - note: This method should only be used by internal executor
@@ -41,8 +46,16 @@ public protocol Task {
4146
// wildcard generics.
4247
open class AbstractTask<ResultType>: Task {
4348

49+
/// A unique ID number identifying the task.
50+
public let id: Int
51+
4452
/// Initializer.
45-
public init() {}
53+
///
54+
/// - parameter id: A unique ID number identifying the task. This value
55+
/// defaults to `nonTrackingDefaultTaskId`.
56+
public init(id: Int = nonTrackingDefaultTaskId) {
57+
self.id = id
58+
}
4659

4760
/// Execute this task without any type information.
4861
///

Tests/ConcurrencyTests/Executor/ConcurrentSequenceExecutorTests.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,14 @@ class ConcurrentSequenceExecutorTests: XCTestCase {
122122
}
123123

124124
func test_executeSequence_withNonTerminatingSequence_withTimeout_verifyAwaitTimeout() {
125-
let executor = ConcurrentSequenceExecutor(name: "test_executeSequence_withNonTerminatingSequence_withTimeout_verifyAwaitTimeout")
125+
let executor = ConcurrentSequenceExecutor(name: "test_executeSequence_withNonTerminatingSequence_withTimeout_verifyAwaitTimeout", shouldTackTaskId: true)
126126

127-
let sequencedTask = MockSelfRepeatingTask {
127+
let sequencedTask = MockSelfRepeatingTask(id: 123) {
128128
return 0
129129
}
130130

131131
let handle = executor.executeSequence(from: sequencedTask) { _, _ -> SequenceExecution<Int> in
132-
return .continueSequence(MockSelfRepeatingTask {
132+
return .continueSequence(MockSelfRepeatingTask(id: 123) {
133133
return 0
134134
})
135135
}
@@ -138,10 +138,11 @@ class ConcurrentSequenceExecutorTests: XCTestCase {
138138
let startTime = CACurrentMediaTime()
139139
do {
140140
_ = try handle.await(withTimeout: 0.5)
141-
} catch SequenceExecutionError.awaitTimeout {
141+
} catch SequenceExecutionError.awaitTimeout(let id) {
142142
didThrowError = true
143143
let endTime = CACurrentMediaTime()
144144
XCTAssertTrue((endTime - startTime) >= 0.5)
145+
XCTAssertEqual(id, 123)
145146
} catch {
146147
XCTFail("Incorrect error thrown: \(error)")
147148
}
@@ -154,8 +155,9 @@ class MockSelfRepeatingTask: AbstractTask<Int> {
154155

155156
private let execution: () -> Int
156157

157-
init(execution: @escaping () -> Int) {
158+
init(id: Int = nonTrackingDefaultTaskId, execution: @escaping () -> Int) {
158159
self.execution = execution
160+
super.init(id: id)
159161
}
160162

161163
override func execute() -> Int {

0 commit comments

Comments
 (0)