Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ Furthermore, the group will setup signal listeners for the configured signals an
on each service.

```swift
import ServiceLifecycle
import Logging

actor FooService: Service {
func run() async throws {
print("FooService starting")
Expand All @@ -72,19 +75,21 @@ actor FooService: Service {

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

static func main() async throws {
let service1 = FooService()
let service2 = FooService()

let serviceGroup = ServiceGroup(
services: [service1, service2],
configuration: .init(gracefulShutdownSignals: [.sigterm]),
gracefulShutdownSignals: [.sigterm],
logger: logger
)

try await serviceGroup.run()
}
}

```

## Security
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,7 @@ struct Application {

let serviceGroup = ServiceGroup(
// We are encoding the dependency hierarchy here by listing the fooService first
configuration: .init(
services: [
.init(service: fooService),
.init(service: barService)
],
logger: logger
),
services: [fooService, barService]
)

try await serviceGroup.run()
Expand Down Expand Up @@ -148,11 +142,8 @@ struct Application {
})

let serviceGroup = ServiceGroup(
configuration: .init(
services: [.init(service: streamingService)],
gracefulShutdownSignals: [.sigterm],
logger: logger
)
services: [streamingService],
gracefulShutdownSignals: [.sigterm]
)

try await serviceGroup.run()
Expand Down Expand Up @@ -210,11 +201,8 @@ struct Application {
})

let serviceGroup = ServiceGroup(
configuration: .init(
services: [.init(service: streamingService)],
gracefulShutdownSignals: [.sigterm],
logger: logger
)
services: [streamingService],
gracefulShutdownSignals: [.sigterm]
)

try await serviceGroup.run()
Expand Down Expand Up @@ -266,8 +254,7 @@ struct Application {
successTerminationBehavior: .shutdownGracefully,
failureTerminationBehavior: .shutdownGracefully
)
],
logger: logger
]
),
)

Expand Down
68 changes: 34 additions & 34 deletions Sources/ServiceLifecycle/ServiceGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public actor ServiceGroup: Sendable, Service {
}

/// The logger.
private let logger: Logger
private let logger: Logger?
/// The logging configuration.
private let loggingConfiguration: ServiceGroupConfiguration.LoggingConfiguration
/// The maximum amount of time that graceful shutdown is allowed to take.
Expand All @@ -55,7 +55,7 @@ public actor ServiceGroup: Sendable, Service {
Set(configuration.gracefulShutdownSignals).isDisjoint(with: configuration.cancellationSignals),
"Overlapping graceful shutdown and cancellation signals"
)
precondition(configuration.logger.label != deprecatedLoggerLabel, "Please migrate to the new initializers")
precondition(configuration.logger?.label != deprecatedLoggerLabel, "Please migrate to the new initializers")
self.state = .initial(services: configuration.services)
self.gracefulShutdownSignals = configuration.gracefulShutdownSignals
self.cancellationSignals = configuration.cancellationSignals
Expand All @@ -76,7 +76,7 @@ public actor ServiceGroup: Sendable, Service {
services: [any Service],
gracefulShutdownSignals: [UnixSignal] = [],
cancellationSignals: [UnixSignal] = [],
logger: Logger
logger: Logger? = nil
) {
let configuration = ServiceGroupConfiguration(
services: services.map { ServiceGroupConfiguration.ServiceConfiguration(service: $0) },
Expand All @@ -88,7 +88,7 @@ public actor ServiceGroup: Sendable, Service {
self.init(configuration: configuration)
}

@available(*, deprecated)
@available(*, deprecated, renamed: "init(services:gracefulShutdownSignals:cancellationSignals:logger:)")
public init(
services: [any Service],
configuration: ServiceGroupConfiguration,
Expand Down Expand Up @@ -204,7 +204,7 @@ public actor ServiceGroup: Sendable, Service {
services: inout [ServiceGroupConfiguration.ServiceConfiguration],
gracefulShutdownStream: AsyncStream<Void>
) async throws {
self.logger.debug(
self.logger?.debug(
"Starting service lifecycle",
metadata: [
self.loggingConfiguration.keys.gracefulShutdownSignalsKey: "\(self.gracefulShutdownSignals)",
Expand Down Expand Up @@ -270,7 +270,7 @@ public actor ServiceGroup: Sendable, Service {
gracefulShutdownManagers.reserveCapacity(services.count)

for (index, serviceConfiguration) in services.enumerated() {
self.logger.debug(
self.logger?.debug(
"Starting service",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(serviceConfiguration.service)"
Expand Down Expand Up @@ -324,7 +324,7 @@ public actor ServiceGroup: Sendable, Service {

switch service.successTerminationBehavior.behavior {
case .cancelGroup:
self.logger.debug(
self.logger?.debug(
"Service finished unexpectedly. Cancelling group.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)"
Expand All @@ -337,7 +337,7 @@ public actor ServiceGroup: Sendable, Service {
return .failure(ServiceGroupError.serviceFinishedUnexpectedly())

case .gracefullyShutdownGroup:
self.logger.debug(
self.logger?.debug(
"Service finished. Gracefully shutting down group.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)"
Expand All @@ -357,7 +357,7 @@ public actor ServiceGroup: Sendable, Service {
}

case .ignore:
self.logger.debug(
self.logger?.debug(
"Service finished.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)"
Expand All @@ -366,7 +366,7 @@ public actor ServiceGroup: Sendable, Service {
services[index] = nil

if services.allSatisfy({ $0 == nil }) {
self.logger.debug(
self.logger?.debug(
"All services finished."
)
self.cancelGroupAndSpawnTimeoutIfNeeded(
Expand All @@ -380,7 +380,7 @@ public actor ServiceGroup: Sendable, Service {
case .serviceThrew(let service, let index, let serviceError):
switch service.failureTerminationBehavior.behavior {
case .cancelGroup:
self.logger.debug(
self.logger?.debug(
"Service threw error. Cancelling group.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)",
Expand All @@ -394,7 +394,7 @@ public actor ServiceGroup: Sendable, Service {
return .failure(serviceError)

case .gracefullyShutdownGroup:
self.logger.debug(
self.logger?.debug(
"Service threw error. Shutting down group.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)",
Expand All @@ -416,7 +416,7 @@ public actor ServiceGroup: Sendable, Service {
}

case .ignore:
self.logger.debug(
self.logger?.debug(
"Service threw error.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)",
Expand All @@ -426,7 +426,7 @@ public actor ServiceGroup: Sendable, Service {
services[index] = nil

if services.allSatisfy({ $0 == nil }) {
self.logger.debug(
self.logger?.debug(
"All services finished."
)

Expand All @@ -441,7 +441,7 @@ public actor ServiceGroup: Sendable, Service {
case .signalCaught(let unixSignal):
if self.gracefulShutdownSignals.contains(unixSignal) {
// Let's initiate graceful shutdown.
self.logger.debug(
self.logger?.debug(
"Signal caught. Shutting down the group.",
metadata: [
self.loggingConfiguration.keys.signalKey: "\(unixSignal)"
Expand All @@ -460,7 +460,7 @@ public actor ServiceGroup: Sendable, Service {
}
} else {
// Let's cancel the group.
self.logger.debug(
self.logger?.debug(
"Signal caught. Cancelling the group.",
metadata: [
self.loggingConfiguration.keys.signalKey: "\(unixSignal)"
Expand All @@ -475,7 +475,7 @@ public actor ServiceGroup: Sendable, Service {

case .gracefulShutdownCaught:
// We got a manual or inherited graceful shutdown. Let's initiate graceful shutdown.
self.logger.debug("Graceful shutdown caught. Cascading shutdown to services")
self.logger?.debug("Graceful shutdown caught. Cascading shutdown to services")

do {
try await self.shutdownGracefully(
Expand All @@ -492,7 +492,7 @@ public actor ServiceGroup: Sendable, Service {
case .cancellationCaught:
// We caught cancellation in our child task so we have to spawn
// our cancellation timeout task if needed
self.logger.debug("Caught cancellation.")
self.logger?.debug("Caught cancellation.")
self.cancelGroupAndSpawnTimeoutIfNeeded(
group: &group,
cancellationTimeoutTask: &cancellationTimeoutTask
Expand All @@ -517,7 +517,7 @@ public actor ServiceGroup: Sendable, Service {
return .success(())
}

self.logger.debug(
self.logger?.debug(
"Service lifecycle ended"
)
cancellationTimeoutTask?.cancel()
Expand Down Expand Up @@ -558,12 +558,12 @@ public actor ServiceGroup: Sendable, Service {
.enumerated().reversed()
{
guard let service = services[gracefulShutdownIndex] else {
self.logger.debug(
self.logger?.debug(
"Service already finished. Skipping shutdown"
)
continue gracefulShutdownLoop
}
self.logger.debug(
self.logger?.debug(
"Triggering graceful shutdown for service",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)"
Expand All @@ -583,7 +583,7 @@ public actor ServiceGroup: Sendable, Service {

guard index == gracefulShutdownIndex else {
// Another service exited unexpectedly
self.logger.debug(
self.logger?.debug(
"Service finished unexpectedly during graceful shutdown. Cancelling all other services now",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)"
Expand All @@ -598,7 +598,7 @@ public actor ServiceGroup: Sendable, Service {
}
// The service that we signalled graceful shutdown did exit/
// We can continue to the next one.
self.logger.debug(
self.logger?.debug(
"Service finished",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)"
Expand All @@ -610,7 +610,7 @@ public actor ServiceGroup: Sendable, Service {
services[index] = nil
switch service.failureTerminationBehavior.behavior {
case .cancelGroup:
self.logger.debug(
self.logger?.debug(
"Service threw error during graceful shutdown. Cancelling group.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)",
Expand All @@ -628,7 +628,7 @@ public actor ServiceGroup: Sendable, Service {
guard index == gracefulShutdownIndex else {
// Another service threw while we were waiting for a shutdown
// We have to continue the iterating the task group's result
self.logger.debug(
self.logger?.debug(
"Another service than the service that we were shutting down threw. Continuing with the next one.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)",
Expand All @@ -639,7 +639,7 @@ public actor ServiceGroup: Sendable, Service {
}
// The service that we were shutting down right now threw. Since it's failure
// behaviour is to shutdown the group we can continue
self.logger.debug(
self.logger?.debug(
"The service that we were shutting down threw. Continuing with the next one.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)",
Expand All @@ -652,7 +652,7 @@ public actor ServiceGroup: Sendable, Service {
guard index == gracefulShutdownIndex else {
// Another service threw while we were waiting for a shutdown
// We have to continue the iterating the task group's result
self.logger.debug(
self.logger?.debug(
"Another service than the service that we were shutting down threw. Continuing with the next one.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)",
Expand All @@ -663,7 +663,7 @@ public actor ServiceGroup: Sendable, Service {
}
// The service that we were shutting down right now threw. Since it's failure
// behaviour is to shutdown the group we can continue
self.logger.debug(
self.logger?.debug(
"The service that we were shutting down threw. Continuing with the next one.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)",
Expand All @@ -676,7 +676,7 @@ public actor ServiceGroup: Sendable, Service {
case .signalCaught(let signal):
if self.cancellationSignals.contains(signal) {
// We got signalled cancellation after graceful shutdown
self.logger.debug(
self.logger?.debug(
"Signal caught. Cancelling the group.",
metadata: [
self.loggingConfiguration.keys.signalKey: "\(signal)"
Expand All @@ -692,7 +692,7 @@ public actor ServiceGroup: Sendable, Service {
case .gracefulShutdownTimedOut:
// Gracefully shutting down took longer than the user configured
// so we have to escalate it now.
self.logger.debug(
self.logger?.debug(
"Graceful shutdown took longer than allowed by the configuration. Cancelling the group now.",
metadata: [
self.loggingConfiguration.keys.serviceKey: "\(service.service)"
Expand All @@ -706,7 +706,7 @@ public actor ServiceGroup: Sendable, Service {
case .cancellationCaught:
// We caught cancellation in our child task so we have to spawn
// our cancellation timeout task if needed
self.logger.debug("Caught cancellation.")
self.logger?.debug("Caught cancellation.")
self.cancelGroupAndSpawnTimeoutIfNeeded(
group: &group,
cancellationTimeoutTask: &cancellationTimeoutTask
Expand Down Expand Up @@ -741,7 +741,7 @@ public actor ServiceGroup: Sendable, Service {
) {
guard cancellationTimeoutTask == nil else {
// We already have a cancellation timeout task running.
self.logger.debug(
self.logger?.debug(
"Task cancellation timeout task already running."
)
return
Expand All @@ -756,7 +756,7 @@ public actor ServiceGroup: Sendable, Service {
// from being cancelled.
cancellationTimeoutTask = Task {
do {
self.logger.debug(
self.logger?.debug(
"Task cancellation timeout task started."
)
try await Task.sleep(
Expand All @@ -765,7 +765,7 @@ public actor ServiceGroup: Sendable, Service {
attosecondsComponent: maximumCancellationDuration.attosecondsComponent
)
)
self.logger.debug(
self.logger?.debug(
"Cancellation took longer than allowed by the configuration."
)
fatalError("Cancellation took longer than allowed by the configuration.")
Expand Down
Loading