├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets └── images │ ├── logo_fullsize.png │ └── logo_fullsize_transparent.png ├── examples ├── async-h1-client.rs ├── async-h1-server.rs ├── certificate.pem ├── chat-client.rs ├── chat-server.rs ├── ctrl-c.rs ├── get-request.rs ├── hyper-client.rs ├── hyper-server.rs ├── identity.pfx ├── linux-inotify.rs ├── linux-timerfd.rs ├── simple-client.rs ├── simple-server.rs ├── tcp-client.rs ├── tcp-server.rs ├── tls-client.rs ├── tls-server.rs ├── unix-signal.rs ├── web-crawler.rs ├── websocket-client.rs ├── websocket-server.rs └── windows-uds.rs └── src ├── lib.rs └── spawn.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | commit-message: 8 | prefix: '' 9 | labels: [] 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - master 11 | schedule: 12 | - cron: '0 2 * * 0' 13 | 14 | env: 15 | CARGO_INCREMENTAL: 0 16 | CARGO_NET_GIT_FETCH_WITH_CLI: true 17 | CARGO_NET_RETRY: 10 18 | CARGO_TERM_COLOR: always 19 | RUST_BACKTRACE: 1 20 | RUSTFLAGS: -D warnings 21 | RUSTDOCFLAGS: -D warnings 22 | RUSTUP_MAX_RETRIES: 10 23 | 24 | defaults: 25 | run: 26 | shell: bash 27 | 28 | jobs: 29 | fmt: 30 | uses: smol-rs/.github/.github/workflows/fmt.yml@main 31 | security_audit: 32 | uses: smol-rs/.github/.github/workflows/security_audit.yml@main 33 | permissions: 34 | checks: write 35 | contents: read 36 | issues: write 37 | secrets: inherit 38 | 39 | test: 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | os: [ubuntu-latest, windows-latest, macos-latest] 45 | rust: [nightly, beta, stable] 46 | steps: 47 | - uses: actions/checkout@v4 48 | - name: Install Rust 49 | # --no-self-update is necessary because the windows environment cannot self-update rustup.exe. 50 | run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} 51 | - run: cargo build --all --all-features --all-targets 52 | - name: Run cargo check (without dev-dependencies to catch missing feature flags) 53 | if: startsWith(matrix.rust, 'nightly') 54 | run: cargo check -Z features=dev_dep 55 | - run: cargo test 56 | 57 | msrv: 58 | runs-on: ubuntu-latest 59 | strategy: 60 | matrix: 61 | # When updating this, the reminder to update the minimum supported 62 | # Rust version in Cargo.toml and README.md. 63 | rust: ['1.63'] 64 | steps: 65 | - uses: actions/checkout@v4 66 | - name: Install Rust 67 | run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 68 | - run: cargo build 69 | 70 | clippy: 71 | runs-on: ubuntu-latest 72 | steps: 73 | - uses: actions/checkout@v4 74 | - name: Install Rust 75 | run: rustup update stable 76 | - run: cargo clippy --all-features --all-targets 77 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - v[0-9]+.* 10 | 11 | jobs: 12 | create-release: 13 | if: github.repository_owner == 'smol-rs' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: taiki-e/create-gh-release-action@v1 18 | with: 19 | changelog: CHANGELOG.md 20 | branch: master 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 2.0.2 2 | 3 | - Update docs to mention `smol-macros`. (#319) 4 | 5 | # Version 2.0.1 6 | 7 | - Add a mention to the documentation of `smol::spawn` that tasks spawned with 8 | this function don't have their destructors called when the program ends. (#312) 9 | 10 | # Version 2.0.0 11 | 12 | - **Breaking:** Bump subcrates to their newest major versions. (#277, #280, #281, #282, #283) 13 | - Run the `async-process` driver on the executor. (#284) 14 | 15 | # Version 1.3.0 16 | 17 | - Remove the dependency on the `once_cell` crate to restore the MSRV. (#241) 18 | 19 | # Version 1.2.5 20 | 21 | - Bump version for docs.rs to pick up latest dependencies. 22 | 23 | # Version 1.2.4 24 | 25 | - Update dependencies. 26 | 27 | # Version 1.2.3 28 | 29 | - Bump version for docs.rs to pick up latest dependencies. 30 | 31 | # Version 1.2.2 32 | 33 | - Bump version for docs.rs to pick up latest dependencies. 34 | 35 | # Version 1.2.1 36 | 37 | - Temporarily downgrade `async-executor`. 38 | 39 | # Version 1.2.0 40 | 41 | - Update all dependencies. 42 | 43 | # Version 1.1.0 44 | 45 | - Update `async-executor`. 46 | 47 | # Version 1.0.1 48 | 49 | - Update dependencies. 50 | 51 | # Version 1.0.0 52 | 53 | - Stabilize. 54 | 55 | # Version 0.4.2 56 | 57 | - Update dependencies. 58 | 59 | # Version 0.4.1 60 | 61 | - Bring back `SMOL_THREADS`. 62 | 63 | # Version 0.4.0 64 | 65 | - Add `process`, `fs`, `net`, `lock`, `channel` modules. 66 | - Update all dependencies 67 | - Remove `smol::run()`. 68 | 69 | # Version 0.3.3 70 | 71 | - Add `block_on()`. 72 | - Use `SMOL_THREADS` environment variable. 73 | 74 | # Version 0.3.2 75 | 76 | - Reexport `FutureExt`. 77 | 78 | # Version 0.3.1 79 | 80 | - Fix some typos in docs. 81 | 82 | # Version 0.3.0 83 | 84 | - Reexport `futures-lite`, `blocking`, `async-executor`. 85 | - Re-introduce `smol::run()`. 86 | 87 | # Version 0.2.0 88 | 89 | - Split `smol` into `async-io`, `blocking`, and `multitask`. 90 | - Big breaking change - there is now only one type `Task`. 91 | 92 | # Version 0.1.18 93 | 94 | - Support Rust 1.39.0 95 | 96 | # Version 0.1.17 97 | 98 | - Support more platforms by changing `AtomicU64` to `AtomicUsize`. 99 | - Remove `IoEvent` and simplify reactor notification. 100 | 101 | # Version 0.1.16 102 | 103 | - Add `Async::readable()` and `Async::writable()`. 104 | 105 | # Version 0.1.15 106 | 107 | - Fix wakeups lost inside the executor. 108 | - Fix a fairness issue in the executor. 109 | 110 | # Version 0.1.14 111 | 112 | - Clear the flag after every call to `react()`. 113 | 114 | # Version 0.1.13 115 | 116 | - Fix deadlocks caused by lost wakeups. 117 | - Refactor the executor. 118 | 119 | # Version 0.1.12 120 | 121 | - Fix a bug in `Async::::recv()`. 122 | 123 | # Version 0.1.11 124 | 125 | - Update `wepoll-binding`. 126 | - Reduce dependencies. 127 | - Replace `nix` with `libc`. 128 | - Set minimum required `tokio` version to 0.2. 129 | 130 | # Version 0.1.10 131 | 132 | - Fix incorrectly reported error kind when connecting fails. 133 | 134 | # Version 0.1.9 135 | 136 | - Switch to oneshot-style notifications on all platforms. 137 | - Fix a bug that caused 100% CPU usage on Windows. 138 | - Deprecate `Async::with()` and `Async::with_mut()`. 139 | - Add `Async::read_with()`, `Async::read_with_mut()`, 140 | `Async::write_with()`, and `Async::write_with_mut()`. 141 | - Fix a bug where eventfd was not closed. 142 | 143 | # Version 0.1.8 144 | 145 | - Revert the use of `blocking` crate. 146 | 147 | # Version 0.1.7 148 | 149 | - Update `blocking` to `0.4.2`. 150 | - Make `Task::blocking()` work without `run()`. 151 | 152 | # Version 0.1.6 153 | 154 | - Fix a deadlock by always re-registering `IoEvent`. 155 | 156 | # Version 0.1.5 157 | 158 | - Use `blocking` crate for blocking I/O. 159 | - Fix a re-registration bug when in oneshot mode. 160 | - Use eventfd on Linux. 161 | - More tests. 162 | - Fix timeout rounding error in epoll/wepoll. 163 | 164 | # Version 0.1.4 165 | 166 | - Fix a bug in UDS async connect 167 | 168 | # Version 0.1.3 169 | 170 | - Fix the writability check in async connect 171 | - More comments and documentation 172 | - Better security advice on certificates 173 | 174 | # Version 0.1.2 175 | 176 | - Improved internal docs, fixed typos, and more comments 177 | 178 | # Version 0.1.1 179 | 180 | - Upgrade dependencies 181 | 182 | # Version 0.1.0 183 | 184 | - Initial release 185 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [stjepang@gmail.com](mailto:stjepang@gmail.com). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "smol" 3 | # When publishing a new version: 4 | # - Update CHANGELOG.md 5 | # - Create "v2.x.y" git tag 6 | version = "2.0.2" 7 | authors = ["Stjepan Glavina "] 8 | edition = "2018" 9 | rust-version = "1.63" 10 | description = "A small and fast async runtime" 11 | license = "Apache-2.0 OR MIT" 12 | repository = "https://github.com/smol-rs/smol" 13 | keywords = ["async", "await", "future", "io", "networking"] 14 | categories = ["asynchronous", "concurrency", "network-programming"] 15 | exclude = ["/.*"] 16 | 17 | [dependencies] 18 | async-channel = "2.0.0" 19 | async-executor = "1.5.0" 20 | async-fs = "2.0.0" 21 | async-io = "2.1.0" 22 | async-lock = "3.0.0" 23 | async-net = "2.0.0" 24 | blocking = "1.3.0" 25 | futures-lite = "2.0.0" 26 | 27 | [target.'cfg(not(target_os = "espidf"))'.dependencies] 28 | async-process = "2.0.0" 29 | 30 | [dev-dependencies] 31 | anyhow = "1" 32 | async-dup = "1" 33 | async-h1 = "2" 34 | async-native-tls = "0.5" 35 | async-tungstenite = { version = "0.29", features = ["async-native-tls"] } 36 | ctrlc = "3" 37 | doc-comment = "0.3" 38 | futures = "0.3" 39 | http = "1.1" 40 | http-body-util = "0.1.0" 41 | http-types = "2" 42 | hyper = { version = "1.0", default-features = false, features = ["client", "http1", "server"] } 43 | macro_rules_attribute = "0.2.0" 44 | native-tls = "0.2" 45 | scraper = "0.23" 46 | signal-hook = "0.3" 47 | smol-hyper = "0.1.0" 48 | smol-macros = "0.1.0" 49 | surf = { version = "2", default-features = false, features = ["h1-client"] } 50 | tempfile = "3" 51 | tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] } 52 | url = "2" 53 | 54 | [target.'cfg(target_os = "linux")'.dev-dependencies] 55 | inotify = { version = "0.11", default-features = false } 56 | rustix = "1.0" 57 | timerfd = "1" 58 | 59 | [target.'cfg(windows)'.dev-dependencies] 60 | uds_windows = "1" 61 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smol 2 | 3 | [![Build](https://github.com/smol-rs/smol/actions/workflows/ci.yml/badge.svg)]( 4 | https://github.com/smol-rs/smol/actions) 5 | [![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)]( 6 | https://github.com/smol-rs/smol) 7 | [![Cargo](https://img.shields.io/crates/v/smol.svg)]( 8 | https://crates.io/crates/smol) 9 | [![Documentation](https://docs.rs/smol/badge.svg)]( 10 | https://docs.rs/smol) 11 | [![Chat](https://img.shields.io/matrix/smol-rs%3Amatrix.org)]( 12 | https://matrix.to/#/#smol-rs:matrix.org) 13 | 14 | kitty 15 | 16 | A small and fast async runtime. 17 | 18 | This crate simply re-exports other smaller async crates (see the source). 19 | 20 | To use tokio-based libraries with smol, apply the [`async-compat`] adapter to futures and I/O 21 | types. 22 | 23 | See the [`smol-macros`] crate if you want a no proc-macro, fast compiling, easy-to-use 24 | async main and/or multi-threaded Executor setup out of the box. 25 | 26 | ## Examples 27 | 28 | Connect to an HTTP website, make a GET request, and pipe the response to the standard output: 29 | 30 | ```rust,no_run 31 | use smol::{io, net, prelude::*, Unblock}; 32 | 33 | fn main() -> io::Result<()> { 34 | smol::block_on(async { 35 | let mut stream = net::TcpStream::connect("example.com:80").await?; 36 | let req = b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"; 37 | stream.write_all(req).await?; 38 | 39 | let mut stdout = Unblock::new(std::io::stdout()); 40 | io::copy(stream, &mut stdout).await?; 41 | Ok(()) 42 | }) 43 | } 44 | ``` 45 | 46 | There's a lot more in the [examples] directory. 47 | 48 | [`async-compat`]: https://docs.rs/async-compat/latest/async_compat/ 49 | [`smol-macros`]: https://docs.rs/smol-macros/latest/smol_macros/ 50 | [examples]: https://github.com/smol-rs/smol/tree/master/examples 51 | 52 | ## Subcrates 53 | 54 | - [async-channel] - Multi-producer multi-consumer channels 55 | - [async-executor] - Composable async executors 56 | - [async-fs] - Async filesystem primitives 57 | - [async-io] - Async adapter for I/O types, also timers 58 | - [async-lock] - Async locks (barrier, mutex, reader-writer lock, semaphore) 59 | - [async-net] - Async networking primitives (TCP/UDP/Unix) 60 | - [async-process] - Async interface for working with processes 61 | - [async-task] - Task abstraction for building executors 62 | - [blocking] - A thread pool for blocking I/O 63 | - [futures-lite] - A lighter fork of [futures] 64 | - [polling] - Portable interface to epoll, kqueue, event ports, and wepoll 65 | 66 | [async-io]: https://github.com/smol-rs/async-io 67 | [polling]: https://github.com/smol-rs/polling 68 | [nb-connect]: https://github.com/smol-rs/nb-connect 69 | [async-executor]: https://github.com/smol-rs/async-executor 70 | [async-task]: https://github.com/smol-rs/async-task 71 | [blocking]: https://github.com/smol-rs/blocking 72 | [futures-lite]: https://github.com/smol-rs/futures-lite 73 | [smol]: https://github.com/smol-rs/smol 74 | [async-net]: https://github.com/smol-rs/async-net 75 | [async-process]: https://github.com/smol-rs/async-process 76 | [async-fs]: https://github.com/smol-rs/async-fs 77 | [async-channel]: https://github.com/smol-rs/async-channel 78 | [concurrent-queue]: https://github.com/smol-rs/concurrent-queue 79 | [event-listener]: https://github.com/smol-rs/event-listener 80 | [async-lock]: https://github.com/smol-rs/async-lock 81 | [fastrand]: https://github.com/smol-rs/fastrand 82 | [futures]: https://github.com/rust-lang/futures-rs 83 | 84 | ## TLS certificate 85 | 86 | Some code examples are using TLS for authentication. The repository 87 | contains a self-signed certificate usable for testing, but it should **not** 88 | be used for real-world scenarios. Browsers and tools like curl will 89 | show this certificate as insecure. 90 | 91 | In browsers, accept the security prompt or use `curl -k` on the 92 | command line to bypass security warnings. 93 | 94 | The certificate file was generated using 95 | [minica](https://github.com/jsha/minica) and 96 | [openssl](https://www.openssl.org/): 97 | 98 | ```text 99 | minica --domains localhost -ip-addresses 127.0.0.1 -ca-cert certificate.pem 100 | openssl pkcs12 -export -out identity.pfx -inkey localhost/key.pem -in localhost/cert.pem 101 | ``` 102 | 103 | Another useful tool for making certificates is [mkcert]. 104 | 105 | [mkcert]: https://github.com/FiloSottile/mkcert 106 | 107 | ## MSRV Policy 108 | 109 | The Minimum Supported Rust Version (MSRV) of this crate is **1.63**. As a **tentative** policy, the MSRV will not advance past the [current Rust version provided by Debian Stable](https://packages.debian.org/stable/rust/rustc). At the time of writing, this version of Rust is *1.63*. However, the MSRV may be advanced further in the event of a major ecosystem shift or a security vulnerability. 110 | 111 | ## License 112 | 113 | Licensed under either of 114 | 115 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 116 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 117 | 118 | at your option. 119 | 120 | #### Contribution 121 | 122 | Unless you explicitly state otherwise, any contribution intentionally submitted 123 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 124 | dual licensed as above, without any additional terms or conditions. 125 | -------------------------------------------------------------------------------- /assets/images/logo_fullsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smol-rs/smol/d9d933368a858df4ad968fe2bc36213aced06e8e/assets/images/logo_fullsize.png -------------------------------------------------------------------------------- /assets/images/logo_fullsize_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smol-rs/smol/d9d933368a858df4ad968fe2bc36213aced06e8e/assets/images/logo_fullsize_transparent.png -------------------------------------------------------------------------------- /examples/async-h1-client.rs: -------------------------------------------------------------------------------- 1 | //! An HTTP+TLS client based on `async-h1` and `async-native-tls`. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example async-h1-client 7 | //! ``` 8 | 9 | use std::net::{TcpStream, ToSocketAddrs}; 10 | 11 | use anyhow::{bail, Context as _, Error, Result}; 12 | use http_types::{Method, Request, Response}; 13 | use smol::{prelude::*, Async}; 14 | use url::Url; 15 | 16 | /// Sends a request and fetches the response. 17 | async fn fetch(req: Request) -> Result { 18 | // Figure out the host and the port. 19 | let host = req.url().host().context("cannot parse host")?.to_string(); 20 | let port = req 21 | .url() 22 | .port_or_known_default() 23 | .context("cannot guess port")?; 24 | 25 | // Connect to the host. 26 | let socket_addr = { 27 | let host = host.clone(); 28 | smol::unblock(move || (host.as_str(), port).to_socket_addrs()) 29 | .await? 30 | .next() 31 | .context("cannot resolve address")? 32 | }; 33 | let stream = Async::::connect(socket_addr).await?; 34 | 35 | // Send the request and wait for the response. 36 | let resp = match req.url().scheme() { 37 | "http" => async_h1::connect(stream, req).await.map_err(Error::msg)?, 38 | "https" => { 39 | // In case of HTTPS, establish a secure TLS connection first. 40 | let stream = async_native_tls::connect(&host, stream).await?; 41 | async_h1::connect(stream, req).await.map_err(Error::msg)? 42 | } 43 | scheme => bail!("unsupported scheme: {}", scheme), 44 | }; 45 | Ok(resp) 46 | } 47 | 48 | fn main() -> Result<()> { 49 | smol::block_on(async { 50 | // Create a request. 51 | let addr = "https://www.rust-lang.org"; 52 | let req = Request::new(Method::Get, Url::parse(addr)?); 53 | 54 | // Fetch the response. 55 | let mut resp = fetch(req).await?; 56 | println!("{:#?}", resp); 57 | 58 | // Read the message body. 59 | let mut body = Vec::new(); 60 | resp.read_to_end(&mut body).await?; 61 | println!("{}", String::from_utf8_lossy(&body)); 62 | 63 | Ok(()) 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /examples/async-h1-server.rs: -------------------------------------------------------------------------------- 1 | //! An HTTP+TLS server based on `async-h1` and `async-native-tls`. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example async-h1-server 7 | //! ``` 8 | //! 9 | //! Open in the browser any of these addresses: 10 | //! 11 | //! - http://localhost:8000/ 12 | //! - https://localhost:8001/ (accept the security prompt in the browser) 13 | //! 14 | //! Refer to `README.md` to see how to the TLS certificate was generated. 15 | 16 | use std::net::TcpListener; 17 | 18 | use anyhow::Result; 19 | use async_native_tls::{Identity, TlsAcceptor}; 20 | use http_types::{Request, Response, StatusCode}; 21 | use smol::{future, Async}; 22 | 23 | /// Serves a request and returns a response. 24 | async fn serve(req: Request) -> http_types::Result { 25 | println!("Serving {}", req.url()); 26 | 27 | let mut res = Response::new(StatusCode::Ok); 28 | res.insert_header("Content-Type", "text/plain"); 29 | res.set_body("Hello from async-h1!"); 30 | Ok(res) 31 | } 32 | 33 | /// Listens for incoming connections and serves them. 34 | async fn listen(listener: Async, tls: Option) -> Result<()> { 35 | // Format the full host address. 36 | let host = match &tls { 37 | None => format!("http://{}", listener.get_ref().local_addr()?), 38 | Some(_) => format!("https://{}", listener.get_ref().local_addr()?), 39 | }; 40 | println!("Listening on {}", host); 41 | 42 | loop { 43 | // Accept the next connection. 44 | let (stream, _) = listener.accept().await?; 45 | 46 | // Spawn a background task serving this connection. 47 | let task = match &tls { 48 | None => { 49 | let stream = async_dup::Arc::new(stream); 50 | smol::spawn(async move { 51 | if let Err(err) = async_h1::accept(stream, serve).await { 52 | println!("Connection error: {:#?}", err); 53 | } 54 | }) 55 | } 56 | Some(tls) => { 57 | // In case of HTTPS, establish a secure TLS connection first. 58 | match tls.accept(stream).await { 59 | Ok(stream) => { 60 | let stream = async_dup::Arc::new(async_dup::Mutex::new(stream)); 61 | smol::spawn(async move { 62 | if let Err(err) = async_h1::accept(stream, serve).await { 63 | println!("Connection error: {:#?}", err); 64 | } 65 | }) 66 | } 67 | Err(err) => { 68 | println!("Failed to establish secure TLS connection: {:#?}", err); 69 | continue; 70 | } 71 | } 72 | } 73 | }; 74 | 75 | // Detach the task to let it run in the background. 76 | task.detach(); 77 | } 78 | } 79 | 80 | fn main() -> Result<()> { 81 | // Initialize TLS with the local certificate, private key, and password. 82 | let identity = Identity::from_pkcs12(include_bytes!("identity.pfx"), "password")?; 83 | let tls = TlsAcceptor::from(native_tls::TlsAcceptor::new(identity)?); 84 | 85 | // Start HTTP and HTTPS servers. 86 | smol::block_on(async { 87 | let http = listen(Async::::bind(([127, 0, 0, 1], 8000))?, None); 88 | let https = listen( 89 | Async::::bind(([127, 0, 0, 1], 8001))?, 90 | Some(tls), 91 | ); 92 | future::try_zip(http, https).await?; 93 | Ok(()) 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /examples/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDSzCCAjOgAwIBAgIIHBgnI8QV+zIwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE 3 | AxMVbWluaWNhIHJvb3QgY2EgMWMxODI3MCAXDTIwMDMyNjE3Mjk1M1oYDzIxMjAw 4 | MzI2MTcyOTUzWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAxYzE4MjcwggEi 5 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5CGrfOjgWhDC67fUL5S/GSYC4 6 | 8jP+/NLsxY8bbmohQv9TBOl3Pxv3w5uVXHBhpwigaOyRqvF3+U/YoZgT99QkV+5c 7 | XjYjdxgmTEl35eZfT7kCr6PjxLuVOIYdMEYVe+7JgkIMZjEMkRq3giBJxIQ86FWi 8 | KhCyg+5vMZ0ZYbfiv+yyfS41JebgJP1WbuR/boT3X90EuyJ3U2F58lOZadXdqJ0T 9 | iTLXyZzy7XKFP+3/dhB3NO5kfHhuP6sCHc6ORfkbYUE+rxuwKgwtVdYoBEI9mZMo 10 | bZKkyUN4xOzupYKFpKXRBc0UiQKsl+RQ6VCrOM4sc4rWoWHLvj9esP4rbhu7AgMB 11 | AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr 12 | BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTEJVhl2F/WAKdO 13 | Br26/llSB4kYSDAfBgNVHSMEGDAWgBTEJVhl2F/WAKdOBr26/llSB4kYSDANBgkq 14 | hkiG9w0BAQsFAAOCAQEAkvxlm+mhMn/UBV06jxTLNFan2ttQ79ms7T7EkbQWW55f 15 | R3ytzumfR52np4u04K0L6xHTE5vLuNiebq5s8IU7GiWcJWNkCxcJejzgAukFgsbb 16 | Ffqg8wBS/tbCaw32btk/lFrJ74t8Q13QDQVSRO9S8S4bHtiSluyV65oPKE2hXbiS 17 | euISC+9Yf2Eb4xCqIIb2/hCUq6DF9/tkAffapjbrIKQQjrMqvX6734X7g2N7cwvD 18 | Lt7U9YI0CuWHu/L+cpo7+YrBMRgM8tECip61XMQ6oBgJt3ZNMnrYiMkwCeWstKFD 19 | tDQJZyAJHE4wbInYqi78rQRMo1eoAfdFcgdS0fyDrA== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /examples/chat-client.rs: -------------------------------------------------------------------------------- 1 | //! A TCP chat client. 2 | //! 3 | //! First start a server: 4 | //! 5 | //! ``` 6 | //! cargo run --example chat-server 7 | //! ``` 8 | //! 9 | //! Then start clients: 10 | //! 11 | //! ``` 12 | //! cargo run --example chat-client 13 | //! ``` 14 | 15 | use std::net::TcpStream; 16 | 17 | use smol::{future, io, Async, Unblock}; 18 | 19 | fn main() -> io::Result<()> { 20 | smol::block_on(async { 21 | // Connect to the server and create async stdin and stdout. 22 | let stream = Async::::connect(([127, 0, 0, 1], 6000)).await?; 23 | let stdin = Unblock::new(std::io::stdin()); 24 | let mut stdout = Unblock::new(std::io::stdout()); 25 | 26 | // Intro messages. 27 | println!("Connected to {}", stream.get_ref().peer_addr()?); 28 | println!("My nickname: {}", stream.get_ref().local_addr()?); 29 | println!("Type a message and hit enter!\n"); 30 | 31 | let reader = &stream; 32 | let mut writer = &stream; 33 | 34 | // Wait until the standard input is closed or the connection is closed. 35 | future::race( 36 | async { 37 | let res = io::copy(stdin, &mut writer).await; 38 | println!("Quit!"); 39 | res 40 | }, 41 | async { 42 | let res = io::copy(reader, &mut stdout).await; 43 | println!("Server disconnected!"); 44 | res 45 | }, 46 | ) 47 | .await?; 48 | 49 | Ok(()) 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /examples/chat-server.rs: -------------------------------------------------------------------------------- 1 | //! A TCP chat server. 2 | //! 3 | //! First start a server: 4 | //! 5 | //! ``` 6 | //! cargo run --example chat-server 7 | //! ``` 8 | //! 9 | //! Then start clients: 10 | //! 11 | //! ``` 12 | //! cargo run --example chat-client 13 | //! ``` 14 | 15 | use std::collections::HashMap; 16 | use std::net::{SocketAddr, TcpListener, TcpStream}; 17 | 18 | use async_channel::{bounded, Receiver, Sender}; 19 | use async_dup::Arc; 20 | use smol::{io, prelude::*, Async}; 21 | 22 | /// An event on the chat server. 23 | enum Event { 24 | /// A client has joined. 25 | Join(SocketAddr, Arc>), 26 | 27 | /// A client has left. 28 | Leave(SocketAddr), 29 | 30 | /// A client sent a message. 31 | Message(SocketAddr, String), 32 | } 33 | 34 | /// Dispatches events to clients. 35 | async fn dispatch(receiver: Receiver) -> io::Result<()> { 36 | // Currently active clients. 37 | let mut map = HashMap::>>::new(); 38 | 39 | // Receive incoming events. 40 | while let Ok(event) = receiver.recv().await { 41 | // Process the event and format a message to send to clients. 42 | let output = match event { 43 | Event::Join(addr, stream) => { 44 | map.insert(addr, stream); 45 | format!("{} has joined\n", addr) 46 | } 47 | Event::Leave(addr) => { 48 | map.remove(&addr); 49 | format!("{} has left\n", addr) 50 | } 51 | Event::Message(addr, msg) => format!("{} says: {}\n", addr, msg), 52 | }; 53 | 54 | // Display the event in the server process. 55 | print!("{}", output); 56 | 57 | // Send the event to all active clients. 58 | for stream in map.values_mut() { 59 | // Ignore errors because the client might disconnect at any point. 60 | stream.write_all(output.as_bytes()).await.ok(); 61 | } 62 | } 63 | Ok(()) 64 | } 65 | 66 | /// Reads messages from the client and forwards them to the dispatcher task. 67 | async fn read_messages(sender: Sender, client: Arc>) -> io::Result<()> { 68 | let addr = client.get_ref().peer_addr()?; 69 | let mut lines = io::BufReader::new(client).lines(); 70 | 71 | while let Some(line) = lines.next().await { 72 | let line = line?; 73 | sender.send(Event::Message(addr, line)).await.ok(); 74 | } 75 | Ok(()) 76 | } 77 | 78 | fn main() -> io::Result<()> { 79 | smol::block_on(async { 80 | // Create a listener for incoming client connections. 81 | let listener = Async::::bind(([127, 0, 0, 1], 6000))?; 82 | 83 | // Intro messages. 84 | println!("Listening on {}", listener.get_ref().local_addr()?); 85 | println!("Start a chat client now!\n"); 86 | 87 | // Spawn a background task that dispatches events to clients. 88 | let (sender, receiver) = bounded(100); 89 | smol::spawn(dispatch(receiver)).detach(); 90 | 91 | loop { 92 | // Accept the next connection. 93 | let (stream, addr) = listener.accept().await?; 94 | let client = Arc::new(stream); 95 | let sender = sender.clone(); 96 | 97 | // Spawn a background task reading messages from the client. 98 | smol::spawn(async move { 99 | // Client starts with a `Join` event. 100 | sender.send(Event::Join(addr, client.clone())).await.ok(); 101 | 102 | // Read messages from the client and ignore I/O errors when the client quits. 103 | read_messages(sender.clone(), client).await.ok(); 104 | 105 | // Client ends with a `Leave` event. 106 | sender.send(Event::Leave(addr)).await.ok(); 107 | }) 108 | .detach(); 109 | } 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /examples/ctrl-c.rs: -------------------------------------------------------------------------------- 1 | //! Uses the `ctrlc` crate to catch the Ctrl-C signal. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example ctrl-c 7 | //! ``` 8 | 9 | fn main() { 10 | // Set a handler that sends a message through a channel. 11 | let (s, ctrl_c) = async_channel::bounded(100); 12 | let handle = move || { 13 | s.try_send(()).ok(); 14 | }; 15 | ctrlc::set_handler(handle).unwrap(); 16 | 17 | smol::block_on(async { 18 | println!("Waiting for Ctrl-C..."); 19 | 20 | // Receive a message that indicates the Ctrl-C signal occurred. 21 | ctrl_c.recv().await.ok(); 22 | 23 | println!("Done!"); 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /examples/get-request.rs: -------------------------------------------------------------------------------- 1 | //! Connect to an HTTP website, make a GET request, and pipe the response to the standard output. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example get-request 7 | //! ``` 8 | 9 | use smol::{io, prelude::*, Async, Unblock}; 10 | use std::net::{TcpStream, ToSocketAddrs}; 11 | 12 | fn main() -> io::Result<()> { 13 | smol::block_on(async { 14 | // Connect to http://example.com 15 | let mut addrs = smol::unblock(move || ("example.com", 80).to_socket_addrs()).await?; 16 | let addr = addrs.next().unwrap(); 17 | let mut stream = Async::::connect(addr).await?; 18 | 19 | // Send an HTTP GET request. 20 | let req = b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"; 21 | stream.write_all(req).await?; 22 | 23 | // Read the response and pipe it to the standard output. 24 | let mut stdout = Unblock::new(std::io::stdout()); 25 | io::copy(&stream, &mut stdout).await?; 26 | Ok(()) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /examples/hyper-client.rs: -------------------------------------------------------------------------------- 1 | //! An HTTP+TLS client based on `hyper` and `async-native-tls`. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example hyper-client 7 | //! ``` 8 | 9 | use std::convert::TryInto; 10 | use std::pin::Pin; 11 | use std::task::{Context, Poll}; 12 | 13 | use anyhow::{bail, Context as _, Result}; 14 | use async_native_tls::TlsStream; 15 | use http_body_util::{BodyStream, Empty}; 16 | use hyper::body::Incoming; 17 | use hyper::{Request, Response}; 18 | use macro_rules_attribute::apply; 19 | use smol::{io, net::TcpStream, prelude::*, Executor}; 20 | use smol_hyper::rt::FuturesIo; 21 | use smol_macros::main; 22 | 23 | /// Sends a request and fetches the response. 24 | async fn fetch( 25 | ex: &Executor<'static>, 26 | req: Request>, 27 | ) -> Result> { 28 | // Connect to the HTTP server. 29 | let io = { 30 | let host = req.uri().host().context("cannot parse host")?; 31 | 32 | match req.uri().scheme_str() { 33 | Some("http") => { 34 | let stream = { 35 | let port = req.uri().port_u16().unwrap_or(80); 36 | TcpStream::connect((host, port)).await? 37 | }; 38 | SmolStream::Plain(stream) 39 | } 40 | Some("https") => { 41 | // In case of HTTPS, establish a secure TLS connection first. 42 | let stream = { 43 | let port = req.uri().port_u16().unwrap_or(443); 44 | TcpStream::connect((host, port)).await? 45 | }; 46 | let stream = async_native_tls::connect(host, stream).await?; 47 | SmolStream::Tls(stream) 48 | } 49 | scheme => bail!("unsupported scheme: {:?}", scheme), 50 | } 51 | }; 52 | 53 | // Spawn the HTTP/1 connection. 54 | let (mut sender, conn) = hyper::client::conn::http1::handshake(FuturesIo::new(io)).await?; 55 | ex.spawn(async move { 56 | if let Err(e) = conn.await { 57 | println!("Connection failed: {:?}", e); 58 | } 59 | }) 60 | .detach(); 61 | 62 | // Get the result 63 | let result = sender.send_request(req).await?; 64 | Ok(result) 65 | } 66 | 67 | #[apply(main!)] 68 | async fn main(ex: &Executor<'static>) -> Result<()> { 69 | // Create a request. 70 | let url: hyper::Uri = "https://www.rust-lang.org".try_into()?; 71 | let req = Request::builder() 72 | .header( 73 | hyper::header::HOST, 74 | url.authority().unwrap().clone().as_str(), 75 | ) 76 | .uri(url) 77 | .body(Empty::new())?; 78 | 79 | // Fetch the response. 80 | let resp = fetch(ex, req).await?; 81 | println!("{:#?}", resp); 82 | 83 | // Read the message body. 84 | let body: Vec = BodyStream::new(resp.into_body()) 85 | .try_fold(Vec::new(), |mut body, chunk| { 86 | if let Some(chunk) = chunk.data_ref() { 87 | body.extend_from_slice(chunk); 88 | } 89 | Ok(body) 90 | }) 91 | .await?; 92 | println!("{}", String::from_utf8_lossy(&body)); 93 | 94 | Ok(()) 95 | } 96 | 97 | /// A TCP or TCP+TLS connection. 98 | enum SmolStream { 99 | /// A plain TCP connection. 100 | Plain(TcpStream), 101 | 102 | /// A TCP connection secured by TLS. 103 | Tls(TlsStream), 104 | } 105 | 106 | impl AsyncRead for SmolStream { 107 | fn poll_read( 108 | mut self: Pin<&mut Self>, 109 | cx: &mut Context<'_>, 110 | buf: &mut [u8], 111 | ) -> Poll> { 112 | match &mut *self { 113 | SmolStream::Plain(stream) => Pin::new(stream).poll_read(cx, buf), 114 | SmolStream::Tls(stream) => Pin::new(stream).poll_read(cx, buf), 115 | } 116 | } 117 | } 118 | 119 | impl AsyncWrite for SmolStream { 120 | fn poll_write( 121 | mut self: Pin<&mut Self>, 122 | cx: &mut Context<'_>, 123 | buf: &[u8], 124 | ) -> Poll> { 125 | match &mut *self { 126 | SmolStream::Plain(stream) => Pin::new(stream).poll_write(cx, buf), 127 | SmolStream::Tls(stream) => Pin::new(stream).poll_write(cx, buf), 128 | } 129 | } 130 | 131 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 132 | match &mut *self { 133 | SmolStream::Plain(stream) => Pin::new(stream).poll_close(cx), 134 | SmolStream::Tls(stream) => Pin::new(stream).poll_close(cx), 135 | } 136 | } 137 | 138 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 139 | match &mut *self { 140 | SmolStream::Plain(stream) => Pin::new(stream).poll_flush(cx), 141 | SmolStream::Tls(stream) => Pin::new(stream).poll_flush(cx), 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /examples/hyper-server.rs: -------------------------------------------------------------------------------- 1 | //! An HTTP+TLS server based on `hyper` and `async-native-tls`. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example hyper-server 7 | //! ``` 8 | //! 9 | //! Open in the browser any of these addresses: 10 | //! 11 | //! - http://localhost:8000/ 12 | //! - https://localhost:8001/ (accept the security prompt in the browser) 13 | //! 14 | //! Refer to `README.md` to see how to the TLS certificate was generated. 15 | 16 | use std::net::{TcpListener, TcpStream}; 17 | use std::pin::Pin; 18 | use std::sync::Arc; 19 | use std::task::{Context, Poll}; 20 | 21 | use anyhow::Result; 22 | use async_native_tls::{Identity, TlsAcceptor, TlsStream}; 23 | use http_body_util::Full; 24 | use hyper::body::Incoming; 25 | use hyper::service::service_fn; 26 | use hyper::{Request, Response}; 27 | use macro_rules_attribute::apply; 28 | use smol::{future, io, prelude::*, Async, Executor}; 29 | use smol_hyper::rt::{FuturesIo, SmolTimer}; 30 | use smol_macros::main; 31 | 32 | /// Serves a request and returns a response. 33 | async fn serve(req: Request) -> Result>> { 34 | println!("Serving {}", req.uri()); 35 | Ok(Response::new(Full::new("Hello from hyper!".as_bytes()))) 36 | } 37 | 38 | /// Handle a new client. 39 | async fn handle_client(client: Async, tls: Option) -> Result<()> { 40 | // Wrap it in TLS if necessary. 41 | let client = match &tls { 42 | None => SmolStream::Plain(client), 43 | Some(tls) => { 44 | // In case of HTTPS, establish a secure TLS connection. 45 | SmolStream::Tls(tls.accept(client).await?) 46 | } 47 | }; 48 | 49 | // Build the server. 50 | hyper::server::conn::http1::Builder::new() 51 | .timer(SmolTimer::new()) 52 | .serve_connection(FuturesIo::new(client), service_fn(serve)) 53 | .await?; 54 | 55 | Ok(()) 56 | } 57 | 58 | /// Listens for incoming connections and serves them. 59 | async fn listen( 60 | ex: &Arc>, 61 | listener: Async, 62 | tls: Option, 63 | ) -> Result<()> { 64 | // Format the full host address. 65 | let host = &match tls { 66 | None => format!("http://{}", listener.get_ref().local_addr()?), 67 | Some(_) => format!("https://{}", listener.get_ref().local_addr()?), 68 | }; 69 | println!("Listening on {}", host); 70 | 71 | loop { 72 | // Wait for a new client. 73 | let (client, _) = listener.accept().await?; 74 | 75 | // Spawn a task to handle this connection. 76 | ex.spawn({ 77 | let tls = tls.clone(); 78 | async move { 79 | if let Err(e) = handle_client(client, tls).await { 80 | println!("Error while handling client: {}", e); 81 | } 82 | } 83 | }) 84 | .detach(); 85 | } 86 | } 87 | 88 | #[apply(main!)] 89 | async fn main(ex: &Arc>) -> Result<()> { 90 | // Initialize TLS with the local certificate, private key, and password. 91 | let identity = Identity::from_pkcs12(include_bytes!("identity.pfx"), "password")?; 92 | let tls = TlsAcceptor::from(native_tls::TlsAcceptor::new(identity)?); 93 | 94 | // Start HTTP and HTTPS servers. 95 | let http = listen( 96 | ex, 97 | Async::::bind(([127, 0, 0, 1], 8000))?, 98 | None, 99 | ); 100 | let https = listen( 101 | ex, 102 | Async::::bind(([127, 0, 0, 1], 8001))?, 103 | Some(tls), 104 | ); 105 | future::try_zip(http, https).await?; 106 | Ok(()) 107 | } 108 | 109 | /// A TCP or TCP+TLS connection. 110 | enum SmolStream { 111 | /// A plain TCP connection. 112 | Plain(Async), 113 | 114 | /// A TCP connection secured by TLS. 115 | Tls(TlsStream>), 116 | } 117 | 118 | impl AsyncRead for SmolStream { 119 | fn poll_read( 120 | mut self: Pin<&mut Self>, 121 | cx: &mut Context<'_>, 122 | buf: &mut [u8], 123 | ) -> Poll> { 124 | match &mut *self { 125 | Self::Plain(s) => Pin::new(s).poll_read(cx, buf), 126 | Self::Tls(s) => Pin::new(s).poll_read(cx, buf), 127 | } 128 | } 129 | } 130 | 131 | impl AsyncWrite for SmolStream { 132 | fn poll_write( 133 | mut self: Pin<&mut Self>, 134 | cx: &mut Context<'_>, 135 | buf: &[u8], 136 | ) -> Poll> { 137 | match &mut *self { 138 | Self::Plain(s) => Pin::new(s).poll_write(cx, buf), 139 | Self::Tls(s) => Pin::new(s).poll_write(cx, buf), 140 | } 141 | } 142 | 143 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 144 | match &mut *self { 145 | Self::Plain(s) => Pin::new(s).poll_close(cx), 146 | Self::Tls(s) => Pin::new(s).poll_close(cx), 147 | } 148 | } 149 | 150 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 151 | match &mut *self { 152 | Self::Plain(s) => Pin::new(s).poll_close(cx), 153 | Self::Tls(s) => Pin::new(s).poll_close(cx), 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /examples/identity.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smol-rs/smol/d9d933368a858df4ad968fe2bc36213aced06e8e/examples/identity.pfx -------------------------------------------------------------------------------- /examples/linux-inotify.rs: -------------------------------------------------------------------------------- 1 | //! Uses the `inotify` crate to watch for changes in the current directory. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example linux-inotify 7 | //! ``` 8 | 9 | #[cfg(target_os = "linux")] 10 | fn main() -> std::io::Result<()> { 11 | use std::ffi::OsString; 12 | use std::os::unix::io::AsFd; 13 | 14 | use inotify::{EventMask, Inotify, WatchMask}; 15 | use smol::{io, Async}; 16 | 17 | type Event = (OsString, EventMask); 18 | 19 | /// Reads some events without blocking. 20 | /// 21 | /// If there are no events, an [`io::ErrorKind::WouldBlock`] error is returned. 22 | fn read_op(inotify: &mut Inotify) -> io::Result> { 23 | let mut buffer = [0; 1024]; 24 | let events = inotify 25 | .read_events(&mut buffer)? 26 | .filter_map(|ev| ev.name.map(|name| (name.to_owned(), ev.mask))) 27 | .collect::>(); 28 | 29 | if events.is_empty() { 30 | Err(io::ErrorKind::WouldBlock.into()) 31 | } else { 32 | Ok(events) 33 | } 34 | } 35 | 36 | smol::block_on(async { 37 | // Watch events in the current directory. 38 | let mut inotify = Inotify::init()?; 39 | let source = Async::new(inotify.as_fd().try_clone_to_owned()?)?; 40 | inotify.watches().add(".", WatchMask::ALL_EVENTS)?; 41 | println!("Watching for filesystem events in the current directory..."); 42 | println!("Try opening a file to trigger some events."); 43 | println!(); 44 | 45 | // Wait for events in a loop and print them on the screen. 46 | loop { 47 | for event in source.read_with(|_| read_op(&mut inotify)).await? { 48 | println!("{:?}", event); 49 | } 50 | } 51 | }) 52 | } 53 | 54 | #[cfg(not(target_os = "linux"))] 55 | fn main() { 56 | println!("This example works only on Linux!"); 57 | } 58 | -------------------------------------------------------------------------------- /examples/linux-timerfd.rs: -------------------------------------------------------------------------------- 1 | //! Uses the `timerfd` crate to sleep using an OS timer. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example linux-timerfd 7 | //! ``` 8 | 9 | #[cfg(target_os = "linux")] 10 | fn main() -> std::io::Result<()> { 11 | use std::time::{Duration, Instant}; 12 | 13 | use smol::{io, Async}; 14 | use timerfd::{SetTimeFlags, TimerFd, TimerState}; 15 | 16 | /// Sleeps using an OS timer. 17 | async fn sleep(dur: Duration) -> io::Result<()> { 18 | // Create an OS timer. 19 | let mut timer = TimerFd::new()?; 20 | timer.set_state(TimerState::Oneshot(dur), SetTimeFlags::Default); 21 | 22 | // When the OS timer fires, a 64-bit integer can be read from it. 23 | Async::new(timer)? 24 | .read_with(|t| rustix::io::read(t, &mut [0u8; 8]).map_err(io::Error::from)) 25 | .await?; 26 | Ok(()) 27 | } 28 | 29 | smol::block_on(async { 30 | let start = Instant::now(); 31 | println!("Sleeping..."); 32 | 33 | // Sleep for a second using an OS timer. 34 | sleep(Duration::from_secs(1)).await?; 35 | 36 | println!("Woke up after {:?}", start.elapsed()); 37 | Ok(()) 38 | }) 39 | } 40 | 41 | #[cfg(not(target_os = "linux"))] 42 | fn main() { 43 | println!("This example works only on Linux!"); 44 | } 45 | -------------------------------------------------------------------------------- /examples/simple-client.rs: -------------------------------------------------------------------------------- 1 | //! A simple HTTP+TLS client based on `async-native-tls`. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example simple-client 7 | //! ``` 8 | 9 | use std::net::{TcpStream, ToSocketAddrs}; 10 | 11 | use anyhow::{bail, Context as _, Result}; 12 | use smol::{prelude::*, Async}; 13 | use url::Url; 14 | 15 | /// Sends a GET request and fetches the response. 16 | async fn fetch(addr: &str) -> Result> { 17 | // Parse the URL. 18 | let url = Url::parse(addr)?; 19 | let host = url.host().context("cannot parse host")?.to_string(); 20 | let port = url.port_or_known_default().context("cannot guess port")?; 21 | let path = url.path().to_string(); 22 | let query = match url.query() { 23 | Some(q) => format!("?{}", q), 24 | None => String::new(), 25 | }; 26 | 27 | // Construct a request. 28 | let req = format!( 29 | "GET {}{} HTTP/1.1\r\nHost: {}\r\nAccept: */*\r\nConnection: close\r\n\r\n", 30 | path, query, host, 31 | ); 32 | 33 | // Connect to the host. 34 | let socket_addr = { 35 | let host = host.clone(); 36 | smol::unblock(move || (host.as_str(), port).to_socket_addrs()) 37 | .await? 38 | .next() 39 | .context("cannot resolve address")? 40 | }; 41 | let mut stream = Async::::connect(socket_addr).await?; 42 | 43 | // Send the request and wait for the response. 44 | let mut resp = Vec::new(); 45 | match url.scheme() { 46 | "http" => { 47 | stream.write_all(req.as_bytes()).await?; 48 | stream.read_to_end(&mut resp).await?; 49 | } 50 | "https" => { 51 | // In case of HTTPS, establish a secure TLS connection first. 52 | let mut stream = async_native_tls::connect(&host, stream).await?; 53 | stream.write_all(req.as_bytes()).await?; 54 | stream.read_to_end(&mut resp).await?; 55 | } 56 | scheme => bail!("unsupported scheme: {}", scheme), 57 | } 58 | 59 | Ok(resp) 60 | } 61 | 62 | fn main() -> Result<()> { 63 | smol::block_on(async { 64 | let addr = "https://www.rust-lang.org"; 65 | let resp = fetch(addr).await?; 66 | println!("{}", String::from_utf8_lossy(&resp)); 67 | Ok(()) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /examples/simple-server.rs: -------------------------------------------------------------------------------- 1 | //! A simple HTTP+TLS server based on `async-native-tls`. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example simple-server 7 | //! ``` 8 | //! 9 | //! Open in the browser any of these addresses: 10 | //! 11 | //! - http://localhost:8000/ 12 | //! - https://localhost:8001/ (accept the security prompt in the browser) 13 | //! 14 | //! Refer to `README.md` to see how to the TLS certificate was generated. 15 | 16 | use std::net::{TcpListener, TcpStream}; 17 | 18 | use anyhow::Result; 19 | use async_native_tls::{Identity, TlsAcceptor}; 20 | use smol::{future, prelude::*, Async}; 21 | 22 | const RESPONSE: &[u8] = br#" 23 | HTTP/1.1 200 OK 24 | Content-Type: text/html 25 | Content-Length: 47 26 | 27 | Hello! 28 | "#; 29 | 30 | /// Reads a request from the client and sends it a response. 31 | async fn serve(mut stream: Async, tls: Option) -> Result<()> { 32 | match tls { 33 | None => { 34 | println!("Serving http://{}", stream.get_ref().local_addr()?); 35 | stream.write_all(RESPONSE).await?; 36 | } 37 | Some(tls) => { 38 | println!("Serving https://{}", stream.get_ref().local_addr()?); 39 | 40 | // In case of HTTPS, establish a secure TLS connection first. 41 | match tls.accept(stream).await { 42 | Ok(mut stream) => { 43 | stream.write_all(RESPONSE).await?; 44 | stream.flush().await?; 45 | stream.close().await?; 46 | } 47 | Err(err) => println!("Failed to establish secure TLS connection: {:#?}", err), 48 | } 49 | } 50 | } 51 | Ok(()) 52 | } 53 | 54 | /// Listens for incoming connections and serves them. 55 | async fn listen(listener: Async, tls: Option) -> Result<()> { 56 | // Display the full host address. 57 | match &tls { 58 | None => println!("Listening on http://{}", listener.get_ref().local_addr()?), 59 | Some(_) => println!("Listening on https://{}", listener.get_ref().local_addr()?), 60 | } 61 | 62 | loop { 63 | // Accept the next connection. 64 | let (stream, _) = listener.accept().await?; 65 | let tls = tls.clone(); 66 | 67 | // Spawn a background task serving this connection. 68 | smol::spawn(async move { 69 | if let Err(err) = serve(stream, tls).await { 70 | println!("Connection error: {:#?}", err); 71 | } 72 | }) 73 | .detach(); 74 | } 75 | } 76 | 77 | fn main() -> Result<()> { 78 | // Initialize TLS with the local certificate, private key, and password. 79 | let identity = Identity::from_pkcs12(include_bytes!("identity.pfx"), "password")?; 80 | let tls = TlsAcceptor::from(native_tls::TlsAcceptor::new(identity)?); 81 | 82 | // Start HTTP and HTTPS servers. 83 | smol::block_on(async { 84 | let http = listen(Async::::bind(([127, 0, 0, 1], 8000))?, None); 85 | let https = listen( 86 | Async::::bind(([127, 0, 0, 1], 8001))?, 87 | Some(tls), 88 | ); 89 | future::try_zip(http, https).await?; 90 | Ok(()) 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /examples/tcp-client.rs: -------------------------------------------------------------------------------- 1 | //! A TCP client. 2 | //! 3 | //! First start a server: 4 | //! 5 | //! ``` 6 | //! cargo run --example tcp-server 7 | //! ``` 8 | //! 9 | //! Then start a client: 10 | //! 11 | //! ``` 12 | //! cargo run --example tcp-client 13 | //! ``` 14 | 15 | use std::net::TcpStream; 16 | 17 | use smol::{future, io, Async, Unblock}; 18 | 19 | fn main() -> io::Result<()> { 20 | smol::block_on(async { 21 | // Create async stdin and stdout handles. 22 | let stdin = Unblock::new(std::io::stdin()); 23 | let mut stdout = Unblock::new(std::io::stdout()); 24 | 25 | // Connect to the server. 26 | let stream = Async::::connect(([127, 0, 0, 1], 7000)).await?; 27 | println!("Connected to {}", stream.get_ref().peer_addr()?); 28 | println!("Type a message and hit enter!\n"); 29 | 30 | // Pipe messages from stdin to the server and pipe messages from the server to stdout. 31 | future::try_zip( 32 | io::copy(stdin, &mut &stream), 33 | io::copy(&stream, &mut stdout), 34 | ) 35 | .await?; 36 | 37 | Ok(()) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /examples/tcp-server.rs: -------------------------------------------------------------------------------- 1 | //! A TCP server. 2 | //! 3 | //! First start a server: 4 | //! 5 | //! ``` 6 | //! cargo run --example tcp-server 7 | //! ``` 8 | //! 9 | //! Then start a client: 10 | //! 11 | //! ``` 12 | //! cargo run --example tcp-client 13 | //! ``` 14 | 15 | use std::net::{TcpListener, TcpStream}; 16 | 17 | use smol::{io, Async}; 18 | 19 | /// Echoes messages from the client back to it. 20 | async fn echo(stream: Async) -> io::Result<()> { 21 | io::copy(&stream, &mut &stream).await?; 22 | Ok(()) 23 | } 24 | 25 | fn main() -> io::Result<()> { 26 | smol::block_on(async { 27 | // Create a listener. 28 | let listener = Async::::bind(([127, 0, 0, 1], 7000))?; 29 | println!("Listening on {}", listener.get_ref().local_addr()?); 30 | println!("Now start a TCP client."); 31 | 32 | // Accept clients in a loop. 33 | loop { 34 | let (stream, peer_addr) = listener.accept().await?; 35 | println!("Accepted client: {}", peer_addr); 36 | 37 | // Spawn a task that echoes messages from the client back to it. 38 | smol::spawn(echo(stream)).detach(); 39 | } 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /examples/tls-client.rs: -------------------------------------------------------------------------------- 1 | //! A TCP client secured by TLS based on `async-native-tls`. 2 | //! 3 | //! First start a server: 4 | //! 5 | //! ``` 6 | //! cargo run --example tls-server 7 | //! ``` 8 | //! 9 | //! Then start a client: 10 | //! 11 | //! ``` 12 | //! cargo run --example tls-client 13 | //! ``` 14 | 15 | use std::net::TcpStream; 16 | 17 | use anyhow::Result; 18 | use async_native_tls::{Certificate, TlsConnector}; 19 | use smol::{future, io, Async, Unblock}; 20 | 21 | fn main() -> Result<()> { 22 | // Initialize TLS with the local certificate. 23 | let mut builder = native_tls::TlsConnector::builder(); 24 | builder.add_root_certificate(Certificate::from_pem(include_bytes!("certificate.pem"))?); 25 | let tls = TlsConnector::from(builder); 26 | 27 | smol::block_on(async { 28 | // Create async stdin and stdout handles. 29 | let stdin = Unblock::new(std::io::stdin()); 30 | let mut stdout = Unblock::new(std::io::stdout()); 31 | 32 | // Connect to the server. 33 | let stream = Async::::connect(([127, 0, 0, 1], 7001)).await?; 34 | let stream = tls.connect("127.0.0.1", stream).await?; 35 | println!("Connected to {}", stream.get_ref().get_ref().peer_addr()?); 36 | println!("Type a message and hit enter!\n"); 37 | 38 | // Pipe messages from stdin to the server and pipe messages from the server to stdout. 39 | let stream = async_dup::Mutex::new(stream); 40 | future::try_zip( 41 | io::copy(stdin, &mut &stream), 42 | io::copy(&stream, &mut stdout), 43 | ) 44 | .await?; 45 | 46 | Ok(()) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /examples/tls-server.rs: -------------------------------------------------------------------------------- 1 | //! A TCP server secured by TLS based on `async-native-tls`. 2 | //! 3 | //! First start a server: 4 | //! 5 | //! ``` 6 | //! cargo run --example tls-server 7 | //! ``` 8 | //! 9 | //! Then start a client: 10 | //! 11 | //! ``` 12 | //! cargo run --example tls-client 13 | //! ``` 14 | 15 | use std::net::{TcpListener, TcpStream}; 16 | 17 | use anyhow::Result; 18 | use async_native_tls::{Identity, TlsAcceptor, TlsStream}; 19 | use smol::{io, Async}; 20 | 21 | /// Echoes messages from the client back to it. 22 | async fn echo(stream: TlsStream>) -> Result<()> { 23 | let stream = async_dup::Mutex::new(stream); 24 | io::copy(&stream, &mut &stream).await?; 25 | Ok(()) 26 | } 27 | 28 | fn main() -> Result<()> { 29 | // Initialize TLS with the local certificate, private key, and password. 30 | let identity = Identity::from_pkcs12(include_bytes!("identity.pfx"), "password")?; 31 | let tls = TlsAcceptor::from(native_tls::TlsAcceptor::new(identity)?); 32 | 33 | smol::block_on(async { 34 | // Create a listener. 35 | let listener = Async::::bind(([127, 0, 0, 1], 7001))?; 36 | println!("Listening on {}", listener.get_ref().local_addr()?); 37 | println!("Now start a TLS client."); 38 | 39 | // Accept clients in a loop. 40 | loop { 41 | let (stream, _) = listener.accept().await?; 42 | let stream = tls.accept(stream).await?; 43 | println!( 44 | "Accepted client: {}", 45 | stream.get_ref().get_ref().peer_addr()? 46 | ); 47 | 48 | // Spawn a task that echoes messages from the client back to it. 49 | smol::spawn(echo(stream)).detach(); 50 | } 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /examples/unix-signal.rs: -------------------------------------------------------------------------------- 1 | //! Uses the `signal-hook` crate to catch the Ctrl-C signal. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example unix-signal 7 | //! ``` 8 | 9 | #[cfg(unix)] 10 | fn main() -> std::io::Result<()> { 11 | use std::os::unix::{io::AsRawFd, net::UnixStream}; 12 | 13 | use smol::{prelude::*, Async}; 14 | 15 | smol::block_on(async { 16 | // Create a Unix stream that receives a byte on each signal occurrence. 17 | let (a, mut b) = Async::::pair()?; 18 | // Async isn't IntoRawFd, but it is AsRawFd, so let's pass the raw fd directly. 19 | signal_hook::low_level::pipe::register_raw(signal_hook::consts::SIGINT, a.as_raw_fd())?; 20 | println!("Waiting for Ctrl-C..."); 21 | 22 | // Receive a byte that indicates the Ctrl-C signal occurred. 23 | b.read_exact(&mut [0]).await?; 24 | 25 | println!("Done!"); 26 | Ok(()) 27 | }) 28 | } 29 | 30 | #[cfg(not(unix))] 31 | fn main() { 32 | println!("This example works only on Unix systems!"); 33 | } 34 | -------------------------------------------------------------------------------- /examples/web-crawler.rs: -------------------------------------------------------------------------------- 1 | //! Crawls the Rust language website and prints found pages. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example web-crawler 7 | //! ``` 8 | 9 | use std::collections::{HashSet, VecDeque}; 10 | 11 | use anyhow::Result; 12 | use async_channel::{bounded, Sender}; 13 | use scraper::{Html, Selector}; 14 | 15 | const ROOT: &str = "https://www.rust-lang.org"; 16 | 17 | /// Fetches the HTML contents of a web page. 18 | async fn fetch(url: String, sender: Sender) { 19 | let body = surf::get(&url).recv_string().await; 20 | let body = body.unwrap_or_default(); 21 | sender.send(body).await.ok(); 22 | } 23 | 24 | /// Extracts links from a HTML body. 25 | fn links(body: String) -> Vec { 26 | let mut v = Vec::new(); 27 | for elem in Html::parse_fragment(&body).select(&Selector::parse("a").unwrap()) { 28 | if let Some(link) = elem.value().attr("href") { 29 | v.push(link.to_string()); 30 | } 31 | } 32 | v 33 | } 34 | 35 | fn main() -> Result<()> { 36 | smol::block_on(async { 37 | let mut seen = HashSet::new(); 38 | let mut queue = VecDeque::new(); 39 | seen.insert(ROOT.to_string()); 40 | queue.push_back(ROOT.to_string()); 41 | 42 | let (s, r) = bounded(200); 43 | let mut tasks = 0; 44 | 45 | // Loop while the queue is not empty or tasks are fetching pages. 46 | while queue.len() + tasks > 0 { 47 | // Limit the number of concurrent tasks. 48 | while tasks < s.capacity().unwrap() { 49 | // Process URLs in the queue and fetch more pages. 50 | match queue.pop_front() { 51 | None => break, 52 | Some(url) => { 53 | println!("{}", url); 54 | tasks += 1; 55 | smol::spawn(fetch(url, s.clone())).detach(); 56 | } 57 | } 58 | } 59 | 60 | // Get a fetched web page. 61 | let body = r.recv().await.unwrap(); 62 | tasks -= 1; 63 | 64 | // Parse links in the web page and add them to the queue. 65 | for mut url in links(body) { 66 | // Add the site prefix if it's missing. 67 | if url.starts_with('/') { 68 | url = format!("{}{}", ROOT, url); 69 | } 70 | 71 | // If the URL makes sense and was not seen already, push it into the queue. 72 | if url.starts_with(ROOT) && seen.insert(url.clone()) { 73 | url = url.trim_end_matches('/').to_string(); 74 | queue.push_back(url); 75 | } 76 | } 77 | } 78 | Ok(()) 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /examples/websocket-client.rs: -------------------------------------------------------------------------------- 1 | //! A WebSocket+TLS client based on `async-tungstenite` and `async-native-tls`. 2 | //! 3 | //! First start a server: 4 | //! 5 | //! ``` 6 | //! cargo run --example websocket-server 7 | //! ``` 8 | //! 9 | //! Then start a client: 10 | //! 11 | //! ``` 12 | //! cargo run --example websocket-client 13 | //! ``` 14 | 15 | use std::net::{TcpStream, ToSocketAddrs}; 16 | use std::pin::Pin; 17 | use std::task::{Context, Poll}; 18 | 19 | use anyhow::{bail, Context as _, Result}; 20 | use async_native_tls::{Certificate, TlsConnector, TlsStream}; 21 | use async_tungstenite::{tungstenite, WebSocketStream}; 22 | use futures::sink::{Sink, SinkExt}; 23 | use smol::{prelude::*, Async}; 24 | use tungstenite::handshake::client::Response; 25 | use tungstenite::Message; 26 | use url::Url; 27 | 28 | /// Connects to a WebSocket address (optionally secured by TLS). 29 | async fn connect(addr: &str, tls: TlsConnector) -> Result<(WsStream, Response)> { 30 | // Parse the address. 31 | let url = Url::parse(addr)?; 32 | let host = url.host_str().context("cannot parse host")?.to_string(); 33 | let port = url.port_or_known_default().context("cannot guess port")?; 34 | 35 | // Resolve the address. 36 | let socket_addr = { 37 | let host = host.clone(); 38 | smol::unblock(move || (host.as_str(), port).to_socket_addrs()) 39 | .await? 40 | .next() 41 | .context("cannot resolve address")? 42 | }; 43 | 44 | // Connect to the address. 45 | match url.scheme() { 46 | "ws" => { 47 | let stream = Async::::connect(socket_addr).await?; 48 | let (stream, resp) = async_tungstenite::client_async(addr, stream).await?; 49 | Ok((WsStream::Plain(stream), resp)) 50 | } 51 | "wss" => { 52 | // In case of WSS, establish a secure TLS connection first. 53 | let stream = Async::::connect(socket_addr).await?; 54 | let stream = tls.connect(host, stream).await?; 55 | let (stream, resp) = async_tungstenite::client_async(addr, stream).await?; 56 | Ok((WsStream::Tls(stream), resp)) 57 | } 58 | scheme => bail!("unsupported scheme: {}", scheme), 59 | } 60 | } 61 | 62 | fn main() -> Result<()> { 63 | // Initialize TLS with the local certificate. 64 | let mut builder = native_tls::TlsConnector::builder(); 65 | builder.add_root_certificate(Certificate::from_pem(include_bytes!("certificate.pem"))?); 66 | let tls = TlsConnector::from(builder); 67 | 68 | smol::block_on(async { 69 | // Connect to the server. 70 | let (mut stream, resp) = connect("wss://127.0.0.1:9001", tls).await?; 71 | dbg!(resp); 72 | 73 | // Send a message and receive a response. 74 | stream.send(Message::text("Hello!")).await?; 75 | dbg!(stream.next().await); 76 | 77 | Ok(()) 78 | }) 79 | } 80 | 81 | /// A WebSocket or WebSocket+TLS connection. 82 | enum WsStream { 83 | /// A plain WebSocket connection. 84 | Plain(WebSocketStream>), 85 | 86 | /// A WebSocket connection secured by TLS. 87 | Tls(WebSocketStream>>), 88 | } 89 | 90 | impl Sink for WsStream { 91 | type Error = tungstenite::Error; 92 | 93 | fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 94 | match &mut *self { 95 | WsStream::Plain(s) => Pin::new(s).poll_ready(cx), 96 | WsStream::Tls(s) => Pin::new(s).poll_ready(cx), 97 | } 98 | } 99 | 100 | fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { 101 | match &mut *self { 102 | WsStream::Plain(s) => Pin::new(s).start_send(item), 103 | WsStream::Tls(s) => Pin::new(s).start_send(item), 104 | } 105 | } 106 | 107 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 108 | match &mut *self { 109 | WsStream::Plain(s) => Pin::new(s).poll_flush(cx), 110 | WsStream::Tls(s) => Pin::new(s).poll_flush(cx), 111 | } 112 | } 113 | 114 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 115 | match &mut *self { 116 | WsStream::Plain(s) => Pin::new(s).poll_close(cx), 117 | WsStream::Tls(s) => Pin::new(s).poll_close(cx), 118 | } 119 | } 120 | } 121 | 122 | impl Stream for WsStream { 123 | type Item = tungstenite::Result; 124 | 125 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 126 | match &mut *self { 127 | WsStream::Plain(s) => Pin::new(s).poll_next(cx), 128 | WsStream::Tls(s) => Pin::new(s).poll_next(cx), 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /examples/websocket-server.rs: -------------------------------------------------------------------------------- 1 | //! A WebSocket+TLS echo server based on `async-tungstenite` and `async-native-tls`. 2 | //! 3 | //! First start a server: 4 | //! 5 | //! ``` 6 | //! cargo run --example websocket-server 7 | //! ``` 8 | //! 9 | //! Then start a client: 10 | //! 11 | //! ``` 12 | //! cargo run --example websocket-client 13 | //! ``` 14 | 15 | use std::net::{TcpListener, TcpStream}; 16 | use std::pin::Pin; 17 | use std::task::{Context, Poll}; 18 | 19 | use anyhow::{Context as _, Result}; 20 | use async_native_tls::{Identity, TlsAcceptor, TlsStream}; 21 | use async_tungstenite::{tungstenite, WebSocketStream}; 22 | use futures::sink::{Sink, SinkExt}; 23 | use smol::{future, prelude::*, Async}; 24 | use tungstenite::Message; 25 | 26 | /// Echoes messages from the client back to it. 27 | async fn echo(mut stream: WsStream) -> Result<()> { 28 | let msg = stream.next().await.context("expected a message")??; 29 | stream.send(Message::text(msg.to_string())).await?; 30 | Ok(()) 31 | } 32 | 33 | /// Listens for incoming connections and serves them. 34 | async fn listen(listener: Async, tls: Option) -> Result<()> { 35 | let host = match &tls { 36 | None => format!("ws://{}", listener.get_ref().local_addr()?), 37 | Some(_) => format!("wss://{}", listener.get_ref().local_addr()?), 38 | }; 39 | println!("Listening on {}", host); 40 | 41 | loop { 42 | // Accept the next connection. 43 | let (stream, _) = listener.accept().await?; 44 | println!("Accepted client: {}", stream.get_ref().peer_addr()?); 45 | 46 | match &tls { 47 | None => { 48 | let stream = WsStream::Plain(async_tungstenite::accept_async(stream).await?); 49 | smol::spawn(echo(stream)).detach(); 50 | } 51 | Some(tls) => { 52 | // In case of WSS, establish a secure TLS connection first. 53 | let stream = tls.accept(stream).await?; 54 | let stream = WsStream::Tls(async_tungstenite::accept_async(stream).await?); 55 | smol::spawn(echo(stream)).detach(); 56 | } 57 | } 58 | } 59 | } 60 | 61 | fn main() -> Result<()> { 62 | // Initialize TLS with the local certificate, private key, and password. 63 | let identity = Identity::from_pkcs12(include_bytes!("identity.pfx"), "password")?; 64 | let tls = TlsAcceptor::from(native_tls::TlsAcceptor::new(identity)?); 65 | 66 | // Start WS and WSS servers. 67 | smol::block_on(async { 68 | let ws = listen(Async::::bind(([127, 0, 0, 1], 9000))?, None); 69 | let wss = listen( 70 | Async::::bind(([127, 0, 0, 1], 9001))?, 71 | Some(tls), 72 | ); 73 | future::try_zip(ws, wss).await?; 74 | Ok(()) 75 | }) 76 | } 77 | 78 | /// A WebSocket or WebSocket+TLS connection. 79 | enum WsStream { 80 | /// A plain WebSocket connection. 81 | Plain(WebSocketStream>), 82 | 83 | /// A WebSocket connection secured by TLS. 84 | Tls(WebSocketStream>>), 85 | } 86 | 87 | impl Sink for WsStream { 88 | type Error = tungstenite::Error; 89 | 90 | fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 91 | match &mut *self { 92 | WsStream::Plain(s) => Pin::new(s).poll_ready(cx), 93 | WsStream::Tls(s) => Pin::new(s).poll_ready(cx), 94 | } 95 | } 96 | 97 | fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { 98 | match &mut *self { 99 | WsStream::Plain(s) => Pin::new(s).start_send(item), 100 | WsStream::Tls(s) => Pin::new(s).start_send(item), 101 | } 102 | } 103 | 104 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 105 | match &mut *self { 106 | WsStream::Plain(s) => Pin::new(s).poll_flush(cx), 107 | WsStream::Tls(s) => Pin::new(s).poll_flush(cx), 108 | } 109 | } 110 | 111 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 112 | match &mut *self { 113 | WsStream::Plain(s) => Pin::new(s).poll_close(cx), 114 | WsStream::Tls(s) => Pin::new(s).poll_close(cx), 115 | } 116 | } 117 | } 118 | 119 | impl Stream for WsStream { 120 | type Item = tungstenite::Result; 121 | 122 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 123 | match &mut *self { 124 | WsStream::Plain(s) => Pin::new(s).poll_next(cx), 125 | WsStream::Tls(s) => Pin::new(s).poll_next(cx), 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /examples/windows-uds.rs: -------------------------------------------------------------------------------- 1 | //! Uses the `uds_windows` crate to simulate Unix sockets on Windows. 2 | //! 3 | //! Run with: 4 | //! 5 | //! ``` 6 | //! cargo run --example windows-uds 7 | //! ``` 8 | 9 | #[cfg(windows)] 10 | fn main() -> std::io::Result<()> { 11 | use std::ops::Deref; 12 | use std::os::windows::io::{AsRawSocket, AsSocket, BorrowedSocket}; 13 | use std::path::PathBuf; 14 | 15 | use smol::{future, prelude::*, Async, Unblock}; 16 | use std::io; 17 | use tempfile::tempdir; 18 | 19 | // n.b.: notgull: uds_windows does not support I/O safety yet, hence the wrapper types 20 | 21 | struct UnixListener(uds_windows::UnixListener); 22 | 23 | impl From for UnixListener { 24 | fn from(ul: uds_windows::UnixListener) -> Self { 25 | Self(ul) 26 | } 27 | } 28 | 29 | impl Deref for UnixListener { 30 | type Target = uds_windows::UnixListener; 31 | 32 | fn deref(&self) -> &uds_windows::UnixListener { 33 | &self.0 34 | } 35 | } 36 | 37 | impl AsSocket for UnixListener { 38 | fn as_socket(&self) -> BorrowedSocket<'_> { 39 | unsafe { BorrowedSocket::borrow_raw(self.as_raw_socket()) } 40 | } 41 | } 42 | 43 | struct UnixStream(uds_windows::UnixStream); 44 | 45 | impl From for UnixStream { 46 | fn from(ul: uds_windows::UnixStream) -> Self { 47 | Self(ul) 48 | } 49 | } 50 | 51 | impl Deref for UnixStream { 52 | type Target = uds_windows::UnixStream; 53 | 54 | fn deref(&self) -> &uds_windows::UnixStream { 55 | &self.0 56 | } 57 | } 58 | 59 | impl AsSocket for UnixStream { 60 | fn as_socket(&self) -> BorrowedSocket<'_> { 61 | unsafe { BorrowedSocket::borrow_raw(self.as_raw_socket()) } 62 | } 63 | } 64 | 65 | impl io::Read for UnixStream { 66 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 67 | io::Read::read(&mut self.0, buf) 68 | } 69 | } 70 | 71 | impl io::Write for UnixStream { 72 | fn write(&mut self, buf: &[u8]) -> io::Result { 73 | io::Write::write(&mut self.0, buf) 74 | } 75 | 76 | fn flush(&mut self) -> io::Result<()> { 77 | io::Write::flush(&mut self.0) 78 | } 79 | } 80 | 81 | unsafe impl async_io::IoSafe for UnixStream {} 82 | 83 | async fn client(addr: PathBuf) -> io::Result<()> { 84 | // Connect to the address. 85 | let stream = Async::new(UnixStream::from(uds_windows::UnixStream::connect(addr)?))?; 86 | println!("Connected to {:?}", stream.get_ref().peer_addr()?); 87 | 88 | // Pipe the stream to stdout. 89 | let mut stdout = Unblock::new(std::io::stdout()); 90 | futures_lite::io::copy(stream, &mut stdout).await?; 91 | Ok(()) 92 | } 93 | 94 | let dir = tempdir()?; 95 | let path = dir.path().join("socket"); 96 | 97 | future::block_on(async { 98 | // Create a listener. 99 | let listener = Async::new(UnixListener::from(uds_windows::UnixListener::bind(&path)?))?; 100 | println!("Listening on {:?}", listener.get_ref().local_addr()?); 101 | 102 | future::try_zip( 103 | async { 104 | // Accept the client. 105 | let (stream, _) = listener.read_with(|l| l.accept()).await?; 106 | println!("Accepted a client"); 107 | 108 | // Send a message, drop the stream, and wait for the client. 109 | Async::new(UnixStream::from(stream))? 110 | .write_all(b"Hello!\n") 111 | .await?; 112 | Ok(()) 113 | }, 114 | client(path), 115 | ) 116 | .await?; 117 | 118 | Ok(()) 119 | }) 120 | } 121 | 122 | #[cfg(not(windows))] 123 | fn main() { 124 | println!("This example works only on Windows!"); 125 | } 126 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A small and fast async runtime. 2 | //! 3 | //! This crate simply re-exports other smaller async crates (see the source). 4 | //! 5 | //! To use tokio-based libraries with smol, apply the [`async-compat`] adapter to futures and I/O 6 | //! types. 7 | //! 8 | //! See the [`smol-macros`] crate if you want a no proc-macro, fast compiling, easy-to-use 9 | //! async main and/or multi-threaded Executor setup out of the box. 10 | //! 11 | //! # Examples 12 | //! 13 | //! Connect to an HTTP website, make a GET request, and pipe the response to the standard output: 14 | //! 15 | //! ``` 16 | //! use smol::{io, net, prelude::*, Unblock}; 17 | //! 18 | //! fn main() -> io::Result<()> { 19 | //! smol::block_on(async { 20 | //! let mut stream = net::TcpStream::connect("example.com:80").await?; 21 | //! let req = b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"; 22 | //! stream.write_all(req).await?; 23 | //! 24 | //! let mut stdout = Unblock::new(std::io::stdout()); 25 | //! io::copy(stream, &mut stdout).await?; 26 | //! Ok(()) 27 | //! }) 28 | //! } 29 | //! ``` 30 | //! 31 | //! There's a lot more in the [examples] directory. 32 | //! 33 | //! [`async-compat`]: https://docs.rs/async-compat/latest/async_compat/ 34 | //! [`smol-macros`]: https://docs.rs/smol-macros/latest/smol_macros/ 35 | //! [examples]: https://github.com/smol-rs/smol/tree/master/examples 36 | 37 | #![doc( 38 | html_favicon_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png" 39 | )] 40 | #![doc( 41 | html_logo_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png" 42 | )] 43 | #![forbid(unsafe_code)] 44 | #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] 45 | 46 | #[cfg(doctest)] 47 | doc_comment::doctest!("../README.md"); 48 | 49 | #[doc(inline)] 50 | pub use { 51 | async_executor::{Executor, LocalExecutor, Task}, 52 | async_io::{block_on, Async, Timer}, 53 | blocking::{unblock, Unblock}, 54 | futures_lite::{future, io, pin, prelude, ready, stream}, 55 | }; 56 | 57 | #[doc(inline)] 58 | pub use {async_channel as channel, async_fs as fs, async_lock as lock, async_net as net}; 59 | 60 | #[cfg(not(target_os = "espidf"))] 61 | #[doc(inline)] 62 | pub use async_process as process; 63 | 64 | mod spawn; 65 | pub use spawn::spawn; 66 | -------------------------------------------------------------------------------- /src/spawn.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::panic::catch_unwind; 3 | use std::thread; 4 | 5 | use async_executor::{Executor, Task}; 6 | use async_io::block_on; 7 | use async_lock::OnceCell; 8 | use futures_lite::future; 9 | 10 | /// Spawns a task onto the global executor (single-threaded by default). 11 | /// 12 | /// There is a global executor that gets lazily initialized on first use. It is included in this 13 | /// library for convenience when writing unit tests and small programs, but it is otherwise 14 | /// more advisable to create your own [`Executor`]. 15 | /// 16 | /// By default, the global executor is run by a single background thread, but you can also 17 | /// configure the number of threads by setting the `SMOL_THREADS` environment variable. 18 | /// 19 | /// Since the executor is kept around forever, `drop` is not called for tasks when the program 20 | /// exits. 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// let task = smol::spawn(async { 26 | /// 1 + 2 27 | /// }); 28 | /// 29 | /// smol::block_on(async { 30 | /// assert_eq!(task.await, 3); 31 | /// }); 32 | /// ``` 33 | pub fn spawn(future: impl Future + Send + 'static) -> Task { 34 | static GLOBAL: OnceCell> = OnceCell::new(); 35 | 36 | fn global() -> &'static Executor<'static> { 37 | GLOBAL.get_or_init_blocking(|| { 38 | let num_threads = { 39 | // Parse SMOL_THREADS or default to 1. 40 | std::env::var("SMOL_THREADS") 41 | .ok() 42 | .and_then(|s| s.parse().ok()) 43 | .unwrap_or(1) 44 | }; 45 | 46 | for n in 1..=num_threads { 47 | thread::Builder::new() 48 | .name(format!("smol-{}", n)) 49 | .spawn(|| loop { 50 | catch_unwind(|| block_on(global().run(future::pending::<()>()))).ok(); 51 | }) 52 | .expect("cannot spawn executor thread"); 53 | } 54 | 55 | // Prevent spawning another thread by running the process driver on this thread. 56 | let ex = Executor::new(); 57 | #[cfg(not(target_os = "espidf"))] 58 | ex.spawn(async_process::driver()).detach(); 59 | ex 60 | }) 61 | } 62 | 63 | global().spawn(future) 64 | } 65 | --------------------------------------------------------------------------------