From 8ee527ac5258f6d68c074ae9221bc7691b91fb29 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 8 Jan 2018 16:39:54 +0000 Subject: [PATCH] Simplify running code --- Sources/NIO/Embedded.swift | 31 +++---- Tests/LinuxMain.swift | 1 + Tests/NIOTLSTests/SNIHandlerTests.swift | 8 +- Tests/NIOTests/ChannelPipelineTest.swift | 4 +- .../EmbeddedEventLoopTest+XCTest.swift | 37 ++++++++ Tests/NIOTests/EmbeddedEventLoopTest.swift | 88 +++++++++++++++++++ 6 files changed, 144 insertions(+), 25 deletions(-) create mode 100644 Tests/NIOTests/EmbeddedEventLoopTest+XCTest.swift create mode 100644 Tests/NIOTests/EmbeddedEventLoopTest.swift diff --git a/Sources/NIO/Embedded.swift b/Sources/NIO/Embedded.swift index 53daf98b..8f497917 100644 --- a/Sources/NIO/Embedded.swift +++ b/Sources/NIO/Embedded.swift @@ -16,11 +16,9 @@ import Dispatch public class EmbeddedEventLoop : EventLoop { - let queue = DispatchQueue(label: "embeddedEventLoopQueue", qos: .utility) public var inEventLoop: Bool { return true } - var isRunning: Bool = false var tasks = CircularBuffer<() -> ()>(initialRingCapacity: 2) @@ -46,25 +44,15 @@ public class EmbeddedEventLoop : EventLoop { // We're not really running a loop here. Tasks aren't run until run() is called, // at which point we run everything that's been submitted. Anything newly submitted - // either gets on that train if it's still moving or + // either gets on that train if it's still moving or waits until the next call to run(). public func execute(task: @escaping () -> ()) { - queue.sync { - if isRunning && tasks.isEmpty { - task() - } else { - tasks.append(task) - } - } + tasks.append(task) } - func run() throws { - queue.sync { - isRunning = true - - // Execute all tasks that are currently enqueued. - while !tasks.isEmpty { - tasks.removeFirst()() - } + func run() { + // Execute all tasks that are currently enqueued. + while !tasks.isEmpty { + tasks.removeFirst()() } } @@ -73,10 +61,15 @@ public class EmbeddedEventLoop : EventLoop { } public func shutdownGracefully(queue: DispatchQueue, _ callback: @escaping (Error?) -> Void) { - queue.async { + run() + queue.sync { callback(nil) } } + + deinit { + precondition(tasks.isEmpty, "Embedded event loop freed with unexecuted tasks!") + } } class EmbeddedChannelCore : ChannelCore { diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 7e0bf13d..f129a499 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -42,6 +42,7 @@ import XCTest testCase(ClientSNITests.allTests), testCase(EchoServerClientTest.allTests), testCase(EmbeddedChannelTest.allTests), + testCase(EmbeddedEventLoopTest.allTests), testCase(EventLoopFutureTest.allTests), testCase(EventLoopTest.allTests), testCase(FileRegionTest.allTests), diff --git a/Tests/NIOTLSTests/SNIHandlerTests.swift b/Tests/NIOTLSTests/SNIHandlerTests.swift index 637b43f1..b086fd7b 100644 --- a/Tests/NIOTLSTests/SNIHandlerTests.swift +++ b/Tests/NIOTLSTests/SNIHandlerTests.swift @@ -281,7 +281,7 @@ class SniHandlerTest: XCTestCase { while buffer.readableBytes > 0 { let writeableData = buffer.readSlice(length: 1)! try channel.writeInbound(data: writeableData) - try loop.run() + loop.run() XCTAssertNil(channel.readInbound()) try channel.pipeline.assertContains(handler: handler) @@ -296,7 +296,7 @@ class SniHandlerTest: XCTestCase { // Now we're going to complete the promise and run the loop. This should cause the complete // ClientHello to be sent on, and the SniHandler to be removed from the pipeline. continuePromise.succeed(result: ()) - try loop.run() + loop.run() let writtenBuffer: ByteBuffer = channel.readInbound()! let writtenData = writtenBuffer.getData(at: writtenBuffer.readerIndex, length: writtenBuffer.readableBytes) @@ -325,7 +325,7 @@ class SniHandlerTest: XCTestCase { // Ok, let's go. try channel.writeInbound(data: buffer) - try loop.run() + loop.run() // The callback should have fired, but the handler should not have // sent on any data and should still be in the pipeline. @@ -336,7 +336,7 @@ class SniHandlerTest: XCTestCase { // Now we're going to complete the promise and run the loop. This should cause the complete // ClientHello to be sent on, and the SniHandler to be removed from the pipeline. continuePromise.succeed(result: ()) - try loop.run() + loop.run() let writtenBuffer: ByteBuffer? = channel.readInbound() if let writtenBuffer = writtenBuffer { diff --git a/Tests/NIOTests/ChannelPipelineTest.swift b/Tests/NIOTests/ChannelPipelineTest.swift index aa32eb40..cc7cb3e9 100644 --- a/Tests/NIOTests/ChannelPipelineTest.swift +++ b/Tests/NIOTests/ChannelPipelineTest.swift @@ -160,12 +160,12 @@ class ChannelPipelineTest: XCTestCase { let channel = EmbeddedChannel() _ = try channel.close().wait() let loop = channel.eventLoop as! EmbeddedEventLoop - try loop.run() + loop.run() XCTAssertTrue(loop.inEventLoop) do { try channel.writeOutbound(data: FileRegion(descriptor: -1, readerIndex: 0, endIndex: 0)) - try loop.run() + loop.run() XCTFail("we ran but an error should have been thrown") } catch let err as ChannelError { XCTAssertEqual(err, .ioOnClosedChannel) diff --git a/Tests/NIOTests/EmbeddedEventLoopTest+XCTest.swift b/Tests/NIOTests/EmbeddedEventLoopTest+XCTest.swift new file mode 100644 index 00000000..3144df81 --- /dev/null +++ b/Tests/NIOTests/EmbeddedEventLoopTest+XCTest.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +/// +/// EmbeddedEventLoopTest+XCTest.swift +/// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension EmbeddedEventLoopTest { + + static var allTests : [(String, (EmbeddedEventLoopTest) -> () throws -> Void)] { + return [ + ("testExecuteDoesNotImmediatelyRunTasks", testExecuteDoesNotImmediatelyRunTasks), + ("testExecuteWillRunAllTasks", testExecuteWillRunAllTasks), + ("testExecuteWillRunTasksAddedRecursively", testExecuteWillRunTasksAddedRecursively), + ("testTasksSubmittedAfterRunDontRun", testTasksSubmittedAfterRunDontRun), + ("testShutdownGracefullyRunsTasks", testShutdownGracefullyRunsTasks), + ] + } +} + diff --git a/Tests/NIOTests/EmbeddedEventLoopTest.swift b/Tests/NIOTests/EmbeddedEventLoopTest.swift new file mode 100644 index 00000000..08a86b22 --- /dev/null +++ b/Tests/NIOTests/EmbeddedEventLoopTest.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@testable import NIO +import XCTest + +public class EmbeddedEventLoopTest: XCTestCase { + func testExecuteDoesNotImmediatelyRunTasks() throws { + var callbackRan = false + let loop = EmbeddedEventLoop() + loop.execute { callbackRan = true } + + XCTAssertFalse(callbackRan) + loop.run() + XCTAssertTrue(callbackRan) + } + + func testExecuteWillRunAllTasks() throws { + var runCount = 0 + let loop = EmbeddedEventLoop() + loop.execute { runCount += 1 } + loop.execute { runCount += 1 } + loop.execute { runCount += 1 } + + XCTAssertEqual(runCount, 0) + loop.run() + XCTAssertEqual(runCount, 3) + } + + func testExecuteWillRunTasksAddedRecursively() throws { + var sentinel = 0 + let loop = EmbeddedEventLoop() + + loop.execute { + // This should execute first. + XCTAssertEqual(sentinel, 0) + sentinel = 1 + loop.execute { + // This should execute third. + XCTAssertEqual(sentinel, 2) + sentinel = 3 + } + } + loop.execute { + // This should execute second. + XCTAssertEqual(sentinel, 1) + sentinel = 2 + } + + XCTAssertEqual(sentinel, 0) + loop.run() + XCTAssertEqual(sentinel, 3) + } + + func testTasksSubmittedAfterRunDontRun() throws { + var callbackRan = false + let loop = EmbeddedEventLoop() + loop.execute { callbackRan = true } + + XCTAssertFalse(callbackRan) + loop.run() + loop.execute { callbackRan = false } + XCTAssertTrue(callbackRan) + loop.run() + XCTAssertFalse(callbackRan) + } + + func testShutdownGracefullyRunsTasks() throws { + var callbackRan = false + let loop = EmbeddedEventLoop() + loop.execute { callbackRan = true } + + XCTAssertFalse(callbackRan) + XCTAssertNoThrow(try loop.syncShutdownGracefully()) + XCTAssertTrue(callbackRan) + } +}