You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+90-29
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@ It also provides a `Signal`-based shutdown hook, to shutdown on signals like `TE
6
6
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.
7
7
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.
8
8
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.
10
10
11
11
## Getting started
12
12
@@ -17,23 +17,23 @@ If you have a server-side Swift application or a cross-platform (e.g. Linux, mac
17
17
To add a dependency on the package, declare it in your `Package.swift`:
// register a resource that should be shut down when the application exits.
39
39
//
@@ -45,14 +45,17 @@ lifecycle.registerShutdown(
45
45
eventLoopGroup.syncShutdownGracefully
46
46
)
47
47
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.
49
50
//
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)
56
59
)
57
60
58
61
// start the application
@@ -79,11 +82,18 @@ lifecycle.wait()
79
82
80
83
## Detailed design
81
84
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.
83
93
84
94
### Registering items
85
95
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:
87
97
88
98
You can register simple blocking throwing handlers using:
`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
+
136
156
### Starting the lifecycle
137
157
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.
139
160
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.
141
163
142
164
```swift
143
165
lifecycle.start() { error in
@@ -149,17 +171,9 @@ lifecycle.start() { error in
149
171
}
150
172
```
151
173
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
-
160
174
### Shutdown
161
175
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`.
163
177
164
178
```swift
165
179
lifecycle.start() { error in
@@ -174,7 +188,7 @@ If you are not interested in handling start completion, there is also a convenie
174
188
lifecycle.startAndWait() // <-- blocks the thread
175
189
```
176
190
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.
178
192
The shutdown sequence is typically triggered by the `shutdownSignal` defined in the configuration. By default, `INT` and `TERM` are trapped.
179
193
180
194
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.
189
203
190
204
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.
191
205
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
+
structSubSystem {
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
+
structSubSubSystem {
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
+
```
193
245
194
246
### Compatibility with SwiftNIO Futures
195
247
196
248
[SwiftNIO](https://github.com/apple/swift-nio) is a popular networking library that among other things provides Future abstraction named `EventLoopFuture`.
197
249
198
250
SwiftServiceBootstrap comes with a compatibility module designed to make managing SwiftNIO based resources easy.
199
251
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:
201
253
202
254
```swift
203
255
let foo =...
@@ -208,7 +260,16 @@ lifecycle.register(
208
260
)
209
261
```
210
262
211
-
-------
263
+
or, just shutdown:
212
264
265
+
```swift
266
+
let foo =...
267
+
lifecycle.registerShutdown(
268
+
name: "foo",
269
+
.eventLoopFuture(foo.shutdown)
270
+
)
271
+
```
272
+
273
+
-------
213
274
214
275
Do not hesitate to get in touch as well, over on https://forums.swift.org/c/server.
0 commit comments