Skip to content

Commit c462502

Browse files
authored
Move away from Foundation.Thread (#404)
Motivation In preparation for adopting FoundationEssentials where available, we need to avoid using Thread as that isn't available. To that end, let's bring over NIO's ThreadSpecificVariable wrapper. Modifications Bring over NIO's ThreadSpecificVariable wrapper and supporting code. Result No need for Thread anymore.
1 parent d1c6b70 commit c462502

File tree

7 files changed

+292
-10
lines changed

7 files changed

+292
-10
lines changed

NOTICE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ This product contains test vectors from Google's wycheproof project.
3434

3535
---
3636

37-
This product contains a derivation of various scripts from SwiftNIO.
37+
This product contains a derivation of various files from SwiftNIO.
3838

3939
* LICENSE (Apache License 2.0):
4040
* https://www.apache.org/licenses/LICENSE-2.0

Sources/_CryptoExtras/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ add_library(_CryptoExtras
6565
"Util/PEMDocument.swift"
6666
"Util/PrettyBytes.swift"
6767
"Util/SubjectPublicKeyInfo.swift"
68+
"Util/ThreadSpecific/ThreadOps.swift"
69+
"Util/ThreadSpecific/ThreadPosix.swift"
70+
"Util/ThreadSpecific/ThreadSpecific.swift"
71+
"Util/ThreadSpecific/ThreadWindows.swift"
6872
"ZKPs/DLEQ.swift"
6973
"ZKPs/Prover.swift"
7074
"ZKPs/Verifier.swift"

Sources/_CryptoExtras/ECToolbox/BoringSSL/ECToolbox_boring.swift

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,15 @@ extension P256: OpenSSLSupportedNISTCurve {
6565
@inlinable
6666
static var hashToFieldByteCount: Int { 48 }
6767

68+
private static let __ffacTSV = ThreadSpecificVariable<FiniteFieldArithmeticContext>()
69+
6870
@usableFromInline
6971
static var __ffac: FiniteFieldArithmeticContext {
70-
let key = "com.apple.swift-crypto.P256.__ffac"
71-
if let value = Thread.current.threadDictionary[key] as? FiniteFieldArithmeticContext {
72+
if let value = Self.__ffacTSV.currentValue {
7273
return value
7374
}
7475
let value = try! FiniteFieldArithmeticContext(fieldSize: P256.group.order)
75-
Thread.current.threadDictionary[key] = value
76+
Self.__ffacTSV.currentValue = value
7677
return value
7778
}
7879
}
@@ -98,14 +99,15 @@ extension P384: OpenSSLSupportedNISTCurve {
9899
@inlinable
99100
static var hashToFieldByteCount: Int { 72 }
100101

102+
private static let __ffacTSV = ThreadSpecificVariable<FiniteFieldArithmeticContext>()
103+
101104
@usableFromInline
102105
static var __ffac: FiniteFieldArithmeticContext {
103-
let key = "com.apple.swift-crypto.P384.__ffac"
104-
if let value = Thread.current.threadDictionary[key] as? FiniteFieldArithmeticContext {
106+
if let value = Self.__ffacTSV.currentValue {
105107
return value
106108
}
107109
let value = try! FiniteFieldArithmeticContext(fieldSize: P384.group.order)
108-
Thread.current.threadDictionary[key] = value
110+
Self.__ffacTSV.currentValue = value
109111
return value
110112
}
111113
}
@@ -131,14 +133,15 @@ extension P521: OpenSSLSupportedNISTCurve {
131133
@inlinable
132134
static var hashToFieldByteCount: Int { 98 }
133135

136+
private static let __ffacTSV = ThreadSpecificVariable<FiniteFieldArithmeticContext>()
137+
134138
@usableFromInline
135139
static var __ffac: FiniteFieldArithmeticContext {
136-
let key = "com.apple.swift-crypto.P521.__ffac"
137-
if let value = Thread.current.threadDictionary[key] as? FiniteFieldArithmeticContext {
140+
if let value = Self.__ffacTSV.currentValue {
138141
return value
139142
}
140143
let value = try! FiniteFieldArithmeticContext(fieldSize: P521.group.order)
141-
Thread.current.threadDictionary[key] = value
144+
Self.__ffacTSV.currentValue = value
142145
return value
143146
}
144147
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftCrypto open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftCrypto 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 SwiftCrypto project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
//===----------------------------------------------------------------------===//
15+
//
16+
// This source file is part of the SwiftNIO open source project
17+
//
18+
// Copyright (c) 2017-2014 Apple Inc. and the SwiftNIO project authors
19+
// Licensed under Apache License v2.0
20+
//
21+
// See LICENSE.txt for license information
22+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
23+
//
24+
// SPDX-License-Identifier: Apache-2.0
25+
//
26+
//===----------------------------------------------------------------------===//
27+
protocol ThreadOps {
28+
associatedtype ThreadSpecificKey
29+
associatedtype ThreadSpecificKeyDestructor
30+
31+
static func allocateThreadSpecificValue(destructor: ThreadSpecificKeyDestructor) -> ThreadSpecificKey
32+
static func deallocateThreadSpecificValue(_ key: ThreadSpecificKey)
33+
static func getThreadSpecificValue(_ key: ThreadSpecificKey) -> UnsafeMutableRawPointer?
34+
static func setThreadSpecificValue(key: ThreadSpecificKey, value: UnsafeMutableRawPointer?)
35+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftCrypto open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftCrypto 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 SwiftCrypto project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
//===----------------------------------------------------------------------===//
15+
//
16+
// This source file is part of the SwiftNIO open source project
17+
//
18+
// Copyright (c) 2020 Apple Inc. and the SwiftNIO project authors
19+
// Licensed under Apache License v2.0
20+
//
21+
// See LICENSE.txt for license information
22+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
23+
//
24+
// SPDX-License-Identifier: Apache-2.0
25+
//
26+
//===----------------------------------------------------------------------===//
27+
28+
#if os(Linux) || os(Android) || os(FreeBSD) || canImport(Darwin)
29+
#if canImport(Glibc)
30+
@preconcurrency import Glibc
31+
#elseif canImport(Bionic)
32+
@preconcurrency import Bionic
33+
#elseif canImport(Musl)
34+
@preconcurrency import Musl
35+
#elseif canImport(Android)
36+
@preconcurrency import Android
37+
#elseif canImport(Darwin)
38+
import Darwin
39+
#endif
40+
41+
typealias ThreadOpsSystem = ThreadOpsPosix
42+
43+
enum ThreadOpsPosix: ThreadOps {
44+
typealias ThreadSpecificKey = pthread_key_t
45+
#if canImport(Darwin)
46+
typealias ThreadSpecificKeyDestructor = @convention(c) (UnsafeMutableRawPointer) -> Void
47+
#else
48+
typealias ThreadSpecificKeyDestructor = @convention(c) (UnsafeMutableRawPointer?) -> Void
49+
#endif
50+
51+
static func allocateThreadSpecificValue(destructor: @escaping ThreadSpecificKeyDestructor) -> ThreadSpecificKey {
52+
var value = pthread_key_t()
53+
let result = pthread_key_create(&value, Optional(destructor))
54+
precondition(result == 0, "pthread_key_create failed: \(result)")
55+
return value
56+
}
57+
58+
static func deallocateThreadSpecificValue(_ key: ThreadSpecificKey) {
59+
let result = pthread_key_delete(key)
60+
precondition(result == 0, "pthread_key_delete failed: \(result)")
61+
}
62+
63+
static func getThreadSpecificValue(_ key: ThreadSpecificKey) -> UnsafeMutableRawPointer? {
64+
pthread_getspecific(key)
65+
}
66+
67+
static func setThreadSpecificValue(key: ThreadSpecificKey, value: UnsafeMutableRawPointer?) {
68+
let result = pthread_setspecific(key, value)
69+
precondition(result == 0, "pthread_setspecific failed: \(result)")
70+
}
71+
}
72+
73+
#endif
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftCrypto open source project
4+
//
5+
// Copyright (c) 2019-2025 Apple Inc. and the SwiftCrypto 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 SwiftCrypto project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
// Derived from swift-nio's ThreadSpecificVariable type.
16+
17+
/// A ``ThreadSpecificVariable`` is a variable that can be read and set like a normal variable except that it holds
18+
/// different variables per thread.
19+
///
20+
/// ``ThreadSpecificVariable`` is thread-safe so it can be used with multiple threads at the same time but the value
21+
/// returned by ``currentValue`` is defined per thread.
22+
///
23+
/// - Note: Though ``ThreadSpecificVariable`` is thread-safe, it is not `Sendable` unless `Value` is `Sendable`.
24+
/// If ``ThreadSpecificVariable`` were unconditionally `Sendable`, it could be used to "smuggle"
25+
/// non-`Sendable` state out of an actor or other isolation domain without triggering warnings. If you
26+
/// are attempting to use ``ThreadSpecificVariable`` with non-`Sendable` data, consider using a dynamic
27+
/// enforcement tool like `NIOLoopBoundBox` to police the access.
28+
final class ThreadSpecificVariable<Value: AnyObject> {
29+
// the actual type in there is `Box<(ThreadSpecificVariable<T>, T)>` but we can't use that as C functions can't capture (even types)
30+
private typealias BoxedType = Box<(AnyObject, AnyObject)>
31+
32+
private class Key {
33+
private var underlyingKey: ThreadOpsSystem.ThreadSpecificKey
34+
35+
internal init(destructor: @escaping ThreadOpsSystem.ThreadSpecificKeyDestructor) {
36+
self.underlyingKey = ThreadOpsSystem.allocateThreadSpecificValue(destructor: destructor)
37+
}
38+
39+
deinit {
40+
ThreadOpsSystem.deallocateThreadSpecificValue(self.underlyingKey)
41+
}
42+
43+
func get() -> UnsafeMutableRawPointer? {
44+
ThreadOpsSystem.getThreadSpecificValue(self.underlyingKey)
45+
}
46+
47+
func set(value: UnsafeMutableRawPointer?) {
48+
ThreadOpsSystem.setThreadSpecificValue(key: self.underlyingKey, value: value)
49+
}
50+
}
51+
52+
private let key: Key
53+
54+
/// Initialize a new `ThreadSpecificVariable` without a current value (`currentValue == nil`).
55+
init() {
56+
self.key = Key(destructor: {
57+
Unmanaged<BoxedType>.fromOpaque(($0 as UnsafeMutableRawPointer?)!).release()
58+
})
59+
}
60+
61+
/// Initialize a new `ThreadSpecificVariable` with `value` for the calling thread. After calling this, the calling
62+
/// thread will see `currentValue == value` but on all other threads `currentValue` will be `nil` until changed.
63+
///
64+
/// - Parameters:
65+
/// - value: The value to set for the calling thread.
66+
convenience init(value: Value) {
67+
self.init()
68+
self.currentValue = value
69+
}
70+
71+
/// The value for the current thread.
72+
@available(
73+
*,
74+
noasync,
75+
message: "threads can change between suspension points and therefore the thread specific value too"
76+
)
77+
var currentValue: Value? {
78+
get {
79+
self.get()
80+
}
81+
set {
82+
self.set(newValue)
83+
}
84+
}
85+
86+
/// Get the current value for the calling thread.
87+
private func get() -> Value? {
88+
guard let raw = self.key.get() else { return nil }
89+
// parenthesize the return value to silence the cast warning
90+
return
91+
(Unmanaged<BoxedType>
92+
.fromOpaque(raw)
93+
.takeUnretainedValue()
94+
.value.1 as! Value)
95+
}
96+
97+
/// Set the current value for the calling threads. The `currentValue` for all other threads remains unchanged.
98+
private func set(_ newValue: Value?) {
99+
if let raw = self.key.get() {
100+
Unmanaged<BoxedType>.fromOpaque(raw).release()
101+
}
102+
self.key.set(value: newValue.map { Unmanaged.passRetained(Box((self, $0))).toOpaque() })
103+
}
104+
}
105+
106+
extension ThreadSpecificVariable: @unchecked Sendable where Value: Sendable {}
107+
108+
final class Box<T> {
109+
let value: T
110+
init(_ value: T) { self.value = value }
111+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftCrypto open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftCrypto 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 SwiftCrypto project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
//===----------------------------------------------------------------------===//
15+
//
16+
// This source file is part of the SwiftNIO open source project
17+
//
18+
// Copyright (c) 2020 Apple Inc. and the SwiftNIO project authors
19+
// Licensed under Apache License v2.0
20+
//
21+
// See LICENSE.txt for license information
22+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
23+
//
24+
// SPDX-License-Identifier: Apache-2.0
25+
//
26+
//===----------------------------------------------------------------------===//
27+
28+
#if os(Windows)
29+
30+
import WinSDK
31+
32+
typealias ThreadOpsSystem = ThreadOpsWindows
33+
34+
enum ThreadOpsWindows: ThreadOps {
35+
typealias ThreadSpecificKey = DWORD
36+
typealias ThreadSpecificKeyDestructor = @convention(c) (UnsafeMutableRawPointer?) -> Void
37+
38+
static func allocateThreadSpecificValue(destructor: @escaping ThreadSpecificKeyDestructor) -> ThreadSpecificKey {
39+
FlsAlloc(destructor)
40+
}
41+
42+
static func deallocateThreadSpecificValue(_ key: ThreadSpecificKey) {
43+
let dwResult: Bool = FlsFree(key)
44+
precondition(dwResult, "FlsFree: \(GetLastError())")
45+
}
46+
47+
static func getThreadSpecificValue(_ key: ThreadSpecificKey) -> UnsafeMutableRawPointer? {
48+
FlsGetValue(key)
49+
}
50+
51+
static func setThreadSpecificValue(key: ThreadSpecificKey, value: UnsafeMutableRawPointer?) {
52+
FlsSetValue(key, value)
53+
}
54+
}
55+
56+
#endif

0 commit comments

Comments
 (0)