Skip to content

Commit 70cda1b

Browse files
authored
Merge pull request #69 from guoye-zhang/lock
Adopt NIOLock for more platform support
2 parents f9a647b + 4ba4e72 commit 70cda1b

File tree

2 files changed

+220
-71
lines changed

2 files changed

+220
-71
lines changed

Sources/HTTPTypes/HTTPFields.swift

Lines changed: 17 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,6 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
#if canImport(os.lock)
16-
import os.lock
17-
#elseif canImport(Glibc)
18-
import Glibc
19-
#elseif canImport(Musl)
20-
import Musl
21-
#elseif canImport(WASILibc)
22-
import WASILibc
23-
#endif
24-
2515
/// A collection of HTTP fields. It is used in `HTTPRequest` and `HTTPResponse`, and can also be
2616
/// used as HTTP trailer fields.
2717
///
@@ -34,77 +24,33 @@ public struct HTTPFields: Sendable, Hashable {
3424
private final class _Storage: @unchecked Sendable, Hashable {
3525
var fields: [(field: HTTPField, next: UInt16)] = []
3626
var index: [String: (first: UInt16, last: UInt16)]? = [:]
37-
#if canImport(os.lock)
38-
let lock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
39-
#else
40-
let lock = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1)
41-
#endif
42-
43-
init() {
44-
#if canImport(os.lock)
45-
self.lock.initialize(to: os_unfair_lock())
46-
#else
47-
let err = pthread_mutex_init(self.lock, nil)
48-
precondition(err == 0, "pthread_mutex_init failed with error \(err)")
49-
#endif
50-
}
51-
52-
deinit {
53-
#if !canImport(os.lock)
54-
let err = pthread_mutex_destroy(self.lock)
55-
precondition(err == 0, "pthread_mutex_destroy failed with error \(err)")
56-
#endif
57-
self.lock.deallocate()
58-
}
27+
let lock = LockStorage.create(value: ())
5928

6029
var ensureIndex: [String: (first: UInt16, last: UInt16)] {
61-
#if canImport(os.lock)
62-
os_unfair_lock_lock(self.lock)
63-
defer { os_unfair_lock_unlock(self.lock) }
64-
#else
65-
let err = pthread_mutex_lock(self.lock)
66-
precondition(err == 0, "pthread_mutex_lock failed with error \(err)")
67-
defer {
68-
let err = pthread_mutex_unlock(self.lock)
69-
precondition(err == 0, "pthread_mutex_unlock failed with error \(err)")
70-
}
71-
#endif
72-
if let index = self.index {
73-
return index
74-
}
75-
var newIndex = [String: (first: UInt16, last: UInt16)]()
76-
for index in self.fields.indices {
77-
let name = self.fields[index].field.name.canonicalName
78-
self.fields[index].next = .max
79-
if let lastIndex = newIndex[name]?.last {
80-
self.fields[Int(lastIndex)].next = UInt16(index)
30+
self.lock.withLockedValue { _ in
31+
if let index = self.index {
32+
return index
8133
}
82-
newIndex[name, default: (first: UInt16(index), last: 0)].last = UInt16(index)
34+
var newIndex = [String: (first: UInt16, last: UInt16)]()
35+
for index in self.fields.indices {
36+
let name = self.fields[index].field.name.canonicalName
37+
self.fields[index].next = .max
38+
if let lastIndex = newIndex[name]?.last {
39+
self.fields[Int(lastIndex)].next = UInt16(index)
40+
}
41+
newIndex[name, default: (first: UInt16(index), last: 0)].last = UInt16(index)
42+
}
43+
self.index = newIndex
44+
return newIndex
8345
}
84-
self.index = newIndex
85-
return newIndex
8646
}
8747

8848
func copy() -> _Storage {
8949
let newStorage = _Storage()
9050
newStorage.fields = self.fields
91-
#if canImport(os.lock)
92-
os_unfair_lock_lock(self.lock)
93-
#else
94-
do {
95-
let err = pthread_mutex_lock(self.lock)
96-
precondition(err == 0, "pthread_mutex_lock failed with error \(err)")
97-
}
98-
#endif
99-
newStorage.index = self.index
100-
#if canImport(os.lock)
101-
os_unfair_lock_unlock(self.lock)
102-
#else
103-
do {
104-
let err = pthread_mutex_unlock(self.lock)
105-
precondition(err == 0, "pthread_mutex_unlock failed with error \(err)")
51+
self.lock.withLockedValue { _ in
52+
newStorage.index = self.index
10653
}
107-
#endif
10854
return newStorage
10955
}
11056

Sources/HTTPTypes/NIOLock.swift

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2017-2024 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+
import Glibc
22+
#elseif canImport(Musl)
23+
import Musl
24+
#elseif canImport(Bionic)
25+
import Bionic
26+
#elseif canImport(WASILibc)
27+
import WASILibc
28+
#if canImport(wasi_pthread)
29+
import wasi_pthread
30+
#endif
31+
#else
32+
#error("The concurrency NIOLock module was unable to identify your C library.")
33+
#endif
34+
35+
#if os(Windows)
36+
@usableFromInline
37+
typealias LockPrimitive = SRWLOCK
38+
#elseif canImport(Darwin)
39+
@usableFromInline
40+
typealias LockPrimitive = os_unfair_lock
41+
#else
42+
@usableFromInline
43+
typealias LockPrimitive = pthread_mutex_t
44+
#endif
45+
46+
@usableFromInline
47+
enum LockOperations {}
48+
49+
extension LockOperations {
50+
@inlinable
51+
static func create(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
52+
mutex.assertValidAlignment()
53+
54+
#if os(Windows)
55+
InitializeSRWLock(mutex)
56+
#elseif canImport(Darwin)
57+
mutex.initialize(to: .init())
58+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
59+
var attr = pthread_mutexattr_t()
60+
pthread_mutexattr_init(&attr)
61+
62+
let err = pthread_mutex_init(mutex, &attr)
63+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
64+
#endif
65+
}
66+
67+
@inlinable
68+
static func destroy(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
69+
mutex.assertValidAlignment()
70+
71+
#if os(Windows)
72+
// SRWLOCK does not need to be free'd
73+
#elseif canImport(Darwin)
74+
// os_unfair_lock does not need to be free'd
75+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
76+
let err = pthread_mutex_destroy(mutex)
77+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
78+
#endif
79+
}
80+
81+
@inlinable
82+
static func lock(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
83+
mutex.assertValidAlignment()
84+
85+
#if os(Windows)
86+
AcquireSRWLockExclusive(mutex)
87+
#elseif canImport(Darwin)
88+
os_unfair_lock_lock(mutex)
89+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
90+
let err = pthread_mutex_lock(mutex)
91+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
92+
#endif
93+
}
94+
95+
@inlinable
96+
static func unlock(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
97+
mutex.assertValidAlignment()
98+
99+
#if os(Windows)
100+
ReleaseSRWLockExclusive(mutex)
101+
#elseif canImport(Darwin)
102+
os_unfair_lock_unlock(mutex)
103+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
104+
let err = pthread_mutex_unlock(mutex)
105+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
106+
#endif
107+
}
108+
}
109+
110+
// Tail allocate both the mutex and a generic value using ManagedBuffer.
111+
// Both the header pointer and the elements pointer are stable for
112+
// the class's entire lifetime.
113+
//
114+
// However, for safety reasons, we elect to place the lock in the "elements"
115+
// section of the buffer instead of the head. The reasoning here is subtle,
116+
// so buckle in.
117+
//
118+
// _As a practical matter_, the implementation of ManagedBuffer ensures that
119+
// the pointer to the header is stable across the lifetime of the class, and so
120+
// each time you call `withUnsafeMutablePointers` or `withUnsafeMutablePointerToHeader`
121+
// the value of the header pointer will be the same. This is because ManagedBuffer uses
122+
// `Builtin.addressOf` to load the value of the header, and that does ~magic~ to ensure
123+
// that it does not invoke any weird Swift accessors that might copy the value.
124+
//
125+
// _However_, the header is also available via the `.header` field on the ManagedBuffer.
126+
// This presents a problem! The reason there's an issue is that `Builtin.addressOf` and friends
127+
// do not interact with Swift's exclusivity model. That is, the various `with` functions do not
128+
// conceptually trigger a mutating access to `.header`. For elements this isn't a concern because
129+
// there's literally no other way to perform the access, but for `.header` it's entirely possible
130+
// to accidentally recursively read it.
131+
//
132+
// Our implementation is free from these issues, so we don't _really_ need to worry about it.
133+
// However, out of an abundance of caution, we store the Value in the header, and the LockPrimitive
134+
// in the trailing elements. We still don't use `.header`, but it's better to be safe than sorry,
135+
// and future maintainers will be happier that we were cautious.
136+
//
137+
// See also: https://github.com/apple/swift/pull/40000
138+
@usableFromInline
139+
final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
140+
141+
@inlinable
142+
static func create(value: Value) -> Self {
143+
let buffer = Self.create(minimumCapacity: 1) { _ in
144+
value
145+
}
146+
// Intentionally using a force cast here to avoid a miss compiliation in 5.10.
147+
// This is as fast as an unsafeDownCast since ManagedBuffer is inlined and the optimizer
148+
// can eliminate the upcast/downcast pair
149+
let storage = buffer as! Self
150+
151+
storage.withUnsafeMutablePointers { _, lockPtr in
152+
LockOperations.create(lockPtr)
153+
}
154+
155+
return storage
156+
}
157+
158+
@inlinable
159+
func lock() {
160+
self.withUnsafeMutablePointerToElements { lockPtr in
161+
LockOperations.lock(lockPtr)
162+
}
163+
}
164+
165+
@inlinable
166+
func unlock() {
167+
self.withUnsafeMutablePointerToElements { lockPtr in
168+
LockOperations.unlock(lockPtr)
169+
}
170+
}
171+
172+
@inlinable
173+
deinit {
174+
self.withUnsafeMutablePointerToElements { lockPtr in
175+
LockOperations.destroy(lockPtr)
176+
}
177+
}
178+
179+
@inlinable
180+
func withLockPrimitive<T>(_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T) rethrows -> T {
181+
try self.withUnsafeMutablePointerToElements { lockPtr in
182+
try body(lockPtr)
183+
}
184+
}
185+
186+
@inlinable
187+
func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T {
188+
try self.withUnsafeMutablePointers { valuePtr, lockPtr in
189+
LockOperations.lock(lockPtr)
190+
defer { LockOperations.unlock(lockPtr) }
191+
return try mutate(&valuePtr.pointee)
192+
}
193+
}
194+
}
195+
196+
extension LockStorage: @unchecked Sendable {}
197+
198+
extension UnsafeMutablePointer {
199+
@inlinable
200+
func assertValidAlignment() {
201+
assert(UInt(bitPattern: self) % UInt(MemoryLayout<Pointee>.alignment) == 0)
202+
}
203+
}

0 commit comments

Comments
 (0)