From fea4e19c595e1fc81c3108d96ca4de9eea431625 Mon Sep 17 00:00:00 2001 From: Gwynne Raskind Date: Mon, 11 May 2020 08:07:56 -0500 Subject: [PATCH] Add inverse of `preconditionInEventLoop()` and ability to add messages to the assertion (#1508) Motivation: It is valuable to be able to provide additional context for any kind of assertion failure, such as obvious points of API misuse or when the purpose of the assertion itself is unclear. Modifications: Added an overload of preconditionInEventLoop() that accepts a message autoclosure parameter in the same fashion as `Swift.precondition(_:file:line:)`. Default the implementation of the original version to calling the new one with an empty message. Add defaulted implementation of the new version which forwards the message closure to precondition(). Add the new message parameter directly to assertInEventLoop() without a forwarder since it is not a customization point. Result: It is now possible to optionally provide explanatory messages to be included when the "in event loop" precondition fails, as with any other precondition. --- Sources/NIO/EventLoop.swift | 33 ++++++++++++++++--- Sources/NIOTestUtils/NIOHTTP1TestServer.swift | 6 ++-- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Sources/NIO/EventLoop.swift b/Sources/NIO/EventLoop.swift index c0bede0591..75202b51c3 100644 --- a/Sources/NIO/EventLoop.swift +++ b/Sources/NIO/EventLoop.swift @@ -244,9 +244,13 @@ public protocol EventLoop: EventLoopGroup { @discardableResult func scheduleTask(in: TimeAmount, _ task: @escaping () throws -> T) -> Scheduled - /// Checks that this call is run from the `EventLoop`. If this is called from within the `EventLoop` this function - /// will have no effect, if called from outside the `EventLoop` it will crash the process with a trap. + /// Asserts that the current thread is the one tied to this `EventLoop`. + /// Otherwise, the process will be abnormally terminated as per the semantics of `preconditionFailure(_:file:line:)`. func preconditionInEventLoop(file: StaticString, line: UInt) + + /// Asserts that the current thread is _not_ the one tied to this `EventLoop`. + /// Otherwise, the process will be abnormally terminated as per the semantics of `preconditionFailure(_:file:line:)`. + func preconditionNotInEventLoop(file: StaticString, line: UInt) } extension EventLoopGroup { @@ -580,9 +584,9 @@ extension EventLoop { return EventLoopIterator([self]) } - /// Checks that this call is run from the EventLoop. If this is called from within the EventLoop this function will - /// have no effect, if called from outside the EventLoop it will crash the process with a trap if run in debug mode. - /// In release mode this function never has any effect. + /// Asserts that the current thread is the one tied to this `EventLoop`. + /// Otherwise, if running in debug mode, the process will be abnormally terminated as per the semantics of + /// `preconditionFailure(_:file:line:)`. Never has any effect in release mode. /// /// - note: This is not a customization point so calls to this function can be fully optimized out in release mode. @inlinable @@ -592,10 +596,29 @@ extension EventLoop { } } + /// Asserts that the current thread is _not_ the one tied to this `EventLoop`. + /// Otherwise, if running in debug mode, the process will be abnormally terminated as per the semantics of + /// `preconditionFailure(_:file:line:)`. Never has any effect in release mode. + /// + /// - note: This is not a customization point so calls to this function can be fully optimized out in release mode. + @inlinable + public func assertNotInEventLoop(file: StaticString = #file, line: UInt = #line) { + debugOnly { + self.preconditionNotInEventLoop(file: file, line: line) + } + } + /// Checks the necessary condition of currently running on the called `EventLoop` for making forward progress. + @inlinable public func preconditionInEventLoop(file: StaticString = #file, line: UInt = #line) { precondition(self.inEventLoop, file: file, line: line) } + + /// Checks the necessary condition of currently _not_ running on the called `EventLoop` for making forward progress. + @inlinable + public func preconditionNotInEventLoop(file: StaticString = #file, line: UInt = #line) { + precondition(!self.inEventLoop, file: file, line: line) + } } /// Internal representation of a `Registration` to an `Selector`. diff --git a/Sources/NIOTestUtils/NIOHTTP1TestServer.swift b/Sources/NIOTestUtils/NIOHTTP1TestServer.swift index 13c910a853..d487d281ad 100644 --- a/Sources/NIOTestUtils/NIOHTTP1TestServer.swift +++ b/Sources/NIOTestUtils/NIOHTTP1TestServer.swift @@ -283,14 +283,14 @@ extension NIOHTTP1TestServer { } public func readInbound(deadline: NIODeadline = .now() + .seconds(10)) throws -> HTTPServerRequestPart { - assert(!self.eventLoop.inEventLoop) + self.eventLoop.assertNotInEventLoop() return try self.eventLoop.submit { () -> BlockingQueue in self.inboundBuffer }.wait().popFirst(deadline: deadline) } public func writeOutbound(_ data: HTTPServerResponsePart) throws { - assert(!self.eventLoop.inEventLoop) + self.eventLoop.assertNotInEventLoop() try self.eventLoop.flatSubmit { () -> EventLoopFuture in if let channel = self.currentClientChannel { return channel.writeAndFlush(data) @@ -301,7 +301,7 @@ extension NIOHTTP1TestServer { } public var serverPort: Int { - assert(!self.eventLoop.inEventLoop) + self.eventLoop.assertNotInEventLoop() return self.serverChannel!.localAddress!.port! } }