Skip to content

Automatic Shutdown #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 18, 2024
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Please check the [releases](https://github.com/mochidev/swift-webpush/releases)
dependencies: [
.package(
url: "https://github.com/mochidev/swift-webpush.git",
.upToNextMinor(from: "0.2.1")
.upToNextMinor(from: "0.3.0")
),
],
...
Expand Down
15 changes: 15 additions & 0 deletions Sources/WebPush/WebPushManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public actor WebPushManager: Sendable {
/// The internal executor to use when delivering messages.
var executor: Executor

/// An internal flag indicating if a manager was shutdown already.
var didShutdown = false

/// An internal lookup of keys as provided by the VAPID configuration.
let vapidKeyLookup: [VAPID.Key.ID : VAPID.Key]

Expand Down Expand Up @@ -121,6 +124,12 @@ public actor WebPushManager: Sendable {
self.executor = executor
}

deinit {
if !didShutdown, case let .httpClient(httpClient) = executor {
try? httpClient.syncShutdown()
}
}

/// Load an up-to-date Authorization header for the specified endpoint and signing key combo.
/// - Parameters:
/// - endpoint: The endpoint we'll be contacting to send push messages for a given subscriber.
Expand Down Expand Up @@ -448,6 +457,11 @@ public actor WebPushManager: Sendable {
extension WebPushManager: Service {
public func run() async throws {
logger.info("Starting up WebPushManager")
guard !didShutdown else {
assertionFailure("The manager was already shutdown and cannot be started.")
logger.error("The manager was already shutdown and cannot be started. Run your application server in debug mode to catch this.")
return
}
try await withTaskCancellationOrGracefulShutdownHandler {
try await gracefulShutdown()
} onCancelOrGracefulShutdown: { [logger, executor] in
Expand All @@ -462,6 +476,7 @@ extension WebPushManager: Service {
])
}
}
didShutdown = true
}
}

Expand Down
42 changes: 0 additions & 42 deletions Tests/WebPushTests/WebPushManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,6 @@ struct WebPushManagerTests {
.mockedKeyID2 : .mockedKey2,
.mockedKeyID3 : .mockedKey3,
])
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await manager.run()
}
group.cancelAll()
}
}

/// This is needed to cover the `uniquingKeysWith` safety call completely.
Expand All @@ -94,25 +88,13 @@ struct WebPushManagerTests {
configuration.unsafeUpdateKeys(primaryKey: .mockedKey1, keys: [.mockedKey1], deprecatedKeys: [.mockedKey1])
let manager = WebPushManager(vapidConfiguration: configuration)
#expect(await manager.vapidKeyLookup == [.mockedKeyID1 : .mockedKey1])
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await manager.run()
}
group.cancelAll()
}
}
}

@Suite("VAPID Key Retrieval") struct VAPIDKeyRetrieval {
@Test func retrievesPrimaryKey() async {
let manager = WebPushManager(vapidConfiguration: .mockedConfiguration)
#expect(manager.nextVAPIDKeyID == .mockedKeyID1)
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await manager.run()
}
group.cancelAll()
}
}

@Test func alwaysRetrievesPrimaryKey() async throws {
Expand All @@ -122,12 +104,6 @@ struct WebPushManagerTests {
for _ in 0..<100_000 {
#expect(manager.nextVAPIDKeyID == .mockedKeyID1)
}
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await manager.run()
}
group.cancelAll()
}
}

@Test func retrievesFallbackKeys() async throws {
Expand All @@ -139,12 +115,6 @@ struct WebPushManagerTests {
keyCounts[manager.nextVAPIDKeyID, default: 0] += 1
}
#expect(abs(keyCounts[.mockedKeyID1, default: 0] - keyCounts[.mockedKeyID2, default: 0]) < 1_000) /// If this test fails, increase this number accordingly
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await manager.run()
}
group.cancelAll()
}
}

@Test func neverRetrievesDeprecatedKeys() async throws {
Expand All @@ -154,12 +124,6 @@ struct WebPushManagerTests {
for _ in 0..<100_000 {
#expect(manager.nextVAPIDKeyID != .mockedKeyID3)
}
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await manager.run()
}
group.cancelAll()
}
}

@Test func keyStatus() async throws {
Expand All @@ -170,12 +134,6 @@ struct WebPushManagerTests {
#expect(manager.keyStatus(for: .mockedKeyID2) == .valid)
#expect(manager.keyStatus(for: .mockedKeyID3) == .deprecated)
#expect(manager.keyStatus(for: .mockedKeyID4) == .unknown)
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await manager.run()
}
group.cancelAll()
}
}
}

Expand Down
Loading