diff --git a/Sources/NIOCore/NIOLoopBound.swift b/Sources/NIOCore/NIOLoopBound.swift index a3631f5b2f..6ce3c465dd 100644 --- a/Sources/NIOCore/NIOLoopBound.swift +++ b/Sources/NIOCore/NIOLoopBound.swift @@ -47,9 +47,9 @@ public struct NIOLoopBound: @unchecked Sendable { self._eventLoop.preconditionInEventLoop() return self._value } - set { + _modify { self._eventLoop.preconditionInEventLoop() - self._value = newValue + yield &self._value } } } @@ -136,9 +136,9 @@ public final class NIOLoopBoundBox: @unchecked Sendable { self._eventLoop.preconditionInEventLoop() return self._value } - set { + _modify { self._eventLoop.preconditionInEventLoop() - self._value = newValue + yield &self._value } } } diff --git a/Tests/NIOPosixTests/CoWValue.swift b/Tests/NIOPosixTests/CoWValue.swift new file mode 100644 index 0000000000..894dace81d --- /dev/null +++ b/Tests/NIOPosixTests/CoWValue.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +/// A Copy on Write (CoW) type that can be used in tests to assert in-place mutation +struct CoWValue: @unchecked Sendable { + private final class UniquenessIndicator {} + + /// This reference is "copied" if not uniquely referenced + private var uniquenessIndicator = UniquenessIndicator() + + /// mutates `self` and returns a boolean whether it was mutated in place or not + /// - Returns: true if mutation happened in-place, false if Copy on Write (CoW) was triggered + mutating func mutateInPlace() -> Bool { + guard isKnownUniquelyReferenced(&self.uniquenessIndicator) else { + self.uniquenessIndicator = UniquenessIndicator() + return false + } + return true + } +} diff --git a/Tests/NIOPosixTests/NIOLoopBoundTests.swift b/Tests/NIOPosixTests/NIOLoopBoundTests.swift index 0c525125ad..cf4207ecfc 100644 --- a/Tests/NIOPosixTests/NIOLoopBoundTests.swift +++ b/Tests/NIOPosixTests/NIOLoopBoundTests.swift @@ -68,6 +68,14 @@ final class NIOLoopBoundTests: XCTestCase { }.wait()) } + func testInPlaceMutation() { + var loopBound = NIOLoopBound(CoWValue(), eventLoop: loop) + XCTAssertTrue(loopBound.value.mutateInPlace()) + + let loopBoundBox = NIOLoopBoundBox(CoWValue(), eventLoop: loop) + XCTAssertTrue(loopBoundBox.value.mutateInPlace()) + } + // MARK: - Helpers func sendableBlackhole(_ sendableThing: S) {}