From 0c07eca51d6c680fda32bf33a6badb25d8f5038f Mon Sep 17 00:00:00 2001 From: RussBaz Date: Sun, 19 May 2024 21:18:55 +0100 Subject: [PATCH 1/3] asyncBoot will now skip running the lifecycle handlers again if the server is already booted --- Sources/Vapor/Application.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/Vapor/Application.swift b/Sources/Vapor/Application.swift index b5911431e5..f4c8ba4305 100644 --- a/Sources/Vapor/Application.swift +++ b/Sources/Vapor/Application.swift @@ -247,12 +247,17 @@ public final class Application: Sendable { /// Called when the applications starts up, will trigger the lifecycle handlers. The asynchronous version of ``boot()`` public func asyncBoot() async throws { - self.isBooted.withLockedValue { booted in - guard !booted else { - return + let notBooted = self.isBooted.withLockedValue { booted in + guard booted else { + booted = true + return true } - booted = true + + return false } + + guard notBooted else { return } + for handler in self.lifecycle.handlers { try await handler.willBootAsync(self) } From 6e830236d4b743794462954107fd2de05fda599b Mon Sep 17 00:00:00 2001 From: RussBaz Date: Mon, 20 May 2024 18:00:35 +0100 Subject: [PATCH 2/3] Made the asyncBoot code more concise --- Sources/Vapor/Application.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Sources/Vapor/Application.swift b/Sources/Vapor/Application.swift index f4c8ba4305..a84dcdb3d3 100644 --- a/Sources/Vapor/Application.swift +++ b/Sources/Vapor/Application.swift @@ -247,17 +247,15 @@ public final class Application: Sendable { /// Called when the applications starts up, will trigger the lifecycle handlers. The asynchronous version of ``boot()`` public func asyncBoot() async throws { - let notBooted = self.isBooted.withLockedValue { booted in - guard booted else { - booted = true - return true - } - - return false + /// Skip the boot process if already booted + guard !self.isBooted.withLockedValue({ + var result = true + swap(&$0, &result) + return result + }) else { + return } - guard notBooted else { return } - for handler in self.lifecycle.handlers { try await handler.willBootAsync(self) } From 3326215b9c3aea41b6ab19ddda19f5dde6aaf11d Mon Sep 17 00:00:00 2001 From: RussBaz Date: Tue, 21 May 2024 21:09:44 +0100 Subject: [PATCH 3/3] Added tests to ensure that the server boot function will trigger the lifecycke handlers only once --- Tests/VaporTests/ApplicationTests.swift | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Tests/VaporTests/ApplicationTests.swift b/Tests/VaporTests/ApplicationTests.swift index dffc131e5f..8424f12ac3 100644 --- a/Tests/VaporTests/ApplicationTests.swift +++ b/Tests/VaporTests/ApplicationTests.swift @@ -169,6 +169,47 @@ final class ApplicationTests: XCTestCase { XCTAssertEqual(foo.didBootAsyncFlag.withLockedValue({ $0 }), true) XCTAssertEqual(foo.shutdownAsyncFlag.withLockedValue({ $0 }), true) } + + func testBootDoesNotTriggerLifecycleHandlerMultipleTimes() throws { + let app = Application(.testing) + defer { app.shutdown() } + + final class Handler: LifecycleHandler, Sendable { + let bootCount = NIOLockedValueBox(0) + func willBoot(_ application: Application) throws { + bootCount.withLockedValue { $0 += 1 } + } + } + + let handler = Handler() + app.lifecycle.use(handler) + + try app.boot() + try app.boot() + + XCTAssertEqual(handler.bootCount.withLockedValue({ $0 }), 1) + } + + func testAsyncBootDoesNotTriggerLifecycleHandlerMultipleTimes() async throws { + let app = try await Application.make(.testing) + + final class Handler: LifecycleHandler, Sendable { + let bootCount = NIOLockedValueBox(0) + func willBoot(_ application: Application) throws { + bootCount.withLockedValue { $0 += 1 } + } + } + + let handler = Handler() + app.lifecycle.use(handler) + + try await app.asyncBoot() + try await app.asyncBoot() + + XCTAssertEqual(handler.bootCount.withLockedValue({ $0 }), 1) + + try await app.asyncShutdown() + } func testThrowDoesNotCrash() throws { enum Static {