Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Sources/Lifecycle/Lifecycle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -507,11 +507,17 @@ public class ComponentLifecycle: LifecycleTask {

self.stateLock.lock()
switch self.state {
case .idle:
case .idle(let tasks) where tasks.isEmpty:
self.state = .shutdown(nil)
self.stateLock.unlock()
defer { self.shutdownGroup.leave() }
callback(nil)
case .idle(let tasks):
self.stateLock.unlock()
// attempt to shutdown any registered tasks
let stoppable = tasks.filter { $0.shutdownIfNotStarted }
setupShutdownListener(.global())
self._shutdown(on: .global(), tasks: stoppable, callback: self.shutdownGroup.leave)
case .shutdown:
self.stateLock.unlock()
self.logger.warning("already shutdown")
Expand Down
2 changes: 2 additions & 0 deletions Tests/LifecycleTests/ComponentLifecycleTests+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ extension ComponentLifecycleTests {
("testUserDefinedCallbackQueue", testUserDefinedCallbackQueue),
("testShutdownWhileStarting", testShutdownWhileStarting),
("testShutdownWhenIdle", testShutdownWhenIdle),
("testShutdownWhenIdleAndNoItems", testShutdownWhenIdleAndNoItems),
("testIfNotStartedWhenIdle", testIfNotStartedWhenIdle),
("testShutdownWhenShutdown", testShutdownWhenShutdown),
("testShutdownDuringHangingStart", testShutdownDuringHangingStart),
("testShutdownErrors", testShutdownErrors),
Expand Down
66 changes: 59 additions & 7 deletions Tests/LifecycleTests/ComponentLifecycleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,23 +159,75 @@ final class ComponentLifecycleTests: XCTestCase {

func testShutdownWhenIdle() {
let lifecycle = ComponentLifecycle(label: "test")
lifecycle.register(GoodItem())

let sempahpore1 = DispatchSemaphore(value: 0)
let item = GoodItem()
lifecycle.register(item)

let semaphore1 = DispatchSemaphore(value: 0)
lifecycle.shutdown { errors in
XCTAssertNil(errors)
sempahpore1.signal()
semaphore1.signal()
}
lifecycle.wait()
XCTAssertEqual(.success, sempahpore1.wait(timeout: .now() + 1))
XCTAssertEqual(.success, semaphore1.wait(timeout: .now() + 1))

let sempahpore2 = DispatchSemaphore(value: 0)
let semaphore2 = DispatchSemaphore(value: 0)
lifecycle.shutdown { errors in
XCTAssertNil(errors)
sempahpore2.signal()
semaphore2.signal()
}
lifecycle.wait()
XCTAssertEqual(.success, sempahpore2.wait(timeout: .now() + 1))
XCTAssertEqual(.success, semaphore2.wait(timeout: .now() + 1))

XCTAssertEqual(item.state, .idle, "expected item to be idle")
}

func testShutdownWhenIdleAndNoItems() {
let lifecycle = ComponentLifecycle(label: "test")

let semaphore1 = DispatchSemaphore(value: 0)
lifecycle.shutdown { errors in
XCTAssertNil(errors)
semaphore1.signal()
}
lifecycle.wait()
XCTAssertEqual(.success, semaphore1.wait(timeout: .now() + 1))

let semaphore2 = DispatchSemaphore(value: 0)
lifecycle.shutdown { errors in
XCTAssertNil(errors)
semaphore2.signal()
}
lifecycle.wait()
XCTAssertEqual(.success, semaphore2.wait(timeout: .now() + 1))
}

func testIfNotStartedWhenIdle() {
var shutdown1Called = false
var shutdown2Called = false
var shutdown3Called = false

let lifecycle = ComponentLifecycle(label: "test")

lifecycle.register(label: "shutdown1",
start: .sync {},
shutdown: .sync { shutdown1Called = true },
shutdownIfNotStarted: true)

lifecycle.register(label: "shutdown2", start: .none, shutdown: .sync {
shutdown2Called = true
})

lifecycle.registerShutdown(label: "shutdown3", .sync {
shutdown3Called = true
})

lifecycle.shutdown()
lifecycle.wait()

XCTAssertTrue(shutdown1Called, "expected shutdown to be called")
XCTAssertTrue(shutdown2Called, "expected shutdown to be called")
XCTAssertTrue(shutdown3Called, "expected shutdown to be called")
}

func testShutdownWhenShutdown() {
Expand Down