Skip to content

Commit 9d0dcd3

Browse files
committed
Ensure clocks properly respect leeways and use raw time for calculations for continuous clocks
1 parent 9773a3c commit 9d0dcd3

File tree

3 files changed

+75
-18
lines changed

3 files changed

+75
-18
lines changed

stdlib/public/Concurrency/Clock.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ void swift_get_time(
3737
clock_gettime(CLOCK_BOOTTIME, &continuous);
3838
*seconds = continuous.tv_sec;
3939
*nanoseconds = continuous.tv_nsec;
40-
#elif (defined(__APPLE__) || defined(__OpenBSD__)) && HAS_TIME
40+
#elif defined(__APPLE__) && HAS_TIME
41+
struct timespec continuous;
42+
clock_gettime(CLOCK_MONOTONIC_RAW, &continuous);
43+
*seconds = continuous.tv_sec;
44+
*nanoseconds = continuous.tv_nsec;
45+
#elif defined(__OpenBSD__)) && HAS_TIME
4146
struct timespec continuous;
4247
clock_gettime(CLOCK_MONOTONIC, &continuous);
4348
*seconds = continuous.tv_sec;
@@ -111,7 +116,12 @@ switch (clock_id) {
111116
clock_getres(CLOCK_BOOTTIME, &continuous);
112117
*seconds = continuous.tv_sec;
113118
*nanoseconds = continuous.tv_nsec;
114-
#elif (defined(__APPLE__) || defined(__OpenBSD__)) && HAS_TIME
119+
#elif defined(__APPLE__) && HAS_TIME
120+
struct timespec continuous;
121+
clock_getres(CLOCK_MONOTONIC_RAW, &continuous);
122+
*seconds = continuous.tv_sec;
123+
*nanoseconds = continuous.tv_nsec;
124+
#elif defined(__OpenBSD__)) && HAS_TIME;
115125
struct timespec continuous;
116126
clock_getres(CLOCK_MONOTONIC, &continuous);
117127
*seconds = continuous.tv_sec;

stdlib/public/Concurrency/DispatchGlobalExecutor.inc

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@
2424

2525
#if SWIFT_CONCURRENCY_ENABLE_DISPATCH
2626
#include <dispatch/dispatch.h>
27+
#if __has_include(<dispatch/private.h>)
28+
#include <dispatch/private.h>
29+
#else
30+
extern "C" {
31+
DISPATCH_ENUM(dispatch_clockid, uintptr_t,
32+
DISPATCH_CLOCKID_UPTIME = 1,
33+
DISPATCH_CLOCKID_MONOTONIC = 2,
34+
DISPATCH_CLOCKID_WALLTIME = 3,
35+
DISPATCH_CLOCKID_REALTIME = 3,
36+
);
37+
38+
DISPATCH_EXPORT DISPATCH_WARN_RESULT DISPATCH_NOTHROW
39+
dispatch_time_t
40+
dispatch_time_from_nsec(dispatch_clockid_t clock, uint64_t deadline);
41+
}
42+
#endif
2743

2844
#if !defined(_WIN32)
2945
#include <dlfcn.h>
@@ -227,38 +243,53 @@ static void swift_task_enqueueGlobalWithDelayImpl(JobDelay delay,
227243

228244
#define DISPATCH_UP_OR_MONOTONIC_TIME_MASK (1ULL << 63)
229245

246+
struct __swift_job_source {
247+
dispatch_source_t source;
248+
Job *job;
249+
};
250+
251+
static void __swift_run_job_leeway(Job *job) {
252+
dispatch_source_t source = (dispatch_source_t)job->Reserved;
253+
job->Reserved = nullptr;
254+
__swift_run_job(job);
255+
dispatch_release(source);
256+
}
257+
230258
SWIFT_CC(swift)
231259
static void swift_task_enqueueGlobalWithDeadlineImpl(long long sec,
232260
long long nsec,
233261
long long tsec,
234262
long long tnsec,
235263
int clock, Job *job) {
236264
assert(job && "no job provided");
237-
238-
dispatch_function_t dispatchFunction = &__swift_run_job;
239-
void *dispatchContext = job;
240-
241265
JobPriority priority = job->getPriority();
242266

243267
auto queue = getGlobalQueue(priority);
244268

245269
job->SchedulerPrivate[Job::DispatchQueueIndex] =
246270
DISPATCH_QUEUE_GLOBAL_EXECUTOR;
247271

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-
272+
dispatch_clockid_t dispatchClock;
256273
if (clock == swift_clock_id_continuous) {
257-
when |= DISPATCH_UP_OR_MONOTONIC_TIME_MASK;
274+
dispatchClock = DISPATCH_CLOCKID_MONOTONIC;
275+
} else {
276+
dispatchClock = DISPATCH_CLOCKID_UPTIME;
258277
}
259-
// TODO: this should pass the leeway/tolerance along when it is not -1 nanoseconds
260-
// either a dispatch_source can be created or a better dispatch_after_f can be made for this
261-
dispatch_after_f(when, queue, dispatchContext, dispatchFunction);
278+
uint64_t deadline = sec * NSEC_PER_SEC + nsec;
279+
dispatch_time_t when = dispatch_time_from_nsec(dispatchClock, deadline);
280+
dispatch_source_t source =
281+
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
282+
job->Reserved = source;
283+
dispatch_set_context(source, job);
284+
uint64_t leeway = 0;
285+
if (tnsec != -1) {
286+
leeway = tsec * NSEC_PER_SEC + tnsec;
287+
}
288+
dispatch_source_set_timer(source, when,
289+
DISPATCH_TIME_FOREVER, leeway);
290+
dispatch_source_set_event_handler_f(source,
291+
(dispatch_function_t)&__swift_run_job_leeway);
292+
dispatch_activate(source);
262293
}
263294

264295
SWIFT_CC(swift)

test/Concurrency/Runtime/clock.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ var tests = TestSuite("Time")
2121
expectTrue(elapsed < .milliseconds(200))
2222
}
2323

24+
tests.test("ContinuousClock sleep longer") {
25+
let elapsed = await ContinuousClock().measure {
26+
try! await Task.sleep(until: .now + .seconds(1), clock: .continuous)
27+
}
28+
expectTrue(elapsed > .seconds(1) - .milliseconds(90))
29+
expectTrue(elapsed < .seconds(1) + .milliseconds(200))
30+
}
31+
2432
tests.test("SuspendingClock sleep") {
2533
let clock = SuspendingClock()
2634
let elapsed = await clock.measure {
@@ -31,6 +39,14 @@ var tests = TestSuite("Time")
3139
expectTrue(elapsed < .milliseconds(200))
3240
}
3341

42+
tests.test("SuspendingClock sleep longer") {
43+
let elapsed = await SuspendingClock().measure {
44+
try! await Task.sleep(until: .now + .seconds(1), clock: .suspending)
45+
}
46+
expectTrue(elapsed > .seconds(1) - .milliseconds(90))
47+
expectTrue(elapsed < .seconds(1) + .milliseconds(200))
48+
}
49+
3450
tests.test("duration addition") {
3551
let d1 = Duration.milliseconds(500)
3652
let d2 = Duration.milliseconds(500)

0 commit comments

Comments
 (0)