Skip to content

Commit c891f34

Browse files
authored
Ensure clocks properly respect leeways and use raw time for calculations for continuous clocks (#58396)
* Ensure clocks properly respect leeways and use raw time for calculations for continuous clocks * slurp up time calcualtions to inline forms * ensure the tolerance codepaths get tested as well * Use task local storage for task sources in leeway based calculations * use comparison assertions for leeway based calculations * Whitespace cleanup * Use the CLOCK_MONOTONIC value for linux to be compatible with linux scheduling for dispatch * remove incorrect paren in macro check for OpenBSD * Address feedback and remove stray ;
1 parent c43f60a commit c891f34

File tree

3 files changed

+145
-22
lines changed

3 files changed

+145
-22
lines changed

stdlib/public/Concurrency/Clock.cpp

Lines changed: 13 additions & 3 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;
@@ -63,7 +68,7 @@ void swift_get_time(
6368
case swift_clock_id_suspending: {
6469
#if defined(__linux__) && HAS_TIME
6570
struct timespec suspending;
66-
clock_gettime(CLOCK_MONOTONIC_RAW, &suspending);
71+
clock_gettime(CLOCK_MONOTONIC, &suspending);
6772
*seconds = suspending.tv_sec;
6873
*nanoseconds = suspending.tv_nsec;
6974
#elif defined(__APPLE__) && HAS_TIME
@@ -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: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@
2424

2525
#if SWIFT_CONCURRENCY_ENABLE_DISPATCH
2626
#include <dispatch/dispatch.h>
27-
2827
#if !defined(_WIN32)
2928
#include <dlfcn.h>
3029
#endif
31-
3230
#endif
3331

3432
// Ensure that Job's layout is compatible with what Dispatch expects.
@@ -226,6 +224,74 @@ static void swift_task_enqueueGlobalWithDelayImpl(JobDelay delay,
226224
}
227225

228226
#define DISPATCH_UP_OR_MONOTONIC_TIME_MASK (1ULL << 63)
227+
#define DISPATCH_WALLTIME_MASK (1ULL << 62)
228+
#define DISPATCH_TIME_MAX_VALUE (DISPATCH_WALLTIME_MASK - 1)
229+
230+
struct __swift_job_source {
231+
dispatch_source_t source;
232+
Job *job;
233+
};
234+
235+
static void _swift_run_job_leeway(struct __swift_job_source *jobSource) {
236+
dispatch_source_t source = jobSource->source;
237+
dispatch_release(source);
238+
Job *job = jobSource->job;
239+
auto task = dyn_cast<AsyncTask>(job);
240+
assert(task && "provided job must be a task");
241+
_swift_task_dealloc_specific(task, jobSource);
242+
__swift_run_job(job);
243+
}
244+
245+
#if defined(__i386__) || defined(__x86_64__) || !defined(__APPLE__)
246+
#define TIME_UNIT_USES_NANOSECONDS 1
247+
#else
248+
#define TIME_UNIT_USES_NANOSECONDS 0
249+
#endif
250+
251+
#if TIME_UNIT_USES_NANOSECONDS
252+
// x86 currently implements mach time in nanoseconds
253+
// this is NOT likely to change
254+
static inline uint64_t
255+
platform_time(uint64_t nsec) {
256+
return nsec;
257+
}
258+
#else
259+
#define DISPATCH_USE_HOST_TIME 1
260+
#if defined(__APPLE__)
261+
#if defined(__arm__) || defined(__arm64__)
262+
// Apple arm platforms currently use a fixed mach timebase of 125/3 (24 MHz)
263+
static inline uint64_t
264+
platform_time(uint64_t nsec) {
265+
if (!nsec) {
266+
return nsec;
267+
}
268+
if (nsec >= (uint64_t)INT64_MAX) {
269+
return INT64_MAX;
270+
}
271+
if (nsec >= UINT64_MAX / 3ull) {
272+
return (nsec / 125ull) * 3ull;
273+
} else {
274+
return (nsec * 3ull) / 125ull;
275+
}
276+
}
277+
#endif
278+
#endif
279+
#endif
280+
281+
static inline dispatch_time_t
282+
clock_and_value_to_time(int clock, long long deadline) {
283+
uint64_t value = platform_time((uint64_t)deadline);
284+
if (value >= DISPATCH_TIME_MAX_VALUE) {
285+
return DISPATCH_TIME_FOREVER;
286+
}
287+
switch (clock) {
288+
case swift_clock_id_suspending:
289+
return value;
290+
case swift_clock_id_continuous:
291+
return value | DISPATCH_UP_OR_MONOTONIC_TIME_MASK;
292+
}
293+
__builtin_unreachable();
294+
}
229295

230296
SWIFT_CC(swift)
231297
static void swift_task_enqueueGlobalWithDeadlineImpl(long long sec,
@@ -234,9 +300,7 @@ static void swift_task_enqueueGlobalWithDeadlineImpl(long long sec,
234300
long long tnsec,
235301
int clock, Job *job) {
236302
assert(job && "no job provided");
237-
238-
dispatch_function_t dispatchFunction = &__swift_run_job;
239-
void *dispatchContext = job;
303+
auto task = cast<AsyncTask>(job);
240304

241305
JobPriority priority = job->getPriority();
242306

@@ -245,20 +309,33 @@ static void swift_task_enqueueGlobalWithDeadlineImpl(long long sec,
245309
job->SchedulerPrivate[Job::DispatchQueueIndex] =
246310
DISPATCH_QUEUE_GLOBAL_EXECUTOR;
247311

248-
long long nowSec;
249-
long long nowNsec;
250-
swift_get_time(&nowSec, &nowNsec, (swift_clock_id)clock);
312+
uint64_t deadline = sec * NSEC_PER_SEC + nsec;
313+
dispatch_time_t when = clock_and_value_to_time(clock, deadline);
314+
315+
if (tnsec != -1) {
316+
uint64_t leeway = tsec * NSEC_PER_SEC + tnsec;
251317

252-
uint64_t delta = (sec - nowSec) * NSEC_PER_SEC + nsec - nowNsec;
318+
dispatch_source_t source =
319+
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
320+
dispatch_source_set_timer(source, when, DISPATCH_TIME_FOREVER, leeway);
253321

254-
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, delta);
322+
size_t sz = sizeof(struct __swift_job_source);
255323

256-
if (clock == swift_clock_id_continuous) {
257-
when |= DISPATCH_UP_OR_MONOTONIC_TIME_MASK;
324+
struct __swift_job_source *jobSource =
325+
(struct __swift_job_source *)_swift_task_alloc_specific(task, sz);
326+
327+
jobSource->job = job;
328+
jobSource->source = source;
329+
330+
dispatch_set_context(source, jobSource);
331+
dispatch_source_set_event_handler_f(source,
332+
(dispatch_function_t)&_swift_run_job_leeway);
333+
334+
dispatch_activate(source);
335+
} else {
336+
dispatch_after_f(when, queue, (void *)job,
337+
(dispatch_function_t)&__swift_run_job);
258338
}
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);
262339
}
263340

264341
SWIFT_CC(swift)

test/Concurrency/Runtime/clock.swift

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,26 @@ var tests = TestSuite("Time")
1717
try! await clock.sleep(until: .now + .milliseconds(100))
1818
}
1919
// give a reasonable range of expected elapsed time
20-
expectTrue(elapsed > .milliseconds(90))
21-
expectTrue(elapsed < .milliseconds(200))
20+
expectGT(elapsed, .milliseconds(90))
21+
expectLT(elapsed, .milliseconds(200))
22+
}
23+
24+
tests.test("ContinuousClock sleep with tolerance") {
25+
let clock = ContinuousClock()
26+
let elapsed = await clock.measure {
27+
try! await clock.sleep(until: .now + .milliseconds(100), tolerance: .milliseconds(100))
28+
}
29+
// give a reasonable range of expected elapsed time
30+
expectGT(elapsed, .milliseconds(90))
31+
expectLT(elapsed, .milliseconds(300))
32+
}
33+
34+
tests.test("ContinuousClock sleep longer") {
35+
let elapsed = await ContinuousClock().measure {
36+
try! await Task.sleep(until: .now + .seconds(1), clock: .continuous)
37+
}
38+
expectGT(elapsed, .seconds(1) - .milliseconds(90))
39+
expectLT(elapsed, .seconds(1) + .milliseconds(200))
2240
}
2341

2442
tests.test("SuspendingClock sleep") {
@@ -27,8 +45,26 @@ var tests = TestSuite("Time")
2745
try! await clock.sleep(until: .now + .milliseconds(100))
2846
}
2947
// give a reasonable range of expected elapsed time
30-
expectTrue(elapsed > .milliseconds(90))
31-
expectTrue(elapsed < .milliseconds(200))
48+
expectGT(elapsed, .milliseconds(90))
49+
expectLT(elapsed, .milliseconds(200))
50+
}
51+
52+
tests.test("SuspendingClock sleep with tolerance") {
53+
let clock = SuspendingClock()
54+
let elapsed = await clock.measure {
55+
try! await clock.sleep(until: .now + .milliseconds(100), tolerance: .milliseconds(100))
56+
}
57+
// give a reasonable range of expected elapsed time
58+
expectGT(elapsed, .milliseconds(90))
59+
expectLT(elapsed, .milliseconds(300))
60+
}
61+
62+
tests.test("SuspendingClock sleep longer") {
63+
let elapsed = await SuspendingClock().measure {
64+
try! await Task.sleep(until: .now + .seconds(1), clock: .suspending)
65+
}
66+
expectGT(elapsed, .seconds(1) - .milliseconds(90))
67+
expectLT(elapsed, .seconds(1) + .milliseconds(200))
3268
}
3369

3470
tests.test("duration addition") {

0 commit comments

Comments
 (0)