diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3939fba --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + + +[*.yml] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..f96b514 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,14 @@ +changelog: + categories: + - title: SemVer Major + labels: + - ⚠️ semver/major + - title: SemVer Minor + labels: + - semver/minor + - title: SemVer Patch + labels: + - semver/patch + - title: Other Changes + labels: + - semver/none diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..4eadddb --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,20 @@ +name: Main + +on: + push: + branches: [main] + +jobs: + unit-tests: + name: Unit tests + uses: ./.github/workflows/unit_tests.yml + with: + linux_5_8_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_nightly_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_nightly_main_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + + cxx-interop: + name: Cxx interop + uses: ./.github/workflows/cxx_interop.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 0000000..4f82f61 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,26 @@ +name: PR + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + soundness: + name: Soundness + uses: apple/swift-nio/.github/workflows/soundness.yml@main + with: + license_header_check_project_name: "SwiftCertificates" + + unit-tests: + name: Unit tests + uses: apple/swift-nio/.github/workflows/unit_tests.yml@main + with: + linux_5_8_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_nightly_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_nightly_main_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + + swift-6-language-mode: + name: Swift 6 Language Mode + uses: apple/swift-nio/.github/workflows/swift_6_language_mode.yml@main diff --git a/.github/workflows/pull_request_label.yml b/.github/workflows/pull_request_label.yml new file mode 100644 index 0000000..86f199f --- /dev/null +++ b/.github/workflows/pull_request_label.yml @@ -0,0 +1,18 @@ +name: PR label + +on: + pull_request: + types: [labeled, unlabeled, opened, reopened, synchronize] + +jobs: + semver-label-check: + name: Semantic Version label check + runs-on: ubuntu-latest + timeout-minutes: 1 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Check for Semantic Version label + uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml new file mode 100644 index 0000000..cee063b --- /dev/null +++ b/.github/workflows/scheduled.yml @@ -0,0 +1,16 @@ +name: Scheduled + +on: + schedule: + - cron: "0 8,20 * * *" + +jobs: + unit-tests: + name: Unit tests + uses: ./.github/workflows/unit_tests.yml + with: + linux_5_8_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_nightly_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_nightly_main_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" diff --git a/.licenseignore b/.licenseignore new file mode 100644 index 0000000..9f6fdaa --- /dev/null +++ b/.licenseignore @@ -0,0 +1,39 @@ +.gitignore +**/.gitignore +.licenseignore +.gitattributes +.mailfilter +.mailmap +.spi.yml +.swift-format +.editorconfig +.github/* +*.md +*.txt +*.yml +*.yaml +*.json +Package.swift +**/Package.swift +Package@-*.swift +**/Package@-*.swift +Package.resolved +**/Package.resolved +Makefile +*.modulemap +**/*.modulemap +**/*.docc/* +*.xcprivacy +**/*.xcprivacy +*.symlink +**/*.symlink +Dockerfile +**/Dockerfile +Snippets/* +dev/git.commit.template +*.crt +**/*.crt +*.pem +**/*.pem +*.der +**/*.der diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..26b3f51 --- /dev/null +++ b/.swift-format @@ -0,0 +1,58 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "spaces" : 4 + }, + "indentConditionalCompilationBlocks" : false, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : true, + "lineBreakBeforeEachGenericRequirement" : true, + "lineLength" : 120, + "maximumBlankLines" : 1, + "prioritizeKeepingFunctionOutputTogether" : true, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLowerCamelCase" : false, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : false, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : false, + "ReturnVoidInsteadOfEmptyTuple" : true, + "UseEarlyExits" : true, + "UseLetInEveryBoundCaseVariable" : false, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : false, + "UseSynthesizedInitializer" : false, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 4, + "version" : 1 +} \ No newline at end of file diff --git a/.swiftformat b/.swiftformat deleted file mode 100644 index 5ba588e..0000000 --- a/.swiftformat +++ /dev/null @@ -1,27 +0,0 @@ -# file options - ---swiftversion 5.7 ---exclude .build ---exclude Sources/_AsyncMergeSequence - -# format options - ---self insert ---patternlet inline ---ranges nospace ---stripunusedargs unnamed-only ---ifdef no-indent ---extensionacl on-declarations ---disable typeSugar # https://github.com/nicklockwood/SwiftFormat/issues/636 ---disable andOperator ---disable wrapMultilineStatementBraces ---disable enumNamespaces ---disable redundantExtensionACL ---disable redundantReturn ---disable preferKeyPath ---disable sortedSwitchCases ---disable hoistTry ---disable hoistAwait ---disable redundantOptionalBinding - -# rules diff --git a/Package.swift b/Package.swift index 368dca0..54a158e 100644 --- a/Package.swift +++ b/Package.swift @@ -56,13 +56,13 @@ let package = Package( .target( name: "ServiceLifecycleTestKit", dependencies: [ - .target(name: "ServiceLifecycle"), + .target(name: "ServiceLifecycle") ] ), .target( name: "UnixSignals", dependencies: [ - .target(name: "ConcurrencyHelpers"), + .target(name: "ConcurrencyHelpers") ] ), .target( @@ -78,7 +78,7 @@ let package = Package( .testTarget( name: "UnixSignalsTests", dependencies: [ - .target(name: "UnixSignals"), + .target(name: "UnixSignals") ] ), ] diff --git a/Sources/ServiceLifecycle/AsyncCancelOnGracefulShutdownSequence.swift b/Sources/ServiceLifecycle/AsyncCancelOnGracefulShutdownSequence.swift index 55100ca..47fef51 100644 --- a/Sources/ServiceLifecycle/AsyncCancelOnGracefulShutdownSequence.swift +++ b/Sources/ServiceLifecycle/AsyncCancelOnGracefulShutdownSequence.swift @@ -24,7 +24,8 @@ extension AsyncSequence where Self: Sendable, Element: Sendable { } /// An asynchronous sequence that is cancelled once graceful shutdown has triggered. -public struct AsyncCancelOnGracefulShutdownSequence: AsyncSequence, Sendable where Base.Element: Sendable { +public struct AsyncCancelOnGracefulShutdownSequence: AsyncSequence, Sendable +where Base.Element: Sendable { @usableFromInline enum _ElementOrGracefulShutdown: Sendable { case base(AsyncMapNilSequence.Element) diff --git a/Sources/ServiceLifecycle/GracefulShutdown.swift b/Sources/ServiceLifecycle/GracefulShutdown.swift index ca6488c..f42c3f7 100644 --- a/Sources/ServiceLifecycle/GracefulShutdown.swift +++ b/Sources/ServiceLifecycle/GracefulShutdown.swift @@ -115,7 +115,9 @@ enum ValueOrGracefulShutdown: Sendable { /// Cancels the closure when a graceful shutdown was triggered. /// /// - Parameter operation: The actual operation. -public func cancelWhenGracefulShutdown(_ operation: @Sendable @escaping () async throws -> T) async rethrows -> T { +public func cancelWhenGracefulShutdown( + _ operation: @Sendable @escaping () async throws -> T +) async rethrows -> T { return try await withThrowingTaskGroup(of: ValueOrGracefulShutdown.self) { group in group.addTask { let value = try await operation() @@ -163,7 +165,9 @@ public func cancelWhenGracefulShutdown(_ operation: @Sendable @esca // renamed pattern has been shown to cause compiler crashes in 5.x compilers. @available(*, deprecated, message: "renamed to cancelWhenGracefulShutdown") #endif -public func cancelOnGracefulShutdown(_ operation: @Sendable @escaping () async throws -> T) async rethrows -> T? { +public func cancelOnGracefulShutdown( + _ operation: @Sendable @escaping () async throws -> T +) async rethrows -> T? { return try await cancelWhenGracefulShutdown(operation) } @@ -218,11 +222,7 @@ public final class GracefulShutdownManager: @unchecked Sendable { func registerHandler(_ handler: @Sendable @escaping () -> Void) -> UInt64? { return self.state.withLockedValue { state in - if state.isShuttingDown { - // We are already shutting down so we just run the handler now. - handler() - return nil - } else { + guard state.isShuttingDown else { defer { state.handlerCounter += 1 } @@ -231,6 +231,9 @@ public final class GracefulShutdownManager: @unchecked Sendable { return handlerID } + // We are already shutting down so we just run the handler now. + handler() + return nil } } diff --git a/Sources/ServiceLifecycle/ServiceGroup.swift b/Sources/ServiceLifecycle/ServiceGroup.swift index ad3e62b..120983b 100644 --- a/Sources/ServiceLifecycle/ServiceGroup.swift +++ b/Sources/ServiceLifecycle/ServiceGroup.swift @@ -95,7 +95,9 @@ public actor ServiceGroup: Sendable, Service { logger: Logger ) { precondition(configuration.services.isEmpty, "Please migrate to the new initializers") - self.state = .initial(services: Array(services.map { ServiceGroupConfiguration.ServiceConfiguration(service: $0) })) + self.state = .initial( + services: Array(services.map { ServiceGroupConfiguration.ServiceConfiguration(service: $0) }) + ) self.gracefulShutdownSignals = configuration.gracefulShutdownSignals self.cancellationSignals = configuration.cancellationSignals self.logger = logger @@ -217,7 +219,8 @@ public actor ServiceGroup: Sendable, Service { // Using a result here since we want a task group that has non-throwing child tasks // but the body itself is throwing - let result = try await withThrowingTaskGroup(of: ChildTaskResult.self, returning: Result.self) { group in + let result = try await withThrowingTaskGroup(of: ChildTaskResult.self, returning: Result.self) { + group in // First we have to register our signals. let gracefulShutdownSignals = await UnixSignalsSequence(trapping: self.gracefulShutdownSignals) let cancellationSignals = await UnixSignalsSequence(trapping: self.cancellationSignals) @@ -270,7 +273,7 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "Starting service", metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(serviceConfiguration.service)", + self.loggingConfiguration.keys.serviceKey: "\(serviceConfiguration.service)" ] ) @@ -302,7 +305,10 @@ public actor ServiceGroup: Sendable, Service { // empty it indicates that the service has been shutdown. var services = services.map { Optional($0) } - precondition(gracefulShutdownManagers.count == services.count, "We did not create a graceful shutdown manager per service") + precondition( + gracefulShutdownManagers.count == services.count, + "We did not create a graceful shutdown manager per service" + ) // We are going to wait for any of the services to finish or // the signal sequence to throw an error. @@ -321,17 +327,20 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "Service finished unexpectedly. Cancelling group.", metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(service.service)", + self.loggingConfiguration.keys.serviceKey: "\(service.service)" ] ) - self.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) return .failure(ServiceGroupError.serviceFinishedUnexpectedly()) case .gracefullyShutdownGroup: self.logger.debug( "Service finished. Gracefully shutting down group.", metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(service.service)", + self.loggingConfiguration.keys.serviceKey: "\(service.service)" ] ) services[index] = nil @@ -351,7 +360,7 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "Service finished.", metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(service.service)", + self.loggingConfiguration.keys.serviceKey: "\(service.service)" ] ) services[index] = nil @@ -360,7 +369,10 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "All services finished." ) - self.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) return .success(()) } } @@ -375,7 +387,10 @@ public actor ServiceGroup: Sendable, Service { self.loggingConfiguration.keys.errorKey: "\(serviceError)", ] ) - self.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) return .failure(serviceError) case .gracefullyShutdownGroup: @@ -415,7 +430,10 @@ public actor ServiceGroup: Sendable, Service { "All services finished." ) - self.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) return .success(()) } } @@ -426,7 +444,7 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "Signal caught. Shutting down the group.", metadata: [ - self.loggingConfiguration.keys.signalKey: "\(unixSignal)", + self.loggingConfiguration.keys.signalKey: "\(unixSignal)" ] ) do { @@ -445,11 +463,14 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "Signal caught. Cancelling the group.", metadata: [ - self.loggingConfiguration.keys.signalKey: "\(unixSignal)", + self.loggingConfiguration.keys.signalKey: "\(unixSignal)" ] ) - self.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) } case .gracefulShutdownCaught: @@ -472,7 +493,10 @@ public actor ServiceGroup: Sendable, Service { // We caught cancellation in our child task so we have to spawn // our cancellation timeout task if needed self.logger.debug("Caught cancellation.") - self.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) case .signalSequenceFinished, .gracefulShutdownFinished: // This can happen when we are either cancelling everything or @@ -484,7 +508,9 @@ public actor ServiceGroup: Sendable, Service { fatalError("Received gracefulShutdownTimedOut but never triggered a graceful shutdown") case nil: - fatalError("Invalid result from group.next(). We checked if the group is empty before and still got nil") + fatalError( + "Invalid result from group.next(). We checked if the group is empty before and still got nil" + ) } } @@ -508,12 +534,16 @@ public actor ServiceGroup: Sendable, Service { fatalError("Unexpected state") } - if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *), let maximumGracefulShutdownDuration = self.maximumGracefulShutdownDuration { + if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *), + let maximumGracefulShutdownDuration = self.maximumGracefulShutdownDuration + { group.addTask { - try? await Task.sleep(for: Duration( - secondsComponent: maximumGracefulShutdownDuration.secondsComponent, - attosecondsComponent: maximumGracefulShutdownDuration.attosecondsComponent - )) + try? await Task.sleep( + for: Duration( + secondsComponent: maximumGracefulShutdownDuration.secondsComponent, + attosecondsComponent: maximumGracefulShutdownDuration.attosecondsComponent + ) + ) return .gracefulShutdownTimedOut } } @@ -524,7 +554,9 @@ public actor ServiceGroup: Sendable, Service { // We have to shutdown the services in reverse. To do this // we are going to signal each child task the graceful shutdown and then wait for // its exit. - gracefulShutdownLoop: for (gracefulShutdownIndex, gracefulShutdownManager) in gracefulShutdownManagers.lazy.enumerated().reversed() { + gracefulShutdownLoop: for (gracefulShutdownIndex, gracefulShutdownManager) in gracefulShutdownManagers.lazy + .enumerated().reversed() + { guard let service = services[gracefulShutdownIndex] else { self.logger.debug( "Service already finished. Skipping shutdown" @@ -534,7 +566,7 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "Triggering graceful shutdown for service", metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(service.service)", + self.loggingConfiguration.keys.serviceKey: "\(service.service)" ] ) @@ -549,28 +581,30 @@ public actor ServiceGroup: Sendable, Service { continue gracefulShutdownLoop } - if index == gracefulShutdownIndex { - // The service that we signalled graceful shutdown did exit/ - // We can continue to the next one. - self.logger.debug( - "Service finished", - metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(service.service)", - ] - ) - continue gracefulShutdownLoop - } else { + guard index == gracefulShutdownIndex else { // Another service exited unexpectedly self.logger.debug( "Service finished unexpectedly during graceful shutdown. Cancelling all other services now", metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(service.service)", + self.loggingConfiguration.keys.serviceKey: "\(service.service)" ] ) - self.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) throw ServiceGroupError.serviceFinishedUnexpectedly() } + // The service that we signalled graceful shutdown did exit/ + // We can continue to the next one. + self.logger.debug( + "Service finished", + metadata: [ + self.loggingConfiguration.keys.serviceKey: "\(service.service)" + ] + ) + continue gracefulShutdownLoop case .serviceThrew(let service, let index, let serviceError): services[index] = nil @@ -591,18 +625,7 @@ public actor ServiceGroup: Sendable, Service { error = serviceError } - if index == gracefulShutdownIndex { - // 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( - "The service that we were shutting down threw. Continuing with the next one.", - metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(service.service)", - self.loggingConfiguration.keys.errorKey: "\(serviceError)", - ] - ) - continue gracefulShutdownLoop - } else { + 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( @@ -614,20 +637,19 @@ public actor ServiceGroup: Sendable, Service { ) break } + // 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( + "The service that we were shutting down threw. Continuing with the next one.", + metadata: [ + self.loggingConfiguration.keys.serviceKey: "\(service.service)", + self.loggingConfiguration.keys.errorKey: "\(serviceError)", + ] + ) + continue gracefulShutdownLoop case .ignore: - if index == gracefulShutdownIndex { - // 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( - "The service that we were shutting down threw. Continuing with the next one.", - metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(service.service)", - self.loggingConfiguration.keys.errorKey: "\(serviceError)", - ] - ) - continue gracefulShutdownLoop - } else { + 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( @@ -639,6 +661,16 @@ public actor ServiceGroup: Sendable, Service { ) break } + // 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( + "The service that we were shutting down threw. Continuing with the next one.", + metadata: [ + self.loggingConfiguration.keys.serviceKey: "\(service.service)", + self.loggingConfiguration.keys.errorKey: "\(serviceError)", + ] + ) + continue gracefulShutdownLoop } case .signalCaught(let signal): @@ -647,11 +679,14 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "Signal caught. Cancelling the group.", metadata: [ - self.loggingConfiguration.keys.signalKey: "\(signal)", + self.loggingConfiguration.keys.signalKey: "\(signal)" ] ) - self.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) } case .gracefulShutdownTimedOut: @@ -660,16 +695,22 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "Graceful shutdown took longer than allowed by the configuration. Cancelling the group now.", metadata: [ - self.loggingConfiguration.keys.serviceKey: "\(service.service)", + self.loggingConfiguration.keys.serviceKey: "\(service.service)" ] ) - self.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) 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.cancelGroupAndSpawnTimeoutIfNeeded(group: &group, cancellationTimeoutTask: &cancellationTimeoutTask) + self.cancelGroupAndSpawnTimeoutIfNeeded( + group: &group, + cancellationTimeoutTask: &cancellationTimeoutTask + ) case .signalSequenceFinished, .gracefulShutdownCaught, .gracefulShutdownFinished: // We just have to tolerate this since signals and parent graceful shutdowns downs can race. @@ -707,7 +748,9 @@ public actor ServiceGroup: Sendable, Service { } group.cancelAll() - if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *), let maximumCancellationDuration = self.maximumCancellationDuration { + if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *), + let maximumCancellationDuration = self.maximumCancellationDuration + { // We have to spawn an unstructured task here because the call to our `run` // method might have already been cancelled and we need to protect the sleep // from being cancelled. @@ -716,10 +759,12 @@ public actor ServiceGroup: Sendable, Service { self.logger.debug( "Task cancellation timeout task started." ) - try await Task.sleep(for: Duration( - secondsComponent: maximumCancellationDuration.secondsComponent, - attosecondsComponent: maximumCancellationDuration.attosecondsComponent - )) + try await Task.sleep( + for: Duration( + secondsComponent: maximumCancellationDuration.secondsComponent, + attosecondsComponent: maximumCancellationDuration.attosecondsComponent + ) + ) self.logger.debug( "Cancellation took longer than allowed by the configuration." ) diff --git a/Sources/ServiceLifecycle/ServiceGroupConfiguration.swift b/Sources/ServiceLifecycle/ServiceGroupConfiguration.swift index 5f410ff..30b21ff 100644 --- a/Sources/ServiceLifecycle/ServiceGroupConfiguration.swift +++ b/Sources/ServiceLifecycle/ServiceGroupConfiguration.swift @@ -118,14 +118,13 @@ public struct ServiceGroupConfiguration: Sendable { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public var maximumGracefulShutdownDuration: Duration? { get { - if let maximumGracefulShutdownDuration = self._maximumGracefulShutdownDuration { - return .init( - secondsComponent: maximumGracefulShutdownDuration.secondsComponent, - attosecondsComponent: maximumGracefulShutdownDuration.attosecondsComponent - ) - } else { + guard let maximumGracefulShutdownDuration = self._maximumGracefulShutdownDuration else { return nil } + return .init( + secondsComponent: maximumGracefulShutdownDuration.secondsComponent, + attosecondsComponent: maximumGracefulShutdownDuration.attosecondsComponent + ) } set { if let newValue = newValue { @@ -147,14 +146,13 @@ public struct ServiceGroupConfiguration: Sendable { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public var maximumCancellationDuration: Duration? { get { - if let maximumCancellationDuration = self._maximumCancellationDuration { - return .init( - secondsComponent: maximumCancellationDuration.secondsComponent, - attosecondsComponent: maximumCancellationDuration.attosecondsComponent - ) - } else { + guard let maximumCancellationDuration = self._maximumCancellationDuration else { return nil } + return .init( + secondsComponent: maximumCancellationDuration.secondsComponent, + attosecondsComponent: maximumCancellationDuration.attosecondsComponent + ) } set { if let newValue = newValue { diff --git a/Sources/UnixSignals/UnixSignalsSequence.swift b/Sources/UnixSignals/UnixSignalsSequence.swift index 9664e47..3b570e2 100644 --- a/Sources/UnixSignals/UnixSignalsSequence.swift +++ b/Sources/UnixSignals/UnixSignalsSequence.swift @@ -85,7 +85,10 @@ extension UnixSignalsSequence { #endif return .init( // This force-unwrap is safe since Dispatch always returns a `DispatchSource` - dispatchSource: DispatchSource.makeSignalSource(signal: sig.rawValue, queue: UnixSignalsSequence.queue) as! DispatchSource, + dispatchSource: DispatchSource.makeSignalSource( + signal: sig.rawValue, + queue: UnixSignalsSequence.queue + ) as! DispatchSource, signal: sig ) } @@ -112,7 +115,9 @@ extension UnixSignalsSequence { await withTaskCancellationHandler { for source in sources { await withCheckedContinuation { (continuation: CheckedContinuation) in - let action = self.stateMachine.withLockedValue { $0.registeringSignal(continuation: continuation) } + let action = self.stateMachine.withLockedValue { + $0.registeringSignal(continuation: continuation) + } switch action { case .setRegistrationHandlerAndResumeDispatchSource: source.dispatchSource.setRegistrationHandler { diff --git a/Tests/ServiceLifecycleTests/GracefulShutdownTests.swift b/Tests/ServiceLifecycleTests/GracefulShutdownTests.swift index c281cf7..a330a92 100644 --- a/Tests/ServiceLifecycleTests/GracefulShutdownTests.swift +++ b/Tests/ServiceLifecycleTests/GracefulShutdownTests.swift @@ -114,7 +114,8 @@ final class GracefulShutdownTests: XCTestCase { func testWithGracefulShutdownHandler_cleansUpHandlerAfterScopeExit() async { final actor Foo { func run() async { - await withGracefulShutdownHandler {} onGracefulShutdown: { + await withGracefulShutdownHandler { + } onGracefulShutdown: { self.foo() } } diff --git a/Tests/ServiceLifecycleTests/ServiceGroupTests.swift b/Tests/ServiceLifecycleTests/ServiceGroupTests.swift index 8a408ba..def361c 100644 --- a/Tests/ServiceLifecycleTests/ServiceGroupTests.swift +++ b/Tests/ServiceLifecycleTests/ServiceGroupTests.swift @@ -198,7 +198,10 @@ final class ServiceGroupTests: XCTestCase { let service1 = MockService(description: "Service1") let service2 = MockService(description: "Service2") let serviceGroup = self.makeServiceGroup( - services: [.init(service: service1, successTerminationBehavior: .ignore), .init(service: service2, failureTerminationBehavior: .ignore)], + services: [ + .init(service: service1, successTerminationBehavior: .ignore), + .init(service: service2, failureTerminationBehavior: .ignore), + ], gracefulShutdownSignals: [.sigalrm] ) @@ -1194,9 +1197,11 @@ final class ServiceGroupTests: XCTestCase { let service2 = MockService(description: "Service2") let service3 = MockService(description: "Service3") let serviceGroup = self.makeServiceGroup( - services: [.init(service: service1, failureTerminationBehavior: .gracefullyShutdownGroup), - .init(service: service2, failureTerminationBehavior: .gracefullyShutdownGroup), - .init(service: service3, failureTerminationBehavior: .gracefullyShutdownGroup)] + services: [ + .init(service: service1, failureTerminationBehavior: .gracefullyShutdownGroup), + .init(service: service2, failureTerminationBehavior: .gracefullyShutdownGroup), + .init(service: service3, failureTerminationBehavior: .gracefullyShutdownGroup), + ] ) do { @@ -1266,9 +1271,11 @@ final class ServiceGroupTests: XCTestCase { let service2 = MockService(description: "Service2") let service3 = MockService(description: "Service3") let serviceGroup = self.makeServiceGroup( - services: [.init(service: service1, failureTerminationBehavior: .ignore), - .init(service: service2, failureTerminationBehavior: .ignore), - .init(service: service3, failureTerminationBehavior: .ignore)] + services: [ + .init(service: service1, failureTerminationBehavior: .ignore), + .init(service: service2, failureTerminationBehavior: .ignore), + .init(service: service3, failureTerminationBehavior: .ignore), + ] ) try await withThrowingTaskGroup(of: Void.self) { group in @@ -1332,9 +1339,11 @@ final class ServiceGroupTests: XCTestCase { let service2 = MockService(description: "Service2") let service3 = MockService(description: "Service3") let serviceGroup = self.makeServiceGroup( - services: [.init(service: service1, failureTerminationBehavior: .gracefullyShutdownGroup), - .init(service: service2, failureTerminationBehavior: .gracefullyShutdownGroup), - .init(service: service3, failureTerminationBehavior: .gracefullyShutdownGroup)] + services: [ + .init(service: service1, failureTerminationBehavior: .gracefullyShutdownGroup), + .init(service: service2, failureTerminationBehavior: .gracefullyShutdownGroup), + .init(service: service3, failureTerminationBehavior: .gracefullyShutdownGroup), + ] ) do { @@ -1403,9 +1412,11 @@ final class ServiceGroupTests: XCTestCase { let service2 = MockService(description: "Service2") let service3 = MockService(description: "Service3") let serviceGroup = self.makeServiceGroup( - services: [.init(service: service1, failureTerminationBehavior: .ignore), - .init(service: service2, failureTerminationBehavior: .ignore), - .init(service: service3, failureTerminationBehavior: .ignore)] + services: [ + .init(service: service1, failureTerminationBehavior: .ignore), + .init(service: service2, failureTerminationBehavior: .ignore), + .init(service: service3, failureTerminationBehavior: .ignore), + ] ) try await withThrowingTaskGroup(of: Void.self) { group in diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 5685dbc..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -ARG swift_version=5.7 -ARG ubuntu_version=focal -ARG base_image=swift:$swift_version-$ubuntu_version -FROM $base_image -# needed to do again after FROM due to docker limitation -ARG swift_version -ARG ubuntu_version - -# set as UTF-8 -RUN apt-get update && apt-get install -y locales locales-all -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US.UTF-8 - -# tools -RUN mkdir -p $HOME/.tools -RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile - -# swiftformat (until part of the toolchain) - -ARG swiftformat_version=0.51.7 -RUN git clone --branch $swiftformat_version --depth 1 https://github.com/nicklockwood/SwiftFormat $HOME/.tools/swift-format -RUN cd $HOME/.tools/swift-format && swift build -c release -RUN ln -s $HOME/.tools/swift-format/.build/release/swiftformat $HOME/.tools/swiftformat diff --git a/docker/docker-compose.2004.58.yaml b/docker/docker-compose.2004.58.yaml deleted file mode 100644 index 269033f..0000000 --- a/docker/docker-compose.2004.58.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-lifecycle:20.04-5.8 - build: - args: - ubuntu_version: "focal" - swift_version: "5.8" - - test: - image: swift-service-lifecycle:20.04-5.8 - environment: - - SKIP_SIGNAL_TEST=true - - FORCE_TEST_DISCOVERY=--enable-test-discovery - - shell: - image: swift-service-lifecycle:20.04-5.8 diff --git a/docker/docker-compose.2204.510.yaml b/docker/docker-compose.2204.510.yaml deleted file mode 100644 index ba2c0a5..0000000 --- a/docker/docker-compose.2204.510.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-lifecycle:22.04-5.10 - build: - args: - ubuntu_version: "jammy" - swift_version: "5.10" - - test: - image: swift-service-lifecycle:22.04-5.10 - environment: - - SKIP_SIGNAL_TEST=true - - FORCE_TEST_DISCOVERY=--enable-test-discovery - - shell: - image: swift-service-lifecycle:22.04-5.10 diff --git a/docker/docker-compose.2204.59.yaml b/docker/docker-compose.2204.59.yaml deleted file mode 100644 index edcf0ba..0000000 --- a/docker/docker-compose.2204.59.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-lifecycle:22.04-5.9 - build: - args: - ubuntu_version: "jammy" - swift_version: "5.9" - - test: - image: swift-service-lifecycle:22.04-5.9 - environment: - - SKIP_SIGNAL_TEST=true - - FORCE_TEST_DISCOVERY=--enable-test-discovery - - shell: - image: swift-service-lifecycle:22.04-5.9 diff --git a/docker/docker-compose.2204.main.yaml b/docker/docker-compose.2204.main.yaml deleted file mode 100644 index f0d8a35..0000000 --- a/docker/docker-compose.2204.main.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-lifecycle:22.04-main - build: - args: - base_image: "swiftlang/swift:nightly-main-jammy" - - test: - image: swift-service-lifecycle:22.04-main - environment: - - SKIP_SIGNAL_TEST=true - - FORCE_TEST_DISCOVERY=--enable-test-discovery - - shell: - image: swift-service-lifecycle:22.04-main diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml deleted file mode 100644 index d78a09b..0000000 --- a/docker/docker-compose.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# this file is not designed to be run directly -# instead, use the docker-compose.. files -# eg docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.1804.50.yaml run test -version: "3" - -services: - - runtime-setup: - image: swift-service-lifecycle:default - build: - context: . - dockerfile: Dockerfile - - common: &common - image: swift-service-lifecycle:default - depends_on: [runtime-setup] - volumes: - - ~/.ssh:/root/.ssh - - ..:/code:z - working_dir: /code - cap_drop: - - CAP_NET_RAW - - CAP_NET_BIND_SERVICE - - soundness: - <<: *common - command: /bin/bash -cl "./scripts/soundness.sh" - - test: - <<: *common - command: /bin/bash -cl "swift test -Xswiftc -warnings-as-errors $${FORCE_TEST_DISCOVERY-} $${SANITIZER_ARG-}" - - # util - - shell: - <<: *common - entrypoint: /bin/bash -l diff --git a/scripts/generate_contributors_list.sh b/scripts/generate_contributors_list.sh deleted file mode 100755 index 6c418bb..0000000 --- a/scripts/generate_contributors_list.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceLifecycle open source project -## -## Copyright (c) 2019-2020 Apple Inc. and the SwiftServiceLifecycle project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceLifecycle project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -set -eu -here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -contributors=$( cd "$here"/.. && git shortlog -es | cut -f2 | sed 's/^/- /' ) - -cat > "$here/../CONTRIBUTORS.txt" <<- EOF - For the purpose of tracking copyright, this is the list of individuals and - organizations who have contributed source code to SwiftServiceLifecycle. - - For employees of an organization/company where the copyright of work done - by employees of that company is held by the company itself, only the company - needs to be listed here. - - ## COPYRIGHT HOLDERS - - - Apple Inc. (all contributors with '@apple.com') - - ### Contributors - - $contributors - - **Updating this list** - - Please do not edit this file manually. It is generated using \`./scripts/generate_contributors_list.sh\`. If a name is misspelled or appearing multiple times: add an entry in \`./.mailmap\` -EOF diff --git a/scripts/preview_docc.sh b/scripts/preview_docc.sh deleted file mode 100755 index 7632359..0000000 --- a/scripts/preview_docc.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceLifecycle open source project -## -## Copyright (c) 2022 Apple Inc. and the SwiftServiceLifecycle project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceLifecycle project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -##===----------------------------------------------------------------------===## -## -## This source file is part of the Swift Distributed Actors open source project -## -## Copyright (c) 2018-2019 Apple Inc. and the Swift Distributed Actors project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.md for the list of Swift Distributed Actors project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -swift package --disable-sandbox preview-documentation --target $1 diff --git a/scripts/soundness.sh b/scripts/soundness.sh deleted file mode 100755 index c24424b..0000000 --- a/scripts/soundness.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceLifecycle open source project -## -## Copyright (c) 2017-2022 Apple Inc. and the SwiftServiceLifecycle project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceLifecycle project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -set -eu - -here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -function replace_acceptable_years() { - # this needs to replace all acceptable forms with 'YEARS' - sed -e 's/20[12][78901]-202[0123]/YEARS/' -e 's/2019/YEARS/' -e 's/202[0123]/YEARS/' -} - -printf "=> Checking for unacceptable language... " -# This greps for unacceptable terminology. The square bracket[s] are so that -# "git grep" doesn't find the lines that greps :). -unacceptable_terms=( - -e blacklis[t] - -e whitelis[t] - -e slav[e] - -e sanit[y] -) -if git grep --color=never -i "${unacceptable_terms[@]}" > /dev/null; then - printf "\033[0;31mUnacceptable language found.\033[0m\n" - git grep -i "${unacceptable_terms[@]}" - exit 1 -fi -printf "\033[0;32mokay.\033[0m\n" - -printf "=> Checking format... " -FIRST_OUT="$(git status --porcelain)" -swiftformat . > /dev/null 2>&1 -SECOND_OUT="$(git status --porcelain)" -if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then - printf "\033[0;31mformatting issues!\033[0m\n" - git --no-pager diff - exit 1 -else - printf "\033[0;32mokay.\033[0m\n" -fi - -printf "=> Checking license headers\n" -tmp=$(mktemp /tmp/.service-lifecycle-soundness_XXXXXX) - -for language in swift-or-c bash dtrace; do - printf " * $language... " - declare -a matching_files - declare -a exceptions - expections=( ) - matching_files=( -name '*' ) - case "$language" in - swift-or-c) - exceptions=( -name Package.swift -o -name 'Package@*.swift' ) - matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) - cat > "$tmp" <<"EOF" -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftServiceLifecycle open source project -// -// Copyright (c) YEARS Apple Inc. and the SwiftServiceLifecycle project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftServiceLifecycle project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// -EOF - ;; - bash) - matching_files=( -name '*.sh' ) - cat > "$tmp" <<"EOF" -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceLifecycle open source project -## -## Copyright (c) YEARS Apple Inc. and the SwiftServiceLifecycle project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceLifecycle project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## -EOF - ;; - dtrace) - matching_files=( -name '*.d' ) - cat > "$tmp" <<"EOF" -#!/usr/sbin/dtrace -q -s -/*===----------------------------------------------------------------------===* - * - * This source file is part of the SwiftServiceLifecycle open source project - * - * Copyright (c) YEARS Apple Inc. and the SwiftServiceLifecycle project authors - * Licensed under Apache License v2.0 - * - * See LICENSE.txt for license information - * See CONTRIBUTORS.txt for the list of SwiftServiceLifecycle project authors - * - * SPDX-License-Identifier: Apache-2.0 - * - *===----------------------------------------------------------------------===*/ -EOF - ;; - *) - echo >&2 "ERROR: unknown language '$language'" - ;; - esac - - expected_lines=$(cat "$tmp" | wc -l) - expected_sha=$(cat "$tmp" | shasum) - - ( - cd "$here/.." - find . \ - \( \! -path './.build/*' -a \ - \( "${matching_files[@]}" \) -a \ - \( \! \( "${exceptions[@]}" \) \) \) | while read line; do - if [[ "$(cat "$line" | replace_acceptable_years | head -n $expected_lines | shasum)" != "$expected_sha" ]]; then - printf "\033[0;31mmissing headers in file '$line'!\033[0m\n" - diff -u <(cat "$line" | replace_acceptable_years | head -n $expected_lines) "$tmp" - exit 1 - fi - done - printf "\033[0;32mokay.\033[0m\n" - ) -done - -rm "$tmp"