|
| 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