Skip to content

Commit e97206c

Browse files
authored
Vendor in NIOs lock (#1681)
Motivation: For v2 we need a lock, and since we want to avoid taking on a NIO dependency in the core library we can vendor in NIOs lock type instead. Motivation: - Vendor in NIOs lock type and locked value box, make them internal - Update NOTICES.txt Result: We have a lock type.
1 parent cd104a0 commit e97206c

File tree

2 files changed

+258
-0
lines changed

2 files changed

+258
-0
lines changed

NOTICES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ This product uses scripts derived from SwiftNIO's integration testing
2121
framework: 'test_01_allocation_counts.sh', 'run-nio-alloc-counter-tests.sh' and
2222
'test_functions.sh'.
2323

24+
It also uses derivations of SwiftNIO's lock 'NIOLock.swift' and locked value box
25+
'NIOLockedValueBox.swift'.
26+
2427
* LICENSE (Apache License 2.0):
2528
* https://github.com/apple/swift-nio/blob/main/LICENSE.txt
2629
* HOMEPAGE:
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/*
2+
* Copyright 2023, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
//===----------------------------------------------------------------------===//
17+
//
18+
// This source file is part of the SwiftNIO open source project
19+
//
20+
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
21+
// Licensed under Apache License v2.0
22+
//
23+
// See LICENSE.txt for license information
24+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
25+
//
26+
// SPDX-License-Identifier: Apache-2.0
27+
//
28+
//===----------------------------------------------------------------------===//
29+
30+
#if canImport(Darwin)
31+
import Darwin
32+
#elseif canImport(Glibc)
33+
import Glibc
34+
#endif
35+
36+
@usableFromInline
37+
typealias LockPrimitive = pthread_mutex_t
38+
39+
@usableFromInline
40+
enum LockOperations {}
41+
42+
extension LockOperations {
43+
@inlinable
44+
static func create(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
45+
mutex.assertValidAlignment()
46+
47+
var attr = pthread_mutexattr_t()
48+
pthread_mutexattr_init(&attr)
49+
50+
let err = pthread_mutex_init(mutex, &attr)
51+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
52+
}
53+
54+
@inlinable
55+
static func destroy(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
56+
mutex.assertValidAlignment()
57+
58+
let err = pthread_mutex_destroy(mutex)
59+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
60+
}
61+
62+
@inlinable
63+
static func lock(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
64+
mutex.assertValidAlignment()
65+
66+
let err = pthread_mutex_lock(mutex)
67+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
68+
}
69+
70+
@inlinable
71+
static func unlock(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
72+
mutex.assertValidAlignment()
73+
74+
let err = pthread_mutex_unlock(mutex)
75+
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
76+
}
77+
}
78+
79+
// Tail allocate both the mutex and a generic value using ManagedBuffer.
80+
// Both the header pointer and the elements pointer are stable for
81+
// the class's entire lifetime.
82+
//
83+
// However, for safety reasons, we elect to place the lock in the "elements"
84+
// section of the buffer instead of the head. The reasoning here is subtle,
85+
// so buckle in.
86+
//
87+
// _As a practical matter_, the implementation of ManagedBuffer ensures that
88+
// the pointer to the header is stable across the lifetime of the class, and so
89+
// each time you call `withUnsafeMutablePointers` or `withUnsafeMutablePointerToHeader`
90+
// the value of the header pointer will be the same. This is because ManagedBuffer uses
91+
// `Builtin.addressOf` to load the value of the header, and that does ~magic~ to ensure
92+
// that it does not invoke any weird Swift accessors that might copy the value.
93+
//
94+
// _However_, the header is also available via the `.header` field on the ManagedBuffer.
95+
// This presents a problem! The reason there's an issue is that `Builtin.addressOf` and friends
96+
// do not interact with Swift's exclusivity model. That is, the various `with` functions do not
97+
// conceptually trigger a mutating access to `.header`. For elements this isn't a concern because
98+
// there's literally no other way to perform the access, but for `.header` it's entirely possible
99+
// to accidentally recursively read it.
100+
//
101+
// Our implementation is free from these issues, so we don't _really_ need to worry about it.
102+
// However, out of an abundance of caution, we store the Value in the header, and the LockPrimitive
103+
// in the trailing elements. We still don't use `.header`, but it's better to be safe than sorry,
104+
// and future maintainers will be happier that we were cautious.
105+
//
106+
// See also: https://github.com/apple/swift/pull/40000
107+
@usableFromInline
108+
final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
109+
110+
@inlinable
111+
static func create(value: Value) -> Self {
112+
let buffer = Self.create(minimumCapacity: 1) { _ in
113+
return value
114+
}
115+
let storage = unsafeDowncast(buffer, to: Self.self)
116+
117+
storage.withUnsafeMutablePointers { _, lockPtr in
118+
LockOperations.create(lockPtr)
119+
}
120+
121+
return storage
122+
}
123+
124+
@inlinable
125+
func lock() {
126+
self.withUnsafeMutablePointerToElements { lockPtr in
127+
LockOperations.lock(lockPtr)
128+
}
129+
}
130+
131+
@inlinable
132+
func unlock() {
133+
self.withUnsafeMutablePointerToElements { lockPtr in
134+
LockOperations.unlock(lockPtr)
135+
}
136+
}
137+
138+
@inlinable
139+
deinit {
140+
self.withUnsafeMutablePointerToElements { lockPtr in
141+
LockOperations.destroy(lockPtr)
142+
}
143+
}
144+
145+
@inlinable
146+
func withLockPrimitive<T>(
147+
_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T
148+
) rethrows -> T {
149+
try self.withUnsafeMutablePointerToElements { lockPtr in
150+
return try body(lockPtr)
151+
}
152+
}
153+
154+
@inlinable
155+
func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T {
156+
try self.withUnsafeMutablePointers { valuePtr, lockPtr in
157+
LockOperations.lock(lockPtr)
158+
defer { LockOperations.unlock(lockPtr) }
159+
return try mutate(&valuePtr.pointee)
160+
}
161+
}
162+
}
163+
164+
extension LockStorage: @unchecked Sendable {}
165+
166+
/// A threading lock based on `libpthread` instead of `libdispatch`.
167+
///
168+
/// - note: ``Lock`` has reference semantics.
169+
///
170+
/// This object provides a lock on top of a single `pthread_mutex_t`. This kind
171+
/// of lock is safe to use with `libpthread`-based threading models, such as the
172+
/// one used by NIO. On Windows, the lock is based on the substantially similar
173+
/// `SRWLOCK` type.
174+
@usableFromInline
175+
struct Lock {
176+
@usableFromInline
177+
internal let _storage: LockStorage<Void>
178+
179+
/// Create a new lock.
180+
@inlinable
181+
init() {
182+
self._storage = .create(value: ())
183+
}
184+
185+
/// Acquire the lock.
186+
///
187+
/// Whenever possible, consider using `withLock` instead of this method and
188+
/// `unlock`, to simplify lock handling.
189+
@inlinable
190+
func lock() {
191+
self._storage.lock()
192+
}
193+
194+
/// Release the lock.
195+
///
196+
/// Whenever possible, consider using `withLock` instead of this method and
197+
/// `lock`, to simplify lock handling.
198+
@inlinable
199+
func unlock() {
200+
self._storage.unlock()
201+
}
202+
203+
@inlinable
204+
internal func withLockPrimitive<T>(
205+
_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T
206+
) rethrows -> T {
207+
return try self._storage.withLockPrimitive(body)
208+
}
209+
}
210+
211+
extension Lock {
212+
/// Acquire the lock for the duration of the given block.
213+
///
214+
/// This convenience method should be preferred to `lock` and `unlock` in
215+
/// most situations, as it ensures that the lock will be released regardless
216+
/// of how `body` exits.
217+
///
218+
/// - Parameter body: The block to execute while holding the lock.
219+
/// - Returns: The value returned by the block.
220+
@inlinable
221+
func withLock<T>(_ body: () throws -> T) rethrows -> T {
222+
self.lock()
223+
defer {
224+
self.unlock()
225+
}
226+
return try body()
227+
}
228+
}
229+
230+
extension Lock: Sendable {}
231+
232+
extension UnsafeMutablePointer {
233+
@inlinable
234+
func assertValidAlignment() {
235+
assert(UInt(bitPattern: self) % UInt(MemoryLayout<Pointee>.alignment) == 0)
236+
}
237+
}
238+
239+
@usableFromInline
240+
struct LockedValueBox<Value> {
241+
@usableFromInline
242+
let storage: LockStorage<Value>
243+
244+
@inlinable
245+
init(_ value: Value) {
246+
self.storage = .create(value: value)
247+
}
248+
249+
@inlinable
250+
func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T {
251+
return try self.storage.withLockedValue(mutate)
252+
}
253+
}
254+
255+
extension LockedValueBox: Sendable where Value: Sendable {}

0 commit comments

Comments
 (0)