Skip to content

Commit bc4008f

Browse files
authored
Update lock implementations (#8)
Copy from the latest NIO source
1 parent db6d8f7 commit bc4008f

File tree

3 files changed

+238
-166
lines changed

3 files changed

+238
-166
lines changed

Sources/AsyncHelpers/Locking+ConditionLock.swift

Lines changed: 150 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//
1212
//===----------------------------------------------------------------------===//
1313

14-
// Vendored from NIO 2.62.0 commit 702cd7c56d5d44eeba73fdf83918339b26dc855c on 2023-12-02
14+
// Vendored from NIO 2.83.0
1515

1616
//===----------------------------------------------------------------------===//
1717
//
@@ -48,166 +48,190 @@ extension Locking {
4848
public final class ConditionLock<T: Equatable> {
4949
private var _value: T
5050
private let mutex: FastLock
51-
#if os(Windows)
51+
#if os(Windows)
5252
private let cond: UnsafeMutablePointer<CONDITION_VARIABLE> =
53-
UnsafeMutablePointer.allocate(capacity: 1)
54-
#else
53+
UnsafeMutablePointer.allocate(capacity: 1)
54+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
5555
private let cond: UnsafeMutablePointer<pthread_cond_t> =
56-
UnsafeMutablePointer.allocate(capacity: 1)
57-
#endif
58-
56+
UnsafeMutablePointer.allocate(capacity: 1)
57+
#endif
58+
5959
/// Create the lock, and initialize the state variable to `value`.
6060
///
6161
/// - Parameter value: The initial value to give the state variable.
6262
public init(value: T) {
6363
self._value = value
6464
self.mutex = FastLock()
65-
#if os(Windows)
65+
#if os(Windows)
6666
InitializeConditionVariable(self.cond)
67-
#else
67+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
6868
let err = pthread_cond_init(self.cond, nil)
6969
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
70-
#endif
70+
#endif
7171
}
72-
72+
7373
deinit {
74-
#if os(Windows)
74+
#if os(Windows)
7575
// condition variables do not need to be explicitly destroyed
76-
#else
76+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
7777
let err = pthread_cond_destroy(self.cond)
7878
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
79-
#endif
8079
self.cond.deallocate()
80+
#endif
8181
}
82-
}
83-
}
84-
85-
extension Locking.ConditionLock {
86-
/// Acquire the lock, regardless of the value of the state variable.
87-
public func lock() {
88-
self.mutex.lock()
89-
}
90-
91-
/// Release the lock, regardless of the value of the state variable.
92-
public func unlock() {
93-
self.mutex.unlock()
94-
}
95-
96-
/// The value of the state variable.
97-
///
98-
/// Obtaining the value of the state variable requires acquiring the lock.
99-
/// This means that it is not safe to access this property while holding the
100-
/// lock: it is only safe to use it when not holding it.
101-
public var value: T {
102-
self.lock()
103-
defer {
104-
self.unlock()
82+
83+
/// Acquire the lock, regardless of the value of the state variable.
84+
public func lock() {
85+
self.mutex.lock()
10586
}
106-
return self._value
107-
}
108-
109-
/// Acquire the lock when the state variable is equal to `wantedValue`.
110-
///
111-
/// - Parameter wantedValue: The value to wait for the state variable
112-
/// to have before acquiring the lock.
113-
public func lock(whenValue wantedValue: T) {
114-
self.lock()
115-
while true {
116-
if self._value == wantedValue {
117-
break
118-
}
119-
self.mutex.withLockPrimitive { mutex in
120-
#if os(Windows)
121-
let result = SleepConditionVariableSRW(self.cond, mutex, INFINITE, 0)
122-
precondition(result, "\(#function) failed in SleepConditionVariableSRW with error \(GetLastError())")
123-
#else
124-
let err = pthread_cond_wait(self.cond, mutex)
125-
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
126-
#endif
87+
88+
/// Release the lock, regardless of the value of the state variable.
89+
public func unlock() {
90+
self.mutex.unlock()
91+
}
92+
93+
/// The value of the state variable.
94+
///
95+
/// Obtaining the value of the state variable requires acquiring the lock.
96+
/// This means that it is not safe to access this property while holding the
97+
/// lock: it is only safe to use it when not holding it.
98+
public var value: T {
99+
self.lock()
100+
defer {
101+
self.unlock()
127102
}
103+
return self._value
128104
}
129-
}
130-
131-
/// Acquire the lock when the state variable is equal to `wantedValue`,
132-
/// waiting no more than `timeoutSeconds` seconds.
133-
///
134-
/// - Parameter wantedValue: The value to wait for the state variable
135-
/// to have before acquiring the lock.
136-
/// - Parameter timeoutSeconds: The number of seconds to wait to acquire
137-
/// the lock before giving up.
138-
/// - Returns: `true` if the lock was acquired, `false` if the wait timed out.
139-
public func lock(whenValue wantedValue: T, timeoutSeconds: Double) -> Bool {
140-
precondition(timeoutSeconds >= 0)
141-
105+
106+
/// Acquire the lock when the state variable is equal to `wantedValue`.
107+
///
108+
/// - Parameter wantedValue: The value to wait for the state variable
109+
/// to have before acquiring the lock.
110+
public func lock(whenValue wantedValue: T) {
111+
self.lock()
112+
while true {
113+
if self._value == wantedValue {
114+
break
115+
}
116+
self.mutex.withLockPrimitive { mutex in
142117
#if os(Windows)
143-
var dwMilliseconds: DWORD = DWORD(timeoutSeconds * 1000)
144-
145-
self.lock()
146-
while true {
147-
if self._value == wantedValue {
148-
return true
149-
}
150-
151-
let dwWaitStart = timeGetTime()
152-
if !SleepConditionVariableSRW(self.cond, self.mutex._storage.mutex,
153-
dwMilliseconds, 0) {
154-
let dwError = GetLastError()
155-
if (dwError == ERROR_TIMEOUT) {
156-
self.unlock()
157-
return false
118+
let result = SleepConditionVariableSRW(self.cond, mutex, INFINITE, 0)
119+
precondition(result, "\(#function) failed in SleepConditionVariableSRW with error \(GetLastError())")
120+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
121+
let err = pthread_cond_wait(self.cond, mutex)
122+
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
123+
#endif
158124
}
159-
fatalError("SleepConditionVariableSRW: \(dwError)")
160125
}
161-
162-
// NOTE: this may be a spurious wakeup, adjust the timeout accordingly
163-
dwMilliseconds = dwMilliseconds - (timeGetTime() - dwWaitStart)
164126
}
165-
#else
166-
let nsecPerSec: Int64 = 1000000000
167-
self.lock()
168-
/* the timeout as a (seconds, nano seconds) pair */
169-
let timeoutNS = Int64(timeoutSeconds * Double(nsecPerSec))
170-
171-
var curTime = timeval()
172-
gettimeofday(&curTime, nil)
173-
174-
let allNSecs: Int64 = timeoutNS + Int64(curTime.tv_usec) * 1000
175-
var timeoutAbs = timespec(tv_sec: curTime.tv_sec + Int((allNSecs / nsecPerSec)),
176-
tv_nsec: Int(allNSecs % nsecPerSec))
177-
assert(timeoutAbs.tv_nsec >= 0 && timeoutAbs.tv_nsec < Int(nsecPerSec))
178-
assert(timeoutAbs.tv_sec >= curTime.tv_sec)
179-
return self.mutex.withLockPrimitive { mutex -> Bool in
127+
128+
/// Acquire the lock when the state variable is equal to `wantedValue`,
129+
/// waiting no more than `timeoutSeconds` seconds.
130+
///
131+
/// - Parameter wantedValue: The value to wait for the state variable
132+
/// to have before acquiring the lock.
133+
/// - Parameter timeoutSeconds: The number of seconds to wait to acquire
134+
/// the lock before giving up.
135+
/// - Returns: `true` if the lock was acquired, `false` if the wait timed out.
136+
public func lock(whenValue wantedValue: T, timeoutSeconds: Double) -> Bool {
137+
precondition(timeoutSeconds >= 0)
138+
139+
#if os(Windows)
140+
var dwMilliseconds: DWORD = DWORD(timeoutSeconds * 1000)
141+
142+
self.lock()
180143
while true {
181144
if self._value == wantedValue {
182145
return true
183146
}
184-
switch pthread_cond_timedwait(self.cond, mutex, &timeoutAbs) {
185-
case 0:
186-
continue
187-
case ETIMEDOUT:
188-
self.unlock()
189-
return false
190-
case let e:
191-
fatalError("caught error \(e) when calling pthread_cond_timedwait")
147+
148+
let dwWaitStart = timeGetTime()
149+
let result = self.mutex.withLockPrimitive { mutex in
150+
SleepConditionVariableSRW(self.cond, mutex, dwMilliseconds, 0)
192151
}
152+
if !result {
153+
let dwError = GetLastError()
154+
if dwError == ERROR_TIMEOUT {
155+
self.unlock()
156+
return false
157+
}
158+
fatalError("SleepConditionVariableSRW: \(dwError)")
159+
}
160+
// NOTE: this may be a spurious wakeup, adjust the timeout accordingly
161+
dwMilliseconds = dwMilliseconds - (timeGetTime() - dwWaitStart)
193162
}
163+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
164+
let nsecPerSec: Int64 = 1_000_000_000
165+
self.lock()
166+
// the timeout as a (seconds, nano seconds) pair
167+
let timeoutNS = Int64(timeoutSeconds * Double(nsecPerSec))
168+
169+
var curTime = timeval()
170+
gettimeofday(&curTime, nil)
171+
172+
let allNSecs: Int64 = timeoutNS + Int64(curTime.tv_usec) * 1000
173+
#if canImport(wasi_pthread)
174+
let tvSec = curTime.tv_sec + (allNSecs / nsecPerSec)
175+
#else
176+
let tvSec = curTime.tv_sec + Int((allNSecs / nsecPerSec))
177+
#endif
178+
179+
var timeoutAbs = timespec(
180+
tv_sec: tvSec,
181+
tv_nsec: Int(allNSecs % nsecPerSec)
182+
)
183+
assert(timeoutAbs.tv_nsec >= 0 && timeoutAbs.tv_nsec < Int(nsecPerSec))
184+
assert(timeoutAbs.tv_sec >= curTime.tv_sec)
185+
return self.mutex.withLockPrimitive { mutex -> Bool in
186+
while true {
187+
if self._value == wantedValue {
188+
return true
189+
}
190+
switch pthread_cond_timedwait(self.cond, mutex, &timeoutAbs) {
191+
case 0:
192+
continue
193+
case ETIMEDOUT:
194+
self.unlock()
195+
return false
196+
case let e:
197+
fatalError("caught error \(e) when calling pthread_cond_timedwait")
198+
}
199+
}
200+
}
201+
#else
202+
return true
203+
#endif
194204
}
205+
206+
/// Release the lock, setting the state variable to `newValue`.
207+
///
208+
/// - Parameter newValue: The value to give to the state variable when we
209+
/// release the lock.
210+
public func unlock(withValue newValue: T) {
211+
self._value = newValue
212+
self.unlock()
213+
#if os(Windows)
214+
WakeAllConditionVariable(self.cond)
215+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
216+
let err = pthread_cond_broadcast(self.cond)
217+
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
195218
#endif
219+
}
196220
}
197-
198-
/// Release the lock, setting the state variable to `newValue`.
221+
222+
/// A utility function that runs the body code only in debug builds, without
223+
/// emitting compiler warnings.
199224
///
200-
/// - Parameter newValue: The value to give to the state variable when we
201-
/// release the lock.
202-
public func unlock(withValue newValue: T) {
203-
self._value = newValue
204-
self.unlock()
205-
#if os(Windows)
206-
WakeAllConditionVariable(self.cond)
207-
#else
208-
let err = pthread_cond_broadcast(self.cond)
209-
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
210-
#endif
225+
/// This is currently the only way to do this in Swift: see
226+
/// https://forums.swift.org/t/support-debug-only-code/11037 for a discussion.
227+
@inlinable
228+
internal func debugOnly(_ body: () -> Void) {
229+
assert(
230+
{
231+
body()
232+
return true
233+
}()
234+
)
211235
}
212236
}
213237

0 commit comments

Comments
 (0)