Skip to content

Commit

Permalink
Add fully async entrypoints (#3114)
Browse files Browse the repository at this point in the history
* Add `startup()` and `execute()` as preferred async versions of `start()` and `run()` which handle use of async commands cleanly.
* Update tests and sample code to use new async entrypoints.
* Use new ConsoleKit
  • Loading branch information
gwynne committed Dec 6, 2023
1 parent c710b8f commit 3d62c0c
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Expand Up @@ -5,6 +5,8 @@ updates:
directory: "/"
schedule:
interval: "daily"
allow:
- dependency-type: all
groups:
dependencies:
patterns:
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Expand Up @@ -22,7 +22,7 @@ let package = Package(
.package(url: "https://github.com/vapor/async-kit.git", from: "1.15.0"),

// 💻 APIs for creating interactive CLI tools.
.package(url: "https://github.com/vapor/console-kit.git", from: "4.13.0"),
.package(url: "https://github.com/vapor/console-kit.git", from: "4.14.0"),

// 🔑 Hashing (SHA2, HMAC), encryption (AES), public-key (RSA), and random data generation.
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "4.0.0"),
Expand Down
2 changes: 1 addition & 1 deletion Package@swift-5.9.swift
Expand Up @@ -22,7 +22,7 @@ let package = Package(
.package(url: "https://github.com/vapor/async-kit.git", from: "1.15.0"),

// 💻 APIs for creating interactive CLI tools.
.package(url: "https://github.com/vapor/console-kit.git", from: "4.13.0"),
.package(url: "https://github.com/vapor/console-kit.git", from: "4.14.0"),

// 🔑 Hashing (SHA2, HMAC), encryption (AES), public-key (RSA), and random data generation.
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "4.0.0"),
Expand Down
4 changes: 1 addition & 3 deletions Sources/Development/entrypoint.swift
Expand Up @@ -11,9 +11,7 @@ struct Entrypoint {
defer { app.shutdown() }

try configure(app)
// TODO: Replace with correctly async version of `app.run()`.
try app.start()
try await app.running?.onStop.get()
try await app.execute()
}
}

60 changes: 43 additions & 17 deletions Sources/Vapor/Application.swift
Expand Up @@ -148,10 +148,14 @@ public final class Application: Sendable {
DotEnvFile.load(for: environment, on: .shared(self.eventLoopGroup), fileio: self.fileio, logger: self.logger)
}

/// Starts the Application using the `start()` method, then waits for any running tasks to complete
/// Starts the ``Application`` using the ``start()`` method, then waits for any running tasks to complete.
/// If your application is started without arguments, the default argument is used.
///
/// Under normal circumstances, `run()` begin start the shutdown, then wait for the web server to (manually) shut down before returning.
/// Under normal circumstances, ``run()`` runs until a shutdown is triggered, then waits for the web server to
/// (manually) shut down before returning.
///
/// > Warning: You should probably be using ``execute()`` instead of this method.
@available(*, noasync, message: "Use the async execute() method instead.")
public func run() throws {
do {
try self.start()
Expand All @@ -161,31 +165,53 @@ public final class Application: Sendable {
throw error
}
}

/// Starts the ``Application`` asynchronous using the ``startup()`` method, then waits for any running tasks
/// to complete. If your application is started without arguments, the default argument is used.
///
/// Under normal circumstances, ``execute()`` runs until a shutdown is triggered, then wait for the web server to
/// (manually) shut down before returning.
public func execute() async throws {
do {
try await self.startup()
try await self.running?.onStop.get()
} catch {
self.logger.report(error: error)
throw error
}
}

/// When called, this will execute the startup command provided through an argument. If no startup command is
/// provided, the default is used. Under normal circumstances, this will start running Vapor's webserver.
///
/// If you `start` Vapor through this method, you'll need to prevent your Swift Executable from closing yourself.
/// If you want to run your Application indefinitely, or until your code shuts the application down,
/// use `run()` instead.
/// If you start Vapor through this method, you'll need to prevent your Swift Executable from closing yourself.
/// If you want to run your ``Application`` indefinitely, or until your code shuts the application down,
/// use ``run()`` instead.
///
/// > Warning: You should probably be using ``startup()`` instead of this method.
@available(*, noasync, message: "Use the async startup() method instead.")
public func start() throws {
try self.eventLoopGroup.any().makeFutureWithTask { try await self.startup() }.wait()
}

/// When called, this will asynchronously execute the startup command provided through an argument. If no startup
/// command is provided, the default is used. Under normal circumstances, this will start running Vapor's webserver.
///
/// If you start Vapor through this method, you'll need to prevent your Swift Executable from closing yourself.
/// If you want to run your ``Application`` indefinitely, or until your code shuts the application down,
/// use ``execute()`` instead.
public func startup() async throws {
try self.boot()

let commands = self.commands.group()
let asyncCommands = self.asyncCommands.group()
let combinedCommands = asyncCommands.merge(
with: commands,
defaultCommand: commands.defaultCommand,
help: commands.help
)
let combinedCommands = AsyncCommands(
commands: self.asyncCommands.commands.merging(self.commands.commands) { $1 },
defaultCommand: self.asyncCommands.defaultCommand ?? self.commands.defaultCommand,
enableAutocomplete: self.asyncCommands.enableAutocomplete || self.commands.enableAutocomplete
).group()

var context = CommandContext(console: self.console, input: self.environment.commandInput)
context.application = self
try self.eventLoopGroup.any()
.makeFutureWithTask { [context] in
try await self.console.run(combinedCommands, with: context)
}
.wait()
try await self.console.run(combinedCommands, with: context)
}

public func boot() throws {
Expand Down
4 changes: 2 additions & 2 deletions Tests/AsyncTests/AsyncClientTests.swift
Expand Up @@ -42,7 +42,7 @@ final class AsyncClientTests: XCTestCase {

remoteApp.environment.arguments = ["serve"]
try remoteApp.boot()
try remoteApp.start()
try await remoteApp.startup()

XCTAssertNotNil(remoteApp.http.server.shared.localAddress)
guard let localAddress = remoteApp.http.server.shared.localAddress,
Expand Down Expand Up @@ -152,7 +152,7 @@ final class AsyncClientTests: XCTestCase {

app.environment.arguments = ["serve"]
try app.boot()
try app.start()
try await app.startup()

XCTAssertNotNil(app.http.server.shared.localAddress)
guard let localAddress = app.http.server.shared.localAddress,
Expand Down
8 changes: 4 additions & 4 deletions Tests/AsyncTests/AsyncWebSocketTests.swift
Expand Up @@ -15,7 +15,7 @@ final class AsyncWebSocketTests: XCTestCase {
ws.onText { ws.send($1) }
}
server.environment.arguments = ["serve"]
try server.start()
try await server.startup()

defer {
server.shutdown()
Expand Down Expand Up @@ -58,7 +58,7 @@ final class AsyncWebSocketTests: XCTestCase {

app.environment.arguments = ["serve"]

try app.start()
try await app.startup()

XCTAssertNotNil(app.http.server.shared.localAddress)
guard let localAddress = app.http.server.shared.localAddress,
Expand Down Expand Up @@ -89,7 +89,7 @@ final class AsyncWebSocketTests: XCTestCase {
app.http.server.configuration.port = 0
app.environment.arguments = ["serve"]

try app.start()
try await app.startup()

XCTAssertNotNil(app.http.server.shared.localAddress)
guard let localAddress = app.http.server.shared.localAddress,
Expand Down Expand Up @@ -128,7 +128,7 @@ final class AsyncWebSocketTests: XCTestCase {

app.environment.arguments = ["serve"]

try app.start()
try await app.startup()

XCTAssertNotNil(app.http.server.shared.localAddress)
guard let localAddress = app.http.server.shared.localAddress,
Expand Down
2 changes: 1 addition & 1 deletion Tests/VaporTests/ClientTests.swift
Expand Up @@ -51,7 +51,7 @@ final class ClientTests: XCTestCase {

remoteApp.environment.arguments = ["serve"]
try remoteApp.boot()
try remoteApp.start()
try await remoteApp.startup()

XCTAssertNotNil(remoteApp.http.server.shared.localAddress)
guard let localAddress = remoteApp.http.server.shared.localAddress,
Expand Down
6 changes: 3 additions & 3 deletions Tests/VaporTests/PipelineTests.swift
Expand Up @@ -161,7 +161,7 @@ final class PipelineTests: XCTestCase {

app.environment.arguments = ["serve"]
app.http.server.configuration.port = 0
try app.start()
try await app.startup()

XCTAssertNotNil(app.http.server.shared.localAddress)
guard let localAddress = app.http.server.shared.localAddress,
Expand Down Expand Up @@ -191,7 +191,7 @@ final class PipelineTests: XCTestCase {

app.environment.arguments = ["serve"]
app.http.server.configuration.port = 0
try app.start()
try await app.startup()

XCTAssertNotNil(app.http.server.shared.localAddress)
guard let localAddress = app.http.server.shared.localAddress,
Expand Down Expand Up @@ -223,7 +223,7 @@ final class PipelineTests: XCTestCase {

app.environment.arguments = ["serve"]
app.http.server.configuration.port = 0
try app.start()
try await app.startup()

XCTAssertNotNil(app.http.server.shared.localAddress)
guard let localAddress = app.http.server.shared.localAddress,
Expand Down

0 comments on commit 3d62c0c

Please sign in to comment.