Skip to content

Commit 35a073f

Browse files
committed
Remove async-helpers dependency
Copy over the latest NILock etc. implementations
1 parent d74608e commit 35a073f

File tree

8 files changed

+668
-9
lines changed

8 files changed

+668
-9
lines changed

Package.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,13 @@ let package = Package(
3838
// Swift logging API
3939
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
4040
.package(url: "https://github.com/apple/swift-http-types.git", from: "1.0.2"),
41-
.package(url: "https://github.com/stairtree/async-helpers.git", from: "0.2.0"),
4241
],
4342
targets: [
4443
.target(
4544
name: "StructuredAPIClient",
4645
dependencies: [
4746
.product(name: "Logging", package: "swift-log"),
4847
.product(name: "HTTPTypes", package: "swift-http-types"),
49-
.product(name: "AsyncHelpers", package: "async-helpers"),
5048
],
5149
swiftSettings: swiftSettings
5250
),

Sources/StructuredAPIClient/Handlers/TokenAuthenticationHandler.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import Foundation
1616
@preconcurrency import FoundationNetworking
1717
#endif
1818
import Logging
19-
import AsyncHelpers
2019

2120
// Handle token auth and add appropriate auth headers to requests sent by an existing ``Transport``.
2221
public final class TokenAuthenticationHandler: Transport {
@@ -59,7 +58,7 @@ public protocol Token: Sendable {
5958
}
6059

6160
final class AuthState: @unchecked Sendable {
62-
private let tokens: Locking.LockedValueBox<(accessToken: (any Token)?, refreshToken: (any Token)?)>
61+
private let tokens: NIOLockedValueBox<(accessToken: (any Token)?, refreshToken: (any Token)?)>
6362
let provider: any TokenProvider
6463
let logger: Logger
6564

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#if canImport(Darwin)
16+
import Darwin
17+
#elseif os(Windows)
18+
import ucrt
19+
import WinSDK
20+
#elseif canImport(Glibc)
21+
@preconcurrency import Glibc
22+
#elseif canImport(Musl)
23+
@preconcurrency import Musl
24+
#elseif canImport(Bionic)
25+
@preconcurrency import Bionic
26+
#elseif canImport(WASILibc)
27+
@preconcurrency import WASILibc
28+
#if canImport(wasi_pthread)
29+
import wasi_pthread
30+
#endif
31+
#else
32+
#error("The concurrency lock module was unable to identify your C library.")
33+
#endif
34+
35+
/// A threading lock based on `libpthread` instead of `libdispatch`.
36+
///
37+
/// This object provides a lock on top of a single `pthread_mutex_t`. This kind
38+
/// of lock is safe to use with `libpthread`-based threading models, such as the
39+
/// one used by NIO. On Windows, the lock is based on the substantially similar
40+
/// `SRWLOCK` type.
41+
@available(*, deprecated, renamed: "NIOLock")
42+
package final class Lock {
43+
#if os(Windows)
44+
fileprivate let mutex: UnsafeMutablePointer<SRWLOCK> =
45+
UnsafeMutablePointer.allocate(capacity: 1)
46+
#else
47+
fileprivate let mutex: UnsafeMutablePointer<pthread_mutex_t> =
48+
UnsafeMutablePointer.allocate(capacity: 1)
49+
#endif
50+
51+
/// Create a new lock.
52+
package init() {
53+
#if os(Windows)
54+
InitializeSRWLock(self.mutex)
55+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
56+
var attr = pthread_mutexattr_t()
57+
pthread_mutexattr_init(&attr)
58+
debugOnly {
59+
pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK))
60+
}
61+
62+
let err = pthread_mutex_init(self.mutex, &attr)
63+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
64+
#endif
65+
}
66+
67+
deinit {
68+
#if os(Windows)
69+
// SRWLOCK does not need to be free'd
70+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
71+
let err = pthread_mutex_destroy(self.mutex)
72+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
73+
#endif
74+
mutex.deallocate()
75+
}
76+
77+
/// Acquire the lock.
78+
///
79+
/// Whenever possible, consider using `withLock` instead of this method and
80+
/// `unlock`, to simplify lock handling.
81+
package func lock() {
82+
#if os(Windows)
83+
AcquireSRWLockExclusive(self.mutex)
84+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
85+
let err = pthread_mutex_lock(self.mutex)
86+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
87+
#endif
88+
}
89+
90+
/// Release the lock.
91+
///
92+
/// Whenever possible, consider using `withLock` instead of this method and
93+
/// `lock`, to simplify lock handling.
94+
package func unlock() {
95+
#if os(Windows)
96+
ReleaseSRWLockExclusive(self.mutex)
97+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
98+
let err = pthread_mutex_unlock(self.mutex)
99+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
100+
#endif
101+
}
102+
103+
/// Acquire the lock for the duration of the given block.
104+
///
105+
/// This convenience method should be preferred to `lock` and `unlock` in
106+
/// most situations, as it ensures that the lock will be released regardless
107+
/// of how `body` exits.
108+
///
109+
/// - Parameter body: The block to execute while holding the lock.
110+
/// - Returns: The value returned by the block.
111+
@inlinable
112+
package func withLock<T>(_ body: () throws -> T) rethrows -> T {
113+
self.lock()
114+
defer {
115+
self.unlock()
116+
}
117+
return try body()
118+
}
119+
120+
// specialise Void return (for performance)
121+
@inlinable
122+
package func withLockVoid(_ body: () throws -> Void) rethrows {
123+
try self.withLock(body)
124+
}
125+
}
126+
127+
/// A `Lock` with a built-in state variable.
128+
///
129+
/// This class provides a convenience addition to `Lock`: it provides the ability to wait
130+
/// until the state variable is set to a specific value to acquire the lock.
131+
package final class ConditionLock<T: Equatable> {
132+
private var _value: T
133+
private let mutex: NIOLock
134+
#if os(Windows)
135+
private let cond: UnsafeMutablePointer<CONDITION_VARIABLE> =
136+
UnsafeMutablePointer.allocate(capacity: 1)
137+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
138+
private let cond: UnsafeMutablePointer<pthread_cond_t> =
139+
UnsafeMutablePointer.allocate(capacity: 1)
140+
#endif
141+
142+
/// Create the lock, and initialize the state variable to `value`.
143+
///
144+
/// - Parameter value: The initial value to give the state variable.
145+
package init(value: T) {
146+
self._value = value
147+
self.mutex = NIOLock()
148+
#if os(Windows)
149+
InitializeConditionVariable(self.cond)
150+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
151+
let err = pthread_cond_init(self.cond, nil)
152+
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
153+
#endif
154+
}
155+
156+
deinit {
157+
#if os(Windows)
158+
// condition variables do not need to be explicitly destroyed
159+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
160+
let err = pthread_cond_destroy(self.cond)
161+
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
162+
self.cond.deallocate()
163+
#endif
164+
}
165+
166+
/// Acquire the lock, regardless of the value of the state variable.
167+
package func lock() {
168+
self.mutex.lock()
169+
}
170+
171+
/// Release the lock, regardless of the value of the state variable.
172+
package func unlock() {
173+
self.mutex.unlock()
174+
}
175+
176+
/// The value of the state variable.
177+
///
178+
/// Obtaining the value of the state variable requires acquiring the lock.
179+
/// This means that it is not safe to access this property while holding the
180+
/// lock: it is only safe to use it when not holding it.
181+
package var value: T {
182+
self.lock()
183+
defer {
184+
self.unlock()
185+
}
186+
return self._value
187+
}
188+
189+
/// Acquire the lock when the state variable is equal to `wantedValue`.
190+
///
191+
/// - Parameter wantedValue: The value to wait for the state variable
192+
/// to have before acquiring the lock.
193+
package func lock(whenValue wantedValue: T) {
194+
self.lock()
195+
while true {
196+
if self._value == wantedValue {
197+
break
198+
}
199+
self.mutex.withLockPrimitive { mutex in
200+
#if os(Windows)
201+
let result = SleepConditionVariableSRW(self.cond, mutex, INFINITE, 0)
202+
precondition(result, "\(#function) failed in SleepConditionVariableSRW with error \(GetLastError())")
203+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
204+
let err = pthread_cond_wait(self.cond, mutex)
205+
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
206+
#endif
207+
}
208+
}
209+
}
210+
211+
/// Acquire the lock when the state variable is equal to `wantedValue`,
212+
/// waiting no more than `timeoutSeconds` seconds.
213+
///
214+
/// - Parameter wantedValue: The value to wait for the state variable
215+
/// to have before acquiring the lock.
216+
/// - Parameter timeoutSeconds: The number of seconds to wait to acquire
217+
/// the lock before giving up.
218+
/// - Returns: `true` if the lock was acquired, `false` if the wait timed out.
219+
package func lock(whenValue wantedValue: T, timeoutSeconds: Double) -> Bool {
220+
precondition(timeoutSeconds >= 0)
221+
222+
#if os(Windows)
223+
var dwMilliseconds: DWORD = DWORD(timeoutSeconds * 1000)
224+
225+
self.lock()
226+
while true {
227+
if self._value == wantedValue {
228+
return true
229+
}
230+
231+
let dwWaitStart = timeGetTime()
232+
let result = self.mutex.withLockPrimitive { mutex in
233+
SleepConditionVariableSRW(self.cond, mutex, dwMilliseconds, 0)
234+
}
235+
if !result {
236+
let dwError = GetLastError()
237+
if dwError == ERROR_TIMEOUT {
238+
self.unlock()
239+
return false
240+
}
241+
fatalError("SleepConditionVariableSRW: \(dwError)")
242+
}
243+
// NOTE: this may be a spurious wakeup, adjust the timeout accordingly
244+
dwMilliseconds = dwMilliseconds - (timeGetTime() - dwWaitStart)
245+
}
246+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
247+
let nsecPerSec: Int64 = 1_000_000_000
248+
self.lock()
249+
// the timeout as a (seconds, nano seconds) pair
250+
let timeoutNS = Int64(timeoutSeconds * Double(nsecPerSec))
251+
252+
var curTime = timeval()
253+
gettimeofday(&curTime, nil)
254+
255+
let allNSecs: Int64 = timeoutNS + Int64(curTime.tv_usec) * 1000
256+
#if canImport(wasi_pthread)
257+
let tvSec = curTime.tv_sec + (allNSecs / nsecPerSec)
258+
#else
259+
let tvSec = curTime.tv_sec + Int((allNSecs / nsecPerSec))
260+
#endif
261+
262+
var timeoutAbs = timespec(
263+
tv_sec: tvSec,
264+
tv_nsec: Int(allNSecs % nsecPerSec)
265+
)
266+
assert(timeoutAbs.tv_nsec >= 0 && timeoutAbs.tv_nsec < Int(nsecPerSec))
267+
assert(timeoutAbs.tv_sec >= curTime.tv_sec)
268+
return self.mutex.withLockPrimitive { mutex -> Bool in
269+
while true {
270+
if self._value == wantedValue {
271+
return true
272+
}
273+
switch pthread_cond_timedwait(self.cond, mutex, &timeoutAbs) {
274+
case 0:
275+
continue
276+
case ETIMEDOUT:
277+
self.unlock()
278+
return false
279+
case let e:
280+
fatalError("caught error \(e) when calling pthread_cond_timedwait")
281+
}
282+
}
283+
}
284+
#else
285+
return true
286+
#endif
287+
}
288+
289+
/// Release the lock, setting the state variable to `newValue`.
290+
///
291+
/// - Parameter newValue: The value to give to the state variable when we
292+
/// release the lock.
293+
package func unlock(withValue newValue: T) {
294+
self._value = newValue
295+
self.unlock()
296+
#if os(Windows)
297+
WakeAllConditionVariable(self.cond)
298+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
299+
let err = pthread_cond_broadcast(self.cond)
300+
precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)")
301+
#endif
302+
}
303+
}
304+
305+
/// A utility function that runs the body code only in debug builds, without
306+
/// emitting compiler warnings.
307+
///
308+
/// This is currently the only way to do this in Swift: see
309+
/// https://forums.swift.org/t/support-debug-only-code/11037 for a discussion.
310+
@inlinable
311+
internal func debugOnly(_ body: () -> Void) {
312+
assert(
313+
{
314+
body()
315+
return true
316+
}()
317+
)
318+
}
319+
320+
@available(*, deprecated)
321+
extension Lock: @unchecked Sendable {}
322+
extension ConditionLock: @unchecked Sendable {}

0 commit comments

Comments
 (0)