|
|
@@ -0,0 +1,255 @@
|
|
|
+/*
|
|
|
+ * Copyright 2023, gRPC Authors All rights reserved.
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+//===----------------------------------------------------------------------===//
|
|
|
+//
|
|
|
+// This source file is part of the SwiftNIO open source project
|
|
|
+//
|
|
|
+// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
|
|
|
+// Licensed under Apache License v2.0
|
|
|
+//
|
|
|
+// See LICENSE.txt for license information
|
|
|
+// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
|
|
+//
|
|
|
+// SPDX-License-Identifier: Apache-2.0
|
|
|
+//
|
|
|
+//===----------------------------------------------------------------------===//
|
|
|
+
|
|
|
+#if canImport(Darwin)
|
|
|
+import Darwin
|
|
|
+#elseif canImport(Glibc)
|
|
|
+import Glibc
|
|
|
+#endif
|
|
|
+
|
|
|
+@usableFromInline
|
|
|
+typealias LockPrimitive = pthread_mutex_t
|
|
|
+
|
|
|
+@usableFromInline
|
|
|
+enum LockOperations {}
|
|
|
+
|
|
|
+extension LockOperations {
|
|
|
+ @inlinable
|
|
|
+ static func create(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
|
|
|
+ mutex.assertValidAlignment()
|
|
|
+
|
|
|
+ var attr = pthread_mutexattr_t()
|
|
|
+ pthread_mutexattr_init(&attr)
|
|
|
+
|
|
|
+ let err = pthread_mutex_init(mutex, &attr)
|
|
|
+ precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ static func destroy(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
|
|
|
+ mutex.assertValidAlignment()
|
|
|
+
|
|
|
+ let err = pthread_mutex_destroy(mutex)
|
|
|
+ precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ static func lock(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
|
|
|
+ mutex.assertValidAlignment()
|
|
|
+
|
|
|
+ let err = pthread_mutex_lock(mutex)
|
|
|
+ precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ static func unlock(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
|
|
|
+ mutex.assertValidAlignment()
|
|
|
+
|
|
|
+ let err = pthread_mutex_unlock(mutex)
|
|
|
+ precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Tail allocate both the mutex and a generic value using ManagedBuffer.
|
|
|
+// Both the header pointer and the elements pointer are stable for
|
|
|
+// the class's entire lifetime.
|
|
|
+//
|
|
|
+// However, for safety reasons, we elect to place the lock in the "elements"
|
|
|
+// section of the buffer instead of the head. The reasoning here is subtle,
|
|
|
+// so buckle in.
|
|
|
+//
|
|
|
+// _As a practical matter_, the implementation of ManagedBuffer ensures that
|
|
|
+// the pointer to the header is stable across the lifetime of the class, and so
|
|
|
+// each time you call `withUnsafeMutablePointers` or `withUnsafeMutablePointerToHeader`
|
|
|
+// the value of the header pointer will be the same. This is because ManagedBuffer uses
|
|
|
+// `Builtin.addressOf` to load the value of the header, and that does ~magic~ to ensure
|
|
|
+// that it does not invoke any weird Swift accessors that might copy the value.
|
|
|
+//
|
|
|
+// _However_, the header is also available via the `.header` field on the ManagedBuffer.
|
|
|
+// This presents a problem! The reason there's an issue is that `Builtin.addressOf` and friends
|
|
|
+// do not interact with Swift's exclusivity model. That is, the various `with` functions do not
|
|
|
+// conceptually trigger a mutating access to `.header`. For elements this isn't a concern because
|
|
|
+// there's literally no other way to perform the access, but for `.header` it's entirely possible
|
|
|
+// to accidentally recursively read it.
|
|
|
+//
|
|
|
+// Our implementation is free from these issues, so we don't _really_ need to worry about it.
|
|
|
+// However, out of an abundance of caution, we store the Value in the header, and the LockPrimitive
|
|
|
+// in the trailing elements. We still don't use `.header`, but it's better to be safe than sorry,
|
|
|
+// and future maintainers will be happier that we were cautious.
|
|
|
+//
|
|
|
+// See also: https://github.com/apple/swift/pull/40000
|
|
|
+@usableFromInline
|
|
|
+final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ static func create(value: Value) -> Self {
|
|
|
+ let buffer = Self.create(minimumCapacity: 1) { _ in
|
|
|
+ return value
|
|
|
+ }
|
|
|
+ let storage = unsafeDowncast(buffer, to: Self.self)
|
|
|
+
|
|
|
+ storage.withUnsafeMutablePointers { _, lockPtr in
|
|
|
+ LockOperations.create(lockPtr)
|
|
|
+ }
|
|
|
+
|
|
|
+ return storage
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ func lock() {
|
|
|
+ self.withUnsafeMutablePointerToElements { lockPtr in
|
|
|
+ LockOperations.lock(lockPtr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ func unlock() {
|
|
|
+ self.withUnsafeMutablePointerToElements { lockPtr in
|
|
|
+ LockOperations.unlock(lockPtr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ deinit {
|
|
|
+ self.withUnsafeMutablePointerToElements { lockPtr in
|
|
|
+ LockOperations.destroy(lockPtr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ func withLockPrimitive<T>(
|
|
|
+ _ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T
|
|
|
+ ) rethrows -> T {
|
|
|
+ try self.withUnsafeMutablePointerToElements { lockPtr in
|
|
|
+ return try body(lockPtr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T {
|
|
|
+ try self.withUnsafeMutablePointers { valuePtr, lockPtr in
|
|
|
+ LockOperations.lock(lockPtr)
|
|
|
+ defer { LockOperations.unlock(lockPtr) }
|
|
|
+ return try mutate(&valuePtr.pointee)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension LockStorage: @unchecked Sendable {}
|
|
|
+
|
|
|
+/// A threading lock based on `libpthread` instead of `libdispatch`.
|
|
|
+///
|
|
|
+/// - note: ``Lock`` has reference semantics.
|
|
|
+///
|
|
|
+/// This object provides a lock on top of a single `pthread_mutex_t`. This kind
|
|
|
+/// of lock is safe to use with `libpthread`-based threading models, such as the
|
|
|
+/// one used by NIO. On Windows, the lock is based on the substantially similar
|
|
|
+/// `SRWLOCK` type.
|
|
|
+@usableFromInline
|
|
|
+struct Lock {
|
|
|
+ @usableFromInline
|
|
|
+ internal let _storage: LockStorage<Void>
|
|
|
+
|
|
|
+ /// Create a new lock.
|
|
|
+ @inlinable
|
|
|
+ init() {
|
|
|
+ self._storage = .create(value: ())
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Acquire the lock.
|
|
|
+ ///
|
|
|
+ /// Whenever possible, consider using `withLock` instead of this method and
|
|
|
+ /// `unlock`, to simplify lock handling.
|
|
|
+ @inlinable
|
|
|
+ func lock() {
|
|
|
+ self._storage.lock()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Release the lock.
|
|
|
+ ///
|
|
|
+ /// Whenever possible, consider using `withLock` instead of this method and
|
|
|
+ /// `lock`, to simplify lock handling.
|
|
|
+ @inlinable
|
|
|
+ func unlock() {
|
|
|
+ self._storage.unlock()
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ internal func withLockPrimitive<T>(
|
|
|
+ _ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T
|
|
|
+ ) rethrows -> T {
|
|
|
+ return try self._storage.withLockPrimitive(body)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension Lock {
|
|
|
+ /// Acquire the lock for the duration of the given block.
|
|
|
+ ///
|
|
|
+ /// This convenience method should be preferred to `lock` and `unlock` in
|
|
|
+ /// most situations, as it ensures that the lock will be released regardless
|
|
|
+ /// of how `body` exits.
|
|
|
+ ///
|
|
|
+ /// - Parameter body: The block to execute while holding the lock.
|
|
|
+ /// - Returns: The value returned by the block.
|
|
|
+ @inlinable
|
|
|
+ func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
|
|
+ self.lock()
|
|
|
+ defer {
|
|
|
+ self.unlock()
|
|
|
+ }
|
|
|
+ return try body()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension Lock: Sendable {}
|
|
|
+
|
|
|
+extension UnsafeMutablePointer {
|
|
|
+ @inlinable
|
|
|
+ func assertValidAlignment() {
|
|
|
+ assert(UInt(bitPattern: self) % UInt(MemoryLayout<Pointee>.alignment) == 0)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@usableFromInline
|
|
|
+struct LockedValueBox<Value> {
|
|
|
+ @usableFromInline
|
|
|
+ let storage: LockStorage<Value>
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ init(_ value: Value) {
|
|
|
+ self.storage = .create(value: value)
|
|
|
+ }
|
|
|
+
|
|
|
+ @inlinable
|
|
|
+ func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T {
|
|
|
+ return try self.storage.withLockedValue(mutate)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension LockedValueBox: Sendable where Value: Sendable {}
|