Skip to content

Commit

Permalink
Add NIOLoopBound(Box) uncheckedValue
Browse files Browse the repository at this point in the history
**Summary**:
This commit adds unchecked version of init and value get+set of
`NIOLoopBound` and `NIOLoopBoundBox`.

**Motivation**:
- Closes apple#2506.

**Changes**:
- Added `init` and `public var uncheckedValue` properties to both
  `NIOLoopBound` and `NIOLoopBoundBox`.

**Review & help**
- [x] Tests pass locally.
- [ ] How should we test that `NIOLoopBound.init(uncheckedEventLoop:)`
  calls `assertInEventLoop`? `EmbeddedEventLoop` seems to be final — how
  do I mock the assert function on in elegantly, without copy and
    pasting all the protocol conformance stuff?
  • Loading branch information
natikgadzhi committed Sep 9, 2023
1 parent e0d8554 commit a272a54
Showing 1 changed file with 57 additions and 5 deletions.
62 changes: 57 additions & 5 deletions Sources/NIOCore/NIOLoopBound.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ public struct NIOLoopBound<Value>: @unchecked Sendable {
@usableFromInline
/* private */ var _value: Value

/// Initialise a ``NIOLoopBound`` to `value` with the precondition that the code is running on `eventLoop`.
/// Initialize a ``NIOLoopBound`` to `value` with the precondition that the code is running on `eventLoop`.
@inlinable
public init(_ value: Value, eventLoop: EventLoop) {
eventLoop.preconditionInEventLoop()
self.init(value, uncheckedEventLoop: eventLoop)
}

@inlinable
/// Initialize a ``NIOLoopBound`` to `value` with _an assertion_ that the code is running on `uncheckedEventLoop`.
/// Unlike a precondition check, ``EventLoop/assertInEventLoop(file:line:)`` only performs the check in debug configuration, so the check is free in release configuration.
public init(_ value: Value, uncheckedEventLoop eventLoop: EventLoop) {
eventLoop.assertInEventLoop()
self._eventLoop = eventLoop
self._value = value
}
Expand All @@ -52,6 +60,23 @@ public struct NIOLoopBound<Value>: @unchecked Sendable {
self._value = newValue
}
}

/// Access the `value` with the assertion that the code is running on `eventLoop`.
///
/// Unlike ``NIOLoopBound/value``, this performs the assertion in debug configuration only, so it's
/// cheaper, and still performs the precondition check in debug mode.
/// - note: ``NIOLoopBound`` itself is value-typed, so any writes will only affect the current value.
@inlinable
public var uncheckedValue: Value {
get {
self._eventLoop.assertInEventLoop()
return self._value
}
set {
self._eventLoop.assertInEventLoop()
self._value = newValue
}
}
}

/// ``NIOLoopBoundBox`` is an always-`Sendable`, reference-typed container allowing you access to ``value`` if and
Expand All @@ -77,19 +102,29 @@ public final class NIOLoopBoundBox<Value>: @unchecked Sendable {
@usableFromInline
/* private */var _value: Value

/// Initialize a ``NIOLoopBoundBox`` to `value` with the an assertion that the code is running on `eventLoop`.
///
/// - note: Unlike ``NIOLoopBoundBox/init(_:eventLoop:)``, this performs ``EventLoop/assertInEventLoop(file:line:)`` instead of a precondition check, which is free in release mode.
@inlinable
public init(_ value: Value, uncheckedEventLoop eventLoop: EventLoop) {
eventLoop.assertInEventLoop()
self._eventLoop = eventLoop
self._value = value
}

@inlinable
internal init(_value value: Value, uncheckedEventLoop eventLoop: EventLoop) {
internal init(_ value: Value, notVerifyingEventLoop eventLoop: EventLoop) {
self._eventLoop = eventLoop
self._value = value
}

/// Initialise a ``NIOLoopBoundBox`` to `value` with the precondition that the code is running on `eventLoop`.
/// Initialize a ``NIOLoopBoundBox`` to `value` with the precondition that the code is running on `eventLoop`.
@inlinable
public convenience init(_ value: Value, eventLoop: EventLoop) {
// This precondition is absolutely required. If not, it were possible to take a non-Sendable `Value` from
// _off_ the ``EventLoop`` and transport it _to_ the ``EventLoop``. That would be illegal.
eventLoop.preconditionInEventLoop()
self.init(_value: value, uncheckedEventLoop: eventLoop)
self.init(value, uncheckedEventLoop: eventLoop)
}

/// Initialise a ``NIOLoopBoundBox`` that is empty (contains `nil`), this does _not_ require you to be running on `eventLoop`.
Expand All @@ -104,7 +139,7 @@ public final class NIOLoopBoundBox<Value>: @unchecked Sendable {
// - Because of Swift's Definitive Initialisation (DI), we know that we did write `self._value` before `init`
// returns.
// - The only way to ever write (or read indeed) `self._value` is by proving to be inside the `EventLoop`.
return .init(_value: nil, uncheckedEventLoop: eventLoop)
return .init(nil, notVerifyingEventLoop: eventLoop)
}

/// Access the `value` with the precondition that the code is running on `eventLoop`.
Expand All @@ -121,5 +156,22 @@ public final class NIOLoopBoundBox<Value>: @unchecked Sendable {
self._value = newValue
}
}

/// Access the `value` with the assertion that the code is running on `eventLoop`.
///
/// - note: ``NIOLoopBoundBox`` itself is reference-typed, so any writes will affect anybody sharing this reference.
@inlinable
public var uncheckedValue: Value {
get {
self._eventLoop.assertInEventLoop()
return self._value
}
set {
self._eventLoop.assertInEventLoop()
self._value = newValue
}
}


}

0 comments on commit a272a54

Please sign in to comment.