Skip to content

Commit 29efab6

Browse files
committed
[Concurrency] Fix Task.sleep on values greater than Int64.max.
In the Dispatch implementation, clamp the delay to INT64_MAX. Swift's nanoseconds value is unsigned, but we ultimately use it with dispatch_time, which takes a signed int64_t. Extremely large values get interpreted as negative, which results in not sleeping. INT64_MAX nanoseconds is about 292 years, so it should be difficult to notice a practical effect from sleeping for less time than requested due to the clamping. rdar://143278824
1 parent 283ce7f commit 29efab6

File tree

2 files changed

+35
-0
lines changed

2 files changed

+35
-0
lines changed

stdlib/public/Concurrency/DispatchGlobalExecutor.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,13 @@ void swift_task_enqueueGlobalWithDelayImpl(SwiftJobDelay delay,
276276
job->schedulerPrivate[SwiftJobDispatchQueueIndex] =
277277
DISPATCH_QUEUE_GLOBAL_EXECUTOR;
278278

279+
// dispatch_time takes a signed int64_t. SwiftJobDelay is unsigned, so
280+
// extremely large values get interpreted as negative numbers, which results
281+
// in zero delay. Clamp the value to INT64_MAX. That's about 292 years, so
282+
// there should be no noticeable difference.
283+
if (delay > (SwiftJobDelay)INT64_MAX)
284+
delay = INT64_MAX;
285+
279286
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, delay);
280287
dispatch_after_f(when, queue, dispatchContext, dispatchFunction);
281288
}

test/Concurrency/Runtime/async_task_sleep.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Dispatch
1818
static func main() async {
1919
await testSleepDuration()
2020
await testSleepDoesNotBlock()
21+
await testSleepHuge()
2122
}
2223

2324
static func testSleepDuration() async {
@@ -45,4 +46,31 @@ import Dispatch
4546
// CHECK: Run second
4647
await task.get()
4748
}
49+
50+
static func testSleepHuge() async {
51+
// Make sure nanoseconds values about Int64.max don't get interpreted as
52+
// negative and fail to sleep.
53+
let task1 = detach {
54+
try await Task.sleep(nanoseconds: UInt64(Int64.max) + 1)
55+
}
56+
let task2 = detach {
57+
try await Task.sleep(nanoseconds: UInt64.max)
58+
}
59+
60+
try! await Task.sleep(nanoseconds: UInt64(pause))
61+
62+
task1.cancel()
63+
task2.cancel()
64+
65+
// These should throw due to being canceled. If the sleeps completed then
66+
// the cancellation will do nothing and we won't throw, which is a failure.
67+
do {
68+
_ = try await task1.value
69+
fatalError("Sleep 1 completed early.")
70+
} catch {}
71+
do {
72+
_ = try await task2.value
73+
fatalError("Sleep 2 completed early.")
74+
} catch {}
75+
}
4876
}

0 commit comments

Comments
 (0)