Skip to content

Commit 7d840f1

Browse files
authored
Merge pull request #61352 from al45tair/eng/PR-100236038-2
[Threading] Improve ConditionVariable wait behaviour
2 parents 27964ad + d0f42ca commit 7d840f1

File tree

7 files changed

+166
-22
lines changed

7 files changed

+166
-22
lines changed

include/swift/Threading/Impl/C11.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
#define SWIFT_THREADING_IMPL_C11_H
1919

2020
#include <atomic>
21-
#include <chrono>
2221
#include <cstdint>
2322

2423
#include <threads.h>
2524

25+
#include "chrono_utils.h"
26+
2627
#include "llvm/ADT/Optional.h"
2728

2829
#include "swift/Threading/Errors.h"
@@ -180,12 +181,14 @@ inline void cond_wait(cond_handle &handle) {
180181
template <class Rep, class Period>
181182
inline bool cond_wait(cond_handle &handle,
182183
std::chrono::duration<Rep, Period> duration) {
183-
auto deadline = std::chrono::system_clock::now() + duration;
184+
auto to_wait = chrono_utils::ceil<
185+
std::chrono::system_clock::duration>(duration);
186+
auto deadline = std::chrono::system_clock::now() + to_wait;
184187
return cond_wait(handle, deadline);
185188
}
186189
inline bool cond_wait(cond_handle &handle,
187190
std::chrono::system_clock::time_point deadline) {
188-
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
191+
auto ns = chrono_utils::ceil<std::chrono::nanoseconds>(
189192
deadline.time_since_epoch()).count();
190193
struct ::timespec ts = { ::time_t(ns / 1000000000), long(ns % 1000000000) };
191194
SWIFT_C11THREADS_RETURN_TRUE_OR_FALSE(

include/swift/Threading/Impl/Darwin.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#include <os/lock.h>
2222
#include <pthread.h>
2323

24-
#include <chrono>
24+
#include "chrono_utils.h"
2525

2626
#include "llvm/ADT/Optional.h"
2727

@@ -157,14 +157,14 @@ inline void cond_wait(cond_handle &handle) {
157157
template <class Rep, class Period>
158158
inline bool cond_wait(cond_handle &handle,
159159
std::chrono::duration<Rep, Period> duration) {
160-
auto deadline = std::chrono::time_point_cast<
161-
std::chrono::system_clock::duration>(std::chrono::system_clock::now()
162-
+ duration);
160+
auto to_wait = chrono_utils::ceil<
161+
std::chrono::system_clock::duration>(duration);
162+
auto deadline = std::chrono::system_clock::now() + to_wait;
163163
return cond_wait(handle, deadline);
164164
}
165165
inline bool cond_wait(cond_handle &handle,
166166
std::chrono::system_clock::time_point deadline) {
167-
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
167+
auto ns = chrono_utils::ceil<std::chrono::nanoseconds>(
168168
deadline.time_since_epoch()).count();
169169
struct ::timespec ts = { ::time_t(ns / 1000000000), long(ns % 1000000000) };
170170
SWIFT_PTHREADS_RETURN_TRUE_OR_FALSE(

include/swift/Threading/Impl/Linux.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
#include <pthread.h>
2222

2323
#include <atomic>
24-
#include <chrono>
2524
#include <optional>
2625

26+
#include "chrono_utils.h"
27+
2728
#include "llvm/ADT/Optional.h"
2829

2930
#include "swift/Threading/Errors.h"
@@ -167,14 +168,14 @@ inline void cond_wait(cond_handle &handle) {
167168
template <class Rep, class Period>
168169
inline bool cond_wait(cond_handle &handle,
169170
std::chrono::duration<Rep, Period> duration) {
170-
auto deadline = std::chrono::time_point_cast<
171-
std::chrono::system_clock::duration>(std::chrono::system_clock::now()
172-
+ duration);
171+
auto to_wait = chrono_utils::ceil<
172+
std::chrono::system_clock::duration>(duration);
173+
auto deadline = std::chrono::system_clock::now() + to_wait;
173174
return cond_wait(handle, deadline);
174175
}
175176
inline bool cond_wait(cond_handle &handle,
176177
std::chrono::system_clock::time_point deadline) {
177-
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
178+
auto ns = chrono_utils::ceil<std::chrono::nanoseconds>(
178179
deadline.time_since_epoch()).count();
179180
struct ::timespec ts = { ::time_t(ns / 1000000000), long(ns % 1000000000) };
180181
SWIFT_LINUXTHREADS_RETURN_TRUE_OR_FALSE(

include/swift/Threading/Impl/Nothreads.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
#include "llvm/ADT/Optional.h"
2121

22-
#include <chrono>
22+
#include "chrono_utils.h"
2323

2424
namespace swift {
2525
namespace threading_impl {

include/swift/Threading/Impl/Pthreads.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
#include <pthread.h>
2222

2323
#include <atomic>
24-
#include <chrono>
2524
#include <cstdint>
2625

26+
#include "chrono_utils.h"
27+
2728
#include "llvm/ADT/Optional.h"
2829

2930
#include "swift/Threading/Errors.h"
@@ -163,14 +164,14 @@ inline void cond_wait(cond_handle &handle) {
163164
template <class Rep, class Period>
164165
inline bool cond_wait(cond_handle &handle,
165166
std::chrono::duration<Rep, Period> duration) {
166-
auto deadline = std::chrono::time_point_cast<
167-
std::chrono::system_clock::duration>(std::chrono::system_clock::now()
168-
+ duration);
167+
auto to_wait = chrono_utils::ceil<
168+
std::chrono::system_clock::duration>(duration);
169+
auto deadline = std::chrono::system_clock::now() + to_wait;
169170
return cond_wait(handle, deadline);
170171
}
171172
inline bool cond_wait(cond_handle &handle,
172173
std::chrono::system_clock::time_point deadline) {
173-
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
174+
auto ns = chrono_utils::ceil<std::chrono::nanoseconds>(
174175
deadline.time_since_epoch()).count();
175176
struct ::timespec ts = { ::time_t(ns / 1000000000), long(ns % 1000000000) };
176177
SWIFT_PTHREADS_RETURN_TRUE_OR_FALSE(

include/swift/Threading/Impl/Win32.h

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919

2020
#include "Win32/Win32Defs.h"
2121

22-
#include <chrono>
22+
#include "chrono_utils.h"
23+
2324
#include <atomic>
2425

2526
#include "llvm/ADT/Optional.h"
@@ -120,10 +121,77 @@ inline void cond_wait(cond_handle &handle) {
120121
template <class Rep, class Period>
121122
inline bool cond_wait(cond_handle &handle,
122123
std::chrono::duration<Rep, Period> duration) {
123-
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration);
124+
auto ms = chrono_utils::ceil<std::chrono::milliseconds>(duration);
125+
126+
/* If you are paying attention to the next line, you are now asking
127+
128+
"Why are you adding 16 - that seems mad?"
129+
130+
To explain, we need to understand how the Sleep...() APIs in
131+
Windows work.
132+
133+
The Windows kernel runs a timer, the system tick, which is
134+
used for scheduling; the timer in question, for historical
135+
reasons, by default runs at 64Hz. Every time the timer fires,
136+
Windows updates the tick count, schedules processes and so on.
137+
138+
When you ask to sleep, there are two cases:
139+
140+
1. You've asked to sleep for less than a tick, or
141+
142+
2. You've asked to sleep for at least a tick.
143+
144+
In case 1, the sleep functions appear to run a delay loop; this
145+
is by its very nature inaccurate and you may or may not wait less
146+
than the time you requested. You might also get rescheduled,
147+
in which case you'll wait at least a whole tick.
148+
149+
In case 2, Windows appears to be adding the requested wait to
150+
its current tick count to work out when to wake your thread.
151+
That *sounds* sensible, until you realise that if it does this
152+
towards the end of a tick period, you will wait for that much
153+
less time. i.e. the sleep functions will return *early* by
154+
up to one tick period.
155+
156+
This is especially unfortunate if you ask to wait for exactly
157+
one tick period, because you will end up waiting for anything
158+
between no time at all and a full tick period.
159+
160+
To complicate matters, the system tick rate might not be 64Hz;
161+
the multimedia timer API can cause it to change to as high as
162+
1kHz, and on *some* machines this happens globally for all
163+
processes, while on *other* machines the kernel is actually using
164+
a separate system tick rate and merely pretending to Win32
165+
processes that things still work this way.
166+
167+
On other platforms, these kinds of functions are guaranteed to
168+
wait for at least the time you requested, and we'd like for that
169+
to be true for the Threading package's APIs here. One way to
170+
achieve that is to add a whole tick's worth of milliseconds
171+
to the time we're requesting to wait for. In that case, we'll
172+
avoid the delay loop from case (1), and we'll also guarantee that
173+
we wait for at least the amount of time the caller of this
174+
function expected.
175+
176+
It would be nice if there were a Windows API we could call to
177+
obtain the current system tick period. The Internet suggests
178+
that the undocumented call NtQueryTimerResolution() might be of
179+
some use for this, but it turns out that that just gives you
180+
the kernel's idea of the system tick period, which isn't the
181+
same as Win32's idea necessarily. e.g. on my test system, I
182+
can see that the tick period is 15.625ms, while that call tells
183+
me 1ms.
184+
185+
We don't want to change the system tick rate using the multimedia
186+
timer API mentioned earlier, because on some machines that is
187+
global and will use extra power and CPU cycles.
188+
189+
The upshot is that the best choice here appears to be to add
190+
15.625ms (1/64s) to the time we're asking to wait. That rounds
191+
up to 16ms, which is why there's a +16. */
124192
return SleepConditionVariableSRW(&handle.condition,
125193
&handle.lock,
126-
DWORD(ms.count()),
194+
DWORD(ms.count()) + 16,
127195
0);
128196
}
129197
inline bool cond_wait(cond_handle &handle,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//===--- chrono_utils.h - Utility functions for duration ------ -*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// Specifically, we want ceil() for these types, but that's only available in
14+
// C++17, and we need to build with C++14, so... include a version of the
15+
// necesary code here.
16+
//
17+
//===----------------------------------------------------------------------===//
18+
19+
#ifndef SWIFT_THREADING_IMPL_CHRONO_UTILS_H
20+
#define SWIFT_THREADING_IMPL_CHRONO_UTILS_H
21+
22+
#include <chrono>
23+
#include <type_traits>
24+
25+
namespace swift {
26+
namespace threading_impl {
27+
namespace chrono_utils {
28+
29+
#if __cplusplus >= 201703L
30+
using std::chrono::ceil;
31+
#else
32+
33+
namespace detail {
34+
template <class _Tp>
35+
struct is_duration : std::false_type {};
36+
37+
template <class _Rep, class _Period>
38+
struct is_duration<std::chrono::duration<_Rep, _Period> >
39+
: std::true_type {};
40+
41+
template <class _Rep, class _Period>
42+
struct is_duration<const std::chrono::duration<_Rep, _Period> >
43+
: std::true_type {};
44+
45+
template <class _Rep, class _Period>
46+
struct is_duration<volatile std::chrono::duration<_Rep, _Period> >
47+
: std::true_type {};
48+
49+
template <class _Rep, class _Period>
50+
struct is_duration<const volatile std::chrono::duration<_Rep, _Period> >
51+
: std::true_type {};
52+
}
53+
54+
template <class To, class Rep, class Period,
55+
class = std::enable_if_t<detail::is_duration<To>::value>>
56+
constexpr To
57+
ceil(const std::chrono::duration<Rep, Period>& d)
58+
{
59+
To t = std::chrono::duration_cast<To>(d);
60+
if (t < d)
61+
t = t + To{1};
62+
return t;
63+
}
64+
65+
#endif
66+
67+
} // namespace chrono_utils
68+
} // namespace threading_impl
69+
} // namespace swift
70+
71+
#endif // SWIFT_THREADING_IMPL_CHRONO_UTILS_H

0 commit comments

Comments
 (0)