Skip to content

Commit 0e2d952

Browse files
authored
update readme to reflect updated API (#37)
motivation: keep readme in sync with code changes: * update readme to latest API + add nesting section * update leftover renaming issues
1 parent 77c2610 commit 0e2d952

10 files changed

+148
-53
lines changed

CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,4 @@ For this reason, whenever you add new tests **you have to run a script** that ge
7070

7171
## How to contribute your work
7272

73-
Please open a pull request at https://github.com/swift-server/swift-service-launcher. Make sure the CI passes, and then wait for code review.
73+
Please open a pull request at https://github.com/swift-server/swift-service-bootstrap. Make sure the CI passes, and then wait for code review.

README.md

+90-29
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ It also provides a `Signal`-based shutdown hook, to shutdown on signals like `TE
66
SwiftServiceBootstrap was designed with the idea that every application has some startup and shutdown workflow-like-logic which is often sensitive to failure and hard to get right.
77
The library codes this common need in a safe and reusable way that is non-framework specific, and designed to be integrated with any server framework or directly in an application.
88

9-
This is the beginning of a community-driven open-source project actively seeking contributions, be it code, documentation, or ideas. What SwiftServiceBootstrap provides today is covered in the [API docs](https://swift-server.github.io/swift-service-launcher/), but it will continue to evolve with community input.
9+
This is the beginning of a community-driven open-source project actively seeking contributions, be it code, documentation, or ideas. What SwiftServiceBootstrap provides today is covered in the [API docs](https://swift-server.github.io/swift-service-bootstrap/), but it will continue to evolve with community input.
1010

1111
## Getting started
1212

@@ -17,23 +17,23 @@ If you have a server-side Swift application or a cross-platform (e.g. Linux, mac
1717
To add a dependency on the package, declare it in your `Package.swift`:
1818

1919
```swift
20-
.package(url: "https://github.com/swift-server/swift-service-launcher.git", from: "1.0.0"),
20+
.package(url: "https://github.com/swift-server/swift-service-bootstrap.git", from: "1.0.0-alpha.2"),
2121
```
2222

2323
and to your application target, add "SwiftServiceBootstrap" to your dependencies:
2424

2525
```swift
26-
.target(name: "BestExampleApp", dependencies: ["SwiftServiceBootstrap"]),
26+
.target(name: "MyApplication", dependencies: ["Lifecycle"]),
2727
```
2828

2929
### Defining the lifecycle
3030

3131
```swift
3232
// import the package
33-
import ServiceLauncher
33+
import Lifecycle
3434

3535
// initialize the lifecycle container
36-
var lifecycle = Lifecycle()
36+
let lifecycle = ServiceLifecycle()
3737

3838
// register a resource that should be shut down when the application exits.
3939
//
@@ -45,14 +45,17 @@ lifecycle.registerShutdown(
4545
eventLoopGroup.syncShutdownGracefully
4646
)
4747

48-
// register another resource that should be shut down when the application exits.
48+
// register another resource that should be started when the application starts
49+
// and shut down when the application exits.
4950
//
50-
// in this case, we are registering an `HTTPClient`
51-
// and passing its `syncShutdown` function to be called on shutdown
52-
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup))
53-
lifecycle.registerShutdown(
54-
name: "httpClient",
55-
httpClient.syncShutdown
51+
// in this case, we are registering a contrived `DatabaseMigrator`
52+
// and passing its `migrate` function to be called on startup
53+
// and `shutdown` function to be called on shutdown
54+
let migrator = DatabaseMigrator()
55+
lifecycle.register(
56+
name: "migrator",
57+
start: .async(migrator.migrate),
58+
shutdown: .async(migrator.shutdown)
5659
)
5760

5861
// start the application
@@ -79,11 +82,18 @@ lifecycle.wait()
7982

8083
## Detailed design
8184

82-
The main type in the library is `Lifecycle` which manages a state machine representing the application's startup and shutdown logic.
85+
The main types in the library are `ServiceLifecycle` and `ComponentLifecycle`.
86+
87+
`ServiceLifecycle` is the most commonly used type.
88+
It is designed to manage the top level Application (Service) lifecycle,
89+
and in addition to managing the startup and shutdown flows it can also set up `Signal` trap for shutdown and install backtraces.
90+
91+
`ComponentLifecycle` manages a state machine representing the startup and shutdown logic flow.
92+
In larger Applications (Services) `ComponentLifecycle` can be used to manage the lifecycle of subsystems, such that `ServiceLifecycle` can start and shutdown `ComponentLifecycle`s.
8393

8494
### Registering items
8595

86-
`Lifecycle` is a container for `LifecycleItem`s which need to be registered via one of the following variants:
96+
`ServiceLifecycle` and `ComponentLifecycle` are containers for `Lifecycle.Task`s which need to be registered via one of the following variants:
8797

8898
You can register simple blocking throwing handlers using:
8999

@@ -133,11 +143,23 @@ func register(_ items: [LifecycleItem])
133143
internal func register(_ items: LifecycleItem...)
134144
```
135145

146+
### Configuration
147+
148+
`ServiceLifecycle` constructor takes optional `Lifecycle.Configuration` to further refine the `ServiceLifecycle` behavior:
149+
150+
* `callbackQueue`: Defines the `DispatchQueue` on which startup and shutdown handlers are executed. By default, `DispatchQueue.global` is used.
151+
152+
* `shutdownSignal`: Defines what, if any, signals to trap for invoking shutdown. By default, `INT` and `TERM` are trapped.
153+
154+
* `installBacktrace`: Defines if to install a crash signal trap that prints backtraces. This is especially useful for application running on Linux since Swift does not provide backtraces on Linux out of the box. This functionality is provided via the [Swift Backtrace](https://github.com/swift-server/swift-backtrace) library.
155+
136156
### Starting the lifecycle
137157

138-
Use `Lifecycle::start` function to start the application. Start handlers passed using the `register` function will be called in the order the items were registered in.
158+
Use `start` function to start the application.
159+
Start handlers passed using the `register` function will be called in the order the items were registered in.
139160

140-
`Lifecycle::start` is an asynchronous operation. If a startup error occurred, it will be logged and the startup sequence will halt on the first error, and bubble it up to the provided completion handler.
161+
`start` is an asynchronous operation.
162+
If a startup error occurred, it will be logged and the startup sequence will halt on the first error, and bubble it up to the provided completion handler.
141163

142164
```swift
143165
lifecycle.start() { error in
@@ -149,17 +171,9 @@ lifecycle.start() { error in
149171
}
150172
```
151173

152-
`Lifecycle::start` takes optional `Lifecycle.Configuration` to further refine the `Lifecycle` behavior:
153-
154-
* `callbackQueue`: Defines the `DispatchQueue` on which startup and shutdown handlers are executed. By default, `DispatchQueue.global` is used.
155-
156-
* `shutdownSignal`: Defines what, if any, signals to trap for invoking shutdown. By default, `INT` and `TERM` are trapped.
157-
158-
* `installBacktrace`: Defines if to install a crash signal trap that prints backtraces. This is especially useful for application running on Linux since Swift does not provide backtraces on Linux out of the box. This functionality is provided via the [Swift Backtrace](https://github.com/swift-server/swift-backtrace) library.
159-
160174
### Shutdown
161175

162-
Typical use of the library is to call on `Lifecycle::wait` after calling `Lifecycle::start`.
176+
Typical use of the library is to call on `wait` after calling `start`.
163177

164178
```swift
165179
lifecycle.start() { error in
@@ -174,7 +188,7 @@ If you are not interested in handling start completion, there is also a convenie
174188
lifecycle.startAndWait() // <-- blocks the thread
175189
```
176190

177-
`Lifecycle::wait` and `Lifecycle::startAndWait` are blocking operations that wait for the lifecycle library to finish its shutdown sequence.
191+
Both `wait` and `startAndWait` are blocking operations that wait for the lifecycle library to finish the shutdown sequence.
178192
The shutdown sequence is typically triggered by the `shutdownSignal` defined in the configuration. By default, `INT` and `TERM` are trapped.
179193

180194
During shutdown, the shutdown handlers passed using the `register` or `registerShutdown` functions are called in the reverse order of the registration. E.g.
@@ -189,15 +203,53 @@ startup order will be 1, 2, 3 and shutdown order will be 3, 2, 1.
189203

190204
If a shutdown error occurred, it will be logged and the shutdown sequence will *continue* to the next item, and attempt to shut it down until all registered items that have been started are shut down.
191205

192-
In more complex cases, when signal trapping based shutdown is not appropriate, you may pass `nil` as the `shutdownSignal` configuration, and call `Lifecycle::shutdown` manually when appropriate. This is a rarely used pressure valve. `Lifecycle::shutdown` is an asynchronous operation. Errors will be logged and bubble it up to the provided completion handler.
206+
In more complex cases, when signal trapping based shutdown is not appropriate, you may pass `nil` as the `shutdownSignal` configuration, and call `shutdown` manually when appropriate. This is designed to be a rarely used pressure valve.
207+
208+
`shutdown` is an asynchronous operation. Errors will be logged and bubble it up to the provided completion handler.
209+
210+
### Complex Systems and Nesting of Subsystems
211+
212+
In larger Applications (Services) `ComponentLifecycle` can be used to manage the lifecycle of subsystems, such that `ServiceLifecycle` can start and shutdown `ComponentLifecycle`s.
213+
214+
In fact, since `ComponentLifecycle` conforms to `Lifecycle.Task`,
215+
it can start and stop other `ComponentLifecycles`, forming a tree. E.g.:
216+
217+
```swift
218+
struct SubSystem {
219+
let lifecycle = ComponentLifecycle(label: "SubSystem")
220+
let subsystem: SubSubSystem
221+
222+
init() {
223+
self.subsystem = SubSubSystem()
224+
self.lifecycle.register(self.subsystem.lifecycle)
225+
}
226+
227+
struct SubSubSystem {
228+
let lifecycle = ComponentLifecycle(label: "SubSubSystem")
229+
230+
init() {
231+
self.lifecycle.register(...)
232+
}
233+
}
234+
}
235+
236+
let lifecycle = ServiceLifecycle()
237+
let subsystem = SubSystem()
238+
lifecycle.register(subsystem.lifecycle)
239+
240+
lifecycle.start { error in
241+
...
242+
}
243+
lifecycle.wait()
244+
```
193245

194246
### Compatibility with SwiftNIO Futures
195247

196248
[SwiftNIO](https://github.com/apple/swift-nio) is a popular networking library that among other things provides Future abstraction named `EventLoopFuture`.
197249

198250
SwiftServiceBootstrap comes with a compatibility module designed to make managing SwiftNIO based resources easy.
199251

200-
Once you import `ServiceLauncherNIOCompat` module, `Lifecycle.Handler` gains a static helpers named `eventLoopFuture` designed to help simplify the registration call to:
252+
Once you import `LifecycleNIOCompat` module, `Lifecycle.Handler` gains a static helpers named `eventLoopFuture` designed to help simplify the registration call to:
201253

202254
```swift
203255
let foo = ...
@@ -208,7 +260,16 @@ lifecycle.register(
208260
)
209261
```
210262

211-
-------
263+
or, just shutdown:
212264

265+
```swift
266+
let foo = ...
267+
lifecycle.registerShutdown(
268+
name: "foo",
269+
.eventLoopFuture(foo.shutdown)
270+
)
271+
```
272+
273+
-------
213274

214275
Do not hesitate to get in touch as well, over on https://forums.swift.org/c/server.

Tests/LifecycleTests/ServiceLifecycleTests+XCTest.swift

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ extension ServiceLifecycleTests {
3030
("testStartAndWait", testStartAndWait),
3131
("testBadStartAndWait", testBadStartAndWait),
3232
("testNesting", testNesting),
33+
("testNesting2", testNesting2),
3334
]
3435
}
3536
}

Tests/LifecycleTests/ServiceLifecycleTests.swift

+34-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import XCTest
1919
final class ServiceLifecycleTests: XCTestCase {
2020
func testStartThenShutdown() {
2121
let items = (5 ... Int.random(in: 10 ... 20)).map { _ in GoodItem() }
22-
let lifecycle = ComponentLifecycle(label: "test")
22+
let lifecycle = ServiceLifecycle(configuration: .init(shutdownSignal: nil))
2323
lifecycle.register(items)
2424
lifecycle.start { startError in
2525
XCTAssertNil(startError, "not expecting error")
@@ -138,4 +138,37 @@ final class ServiceLifecycleTests: XCTestCase {
138138
items2.forEach { XCTAssertEqual($0.state, .shutdown, "expected item to be shutdown, but \($0.state)") }
139139
items3.forEach { XCTAssertEqual($0.state, .shutdown, "expected item to be shutdown, but \($0.state)") }
140140
}
141+
142+
func testNesting2() {
143+
struct SubSystem {
144+
let lifecycle = ComponentLifecycle(label: "SubSystem")
145+
let subsystem: SubSubSystem
146+
147+
init() {
148+
self.subsystem = SubSubSystem()
149+
self.lifecycle.register(self.subsystem.lifecycle)
150+
}
151+
152+
struct SubSubSystem {
153+
let lifecycle = ComponentLifecycle(label: "SubSubSystem")
154+
let items = (0 ... Int.random(in: 10 ... 20)).map { _ in GoodItem() }
155+
156+
init() {
157+
self.lifecycle.register(self.items)
158+
}
159+
}
160+
}
161+
162+
let lifecycle = ServiceLifecycle()
163+
let subsystem = SubSystem()
164+
lifecycle.register(subsystem.lifecycle)
165+
166+
lifecycle.start { error in
167+
XCTAssertNil(error, "not expecting error")
168+
subsystem.subsystem.items.forEach { XCTAssertEqual($0.state, .started, "expected item to be started, but \($0.state)") }
169+
lifecycle.shutdown()
170+
}
171+
lifecycle.wait()
172+
subsystem.subsystem.items.forEach { XCTAssertEqual($0.state, .shutdown, "expected item to be shutdown, but \($0.state)") }
173+
}
141174
}

docker/docker-compose.1604.51.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ version: "3"
33
services:
44

55
runtime-setup:
6-
image: swift-service-launcher:16.04-5.1
6+
image: swift-service-bootstrap:16.04-5.1
77
build:
88
args:
99
ubuntu_version: "xenial"
1010
swift_version: "5.1"
1111

1212
test:
13-
image: swift-service-launcher:16.04-5.1
13+
image: swift-service-bootstrap:16.04-5.1
1414
environment:
1515
- SANITIZER_ARG=--sanitize=thread
1616
- SKIP_SIGNAL_TEST=true
1717

1818
shell:
19-
image: swift-service-launcher:16.04-5.1
19+
image: swift-service-bootstrap:16.04-5.1

docker/docker-compose.1804.50.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ version: "3"
33
services:
44

55
runtime-setup:
6-
image: swift-service-launcher:18.04-5.0
6+
image: swift-service-bootstrap:18.04-5.0
77
build:
88
args:
99
ubuntu_version: "bionic"
1010
swift_version: "5.0"
1111

1212
test:
13-
image: swift-service-launcher:18.04-5.0
13+
image: swift-service-bootstrap:18.04-5.0
1414

1515
shell:
16-
image: swift-service-launcher:18.04-5.0
16+
image: swift-service-bootstrap:18.04-5.0

docker/docker-compose.1804.52.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ version: "3"
33
services:
44

55
runtime-setup:
6-
image: swift-service-launcher:18.04-5.2
6+
image: swift-service-bootstrap:18.04-5.2
77
build:
88
args:
99
ubuntu_version: "bionic"
1010
swift_version: "5.2"
1111

1212
test:
13-
image: swift-service-launcher:18.04-5.2
13+
image: swift-service-bootstrap:18.04-5.2
1414

1515
shell:
16-
image: swift-service-launcher:18.04-5.2
16+
image: swift-service-bootstrap:18.04-5.2

docker/docker-compose.1804.53.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ version: "3"
33
services:
44

55
runtime-setup:
6-
image: swift-service-launcher:18.04-5.3
6+
image: swift-service-bootstrap:18.04-5.3
77
build:
88
args:
99
base_image: "swiftlang/swift:nightly-bionic"
1010

1111
test:
12-
image: swift-service-launcher:18.04-5.3
12+
image: swift-service-bootstrap:18.04-5.3
1313

1414
shell:
15-
image: swift-service-launcher:18.04-5.3
15+
image: swift-service-bootstrap:18.04-5.3

docker/docker-compose.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ version: "3"
66
services:
77

88
runtime-setup:
9-
image: swift-service-launcher:default
9+
image: swift-service-bootstrap:default
1010
build:
1111
context: .
1212
dockerfile: Dockerfile
1313

1414
common: &common
15-
image: swift-service-launcher:default
15+
image: swift-service-bootstrap:default
1616
depends_on: [runtime-setup]
1717
volumes:
1818
- ~/.ssh:/root/.ssh

0 commit comments

Comments
 (0)