Skip to content

Commit 45b463c

Browse files
authored
Quality of Life: Add NIOLoopBoundBox.withValue (#3385)
When having a state machine inside the `NIOLoopBoundBox` we currently check the EL when reading and writing. This is unnecessary. Because of this, this PR introduces a new `withValue` method, that allows users to read and write the value inside the NIOLoopBoundBox while only paying the cost for the EL check once.
1 parent 83d4386 commit 45b463c

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

Sources/NIOCore/NIOLoopBound.swift

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

Tests/NIOPosixTests/NIOLoopBoundTests.swift

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

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

0 commit comments

Comments
 (0)