Skip to content

Commit

Permalink
Bring back AsyncCommands (#3109)
Browse files Browse the repository at this point in the history
  • Loading branch information
marius-se committed Dec 1, 2023
1 parent da9c280 commit c710b8f
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 60 deletions.
20 changes: 10 additions & 10 deletions 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.10.0"),
.package(url: "https://github.com/vapor/console-kit.git", from: "4.13.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 All @@ -32,34 +32,34 @@ let package = Package(

// 💥 Backtraces for Swift on Linux
.package(url: "https://github.com/swift-server/swift-backtrace.git", from: "1.1.1"),

// Event-driven network application framework for high performance protocol servers & clients, non-blocking.
.package(url: "https://github.com/apple/swift-nio.git", from: "2.62.0"),

// Bindings to OpenSSL-compatible libraries for TLS support in SwiftNIO
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.8.0"),

// HTTP/2 support for SwiftNIO
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.28.0"),

// Useful code around SwiftNIO.
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.19.0"),

// Swift logging API
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),

// Swift metrics API
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.0.0"),

// Swift collection algorithms
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),

// WebSocket client library built on SwiftNIO
.package(url: "https://github.com/vapor/websocket-kit.git", from: "2.13.0"),

// MultipartKit, Multipart encoding and decoding
.package(url: "https://github.com/vapor/multipart-kit.git", from: "4.2.1"),

// Low-level atomic operations
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.1.0"),
],
Expand Down Expand Up @@ -95,7 +95,7 @@ let package = Package(
.product(name: "MultipartKit", package: "multipart-kit"),
.product(name: "Atomics", package: "swift-atomics"),
]),

// Development
.executableTarget(
name: "Development",
Expand Down
36 changes: 18 additions & 18 deletions Package@swift-5.9.swift
Expand Up @@ -17,54 +17,54 @@ let package = Package(
dependencies: [
// HTTP client library built on SwiftNIO
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.19.0"),

// Sugary extensions for the SwiftNIO library
.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.10.0"),
.package(url: "https://github.com/vapor/console-kit.git", from: "4.13.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"),

// 🚍 High-performance trie-node router.
.package(url: "https://github.com/vapor/routing-kit.git", from: "4.5.0"),

// Event-driven network application framework for high performance protocol servers & clients, non-blocking.
.package(url: "https://github.com/apple/swift-nio.git", from: "2.62.0"),

// Bindings to OpenSSL-compatible libraries for TLS support in SwiftNIO
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.8.0"),

// HTTP/2 support for SwiftNIO
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.28.0"),

// Useful code around SwiftNIO.
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.19.0"),

// Swift logging API
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),

// Swift metrics API
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.0.0"),

// Swift collection algorithms
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),

// WebSocket client library built on SwiftNIO
.package(url: "https://github.com/vapor/websocket-kit.git", from: "2.13.0"),

// MultipartKit, Multipart encoding and decoding
.package(url: "https://github.com/vapor/multipart-kit.git", from: "4.2.1"),

// Low-level atomic operations
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.1.0"),
],
targets: [
// C helpers
.target(name: "CVaporBcrypt"),
.target(name: "CVaporURLParser"),

// Vapor
.target(
name: "Vapor",
Expand Down Expand Up @@ -95,7 +95,7 @@ let package = Package(
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")]
),

// Development
.executableTarget(
name: "Development",
Expand All @@ -105,7 +105,7 @@ let package = Package(
resources: [.copy("Resources")],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")]
),

// Testing
.target(
name: "XCTVapor",
Expand Down
71 changes: 41 additions & 30 deletions Sources/Vapor/Application.swift
@@ -1,12 +1,13 @@
#if swift(<5.9)
import Backtrace
#endif
import ConsoleKit
import Logging
import NIOConcurrencyHelpers
import NIOCore
import Logging
import ConsoleKit
import NIOPosix

#if swift(<5.9)
import Backtrace
#endif

/// Core type representing a Vapor application.
public final class Application: Sendable {
public var environment: Environment {
Expand All @@ -17,7 +18,7 @@ public final class Application: Sendable {
self._environment.withLockedValue { $0 = newValue }
}
}

public var storage: Storage {
get {
self._storage.withLockedValue { $0 }
Expand All @@ -26,13 +27,11 @@ public final class Application: Sendable {
self._storage.withLockedValue { $0 = newValue }
}
}

public var didShutdown: Bool {
get {
self._didShutdown.withLockedValue { $0 }
}
self._didShutdown.withLockedValue { $0 }
}

public var logger: Logger {
get {
self._logger.withLockedValue { $0 }
Expand Down Expand Up @@ -73,10 +72,9 @@ public final class Application: Sendable {
}

public func lock<Key>(for key: Key.Type) -> NIOLock
where Key: LockKey
{
where Key: LockKey {
self.main.withLock {
self.storage.withLockedValue{
self.storage.withLockedValue {
$0.insertOrReturn(.init(), at: .init(Key.self))
}
}
Expand All @@ -95,12 +93,12 @@ public final class Application: Sendable {
public var sync: NIOLock {
self.locks.main
}

public enum EventLoopGroupProvider: Sendable {
case shared(EventLoopGroup)
case createNew
}

public let eventLoopGroupProvider: EventLoopGroupProvider
public let eventLoopGroup: EventLoopGroup
internal let isBooted: NIOLockedValueBox<Bool>
Expand All @@ -116,7 +114,7 @@ public final class Application: Sendable {
_ eventLoopGroupProvider: EventLoopGroupProvider = .createNew
) {
#if swift(<5.9)
Backtrace.install()
Backtrace.install()
#endif
self._environment = .init(environment)
self.eventLoopGroupProvider = eventLoopGroupProvider
Expand Down Expand Up @@ -149,7 +147,7 @@ public final class Application: Sendable {
self.commands.use(RoutesCommand(), as: "routes")
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
/// If your application is started without arguments, the default argument is used.
///
Expand All @@ -163,18 +161,31 @@ public final class Application: Sendable {
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.

/// 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 want to run your Application indefinitely, or until your code shuts the application down,
/// use `run()` instead.
public func start() throws {
try self.boot()
let command = self.commands.group()

let commands = self.commands.group()
let asyncCommands = self.asyncCommands.group()
let combinedCommands = asyncCommands.merge(
with: commands,
defaultCommand: commands.defaultCommand,
help: commands.help
)

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

public func boot() throws {
Expand All @@ -187,15 +198,15 @@ public final class Application: Sendable {
try self.lifecycle.handlers.forEach { try $0.didBoot(self) }
}
}

public func shutdown() {
assert(!self.didShutdown, "Application has already shut down")
self.logger.debug("Application shutting down")

self.logger.trace("Shutting down providers")
self.lifecycle.handlers.reversed().forEach { $0.shutdown(self) }
self.lifecycle.handlers = []

self.logger.trace("Clearing Application storage")
self.storage.shutdown()
self.storage.clear()
Expand All @@ -215,7 +226,7 @@ public final class Application: Sendable {
self._didShutdown.withLockedValue { $0 = true }
self.logger.trace("Application shutdown complete")
}

deinit {
self.logger.trace("Application deinitialized, goodbye!")
if !self.didShutdown {
Expand All @@ -225,10 +236,10 @@ public final class Application: Sendable {
}
}

public protocol LockKey { }
public protocol LockKey {}

fileprivate extension Dictionary {
mutating func insertOrReturn(_ value: @autoclosure () -> Value, at key: Key) -> Value {
extension Dictionary {
fileprivate mutating func insertOrReturn(_ value: @autoclosure () -> Value, at key: Key) -> Value {
if let existing = self[key] {
return existing
}
Expand Down
11 changes: 9 additions & 2 deletions Sources/Vapor/Core/Core.swift
Expand Up @@ -14,6 +14,11 @@ extension Application {
set { self.core.storage.commands.withLockedValue { $0 = newValue } }
}

public var asyncCommands: AsyncCommands {
get { self.core.storage.asyncCommands.withLockedValue { $0 } }
set { self.core.storage.asyncCommands.withLockedValue { $0 = newValue } }
}

/// The application thread pool. Vapor provides a thread pool with 64 threads by default.
///
/// It's possible to configure the thread pool size by overriding this value with your own thread pool.
Expand All @@ -32,15 +37,15 @@ extension Application {
self.logger.critical("Cannot replace thread pool after application has booted")
fatalError("Cannot replace thread pool after application has booted")
}

self.core.storage.threadPool.withLockedValue({
try! $0.syncShutdownGracefully()
$0 = newValue
$0.start()
})
}
}

public var fileio: NonBlockingFileIO {
.init(threadPool: self.threadPool)
}
Expand All @@ -67,6 +72,7 @@ extension Application {
final class Storage: Sendable {
let console: NIOLockedValueBox<Console>
let commands: NIOLockedValueBox<Commands>
let asyncCommands: NIOLockedValueBox<AsyncCommands>
let threadPool: NIOLockedValueBox<NIOThreadPool>
let allocator: ByteBufferAllocator
let running: Application.Running.Storage
Expand All @@ -77,6 +83,7 @@ extension Application {
var commands = Commands()
commands.use(BootCommand(), as: "boot")
self.commands = .init(commands)
self.asyncCommands = .init(AsyncCommands())
let threadPool = NIOThreadPool(numberOfThreads: System.coreCount)
threadPool.start()
self.threadPool = .init(threadPool)
Expand Down

0 comments on commit c710b8f

Please sign in to comment.