Skip to content

Commit fd9f714

Browse files
authored
Resolve a structural calculation error with clocks that prevented sleep to properly transmit to dispatch_after (swiftlang#42518)
* Resolve a structural calculation error with clocks that prevented sleep to properly transmit to dispatch_after * Remove dispatch dependency
1 parent 320e4c6 commit fd9f714

File tree

2 files changed

+81
-49
lines changed

2 files changed

+81
-49
lines changed

stdlib/public/Concurrency/DispatchGlobalExecutor.inc

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -225,44 +225,7 @@ static void swift_task_enqueueGlobalWithDelayImpl(JobDelay delay,
225225
dispatch_after_f(when, queue, dispatchContext, dispatchFunction);
226226
}
227227

228-
// TODO: The following is cribbed from libdispatch, we should replace it with an official API
229-
typedef enum {
230-
DISPATCH_CLOCK_UPTIME,
231-
DISPATCH_CLOCK_MONOTONIC,
232-
DISPATCH_CLOCK_WALL,
233-
#define DISPATCH_CLOCK_COUNT (DISPATCH_CLOCK_WALL + 1)
234-
} dispatch_clock_t;
235-
236228
#define DISPATCH_UP_OR_MONOTONIC_TIME_MASK (1ULL << 63)
237-
#define DISPATCH_WALLTIME_MASK (1ULL << 62)
238-
#define DISPATCH_TIME_MAX_VALUE (DISPATCH_WALLTIME_MASK - 1)
239-
static inline uint64_t
240-
_dispatch_time_nano2mach(uint64_t value) {
241-
#if HAS_MACH_TIME
242-
struct mach_timebase_info info = calculateTimebase();
243-
return (value * info.denom) / info.numer;
244-
#else
245-
return value;
246-
#endif
247-
}
248-
249-
static inline dispatch_time_t
250-
_dispatch_clock_and_value_to_time(dispatch_clock_t clock, uint64_t value)
251-
{
252-
if (value >= DISPATCH_TIME_MAX_VALUE) {
253-
return DISPATCH_TIME_FOREVER;
254-
}
255-
switch (clock) {
256-
case DISPATCH_CLOCK_WALL:
257-
return -_dispatch_time_nano2mach(value);
258-
case DISPATCH_CLOCK_UPTIME:
259-
return _dispatch_time_nano2mach(value);
260-
case DISPATCH_CLOCK_MONOTONIC:
261-
return _dispatch_time_nano2mach(value) | DISPATCH_UP_OR_MONOTONIC_TIME_MASK;
262-
}
263-
__builtin_unreachable();
264-
}
265-
// END: REPLACEMENT
266229

267230
SWIFT_CC(swift)
268231
static void swift_task_enqueueGlobalWithDeadlineImpl(long long sec,
@@ -282,18 +245,16 @@ static void swift_task_enqueueGlobalWithDeadlineImpl(long long sec,
282245
job->SchedulerPrivate[Job::DispatchQueueIndex] =
283246
DISPATCH_QUEUE_GLOBAL_EXECUTOR;
284247

285-
dispatch_time_t when;
286-
switch (clock) {
287-
case swift_clock_id_continuous: {
288-
uint64_t q = sec * NSEC_PER_SEC + nsec;
289-
when = _dispatch_clock_and_value_to_time(DISPATCH_CLOCK_MONOTONIC, q);
290-
break;
291-
}
292-
case swift_clock_id_suspending: {
293-
uint64_t q = sec * NSEC_PER_SEC + nsec;
294-
when = _dispatch_clock_and_value_to_time(DISPATCH_CLOCK_UPTIME, q);
295-
break;
296-
}
248+
long long nowSec;
249+
long long nowNsec;
250+
swift_get_time(&nowSec, &nowNsec, (swift_clock_id)clock);
251+
252+
uint64_t delta = (sec - nowSec) * NSEC_PER_SEC + nsec - nowNsec;
253+
254+
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, delta);
255+
256+
if (clock == swift_clock_id_continuous) {
257+
when |= DISPATCH_UP_OR_MONOTONIC_TIME_MASK;
297258
}
298259
// TODO: this should pass the leeway/tolerance along when it is not -1 nanoseconds
299260
// either a dispatch_source can be created or a better dispatch_after_f can be made for this

test/Concurrency/Runtime/clock.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library)
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: executable_test
5+
// REQUIRES: concurrency_runtime
6+
7+
import _Concurrency
8+
import StdlibUnittest
9+
10+
var tests = TestSuite("Time")
11+
12+
@main struct Main {
13+
static func main() async {
14+
tests.test("ContinuousClock sleep") {
15+
let clock = ContinuousClock()
16+
let elapsed = await clock.measure {
17+
try! await clock.sleep(until: .now + .milliseconds(100))
18+
}
19+
// give a reasonable range of expected elapsed time
20+
expectTrue(elapsed > .milliseconds(90))
21+
expectTrue(elapsed < .milliseconds(200))
22+
}
23+
24+
tests.test("SuspendingClock sleep") {
25+
let clock = SuspendingClock()
26+
let elapsed = await clock.measure {
27+
try! await clock.sleep(until: .now + .milliseconds(100))
28+
}
29+
// give a reasonable range of expected elapsed time
30+
expectTrue(elapsed > .milliseconds(90))
31+
expectTrue(elapsed < .milliseconds(200))
32+
}
33+
34+
tests.test("duration addition") {
35+
let d1 = Duration.milliseconds(500)
36+
let d2 = Duration.milliseconds(500)
37+
let d3 = Duration.milliseconds(-500)
38+
let sum = d1 + d2
39+
expectEqual(sum, .seconds(1))
40+
let comps = sum.components
41+
expectEqual(comps.seconds, 1)
42+
expectEqual(comps.attoseconds, 0)
43+
let adjusted = sum + d3
44+
expectEqual(adjusted, .milliseconds(500))
45+
}
46+
47+
tests.test("duration subtraction") {
48+
let d1 = Duration.nanoseconds(500)
49+
let d2 = d1 - .nanoseconds(100)
50+
expectEqual(d2, .nanoseconds(400))
51+
let d3 = d1 - .nanoseconds(500)
52+
expectEqual(d3, .nanoseconds(0))
53+
let d4 = d1 - .nanoseconds(600)
54+
expectEqual(d4, .nanoseconds(-100))
55+
}
56+
57+
tests.test("duration division") {
58+
let d1 = Duration.seconds(1)
59+
let halfSecond = d1 / 2
60+
expectEqual(halfSecond, .milliseconds(500))
61+
}
62+
63+
tests.test("duration multiplication") {
64+
let d1 = Duration.seconds(1)
65+
let twoSeconds = d1 * 2
66+
expectEqual(twoSeconds, .seconds(2))
67+
}
68+
69+
await runAllTestsAsync()
70+
}
71+
}

0 commit comments

Comments
 (0)