Library / SDK
swift-server/swift-service-lifecycle avatar
swift-server/swift-service-lifecycle

Swift Service Lifecycle: ordered startup and shutdown for Swift servers

Cleanly startup and shutdown server application, freeing resources in order before exiting.

537 stars52 forksSwiftApache-2.0

At a glance

What is it?
Swift Service Lifecycle is a small Swift package that turns the startup and shutdown sequence of a server application into a first class, testable concern, with a Service protocol, a ServiceGroup actor and signal-based graceful shutdown. It suits anyone running a long-lived Swift process on Linux or macOS, and it does not replace the framework you already use.
Who is it for?
Adopt Swift Service Lifecycle when you run a long-lived Swift process whose startup order or shutdown order matters, because encoding that sequence as a list of services in a group makes it something you can reason about and test, rather than a chain of calls in a main function. Do not adopt it if your application has a single task and no resources to release, since the group exists to coordinate several.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: shutdown logic that is easy to get wrong

Swift Service Lifecycle provides a basic mechanism to cleanly start up and shut down an application, freeing resources in order before exiting.

The README explains the motivation in a way that is worth repeating because it names the real difficulty. Every application has some startup and shutdown workflow-like logic which is often sensitive to failure and hard to get right. The library encodes this common need in a safe and reusable way that is non-framework specific, and it is designed to be integrated with any server framework or directly in an application.

That last point is the design decision that makes the package worth looking at. Server frameworks in the Swift ecosystem each have their own opinions about lifecycle, and a project that changes frameworks, or that has one service in a server and another in a command line tool, would otherwise end up writing the same ordering logic twice. The library also integrates natively with Structured Concurrency, which matters because Swift's concurrency model is now the default way to write server code.

The intended user is stated in the getting started section: someone with a server-side Swift application or a cross-platform application, for example on Linux or macOS, who wants to manage its startup and shutdown lifecycle.

The project is part of the swift-server organisation, is licensed Apache-2.0 with a `LICENSE.txt` and a `NOTICE.txt` in the tree, and the README says it is a community-driven open-source project actively seeking contributions, be it code, documentation or ideas.

Two building blocks: a protocol and an actor

ServiceLifecycle consists of two main building blocks, the `Service` protocol and the `ServiceGroup` actor, and the division of labour between them is the whole design.

The protocol is deliberately minimal. A service is modelled as a long-running unit of work, and the protocol only requires the implementation of a single `func run() async throws` method. A service can be implemented by a struct, a class or an actor, and the README's example uses a struct.

The `ServiceGroup` actor is the orchestrator. The group will spawn a child task for each service and call the respective `run` method in the child task. It also sets up signal listeners for the configured signals and triggers a graceful shutdown on each service when one of those signals arrives.

So the mechanics are: your long-running work is expressed as a `run` method that returns when the work is done, the group runs each of those concurrently in a child task, and the group owns the signal handling so that a termination request turns into an orderly shutdown rather than a killed process.

That last point is the practical payoff. A server that receives `SIGTERM` from a process supervisor and dies immediately can leave connections half-closed, in-flight requests dropped, and temporary state inconsistent. A graceful shutdown gives each service the chance to finish what it is doing and release what it holds. The library also provides a Signal-based shutdown hook to shut down on signals like `TERM` or `INT`, so the same mechanism covers a supervisor's termination request and a Ctrl-C in a terminal.

The `ServiceGroup` being an actor rather than a class is not decoration in a Swift package whose selling point is native integration with Structured Concurrency. Isolation of the group's state follows from the concurrency model rather than being bolted on.

Adding the dependency and the shape of a Package.swift

The package is a Swift Package Manager dependency, and the README shows the two declarations you need. In `Package.swift`, in the dependencies list:

swift
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"),

and in the target's dependencies, the product:

swift
.product(name: "ServiceLifecycle", package: "swift-service-lifecycle")

The README then gives a complete `Package.swift` as an example, which is the clearest way to see how the two declarations fit together:

swift
let package = Package(
    name: "my-application",
    dependencies: [
        .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.3.0"),
    ],
    targets: [
        .target(name: "MyApplication", dependencies: [
            .product(name: "ServiceLifecycle", package: "swift-service-lifecycle")
        ]),
        .testTarget(name: "MyApplicationTests", dependencies: [
            .target(name: "MyApplication"),
        ]),
    ]
)

Note that the two version floors in the documentation differ. The dependency line shown on its own says `from: "2.0.0"`, and the full example says `from: "2.3.0"`. Both are lower bounds rather than pins, so in practice your resolver will pick a recent version, but the example is the one to copy since it is what the maintainers showed as a working configuration, and the tools version in that file is 6.2.

The module you import in your own source is `ServiceLifecycle`, and the library is small enough that the API documentation linked from the README is the fastest route to the details.

A complete example: two services in one group

The README's usage example is short enough to read in full, and it shows the shape of a typical application.

swift
import ServiceLifecycle
import Logging

// A service can be implemented by a struct, class or actor. For this example we are using a struct.
struct FooService: Service {
    func run() async throws {
        print("FooService starting")
        try await Task.sleep(for: .seconds(10))
        print("FooService done")
    }
}

@main
struct Application {
    static let logger = Logger(label: "Application")

The `FooService` is the smallest useful service: a `run` method that prints, sleeps for ten seconds with `Task.sleep`, and prints again. Because the whole body is `async throws`, a real service would await its work rather than block a thread.

The application type is where the group is configured. The `Application` struct carries a `Logger` from the swift-log package, and its `main` method creates the services, builds a `ServiceGroup` with a list of services, a list of graceful shutdown signals and the logger, then awaits `serviceGroup.run()`.

The three arguments are the entire configuration surface. The list of services is what the group will run, the `gracefulShutdownSignals` set determines which signals trigger the shutdown, and the logger is used to report what the group is doing. The example uses `.sigterm`, which is the signal a process supervisor or an init system sends when it wants the process to stop.

Once `serviceGroup.run()` is awaited, the group owns the process. The application does not return from `main` until the group decides the run is over, which happens when the services have finished or a shutdown signal has been received.

Where Swift Service Lifecycle is the wrong tool

The first wrong-tool case is an application with one task. The whole value of a `ServiceGroup` comes from coordinating several services and ordering their shutdown. If your process starts one thing and exits when it is done, the group adds an actor, a child task and a signal handler to solve a problem you do not have.

The second case is a framework that already manages the lifecycle. If your HTTP server or job runner already owns startup and shutdown, including a second lifecycle system means two things reacting to the same signal. The README positions the package as framework agnostic, which is what makes it usable alongside an existing framework, but it does not mean it should be layered on top of a framework's own supervisor without checking what that supervisor does with a `SIGTERM`.

The third is a long-lived process that is not a server or a command line tool, for example a library used inside someone else's application. Lifecycle ownership belongs to whoever owns the process, and a library that starts services on your behalf is a library that makes composition difficult.

The fourth is Swift code that does not use Structured Concurrency. The library is built around `async throws` run methods and child tasks, and integrating it into a callback or delegate based codebase means wrapping that code in tasks yourself, at which point the library's contribution is thin.

The alternative worth naming is writing the shutdown sequence by hand, which for a small application is a few lines: install a `SIGTERM` handler, cancel a task group, await the cancellation, exit. The difference in approach is that hand-rolled code puts the ordering in your own `main` function, where it is visible but untested and unshared, while this library puts it in a declarative list of services that the group runs and stops, and which can be exercised in a test with the signal path replaced by an ordinary cancellation.

Maintenance, versioning and the licence

The package is actively worked on. Version 2.13.0 was released on 2026-10-14, 2.12.1 on 2026-10-05 and 2.12.0 on 2026-08-18, and the last push to the default branch was on 2026-10-19. The repository is not archived, and the README describes the project as community-driven and actively seeking contributions, which is corroborated by the `CONTRIBUTING.md` file in the tree.

The version numbering is the thing to note. The package is at 2.x, which under Swift Package Manager rules means the major version is part of the dependency declaration, and the README's own example declares `from: "2.3.0"` rather than a `2.0.0` or `1.0.0` floor. Two releases in October 2026 and one in August suggests a fairly regular cadence, so a project that depends on it should follow the releases rather than pinning a version once and forgetting about it.

The licence is Apache-2.0, with `LICENSE.txt` and `NOTICE.txt` in the tree. That is a permissive licence with an explicit patent grant and a notice requirement, and for a server-side library that other teams will vendor, it is a straightforward choice. The `NOTICE.txt` file is the attribution notice that goes with it. This is a description of the terms as published, not legal advice.

The security process is borrowed from the swift-log project, and the README points at that repository's security policy for how vulnerabilities are handled, so the reporting path is documented but lives in another repository.

For a full picture of the API, the README points to the documentation published through Swift Package Index, and the repository itself contains `Sources/` and `Tests/` alongside the Swift tooling configuration files such as `.swift-format` and `.spi.yml`.

Editorial conclusion

Adopt Swift Service Lifecycle when you run a long-lived Swift process whose startup order or shutdown order matters, because encoding that sequence as a list of services in a group makes it something you can reason about and test, rather than a chain of calls in a main function. Do not adopt it if your application has a single task and no resources to release, since the group exists to coordinate several. Before you depend on it, read the API docs linked from the README, check the version floor you are pinning, because the current release is 2.13.0 from 2026-10-14 and the package is pre-1.0 style versioned, and confirm that the signals you need are available on your platform, since the mechanism is a Signal-based shutdown hook for TERM and INT.

Frequently asked questions

What is Swift Service Lifecycle and what problem does it solve?

It provides a basic mechanism to cleanly start up and shut down an application, freeing resources in order before exiting, plus a Signal-based shutdown hook for signals like TERM or INT. The README describes it as a safe, reusable, non-framework-specific encoding of the startup and shutdown workflow logic that every application tends to need.

How do I add Swift Service Lifecycle to my Package.swift?

Declare the package in the dependencies list with `.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.3.0")`, then add the product to your target with `.product(name: "ServiceLifecycle", package: "swift-service-lifecycle")`. The README's full example shows both in one `Package.swift` file.

What do the Service protocol and the ServiceGroup actor do?

The `Service` protocol requires only a single `func run() async throws` method, so a service can be a struct, class or actor. The `ServiceGroup` actor spawns a child task for each service and calls its `run` method in that task, sets up signal listeners for the configured signals, and triggers a graceful shutdown on each service when one arrives.

How does graceful shutdown work in Swift Service Lifecycle?

You pass a set of signals to the `ServiceGroup`, for example `gracefulShutdownSignals: [.sigterm]`, and the group installs listeners for them. When a matching signal arrives, the group triggers a graceful shutdown on each service, so a process supervisor's termination request results in an orderly release of resources rather than an abrupt exit.

What licence is Swift Service Lifecycle released under?

The package is licensed Apache-2.0, and the repository contains both a `LICENSE.txt` and a `NOTICE.txt`. The security disclosure process follows the swift-log project's security policy, which the README links from the swift-log repository.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. swift-server/swift-service-lifecycle on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/swift-server-swift-service-lifecycle.svg)](https://hysenlabs.com/projects/swift-server-swift-service-lifecycle)

Was this useful?