Skip to content

Commit c8e9952

Browse files
authored
setup shutdown hooks on ServiceLifecycle::startAndWait (#69)
motivation: fix startAndWait to work as advertised changes: * extract shutdown setup to a func and call it from both start and startAndWait * add test * fix api doc * fixi jazzy xenial issue (fixes ci)
1 parent 2433054 commit c8e9952

File tree

7 files changed

+73
-15
lines changed

7 files changed

+73
-15
lines changed

Sources/Lifecycle/Lifecycle.swift

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,25 +105,14 @@ public struct ServiceLifecycle {
105105
/// - parameters:
106106
/// - callback: The handler which is called after the start operation completes. The parameter will be `nil` on success and contain the `Error` otherwise.
107107
public func start(_ callback: @escaping (Error?) -> Void) {
108-
self.configuration.shutdownSignal?.forEach { signal in
109-
self.lifecycle.log("setting up shutdown hook on \(signal)")
110-
let signalSource = ServiceLifecycle.trap(signal: signal, handler: { signal in
111-
self.lifecycle.log("intercepted signal: \(signal)")
112-
self.shutdown()
113-
})
114-
self.lifecycle.shutdownGroup.notify(queue: .global()) {
115-
signalSource.cancel()
116-
}
117-
}
108+
self.setupShutdownHook()
118109
self.lifecycle.start(on: self.configuration.callbackQueue, callback)
119110
}
120111

121112
/// Starts the provided `LifecycleItem` array and waits (blocking) until a shutdown `Signal` is captured or `shutdown` is called on another thread.
122113
/// Startup is performed in the order of items provided.
123-
///
124-
/// - parameters:
125-
/// - configuration: Defines lifecycle `Configuration`
126114
public func startAndWait() throws {
115+
self.setupShutdownHook()
127116
try self.lifecycle.startAndWait(on: self.configuration.callbackQueue)
128117
}
129118

@@ -140,6 +129,19 @@ public struct ServiceLifecycle {
140129
public func wait() {
141130
self.lifecycle.wait()
142131
}
132+
133+
private func setupShutdownHook() {
134+
self.configuration.shutdownSignal?.forEach { signal in
135+
self.lifecycle.log("setting up shutdown hook on \(signal)")
136+
let signalSource = ServiceLifecycle.trap(signal: signal, handler: { signal in
137+
self.lifecycle.log("intercepted signal: \(signal)")
138+
self.shutdown()
139+
})
140+
self.lifecycle.shutdownGroup.notify(queue: .global()) {
141+
signalSource.cancel()
142+
}
143+
}
144+
}
143145
}
144146

145147
extension ServiceLifecycle {

Tests/LifecycleTests/ServiceLifecycleTests+XCTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ extension ServiceLifecycleTests {
2828
("testStartThenShutdown", testStartThenShutdown),
2929
("testShutdownWithSignal", testShutdownWithSignal),
3030
("testStartAndWait", testStartAndWait),
31+
("testStartAndWaitShutdownWithSignal", testStartAndWaitShutdownWithSignal),
3132
("testBadStartAndWait", testBadStartAndWait),
3233
("testNesting", testNesting),
3334
("testNesting2", testNesting2),

Tests/LifecycleTests/ServiceLifecycleTests.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,55 @@ final class ServiceLifecycleTests: XCTestCase {
9191
XCTAssertEqual(item.state, .shutdown, "expected item to be shutdown")
9292
}
9393

94+
func testStartAndWaitShutdownWithSignal() {
95+
if ProcessInfo.processInfo.environment["SKIP_SIGNAL_TEST"].flatMap(Bool.init) ?? false {
96+
print("skipping testStartAndWaitShutdownWithSignal")
97+
return
98+
}
99+
100+
class Item: LifecycleTask {
101+
private let semaphore: DispatchSemaphore
102+
var state = State.idle
103+
104+
init(_ semaphore: DispatchSemaphore) {
105+
self.semaphore = semaphore
106+
}
107+
108+
var label: String {
109+
return "\(self)"
110+
}
111+
112+
func start(_ callback: (Error?) -> Void) {
113+
self.state = .started
114+
self.semaphore.signal()
115+
callback(nil)
116+
}
117+
118+
func shutdown(_ callback: (Error?) -> Void) {
119+
self.state = .shutdown
120+
callback(nil)
121+
}
122+
123+
enum State {
124+
case idle
125+
case started
126+
case shutdown
127+
}
128+
}
129+
130+
let signal = ServiceLifecycle.Signal.ALRM
131+
let lifecycle = ServiceLifecycle(configuration: .init(shutdownSignal: [signal]))
132+
let semaphore = DispatchSemaphore(value: 0)
133+
DispatchQueue(label: "test").asyncAfter(deadline: .now() + 0.1) {
134+
semaphore.wait()
135+
kill(getpid(), signal.rawValue)
136+
}
137+
let item = Item(semaphore)
138+
lifecycle.register(item)
139+
XCTAssertNoThrow(try lifecycle.startAndWait())
140+
XCTAssertEqual(item.state, .shutdown, "expected item to be shutdown")
141+
}
142+
94143
func testBadStartAndWait() {
95144
class BadItem: LifecycleTask {
96145
var label: String {

docker/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ RUN apt-get update && apt-get install -y lsof dnsutils netcat-openbsd net-tools
1818

1919
# ruby and jazzy for docs generation
2020
RUN apt-get update && apt-get install -y ruby ruby-dev libsqlite3-dev
21-
RUN gem install jazzy --no-ri --no-rdoc
21+
RUN if [ "${ubuntu_version}" != "xenial" ] ; then gem install jazzy --no-ri --no-rdoc ; fi
2222

2323
# tools
2424
RUN mkdir -p $HOME/.tools

docker/docker-compose.1804.50.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ services:
1111

1212
test:
1313
image: swift-service-lifecycle:18.04-5.0
14-
14+
environment:
15+
- SKIP_SIGNAL_TEST=true
16+
1517
shell:
1618
image: swift-service-lifecycle:18.04-5.0

docker/docker-compose.1804.52.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ services:
1111

1212
test:
1313
image: swift-service-lifecycle:18.04-5.2
14+
environment:
15+
- SKIP_SIGNAL_TEST=true
1416

1517
shell:
1618
image: swift-service-lifecycle:18.04-5.2

docker/docker-compose.1804.53.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ services:
1010

1111
test:
1212
image: swift-service-lifecycle:18.04-5.3
13+
environment:
14+
- SKIP_SIGNAL_TEST=true
1315

1416
shell:
1517
image: swift-service-lifecycle:18.04-5.3

0 commit comments

Comments
 (0)