From 28688a9475dffd31b64142bb14afbf91d1e73dab Mon Sep 17 00:00:00 2001 From: Franz Busch Date: Tue, 17 Sep 2024 15:10:13 +0200 Subject: [PATCH] [CI] Adopt GitHub actions # Motivation We want to switch our CI to GitHub actions. # Modification This PR sets up the reusable workflows from NIO. # Result More modern CI. --- .editorconfig | 13 ++ .github/release.yml | 14 ++ .github/workflows/main.yml | 22 +++ .github/workflows/pull_request.yml | 26 +++ .github/workflows/pull_request_label.yml | 18 ++ .licenseignore | 39 ++++ .swift-format | 58 ++++++ .swiftformat | 27 --- .vscode/settings.json | 1 + Package.swift | 14 +- Sources/ConcurrencyHelpers/Lock.swift | 4 +- .../ConcurrencyHelpers/LockedValueBox.swift | 2 +- ...syncCancelOnGracefulShutdownSequence.swift | 3 +- .../ServiceLifecycle/GracefulShutdown.swift | 96 ++++++++- Sources/ServiceLifecycle/ServiceGroup.swift | 185 +++++++++++------- .../ServiceGroupConfiguration.swift | 22 +-- Sources/UnixSignals/UnixSignal.swift | 2 +- Sources/UnixSignals/UnixSignalsSequence.swift | 9 +- .../GracefulShutdownTests.swift | 3 +- .../ServiceGroupTests.swift | 57 +++--- Tests/UnixSignalsTests/UnixSignalTests.swift | 8 +- docker/Dockerfile | 24 --- docker/docker-compose.2004.58.yaml | 19 -- docker/docker-compose.2204.510.yaml | 19 -- docker/docker-compose.2204.59.yaml | 19 -- docker/docker-compose.2204.main.yaml | 18 -- docker/docker-compose.yaml | 37 ---- scripts/generate_contributors_list.sh | 39 ---- scripts/preview_docc.sh | 30 --- scripts/soundness.sh | 144 -------------- 30 files changed, 464 insertions(+), 508 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/release.yml create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/pull_request.yml create mode 100644 .github/workflows/pull_request_label.yml create mode 100644 .licenseignore create mode 100644 .swift-format delete mode 100644 .swiftformat create mode 100644 .vscode/settings.json delete mode 100644 docker/Dockerfile delete mode 100644 docker/docker-compose.2004.58.yaml delete mode 100644 docker/docker-compose.2204.510.yaml delete mode 100644 docker/docker-compose.2204.59.yaml delete mode 100644 docker/docker-compose.2204.main.yaml delete mode 100644 docker/docker-compose.yaml delete mode 100755 scripts/generate_contributors_list.sh delete mode 100755 scripts/preview_docc.sh delete mode 100755 scripts/soundness.sh 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..647b87f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,22 @@ +name: Main + +on: + push: + branches: [main] + schedule: + - cron: "0 8,20 * * *" + +jobs: + unit-tests: + name: Unit tests + uses: apple/swift-nio/.github/workflows/unit_tests.yml@main + with: + linux_5_9_arguments_override: "--explicit-target-dependency-import-check error" + linux_5_10_arguments_override: "--explicit-target-dependency-import-check error" + linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + + cxx-interop: + name: Cxx interop + uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 0000000..46fea01 --- /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: swiftlang/github-workflows/.github/workflows/soundness.yml@main + with: + license_header_check_project_name: "SwiftServiceLifecycle" + + unit-tests: + name: Unit tests + uses: apple/swift-nio/.github/workflows/unit_tests.yml@main + with: + linux_5_9_arguments_override: "--explicit-target-dependency-import-check error" + linux_5_10_arguments_override: "--explicit-target-dependency-import-check error" + linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + + cxx-interop: + name: Cxx interop + uses: apple/swift-nio/.github/workflows/cxx_interop.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/.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/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Package.swift b/Package.swift index 368dca0..8ce3f41 100644 --- a/Package.swift +++ b/Package.swift @@ -28,10 +28,6 @@ let package = Package( url: "https://github.com/apple/swift-log.git", from: "1.5.2" ), - .package( - url: "https://github.com/apple/swift-docc-plugin", - from: "1.0.0" - ), .package( url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0" @@ -56,13 +52,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,8 +74,12 @@ let package = Package( .testTarget( name: "UnixSignalsTests", dependencies: [ - .target(name: "UnixSignals"), + .target(name: "UnixSignals") ] ), ] ) + +for target in package.targets { + target.swiftSettings?.append(.enableUpcomingFeature("StrictConcurrency")) +} diff --git a/Sources/ConcurrencyHelpers/Lock.swift b/Sources/ConcurrencyHelpers/Lock.swift index 272a749..844161b 100644 --- a/Sources/ConcurrencyHelpers/Lock.swift +++ b/Sources/ConcurrencyHelpers/Lock.swift @@ -184,8 +184,6 @@ final class LockStorage: ManagedBuffer { } } -extension LockStorage: @unchecked Sendable {} - /// A threading lock based on `libpthread` instead of `libdispatch`. /// /// - note: ``Lock`` has reference semantics. @@ -252,7 +250,7 @@ extension Lock { } } -extension Lock: Sendable {} +extension Lock: @unchecked Sendable {} extension UnsafeMutablePointer { @inlinable diff --git a/Sources/ConcurrencyHelpers/LockedValueBox.swift b/Sources/ConcurrencyHelpers/LockedValueBox.swift index e1a5ea0..3fdbda9 100644 --- a/Sources/ConcurrencyHelpers/LockedValueBox.swift +++ b/Sources/ConcurrencyHelpers/LockedValueBox.swift @@ -51,4 +51,4 @@ public struct LockedValueBox { } } -extension LockedValueBox: Sendable where Value: Sendable {} +extension LockedValueBox: @unchecked Sendable where Value: Sendable {} 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..2ba844e 100644 --- a/Sources/ServiceLifecycle/GracefulShutdown.swift +++ b/Sources/ServiceLifecycle/GracefulShutdown.swift @@ -14,6 +14,7 @@ import ConcurrencyHelpers +#if compiler(>=6.0) /// Execute an operation with a graceful shutdown handler that’s immediately invoked if the current task is shutting down gracefully. /// /// This doesn’t check for graceful shutdown, and always executes the passed operation. @@ -31,10 +32,57 @@ import ConcurrencyHelpers /// will be set up. /// /// - Parameters: +/// - isolation: The isolation of the method. Defaults to the isolation of the caller. /// - operation: The actual operation. /// - handler: The handler which is invoked once graceful shutdown has been triggered. // Unsafely inheriting the executor is safe to do here since we are not calling any other async method // except the operation. This makes sure no other executor hops would occur here. +public func withGracefulShutdownHandler( + isolation: isolated (any Actor)? = #isolation, + operation: () async throws -> T, + onGracefulShutdown handler: @Sendable @escaping () -> Void +) async rethrows -> T { + guard let gracefulShutdownManager = TaskLocals.gracefulShutdownManager else { + return try await operation() + } + + // We have to keep track of our handler here to remove it once the operation is finished. + let handlerID = gracefulShutdownManager.registerHandler(handler) + defer { + if let handlerID = handlerID { + gracefulShutdownManager.removeHandler(handlerID) + } + } + + return try await operation() +} + +@available(*, deprecated, message: "Use the method with the isolation parameter instead.") +@_disfavoredOverload +public func withGracefulShutdownHandler( + operation: () async throws -> T, + onGracefulShutdown handler: @Sendable @escaping () -> Void +) async rethrows -> T { + guard let gracefulShutdownManager = TaskLocals.gracefulShutdownManager else { + return try await operation() + } + + // We have to keep track of our handler here to remove it once the operation is finished. + let handlerID = gracefulShutdownManager.registerHandler(handler) + defer { + if let handlerID = handlerID { + gracefulShutdownManager.removeHandler(handlerID) + } + } + + return try await operation() +} + +#else +// We need to retain this method with `@_unsafeInheritExecutor` otherwise we will break older +// Swift versions since the semantics changed. +@available(*, deprecated, message: "Use the method with the isolation parameter instead.") +@_disfavoredOverload @_unsafeInheritExecutor public func withGracefulShutdownHandler( operation: () async throws -> T, @@ -55,6 +103,9 @@ public func withGracefulShutdownHandler( return try await operation() } +#endif + +#if compiler(>=6.0) /// Execute an operation with a graceful shutdown or task cancellation handler that’s immediately invoked if the current task is /// shutting down gracefully or has been cancelled. /// @@ -72,10 +123,37 @@ public func withGracefulShutdownHandler( /// will be set up. /// /// - Parameters: +/// - isolation: The isolation of the method. Defaults to the isolation of the caller. /// - operation: The actual operation. /// - handler: The handler which is invoked once graceful shutdown or task cancellation has been triggered. // Unsafely inheriting the executor is safe to do here since we are not calling any other async method // except the operation. This makes sure no other executor hops would occur here. +public func withTaskCancellationOrGracefulShutdownHandler( + isolation: isolated (any Actor)? = #isolation, + operation: () async throws -> T, + onCancelOrGracefulShutdown handler: @Sendable @escaping () -> Void +) async rethrows -> T { + return try await withTaskCancellationHandler { + try await withGracefulShutdownHandler(isolation: isolation, operation: operation, onGracefulShutdown: handler) + } onCancel: { + handler() + } +} +@available(*, deprecated, message: "Use the method with the isolation parameter instead.") +@_disfavoredOverload +public func withTaskCancellationOrGracefulShutdownHandler( + operation: () async throws -> T, + onCancelOrGracefulShutdown handler: @Sendable @escaping () -> Void +) async rethrows -> T { + return try await withTaskCancellationHandler { + try await withGracefulShutdownHandler(operation: operation, onGracefulShutdown: handler) + } onCancel: { + handler() + } +} +#else +@available(*, deprecated, message: "Use the method with the isolation parameter instead.") +@_disfavoredOverload @_unsafeInheritExecutor public func withTaskCancellationOrGracefulShutdownHandler( operation: () async throws -> T, @@ -87,6 +165,7 @@ public func withTaskCancellationOrGracefulShutdownHandler( handler() } } +#endif /// Waits until graceful shutdown is triggered. /// @@ -115,7 +194,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 +244,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 +301,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 +310,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/UnixSignal.swift b/Sources/UnixSignals/UnixSignal.swift index 9e0bd9d..01b9398 100644 --- a/Sources/UnixSignals/UnixSignal.swift +++ b/Sources/UnixSignals/UnixSignal.swift @@ -54,7 +54,7 @@ public struct UnixSignal: Hashable, Sendable, CustomStringConvertible { /// Usually generated by the abort() function. Useful for cleanup prior to termination. public static let sigabrt = Self(.sigabrt) - /// Hang up detected on controlling terminal or death of controlling process. + /// Hang up detected on controlling terminal or death of controlling process. # ignore-unacceptable-language public static let sighup = Self(.sighup) /// Issued if the user attempts to execute an illegal, malformed, or privileged instruction. public static let sigill = Self(.sigill) 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..b536d2a 100644 --- a/Tests/ServiceLifecycleTests/ServiceGroupTests.swift +++ b/Tests/ServiceLifecycleTests/ServiceGroupTests.swift @@ -164,7 +164,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator.next(), .run) let pid = getpid() - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language await XCTAsyncAssertEqual(await eventIterator.next(), .shutdownGracefully) await mockService.resumeRunContinuation(with: .success(())) @@ -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] ) @@ -557,7 +560,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator3.next(), .run) let pid = getpid() - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language await XCTAsyncAssertEqual(await eventIterator1.next(), .runCancelled) await XCTAsyncAssertEqual(await eventIterator2.next(), .runCancelled) @@ -597,7 +600,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator3.next(), .run) let pid = getpid() - kill(pid, UnixSignal.sigwinch.rawValue) + kill(pid, UnixSignal.sigwinch.rawValue) // ignore-unacceptable-language await XCTAsyncAssertEqual(await eventIterator3.next(), .shutdownGracefully) @@ -610,7 +613,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator3.next(), .runPing) // Now we signal cancellation - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language await XCTAsyncAssertEqual(await eventIterator1.next(), .runCancelled) await XCTAsyncAssertEqual(await eventIterator2.next(), .runCancelled) @@ -649,7 +652,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator3.next(), .run) let pid = getpid() - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language // The last service should receive the shutdown signal first await XCTAsyncAssertEqual(await eventIterator3.next(), .shutdownGracefully) @@ -713,7 +716,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator3.next(), .run) let pid = getpid() - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language // The last service should receive the shutdown signal first await XCTAsyncAssertEqual(await eventIterator3.next(), .shutdownGracefully) @@ -781,7 +784,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator3.next(), .run) let pid = getpid() - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language // The last service should receive the shutdown signal first await XCTAsyncAssertEqual(await eventIterator3.next(), .shutdownGracefully) @@ -845,7 +848,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator3.next(), .run) let pid = getpid() - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language // The last service should receive the shutdown signal first await XCTAsyncAssertEqual(await eventIterator3.next(), .shutdownGracefully) @@ -909,7 +912,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator3.next(), .run) let pid = getpid() - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language // The last service should receive the shutdown signal first await XCTAsyncAssertEqual(await eventIterator3.next(), .shutdownGracefully) @@ -992,7 +995,7 @@ final class ServiceGroupTests: XCTestCase { await XCTAsyncAssertEqual(await eventIterator2.next(), .runPing) let pid = getpid() - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language await XCTAsyncAssertEqual(await eventIterator2.next(), .shutdownGracefully) service1.sendPing() @@ -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/Tests/UnixSignalsTests/UnixSignalTests.swift b/Tests/UnixSignalsTests/UnixSignalTests.swift index 5d2dd43..020eac8 100644 --- a/Tests/UnixSignalsTests/UnixSignalTests.swift +++ b/Tests/UnixSignalsTests/UnixSignalTests.swift @@ -29,7 +29,7 @@ final class UnixSignalTests: XCTestCase { let pid = getpid() var signalIterator = signals.makeAsyncIterator() - kill(pid, signal.rawValue) + kill(pid, signal.rawValue) // ignore-unacceptable-language let caught = await signalIterator.next() XCTAssertEqual(caught, signal) } @@ -41,7 +41,7 @@ final class UnixSignalTests: XCTestCase { var signalIterator = signals.makeAsyncIterator() for _ in 0..<5 { - kill(pid, signal.rawValue) + kill(pid, signal.rawValue) // ignore-unacceptable-language let caught = await signalIterator.next() XCTAssertEqual(caught, signal) @@ -76,7 +76,7 @@ final class UnixSignalTests: XCTestCase { // Allow 10ms for the tasks to start. try await Task.sleep(nanoseconds: 10_000_000) let pid = getpid() - kill(pid, UnixSignal.sigalrm.rawValue) + kill(pid, UnixSignal.sigalrm.rawValue) // ignore-unacceptable-language let first = try await group.next() XCTAssertEqual(first, .caughtSignal) @@ -103,7 +103,7 @@ final class UnixSignalTests: XCTestCase { let pid = getpid() for _ in 0..<10 { - kill(pid, signal.rawValue) + kill(pid, signal.rawValue) // ignore-unacceptable-language let trapped = await signalIterator.next() XCTAssertEqual(trapped, signal) } 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"