Skip to content

Commit c825a58

Browse files
authored
shutdown any task that have shutdownIfNotStarted set to true when shutting down a never-started-lifecycle (#109)
motivation: more reliable shutdown in edge cases where lifecycle never gets started changes: when shutting down an idle lifecycle, try to shutdown any tasks that have shutdownIfNotStarted set to true
1 parent f6c02f7 commit c825a58

File tree

3 files changed

+68
-8
lines changed

3 files changed

+68
-8
lines changed

Sources/Lifecycle/Lifecycle.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,11 +507,17 @@ public class ComponentLifecycle: LifecycleTask {
507507

508508
self.stateLock.lock()
509509
switch self.state {
510-
case .idle:
510+
case .idle(let tasks) where tasks.isEmpty:
511511
self.state = .shutdown(nil)
512512
self.stateLock.unlock()
513513
defer { self.shutdownGroup.leave() }
514514
callback(nil)
515+
case .idle(let tasks):
516+
self.stateLock.unlock()
517+
// attempt to shutdown any registered tasks
518+
let stoppable = tasks.filter { $0.shutdownIfNotStarted }
519+
setupShutdownListener(.global())
520+
self._shutdown(on: .global(), tasks: stoppable, callback: self.shutdownGroup.leave)
515521
case .shutdown:
516522
self.stateLock.unlock()
517523
self.logger.warning("already shutdown")

Tests/LifecycleTests/ComponentLifecycleTests+XCTest.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ extension ComponentLifecycleTests {
3030
("testUserDefinedCallbackQueue", testUserDefinedCallbackQueue),
3131
("testShutdownWhileStarting", testShutdownWhileStarting),
3232
("testShutdownWhenIdle", testShutdownWhenIdle),
33+
("testShutdownWhenIdleAndNoItems", testShutdownWhenIdleAndNoItems),
34+
("testIfNotStartedWhenIdle", testIfNotStartedWhenIdle),
3335
("testShutdownWhenShutdown", testShutdownWhenShutdown),
3436
("testShutdownDuringHangingStart", testShutdownDuringHangingStart),
3537
("testShutdownErrors", testShutdownErrors),

Tests/LifecycleTests/ComponentLifecycleTests.swift

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,23 +159,75 @@ final class ComponentLifecycleTests: XCTestCase {
159159

160160
func testShutdownWhenIdle() {
161161
let lifecycle = ComponentLifecycle(label: "test")
162-
lifecycle.register(GoodItem())
163162

164-
let sempahpore1 = DispatchSemaphore(value: 0)
163+
let item = GoodItem()
164+
lifecycle.register(item)
165+
166+
let semaphore1 = DispatchSemaphore(value: 0)
165167
lifecycle.shutdown { errors in
166168
XCTAssertNil(errors)
167-
sempahpore1.signal()
169+
semaphore1.signal()
168170
}
169171
lifecycle.wait()
170-
XCTAssertEqual(.success, sempahpore1.wait(timeout: .now() + 1))
172+
XCTAssertEqual(.success, semaphore1.wait(timeout: .now() + 1))
171173

172-
let sempahpore2 = DispatchSemaphore(value: 0)
174+
let semaphore2 = DispatchSemaphore(value: 0)
173175
lifecycle.shutdown { errors in
174176
XCTAssertNil(errors)
175-
sempahpore2.signal()
177+
semaphore2.signal()
176178
}
177179
lifecycle.wait()
178-
XCTAssertEqual(.success, sempahpore2.wait(timeout: .now() + 1))
180+
XCTAssertEqual(.success, semaphore2.wait(timeout: .now() + 1))
181+
182+
XCTAssertEqual(item.state, .idle, "expected item to be idle")
183+
}
184+
185+
func testShutdownWhenIdleAndNoItems() {
186+
let lifecycle = ComponentLifecycle(label: "test")
187+
188+
let semaphore1 = DispatchSemaphore(value: 0)
189+
lifecycle.shutdown { errors in
190+
XCTAssertNil(errors)
191+
semaphore1.signal()
192+
}
193+
lifecycle.wait()
194+
XCTAssertEqual(.success, semaphore1.wait(timeout: .now() + 1))
195+
196+
let semaphore2 = DispatchSemaphore(value: 0)
197+
lifecycle.shutdown { errors in
198+
XCTAssertNil(errors)
199+
semaphore2.signal()
200+
}
201+
lifecycle.wait()
202+
XCTAssertEqual(.success, semaphore2.wait(timeout: .now() + 1))
203+
}
204+
205+
func testIfNotStartedWhenIdle() {
206+
var shutdown1Called = false
207+
var shutdown2Called = false
208+
var shutdown3Called = false
209+
210+
let lifecycle = ComponentLifecycle(label: "test")
211+
212+
lifecycle.register(label: "shutdown1",
213+
start: .sync {},
214+
shutdown: .sync { shutdown1Called = true },
215+
shutdownIfNotStarted: true)
216+
217+
lifecycle.register(label: "shutdown2", start: .none, shutdown: .sync {
218+
shutdown2Called = true
219+
})
220+
221+
lifecycle.registerShutdown(label: "shutdown3", .sync {
222+
shutdown3Called = true
223+
})
224+
225+
lifecycle.shutdown()
226+
lifecycle.wait()
227+
228+
XCTAssertTrue(shutdown1Called, "expected shutdown to be called")
229+
XCTAssertTrue(shutdown2Called, "expected shutdown to be called")
230+
XCTAssertTrue(shutdown3Called, "expected shutdown to be called")
179231
}
180232

181233
func testShutdownWhenShutdown() {

0 commit comments

Comments
 (0)