Skip to content

Commit f63c708

Browse files
committed
Quality of Life: Add NIOLoopBoundBox.withValue
1 parent 2e22c89 commit f63c708

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

Sources/NIOCore/NIOLoopBound.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,23 @@ public final class NIOLoopBoundBox<Value>: @unchecked Sendable {
175175
yield &self._value
176176
}
177177
}
178+
179+
/// Safely access and potentially modify the contained value with a closure.
180+
///
181+
/// This method provides a way to perform operations on the contained value while ensuring
182+
/// thread safety through EventLoop verification. The closure receives an `inout` parameter
183+
/// allowing both read and write access to the value.
184+
///
185+
/// - Parameter handler: A closure that receives an `inout` reference to the contained value.
186+
/// The closure can read from and write to this value. Any modifications made within the
187+
/// closure will be reflected in the box after the closure completes, even if the closure throws.
188+
/// - Returns: The value returned by the `handler` closure.
189+
/// - Note: This method is particularly useful when you need to perform read and write operations
190+
/// on the value because it reduces the on EventLoop checks.
191+
public func withValue<T, E: Error>(_ handler: (inout Value) throws(E) -> T) throws(E) -> T {
192+
self.eventLoop.preconditionInEventLoop()
193+
var value = self._value
194+
defer { self._value = value }
195+
return try handler(&value)
196+
}
178197
}

Tests/NIOPosixTests/NIOLoopBoundTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,34 @@ final class NIOLoopBoundTests: XCTestCase {
116116
XCTAssertTrue(loopBoundBox.value.mutateInPlace())
117117
}
118118

119+
func testWithValue() {
120+
var expectedValue = 0
121+
let loopBound = NIOLoopBoundBox(expectedValue, eventLoop: loop)
122+
for value in 1...100 {
123+
loopBound.withValue { boundValue in
124+
XCTAssertEqual(boundValue, expectedValue)
125+
boundValue = value
126+
expectedValue = value
127+
}
128+
}
129+
XCTAssertEqual(100, loopBound.value)
130+
}
131+
132+
func testWithValueRethrows() {
133+
struct TestError: Error {}
134+
135+
let loopBound = NIOLoopBoundBox(0, eventLoop: loop)
136+
XCTAssertThrowsError(
137+
try loopBound.withValue { boundValue in
138+
XCTAssertEqual(0, boundValue)
139+
boundValue = 10
140+
throw TestError()
141+
}
142+
)
143+
144+
XCTAssertEqual(10, loopBound.value, "Ensure value is set even if we throw")
145+
}
146+
119147
// MARK: - Helpers
120148
func sendableBlackhole<S: Sendable>(_ sendableThing: S) {}
121149

0 commit comments

Comments
 (0)