Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Sources/NIOCore/NIOLoopBound.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,25 @@ public final class NIOLoopBoundBox<Value>: @unchecked Sendable {
yield &self._value
}
}

#if compiler(>=6.0)
/// Safely access and potentially modify the contained value with a closure.
///
/// This method provides a way to perform operations on the contained value while ensuring
/// thread safety through EventLoop verification. The closure receives an `inout` parameter
/// allowing both read and write access to the value.
///
/// - Parameter handler: A closure that receives an `inout` reference to the contained value.
/// The closure can read from and write to this value. Any modifications made within the
/// closure will be reflected in the box after the closure completes, even if the closure throws.
/// - Returns: The value returned by the `handler` closure.
/// - Note: This method is particularly useful when you need to perform read and write operations
/// on the value because it reduces the on EventLoop checks.
public func withValue<Success, Failure: Error>(
_ handler: (inout Value) throws(Failure) -> Success
) throws(Failure) -> Success {
self.eventLoop.preconditionInEventLoop()
return try handler(&self._value)
}
#endif
}
30 changes: 30 additions & 0 deletions Tests/NIOPosixTests/NIOLoopBoundTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,36 @@ final class NIOLoopBoundTests: XCTestCase {
XCTAssertTrue(loopBoundBox.value.mutateInPlace())
}

#if compiler(>=6.0)
func testWithValue() {
var expectedValue = 0
let loopBound = NIOLoopBoundBox(expectedValue, eventLoop: loop)
for value in 1...100 {
loopBound.withValue { boundValue in
XCTAssertEqual(boundValue, expectedValue)
boundValue = value
expectedValue = value
}
}
XCTAssertEqual(100, loopBound.value)
}

func testWithValueRethrows() {
struct TestError: Error {}

let loopBound = NIOLoopBoundBox(0, eventLoop: loop)
XCTAssertThrowsError(
try loopBound.withValue { boundValue in
XCTAssertEqual(0, boundValue)
boundValue = 10
throw TestError()
}
)

XCTAssertEqual(10, loopBound.value, "Ensure value is set even if we throw")
}
#endif

// MARK: - Helpers
func sendableBlackhole<S: Sendable>(_ sendableThing: S) {}

Expand Down
Loading