-
Notifications
You must be signed in to change notification settings - Fork 652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add NIOLoopBound(Box) uncheckedValue #2515
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A few hours after I pushed this up, I realized that having an unchecked There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is worth keeping: while you only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think unchecked isn't quite the right name for this init but I can't come up with a better one right now. It is not completely unchecked but rather just asserts than preconditions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unchecked is fine for that: it's what |
||
eventLoop.assertInEventLoop() | ||
self._eventLoop = eventLoop | ||
self._value = value | ||
} | ||
|
@@ -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 | ||
} | ||
} | ||
Comment on lines
+69
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we just provide a second type that does asserts or preconditions. Alternatively what about just making the whole precondition/assert behaviour configurable on init? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this pattern is better: I don't see a good reason to make this a type or a runtime distinction. It's good to be able to see this in the code, as it's fundamentally a safety-breaking optimization. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair, if we say There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TBH I'm also thinking a type would be better. It's how the rest of Swifts unsafe/unchecked APIs usually work e.g. It may also enable us to drop the stored There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One type is better here: I often want mixed use: First time in a function: checked, follow-ups: unchecked. Different type would be pain and would likely push me to use unchecked everywhere. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
All the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Similarly here: you want to draw attention to the places where the invariants are being broken, not to where they are being observed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree that you need to look at where the invariants are broken but you need to look at the creation for that for You can't say in isolation if We are proposing a set of duplicated APIs which make all operations on This design also gives the wrong impression that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Concretely, I'm proposing this API: public struct NIOUncheckedLoopBound<Value> {
public init(_ value: Value, eventLoop: EventLoop) {}
public init(_ value: NIOLoopBound<Value>) {}
public init(_ value: NIOLoopBoundBox<Value>) {}
public var value: Value {
get {}
}
}
public final class NIOUncheckedLoopBoundBox<Value> {
public init(_ value: Value, eventLoop: EventLoop) {}
public init(_ value: NIOLoopBound<Value>) {}
public init(_ value: NIOLoopBoundBox<Value>) {}
public var value: Value {
get {}
set {}
}
} If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoa, thank you, @dnadoba, @Lukasa, and @weissi for the discussion! I'm learning quite a lot from you here. I'm not attached to the current implementation — as I learn more and if we decide to change it, I'm happy to rewrite it.
I agree, and I agree it's different from the
If we choose to stick with the current implementation, I think I should at the very least comment and document on that behavior.
@Lukasa, with the Again, I feel that I'm out of my depth in the API design discussion, but I'm delighted to be part of it and listen in. Feel free to "well actually" me on any points I'm bringing up. |
||
} | ||
|
||
/// ``NIOLoopBoundBox`` is an always-`Sendable`, reference-typed container allowing you access to ``value`` if and | ||
|
@@ -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`. | ||
|
@@ -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`. | ||
|
@@ -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 | ||
} | ||
} | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Run |
||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tomato tomato, but thought there are more
initializes
in our codebase thaninitialises
.