Update `NIOTSEventLoop` per apple/swift-nio#1508 (#84)

Motivation:

See apple/swift-nio#1508 for rationale

Modifications:

Provides implementations of `preconditionInEventLoop(_:file:line:)` and `preconditionNotInEventLoop(_:file:line:)`.

Result:

NIOTSEventLoop will now use dispatchPrecondition(condition:) for the in/not in preconditions instead of relying on the default implementation, yielding improved correctness.
This commit is contained in:
Gwynne Raskind 2020-05-07 04:27:27 -05:00 committed by GitHub
parent 7c74f62ea7
commit fe7e340f41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 22 additions and 2 deletions

View File

@ -50,7 +50,6 @@ fileprivate enum LifecycleState {
case closed
}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
internal class NIOTSEventLoop: QoSEventLoop {
private let loop: DispatchQueue
@ -81,6 +80,18 @@ internal class NIOTSEventLoop: QoSEventLoop {
/// implementation *could* return `true`, but this version will be unable to prove that and will return `false`.
/// If you need to write an assertion about being in the event loop that must be correct, use SwiftNIO 1.11 or
/// later and call `preconditionInEventLoop` and `assertInEventLoop`.
///
/// Further detail: The use of `DispatchQueue.sync(execute:)` to submit a block to a queue synchronously has the
/// effect of creating a state where the currently executing code is on two queues simultaneously - the one which
/// submitted the block, and the one on which the block runs. If another synchronous block is dispatched to a
/// third queue, that block is effectively running all three at once. Unfortunately, libdispatch maintains only
/// one "current" queue at a time as far as `DispatchQueue.getSpecific(key:)` is concerned, and it's always the
/// one actually executing code at the time. Therefore the queue belonging to the original event loop can't be
/// detected using its queue-specific data. No alternative API for the purpose exists (aside from assertions via
/// `dispatchPrecondition(condition:)`). Under these circumstances, `inEventLoop` will incorrectly be `false`,
/// even though the current code _is_ actually on the loop's queue. The only way to avoid this is to ensure no
/// callers ever use synchronous dispatch (which is impossible to enforce), or to hope that a future version of
/// libdispatch will provide a solution.
public var inEventLoop: Bool {
return DispatchQueue.getSpecific(key: self.inQueueKey) == self.loopID
}
@ -149,9 +160,18 @@ internal class NIOTSEventLoop: QoSEventLoop {
}
}
func preconditionInEventLoop(file: StaticString, line: UInt) {
public func preconditionInEventLoop(_ message: @autoclosure() -> String, file: StaticString, line: UInt) {
/// `dispatchPrecondition(condition:)` is unable to accept an error message for the failure case, and we
/// can not safely rely on `self.inEventLoop` in this implementation. Any message provided by the caller
/// must therefore be ignored. We do not foresee ever being able to fix this behavior, given the design
/// constraints of the respective subsystems.
dispatchPrecondition(condition: .onQueue(self.loop))
}
public func preconditionNotInEventLoop(_ message: @autoclosure() -> String = "", file: StaticString, line: UInt) {
/// As in our counterpart method, we can't do anything with the `message` parameter. See above for details.
dispatchPrecondition(condition: .notOnQueue(self.loop))
}
}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)