unsend-0.2.1/.cargo_vcs_info.json0000644000000001360000000000100123300ustar { "git": { "sha1": "f328c18bd0cac6845e51a53f9ff4c18e355826f0" }, "path_in_vcs": "" }unsend-0.2.1/.github/PULL_REQUEST_TEMPLATE.md000064400000000000000000000002261046102023000162610ustar 00000000000000- [ ] Tested on all platforms affected by this change - [ ] Signed the [Contribution License Agreement (CLA)](https://cla-assistant.io/notgull/unsend)unsend-0.2.1/.github/dependabot.yml000064400000000000000000000002331046102023000153060ustar 00000000000000version: 2 updates: - package-ecosystem: cargo directory: / schedule: interval: weekly commit-message: prefix: '' labels: [] unsend-0.2.1/.github/workflows/ci.yml000064400000000000000000000055221046102023000156370ustar 00000000000000name: CI on: pull_request: push: branches: - main schedule: - cron: '0 2 * * 0' env: CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings RUSTUP_MAX_RETRIES: 10 defaults: run: shell: bash jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] rust: [nightly, beta, stable] steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - run: cargo build --all --all-features --all-targets - name: Run cargo check (without dev-dependencies to catch missing feature flags) if: startsWith(matrix.rust, 'nightly') run: cargo check -Z features=dev_dep - run: cargo test --all - run: cargo test --no-default-features --tests - run: cargo test --no-default-features --features alloc --tests - run: cargo test --no-default-features --features executor --tests - name: Install cargo-hack uses: taiki-e/install-action@cargo-hack - run: rustup target add thumbv7m-none-eabi - run: cargo hack build --target thumbv7m-none-eabi --no-default-features --no-dev-deps - run: cargo hack build --target thumbv7m-none-eabi --no-default-features --no-dev-deps --features alloc msrv: runs-on: ubuntu-latest strategy: matrix: # When updating this, the reminder to update the minimum supported # Rust version in Cargo.toml. rust: ['1.48'] steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - run: cargo build - run: cargo build --no-default-features clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo clippy --all --all-features --all-targets fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo fmt --all --check miri: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup toolchain install nightly --component miri && rustup default nightly - run: cargo miri test --all env: MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-symbolic-alignment-check -Zmiri-disable-isolation RUSTFLAGS: ${{ env.RUSTFLAGS }} -Z randomize-layout security_audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 # https://github.com/rustsec/audit-check/issues/2 - uses: rustsec/audit-check@main with: token: ${{ secrets.GITHUB_TOKEN }} unsend-0.2.1/.github/workflows/release.yml000064400000000000000000000005771046102023000166710ustar 00000000000000name: Release on: push: tags: - v[0-9]+.* jobs: create-release: if: github.repository_owner == 'smol-rs' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: taiki-e/create-gh-release-action@v1 with: changelog: CHANGELOG.md branch: main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} unsend-0.2.1/.gitignore000064400000000000000000000000341046102023000131050ustar 00000000000000/target /Cargo.lock log.txt unsend-0.2.1/CHANGELOG.md000064400000000000000000000003751046102023000127360ustar 00000000000000# Changelog This changelog describes changes in the `unsend` crate. ## unsend 0.2.1 - Add a `listen()` method to the `EventListener` and `EventListenerRc` types. ## unsend 0.2.0 Initial non-broken version (0.1.0 had unforeseen compilation errors). unsend-0.2.1/CONTRIBUTING.md000064400000000000000000000017701046102023000133560ustar 00000000000000# Contribution We welcome contributions to this project. Please feel free to open GitHub issues, pull requests, or comments. ## Coding Style [`rustfmt`] and [`clippy`] is used to enforce coding style. Before pushing a commit, run `cargo fmt --all` to format your code and make sure [`clippy`] warnings are fixed. [`rustfmt`]: https://github.com/rust-lang/rustfmt [`clippy`]: https://github.com/rust-lang/clippy ## Testing All changes submitted to this repository are run through GitHub Actions and the workflow defined into the [`ci.yml`] file. If your change does not pass the tests described, it is unlikely to be merged. [`ci.yml`]: https://github.com/notgull/unsend/blob/main/.github/workflows/ci.yml ## Contributor License Agreement When opening a pull request, you will be asked to sign a [Contributor License Agreement (CLA)](https://cla-assistant.io/notgull/unsend). This is a legal document that confirms you are granting us permission to use your contribution. You only need to sign the CLA once. unsend-0.2.1/Cargo.lock0000644000000245400000000000100103100ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "async-channel" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" dependencies = [ "concurrent-queue", "event-listener", "futures-core", ] [[package]] name = "async-io" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", "cfg-if", "concurrent-queue", "futures-lite", "log", "parking", "polling", "rustix", "slab", "socket2", "waker-fn", ] [[package]] name = "async-lock" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", ] [[package]] name = "async-task" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "atomic-waker" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blocking" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", "async-task", "atomic-waker", "fastrand", "futures-lite", "log", ] [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "concurrent-queue" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] [[package]] name = "errno" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", "windows-sys", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "futures-core" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-io" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", "futures-io", "memchr", "parking", "pin-project-lite", "waker-fn", ] [[package]] name = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "io-lifetimes" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ "hermit-abi", "libc", "windows-sys", ] [[package]] name = "libc" version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "linux-raw-sys" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "parking" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "polling" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", "bitflags", "cfg-if", "concurrent-queue", "libc", "log", "pin-project-lite", "windows-sys", ] [[package]] name = "rustix" version = "0.37.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "slab" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "socket2" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] [[package]] name = "unsend" version = "0.2.1" dependencies = [ "async-io", "async-task", "atomic-waker", "blocking", "concurrent-queue", "futures-lite", "pin-project-lite", "slab", "waker-fn", ] [[package]] name = "waker-fn" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" unsend-0.2.1/Cargo.toml0000644000000034110000000000100103250ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.48.0" name = "unsend" version = "0.2.1" authors = ["John Nunley "] description = "A thread unsafe runtime for thread unsafe people" readme = "README.md" keywords = [ "async", "runtime", ] categories = ["asynchronous"] license = "LGPL-3.0-or-later OR MPL-2.0" repository = "https://github.com/notgull/unsend" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.async-task] version = "4.4.0" optional = true default-features = false [dependencies.atomic-waker] version = "1.1.1" optional = true [dependencies.concurrent-queue] version = "2.2.0" optional = true default-features = false [dependencies.futures-lite] version = "1.13.0" optional = true default-features = false [dependencies.pin-project-lite] version = "0.2.9" [dependencies.slab] version = "0.4.8" optional = true default-features = false [dev-dependencies.async-io] version = "1.13.0" [dev-dependencies.blocking] version = "1.3.1" [dev-dependencies.futures-lite] version = "1.13.0" [dev-dependencies.waker-fn] version = "1.1.0" [features] alloc = [] default = [ "std", "executor", ] executor = [ "alloc", "async-task", "concurrent-queue", "atomic-waker", "slab", "futures-lite", ] std = ["alloc"] unsend-0.2.1/Cargo.toml.orig000064400000000000000000000030651046102023000140130ustar 00000000000000[package] name = "unsend" version = "0.2.1" edition = "2018" authors = ["John Nunley "] description = "A thread unsafe runtime for thread unsafe people" license = "LGPL-3.0-or-later OR MPL-2.0" repository = "https://github.com/notgull/unsend" keywords = ["async", "runtime"] categories = ["asynchronous"] # when updating rust-version: # - update ci.yml # - update readme.md rust-version = "1.48.0" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std", "executor"] std = ["alloc"] alloc = [] executor = ["alloc", "async-task", "concurrent-queue", "atomic-waker", "slab", "futures-lite"] [dev-dependencies] async-io = "1.13.0" blocking = "1.3.1" futures-lite = "1.13.0" waker-fn = "1.1.0" # async-task is a public dependency, the rest aren't [dependencies] async-task = { version = "4.4.0", default-features = false, optional = true } atomic-waker = { version = "1.1.1", optional = true } concurrent-queue = { version = "2.2.0", default-features = false, optional = true } futures-lite = { version = "1.13.0", default-features = false, optional = true } pin-project-lite = "0.2.9" slab = { version = "0.4.8", default-features = false, optional = true } [workspace] members = [ "comparisons/unsend-hello", "comparisons/tokio-hello", "comparisons/tokio-local-hello", "comparisons/smol-hello", "comparisons/smol-local-hello", "comparisons/unsend-lock", "comparisons/tokio-lock", "comparisons/tokio-local-lock", "comparisons/smol-lock", "comparisons/smol-local-lock", ] unsend-0.2.1/LICENSE-LGPL-3.0.md000064400000000000000000000167321046102023000135670ustar 00000000000000### GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. #### 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. #### 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. #### 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: - a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or - b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. #### 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: - a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. - b) Accompany the object code with a copy of the GNU GPL and this license document. #### 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: - a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. - b) Accompany the Combined Work with a copy of the GNU GPL and this license document. - c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. - d) Do one of the following: - 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. - 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. - e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) #### 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: - a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. - b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. #### 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. unsend-0.2.1/LICENSE-MPL-2.0.md000064400000000000000000000363351046102023000134610ustar 00000000000000Mozilla Public License Version 2.0 ================================== ### 1. Definitions **1.1. “Contributor”** means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. **1.2. “Contributor Version”** means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. **1.3. “Contribution”** means Covered Software of a particular Contributor. **1.4. “Covered Software”** means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. **1.5. “Incompatible With Secondary Licenses”** means * **(a)** that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or * **(b)** that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. **1.6. “Executable Form”** means any form of the work other than Source Code Form. **1.7. “Larger Work”** means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. **1.8. “License”** means this document. **1.9. “Licensable”** means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. **1.10. “Modifications”** means any of the following: * **(a)** any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or * **(b)** any new file in Source Code Form that contains any Covered Software. **1.11. “Patent Claims” of a Contributor** means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. **1.12. “Secondary License”** means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. **1.13. “Source Code Form”** means the form of the work preferred for making modifications. **1.14. “You” (or “Your”)** means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means **(a)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(b)** ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. ### 2. License Grants and Conditions #### 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: * **(a)** under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and * **(b)** under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. #### 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. #### 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: * **(a)** for any code that a Contributor has removed from Covered Software; or * **(b)** for infringements caused by: **(i)** Your and any other third party's modifications of Covered Software, or **(ii)** the combination of its Contributions with other software (except as part of its Contributor Version); or * **(c)** under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). #### 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). #### 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. #### 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. #### 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. ### 3. Responsibilities #### 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. #### 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: * **(a)** such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and * **(b)** You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. #### 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). #### 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. #### 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. ### 4. Inability to Comply Due to Statute or Regulation If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: **(a)** comply with the terms of this License to the maximum extent possible; and **(b)** describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. ### 5. Termination **5.1.** The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated **(a)** provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and **(b)** on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. **5.2.** If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. **5.3.** In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ### 6. Disclaimer of Warranty > Covered Software is provided under this License on an “as is” > basis, without warranty of any kind, either expressed, implied, or > statutory, including, without limitation, warranties that the > Covered Software is free of defects, merchantable, fit for a > particular purpose or non-infringing. The entire risk as to the > quality and performance of the Covered Software is with You. > Should any Covered Software prove defective in any respect, You > (not any Contributor) assume the cost of any necessary servicing, > repair, or correction. This disclaimer of warranty constitutes an > essential part of this License. No use of any Covered Software is > authorized under this License except under this disclaimer. ### 7. Limitation of Liability > Under no circumstances and under no legal theory, whether tort > (including negligence), contract, or otherwise, shall any > Contributor, or anyone who distributes Covered Software as > permitted above, be liable to You for any direct, indirect, > special, incidental, or consequential damages of any character > including, without limitation, damages for lost profits, loss of > goodwill, work stoppage, computer failure or malfunction, or any > and all other commercial damages or losses, even if such party > shall have been informed of the possibility of such damages. This > limitation of liability shall not apply to liability for death or > personal injury resulting from such party's negligence to the > extent applicable law prohibits such limitation. Some > jurisdictions do not allow the exclusion or limitation of > incidental or consequential damages, so this exclusion and > limitation may not apply to You. ### 8. Litigation Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. ### 9. Miscellaneous This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. ### 10. Versions of the License #### 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. #### 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. #### 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). #### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. ## Exhibit A - Source Code Form License Notice This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. ## Exhibit B - “Incompatible With Secondary Licenses” Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. unsend-0.2.1/LICENSE-PATRON.md000064400000000000000000000065221046102023000135320ustar 00000000000000# Patron License 1.0.0 Payment Platforms: - https://github.com/sponsors/notgull Participating Contributors: - [John Nunley (notgull)](https://github.com/notgull) ## Purpose This license gives everyone patronizing contributors to this software permission to ignore any noncommercial or copyleft rules of its free public license, while continuing to protect contributors from liability. ## Acceptance In order to agree to these terms and receive a license, you must qualify under [Patrons](#patrons). The rules of these terms are both obligations under your agreement and conditions to your license. That agreement and your license continue only while you qualify as a patron. You must not do anything with this software that triggers a rule that you cannot or will not follow. ## Patrons To accept these terms, you must be enrolled to make regular payments through any of the payment platforms pages listed above, in amounts qualifying you for a tier that includes a "patron license" or otherwise identifies a license under these terms as a reward. ## Scope Except under [Seat](#seat) and [Applications](#applications), you may not sublicense or transfer any agreement or license under these terms to anyone else. ## Seat If a legal entity, rather than an individual, accepts these terms, the entity may sublicense one individual employee or independent contractor at any given time. If the employee or contractor breaks any rule of these terms, the entity will stand directly responsible. ## Applications If you combine this software with other software in a larger application, you may sublicense this software as part of your larger application, and allow further sublicensing in turn, under these rules: 1. Your larger application must have significant additional content or functionality beyond that of this software, and end users must license your larger application primarily for that added content or functionality. 2. You may not sublicense anyone to break any rule of the public license for this software for any changes of their own or any software besides your larger application. 3. You may build, and sublicense for, as many larger applications as you like. ## Copyright Each contributor licenses you to do everything with this software that would otherwise infringe that contributor's copyright in it. ## Notices You must ensure that everyone who gets a copy of any part of this software from you, with or without changes, also gets the texts of both this license and the free public license for this software. ## Excuse If anyone notifies you in writing that you have not complied with [Notices](#notices), you can keep your agreement and your license by taking all practical steps to comply within 30 days after the notice. If you do not do so, your agreement under these terms ends immediately, and your license ends with it. ## Patent Each contributor licenses you to do everything with this software that would otherwise infringe any patent claims they can license or become able to license. ## Reliability No contributor can revoke this license, but your license may end if you break any rule of these terms. ## No Liability ***As far as the law allows, this software comes as is, without any warranty or condition, and no contributor will be liable to anyone for any damages related to this software or this license, under any kind of legal claim.*** unsend-0.2.1/LICENSE-THIRD-PARTY000064400000000000000000000033171046102023000136760ustar 00000000000000unsend is partially derived from smol by Stjepan Glavina and contributors, which is licensed under the MIT License or the Apache 2.0 license. Both license notices are replicated below. ---- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.unsend-0.2.1/MAINTAINERS.md000064400000000000000000000011701046102023000132130ustar 00000000000000# Project Maintainers The intellectual property rights for `unsend` are owned by Project Leadership, which consists of the following structure: ## Benevolent Dictator for Life - [John Nunley](https://github.com/notgull) is the Benevolent Dictator for Life (BDFL) of this project. He has the final say in project direction and disputes. ## Other Information Members of Project Leadership may change in the future without license changes. Contract project leadership at the email consisting of: - The BDFL's first initial. - The BDFL's middle initial. - The BDFL's last name. - Zero then one. - The "at" sign. - Gmail dot com. unsend-0.2.1/README.md000064400000000000000000000161771046102023000124130ustar 00000000000000# unsend A thread unsafe runtime for thread unsafe people. Most contemporary `async` runtimes are thread safe, as they are meant to be used in networking applications where multithreading is all but necessary. This kind of hardware parallelism improves the performance of parallel programs. However, you may want to avoid this kind of synchronization instead. Reasons for this include: - You are dealing with data that is `!Send` and therefore cannot be shared between threads. - You want to avoid including the standard library or the operating system. - You are running on embedded hardware that does not support multithreading. - You want to avoid the overhead of synchronization for programs that aren't as parallel. For instance, if your process relies on heavily mutating shared data structures, synchronization may cause more harm than good. Applications like NodeJS and Redis take advantage of thread unsafety in this way. In these cases, it may be worth it to use `unsend` and avoid the issues with synchronization. `unsend` provides the following utilities: - Equivalents to synchronization primitives, like `Mutex` and `RwLock`, but not `Sync` and with no synchronization code. - A multi-producer multi-consumer unbounded channel. - A single-threaded executor. ## The Caveats Most types in this crate, such as the synchronization primitives and channel, involve no synchronization primitives whatsoever. There are no atomics, mutexes or anything that is multiprocessing-aware. However, with executors, this becomes significantly more complicated. `Waker` needs to be `Send + Sync`, meaning that the internal scheduling function has to be thread safe. By default, the executor uses a thread-aware atomic channel to store tasks. However, if the `std` feature is enabled, the `Waker` can detect whether it was woken up from the same thread that it was created in. If this is the case, the executor will use a thread-unsafe channel instead. ## Philosophy - Use lightweight tasks instead of heavy-weight threads. - Share data through thread-unsafe channels instead of thread-safe ones. - Simplicity over complexity. ## Features All features are enabled by default. - `alloc` enables usage of the global allocator in the `alloc` crate, enabling the usage of channels. - `executor` brings in more dependencies and enables the `Executor` type. Requires `alloc`. - `std` enables use of he standard library, enabling certain optimizations. ## Out of Scope Unlike other `async` runtimes, `unsend` deliberately avoids providing certain popular features. Some of these features include: - Future and stream combinators. The [`futures-lite`] and [`futures`] crates provide many more than we could ever provide, and most of them work with thread-unsafe types by default. - Thread pooling. As we avoid synchronization in most cases, thread pooling goes against the philosophy of this crate. The [`blocking`] crate provides a good `async`-aware thread pool. - An I/O reactor. [`async-io`] provides a decent, minimal reactor that works nearly everywhere. The [`tokio`] crate provides a reactor as well. However, in the future it may be a good idea to provide this, as the [`async-io`] reactor involves a substantial amount of synchronization. Please open a PR if you would like to see this feature. [`futures-lite`]: https://crates.io/crates/futures-lite [`futures`]: https://crates.io/crates/futures [`blocking`]: https://crates.io/crates/blocking [`async-io`]: https://crates.io/crates/async-io [`tokio`]: https://crates.io/crates/tokio ## Minimum Supported Rust Version The Minimum Supported Rust Version (MSRV) of this crate is **1.48**. 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.48*. However, the MSRV may be advanced further in the event of a major ecosystem shift or a security vulnerability. ## Examples A basic TCP server using `unsend`, `blocking` and `async-io`. ```rust use async_io::Async; use blocking::{unblock, Unblock}; use futures_lite::prelude::*; use std::cell::Cell; use std::fs::File; use std::net::TcpListener; use unsend::channel::channel; use unsend::executor::Executor; let (tx, rx) = channel(); // A shared value that will be mutated by the tasks. let shared = Cell::new(1); // Spawn a task that will read from the channel and write to a log file. let executor = Executor::new(); executor .spawn(async move { let file = unblock(|| File::create("log.txt")).await.unwrap(); let mut file = Unblock::new(file); while let Ok(msg) = rx.recv().await { let message = format!("Sent out: {}", msg); file.write_all(message.as_bytes()).await.unwrap(); } }) .detach(); executor .run(async { loop { // Listen for incoming connections. let listener = Async::::bind(([0, 0, 0, 0], 3000)).unwrap(); // Accept a new connection. let (mut stream, _) = listener.accept().await.unwrap(); // Spawn a task that will operate on the stream. let tx = tx.clone(); let shared = &shared; executor .spawn(async move { // Read a 4-byte big-endian integer from the stream. let mut buf = [0; 4]; stream.read_exact(&mut buf).await.unwrap(); let value = u32::from_be_bytes(buf); // Multiply it by the shared value. let value = value * shared.get(); // Increment the shared value. shared.set(shared.get() + 1); // Write the value to the stream. stream.write_all(&value.to_be_bytes()).await.unwrap(); // Send the value to be logged. tx.send(value).unwrap(); }) .detach(); } }) .await; ``` ## Credits Parts of this crate are based on [`smol`] by Stjepan Glavina. [`smol`]: https://crates.io/crates/smol ## License `unsend` is free software: you can redistribute it and/or modify it under the terms of either: * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * Mozilla Public License as published by the Mozilla Foundation, version 2. * The [Patron License](https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) for [sponsors](https://github.com/sponsors/notgull) and [contributors](https://github.com/notgull/async-winit/graphs/contributors), who can ignore the copyleft provisions of the GNU AGPL for this project. `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more details. You should have received a copy of the GNU Lesser General Public License and the Mozilla Public License along with `unsend`. If not, see or .unsend-0.2.1/comparisons/.gitignore000064400000000000000000000000041046102023000154370ustar 00000000000000out/unsend-0.2.1/comparisons/README.md000064400000000000000000000005251046102023000147360ustar 00000000000000# Comparisons Comparison between `unsend` and other `async` runtimes. ## Basic "Hello World" Server ![Hello World](https://raw.githubusercontent.com/notgull/unsend/main/comparisons/hello.png) ## "Hello World" but with contention ![Hello World with contention](https://raw.githubusercontent.com/notgull/unsend/main/comparisons/lock.png) unsend-0.2.1/comparisons/hello.png000064400000000000000000000477211046102023000153010ustar 00000000000000PNG  IHDR59tEXtSoftwareMatplotlib version3.5.1, https://matplotlib.org/a pHYsaa?iO>IDATx{|{cmNr>Cj_!"SE|ȡri!!ɡ)F,vF}zoz]z]^0 6r{6C l`3@! f6C l`3@! f6C l`3@! f6C l`3@! f6Clk׮*SS++RllN8u˔)]1@^E _gQFpO>t 9K_'NІ ϯ{W;wֱcl7ɗ]5g맟~ĉͲtI eٯ_?=rvޭ>@˗/UdtVj.]Rsu  3K^Ku *_~={bbb\?[4(P_`\%f͚R"EԡCw\U\YZt)nݺe;%%E РA,kʕ75Gn@ p~,W\Rk_֓O>=z_~є)SԠAٳG9_|}}5dϟ_5j7v=~Osrss͛UjUI͛UxqR//#FҥKψ#4fhB-ZݻլY3x>._ŋ9njG$-Zu8-[jժ=z<==?h֭f jJ[lQ^kĉxb6׭[ O>*Vʗ/{L}yxxu/^Tu~Yfn\Yĉk9~#_|N]t1BBBI2Fini0=j:u(TѠA}ѢE$aƒ%K OOOUVFzUV5{챛3snիg\z5y;~aqYÈ2222z IF.]nx.sO]ve{lTTTzׯ7$}/N2/_n)Sp80 cĉ$_~n9snnn͛ʧOnH2njI2܌8]j!XtSy-{ҾXK@5uTeyeeϕ'|R R~իWM6{%J駟֖-[rׯ/IڴiVx?͛%IoͺgϞ=nΚ5k}pL[0xj>/%K***J.]ٳUV_*###:-RXX*Uy; 6TxxSYƍUX1-X,;Ծ}{K`.yԃ>… ;]SRԴi,?SpՈ#T~}Xb S|7ݾ}{͜9S=zK/&Mm۶z9CxٶqY=_|j׮͛Tyzz?ו+W}eddphŊٮԫWOk׮՟1Bwyf:tH>>>o/// GUR%z#^^^ڴi֯_˗kʕZ`7nիW]R&LmYNkŊjӦ.\J*Zjf`  p+[ Phh*TR[ŋ>ew}'77,~5kϟt=CrssSzCaՊ>"鯕k/-/:Mw7rssS&MԤIM0A ׫iӦ*[ۧ&M8]&Y 4P%`իWO֭ӰaÜXk@.׶m[kԨQ2 iarܖ5k/#Μ9y^zav|MUZU~~~fڵk_u3;M6U5ey4iMu7:w\իKؕ'|R?f̘y;\K.՜9stU˿Vwej̘1щ'ԦM*THǏ_|^z9=ퟌ3|~ /|WjjƍǗ+WNAAA:|k7h@C$hE)^ cǪe˖jѢ٣+VXbdڴigSRT^=IRNpB[ׯWݺuN .ԪUnxɵڷo)ShȑRLVu@_ॗ^R 4qD5J_j֬ZjuSmU\Y7oVLLƎ ծ][n kկ__-2$լYS޺zjv3;cƌQ4}t_^kիum-Zj'N裏>үbŊaÆ5j*ŋkĉ_[{^|śzKC=`t5mԬSR%.]ZxUR 8psmu27w/^Xڵ$)))Iw$ε/sIIIџ)//lǓTs;%% ?P͛7Wɒ%s{(c|nZ ?֬Y=zeAAAJKKSrrS3g(((Ȭ3uW$)&&F.\0_'O-y6Κ5KovYϯkךeVbb"""$IڿΞ=k։:׶Y' n'`FFf͚.](_6E???u]땐nݺ)""Buԑ$5kLԩۧUVi኎$w:v옆 N.\Iy&5k(11Q>l}'NڵkTEFF3kٲez K.=zY'44T˗/׀4yd*UJ3gTdd9?ܔ!<@5Σpl`3@! f6C l`3@! f6C l`3@! f6C l`3@! f6C l`3r{I'ވ!k`3@ɓg :T+Vri֬YU$0 9R3fPrr֭iӦ|fΝS߾}tR]v}vء *22R/_6tQP\\-[M6W^5kL!!!JHH[oW^yE|=_;-ϭ ֬Y̲P߆ahҤI>|Zn-IŋC:tV\]vSLQ-odɒ;wGC+W޽{5aoV,YZj'P@@~͘1q%%%iӦfj׮xIR||'IM6vaiР<<<::|Ο?O x1|V?~iْ$IR``q澤$8ϗ/)T'6RSSKU^uI߯oVӧOW.]rulcǎըQru xSMpws+%JPxxSYXX%IAAA3g89s挹/((HgϞuU;wΩNvm\… ɓr*uNeBBB$uCHPP֮]kOIIю;!IPrr:֭SFFj׮mٴi\b։SŊ8|}}^w< ۷_?y>Ptt$p3f,Ys*Yڴi#GyD={Ν;uVG:tPɒ%%IO?<<<Խ{w8p@ ,ɓ5p:u;"Ͻ_|=Z4i:vh2d.]^z)99Yʕ+U@ܹsէO5i|;cի5kXb1bzy.JR˖-ղew8=zF}:EѼynOժUy['(O@;RsQȹ<@^@! f6C l`3@! f6C l`3@! f6C l`3@! f6C l`3@K,qVZ,RlӦӶaNۙ] qpFFZzW+V(99YꫯTF \ҪE.^>}իgEFF[zҡC .&G?KN8v^y9WJ/_Vtt-*kNgΜqj#11QQQQV@@W:ٰajԨ!OOO+WN7sw-<:3ghzoʕ+k˖-hҥZh6nܨSNm۶tEEE)--M۶mٳ#Fu?(=ڻw=zhժU.K}{1.]Z'O|Zx *_>e)p>C͛7O7$͚5Kaaaھ}ԩիWZfUzu:t^yyxxh %Iaaaڲe&NH& l\rotROӲe˴~+W:rJ,{W;vTbb$)!!AW\QӦMͺ*URҥ/IW*Uh։TJJ8`ֹ:m\OjjRRR^wVҬY35kۨ]bccUbE>}ZFR*))IYk$IRRRSܟFuRRR+۱;VFs , k׮ڵkuYedd8裏rFWZUkVHH.\x`vhvJJyna%QFYfZv~W?uUB RZZ9s|`PPP3 C|}}^wVOXuɪ&%I/^ѣGթS'լYSڵkծ];IÇIRDD^{5={V8*<<ܬW_9gof `ZZz!4h6nܨ'Nh۶mzzJ~~~޽+!!AݺuSDDԩ#Ij֬թS'۷OV-OOOIR޽u1 2D}{=-\P pyyeG7oOzꩧTbE=*Zo߮ŋK&N-[]vjР矛ǻkٲerwwWDDyuYG6넆j劋Sj4~x͜9G[˗h͚5Z„ 9jg7_@M:USNn,xQFڳgOobYoTzuIҷ~pX \dY\~UM6=駟O?ݎ"`FFF-???($$DzW<DzKÆ Ӈ~7xCu֕$mٲE._^{ͪٳ5sLj,Z @<²KΝSJWTIΝȲXZ5Y}]UVͪn".7NQQQZf:y?>we+ 6Çc)99Yj۶>[ \d $s=Y8k,-Z(KE4{l, cǎUbŲ_Ȳ,!!!JLLȲo&K}ThQ, O=+==]Zn^|Eun"~Wu 5iDlFF:w{,XW_}U퓗T,}$)SFalٲJ {Vʕ;7xên"`LLۧ 6@fyӦM`,Fxb-X@uԑ0+WGZ \d /,.]r ]Zjivf9s""".믿͛z&Oj۶mڸqUE֫WO{իWUJ^ZW͚5.A}e˖Ռ3llpڿ_M64, ={IұcԾ}{y{{kѢE2dUE^իW$-ZH 6Լy>̪n"aȐ$YF-Z$_3Fsƍ%I:~.,N4IwV>}4l0+WN駟ꡇȲTZ.Loݭ.9)P7K;l`3+Wlٲ:t9y7p8ԿˊVѢEv̙3N%&&***J uU:6lP5r)665V4d׮]zUjUhҥZh6nܨSNm۶tEEE)--M۶mٳ#Fu?(=ڻw=zhժU@^b%hYVnŋձcG͘1C 6/\?P&LPƍUfM͚5K۶m%IWOիWWꫯjԩJKK$M>]?~ԧO=8q%ȫ, vҥK+22Rm۶uzݬhEEEiӦN rSyJTtiKUJO TJJ8`{ۑfV=_ڵkڵkW}III𐿿Sy``:?S^^^YNMMUjjr', fͲ'O_T\\\{رc5jԨK,} իWf%INŋsFBBΞ=5j(_|ʗ/6nܨwyGS``Ҕtܙ3g$I rWp?vObbbtu@^a ?GyDJMM*THoRSS5}ӤI,)ܭ[7UTICUppϯkך>DEDDH"""kٳ $Wfʩ8xzz3gGY_|EժUKSѢE{L={q; }TV`A-Z,޽"EW}UDDԩ#Ij֬թS'7NIII>|׻wo2d}Y[N .] <Ͳyfm۶MNeʔ?lU7'MڵSjj"##{ݵl2=󊈈PեK=ڬ˗k|XSLѡC$Iaaaӧ*Ude7pe7|gjժZjڽ{T>̪n"V gIȑ#5dS;,[<}:wgӧ.,6jH7oReկ_ߪn".jJCUBB۷oעE4j(-Yĩ.re^${N{>O#pgXY,{ @! f6cYܽ{onjӦ*--ͪn"s=^t1uAZh bU7peW%I-R 4o<>Ȳh05k֨E`VuYkժ1chΜ9ڸq$IǏW``UE'jӧ rI>S=CVuYYժUs 8[o|,.l{o˪PUE'N(===Kyjj~'\6d߫Vk*44n``6m$IC]tqڗ?~)SFǏwXPڵKŊsyP},=YʒoUe7Z`OH"{o>, ӧOWpp$)..Nk֬ʕ+ռys <تn".'%%pٲez'լY3)SFk׶Ȳ… ɓ+WiӦ$0}> re+m۶O?~S%I{1?ϲ8qD)SF'OԸq#I:}^x, נA0.`(IsQzTdI㏒I&/8m4 8P͛7Wrryㇿ&MdU7pepʔ)1c &wwwVZڿۙ6mV*___*""B+V0_|Y*Z|||Ԯ];9sƩDEEE[5kj׮]|X$EDD^ٳgȸ8*<<ܬW_9gofY߿+Wg\Rq;111j޼J.]ӆ j*{8p)"___WS$Yf WN4n8%%%i኎6Wzw}WC ѳ>ui…Z|UgY_|l˧N:={V;wӧ秪UjժU#I8qԮ];*22Ry-[^*XtѣGuBCC|r 0@'OVR4sLlᶼZ͛7WLLf͚~ (P@SNԩS['$$$%޿kԨٓ1XOUH rȲ&0_~-reM6Nnnn*^5jJ*Y \dY9rUM6,Z-nez_PzzU&Yg͚^zI]v5?Q#>>^gرcUL , &Lz,kժT>@6l+ǫVZYkժ;wZ \dY ֌3Ϝ9SVuYv xĉj׮VXڵkKvܩ#G>Ȳ-Zף>sܹszG߫EVuY(u_IX̲@Iڼyy=C%Isі-[.,~gvޭTI҅ XC, cƌ5c ϟ,[vmU7pejРAr???%''[ \dY ?|˖-{.,S/v!áSNiܹ4hy,{ K/ 5iD4h OOO 4H}Ȳp84l0 P<<<|Pׄ ju7E.TĨVZz衇xbIҬY'jv| xĈzմiSm۶MO.,,L۷oW:uzj]?~$),,L[lĉyNs+wIR"E$I r劚6mj֩TJ.xIR||T@NddRRRtεmdl#;JIIqzmtPUn]w}$yxxߩn``:׆nT'%%Eg;vWpppoVHbbbtuMsԧO-[L6mRR )99i̙3 2ܹө̻;Ϝ9#___yyye;&OOOyzz|n)ϭ>}/кu괿f͚ʟ?֮]k>|X$EDDh:{Y'..N 7\Ff6 `tt͛/R 2߳'///{8p)"___WS$Yf WN4n8%%%i኎6Wzw}WC ѳ>ui…Z|y;VM .QF*QZ`Ygĉjٲڵk (((H]˖-"""3Ϩs=zY'44T˗/W\\Uk̙<@0N4uTM:uBBBW_ݰFiϞ=7=FY[E l`3@! f6C l`3@! f6C l`3@! f6C l`3@! f6C l`3@ɓpӦMzGUdI9-^ia1bJ(!///5mTGqs9uQWuE:|ׯ (88Xƍݧdt钪USfܸqzw4}tرC Tdd._lر88-[L6mR^)))j֬BBBzK>~~)_n ;͛7Wͳg&MÇu֒?XZx:tCiʕڵkjժ%I2eZh~[%Kܹs>H\ݫ &8E<x#ǏWRR6mjvڊ$ ԴiSiǎf ìÇwl<x#III@@s_RR˗OEq} wjjRSS픔λVsرcgs{H7 AAA3g89sg:zΝ;T'6bbbtuIO`hhvZ,%%E;vPDD$)""BJHH0[N]YgӦMrY'..N+V$yzzpɓŋڻw+?ݫD9_cƌђ%K~uY%KT6m$IaaazGԳgOܹS[nU>}ԡC,YROCݻwׁ`MjҤԮ];;~???^ZѪY+#F'`Fdu;=ZGn"Eh޼y7jժڼy-n'/! f6C l`3@! f6C l`3@! f6C l`3@! f6C l`3@! f6C lpԩ*S (ڵkkΝ=$p8pFݻwZjٳgs{h ԳgOuM>}Gn|=ܒĘennnjڴ=&55U $I)))eܖv07y{`5]0nKw_U t* w}1cǎըQߖ1"&y{`=no'ymୈ파 ;wNEő))) ɓ'ñ =w0﹃yvw0→dɒ=\cXX1̙3NgΜQPPPxzzөv 1/y{`sݮ+l{j֬kךeZv"""rqdmW%iҥjժ|P&MҥKԭ[mcؾ}{/1bTzu\2ˍ!F28n/=w0﹃y̻8 ; `C} ]l`3@ܲ2ehҤI 9ݻ~/^RKFԿڵڴiRyf+^ ӹJ?Vם1[NJ\݌ 6p(99urpV]4jHիWɓ=\݅Av`pݲ}+H̙3c[˗ג%K̺ϟWǎUxqyyy|5kɓz'"Eu:qℹ?o%JhѢ֕+W:gϞգ>*///jܹ9>cǎUx[lQ`O.]qWƍ奢EW^xbN׮]qFM>>zGf~z=zTׯٳXs׮]uI_^~{"6l {B zꩧ?G#z&OSOӧ?~hB<ۧiӦ?Ԙ1cmc޼yzꩧ4w\u1:C g}ٳgk*W"##ͯy*?㊉oa\͛7 Rll<ɓ'kƌ8qbǐi*Xvءqiъ3?:{VXըQCM4|>}ZO=}Y:tH6lP۶m֭өSi&M0A#GT˖-UpaرC{s=~I?ǏWZg zuaI9w#]v_%K(>>^aE yy2eddu:w6nܨ8;vL۷7,_\=Zh={hڵzW\ѫ}i:q℺vzc9z/^e˖iٲeڸqx sرck:p gyF7nQ_|^|E[=s֭֯_c.$$Ę8qSYjՌ#GaÇ.^hH2VXaapŋ˗/7܌-'6lh,s9uTHOOw:w5 68٥Kuֆau̟?1w\sZZQdIcܸqW^׼>W{ѸRRR OOOcƌmz-f͚ȑ#jժ s*{C׸|Se~:_ $ĉYeaTXѨ_}U`'|bFξ<󌹝aӦM3 #ސdlݺ믿^^^… ﺹ^zn$&&3Od.0:vx6k.Caׯ7$ϟ1#G4lFڵ 0˗/ƶmۜ޽SOeϬY ???C=d'xhѢE͎XKTZwk=?W!Ch۶mf}~PB#)RD/_ѣGz+W]D C)_|YRJNwalc/Q$m۷ONFFF*##CǏy9t萪U euUFFB`C)""Bé/44`ũaÆmѣr֭kϟ_>:$׼6W9סC&M\w, ,Pݺu$ >\ݼyӘ}ĵ_?gn߾}x-tǝ~n''wժUS&MTJ=1cΟ?o\@URvwwWѢE~oܹsuΕPPP\wd~]YVhQUX񮜫k+88XfYxxڻw 裏t*T3zkԻwoL2*T}??CsWaaa:yNڻw9/2o~ر (.]oշo_uiV)SF;vЉ'믿^ɓ'շo_}w/5rH 8r$UPAׯg}v? ?kʕ:xz?Cݻwwiwz^q(P@CՐ!CKP۷oׇ~(I*_5|=zT-ѿM*""Bmڴիu m۶MÆ ?ҭ׎;믿Vbb>s/u;6ޏ+ʗ/֭[gϞڲeۧgyFsZn1w\5mTUTQǎ{nܹS;wVÆ UV-Iȑ#'hȑ:t߯7|S_W <<<4e;vLK,ѫzKcB iРA0`fϞGjݚ2efϞ6XM6MGф 矻|EߎĨaÆjٲԦM-[6{xx(&&FUVU Ki&.]Zm۶UXXw˗/g͚%KaÆj۶zuK+SWjUmܸQׯ_#FPɒ%stVZs?&M\w%f 4H Wu}WڹsU޽{{>|xWXQ֭'|˶ovکSNQ~ZJ viwz^{q\/O#FPXXڷoo_UV0`իk۶mzsN9}WjРu *Csnv|}}i&hB*T5~x5ovlW͚5K5kT˖-!0W_etn+á/R V ԴiS{Z`YQFZh,Yիqڹs*jѢE o~s#^~e;VaaazG|r6mhzUre5k5jt[o03`3@! f6C l`3@! f6C l`3@! f6C l`3@! f6C ?=t0TIENDB`unsend-0.2.1/comparisons/lock.png000064400000000000000000000451771046102023000151310ustar 00000000000000PNG  IHDR59tEXtSoftwareMatplotlib version3.5.1, https://matplotlib.org/a pHYsaa?iIIDATx{|2r,X|BQ|A)r(rC!01l G6뭍~]^5=O0 C žY@! f6C l`3@! f6C l`3@! f6C l`3@! f6C l`3@!nWЂ n̙r8:t?31[Hn {*EСCp8o[_B=C.]p\u-$qN%J=ܣ_999RXaO'PUS{NM65<gU ;r-[VF$ԵkW۷OGyprww/i\Ullbcc^Vll<}رC5ʖ-#Fhƌ7u[Ξ=_Μ9޽{?=ںuSM6TPP|}}ըQ#_ީMݻwcǎ*^ׯ7xCC?s(OOOh `3vR C7n5k֨N:L5h@{SO=Zj~ӢEt*U*OZj^+V=sŹO?uU;w￯.](::ZժU$/p84p@iڴi|:ԩS5x/ۦ[nZ`z쩨(#@Wr;[Z1???Gc= >Ӳe^ӧ~͛0NGI/㓧Mxx4hy饗^ƍ^{lcX ҥKW)))y+777EDDHv?Cmݺ5k.]( @'Ol˗O?<[U2eԣGC'NPZꫯy j׮z衔͝;WjٲYoX l]͚5_|ǏkΜ9_yM6ھ}>􈙿kӦkz衇gX G[￯K)4b-_\W=TX1Ҙ1c̶ӂ Զm[=S֩Sh"M2E5jϞ=A)((H/ҿ}GzԫW/10ʕөSܐr+9sʖ-S5+Vh;v$MӦMSUZ5=䓺/hժU OoΜ9v9[9EnA;ۥKUVMk׮5j(N:裏gJ]˵vZ 2D}f͚5iDe˖/ҾDDDhժUz5rH.]Z?Q|}}գG-[L~rrrTbEM4Iݻw75nXIII>|yeff*,,Lus=wMckN+VP@@Zhʱq+^m ׻wo̼)fPN'Oԇ~:p @ƍjժ:~O -MkѢ,X{OCjհažܔff6CnqANN=[3g(<<\nn<FtѣGQWf[ Xȳ]{700MΗo7l`3@! f6S$/\%KWެ7 CV2e㣦MtԩSJHHP``յkWeff:ٱc4h oooEDDh̘1"wիWOꫯ{n;Vŋ7ی3FoLM6Oqqq:w&!!Av˵xb}zg 5kL˗ז-[kСz9 0 {z~z]6z0#IJOOWhhfΜkϞ=͛#IZtZh#G(<<\'O֠A&OOOs?\{-\322t E)&&Fm۶UHHnM:լ?xԴiS,((HuQRR$)))IfMM6m24l -++KN/M ɓUR%}޽y͚5K&I uz_hhYbŊDNm1nԨQ 2_.-?ժUK#Gw߭g}V\SfE.)SFQQQNeUVUjj$),,Ltq6Ǐ7t /ԩSNm1KN/MžիWO)))NeS%I ʕ+UfMI]̹i&u]ӧOk˖-$}7Q:u6 ҅ !IZ|*Wt1VžMž->}{ȑ#c{g>pw1b*UHUxxZn-#>y… ٳڷopIRǎ+k׮0`~M0Aƍ+]l_׆_\U=ܣ>L԰a+!!lӿ={V>N>kҥ6̞=[={T&M6m護2냂l2%&&*::ZJp+*r!6nu~@pcl`3@!L{ P;p7*ܜ8`3@! f6C l`3@! f6C l`3@! f6Cb=\^nFipf6)`).36\j[ Gl`3@! f6C l`3@! f6C 8tP9W*Us)11Q%KڴiǏ;x*$$Dŋڬ^ZjՒ*V3gP\jժرckݺuf]>}Oך5ktQ=f}vvuymذAf̙35x`Oݻ~i}~b=+VLaaay5}t͙3G$iƌZ6nܨujٲeڽ{VXPլYSÇ׀4tPyzzjʔ)رc%IUVպu4n8 O+GGoWBBRSS%I[lх ԴiSm*UT\9%%%ITzum┑]vm.#Mn ͦ:uh̙Zt&OA:s`*--Mrs&##Ce6j(WwWN7o]wݥ:u|7o||| qfշo_s;##n:E;O?0?^Ovjsq}4a-[VӦM0\OX'jĉmS|y}WqڶmufVN" f6C l`3@! f6C l`3@! f6C l`3@! f6C l`3@!L1W޼hѢ}] q)nip0 \ٮ t 8''|-[L5kW_}ӧO/UV--]ԪE.T޽5eկ_,}YٳǪnٿСCV YWǏ7ˎ?~vV Y};vLʕSŊUbE+WNOn0peVXQ;v˵w^IRժUմiSP, _}i֬5kfepʕZrN8ʡp, )SӾEepʔ)9sx  `]ϟ׽kUwA, O?̙cUwA, ΝӛoFW^۷z=ZC{v+11Q%KڴijIJMMU|||}}~ŋNmV^ZjK+V̙3{7 ˮܱcj֬)I͛7w]wTާO-YDWPPzG}Tׯ$egg+>>^aaaڰa;N:C#G$| CSSLQddƎ+鯇V[Nƍ#[e/u9rĥ>M:oٲE.\p*Rʕ+$IRRRWPM\\222k.3UYsrr4l0|*_5|<O>D[nըQԥSN塡JKK3\rs&##CgRFF fc)Ai=zի'IZnsW_-P? /˗۪YbԨQzW {.Y4m4u]wu.CSNkl٢'NVZ*V+5k護RbN;~$Iaaay ݾZ@;*==|>|PTXO:*U)RN:U~4i;w*99|(!!V\i'%%E$jΝ:qf TTT>r///:n6Qy[NjԨQ~˩O%K4˻v}D T^uJ5k(=3f/+11Q^^^nݺwQSOoѼydWȳ,3FZby-))I֗_~i0qMmڴQVV4i$]/V+???uYÆ 3DFFjɒ%ӧ&Lejڴi<, 5RJJ&M{J}QC.zjmoooM8Q'N{ʗ/ٸqcm۶ͥl, tmn_n1cϟ|5kUEQFTRyCBB@,*222Oy啚j0pe0$$D;vS}v,YҪa"`kժUVvv j߾UE<|p:tHM4QbuN:q @bYܹs5|pm߾]>>>^ʗ/o *0 q@]k׮Uj;{ѣG[5 \dY8po߮իW,oڴΝk0peh?s͝;Wu֕0˫U[5 \d_U!!!yϞ=P, 111ZdMX,;sڷo$j׮|}}5|ߪa"}TfMIըQ#͙3G3g… .,IҊ+ԢE IRDD~7,}#j͚5$H={ԋ/GyD-2_W2ydu] T``bccW_ΝSbbJ,)iFǏw#55UUHH맋/:Yzjժ%///UXQ3gδj)4ˮѣ$iҤI4iRu_W*[FJ*0 ͚5KZҶmTZ5GK,={G%IيWXX6l`ȑ#%IT||uٳgkʕzULY$Ee0''ǒ~Zlꫯjڸqʖ-ӧkΜ9%I3fPժUqFխ[W˖-ݻb f͚>| CSSLQddƎ+լ[Nƍ#[eol}':{bcce]pAM65TREʕSRR$)))IիWWhh&..Nڵk>rp+AVٹsbccu9>STT`JKK$9ܺ+ПyٯRVV~"yrJNN֦MԽ{wuYw.iiԨQ 2_=%kV$*Vh5J5jЄ N?0IRXXXs&00G$iJOO7_vuWqE2]NN-\ҬKIIQjjbcc%IڹsN8aY|e6}\xƲuVܹ/ԺukK:|8p[:tH;wzj%$$(((H]vU߾}j*mٲEO>bccUn]IRf'xB۷o__~Y$uMPw^M4IS>}Z"˲si߾}}տs uI+WV&Myf}z$IƍC=6mڨaÆ ӧ~j]/bcc㏫SNNQ%KhQƎiӦ` o>լYS4|5lPsվ}{?@L>ޚ8q&Nx6˗ח_~y~7nm۶hNˎa> zŊjѢ$)""BUE1B~֬YxI}ߟcY7nnݪ={jРAX$i{.5j8_WbE Glɲ#~N<ܹs;.,:tHyʳt\>7h"_l\R[n-Ir8ܹS*TcǺ: ,r}_dd6oެRJ<)8ݞ{1c_t1êa")ӧUC](I~ׯpϒ/r8ydW͛7ӧ?5~x, oNA,Ν;.,hªa"ˎJ9r]bk׽ޫ_~EjݺuVX.\8h֭ʒ$sT,1BSLԩSa׫WO[njȲ ) ӧ.,駟~Sn:~V Yy ڴi=ٳg?w^~F{G Qܹ֭sJLLTɒ%6mNmRSS/____~xSիWVZRŊ5se_TǎդIeffaÆzsϩW^g͚5JLLƍ|r]pA͚5ٳg6}?͟?_k֬ѣG裏يa͚5K3g6T||>%''wz_[ Eet84h맟~IOK:mϜ9S!!!ڲe6ltM>]s/I1cV7nݺZlvޭ+V(44T5k5` :T2e"##5vXIRժUn:7NqqqV- @cs%SQQQ]<<rpr9feeiѽޫ?\_G"##5n8Q޽U^=_$SNmCCCf4]MFFkFF f)wUӦMamVO>6nܨ7|Sm۶u~|ȨQ+4\>Ђ l2eggŋھ}ڷogϞZxVZe˚aaa:|g ?~\aaaf}6^qJOO7_}(L.#G(::ZK^^^ӧugz>L|Mᡕ+We)))JMMUll$)66V;wԉ'6˗/W``6&xyy)00pqpvv<==ŊKLLԜ9s_( f/((H>>> R׮]շo_(QBիbccUn]IRf'xBcƌQZZ^~e%&&Kԭ[7;߿z)}77o,Yj}.@0ԥK3X;wNݺuSO?@MYf͚|ڲe^u :T ?T'͛y_VV$I|BCC矫}ڳg.]͛7+&&FoEz7ٳgzjժ)99YoSP#WrAiӦfYPPԩ$IRRR'IM66mdiذ<==6qqqJIIYYYpzln&I u* 5T_X1(Q©M~}\:ߍ5JAAA+""t0 8PÅ=%kv0Iǝʏ?nօĉN/^ԩSǥc^7.FFF*,,L+W4222i&Jbccuimٲl7(''Gu1|pf\/ ?HL%''+99Y_7~$''+55UC{ֈ#h"ܹS:uRxxZn-IZ|A=3~zS۷Wxx$cǎT׮]k.͝;W&LP߾} iE10>s;7uY3gTuY=:}ׯK|ٳճgO5iDnnnjӦz->((H˖-SbbUT)  PHkZ8\wȾf͚:t$phڴizGJ*iѢEf] *]|||TR%͘1ì?|{1Djժ:dVx7TL,YRpĉjٲ|||ٳg_׾YFkזʔ)_|Q/^4srr4fUXQ^^^*W^}|SO=*U(55s%]tњ5k4a99:twK,QPPf?^!!!Vykoaj;o]\]:uR͛tj~z5nX*^ײ,XիG%KTӦMuYg1rH*88XÆ ŋկ_?(QBe˖u쑤;w7{gym7jժ{,{=#ooo*UJΟ?oj*߿_VҬY4sL͜9Ӭҥ>UVi4i5/jѢm߾]'O5b5zhs̙Զm[%''kڵ*W5ͥ &LX=3:v옎;&å̙:hJHHȷMpB͚5K[nUŊg| nk3gjݚ0aNqxҥ{-ZHIII2 C-Z0err4i(%%%iݺujٲUϱcԡC=SڳgV^G}7ѣoojȐ!z衇TxqmڴIݺus=#GHΞ=8/^\7ob )''GZҩSf-_\Pv6K,#<-Zh۶mZrj׮ocƌы/e˖I&… >|o߮?\R.]{N_cǎo;v(..N?L5jH-Z۷׎;T~}uQKsEFq9ըQ2da!xͺLCW_a-[4||ʕ+999fYVVc|׆aFΝ/^4۴mh׮abH2;~Ϟ=< Iƶm 0^z)\&NhFFFeL:]hҤQ~}ӗ 52^xsjp{y#((XzS;w6Zje_?Kcfpc̘1WQ\[U~^u#::2dQF+ڷo!X~Yo>>>Ƽy 0:ջj[˂زe!8tPωܟaFʕ /^4?0 xŋf%KnnnFZZor.\lnvr5_2e?Ϗ]6olH2Μ9caZʐd}=S{ѣaɓ'ŋoFM殻2@\'f͚߿6l`ݾ}~'_*QΝ;U&wwwsL2f{QbmWREvn݇={(66hSff9={(++ СΞ=e˖׈]mr-X@}ըQ~]pA3<<}tQ5iD$u(=::ZN}cǎ )P-ZhǎJJJq w}J.kRn*))I jѢ/^m۶iРANT\*&&iN? Zkꫯ~[+W%9gǥ~a~ V 4Pvv͛w]carOKjڼy>3IK4l0甫tҪ]>cedd\8vE,BJ.cǎȵѹsg}G?~{=IRZ?*$$D+VtzO*UtEmٲ,KIIqzOժUkr_^*[*U$^ݽ{w=Z?֬YS}^NX]mrqZj qͲ .h͊t]jÆ *_ UTI?e4|tEO<s뮫 Wp8T^=+ڶm<==_תjժھ}Ξ=k_^nnn\rNk_Z>Çewӧij׮J#Goq]w޽{uI=Z 4P*Uzs Spǹz霒xm/^,ooo̙3W?`r?ڵksNut ^zi޽/4dWnnnրԿ}ڿ6nܨӧW^1bz![q-*TM6СC߮;j*-\Sկ_?-]Tw3SɵJHH:w~AVR^O{RA4mTիWWBBnݪN:uRF̣`C !ChϞ=ڹs^{<}{/+\+Vʕ~m8p@-ïk.ׯ^{5͝;W)))z^xA_uZ~8 93%KXbj޼wLF]}<ӍvFDD1s<7|gN 2f̘aa >ܨZc(QhժqcnjN:J2oxgt0^05jG||e+WyR0 c=cxzzaaaƀ .ƈ#Fr匑#G^cN[)%%Ũ[cH2#@IENDB`unsend-0.2.1/comparisons/run_comparison.py000064400000000000000000000067661046102023000171040ustar 00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 # This file is a part of `unsend`. # # `unsend` is free software: you can redistribute it and/or modify it under the # terms of either: # # * GNU Lesser General Public License as published by the Free Software Foundation, either # version 3 of the License, or (at your option) any later version. # * Mozilla Public License as published by the Mozilla Foundation, version 2. # * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) # for sponsors and contributors, who can ignore the copyleft provisions of the above licenses # for this project. # # `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License and the Mozilla # Public License along with `unsend`. If not, see . # Run comparisons between different async runtimes running a basic HTTP server. import subprocess as sp import os import matplotlib.pyplot as plt def processWrkOutput(output): """Processes the output of the `wrk` command""" # Split output by newlines. lines = output.split("\n") # Get the line with "Req/Sec" in it reqSecLine = [line for line in lines if "Req/Sec" in line][0] print(reqSecLine) # Split by whitespace and filter out empty strings. reqSecLine = [item for item in reqSecLine.split(" ") if item != ""] # Get the average number of requests per second reqSec = reqSecLine[1] # If it ends with "k", multiply by 1000. if reqSec.endswith("k"): reqSec = float(reqSec[:-1]) * 1000 else: reqSec = float(reqSec) # Return the average number of requests per second. return reqSec def runWrkForPackage(package): """Runs the `wrk` command for the given package""" print(f"Running wrk for {package}...") # Run the "cargo build --release" command for the package. sp.run(["cargo", "build", "--release", "-p", package]) # In the background, use cargo run to run the package. process = sp.Popen(["cargo", "run", "--release", "-p", package], stdout=sp.PIPE, stderr=sp.PIPE) # Run the wrk command. wrk = sp.run(["wrk", "-t12", "-c400", "-d2s", "http://localhost:8000/index.html"], stdout=sp.PIPE, stderr=sp.PIPE) # Kill the process. process.kill() # Return the output. output = wrk.stdout.decode("utf-8") return processWrkOutput(output) def runCollectionAndPlot(packages, outPath, title): """Runs the list of packages and plots them""" # Run the wrk command for each package. results = [runWrkForPackage(package) for package in packages] # Plot the results. plt.bar(packages, results) plt.ylabel("Requests per second") plt.title(title) plt.savefig(outPath) # Clear the plot. plt.clf() HELLO = ["unsend-hello", "tokio-hello", "tokio-local-hello", "smol-hello", "smol-local-hello"] LOCK = ["unsend-lock", "tokio-lock", "tokio-local-lock", "smol-lock", "smol-local-lock"] def main(): """Main function""" # Create the output directory. os.makedirs("out", exist_ok=True) # Run the hello world example. runCollectionAndPlot(HELLO, "out/hello.png", "Hello world HTTP server") runCollectionAndPlot(LOCK, "out/lock.png", "Locking HTTP server") if __name__ == "__main__": main() unsend-0.2.1/examples/tcp_client.rs000064400000000000000000000032371046102023000154350ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see //! A naive TCP client to go along with tcp_server. use async_io::Async; use futures_lite::prelude::*; use std::net::TcpStream; fn main() { futures_lite::future::block_on(async { let mut client = Async::::connect(([127, 0, 0, 1], 3000)) .await .unwrap(); let number = 42i32; client.write_all(&number.to_be_bytes()).await.unwrap(); let mut buf = [0u8; 4]; client.read_exact(&mut buf).await.unwrap(); let response = i32::from_be_bytes(buf); println!("Response: {}", response); }); } unsend-0.2.1/examples/tcp_server.rs000064400000000000000000000067231046102023000154700ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see //! A naive TCP server. use async_io::Async; use blocking::{unblock, Unblock}; use futures_lite::prelude::*; use std::cell::Cell; use std::fs::File; use std::net::TcpListener; use unsend::channel::channel; use unsend::executor::Executor; fn main() { async_io::block_on(async { let (tx, rx) = channel(); // A shared value that will be mutated by the tasks. let shared = Cell::new(1); // Spawn a task that will read from the channel and write to a log file. let executor = Executor::new(); executor .spawn(async move { let file = unblock(|| File::create("log.txt")).await.unwrap(); let mut file = Unblock::new(file); while let Ok(msg) = rx.recv().await { let message = format!("Sent out: {}\n", msg); file.write_all(message.as_bytes()).await.unwrap(); } }) .detach(); executor .run(async { loop { // Listen for incoming connections. let listener = Async::::bind(([0, 0, 0, 0], 3000)).unwrap(); // Accept a new connection. let (mut stream, _) = listener.accept().await.unwrap(); // Spawn a task that will operate on the stream. let tx = tx.clone(); let shared = &shared; executor .spawn(async move { // Read a 4-byte big-endian integer from the stream. let mut buf = [0; 4]; stream.read_exact(&mut buf).await.unwrap(); let value = u32::from_be_bytes(buf); // Multiply it by the shared value. let value = value * shared.get(); // Increment the shared value. shared.set(shared.get() + 1); // Write the value to the stream. stream.write_all(&value.to_be_bytes()).await.unwrap(); // Send the value to be logged. tx.send(value).unwrap(); }) .detach(); } }) .await; }); } unsend-0.2.1/src/channel.rs000064400000000000000000000233151046102023000136710ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! An MPMC channel. use crate::{Event, EventListener, IntoNotification}; use alloc::collections::VecDeque; use alloc::rc::Rc; use core::cell::{Cell, RefCell}; use core::fmt; use core::pin::Pin; struct Channel { /// The underlying data. data: RefCell>, /// Is the channel closed? closed: Cell, /// The number of senders. senders: Cell, /// The number of receivers. receivers: Cell, /// The event for waiting for new items. event: Event>, } /// A sender for an MPMC channel. pub struct Sender { /// The origin channel. channel: Rc>, } /// A receiver for an MPMC channel. pub struct Receiver { /// The origin channel. channel: Rc>, } /// Create a new MPMC channel. pub fn channel() -> (Sender, Receiver) { let channel = Rc::new(Channel { data: RefCell::new(VecDeque::new()), senders: Cell::new(1), receivers: Cell::new(1), closed: Cell::new(false), event: Event::new(), }); ( Sender { channel: channel.clone(), }, Receiver { channel }, ) } impl Sender { /// Send an item. pub fn send(&self, item: T) -> Result<(), ChannelClosed> { if self.channel.closed.get() { return Err(ChannelClosed { _private: () }); } let mut item = Some(item); // Try to send the event directly. self.channel.event.notify(1.tag_with(|| item.take())); // If the event was not sent, push the item to the queue. if let Some(item) = item { self.channel.data.borrow_mut().push_back(item); } Ok(()) } } impl Clone for Sender { fn clone(&self) -> Sender { let new_senders = self.channel.senders.get() + 1; self.channel.senders.set(new_senders); Sender { channel: self.channel.clone(), } } } impl Drop for Sender { fn drop(&mut self) { let new_senders = self.channel.senders.get() - 1; self.channel.senders.set(new_senders); if new_senders == 0 { self.channel.closed.set(true); self.channel .event .notify(core::usize::MAX.tag_with(|| None)); } } } impl Receiver { /// Try to receive an item. pub fn try_recv(&self) -> Result { // Try to receive an item from the queue. self.channel.data.borrow_mut().pop_front().ok_or_else(|| { if self.channel.closed.get() { TryRecvError::Closed } else { TryRecvError::Empty } }) } /// Wait for a new item. pub async fn recv(&self) -> Result { let mut listener = EventListener::new(&self.channel.event); { let mut listener = unsafe { Pin::new_unchecked(&mut listener) }; loop { // Wait for a new item. if let Some(item) = self.channel.data.borrow_mut().pop_front() { return Ok(item); } if self.channel.closed.get() { return Err(ChannelClosed { _private: () }); } // Use the listener. if let Some(item) = listener.as_mut().await { return Ok(item); } } } } } impl Clone for Receiver { fn clone(&self) -> Receiver { let new_receivers = self.channel.receivers.get() + 1; self.channel.receivers.set(new_receivers); Receiver { channel: self.channel.clone(), } } } impl Drop for Receiver { fn drop(&mut self) { let new_receivers = self.channel.receivers.get() - 1; self.channel.receivers.set(new_receivers); if new_receivers == 0 { self.channel.closed.set(true); self.channel .event .notify(core::usize::MAX.tag_with(|| None)); } } } /// The channel has been closed. #[derive(Debug)] pub struct ChannelClosed { _private: (), } impl fmt::Display for ChannelClosed { #[cfg_attr(coverage, no_coverage)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "channel closed") } } #[cfg(feature = "std")] impl std::error::Error for ChannelClosed {} /// The `try_recv` operation failed. #[derive(Debug)] pub enum TryRecvError { /// The channel has been closed. Closed, /// The channel is empty. Empty, } impl fmt::Display for TryRecvError { #[cfg_attr(coverage, no_coverage)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TryRecvError::Closed => write!(f, "channel closed"), TryRecvError::Empty => write!(f, "channel empty"), } } } #[cfg(feature = "std")] impl std::error::Error for TryRecvError { #[cfg_attr(coverage, no_coverage)] fn cause(&self) -> Option<&dyn std::error::Error> { match self { TryRecvError::Closed => Some(&ChannelClosed { _private: () }), TryRecvError::Empty => None, } } } #[cfg(test)] mod tests { use super::*; use futures_lite::future; #[test] fn test_channel() { future::block_on(async { let (sender, receiver) = channel(); sender.send(1).unwrap(); sender.send(2).unwrap(); sender.send(3).unwrap(); assert_eq!(receiver.try_recv().unwrap(), 1); assert_eq!(receiver.recv().await.unwrap(), 2); assert_eq!(receiver.try_recv().unwrap(), 3); assert!(receiver.try_recv().is_err()); drop(sender); assert!(receiver.recv().await.is_err()); }); } #[test] fn test_channel_clone() { future::block_on(async { let (sender, receiver) = channel(); let sender2 = sender.clone(); sender.send(1).unwrap(); sender2.send(2).unwrap(); assert_eq!(receiver.try_recv().unwrap(), 1); assert_eq!(receiver.try_recv().unwrap(), 2); assert!(receiver.try_recv().is_err()); drop(sender); drop(sender2); assert!(receiver.recv().await.is_err()); }); } #[test] fn test_channel_recv_clone() { future::block_on(async { let (sender, receiver) = channel(); let receiver2 = receiver.clone(); sender.send(1).unwrap(); sender.send(2).unwrap(); assert_eq!(receiver.try_recv().unwrap(), 1); assert_eq!(receiver2.try_recv().unwrap(), 2); assert!(receiver.try_recv().is_err()); assert!(receiver2.try_recv().is_err()); drop((receiver, receiver2)); assert!(sender.send(3).is_err()); }); } #[test] fn test_send_direct() { future::block_on(async { let (sender, receiver) = channel(); // Start receiving. let recv = receiver.recv(); futures_lite::pin!(recv); // Poll once. assert!(future::poll_once(&mut recv).await.is_none()); // Send an item. sender.send(1).unwrap(); // Poll again. assert_eq!(future::poll_once(&mut recv).await.unwrap().ok(), Some(1)); }); } #[test] fn test_recv_and_drop() { future::block_on(async { let (sender, receiver) = channel::(); // Start receiving. let recv = receiver.recv(); futures_lite::pin!(recv); let receiver2 = receiver.clone(); let recv2 = receiver2.recv(); futures_lite::pin!(recv2); // Poll once. assert!(future::poll_once(&mut recv).await.is_none()); assert!(future::poll_once(&mut recv2).await.is_none()); // Drop the sender. drop(sender); // Poll again. assert!(recv.await.is_err()); assert!(recv2.await.is_err()); }); } #[test] fn test_channel_drop() { future::block_on(async { let (sender, receiver) = channel(); sender.send(1).unwrap(); sender.send(2).unwrap(); sender.send(3).unwrap(); drop(sender); assert_eq!(receiver.try_recv().unwrap(), 1); assert_eq!(receiver.try_recv().unwrap(), 2); assert_eq!(receiver.try_recv().unwrap(), 3); assert!(receiver.try_recv().is_err()); assert!(receiver.recv().await.is_err()); }); } } unsend-0.2.1/src/event.rs000064400000000000000000000645251046102023000134120ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! Event handlers. use core::borrow::Borrow; use core::cell::{Cell, RefCell, UnsafeCell}; use core::future::Future; use core::marker::PhantomPinned; use core::mem; use core::pin::Pin; use core::ptr::NonNull; use core::task::{Context, Poll, Waker}; #[cfg(feature = "alloc")] use alloc::rc::Rc; use __private::NotificationSealed; /// Wait for an event to occur. pub struct Event(RefCell>); impl Event { /// Create a new event. pub const fn new() -> Self { Self(RefCell::new(Inner { head: None, tail: None, first: None, notified: 0, len: 0, })) } /// Create a new listener for this event. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[cold] pub fn listen(&self) -> Pin>> { alloc::boxed::Box::pin(EventListener::new(self)) } /// Notify this event. #[inline] pub fn notify(&self, notify: impl IntoNotification) -> usize { let notify = notify.into_notification(); let is_additional = notify.is_additional(); let mut inner = self.0.borrow_mut(); if is_additional { // If there are no events, return. if inner.len == 0 { return 0; } } else { // If there aren't enough events, return. if inner.notified > notify.count() { return 0; } } // Notify the event. inner.notify(notify) } } pin_project_lite::pin_project! { /// A listener for an event. pub struct EventListener<'a, T> { #[pin] listener: Listener>, } } impl<'a, T> EventListener<'a, T> { /// Create a new event listener. #[inline] pub fn new(event: &'a Event) -> Self { Self { listener: Listener::new(event), } } /// Make sure this is inserted into the list. #[inline] pub fn listen(self: Pin<&mut Self>) { let listener = self.project().listener; if !listener.in_list { listener.insert(); } } } impl Future for EventListener<'_, T> { type Output = T; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().listener.poll(cx) } } #[cfg(feature = "alloc")] pin_project_lite::pin_project! { /// A listener for an event over an `Rc`. #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub struct EventListenerRc { #[pin] listener: Listener>>, } } #[cfg(feature = "alloc")] impl EventListenerRc { /// Create a new event listener. #[inline] pub fn new(event: Rc>) -> Self { Self { listener: Listener::new(event), } } /// Make sure this is inserted into the list. #[inline] pub fn listen(self: Pin<&mut Self>) { let listener = self.project().listener; if !listener.in_list { listener.insert(); } } } #[cfg(feature = "alloc")] impl Future for EventListenerRc { type Output = T; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().listener.poll(cx) } } /// A listener for an event. struct Listener> + Clone> { /// The event that this listener is listening to. event: B, /// Is this listener in the linked list? in_list: bool, /// The entry for this listener. entry: UnsafeCell>, /// This listener should not be moved after being pinned. _pin: PhantomPinned, } impl> + Clone> Listener { /// Create a new event listener. fn new(event: B) -> Self { Self { event, in_list: false, entry: UnsafeCell::new(Entry { next: Cell::new(None), prev: Cell::new(None), state: Cell::new(State::Created), }), _pin: PhantomPinned, } } /// Insert this listener into the linked list. #[cold] fn insert(self: Pin<&mut Self>) { let evt = self.event.clone(); let mut inner = evt.borrow().0.borrow_mut(); // SAFETY: We've locked the inner state, so we can safely access the entry. let entry = unsafe { &mut *self.entry.get() }; *entry = Entry { next: Cell::new(None), prev: Cell::new(inner.tail), state: Cell::new(State::Created), }; let entry = unsafe { &*self.entry.get() }; // Set the next pointer of the previous entry. match mem::replace(&mut inner.tail, Some(entry.into())) { None => inner.head = Some(entry.into()), Some(t) => unsafe { t.as_ref().next.set(Some(entry.into())) }, } // If there are no unnotified entries, this is the first one. if inner.first.is_none() { inner.first = inner.tail; } // Increment the number of entries. inner.len += 1; unsafe { self.get_unchecked_mut().in_list = true; } } /// Remove this listener from the linked list. fn remove(self: Pin<&mut Self>, propagate: bool) -> Option { let evt = self.event.clone(); let mut inner = evt.borrow().0.borrow_mut(); // SAFETY: We've locked the inner state, so we can safely access the entry. let entry = unsafe { &*self.entry.get() }; let prev = entry.prev.get(); let next = entry.next.get(); // Unlink from the previous entry. match prev { None => inner.head = next, Some(p) => unsafe { p.as_ref().next.set(next); }, } // Unlink from the next entry. match next { None => inner.tail = prev, Some(n) => unsafe { n.as_ref().prev.set(prev); }, } // If this was the first unnotified entry, update the next pointer. if inner.first == Some(entry.into()) { inner.first = next; } // Entry is now unlinked, so we can now take it out. let entry = mem::replace( unsafe { &mut *self.entry.get() }, Entry { next: Cell::new(None), prev: Cell::new(None), state: Cell::new(State::Created), }, ) .state .into_inner(); // Decrement the number of entries. inner.len -= 1; unsafe { self.get_unchecked_mut().in_list = false; } match entry { State::Notified(tag, additional) => { // If this entry was notified, decrement the number of notified entries. inner.notified -= 1; if propagate { inner.notify(SingleNotify { additional, tag: Some(tag), }); None } else { Some(tag) } } _ => None, } } /// Registers this entry into the linked list. /// /// # Safety /// /// We must be inserted into the linked list. #[allow(unused_unsafe)] unsafe fn register(self: Pin<&mut Self>, waker: &Waker) -> RegisterResult { let inner = self.event.borrow().0.borrow_mut(); // SAFETY: We've locked the inner state, so we can safely access the entry. let entry = unsafe { &*self.entry.get() }; // Take out the state and check it. match entry.state.replace(State::Created) { State::Notified(tag, additional) => { // We have been notified, remove the listener. entry.state.set(State::Notified(tag, additional)); drop(inner); let tag = self.remove(false).unwrap(); RegisterResult::Notified(tag) } State::Waiting(task) => { // Only replace the task if it's different. entry.state.set(State::Waiting({ if !task.will_wake(waker) { waker.clone() } else { task } })); RegisterResult::Registered } _ => { // We have not been notified, so we can register the task. entry.state.set(State::Waiting(waker.clone())); RegisterResult::Registered } } } fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if !self.in_list { self.as_mut().insert(); } // SAFETY: We are in the list. match unsafe { self.register(cx.waker()) } { RegisterResult::Notified(tag) => Poll::Ready(tag), RegisterResult::Registered => Poll::Pending, } } } impl> + Clone> Drop for Listener { fn drop(&mut self) { if self.in_list { unsafe { Pin::new_unchecked(self).remove(true); } } } } pub trait Notification: NotificationSealed {} struct SingleNotify { additional: bool, tag: Option, } impl NotificationSealed for SingleNotify { type Tag = T; fn count(&self) -> usize { 1 } fn is_additional(&self) -> bool { self.additional } fn next_tag(&mut self) -> Self::Tag { self.tag.take().unwrap() } } impl Notification for SingleNotify {} pub trait IntoNotification: Sized { type Tag; type Notify: Notification; fn into_notification(self) -> Self::Notify; fn additional(self) -> Additional { Additional(self.into_notification()) } fn tag(self, tag: T) -> Tag { Tag { inner: self.into_notification(), tag, } } fn tag_with(self, f: F) -> TagWith where F: FnMut() -> T, { TagWith { inner: self.into_notification(), tag_fn: f, } } } macro_rules! notify_int { ($($ty:ty)*) => {$( impl IntoNotification for $ty { type Tag = (); type Notify = Notify; #[allow(unused_comparisons)] fn into_notification(self) -> Self::Notify { if self < 0 { panic!("negative notification count"); } Notify(self as usize) } } )*}; } notify_int! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize } impl IntoNotification for N { type Tag = N::Tag; type Notify = N; fn into_notification(self) -> Self::Notify { self } } struct Inner { /// The head of the linked list of entries. head: Option>>, /// The tail of the linked list of entries. tail: Option>>, /// The first non-notified entry in the linked list. first: Option>>, /// Number of entries that are currently notified. notified: usize, /// Total number of entries. len: usize, } impl Inner { #[cold] fn notify(&mut self, mut notify: impl Notification) -> usize { let is_additional = notify.is_additional(); let mut count = notify.count(); if !is_additional { // Make sure we're not notifiying more than we have. if count <= self.notified { return 0; } count -= self.notified; } let mut notified = 0; while count > 0 { count -= 1; // Notify the first entry. match self.first { None => break, Some(e) => { // Get the entry. let entry = unsafe { e.as_ref() }; self.first = entry.next.get(); notified += 1; // Set state to `Notified` and notify. if let State::Waiting(wake) = entry .state .replace(State::Notified(notify.next_tag(), is_additional)) { wake.wake(); } // Bump the notified count. self.notified += 1; } } } notified } } struct Entry { /// Pointer to the next entry in the linked list. next: Cell>>>, /// Pointer to the previous entry in the linked list. prev: Cell>>>, /// The state of this entry. state: Cell>, } enum State { /// The entry was just created. Created, /// The entry is waiting for an event. Waiting(Waker), /// The entry has been notified with this tag. Notified(T, bool), } enum RegisterResult { Registered, Notified(T), } #[doc(hidden)] pub struct Notify(usize); impl NotificationSealed for Notify { type Tag = (); fn is_additional(&self) -> bool { false } fn count(&self) -> usize { self.0 } fn next_tag(&mut self) -> Self::Tag {} } impl Notification for Notify {} /// Make a notification use additional notifications. #[doc(hidden)] pub struct Additional(N); impl NotificationSealed for Additional { type Tag = N::Tag; fn is_additional(&self) -> bool { true } fn count(&self) -> usize { self.0.count() } fn next_tag(&mut self) -> Self::Tag { self.0.next_tag() } } impl Notification for Additional {} /// Notification that uses a tag. #[doc(hidden)] pub struct Tag { inner: N, tag: T, } impl NotificationSealed for Tag { type Tag = T; fn is_additional(&self) -> bool { self.inner.is_additional() } fn count(&self) -> usize { self.inner.count() } fn next_tag(&mut self) -> Self::Tag { self.tag.clone() } } impl Notification for Tag {} /// Notification that uses a tagging function. #[doc(hidden)] pub struct TagWith { inner: N, tag_fn: F, } impl T, T> NotificationSealed for TagWith { type Tag = T; fn is_additional(&self) -> bool { self.inner.is_additional() } fn count(&self) -> usize { self.inner.count() } fn next_tag(&mut self) -> Self::Tag { (self.tag_fn)() } } impl T, T> Notification for TagWith {} mod __private { #[doc(hidden)] pub trait NotificationSealed { type Tag; fn count(&self) -> usize; fn is_additional(&self) -> bool; fn next_tag(&mut self) -> Self::Tag; } } #[cfg(all(test, feature = "std"))] mod tests { use super::*; use futures_lite::future; use waker_fn::waker_fn; use std::sync::{Arc, Mutex}; use std::task::{Context, Wake, Waker}; fn is_notified(listener: Pin<&mut EventListener<'_, ()>>) -> bool { future::block_on(future::poll_once(listener)).is_some() } fn is_notified_rc(listener: Pin<&mut EventListenerRc<()>>) -> bool { future::block_on(future::poll_once(listener)).is_some() } struct ListWaker { notified: Arc>>, index: usize, } impl Wake for ListWaker { fn wake(self: Arc) { self.notified.lock().unwrap().push(self.index); } } #[test] fn notify() { let event = Event::<()>::new(); let mut l1 = event.listen(); let mut l2 = event.listen(); let mut l3 = event.listen(); assert!(!is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); event.notify(2); event.notify(1); assert!(is_notified(l1.as_mut())); assert!(is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); } #[test] fn notify_additional() { let event = Event::<()>::new(); let mut l1 = event.listen(); let mut l2 = event.listen(); let mut l3 = event.listen(); assert!(!is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); event.notify(1.additional()); event.notify(1); event.notify(1.additional()); assert!(is_notified(l1.as_mut())); assert!(is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); } #[cfg(feature = "alloc")] #[test] fn notify_rc() { let event = Rc::new(Event::<()>::new()); let l1 = EventListenerRc::new(event.clone()); let l2 = EventListenerRc::new(event.clone()); futures_lite::pin!(l1); futures_lite::pin!(l2); assert!(!is_notified_rc(l1.as_mut())); assert!(!is_notified_rc(l2.as_mut())); event.notify(1); event.notify(1); assert!(is_notified_rc(l1.as_mut())); assert!(!is_notified_rc(l2.as_mut())); event.notify(1); assert!(is_notified_rc(l2.as_mut())); } #[test] fn notify_out_of_range() { let event = Event::<()>::new(); assert_eq!(event.notify(1.additional()), 0); let mut l1 = event.listen(); let mut l2 = event.listen(); assert!(!is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); assert_eq!(event.notify(2), 2); assert_eq!(event.notify(1), 0); } #[test] fn change_waker() { let v = Arc::new(Mutex::new(0)); let waker1 = waker_fn::waker_fn({ let v = v.clone(); move || *v.lock().unwrap() = 1 }); let waker2 = waker_fn::waker_fn({ let v = v.clone(); move || *v.lock().unwrap() = 2 }); let event = Event::<()>::new(); let mut l1 = event.listen(); assert!(l1 .as_mut() .poll(&mut Context::from_waker(&waker1)) .is_pending(),); // Change the waker. assert!(l1 .as_mut() .poll(&mut Context::from_waker(&waker2)) .is_pending(),); // Notify the event. event.notify(1); // The waker should be called. assert_eq!(*v.lock().unwrap(), 2); } #[test] fn notify_one() { let event = Event::new(); let mut l1 = event.listen(); let mut l2 = event.listen(); assert!(!is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); event.notify(1); assert!(is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); event.notify(1); assert!(is_notified(l2.as_mut())); } #[test] fn notify_all() { let event = Event::new(); let mut l1 = event.listen(); let mut l2 = event.listen(); assert!(!is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); event.notify(core::usize::MAX); assert!(is_notified(l1.as_mut())); assert!(is_notified(l2.as_mut())); } #[test] fn drop_notified() { let event = Event::<()>::new(); let mut l1 = event.listen(); let mut l2 = event.listen(); let mut l3 = event.listen(); assert!(!is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); event.notify(1); drop(l1); assert!(is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); } #[test] fn drop_notified2() { let event = Event::<()>::new(); let mut l1 = event.listen(); let mut l2 = event.listen(); let mut l3 = event.listen(); assert!(!is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); event.notify(2); drop(l1); assert!(is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); } #[test] fn drop_notified_additional() { let event = Event::<()>::new(); let mut l1 = event.listen(); let mut l2 = event.listen(); let mut l3 = event.listen(); let mut l4 = event.listen(); assert!(!is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); assert!(!is_notified(l4.as_mut())); event.notify(1.additional()); event.notify(2); drop(l1); assert!(is_notified(l2.as_mut())); assert!(is_notified(l3.as_mut())); assert!(!is_notified(l4.as_mut())); } #[test] fn drop_non_notified() { let event = Event::<()>::new(); let mut l1 = event.listen(); let mut l2 = event.listen(); let mut l3 = event.listen(); assert!(!is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); assert!(!is_notified(l3.as_mut())); event.notify(1); drop(l3); assert!(is_notified(l1.as_mut())); assert!(!is_notified(l2.as_mut())); } #[test] fn notify_all_fair() { let event = Event::<()>::new(); let v = Arc::new(Mutex::new(vec![])); let waker1 = Waker::from(Arc::new(ListWaker { notified: v.clone(), index: 1, })); let waker2 = Waker::from(Arc::new(ListWaker { notified: v.clone(), index: 2, })); let waker3 = Waker::from(Arc::new(ListWaker { notified: v.clone(), index: 3, })); let mut l1 = event.listen(); let mut l2 = event.listen(); let mut l3 = event.listen(); assert!(l1 .as_mut() .poll(&mut Context::from_waker(&waker1)) .is_pending()); assert!(l2 .as_mut() .poll(&mut Context::from_waker(&waker2)) .is_pending()); assert!(l3 .as_mut() .poll(&mut Context::from_waker(&waker3)) .is_pending()); event.notify(core::usize::MAX); assert_eq!(&*v.lock().unwrap(), &[1, 2, 3]); assert!(l1 .as_mut() .poll(&mut Context::from_waker(&waker1)) .is_ready()); assert!(l2 .as_mut() .poll(&mut Context::from_waker(&waker2)) .is_ready()); assert!(l3 .as_mut() .poll(&mut Context::from_waker(&waker3)) .is_ready()); } #[test] fn notify_tagged() { let event = Event::::new(); let waker = waker_fn(|| {}); let mut l1 = event.listen(); let mut l2 = event.listen(); // Should not be notified. assert!(l1 .as_mut() .poll(&mut Context::from_waker(&waker)) .is_pending()); assert!(l2 .as_mut() .poll(&mut Context::from_waker(&waker)) .is_pending()); // Notify with tags. event.notify(1.tag(1)); event.notify(1.tag(2)); // Should be notified. assert_eq!( l1.as_mut().poll(&mut Context::from_waker(&waker)), Poll::Ready(1) ); assert!(l2 .as_mut() .poll(&mut Context::from_waker(&waker)) .is_pending()); // Notify with tags. event.notify(2.tag(13)); // Should be notified. assert_eq!( l2.as_mut().poll(&mut Context::from_waker(&waker)), Poll::Ready(13) ); } #[test] fn notify_tagged_with() { let event = Event::::new(); let waker = waker_fn(|| {}); let mut l1 = event.listen(); let mut l2 = event.listen(); // Should not be notified. assert!(l1 .as_mut() .poll(&mut Context::from_waker(&waker)) .is_pending()); assert!(l2 .as_mut() .poll(&mut Context::from_waker(&waker)) .is_pending()); // Notify with tags. event.notify(1.tag_with(|| 1)); event.notify(1.tag_with(|| 2)); // Should be notified. assert_eq!( l1.as_mut().poll(&mut Context::from_waker(&waker)), Poll::Ready(1) ); assert!(l2 .as_mut() .poll(&mut Context::from_waker(&waker)) .is_pending()); // Notify with tags. event.notify(2.tag_with(|| 13)); // Should be notified. assert_eq!( l2.as_mut().poll(&mut Context::from_waker(&waker)), Poll::Ready(13) ); } macro_rules! negative_test { ( $( $tname:ident => $t:ty ),* ) => {$( #[test] #[should_panic] fn $tname() { let event = Event::<()>::new(); let n: $t = -1; event.notify(n); } )*}; } negative_test! { negative_test_i8 => i8, negative_test_i16 => i16, negative_test_i32 => i32, negative_test_i64 => i64, negative_test_i128 => i128, negative_test_isize => isize } } unsend-0.2.1/src/executor.rs000064400000000000000000000507711046102023000141250ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! An asynchronous executor. use alloc::collections::VecDeque; use alloc::rc::Rc; use core::cell::{Cell, RefCell}; use core::future::Future; use core::marker::PhantomData; use core::mem::{forget, ManuallyDrop}; use core::num::NonZeroUsize; use core::task::{Poll, Waker}; use crate::sync::Arc; use crate::{Event, EventListenerRc, IntoNotification}; use async_task::{Runnable, Task}; use atomic_waker::AtomicWaker; use concurrent_queue::ConcurrentQueue; use futures_lite::prelude::*; use slab::Slab; pub struct Executor<'a, T = DefaultThreadId> { /// Inner state of the executor. state: Arc>, /// Capture the invariant lifetime. _marker: PhantomData<&'a Cell>>, } impl Drop for Executor<'_, T> { fn drop(&mut self) { // Wake all tasks. loop { let mut thread_state = self.state.thread_state.borrow_mut(); let waker = match thread_state.active.drain().next() { Some(waker) => waker, None => break, }; drop(thread_state); waker.wake(); } // Drain the queues. while self.state.task_queue.pop().is_ok() && self .state .thread_state .borrow_mut() .task_queue .pop_front() .is_some() {} // Destroy the thread state. unsafe { ManuallyDrop::drop(&mut self.state.thread_state.borrow_mut()); } } } struct State { /// Mainstream queue of tasks. task_queue: ConcurrentQueue, /// Waker for the mainstream queue. mainstream_waker: AtomicWaker, /// Getter for the thread ID. thread_id: T, /// The thread ID of the origin thread. origin_thread: Option, /// State that can only be accessed by the thread that owns the executor. thread_state: RefCell>, } unsafe impl Send for State {} unsafe impl Sync for State {} struct ThreadState { /// Thread-local queue of tasks. task_queue: VecDeque, /// Waker for the thread-local queue. thread_waker: Rc>>, /// Slab of tasks that are currently running. active: Slab, /// Is someone listening to the mainstream queue? is_mainstream_listening: bool, } impl<'a, T: Default + ThreadId + Send + Sync + 'static> Default for Executor<'a, T> { fn default() -> Self { Self::with_thread_id(T::default()) } } impl<'a> Executor<'a> { /// Create a new executor with the default thread ID strategy. /// /// # Example /// /// ``` /// use unsend::executor::Executor; /// /// let executor = Executor::new(); /// ``` pub fn new() -> Self { Self::with_thread_id(DefaultThreadId::new()) } } impl<'a, T: ThreadId + Send + Sync + 'static> Executor<'a, T> { /// Create a new executor with the given thread ID strategy. /// /// # Example /// /// ``` /// use unsend::executor::{Executor, StdThreadId}; /// /// let executor = Executor::with_thread_id(StdThreadId::new()); /// ``` pub fn with_thread_id(thread_id: T) -> Self { Self { state: Arc::new(State { task_queue: ConcurrentQueue::unbounded(), mainstream_waker: AtomicWaker::new(), origin_thread: thread_id.id(), thread_id, thread_state: RefCell::new(ManuallyDrop::new(ThreadState { task_queue: VecDeque::new(), thread_waker: Rc::new(Event::new()), active: Slab::new(), is_mainstream_listening: false, })), }), _marker: PhantomData, } } /// Operate on the thread local state. fn with_thread_local(&self, f: impl FnOnce(&mut ThreadState) -> R) -> R { // SAFETY: Since Executor is !Send, we have to be on the same thread. f(&mut self.state.thread_state.borrow_mut()) } /// Tell if this executor is empty. pub fn is_empty(&self) -> bool { self.with_thread_local(|state| state.task_queue.is_empty()) && self.state.task_queue.is_empty() } /// Spawn a new future onto the executor. /// /// # Example /// /// ``` /// use unsend::executor::Executor; /// /// let executor = Executor::new(); /// let task = executor.spawn(async { /// println!("Hello, world!"); /// }); /// ``` pub fn spawn(&self, future: impl Future + 'a) -> Task { let (runnable, task) = self.with_thread_local(move |state| { // Remove the task from the set of active tasks once it finishes. let index = state.active.vacant_key(); let future = { let state = self.state.clone(); async move { // SAFETY: We are still on the origin thread. let _guard = CallOnDrop(move || { let mut thread_state = state.thread_state.borrow_mut(); drop(thread_state.active.try_remove(index)); }); future.await } }; // Create the task and insert it into the set of active tasks. let (runnable, task) = unsafe { async_task::spawn_unchecked(future, self.schedule()) }; state.active.insert(runnable.waker()); (runnable, task) }); runnable.schedule(); task } /// Run a task if one is scheduled. /// /// # Example /// /// ``` /// use unsend::executor::Executor; /// /// let executor = Executor::new(); /// assert!(!executor.try_tick()); // No tasks are scheduled. /// /// // Spawn a task. /// let task = executor.spawn(async { /// println!("Hello, world!"); /// }); /// /// assert!(executor.try_tick()); // The task is run. /// ``` pub fn try_tick(&self) -> bool { let mut runnable = self.with_thread_local(|state| { // Try to run a task from the thread-local queue. if let Some(runnable) = state.task_queue.pop_front() { // Wake up another runner in case we take a while. state.thread_waker.notify(1.tag_with(|| None)); Some(runnable) } else { None } }); if runnable.is_none() { // Try the mainstream queue. if let Ok(r) = self.state.task_queue.pop() { // Wake up the mainstream runner in case we take a while. self.state.mainstream_waker.wake(); runnable = Some(r); } } match runnable { Some(runnable) => { runnable.run(); true } None => false, } } /// Run a single tick of the executor, waiting for a task to be scheduled if necessary. /// /// # Example /// /// ``` /// use unsend::executor::Executor; /// use futures_lite::future; /// /// let executor = Executor::new(); /// let task = executor.spawn(async { /// println!("Hello, world!"); /// }); /// future::block_on(executor.tick()); /// ``` pub async fn tick(&self) { // Create a ticker and run a single tick. Ticker::new(&self.state).tick().await; } /// Run a future against the executor. /// /// # Example /// /// ``` /// use unsend::executor::Executor; /// use futures_lite::future; /// /// let executor = Executor::new(); /// let task = executor.spawn(async { 1 + 2 }); /// let res = future::block_on(executor.run(async { task.await * 2 })); /// /// assert_eq!(res, 6); /// ``` pub async fn run(&self, f: impl Future) -> O { // A future that polls the executor forever. let runner = async move { let mut ticker = Ticker::new(&self.state); loop { ticker.tick().await; } }; f.or(runner).await } /// The scheduler function. #[cfg_attr(coverage, no_coverage)] fn schedule(&self) -> impl Fn(Runnable) { let state = self.state.clone(); move |runnable| { // If we are on the same thread, push to the thread-local queue. if let (Some(origin_id), Some(our_id)) = (state.origin_thread, state.thread_id.id()) { if origin_id == our_id { let mut thread_state = state.thread_state.borrow_mut(); let mut runnable = Some(runnable); // Try to send the runnable directly. thread_state .thread_waker .notify(1.tag_with(|| runnable.take())); // If that didn't take, push to the queue. if let Some(runnable) = runnable { thread_state.task_queue.push_back(runnable); } return; } } // Otherwise, push to the mainstream queue. if let Err(e) = state.task_queue.push(runnable) { // Don't drop the runnable on this thread; leak it. forget(e.into_inner()); return; } state.mainstream_waker.wake(); } } } /// The state of a future trying to tick the executor. struct Ticker<'a, T> { /// The state of the executor. state: &'a State, /// Are we the mainstream runner? is_mainstream: bool, /// This type is not `Send` or `Sync`. _marker: PhantomData<*const ()>, } impl<'a, T: ThreadId + Send + Sync + 'static> Ticker<'a, T> { /// Create a new ticker from the state. fn new(state: &'a State) -> Self { Self { state, is_mainstream: false, _marker: PhantomData, } } /// Run a single tick of the executor, waiting for a task to be scheduled if necessary. async fn tick(&mut self) { // Create a listener. let listener = { // SAFETY: We are still on the origin thread. let thread_state = self.state.thread_state.borrow_mut(); EventListenerRc::new(thread_state.thread_waker.clone()) }; futures_lite::pin!(listener); loop { // Try to pop from the thread local state. { // SAFETY: We are still on the origin thread. let mut thread_state = self.state.thread_state.borrow_mut(); if let Some(runnable) = thread_state.task_queue.pop_front() { // Wake up another runner in case we take a while. thread_state.thread_waker.notify(1.tag_with(|| None)); // Run the runnable. drop(thread_state); runnable.run(); return; } // Acquire the mainstream runner if it hasn't been acquired yet. if !thread_state.is_mainstream_listening { thread_state.is_mainstream_listening = true; self.is_mainstream = true; } } let runnable = { if self.is_mainstream { // If we are the mainstream, wait in tandem. let mainstream_runnable = futures_lite::future::poll_fn(|cx| { let mut waker_set = false; loop { // Try to pop from the mainstream queue. if let Ok(runnable) = self.state.task_queue.pop() { // Remove ourselves from the mainstream queue if necessary. if waker_set { self.state.mainstream_waker.take(); } return Poll::Ready(Some(runnable)); } // Register our interest in the mainstream queue. if !waker_set { self.state.mainstream_waker.register(cx.waker()); waker_set = true; continue; } // Begin the wait. return Poll::Pending; } }); (&mut listener).or(mainstream_runnable).await } else { // Just wait on the listener. (&mut listener).await } }; // Wait on the thread waker, and maybe get a runnable. if let Some(runnable) = runnable { // Run the runnable. runnable.run(); // Poll the listener to see if there are more runnables. if let Some(Some(runnable)) = futures_lite::future::poll_once(listener).await { // Run the runnable. runnable.run(); } return; } } } } impl<'a, T> Drop for Ticker<'a, T> { fn drop(&mut self) { // If we are the mainstream runner, wake up another runner. if self.is_mainstream { let _ = self.state.mainstream_waker.take(); // SAFETY: We are still on the origin thread. let mut thread_state = self.state.thread_state.borrow_mut(); thread_state.is_mainstream_listening = false; thread_state.thread_waker.notify(1.tag_with(|| None)); } } } /// A getter for the current thread ID. /// /// # Safety /// /// The return value of `id` must be either `None` or a unique value for each thread. pub unsafe trait ThreadId { /// Get the current thread ID, or `None` if it isn't available. fn id(&self) -> Option; } unsafe impl ThreadId for &T { #[cfg_attr(coverage, no_coverage)] fn id(&self) -> Option { (**self).id() } } unsafe impl ThreadId for &mut T { #[cfg_attr(coverage, no_coverage)] fn id(&self) -> Option { (**self).id() } } unsafe impl ThreadId for alloc::boxed::Box { #[cfg_attr(coverage, no_coverage)] fn id(&self) -> Option { (**self).id() } } unsafe impl ThreadId for Rc { #[cfg_attr(coverage, no_coverage)] fn id(&self) -> Option { (**self).id() } } unsafe impl ThreadId for Arc { #[cfg_attr(coverage, no_coverage)] fn id(&self) -> Option { (**self).id() } } /// Get the current thread ID using the standard library. #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct StdThreadId { _private: (), } #[cfg(feature = "std")] impl StdThreadId { /// Create a new `StdThreadId`. #[inline(always)] pub fn new() -> Self { Self { _private: () } } } #[cfg(feature = "std")] unsafe impl ThreadId for StdThreadId { fn id(&self) -> Option { std::thread_local! { static LOCAL: u8 = 0x03; } // Convert the address of the thread-local variable to a `usize`. LOCAL .try_with(|x| { // SAFETY: Addresses are always non-zero. unsafe { NonZeroUsize::new_unchecked(x as *const _ as usize) } }) .ok() } } /// The thread ID is not available. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct NoThreadId { _private: (), } impl NoThreadId { /// Create a new `NoThreadId`. #[inline(always)] pub fn new() -> Self { Self { _private: () } } } unsafe impl ThreadId for NoThreadId { #[inline(always)] fn id(&self) -> Option { None } } /// The thread ID used by default. #[doc(hidden)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct DefaultThreadId { #[cfg(feature = "std")] inner: StdThreadId, #[cfg(not(feature = "std"))] inner: NoThreadId, } impl DefaultThreadId { /// Create a new `DefaultThreadId`. #[inline(always)] pub fn new() -> Self { Self::default() } } unsafe impl ThreadId for DefaultThreadId { #[inline(always)] fn id(&self) -> Option { self.inner.id() } } struct CallOnDrop(F); impl Drop for CallOnDrop { fn drop(&mut self) { (self.0)(); } } #[cfg(test)] mod tests { use super::*; use futures_lite::future; #[test] fn smoke() { future::block_on(async { let slot = RefCell::new(0); let ex = Executor::new(); let t1 = ex.spawn(async { *slot.borrow_mut() += 1; 17 }); let t2 = ex.spawn(async { *slot.borrow_mut() += 2; 18 }); // Wait for the tasks to finish. ex.run(async { assert_eq!(t1.await, 17); assert_eq!(t2.await, 18); assert!(ex.is_empty()); assert_eq!(*slot.borrow(), 3); }) .await; }); } #[test] fn smoke_no_thread_id() { future::block_on(async { let slot = RefCell::new(0); let ex = Executor::with_thread_id(NoThreadId::new()); let t1 = ex.spawn(async { *slot.borrow_mut() += 1; 17 }); let t2 = ex.spawn(async { *slot.borrow_mut() += 2; 18 }); // Wait for the tasks to finish. ex.run(async { assert_eq!(t1.await, 17); assert_eq!(t2.await, 18); assert!(ex.is_empty()); assert_eq!(*slot.borrow(), 3); }) .await; }); } #[cfg(feature = "std")] #[test] fn try_tick() { use std::thread; future::block_on(async { let ex = Executor::new(); assert!(!ex.try_tick()); // Spawn a task that will wake up the executor. let task = ex.spawn({ let mut polls_left = 5; async move { while polls_left > 0 { future::yield_now().await; polls_left -= 1; } } }); ex.run(async { // Poll the task once. assert!(ex.try_tick()); // Poll again. assert!(ex.try_tick()); // Send to another thread and poll until we're done. thread::spawn(move || { future::block_on(task); }); // Poll until we're done. assert!(ex.try_tick()); ex.tick().await; while !ex.is_empty() { ex.tick().await; } }) .await; }) } #[test] fn default_smoke() { let _: Executor<'_, NoThreadId> = Executor::default(); } } unsend-0.2.1/src/lib.rs000064400000000000000000000034631046102023000130310ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! A thread-unsafe runtime for thread-unsafe people. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(coverage, feature(no_coverage))] #![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub mod channel; #[cfg(feature = "executor")] #[cfg_attr(docsrs, doc(cfg(feature = "executor")))] pub mod executor; pub mod lock; mod event; pub use event::{Event, EventListener, IntoNotification, Notification}; #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub use event::EventListenerRc; mod sync { #[cfg(feature = "alloc")] pub use alloc::sync::Arc; pub use core::sync::atomic; } unsend-0.2.1/src/lock/barrier.rs000064400000000000000000000055761046102023000146500ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! Wait for a set number of tasks to reach this point. use crate::{Event, EventListener}; use core::cell::Cell; use core::pin::Pin; /// A barrier that can be used to synchronize a set of tasks. pub struct Barrier { /// Number of tasks to wait for. n: usize, /// Number of tasks that have reached the barrier. count: Cell, /// The generation of the barrier. generation: Cell, /// The event for waiting on the barrier. event: Event<()>, } impl Barrier { /// Create a new barrier that waits for this number of tasks. pub fn new(n: usize) -> Barrier { Barrier { n, count: Cell::new(0), generation: Cell::new(0), event: Event::new(), } } /// Wait for the barrier. pub async fn wait(&self) -> BarrierWaitResult { let local_gen = self.generation.get(); self.count.set(self.count.get() + 1); let mut listener = EventListener::new(&self.event); let mut listener = unsafe { Pin::new_unchecked(&mut listener) }; if self.count.get() < self.n { // Wait for the count. while local_gen == self.generation.get() && self.count.get() < self.n { listener.as_mut().await; } BarrierWaitResult { is_leader: false } } else { self.count.set(0); self.generation.set(local_gen + 1); self.event.notify(core::usize::MAX); BarrierWaitResult { is_leader: true } } } } /// The result of waiting on the barrier. #[derive(Debug, Clone)] pub struct BarrierWaitResult { /// Is this task the leader? is_leader: bool, } impl BarrierWaitResult { /// Is this task the leader? pub fn is_leader(&self) -> bool { self.is_leader } } unsend-0.2.1/src/lock/mod.rs000064400000000000000000000244461046102023000137760ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! Asynchronous locking primitives. mod barrier; mod mutex; mod once_cell; mod rwlock; mod semaphore; pub use barrier::{Barrier, BarrierWaitResult}; pub use mutex::{Mutex, MutexGuard}; pub use once_cell::OnceCell; pub use rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; pub use semaphore::{Semaphore, SemaphoreGuard}; #[cfg(feature = "alloc")] pub use semaphore::SemaphoreGuardRc; #[cfg(test)] mod tests { use super::*; use futures_lite::future; #[test] fn test_mutex() { future::block_on(async { let mutex = Mutex::new(0); let mut guard = mutex.lock().await; *guard += 1; assert_eq!(*guard, 1); drop(guard); let guard = mutex.lock().await; // If we try to lock the mutex again, it will block. assert!(mutex.try_lock().is_none()); assert!(future::poll_once(mutex.lock()).await.is_none()); assert_eq!(*guard, 1); }); } #[test] fn test_mutex_try_lock() { future::block_on(async { let mutex = Mutex::new(0); let mut guard = mutex.try_lock().unwrap(); *guard += 1; assert_eq!(*guard, 1); drop(guard); let guard = mutex.try_lock().unwrap(); assert_eq!(*guard, 1); }); } #[test] fn test_mutex_try_lock_fail() { future::block_on(async { let mutex = Mutex::new(0); let mut guard = mutex.lock().await; *guard += 1; assert_eq!(*guard, 1); drop(guard); let guard = mutex.lock().await; assert_eq!(*guard, 1); }); } #[test] fn test_mutex_lock_after_drop() { future::block_on(async { let mutex = Mutex::new(0); let mut guard = mutex.lock().await; *guard += 1; assert_eq!(*guard, 1); drop(guard); let guard = mutex.lock().await; assert_eq!(*guard, 1); }); } #[test] fn test_mutex_lock_after_drop_try_lock() { future::block_on(async { let mutex = Mutex::new(0); let mut guard = mutex.try_lock().unwrap(); *guard += 1; assert_eq!(*guard, 1); drop(guard); let guard = mutex.try_lock().unwrap(); assert_eq!(*guard, 1); }); } #[test] fn test_mutex_get_mut_into_inner() { let mut mutex = Mutex::::default(); *mutex.get_mut() = 3; assert_eq!(mutex.into_inner(), 3); } #[test] fn test_mutex_lock_await() { future::block_on(async { let mutex = Mutex::new(0); let mut guard = mutex.lock().await; *guard += 1; let lock2 = mutex.lock(); futures_lite::pin!(lock2); assert!(future::poll_once(&mut lock2).await.is_none()); drop(guard); assert!(future::poll_once(&mut lock2).await.is_some()); }); } #[test] fn test_rwlock() { future::block_on(async { let rwlock = RwLock::new(0); let mut guard = rwlock.write().await; *guard += 1; assert_eq!(*guard, 1); drop(guard); let guard = rwlock.read().await; assert_eq!(*guard, 1); drop(guard); let guard = rwlock.write().await; assert_eq!(*guard, 1); }); } #[test] fn test_rwlock_try_read() { future::block_on(async { let rwlock = RwLock::new(0); let mut guard = rwlock.write().await; *guard += 1; assert_eq!(*guard, 1); drop(guard); let guard = rwlock.try_read().unwrap(); assert_eq!(*guard, 1); drop(guard); let guard = rwlock.write().await; assert_eq!(*guard, 1); }); } #[test] fn test_rwlock_try_write() { future::block_on(async { let rwlock = RwLock::new(0); let mut guard = rwlock.write().await; *guard += 1; assert_eq!(*guard, 1); drop(guard); let guard = rwlock.read().await; assert_eq!(*guard, 1); drop(guard); let guard = rwlock.try_write().unwrap(); assert_eq!(*guard, 1); }); } #[test] fn test_rwlock_get_mut() { let mut rwlock = RwLock::::default(); *rwlock.get_mut() = 3; assert_eq!(rwlock.into_inner(), 3); } #[test] fn test_rwlock_read_write_await() { future::block_on(async { let rwlock = RwLock::new(0); let guard = rwlock.write().await; assert_eq!(*guard, 0); let read2 = rwlock.read(); futures_lite::pin!(read2); assert!(future::poll_once(&mut read2).await.is_none()); drop(guard); assert!(future::poll_once(&mut read2).await.is_some()); let guard = rwlock.read().await; assert_eq!(*guard, 0); let write2 = rwlock.write(); futures_lite::pin!(write2); assert!(future::poll_once(&mut write2).await.is_none()); drop(guard); assert!(future::poll_once(&mut write2).await.is_some()); }); } #[test] fn test_semaphore() { future::block_on(async { let semaphore = Semaphore::new(1); let _guard = semaphore.acquire().await; // If we try to acquire the semaphore again, it will block. assert!(semaphore.try_acquire().is_none()); assert!(future::poll_once(semaphore.acquire()).await.is_none()); }); } #[test] fn test_semaphore_await() { future::block_on(async { let semaphore = Semaphore::new(1); let _guard = semaphore.acquire().await; // If we try to acquire the semaphore again, it will block. assert!(semaphore.try_acquire().is_none()); let acquire = semaphore.acquire(); futures_lite::pin!(acquire); assert!(future::poll_once(&mut acquire).await.is_none()); drop(_guard); assert!(future::poll_once(&mut acquire).await.is_some()); }); } #[cfg(feature = "alloc")] #[test] fn test_semaphore_rc() { use alloc::rc::Rc; future::block_on(async { let semaphore = Rc::new(Semaphore::new(1)); let _guard = semaphore.clone().acquire_rc().await; // If we try to acquire the semaphore again, it will block. assert!(semaphore.clone().try_acquire_rc().is_none()); assert!(future::poll_once(semaphore.acquire()).await.is_none()); }); } #[cfg(feature = "alloc")] #[test] fn test_semaphore_rc_await() { use alloc::rc::Rc; future::block_on(async { let semaphore = Rc::new(Semaphore::new(1)); let _guard = semaphore.clone().acquire_rc().await; // If we try to acquire the semaphore again, it will block. assert!(semaphore.clone().try_acquire_rc().is_none()); let acquire = semaphore.acquire_rc(); futures_lite::pin!(acquire); assert!(future::poll_once(&mut acquire).await.is_none()); drop(_guard); assert!(future::poll_once(&mut acquire).await.is_some()); }); } #[test] fn once_cell_smoke() { future::block_on(async { let cell = OnceCell::::new(); assert!(cell.get().is_none()); let value = cell.get_or_init(async { 5 }).await; assert_eq!(*value, 5); assert_eq!(cell.get().unwrap(), &5); }); } #[test] fn once_cell_get_mut() { let mut cell = OnceCell::::from(5); assert!(cell.get_mut().is_some()); assert_eq!(cell.take(), Some(5)); } #[test] fn once_cell_wait() { future::block_on(async { let cell = OnceCell::::new(); let waiter = cell.wait(); futures_lite::pin!(waiter); assert!(future::poll_once(&mut waiter).await.is_none()); cell.set(5).await.unwrap(); assert!(future::poll_once(&mut waiter).await.is_some()); }); } #[test] fn once_cell_set2() { future::block_on(async { let cell = OnceCell::::from(3); assert_eq!(cell.set(5).await, Err(5)); }); } #[cfg(feature = "alloc")] #[test] fn barrier_smoke() { future::block_on(async { let barrier = Barrier::new(2); // Start two tasks that wait on the barrier. let wait1 = barrier.wait(); futures_lite::pin!(wait1); assert!(future::poll_once(wait1.as_mut()).await.is_none()); // Wait on the barrier in the current task. let result = barrier.wait().await; assert!(result.clone().is_leader()); // Eat coverage for debug. alloc::format!("{:?}", result); // The other tasks should now be unblocked. let result = future::poll_once(wait1.as_mut()).await.unwrap(); assert!(!result.is_leader()); }); } } unsend-0.2.1/src/lock/mutex.rs000064400000000000000000000064671046102023000143640ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! An asynchronous mutex. use crate::{Event, EventListener}; use core::cell::{RefCell, RefMut}; use core::ops; use core::pin::Pin; /// An asynchronous mutex. pub struct Mutex { /// The event for waiting on the mutex. unlocked: Event<()>, /// The underlying data. data: RefCell, } impl Default for Mutex { fn default() -> Mutex { Mutex::new(Default::default()) } } /// A guard that unlocks the mutex when dropped. pub struct MutexGuard<'a, T: ?Sized> { /// The event to signal. event: &'a Event<()>, /// The underlying data. data: RefMut<'a, T>, } impl Mutex { /// Creates a new asynchronous mutex. pub fn new(data: T) -> Mutex { Mutex { unlocked: Event::new(), data: RefCell::new(data), } } /// Unwraps the underlying data. pub fn into_inner(self) -> T { self.data.into_inner() } } impl Mutex { /// Get a mutable reference to the underlying data. pub fn get_mut(&mut self) -> &mut T { self.data.get_mut() } /// Try to lock the mutex. pub fn try_lock(&self) -> Option> { self.data.try_borrow_mut().ok().map(|data| MutexGuard { event: &self.unlocked, data, }) } /// Lock the mutex. pub async fn lock(&self) -> MutexGuard<'_, T> { // TODO: Use a fairer locking algorithm. let mut listener = EventListener::new(&self.unlocked); { let mut listener = unsafe { Pin::new_unchecked(&mut listener) }; loop { // Try to lock the mutex. if let Some(lock) = self.try_lock() { return lock; } // Wait for the mutex to be unlocked. listener.as_mut().await; } } } } impl ops::Deref for MutexGuard<'_, T> { type Target = T; fn deref(&self) -> &T { &self.data } } impl ops::DerefMut for MutexGuard<'_, T> { fn deref_mut(&mut self) -> &mut T { &mut self.data } } impl Drop for MutexGuard<'_, T> { fn drop(&mut self) { self.event.notify(1); } } unsend-0.2.1/src/lock/once_cell.rs000064400000000000000000000126711046102023000151370ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! Value that can only be written once. use crate::{Event, EventListener, IntoNotification}; use core::cell::{Cell, UnsafeCell}; use core::convert::Infallible; use core::future::Future; use core::pin::Pin; /// A value that can only be written once. pub struct OnceCell { /// The event for writing to the cell. active: Event<()>, /// The event for the cell to be ready. passive: Event<()>, /// There is a future that is writing to the cell. writing: Cell, /// The underlying data. data: UnsafeCell>, } impl OnceCell { /// Creates a new `OnceCell`. pub const fn new() -> OnceCell { OnceCell { active: Event::new(), passive: Event::new(), writing: Cell::new(false), data: UnsafeCell::new(None), } } /// Gets the value of the cell mutably. pub fn get_mut(&mut self) -> Option<&mut T> { unsafe { &mut *self.data.get() }.as_mut() } /// Take the value out of the cell. pub fn take(&mut self) -> Option { unsafe { &mut *self.data.get() }.take() } /// Get the value of the cell. pub fn get(&self) -> Option<&T> { unsafe { &*self.data.get() }.as_ref() } /// Set the value of the cell. pub async fn set(&self, value: T) -> Result<(), T> { let mut value = Some(value); self.get_or_init(async { value.take().unwrap() }).await; match value { Some(value) => Err(value), None => Ok(()), } } /// Get the value of the cell or try to initialize it. pub async fn get_or_try_init( &self, setter: impl Future>, ) -> Result<&T, E> { struct UnwriteOnDrop<'a, T> { cell: &'a OnceCell, } impl Drop for UnwriteOnDrop<'_, T> { fn drop(&mut self) { self.cell.writing.set(false); self.cell.active.notify(1); } } let mut listener = EventListener::new(&self.active); let mut setter = Some(setter); { let mut listener = unsafe { Pin::new_unchecked(&mut listener) }; loop { // Try to get the value. if let Some(value) = self.get() { return Ok(value); } // If someone is already writing to the cell, wait for them to finish. if self.writing.replace(true) { listener.as_mut().await; continue; } // We now have exclusive access to the cell, try to write to it. let guard = UnwriteOnDrop { cell: self }; match setter.take().unwrap().await { Ok(data) => { // Store the data and wake up all listeners. unsafe { *self.data.get() = Some(data); } self.passive.notify(core::usize::MAX.additional()); self.active.notify(core::usize::MAX.additional()); // Return the value. return Ok(self.get().unwrap()); } Err(e) => { // Drop the value and wake up all listeners. drop(guard); return Err(e); } } } } } /// Get the value of the cell or initialize it. pub async fn get_or_init(&self, setter: impl Future) -> &T { match self .get_or_try_init(async move { Ok::(setter.await) }) .await { Ok(value) => value, Err(e) => match e {}, } } /// Wait for the cell to be ready. pub async fn wait(&self) { let mut listener = EventListener::new(&self.passive); let mut listener = unsafe { Pin::new_unchecked(&mut listener) }; while self.get().is_none() { listener.as_mut().await; } } } impl From for OnceCell { fn from(value: T) -> OnceCell { OnceCell { active: Event::new(), passive: Event::new(), writing: Cell::new(false), data: UnsafeCell::new(Some(value)), } } } unsend-0.2.1/src/lock/rwlock.rs000064400000000000000000000121071046102023000145070ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! Asynchronous read/write lock. use crate::{Event, EventListener}; use core::cell::{Cell, Ref, RefCell, RefMut}; use core::ops; use core::pin::Pin; /// An asynchronous read/write lock. pub struct RwLock { /// There are no more readers. no_readers: Event<()>, /// There are no more writers. no_writers: Event<()>, /// The number of readers. readers: Cell, /// The underlying data. data: RefCell, } /// A guard that unlocks the read end of the lock when dropped. pub struct RwLockReadGuard<'a, T: ?Sized> { /// The event to signal. event: &'a Event<()>, /// The number of readers. readers: &'a Cell, /// The underlying data. data: Ref<'a, T>, } /// A guard that unlocks the write end of the lock when dropped. pub struct RwLockWriteGuard<'a, T: ?Sized> { /// The event to signal. event: &'a Event<()>, /// The underlying data. data: RefMut<'a, T>, } impl Default for RwLock { fn default() -> RwLock { RwLock::new(Default::default()) } } impl RwLock { /// Creates a new asynchronous read/write lock. pub fn new(data: T) -> RwLock { RwLock { no_readers: Event::new(), no_writers: Event::new(), readers: Cell::new(0), data: RefCell::new(data), } } /// Unwraps the underlying data. pub fn into_inner(self) -> T { self.data.into_inner() } } impl RwLock { /// Get a mutable reference to the underlying data. pub fn get_mut(&mut self) -> &mut T { self.data.get_mut() } /// Try to lock the read end of the lock. pub fn try_read(&self) -> Option> { self.data.try_borrow().ok().map(|data| { self.readers .set(self.readers.get().checked_add(1).expect("too many readers")); RwLockReadGuard { event: &self.no_readers, readers: &self.readers, data, } }) } /// Try to lock the write end of the lock. pub fn try_write(&self) -> Option> { self.data .try_borrow_mut() .ok() .map(|data| RwLockWriteGuard { event: &self.no_writers, data, }) } /// Lock the read end of the lock. pub async fn read(&self) -> RwLockReadGuard<'_, T> { let mut listener = EventListener::new(&self.no_writers); { let mut listener = unsafe { Pin::new_unchecked(&mut listener) }; loop { if let Some(guard) = self.try_read() { return guard; } listener.as_mut().await; } } } /// Lock the write end of the lock. pub async fn write(&self) -> RwLockWriteGuard<'_, T> { let mut listener = EventListener::new(&self.no_readers); { let mut listener = unsafe { Pin::new_unchecked(&mut listener) }; loop { if let Some(guard) = self.try_write() { return guard; } listener.as_mut().await; } } } } impl ops::Deref for RwLockReadGuard<'_, T> { type Target = T; fn deref(&self) -> &T { &self.data } } impl Drop for RwLockReadGuard<'_, T> { fn drop(&mut self) { let readers = self.readers.get().checked_sub(1).expect("too few readers"); self.readers.set(readers); if readers == 0 { self.event.notify(1); } } } impl ops::Deref for RwLockWriteGuard<'_, T> { type Target = T; fn deref(&self) -> &T { &self.data } } impl ops::DerefMut for RwLockWriteGuard<'_, T> { fn deref_mut(&mut self) -> &mut T { &mut self.data } } impl Drop for RwLockWriteGuard<'_, T> { fn drop(&mut self) { self.event.notify(1); } } unsend-0.2.1/src/lock/semaphore.rs000064400000000000000000000100441046102023000151670ustar 00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `unsend`. // // `unsend` is free software: you can redistribute it and/or modify it under the // terms of either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // * The Patron License (https://github.com/notgull/unsend/blob/main/LICENSE-PATRON.md) // for sponsors and contributors, who can ignore the copyleft provisions of the above licenses // for this project. // // `unsend` is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more // details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `unsend`. If not, see . //! Asynchronous semaphore. use crate::{Event, EventListener}; use core::cell::Cell; use core::pin::Pin; #[cfg(feature = "alloc")] use alloc::rc::Rc; /// An asynchronous semaphore. pub struct Semaphore { /// The event for waiting on the semaphore. event: Event<()>, /// The number of permits. permits: Cell, } /// A guard that releases the permit when dropped. pub struct SemaphoreGuard<'a> { /// The origin semaphore. semaphore: &'a Semaphore, } /// A guard that releases the permit when dropped. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub struct SemaphoreGuardRc { /// The origin semaphore. semaphore: Rc, } impl Semaphore { /// Creates a new asynchronous semaphore. pub fn new(permits: usize) -> Semaphore { Semaphore { event: Event::new(), permits: Cell::new(permits), } } /// Try to acquire a permit. pub fn try_acquire(&self) -> Option> { let permits = self.permits.get(); if permits > 0 { self.permits.set(permits - 1); Some(SemaphoreGuard { semaphore: self }) } else { None } } /// Try to acquire a permit through an `Rc`. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn try_acquire_rc(self: Rc) -> Option { let permits = self.permits.get(); if permits > 0 { self.permits.set(permits - 1); Some(SemaphoreGuardRc { semaphore: self }) } else { None } } /// Acquire a permit. pub async fn acquire(&self) -> SemaphoreGuard<'_> { let mut listener = EventListener::new(&self.event); { let mut listener = unsafe { Pin::new_unchecked(&mut listener) }; loop { if let Some(guard) = self.try_acquire() { return guard; } listener.as_mut().await; } } } /// Acquire a permit through an `Rc`. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub async fn acquire_rc(self: Rc) -> SemaphoreGuardRc { let mut listener = EventListener::new(&self.event); { let mut listener = unsafe { Pin::new_unchecked(&mut listener) }; loop { if let Some(guard) = self.clone().try_acquire_rc() { return guard; } listener.as_mut().await; } } } } impl Drop for SemaphoreGuard<'_> { fn drop(&mut self) { self.semaphore.permits.set(self.semaphore.permits.get() + 1); self.semaphore.event.notify(1); } } #[cfg(feature = "alloc")] impl Drop for SemaphoreGuardRc { fn drop(&mut self) { self.semaphore.permits.set(self.semaphore.permits.get() + 1); self.semaphore.event.notify(1); } }