From b2fbe046c4bd135a52600c71a527094eb2c4eba1 Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Thu, 5 Apr 2018 14:47:45 -0400 Subject: [PATCH] add async section + console docs --- 3.0/docs/async/getting-started.md | 43 +++++ 3.0/docs/async/overview.md | 273 ++++++++++++++++++++++++++++ 3.0/docs/command/getting-started.md | 3 + 3.0/docs/command/overview.md | 3 + 3.0/docs/console/getting-started.md | 43 +++++ 3.0/docs/console/overview.md | 82 +++++++++ 3.0/docs/core/getting-started.md | 3 + 3.0/docs/core/overview.md | 3 + 3.0/docs/getting-started/async.md | 209 ++------------------- 3.0/docs/logging/getting-started.md | 3 + 3.0/docs/logging/overview.md | 3 + 3.0/mkdocs.yml | 17 +- 12 files changed, 494 insertions(+), 191 deletions(-) create mode 100644 3.0/docs/async/getting-started.md create mode 100644 3.0/docs/async/overview.md create mode 100644 3.0/docs/command/getting-started.md create mode 100644 3.0/docs/command/overview.md create mode 100644 3.0/docs/console/getting-started.md create mode 100644 3.0/docs/console/overview.md create mode 100644 3.0/docs/core/getting-started.md create mode 100644 3.0/docs/core/overview.md create mode 100644 3.0/docs/logging/getting-started.md create mode 100644 3.0/docs/logging/overview.md diff --git a/3.0/docs/async/getting-started.md b/3.0/docs/async/getting-started.md new file mode 100644 index 00000000..42805a25 --- /dev/null +++ b/3.0/docs/async/getting-started.md @@ -0,0 +1,43 @@ +# Getting Started with Async + +The Async module is provided as a part of Vapor Core ([vapor/core](https://github.com/vapor/core)). It is a collection of convenience APIs (mostly extensions) built on top of [Swift NIO](https://github.com/apple/swift-nio). + +!!! tip + You can read more about SwiftNIO's async types (`Future`, `Promise`, `EventLoop`, and more) in its GitHub [README](https://github.com/apple/swift-nio/blob/master/README.md) or its [API Docs](https://apple.github.io/swift-nio/docs/current/NIO/index.html). + +## Usage + +This package is included with Vapor and exported by default. You will have access to all `Async` APIs when you import `Vapor`. + +```swift +import Vapor // implies `import Async` +``` + +### Standalone + +The Async module, part of the larger Vapor Core package, can also be used on its own with any Swift project. + +To include it in your package, add the following to your `Package.swift` file. + +```swift +// swift-tools-version:4.0 +import PackageDescription + +let package = Package( + name: "Project", + dependencies: [ + ... + .package(url: "https://github.com/vapor/core.git", from: "3.0.0"), + ], + targets: [ + .target(name: "Project", dependencies: ["Async", ... ]) + ] +) +``` + +Use `import Async` to access the APIs. + +## Overview + +Continue to [Async → Overview](overview.md) for an overview of Async's features. + diff --git a/3.0/docs/async/overview.md b/3.0/docs/async/overview.md new file mode 100644 index 00000000..53feb20e --- /dev/null +++ b/3.0/docs/async/overview.md @@ -0,0 +1,273 @@ +# Async Overview + +You may have noticed some APIs in Vapor expect or return a generic `Future` type. If this is your first time hearing about futures, they might seem a little confusing at first. But don't worry, Vapor makes them easy to use. + +Promises and futures are related, but distinct types. Promises are used to _create_ futures. Most of the time, you will be working with futures returned by Vapor's APIs and you will not need to worry about creating promises. + +|type |description |mutability|methods | +|---------|-----------------------------------------------------|----------|----------------------------------------------------| +|`Future` |Reference to an object that may not be available yet.|read-only |`.map(to:_:)` `.flatMap(to:_:)` `do(_:)` `catch(_:)`| +|`Promise` |A promise to provide some object asynchronously. |read/write|`succeed(_:)` `fail(_:)` | + +Futures are an alternative to callback-based asynchronous APIs. Futures can be chained and transformed in ways that simple closures cannot, making them quite powerful. + +## Transforming + +Just like optionals in Swift, futures can be mapped and flat-mapped. These are the most common operations you will perform on futures. + +|method |signature |description| +|---------|------------------|-----| +|`map` |`to: U.Type, _: (T) -> U` |Maps a future value to a different value. +|`flatMap`|`to: U.Type, _: (T) -> Future`|Maps a future value to different _future_ value.| +|`transform` |`to: U` |Maps a future to an already available value.| + +If you look at the method signatures for `map` and `flatMap` on `Optional` and `Array`, you will see that they are very similar to the methods available on `Future`. + +### Map + +The `.map(to:_:)` method allows you to transform the future's value to another value. Because the future's value may not be available yet (it may be the result of an asynchronous task) we must provide a closure to accept the value. + +```swift +/// Assume we get a future string back from some API +let futureString: Future = ... + +/// Map the future string to an integer +let futureInt = futureString.map(to: Int.self) { string in + print(string) // The actual String + return Int(string) ?? 0 +} + +/// We now have a future integer +print(futureInt) // Future +``` + +### Flat Map + +The `.flatMap(to:_:)` method allows you to transform the future's value to another future value. It gets the name "flat" map because it is what allows you to avoid creating nested futures (e.g., `Future>`). In other words, it helps you keep your generic futures flat. + +```swift +/// Assume we get a future string back from some API +let futureString: Future = ... + +/// Assume we have created an HTTP client +let client: Client = ... + +/// Flat-map the future string to a future response +let futureResponse = futureString.flatMap(to: Response.self) { string in + return client.get(string) // Future +} + +/// We now have a future response +print(futureResponse) // Future +``` + +!!! info + If we instead used `.map(to:_:)` in the above example, we would have ended up with a `Future>`. Yikes! + +### Transform + +The `.transform(_:)` method allows you to modify a future's value, ignoring the existing value. This is especially useful for transforming the results of `Future` where the actual value of the future is not important. + +!!! tip + `Future`, sometimes called a signal, is a future whose sole purpose is to notify you of completion or failure of some async operation. + +```swift +/// Assume we get a void future back from some API +let userDidSave: Future = ... + +/// Transform the void future to an HTTP status +let futureStatus = userDidSave.transform(to: HTTPStatus.ok) +print(futureStatus) // Future +``` + +Even though we have supplied an already-available value to `transform`, this is still a _transformation_. The future will not complete until all previous futures have completed (or failed). + +### Chaining + +The great part about transformations on futures is that they can be chained. This allows you to express many conversions and subtasks easily. + +Let's modify the examples from above to see how we can take advantage of chaining. + +```swift +/// Assume we get a future string back from some API +let futureString: Future = ... + +/// Assume we have created an HTTP client +let client: Client = ... + +/// Transform the string to a url, then to a response +let futureResponse = futureString.map(to: URL.self) { string in + guard let url = URL(string: string) else { + throw Abort(.badRequest, reason: "Invalid URL string: \(string)") + } + return url +}.flatMap(to: Response.self) { url in + return client.get(url) +} + +print(futureResponse) // Future +``` + +After the initial call to map, there is a temporary `Future` created. This future is then immediately flat-mapped to a `Future` + +!!! tip + You can `throw` errors inside of map and flat-map closures. This will result in the future failing with the error thrown. + +## Future + +Let's take a look at some other, less commonly used methods on `Future`. + +### Do / Catch + +Similar to Swift's `do` / `catch` syntax, futures have a `do` and `catch` method for awaiting the future's result. + +```swift +/// Assume we get a future string back from some API +let futureString: Future = ... + +futureString.do { string in + print(string) // The actual String +}.catch { error in + print(error) // A Swift Error +} +``` + +!!! info + `.do` and `.catch` work together. If you forget `.catch`, the compiler will warn you about an unused result. Don't forget to handle the error case! + +### Always + +You can use `always` to add a callback that will be executed whether the future succeeds or fails. + +```swift +/// Assume we get a future string back from some API +let futureString: Future = ... + +futureString.always { + print("The future is complete!") +} +``` + +!!! note + You can add as many callbacks to a future as you want. + +### Wait + +You can use `.wait()` to synchronously wait for the future to be completed. Since a future may fail, this call is throwing. + +```swift +/// Assume we get a future string back from some API +let futureString: Future = ... + +/// Block until the string is ready +let string = try futureString.wait() +print(string) /// String +``` + +!!! warning + Do not use this method in route closures or controllers. Read the section about [Blocking](#blocking) for more information. + + +## Promise + +Most of the time, you will be transforming futures returned by calls to Vapor's APIs. However, at some point you may need to create a promise of your own. + +To create a promise, you will need access to an `EventLoop`. All containers in Vapor have an `eventLoop` property that you can use. Most commonly, this will be the current `Request`. + +```swift +/// Create a new promise for some string +let promiseString = req.eventLoop.newPromise(String.self) +print(promiseString) // Promise +print(promiseString.futureResult) // Future + +/// Completes the associated future +promiseString.succeed(result: "Hello") + +/// Fails the associated future +promiseString.fail(error: ...) +``` + +!!! info + A promise can only be completed once. Any subsequent completions will be ignored. + +### Thread Safety + +Promises can be completed (`succeed(result:)` / `fail(error:)`) from any thread. This is why promises require an event-loop to be initialized. Promises ensure that the completion action gets returned to its event-loop for execution. + +## Event Loop + +When your application boots, it will usually create one event loop for each core in the CPU it is running on. Each event loop has exactly one thread. If you are familiar with event loops from Node.js, the ones in Vapor are very similar. The only difference is that Vapor can run multiple event loops in one process since Swift supports multi-threading. + +Each time a client connects to your server, it will be assigned to one of the event loops. From that point on, all communication between the server and that client will happen on that same event loop (and by association, that event loop's thread). + +The event loop is responsible for keeping track of each connected client's state. If there is a request from the client waiting to be read, the event loop trigger a read notification, causing the data to be read. Once the entire request is read, any futures waiting for that request's data will be completed. + +### Worker + +Things that have access to an event loop are called `Workers`. Every container in Vapor is a worker. + +The most common containers you will interact with in Vapor are: + +- `Application` +- `Request` +- `Response` + +You can use the `.eventLoop` property on these containers to gain access to the event loop. + +```swift +print(app.eventLoop) // EventLoop +``` + +There are many methods in Vapor that require the current worker to be passed along. It will usually be labeled like `on: Worker`. If you are in a route closure or a controller, pass the current `Request` or `Response`. If you need a worker while booting your app, use the `Application`. + +### Blocking + +An absolutely critical rule is the following: + +!!! danger + Never make blocking calls directly on an event loop. + +An example of a blocking call would be something like `libc.sleep(_:)`. + +```swift +router.get("hello") { req in + /// Puts the event loop's thread to sleep. + sleep(5) + + /// Returns a simple string once the thread re-awakens. + return "Hello, world!" +} +``` + +`sleep(_:)` is a command that blocks the current thread for the number of seconds supplied. If you do blocking work directly on an event loop, the event loop will be unable to respond to any other clients assigned to it for the duration of the blocking work. In other words, if you do `sleep(5)` on an event loop, all of the other clients connected to that event loop (possibly hundreds or thousands) will be delayed for at least 5 seconds. + +Make sure to run any blocking work in the background. Use promises to notify the event loop when this work is done in a non-blocking way. + +```swift +router.get("hello") { req in + /// Create a new void promise + let promise = req.eventLoop.newPromise(Void.self) + + /// Dispatch some work to happen on a background thread + DispatchQueue.global() { + /// Puts the background thread to sleep + /// This will not affect any of the event loops + sleep(5) + + /// When the "blocking work" has completed, + /// complete the promise and its associated future. + promise.succeed() + } + + /// Wait for the future to be completed, + /// then transform the result to a simple String + return promise.futureResult.transform(to: "Hello, world!") +} +``` + +Not all blocking calls will be as obvious as `sleep(_:)`. If you are suspicious that a call you are using may be blocking, research the method itself or ask someone. Chances are if the function is doing disk or network IO and uses a synchronous API (no callbacks or futures) it is blocking. + +!!! info + If doing blocking work is a central part of your application, you should consider using a `BlockingIOThreadPool` to control the number of threads you create to do blocking work. This will help you avoid starving your event loops from CPU time while blocking work is being done. + + diff --git a/3.0/docs/command/getting-started.md b/3.0/docs/command/getting-started.md new file mode 100644 index 00000000..29e518a6 --- /dev/null +++ b/3.0/docs/command/getting-started.md @@ -0,0 +1,3 @@ +# Getting Started with Async + +Coming soon. \ No newline at end of file diff --git a/3.0/docs/command/overview.md b/3.0/docs/command/overview.md new file mode 100644 index 00000000..a92fbc66 --- /dev/null +++ b/3.0/docs/command/overview.md @@ -0,0 +1,3 @@ +# Async Overview + +Coming soon. \ No newline at end of file diff --git a/3.0/docs/console/getting-started.md b/3.0/docs/console/getting-started.md new file mode 100644 index 00000000..2fa986b5 --- /dev/null +++ b/3.0/docs/console/getting-started.md @@ -0,0 +1,43 @@ +# Getting Started with Console + +The Console module is provided as a part of Vapor's Console package ([vapor/console](https://github.com/vapor/console)). This module provides APIs for performing console I/O including things like outputting stylized text, requesting user input, and displaying activity indicators like loading bars. + +!!! tip + For an in-depth look at all of Console's APIs, check out the [Console API docs](https://api.vapor.codes/console/latest/Console/indext.html). + +## Usage + +This package is included with Vapor and exported by default. You will have access to all `Console` APIs when you import `Vapor`. + +```swift +import Vapor // implies import Console +``` + +### Standalone + +The Console module, part of the larger Vapor Console package, can also be used on its own with any Swift project. + +To include it in your package, add the following to your `Package.swift` file. + +```swift +// swift-tools-version:4.0 +import PackageDescription + +let package = Package( + name: "Project", + dependencies: [ + ... + /// 💻 APIs for creating interactive CLI tools. + .package(url: "https://github.com/vapor/console.git", from: "3.0.0"), + ], + targets: [ + .target(name: "Project", dependencies: ["Console", ... ]) + ] +) +``` + +Use `import Console` to access the APIs. + +## Overview + +Continue to [Console → Overview](overview.md) for an overview of Console's features. \ No newline at end of file diff --git a/3.0/docs/console/overview.md b/3.0/docs/console/overview.md new file mode 100644 index 00000000..163905e8 --- /dev/null +++ b/3.0/docs/console/overview.md @@ -0,0 +1,82 @@ +# Console Overview + +This guide will give you a brief introduction to the Console module, showing you how to output stylized text and request user input. + +## Terminal + +A default implementation of the [`Console`](https://api.vapor.codes/console/latest/Console/Protocols/Console.html) protocol called [`Terminal`](https://api.vapor.codes/console/latest/Console/Classes/Terminal.html) is provided for you to use. + +```swift +let terminal = Terminal() +print(terminal is Console) // true +terminal.print("Hello") +``` +The rest of this guide will assume a generic `Console`, but using `Terminal` directly will also work fine. You can use any available [`Container`](https://api.vapor.codes/service/latest/Service/Protocols/Container.html) to create a console. + +```swift +let console = try req.make(Console.self) +console.print("Hello") +``` + +## Output + +[`Console`](https://api.vapor.codes/console/latest/Console/Protocols/Console.html) provides several convenience methods for outputting strings, like `print(_:)` and `warning(_:)`. All of these methods eventually call `output(_:)` which is the most powerful output method. This method accepts [`ConsoleText`](https://api.vapor.codes/console/latest/Console/Structs/ConsoleText.html) which supports independently styled string components. + +```swift +/// Prints "Hello, world", but the word 'world' is blue. +console.output("Hello, " + "world".consoleText(color: .blue)) +``` + +You can combine as many differently styled fragments to a [`ConsoleText`](https://api.vapor.codes/console/latest/Console/Structs/ConsoleText.html) as you like. All [`Console`](https://api.vapor.codes/console/latest/Console/Protocols/Console.html) methods that output text should have an overload for accepting [`ConsoleText`](https://api.vapor.codes/console/latest/Console/Structs/ConsoleText.html). + +## Input + +[`Console`](https://api.vapor.codes/console/latest/Console/Protocols/Console.html) offers several methods for requesting input from the user, the most basic of which is `input(isSecure:)`. + +```swift +/// Accepts input from the terminal until the first newline. +let input = console.input(isSecure: false) +console.print("You wrote: \(input)") +``` + +### Ask + +Use `ask(_:)` to supply a prompt and input indicator to the user. + +```swift +/// Outputs the prompt then requests input. +let name = console.ask("What is your name?") +console.print("You said: \(name)") +``` + +The above code will output: + +```sh +What is your name? +> Vapor +You said: Vapor +``` + +### Confirm + +Use `confirm(_:)` to prompt the user for yes / no input. + +```swift +/// Prompts the user for yes / no input. +if console.confirm("Are you sure?") { + // they are sure +} else { + // don't do it! +} +``` + +The above code will output: + +```swift +Are you sure? +y/n> yes +``` + +!!! note + `confirm(_:)` will continue to prompt the user until they respond with something recognized as yes or no. + \ No newline at end of file diff --git a/3.0/docs/core/getting-started.md b/3.0/docs/core/getting-started.md new file mode 100644 index 00000000..29e518a6 --- /dev/null +++ b/3.0/docs/core/getting-started.md @@ -0,0 +1,3 @@ +# Getting Started with Async + +Coming soon. \ No newline at end of file diff --git a/3.0/docs/core/overview.md b/3.0/docs/core/overview.md new file mode 100644 index 00000000..a92fbc66 --- /dev/null +++ b/3.0/docs/core/overview.md @@ -0,0 +1,3 @@ +# Async Overview + +Coming soon. \ No newline at end of file diff --git a/3.0/docs/getting-started/async.md b/3.0/docs/getting-started/async.md index 738ecf01..7f093407 100644 --- a/3.0/docs/getting-started/async.md +++ b/3.0/docs/getting-started/async.md @@ -1,34 +1,16 @@ -# Futures +# Async You may have noticed some APIs in Vapor expect or return a generic `Future` type. If this is your first time hearing about futures, they might seem a little confusing at first. But don't worry, Vapor makes them easy to use. -!!! info - The promises, futures, and event loops that Vapor uses all come from the [Swift NIO](https://github.com/apple/swift-nio) library. This document covers the basics as well as some type-aliases and extensions that Vapor adds to make things more convenient. +This guide will give you a quick introduction to working with Async. Check out [Async → Overview](../async/overview.md) for more information. -Promises and futures are related, but distinct types. Promises are used to _create_ futures. Most of the time, you will be working with futures returned by Vapor's APIs and you will not need to worry about creating promises. +## Futures -|type |description |mutability|methods | -|---------|-----------------------------------------------------|----------|----------------------------------------------------| -|`Future` |Reference to an object that may not be available yet.|read-only |`.map(to:_:)` `.flatMap(to:_:)` `do(_:)` `catch(_:)`| -|`Promise` |A promise to provide some object asynchronously. |read/write|`succeed(_:)` `fail(_:)` | - -Futures are an alternative to callback-based asynchronous APIs. Futures can be chained and transformed in ways that simple closures cannot, making them quite powerful. - -## Transforming - -Just like optionals in Swift, futures can be mapped and flat-mapped. These are the most common operations you will perform on futures. - -|method |signature |description| -|---------|------------------|-----| -|`map` |`to: U.Type, _: (T) -> U` |Maps a future value to a different value. -|`flatMap`|`to: U.Type, _: (T) -> Future`|Maps a future value to different _future_ value.| -|`transform` |`to: U` |Maps a future to an already available value.| - -If you look at the method signatures for `map` and `flatMap` on `Optional` and `Array`, you will see that they are very similar to the methods available on `Future`. +Since `Future`s work asynchronously, we must use closures to interact with and transform their values. Just like optionals in Swift, futures can be mapped and flat-mapped. ### Map -The `.map(to:_:)` method allows you to transform the future's value to another value. Because the future's value may not be available yet (it may be the result of an asynchronous task) we must provide a closure to accept the value. +The `.map(to:_:)` method allows you to transform the future's value to another value. The closure provided will be called once the `Future`'s data becomes available. ```swift /// Assume we get a future string back from some API @@ -46,7 +28,7 @@ print(futureInt) // Future ### Flat Map -The `.flatMap(to:_:)` method allows you to transform the future's value to another future value. It gets the name "flat" map because it is what allows you to avoid creating nested futures (e.g., `Future>`). In other words, it helps you keep your generic futures flat. +The `.flatMap(to:_:)` method allows you to transform the future's value to another future value. It gets the name "flat" map because it is what allows you to avoid creating nested futures (e.g., `Future>`). In other words, it helps you keep your futures flat. ```swift /// Assume we get a future string back from some API @@ -67,23 +49,6 @@ print(futureResponse) // Future !!! info If we instead used `.map(to:_:)` in the above example, we would have ended up with a `Future>`. Yikes! -### Transform - -The `.transform(_:)` method allows you to modify a future's value, ignoring the existing value. This is especially useful for transforming the results of `Future` where the actual value of the future is not important. - -!!! tip - `Future`, sometimes called a signal, is a future whose sole purpose is to notify you of completion or failure of some async operation. - -```swift -/// Assume we get a void future back from some API -let userDidSave: Future = ... - -/// Transform the void future to an HTTP status -let futureStatus = userDidSave.transform(to: HTTPStatus.ok) -print(futureStatus) // Future -``` - -Even though we have supplied an already-available value to `transform`, this is still a _transformation_. The future will not complete until all previous futures have completed (or failed). ### Chaining @@ -115,162 +80,26 @@ After the initial call to map, there is a temporary `Future` created. This !!! tip You can `throw` errors inside of map and flat-map closures. This will result in the future failing with the error thrown. - -## Future -Let's take a look at some other, less commonly used methods on `Future`. +## Worker -### Do / Catch +You may see methods in Vapor that have an `on: Worker` parameter. These are usually methods that perform asynchronous work and require access to the `EventLoop`. -Similar to Swift's `do` / `catch` syntax, futures have a `do` and `catch` method for awaiting the future's result. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -futureString.do { string in - print(string) // The actual String -}.catch { error in - print(error) // A Swift Error -} -``` - -!!! info - `.do` and `.catch` work together. If you forget `.catch`, the compiler will warn you about an unused result. Don't forget to handle the error case! - -### Always - -You can use `always` to add a callback that will be executed whether the future succeeds or fails. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -futureString.always { - print("The future is complete!") -} -``` - -!!! note - You can add as many callbacks to a future as you want. - -### Wait - -You can use `.wait()` to synchronously wait for the future to be completed. Since a future may fail, this call is throwing. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -/// Block until the string is ready -let string = try futureString.wait() -print(string) /// String -``` - -!!! warning - Do not use this method in route closures or controllers. Read the section about [Blocking](#blocking) for more information. - - -## Promise - -Most of the time, you will be transforming futures returned by calls to Vapor's APIs. However, at some point you may need to create a promise of your own. - -To create a promise, you will need access to an `EventLoop`. All containers in Vapor have an `eventLoop` property that you can use. Most commonly, this will be the current `Request`. - -```swift -/// Create a new promise for some string -let promiseString = req.eventLoop.newPromise(String.self) -print(promiseString) // Promise -print(promiseString.futureResult) // Future - -/// Completes the associated future -promiseString.succeed(result: "Hello") - -/// Fails the associated future -promiseString.fail(error: ...) -``` - -!!! info - A promise can only be completed once. Any subsequent completions will be ignored. - -### Thread Safety - -Promises can be completed (`succeed(result:)` / `fail(error:)`) from any thread. This is why promises require an event-loop to be initialized. Promises ensure that the completion action gets returned to its event-loop for execution. - -## Event Loop - -When your application boots, it will usually create one event loop for each core in the CPU it is running on. Each event loop has exactly one thread. If you are familiar with event loops from Node.js, the ones in Vapor are very similar. The only difference is that Vapor can run multiple event loops in one process since Swift supports multi-threading. - -Each time a client connects to your server, it will be assigned to one of the event loops. From that point on, all communication between the server and that client will happen on that same event loop (and by association, that event loop's thread). - -The event loop is responsible for keeping track of each connected client's state. If there is a request from the client waiting to be read, the event loop trigger a read notification, causing the data to be read. Once the entire request is read, any futures waiting for that request's data will be completed. - -### Worker - -Things that have access to an event loop are called `Workers`. Every container in Vapor is a worker. - -The most common containers you will interact with in Vapor are: +The most common `Worker`s you will interact with in Vapor are: - `Application` - `Request` - `Response` -You can use the `.eventLoop` property on these containers to gain access to the event loop. - ```swift -print(app.eventLoop) // EventLoop -``` +/// Assume we have a Request and some ViewRenderer +let req: Request = ... +let view: ViewRenderer = ... -There are many methods in Vapor that require the current worker to be passed along. It will usually be labeled like `on: Worker`. If you are in a route closure or a controller, pass the current `Request` or `Response`. If you need a worker while booting your app, use the `Application`. - -### Blocking - -An absolutely critical rule is the following: - -!!! danger - Never make blocking calls directly on an event loop. - -An example of a blocking call would be something like `libc.sleep(_:)`. - -```swift -router.get("hello") { req in - /// Puts the event loop's thread to sleep. - sleep(5) - - /// Returns a simple string once the thread re-awakens. - return "Hello, world!" -} -``` - -`sleep(_:)` is a command that blocks the current thread for the number of seconds supplied. If you do blocking work directly on an event loop, the event loop will be unable to respond to any other clients assigned to it for the duration of the blocking work. In other words, if you do `sleep(5)` on an event loop, all of the other clients connected to that event loop (possibly hundreds or thousands) will be delayed for at least 5 seconds. - -Make sure to run any blocking work in the background. Use promises to notify the event loop when this work is done in a non-blocking way. - -```swift -router.get("hello") { req in - /// Create a new void promise - let promise = req.eventLoop.newPromise(Void.self) - - /// Dispatch some work to happen on a background thread - DispatchQueue.global() { - /// Puts the background thread to sleep - /// This will not affect any of the event loops - sleep(5) - - /// When the "blocking work" has completed, - /// complete the promise and its associated future. - promise.succeed() - } - - /// Wait for the future to be completed, - /// then transform the result to a simple String - return promise.futureResult.transform(to: "Hello, world!") -} -``` - -Not all blocking calls will be as obvious as `sleep(_:)`. If you are suspicious that a call you are using may be blocking, research the method itself or ask someone. Chances are if the function is doing disk or network IO and uses a synchronous API (no callbacks or futures) it is blocking. - -!!! info - If doing blocking work is a central part of your application, you should consider using a `BlockingIOThreadPool` to control the number of threads you create to do blocking work. This will help you avoid starving your event loops from CPU time while blocking work is being done. - - +/// Render the view, using the Request as a worker. +/// This ensures the async work happens on the correct event loop. +/// +/// This assumes the signature is: +/// func render(_: String, on: Worker) +view.render("home.html", on: req) +``` \ No newline at end of file diff --git a/3.0/docs/logging/getting-started.md b/3.0/docs/logging/getting-started.md new file mode 100644 index 00000000..29e518a6 --- /dev/null +++ b/3.0/docs/logging/getting-started.md @@ -0,0 +1,3 @@ +# Getting Started with Async + +Coming soon. \ No newline at end of file diff --git a/3.0/docs/logging/overview.md b/3.0/docs/logging/overview.md new file mode 100644 index 00000000..a92fbc66 --- /dev/null +++ b/3.0/docs/logging/overview.md @@ -0,0 +1,3 @@ +# Async Overview + +Coming soon. \ No newline at end of file diff --git a/3.0/mkdocs.yml b/3.0/mkdocs.yml index 4a5d0cfa..ef52540c 100644 --- a/3.0/mkdocs.yml +++ b/3.0/mkdocs.yml @@ -6,7 +6,7 @@ pages: - 'Install': - 'macOS': 'install/macos.md' - 'Ubuntu': 'install/ubuntu.md' -- 'Getting started': +- 'Getting Started': - 'Hello, world': 'getting-started/hello-world.md' - 'Toolbox': 'getting-started/toolbox.md' - 'SPM': 'getting-started/spm.md' @@ -19,6 +19,18 @@ pages: - 'Async': 'getting-started/async.md' - 'Services': 'getting-started/services.md' - 'Deployment': 'getting-started/cloud.md' +- 'Async': + - 'Getting Started': 'async/getting-started.md' + - 'Overview': 'async/overview.md' +- 'Console': + - 'Getting Started': 'console/getting-started.md' + - 'Overview': 'console/overview.md' +- 'Command': + - 'Getting Started': 'command/getting-started.md' + - 'Overview': 'command/overview.md' +- 'Core': + - 'Getting Started': 'core/getting-started.md' + - 'Overview': 'core/overview.md' - 'Crypto': - 'Getting Started': 'crypto/getting-started.md' - 'Digests': 'crypto/digests.md' @@ -42,6 +54,9 @@ pages: - 'Getting Started': 'leaf/getting-started.md' - 'Basics': 'leaf/basics.md' - 'Custom tags': 'leaf/custom-tags.md' +- 'Logging': + - 'Getting Started': 'logging/getting-started.md' + - 'Overview': 'logging/overview.md' - 'MySQL': - 'Getting Started': 'mysql/getting-started.md' - 'Fluent MySQL': 'mysql/fluent.md'