Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ jobs:
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_6_1_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_6_2_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_nightly_next_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"
windows_6_0_enabled: true
windows_6_1_enabled: true
windows_6_2_enabled: true
windows_nightly_next_enabled: true
windows_nightly_main_enabled: true

cxx-interop:
name: Cxx interop
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ jobs:
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_6_1_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_6_2_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
linux_nightly_next_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"
windows_6_0_enabled: true
windows_6_1_enabled: true
windows_6_2_enabled: true
windows_nightly_next_enabled: true
windows_nightly_main_enabled: true

cxx-interop:
name: Cxx interop
Expand Down
6 changes: 6 additions & 0 deletions Sources/UnixSignals/UnixSignal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import Dispatch
/// A struct representing a Unix signal.
///
/// Signals are standardized messages sent to a running program to trigger specific behavior, such as quitting or error handling
///
/// - Important: Unix signals are only functional on platforms supporting signals.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we instead compile out these unix signals on Windows for now completely, so even the ServiceGroup doesn't have those parameters in its initializers? We can later add support for the Windows-specific termination handling using the right terminology, and add more initializers then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, no. I intentionally didn't compile them out because that makes writing cross platform Swift apps with ServiceLifecycle really hard to maintain since users now need to #if os(Windows). IMO, the best place forward is to define event handlers here a well and add an event handler parameter to the ServiceGroup but that can be done in a follow up PR.

Copy link
Collaborator

@czechboy0 czechboy0 Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we make this build on Windows, "UnixSignal" becomes API on Windows. I don't love that and would prefer we compile it out and only deliberately add a potential Windows-specific or a cross-platform API (called e.g. "service event") that uses the right platform-specific API under the hood. But landing this is a one-way door we can't remove for the rest of ServiceLifecycle 2.x, and we don't really benefit from it.

I think it's fine for those writing cross-platform services to handle this in their app for now, and we can introduce a cross-platform event API later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again I disagree. Forcing users to write conditional code based on platform is a horrible pattern. I think it is totally fine for UnixSignal to be an API on Windows that just isn't doing anything with it being documented. Packages that have different surface API based on platform is a huge pain that we often encounter ourselves e.g. Foundation vs FoundationEssentials.

For a low-level library like swift-system that might be fine but for a higher level library like ServiceLifecycle I think we should strive to keep the APIs aligned across platforms.

Copy link
Collaborator

@czechboy0 czechboy0 Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what's the plan when event handling is supported on Windows in Service Lifecycle? What will that look like in the ServiceGroup initializer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just expand the API. We have two possibilities:

  1. Adding a new WindowsConsoleEvent struct and two new properties to the group configuration i.e. gracefulShutdownConsoleEvents and cancellationConsoleEvents.
  2. Trying to come up with a cross platform representation like TerminationEvent that covers both unix signals and console events.

I am currently thinking that the former, is the better way forward since I think it is pretty hard to come up with a cross platform representation. swift-subprocess is doing something similar.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would WindowsConsoleEvent be a symbol that's present on macOS and Linux?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered moving unix signals behind a trait. Trait would be default enabled on all platforms where Swift Service Lifecycle currently builds. Could default to disabled on Windows? Could possibly even annotate them as unavailable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would WindowsConsoleEvent be a symbol that's present on macOS and Linux?

Yes. Same argument applies the other way around. Somebody wanting to write a cross platform service that uses ServiceGroup should not be forced to write #if os() code.

Trait would be default enabled on all platforms where Swift Service Lifecycle currently builds.

Traits cannot be enabled based on platform. They are orthogonal concepts.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Same argument applies the other way around. Somebody wanting to write a cross platform service that uses ServiceGroup should not be forced to write #if os() code.

Fair enough, while I don't agree with the tradeoff here, it is a reasonable position and I'll defer to you here.

public struct UnixSignal: Hashable, Sendable, CustomStringConvertible {
internal enum Wrapped {
case sigabrt
Expand Down Expand Up @@ -118,6 +120,9 @@ extension UnixSignal.Wrapped: CustomStringConvertible {

extension UnixSignal.Wrapped {
var rawValue: Int32 {
#if os(Windows)
return -1
#else
switch self {
case .sigabrt:
return SIGABRT
Expand Down Expand Up @@ -146,5 +151,6 @@ extension UnixSignal.Wrapped {
case .sigpipe:
return SIGPIPE
}
#endif
}
}
7 changes: 4 additions & 3 deletions Sources/UnixSignals/UnixSignalsSequence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
#if canImport(Darwin)
import Darwin
import Dispatch
#elseif canImport(Glibc)
#else
@preconcurrency import Dispatch
#endif

#if canImport(Glibc)
import Glibc
#elseif canImport(Musl)
@preconcurrency import Dispatch
import Musl
#elseif canImport(Android)
@preconcurrency import Dispatch
import Android
#endif
import ConcurrencyHelpers
Expand Down
2 changes: 2 additions & 0 deletions Tests/ServiceLifecycleTests/ServiceGroupAddServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ final class ServiceGroupAddServiceTests: XCTestCase {
}
}

#if !os(Windows)
func testGracefulShutdownOrdering_withAddedServices() async throws {
let service1 = MockService(description: "Service1")
let service2 = MockService(description: "Service2")
Expand Down Expand Up @@ -369,6 +370,7 @@ final class ServiceGroupAddServiceTests: XCTestCase {
await service2.resumeRunContinuation(with: .success(()))
}
}
#endif

// MARK: - Helpers

Expand Down
18 changes: 18 additions & 0 deletions Tests/ServiceLifecycleTests/ServiceGroupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ final class ServiceGroupTests: XCTestCase {
}
}

#if !os(Windows)
func test_whenRun_ShutdownGracefully() async throws {
let mockService = MockService(description: "Service1")
let serviceGroup = self.makeServiceGroup(
Expand All @@ -103,6 +104,7 @@ final class ServiceGroupTests: XCTestCase {
await mockService.resumeRunContinuation(with: .success(()))
}
}
#endif

func testRun_whenServiceExitsEarly() async throws {
let mockService = MockService(description: "Service1")
Expand Down Expand Up @@ -469,6 +471,7 @@ final class ServiceGroupTests: XCTestCase {
}
}

#if !os(Windows)
func testCancellationSignal() async throws {
let service1 = MockService(description: "Service1")
let service2 = MockService(description: "Service2")
Expand Down Expand Up @@ -507,7 +510,9 @@ final class ServiceGroupTests: XCTestCase {
await XCTAsyncAssertNoThrow(try await group.next())
}
}
#endif

#if !os(Windows)
func testCancellationSignal_afterGracefulShutdownSignal() async throws {
let service1 = MockService(description: "Service1")
let service2 = MockService(description: "Service2")
Expand Down Expand Up @@ -560,7 +565,9 @@ final class ServiceGroupTests: XCTestCase {
await XCTAsyncAssertNoThrow(try await group.next())
}
}
#endif

#if !os(Windows)
func testGracefulShutdownOrdering() async throws {
let service1 = MockService(description: "Service1")
let service2 = MockService(description: "Service2")
Expand Down Expand Up @@ -624,7 +631,9 @@ final class ServiceGroupTests: XCTestCase {
await service1.resumeRunContinuation(with: .success(()))
}
}
#endif

#if !os(Windows)
func testGracefulShutdownOrdering_whenServiceThrows() async throws {
let service1 = MockService(description: "Service1")
let service2 = MockService(description: "Service2")
Expand Down Expand Up @@ -688,7 +697,9 @@ final class ServiceGroupTests: XCTestCase {
}
}
}
#endif

#if !os(Windows)
func testGracefulShutdownOrdering_whenServiceThrows_andServiceGracefullyShutsdown() async throws {
let service1 = MockService(description: "Service1")
let service2 = MockService(description: "Service2")
Expand Down Expand Up @@ -756,7 +767,9 @@ final class ServiceGroupTests: XCTestCase {
}
}
}
#endif

#if !os(Windows)
func testGracefulShutdownOrdering_whenServiceExits() async throws {
let service1 = MockService(description: "Service1")
let service2 = MockService(description: "Service2")
Expand Down Expand Up @@ -816,7 +829,9 @@ final class ServiceGroupTests: XCTestCase {
await service2.resumeRunContinuation(with: .success(()))
}
}
#endif

#if !os(Windows)
func testGracefulShutdownOrdering_whenServiceExits_andIgnoringThrows() async throws {
let service1 = MockService(description: "Service1")
let service2 = MockService(description: "Service2")
Expand Down Expand Up @@ -886,7 +901,9 @@ final class ServiceGroupTests: XCTestCase {
}
}
}
#endif

#if !os(Windows)
func testNestedServiceLifecycle() async throws {
struct NestedGroupService: Service {
let group: ServiceGroup
Expand Down Expand Up @@ -1007,6 +1024,7 @@ final class ServiceGroupTests: XCTestCase {
await service2.resumeRunContinuation(with: .success(()))
}
}
#endif

func testGracefulShutdownEscalation() async throws {
let mockService = MockService(description: "Service1")
Expand Down
4 changes: 4 additions & 0 deletions Tests/UnixSignalsTests/UnixSignalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//
//===----------------------------------------------------------------------===//

#if !os(Windows)

import UnixSignals
import XCTest
#if canImport(Darwin)
Expand Down Expand Up @@ -154,3 +156,5 @@ final class UnixSignalTests: XCTestCase {
await task.value
}
}

#endif
Loading