├── .github ├── codecov.yml ├── dependabot.yml └── workflows │ ├── check.yml │ ├── safety.yml │ ├── scheduled.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── rustfmt.toml ├── src └── lib.rs └── tests └── lib.rs /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.codecov.com/docs/codecovyml-reference 2 | coverage: 3 | # Hold ourselves to a high bar 4 | range: 85..100 5 | round: down 6 | precision: 1 7 | status: 8 | # ref: https://docs.codecov.com/docs/commit-status 9 | project: 10 | default: 11 | # Avoid false negatives 12 | threshold: 1% 13 | 14 | # Test files aren't important for coverage 15 | ignore: 16 | - "tests" 17 | 18 | # Make comments less noisy 19 | comment: 20 | layout: "files" 21 | require_changes: yes 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: daily 11 | ignore: 12 | - dependency-name: "*" 13 | # patch and minor updates don't matter for libraries 14 | # remove this ignore rule if your package has binaries 15 | update-types: 16 | - "version-update:semver-patch" 17 | - "version-update:semver-minor" 18 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # Spend CI time only on latest ref: https://github.com/jonhoo/rust-ci-conf/pull/5 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | name: check 12 | jobs: 13 | fmt: 14 | runs-on: ubuntu-latest 15 | name: stable / fmt 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | - name: Install stable 21 | uses: dtolnay/rust-toolchain@stable 22 | with: 23 | components: rustfmt 24 | - name: cargo fmt --check 25 | run: cargo fmt --check 26 | clippy: 27 | runs-on: ubuntu-latest 28 | name: ${{ matrix.toolchain }} / clippy 29 | permissions: 30 | contents: read 31 | checks: write 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | toolchain: [stable, beta] 36 | steps: 37 | - uses: actions/checkout@v4 38 | with: 39 | submodules: true 40 | - name: Install ${{ matrix.toolchain }} 41 | uses: dtolnay/rust-toolchain@master 42 | with: 43 | toolchain: ${{ matrix.toolchain }} 44 | components: clippy 45 | - name: cargo clippy 46 | uses: actions-rs/clippy-check@v1 47 | with: 48 | token: ${{ secrets.GITHUB_TOKEN }} 49 | doc: 50 | runs-on: ubuntu-latest 51 | name: nightly / doc 52 | steps: 53 | - uses: actions/checkout@v4 54 | with: 55 | submodules: true 56 | - name: Install nightly 57 | uses: dtolnay/rust-toolchain@nightly 58 | - name: cargo doc 59 | run: cargo doc --no-deps --all-features 60 | env: 61 | RUSTDOCFLAGS: --cfg docsrs 62 | hack: 63 | runs-on: ubuntu-latest 64 | name: ubuntu / stable / features 65 | steps: 66 | - uses: actions/checkout@v4 67 | with: 68 | submodules: true 69 | - name: Install stable 70 | uses: dtolnay/rust-toolchain@stable 71 | - name: cargo install cargo-hack 72 | uses: taiki-e/install-action@cargo-hack 73 | # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 74 | - name: cargo hack 75 | run: cargo hack --feature-powerset check 76 | msrv: 77 | runs-on: ubuntu-latest 78 | # we use a matrix here just because env can't be used in job names 79 | # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability 80 | strategy: 81 | matrix: 82 | msrv: ["1.53.0"] # https://github.com/rust-lang/rust/pull/83707 83 | name: ubuntu / ${{ matrix.msrv }} 84 | steps: 85 | - uses: actions/checkout@v4 86 | with: 87 | submodules: true 88 | - name: Install ${{ matrix.msrv }} 89 | uses: dtolnay/rust-toolchain@master 90 | with: 91 | toolchain: ${{ matrix.msrv }} 92 | - name: cargo +${{ matrix.msrv }} check 93 | run: cargo check 94 | -------------------------------------------------------------------------------- /.github/workflows/safety.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # Spend CI time only on latest ref: https://github.com/jonhoo/rust-ci-conf/pull/5 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | name: safety 12 | jobs: 13 | sanitizers: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: true 19 | - name: Install nightly 20 | uses: dtolnay/rust-toolchain@nightly 21 | - run: | 22 | # to get the symbolizer for debug symbol resolution 23 | sudo apt install llvm 24 | # to fix buggy leak analyzer: 25 | # https://github.com/japaric/rust-san#unrealiable-leaksanitizer 26 | # ensure there's a profile.dev section 27 | if ! grep -qE '^[ \t]*[profile.dev]' Cargo.toml; then 28 | echo >> Cargo.toml 29 | echo '[profile.dev]' >> Cargo.toml 30 | fi 31 | # remove pre-existing opt-levels in profile.dev 32 | sed -i '/^\s*\[profile.dev\]/,/^\s*\[/ {/^\s*opt-level/d}' Cargo.toml 33 | # now set opt-level to 1 34 | sed -i '/^\s*\[profile.dev\]/a opt-level = 1' Cargo.toml 35 | cat Cargo.toml 36 | name: Enable debug symbols 37 | - name: cargo test -Zsanitizer=address 38 | # only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945 39 | run: cargo test --lib --tests --all-features --target x86_64-unknown-linux-gnu 40 | env: 41 | ASAN_OPTIONS: "detect_odr_violation=0" 42 | RUSTFLAGS: "-Z sanitizer=address" 43 | - name: cargo test -Zsanitizer=leak 44 | if: always() 45 | run: cargo test --all-features --target x86_64-unknown-linux-gnu 46 | env: 47 | LSAN_OPTIONS: "suppressions=lsan-suppressions.txt" 48 | RUSTFLAGS: "-Z sanitizer=leak" 49 | miri: 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v4 53 | with: 54 | submodules: true 55 | - run: | 56 | echo "NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)" >> $GITHUB_ENV 57 | - name: Install ${{ env.NIGHTLY }} 58 | uses: dtolnay/rust-toolchain@master 59 | with: 60 | toolchain: ${{ env.NIGHTLY }} 61 | components: miri 62 | - name: cargo miri test 63 | run: cargo miri test 64 | env: 65 | # ignore leaks due to https://github.com/rust-lang/miri/issues/1371 66 | # no isolation since we use time in there 67 | MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-ignore-leaks" 68 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | schedule: 8 | - cron: '7 7 * * *' 9 | # Spend CI time only on latest ref: https://github.com/jonhoo/rust-ci-conf/pull/5 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | name: rolling 14 | jobs: 15 | # https://twitter.com/mycoliza/status/1571295690063753218 16 | nightly: 17 | runs-on: ubuntu-latest 18 | name: ubuntu / nightly 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - name: Install nightly 24 | uses: dtolnay/rust-toolchain@nightly 25 | - name: cargo generate-lockfile 26 | if: hashFiles('Cargo.lock') == '' 27 | run: cargo generate-lockfile 28 | - name: cargo test --locked 29 | run: cargo test --locked --all-features --all-targets 30 | # https://twitter.com/alcuadrado/status/1571291687837732873 31 | update: 32 | runs-on: ubuntu-latest 33 | name: ubuntu / beta / updated 34 | # There's no point running this if no Cargo.lock was checked in in the 35 | # first place, since we'd just redo what happened in the regular test job. 36 | # Unfortunately, hashFiles only works in if on steps, so we reepeat it. 37 | # if: hashFiles('Cargo.lock') != '' 38 | steps: 39 | - uses: actions/checkout@v4 40 | with: 41 | submodules: true 42 | - name: Install beta 43 | if: hashFiles('Cargo.lock') != '' 44 | uses: dtolnay/rust-toolchain@beta 45 | - name: cargo update 46 | if: hashFiles('Cargo.lock') != '' 47 | run: cargo update 48 | - name: cargo test 49 | if: hashFiles('Cargo.lock') != '' 50 | run: cargo test --locked --all-features --all-targets 51 | env: 52 | RUSTFLAGS: -D deprecated 53 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # Spend CI time only on latest ref: https://github.com/jonhoo/rust-ci-conf/pull/5 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | name: test 12 | jobs: 13 | required: 14 | runs-on: ubuntu-latest 15 | name: ubuntu / ${{ matrix.toolchain }} 16 | strategy: 17 | matrix: 18 | toolchain: [stable, beta] 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - name: Install ${{ matrix.toolchain }} 24 | uses: dtolnay/rust-toolchain@master 25 | with: 26 | toolchain: ${{ matrix.toolchain }} 27 | - name: cargo generate-lockfile 28 | if: hashFiles('Cargo.lock') == '' 29 | run: cargo generate-lockfile 30 | # https://twitter.com/jonhoo/status/1571290371124260865 31 | - name: cargo test --locked 32 | run: cargo test --locked --all-features --all-targets 33 | # https://github.com/rust-lang/cargo/issues/6669 34 | - name: cargo test --doc 35 | run: cargo test --locked --all-features --doc 36 | minimal: 37 | runs-on: ubuntu-latest 38 | name: ubuntu / stable / minimal-versions 39 | steps: 40 | - uses: actions/checkout@v4 41 | with: 42 | submodules: true 43 | - name: Install stable 44 | uses: dtolnay/rust-toolchain@stable 45 | - name: Install nightly for -Zminimal-versions 46 | uses: dtolnay/rust-toolchain@nightly 47 | - name: rustup default stable 48 | run: rustup default stable 49 | - name: cargo update -Zminimal-versions 50 | run: cargo +nightly update -Zminimal-versions 51 | - name: cargo test 52 | run: cargo test --locked --all-features --all-targets 53 | os-check: 54 | runs-on: ${{ matrix.os }} 55 | name: ${{ matrix.os }} / stable 56 | strategy: 57 | fail-fast: false 58 | matrix: 59 | os: [macos-latest, windows-latest] 60 | steps: 61 | # if your project needs OpenSSL, uncommment this to fix Windows builds. 62 | # it's commented out by default as tthe install command takes 5-10m. 63 | # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append 64 | # if: runner.os == 'Windows' 65 | # - run: vcpkg install openssl:x64-windows-static-md 66 | # if: runner.os == 'Windows' 67 | - uses: actions/checkout@v4 68 | with: 69 | submodules: true 70 | - name: Install stable 71 | uses: dtolnay/rust-toolchain@stable 72 | - name: cargo generate-lockfile 73 | if: hashFiles('Cargo.lock') == '' 74 | run: cargo generate-lockfile 75 | - name: cargo test 76 | run: cargo test --locked --all-features --all-targets 77 | coverage: 78 | runs-on: ubuntu-latest 79 | name: ubuntu / stable / coverage 80 | steps: 81 | - uses: actions/checkout@v4 82 | with: 83 | submodules: true 84 | - name: Install stable 85 | uses: dtolnay/rust-toolchain@stable 86 | with: 87 | components: llvm-tools-preview 88 | - name: cargo install cargo-llvm-cov 89 | uses: taiki-e/install-action@cargo-llvm-cov 90 | - name: cargo generate-lockfile 91 | if: hashFiles('Cargo.lock') == '' 92 | run: cargo generate-lockfile 93 | - name: cargo llvm-cov 94 | run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info 95 | - name: Upload to codecov.io 96 | uses: codecov/codecov-action@v3 97 | with: 98 | fail_ci_if_error: true 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | callgrind.out.* 2 | 3 | # Compiled files 4 | *.o 5 | *.so 6 | *.rlib 7 | *.dll 8 | 9 | # Executables 10 | *.exe 11 | 12 | # Generated by Cargo 13 | /target/ 14 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 10 | 11 | [[package]] 12 | name = "bus" 13 | version = "2.4.1" 14 | dependencies = [ 15 | "crossbeam-channel", 16 | "num_cpus", 17 | "parking_lot_core", 18 | ] 19 | 20 | [[package]] 21 | name = "cfg-if" 22 | version = "1.0.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 25 | 26 | [[package]] 27 | name = "crossbeam-channel" 28 | version = "0.5.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" 31 | dependencies = [ 32 | "cfg-if", 33 | "crossbeam-utils", 34 | ] 35 | 36 | [[package]] 37 | name = "crossbeam-utils" 38 | version = "0.8.10" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" 41 | dependencies = [ 42 | "cfg-if", 43 | "once_cell", 44 | ] 45 | 46 | [[package]] 47 | name = "hermit-abi" 48 | version = "0.1.19" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 51 | dependencies = [ 52 | "libc", 53 | ] 54 | 55 | [[package]] 56 | name = "libc" 57 | version = "0.2.126" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 60 | 61 | [[package]] 62 | name = "num_cpus" 63 | version = "1.13.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 66 | dependencies = [ 67 | "hermit-abi", 68 | "libc", 69 | ] 70 | 71 | [[package]] 72 | name = "once_cell" 73 | version = "1.12.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 76 | 77 | [[package]] 78 | name = "parking_lot_core" 79 | version = "0.9.3" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 82 | dependencies = [ 83 | "cfg-if", 84 | "libc", 85 | "redox_syscall", 86 | "smallvec", 87 | "windows-sys", 88 | ] 89 | 90 | [[package]] 91 | name = "redox_syscall" 92 | version = "0.2.13" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 95 | dependencies = [ 96 | "bitflags", 97 | ] 98 | 99 | [[package]] 100 | name = "smallvec" 101 | version = "1.9.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 104 | 105 | [[package]] 106 | name = "windows-sys" 107 | version = "0.36.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 110 | dependencies = [ 111 | "windows_aarch64_msvc", 112 | "windows_i686_gnu", 113 | "windows_i686_msvc", 114 | "windows_x86_64_gnu", 115 | "windows_x86_64_msvc", 116 | ] 117 | 118 | [[package]] 119 | name = "windows_aarch64_msvc" 120 | version = "0.36.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 123 | 124 | [[package]] 125 | name = "windows_i686_gnu" 126 | version = "0.36.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 129 | 130 | [[package]] 131 | name = "windows_i686_msvc" 132 | version = "0.36.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 135 | 136 | [[package]] 137 | name = "windows_x86_64_gnu" 138 | version = "0.36.1" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 141 | 142 | [[package]] 143 | name = "windows_x86_64_msvc" 144 | version = "0.36.1" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 147 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bus" 3 | version = "2.4.1" 4 | authors = ["Jon Gjengset "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | readme = "README.md" 9 | description = "A lock-free, bounded, single-producer, multi-consumer, broadcast channel." 10 | repository = "https://github.com/jonhoo/bus.git" 11 | 12 | keywords = ["channel","broadcast","lock-free"] 13 | categories = ["concurrency"] 14 | 15 | [dependencies] 16 | num_cpus = "1.6.2" 17 | parking_lot_core = "0.9" 18 | crossbeam-channel = "0.5" 19 | 20 | [profile.release] 21 | debug = true 22 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jon Gjengset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bus 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/bus.svg)](https://crates.io/crates/bus) 4 | [![Documentation](https://docs.rs/bus/badge.svg)](https://docs.rs/bus/) 5 | [![Codecov](https://codecov.io/github/jonhoo/bus/coverage.svg?branch=master)](https://codecov.io/gh/jonhoo/bus) 6 | 7 | Bus provides a lock-free, bounded, single-producer, multi-consumer, broadcast channel. 8 | 9 | **NOTE: bus sometimes busy-waits in the current implementation, which may cause increased CPU usage — see [#23](https://github.com/jonhoo/bus/issues/23).** 10 | 11 | It uses a circular buffer and atomic instructions to implement a lock-free single-producer, 12 | multi-consumer channel. The interface is similar to that of the `std::sync::mpsc` channels, 13 | except that multiple consumers (readers of the channel) can be produced, whereas only a single 14 | sender can exist. Furthermore, in contrast to most multi-consumer FIFO queues, bus is 15 | *broadcast*; every send goes to every consumer. 16 | 17 | I haven't seen this particular implementation in literature (some extra bookkeeping is 18 | necessary to allow multiple consumers), but a lot of related reading can be found in Ross 19 | Bencina's blog post ["Some notes on lock-free and wait-free 20 | algorithms"](http://www.rossbencina.com/code/lockfree). 21 | 22 | See [the documentation] for usage examples. 23 | 24 | [the documentation]: https://docs.rs/bus/ 25 | 26 | ## License 27 | 28 | Licensed under either of 29 | 30 | * Apache License, Version 2.0 31 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 32 | * MIT license 33 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 34 | 35 | at your option. 36 | 37 | ## Contribution 38 | 39 | Unless you explicitly state otherwise, any contribution intentionally submitted 40 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 41 | dual licensed as above, without any additional terms or conditions. 42 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Bus provides a lock-free, bounded, single-producer, multi-consumer, broadcast channel. 2 | //! 3 | //! It uses a circular buffer and atomic instructions to implement a lock-free single-producer, 4 | //! multi-consumer channel. The interface is similar to that of the `std::sync::mpsc` channels, 5 | //! except that multiple consumers (readers of the channel) can be produced, whereas only a single 6 | //! sender can exist. Furthermore, in contrast to most multi-consumer FIFO queues, bus is 7 | //! *broadcast*; every send goes to every consumer. 8 | //! 9 | //! I haven't seen this particular implementation in literature (some extra bookkeeping is 10 | //! necessary to allow multiple consumers), but a lot of related reading can be found in Ross 11 | //! Bencina's blog post ["Some notes on lock-free and wait-free 12 | //! algorithms"](http://www.rossbencina.com/code/lockfree). 13 | //! 14 | //! Bus achieves broadcast by cloning the element in question, which is why `T` must implement 15 | //! `Clone`. However, Bus is clever about only cloning when necessary. Specifically, the last 16 | //! consumer to see a given value will move it instead of cloning, which means no cloning is 17 | //! happening for the single-consumer case. For cases where cloning is expensive, `Arc` should be 18 | //! used instead. 19 | //! 20 | //! # Examples 21 | //! 22 | //! Single-send, multi-consumer example 23 | //! 24 | //! ```rust 25 | //! use bus::Bus; 26 | //! let mut bus = Bus::new(10); 27 | //! let mut rx1 = bus.add_rx(); 28 | //! let mut rx2 = bus.add_rx(); 29 | //! 30 | //! bus.broadcast("Hello"); 31 | //! assert_eq!(rx1.recv(), Ok("Hello")); 32 | //! assert_eq!(rx2.recv(), Ok("Hello")); 33 | //! ``` 34 | //! 35 | //! Multi-send, multi-consumer example 36 | //! 37 | //! ```rust 38 | //! # if cfg!(miri) { return } // Miri is too slow 39 | //! use bus::Bus; 40 | //! use std::thread; 41 | //! 42 | //! let mut bus = Bus::new(10); 43 | //! let mut rx1 = bus.add_rx(); 44 | //! let mut rx2 = bus.add_rx(); 45 | //! 46 | //! // start a thread that sends 1..100 47 | //! let j = thread::spawn(move || { 48 | //! for i in 1..100 { 49 | //! bus.broadcast(i); 50 | //! } 51 | //! }); 52 | //! 53 | //! // every value should be received by both receivers 54 | //! for i in 1..100 { 55 | //! // rx1 56 | //! assert_eq!(rx1.recv(), Ok(i)); 57 | //! // and rx2 58 | //! assert_eq!(rx2.recv(), Ok(i)); 59 | //! } 60 | //! 61 | //! j.join().unwrap(); 62 | //! ``` 63 | //! 64 | //! Many-to-many channel using a dispatcher 65 | //! 66 | //! ```rust 67 | //! use bus::Bus; 68 | //! 69 | //! use std::thread; 70 | //! use std::sync::mpsc; 71 | //! 72 | //! // set up fan-in 73 | //! let (tx1, mix_rx) = mpsc::sync_channel(100); 74 | //! let tx2 = tx1.clone(); 75 | //! // set up fan-out 76 | //! let mut mix_tx = Bus::new(100); 77 | //! let mut rx1 = mix_tx.add_rx(); 78 | //! let mut rx2 = mix_tx.add_rx(); 79 | //! // start dispatcher 80 | //! thread::spawn(move || { 81 | //! for m in mix_rx.iter() { 82 | //! mix_tx.broadcast(m); 83 | //! } 84 | //! }); 85 | //! 86 | //! // sends on tx1 are received ... 87 | //! tx1.send("Hello").unwrap(); 88 | //! 89 | //! // ... by both receiver rx1 ... 90 | //! assert_eq!(rx1.recv(), Ok("Hello")); 91 | //! // ... and receiver rx2 92 | //! assert_eq!(rx2.recv(), Ok("Hello")); 93 | //! 94 | //! // same with sends on tx2 95 | //! tx2.send("world").unwrap(); 96 | //! assert_eq!(rx1.recv(), Ok("world")); 97 | //! assert_eq!(rx2.recv(), Ok("world")); 98 | //! ``` 99 | 100 | #![deny(missing_docs)] 101 | #![warn(rust_2018_idioms)] 102 | 103 | use crossbeam_channel as mpsc; 104 | use parking_lot_core::SpinWait; 105 | 106 | use std::cell::UnsafeCell; 107 | use std::fmt; 108 | use std::marker::PhantomData; 109 | use std::ops::Deref; 110 | use std::ptr; 111 | use std::sync::atomic; 112 | use std::sync::mpsc as std_mpsc; 113 | use std::sync::Arc; 114 | use std::thread; 115 | use std::time; 116 | 117 | const SPINTIME: u32 = 100_000; //ns 118 | 119 | struct SeatState { 120 | max: usize, 121 | val: Option, 122 | } 123 | 124 | struct MutSeatState(UnsafeCell>); 125 | unsafe impl Sync for MutSeatState {} 126 | impl Deref for MutSeatState { 127 | type Target = UnsafeCell>; 128 | fn deref(&self) -> &Self::Target { 129 | &self.0 130 | } 131 | } 132 | 133 | impl fmt::Debug for MutSeatState { 134 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 135 | f.debug_tuple("MutSeatState").field(&self.0).finish() 136 | } 137 | } 138 | 139 | /// A Seat is a single location in the circular buffer. 140 | /// Each Seat knows how many readers are expected to access it, as well as how many have. The 141 | /// producer will never modify a seat's state unless all readers for a particular seat have either 142 | /// called `.take()` on it, or have left (see `Bus.rleft`). 143 | /// 144 | /// The producer walks the seats of the ring in order, and will always only modify the seat at 145 | /// `tail + 1` once all readers have finished with the seat at `head + 2`. A reader will never 146 | /// access a seat unless it is between the reader's `head` and the producer's `tail`. Together, 147 | /// these properties ensure that a Seat is either accessed only by readers, or by only the 148 | /// producer. 149 | /// 150 | /// The `read` attribute is used to ensure that readers see the most recent write to the seat when 151 | /// they access it. This is done using `atomic::Ordering::Acquire` and `atomic::Ordering::Release`. 152 | struct Seat { 153 | read: atomic::AtomicUsize, 154 | state: MutSeatState, 155 | 156 | // is the writer waiting for this seat to be emptied? needs to be atomic since both the last 157 | // reader and the writer might be accessing it at the same time. 158 | waiting: AtomicOption, 159 | } 160 | 161 | impl fmt::Debug for Seat { 162 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 163 | f.debug_struct("Seat") 164 | .field("read", &self.read) 165 | .field("state", &self.state) 166 | .field("waiting", &self.waiting) 167 | .finish() 168 | } 169 | } 170 | 171 | impl Seat { 172 | /// take is used by a reader to extract a copy of the value stored on this seat. only readers 173 | /// that were created strictly before the time this seat was last written to by the producer 174 | /// are allowed to call this method, and they may each only call it once. 175 | fn take(&self) -> T { 176 | let read = self.read.load(atomic::Ordering::Acquire); 177 | 178 | // the writer will only modify this element when .read hits .max - writer.rleft[i]. we can 179 | // be sure that this is not currently the case (which means it's safe for us to read) 180 | // because: 181 | // 182 | // - .max is set to the number of readers at the time when the write happens 183 | // - any joining readers will start at a later seat 184 | // - so, at most .max readers will call .take() on this seat this time around the buffer 185 | // - a reader must leave either *before* or *after* a call to recv. there are two cases: 186 | // 187 | // - it leaves before, rleft is decremented, but .take is not called 188 | // - it leaves after, .take is called, but head has been incremented, so rleft will be 189 | // decremented for the *next* seat, not this one 190 | // 191 | // so, either .take is called, and .read is incremented, or writer.rleft is incremented. 192 | // thus, for a writer to modify this element, *all* readers at the time of the previous 193 | // write to this seat must have either called .take or have left. 194 | // - since we are one of those readers, this cannot be true, so it's safe for us to assume 195 | // that there is no concurrent writer for this seat 196 | let state = unsafe { &*self.state.get() }; 197 | assert!( 198 | read < state.max, 199 | "reader hit seat with exhausted reader count" 200 | ); 201 | 202 | let mut waiting = None; 203 | 204 | // NOTE 205 | // we must extract the value *before* we decrement the number of remaining items otherwise, 206 | // the object might be replaced by the time we read it! 207 | let v = if read + 1 == state.max { 208 | // we're the last reader, so we may need to notify the writer there's space in the buf. 209 | // can be relaxed, since the acquire at the top already guarantees that we'll see 210 | // updates. 211 | waiting = self.waiting.take(); 212 | 213 | // since we're the last reader, no-one else will be cloning this value, so we can 214 | // safely take a mutable reference, and just take the val instead of cloning it. 215 | unsafe { &mut *self.state.get() }.val.take().unwrap() 216 | } else { 217 | let v = state 218 | .val 219 | .clone() 220 | .expect("seat that should be occupied was empty"); 221 | 222 | // let writer know that we no longer need this item. 223 | // state is no longer safe to access. 224 | #[allow(clippy::drop_ref)] 225 | drop(state); 226 | v 227 | }; 228 | 229 | self.read.fetch_add(1, atomic::Ordering::AcqRel); 230 | 231 | if let Some(t) = waiting { 232 | // writer was waiting for us to finish with this 233 | t.unpark(); 234 | } 235 | 236 | v 237 | } 238 | } 239 | 240 | impl Default for Seat { 241 | fn default() -> Self { 242 | Seat { 243 | read: atomic::AtomicUsize::new(0), 244 | waiting: AtomicOption::empty(), 245 | state: MutSeatState(UnsafeCell::new(SeatState { max: 0, val: None })), 246 | } 247 | } 248 | } 249 | 250 | /// `BusInner` encapsulates data that both the writer and the readers need to access. The tail is 251 | /// only ever modified by the producer, and read by the consumers. The length of the bus is 252 | /// instantiated when the bus is created, and is never modified. 253 | struct BusInner { 254 | ring: Vec>, 255 | len: usize, 256 | tail: atomic::AtomicUsize, 257 | closed: atomic::AtomicBool, 258 | } 259 | 260 | impl fmt::Debug for BusInner { 261 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 262 | f.debug_struct("BusInner") 263 | .field("ring", &self.ring) 264 | .field("len", &self.len) 265 | .field("tail", &self.tail) 266 | .field("closed", &self.closed) 267 | .finish() 268 | } 269 | } 270 | 271 | /// `Bus` is the main interconnect for broadcast messages. It can be used to send broadcast 272 | /// messages, or to connect additional consumers. When the `Bus` is dropped, receivers will 273 | /// continue receiving any outstanding broadcast messages they would have received if the bus were 274 | /// not dropped. After all those messages have been received, any subsequent receive call on a 275 | /// receiver will return a disconnected error. 276 | pub struct Bus { 277 | state: Arc>, 278 | 279 | // current number of readers 280 | readers: usize, 281 | 282 | // rleft keeps track of readers that should be skipped for each index. we must do this because 283 | // .read will be < max for those indices, even though all active readers have received them. 284 | rleft: Vec, 285 | 286 | // leaving is used by receivers to signal that they are done 287 | leaving: (mpsc::Sender, mpsc::Receiver), 288 | 289 | // waiting is used by receivers to signal that they are waiting for new entries, and where they 290 | // are waiting 291 | #[allow(clippy::type_complexity)] 292 | waiting: ( 293 | mpsc::Sender<(thread::Thread, usize)>, 294 | mpsc::Receiver<(thread::Thread, usize)>, 295 | ), 296 | 297 | // channel used to communicate to unparker that a given thread should be woken up 298 | unpark: mpsc::Sender, 299 | 300 | // cache used to keep track of threads waiting for next write. 301 | // this is only here to avoid allocating one on every broadcast() 302 | cache: Vec<(thread::Thread, usize)>, 303 | } 304 | 305 | impl fmt::Debug for Bus { 306 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 307 | f.debug_struct("Bus") 308 | .field("state", &self.state) 309 | .field("readers", &self.readers) 310 | .field("rleft", &self.rleft) 311 | .field("leaving", &self.leaving) 312 | .field("waiting", &self.waiting) 313 | .field("unpark", &self.unpark) 314 | .field("cache", &self.cache) 315 | .finish() 316 | } 317 | } 318 | 319 | impl Bus { 320 | /// Allocates a new `Bus`. 321 | /// 322 | /// The provided length should be sufficient to absorb temporary peaks in the data flow, and is 323 | /// thus workflow-dependent. Bus performance degrades somewhat when the queue is full, so it is 324 | /// generally better to set this high than low unless you are pressed for memory. 325 | pub fn new(mut len: usize) -> Bus { 326 | use std::iter; 327 | 328 | // ring buffer must have room for one padding element 329 | len += 1; 330 | 331 | let inner = Arc::new(BusInner { 332 | ring: (0..len).map(|_| Seat::default()).collect(), 333 | tail: atomic::AtomicUsize::new(0), 334 | closed: atomic::AtomicBool::new(false), 335 | len, 336 | }); 337 | 338 | // work around https://github.com/rust-lang/rust/issues/59020 339 | if !cfg!(miri) && cfg!(target = "macos") { 340 | let _ = time::Instant::now().elapsed(); 341 | } 342 | 343 | // we run a separate thread responsible for unparking 344 | // so we don't have to wait for unpark() to return in broadcast_inner 345 | // sending on a channel without contention is cheap, unparking is not 346 | let (unpark_tx, unpark_rx) = mpsc::unbounded::(); 347 | let _ = thread::Builder::new() 348 | .name("bus_unparking".to_owned()) 349 | .spawn(move || { 350 | for t in unpark_rx.iter() { 351 | t.unpark(); 352 | } 353 | }); 354 | 355 | Bus { 356 | state: inner, 357 | readers: 0, 358 | rleft: iter::repeat(0).take(len).collect(), 359 | leaving: mpsc::unbounded(), 360 | waiting: mpsc::unbounded(), 361 | unpark: unpark_tx, 362 | 363 | cache: Vec::new(), 364 | } 365 | } 366 | 367 | /// Get the expected number of reads for the given seat. This number will always be 368 | /// conservative, in that fewer reads may be fine. Specifically, `.rleft` may not be 369 | /// sufficiently up-to-date to account for all readers that have left. 370 | #[inline] 371 | fn expected(&mut self, at: usize) -> usize { 372 | // since only the producer will modify the ring, and &mut self guarantees that *we* are the 373 | // producer, no-one is modifying the ring. Multiple read-only borrows are safe, and so the 374 | // cast below is safe. 375 | unsafe { &*self.state.ring[at].state.get() }.max - self.rleft[at] 376 | } 377 | 378 | /// Attempts to place the given value on the bus. 379 | /// 380 | /// If the bus is full, the behavior depends on `block`. If false, the value given is returned 381 | /// in an `Err()`. Otherwise, the current thread will be parked until there is space in the bus 382 | /// again, and the broadcast will be tried again until it succeeds. 383 | /// 384 | /// Note that broadcasts will succeed even if there are no consumers! 385 | fn broadcast_inner(&mut self, val: T, block: bool) -> Result<(), T> { 386 | let tail = self.state.tail.load(atomic::Ordering::Relaxed); 387 | 388 | // we want to check if the next element over is free to ensure that we always leave one 389 | // empty space between the head and the tail. This is necessary so that readers can 390 | // distinguish between an empty and a full list. If the fence seat is free, the seat at 391 | // tail must also be free, which is simple enough to show by induction (exercise for the 392 | // reader). 393 | let fence = (tail + 1) % self.state.len; 394 | 395 | let spintime = time::Duration::new(0, SPINTIME); 396 | 397 | // to avoid parking when a slot frees up quickly, we use an exponential back-off SpinWait. 398 | let mut sw = SpinWait::new(); 399 | loop { 400 | let fence_read = self.state.ring[fence].read.load(atomic::Ordering::Acquire); 401 | 402 | // is there room left in the ring? 403 | if fence_read == self.expected(fence) { 404 | break; 405 | } 406 | 407 | // no! 408 | // let's check if any readers have left, which might increment self.rleft[tail]. 409 | while let Ok(mut left) = self.leaving.1.try_recv() { 410 | // a reader has left! this means that every seat between `left` and `tail-1` 411 | // has max set one too high. we track the number of such "missing" reads that 412 | // should be ignored in self.rleft, and compensate for them when looking at 413 | // seat.read above. 414 | self.readers -= 1; 415 | while left != tail { 416 | self.rleft[left] += 1; 417 | left = (left + 1) % self.state.len 418 | } 419 | } 420 | 421 | // is the fence block now free? 422 | if fence_read == self.expected(fence) { 423 | // yes! go ahead and write! 424 | break; 425 | } else if block { 426 | // no, so block by parking and telling readers to notify on last read 427 | self.state.ring[fence] 428 | .waiting 429 | .swap(Some(Box::new(thread::current()))); 430 | 431 | // need the atomic fetch_add to ensure reader threads will see the new .waiting 432 | self.state.ring[fence] 433 | .read 434 | .fetch_add(0, atomic::Ordering::Release); 435 | 436 | if !sw.spin() { 437 | // not likely to get a slot soon -- wait to be unparked instead. 438 | // note that we *need* to wait, because there are some cases in which we 439 | // *won't* be unparked even though a slot has opened up. 440 | thread::park_timeout(spintime); 441 | } 442 | continue; 443 | } else { 444 | // no, and blocking isn't allowed, so return an error 445 | return Err(val); 446 | } 447 | } 448 | 449 | // next one over is free, we have a free seat! 450 | let readers = self.readers; 451 | { 452 | let next = &self.state.ring[tail]; 453 | // we are the only writer, so no-one else can be writing. however, since we're 454 | // mutating state, we also need for there to be no readers for this to be safe. the 455 | // argument for why this is the case is roughly an inverse of the argument for why 456 | // the unsafe block in Seat.take() is safe. basically, since 457 | // 458 | // .read + .rleft == .max 459 | // 460 | // we know all readers at the time of the seat's previous write have accessed this 461 | // seat. we also know that no other readers will access that seat (they must have 462 | // started at later seats). thus, we are the only thread accessing this seat, and 463 | // so we can safely access it as mutable. 464 | let state = unsafe { &mut *next.state.get() }; 465 | state.max = readers; 466 | state.val = Some(val); 467 | next.waiting.take(); 468 | next.read.store(0, atomic::Ordering::Release); 469 | } 470 | self.rleft[tail] = 0; 471 | // now tell readers that they can read 472 | let tail = (tail + 1) % self.state.len; 473 | self.state.tail.store(tail, atomic::Ordering::Release); 474 | 475 | // unblock any blocked receivers 476 | while let Ok((t, at)) = self.waiting.1.try_recv() { 477 | // the only readers we can't unblock are those that have already absorbed the 478 | // broadcast we just made, since they are blocking on the *next* broadcast 479 | if at == tail { 480 | self.cache.push((t, at)) 481 | } else { 482 | self.unpark.send(t).unwrap(); 483 | } 484 | } 485 | for w in self.cache.drain(..) { 486 | // fine to do here because it is guaranteed not to block 487 | self.waiting.0.send(w).unwrap(); 488 | } 489 | 490 | Ok(()) 491 | } 492 | 493 | /// Attempt to broadcast the given value to all consumers, but does not block if full. 494 | /// 495 | /// Note that, in contrast to regular channels, a bus is *not* considered closed if there are 496 | /// no consumers, and thus broadcasts will continue to succeed. Thus, a successful broadcast 497 | /// occurs as long as there is room on the internal bus to store the value, or some older value 498 | /// has been received by all consumers. Note that a return value of `Err` means that the data 499 | /// will never be received (by any consumer), but a return value of Ok does not mean that the 500 | /// data will be received by a given consumer. It is possible for a receiver to hang up 501 | /// immediately after this function returns Ok. 502 | /// 503 | /// This method will never block the current thread. 504 | /// 505 | /// ```rust 506 | /// use bus::Bus; 507 | /// let mut tx = Bus::new(1); 508 | /// let mut rx = tx.add_rx(); 509 | /// assert_eq!(tx.try_broadcast("Hello"), Ok(())); 510 | /// assert_eq!(tx.try_broadcast("world"), Err("world")); 511 | /// ``` 512 | pub fn try_broadcast(&mut self, val: T) -> Result<(), T> { 513 | self.broadcast_inner(val, false) 514 | } 515 | 516 | /// Broadcasts a value on the bus to all consumers. 517 | /// 518 | /// This function will block until space in the internal buffer becomes available. 519 | /// 520 | /// Note that a successful send does not guarantee that the receiver will ever see the data if 521 | /// there is a buffer on this channel. Items may be enqueued in the internal buffer for the 522 | /// receiver to receive at a later time. Furthermore, in contrast to regular channels, a bus is 523 | /// *not* considered closed if there are no consumers, and thus broadcasts will continue to 524 | /// succeed. 525 | pub fn broadcast(&mut self, val: T) { 526 | if let Err(..) = self.broadcast_inner(val, true) { 527 | unreachable!("blocking broadcast_inner can't fail"); 528 | } 529 | } 530 | 531 | /// Add a new consumer to this bus. 532 | /// 533 | /// The new consumer will receive all *future* broadcasts on this bus. 534 | /// 535 | /// # Examples 536 | /// 537 | /// ```rust 538 | /// use bus::Bus; 539 | /// use std::sync::mpsc::TryRecvError; 540 | /// 541 | /// let mut bus = Bus::new(10); 542 | /// let mut rx1 = bus.add_rx(); 543 | /// 544 | /// bus.broadcast("Hello"); 545 | /// 546 | /// // consumer present during broadcast sees update 547 | /// assert_eq!(rx1.recv(), Ok("Hello")); 548 | /// 549 | /// // new consumer does *not* see broadcast 550 | /// let mut rx2 = bus.add_rx(); 551 | /// assert_eq!(rx2.try_recv(), Err(TryRecvError::Empty)); 552 | /// 553 | /// // both consumers see new broadcast 554 | /// bus.broadcast("world"); 555 | /// assert_eq!(rx1.recv(), Ok("world")); 556 | /// assert_eq!(rx2.recv(), Ok("world")); 557 | /// ``` 558 | pub fn add_rx(&mut self) -> BusReader { 559 | self.readers += 1; 560 | 561 | BusReader { 562 | bus: Arc::clone(&self.state), 563 | head: self.state.tail.load(atomic::Ordering::Relaxed), 564 | leaving: self.leaving.0.clone(), 565 | waiting: self.waiting.0.clone(), 566 | closed: false, 567 | } 568 | } 569 | 570 | /// Returns the number of active consumers currently attached to this bus. 571 | /// 572 | /// It is not guaranteed that a sent message will reach this number of consumers, as active 573 | /// consumers may never call `recv` or `try_recv` again before dropping. 574 | /// 575 | /// # Examples 576 | /// 577 | /// ```rust 578 | /// use bus::Bus; 579 | /// 580 | /// let mut bus = Bus::::new(10); 581 | /// assert_eq!(bus.rx_count(), 0); 582 | /// 583 | /// let rx1 = bus.add_rx(); 584 | /// assert_eq!(bus.rx_count(), 1); 585 | /// 586 | /// drop(rx1); 587 | /// assert_eq!(bus.rx_count(), 0); 588 | /// ``` 589 | pub fn rx_count(&self) -> usize { 590 | self.readers - self.leaving.1.len() 591 | } 592 | } 593 | 594 | impl Drop for Bus { 595 | fn drop(&mut self) { 596 | self.state.closed.store(true, atomic::Ordering::Relaxed); 597 | // Acquire/Release .tail to ensure other threads see new .closed 598 | self.state.tail.fetch_add(0, atomic::Ordering::AcqRel); 599 | // TODO: unpark receivers -- this is not absolutely necessary, since the reader's park will 600 | // time out, but it would cause them to detect the closed bus somewhat faster. 601 | } 602 | } 603 | 604 | #[derive(Clone, Copy)] 605 | enum RecvCondition { 606 | Try, 607 | Block, 608 | Timeout(time::Duration), 609 | } 610 | 611 | /// A `BusReader` is a single consumer of `Bus` broadcasts. It will see every new value that is 612 | /// passed to `.broadcast()` (or successful calls to `.try_broadcast()`) on the `Bus` that it was 613 | /// created from. 614 | /// 615 | /// Dropping a `BusReader` is perfectly safe, and will unblock the writer if it was waiting for 616 | /// that read to see a particular update. 617 | /// 618 | /// ```rust 619 | /// use bus::Bus; 620 | /// let mut tx = Bus::new(1); 621 | /// let mut r1 = tx.add_rx(); 622 | /// let r2 = tx.add_rx(); 623 | /// assert_eq!(tx.try_broadcast(true), Ok(())); 624 | /// assert_eq!(r1.recv(), Ok(true)); 625 | /// 626 | /// // the bus does not have room for another broadcast 627 | /// // since it knows r2 has not yet read the first broadcast 628 | /// assert_eq!(tx.try_broadcast(true), Err(true)); 629 | /// 630 | /// // dropping r2 tells the producer that there is a free slot 631 | /// // (i.e., it has been read by everyone) 632 | /// drop(r2); 633 | /// assert_eq!(tx.try_broadcast(true), Ok(())); 634 | /// ``` 635 | pub struct BusReader { 636 | bus: Arc>, 637 | head: usize, 638 | leaving: mpsc::Sender, 639 | waiting: mpsc::Sender<(thread::Thread, usize)>, 640 | closed: bool, 641 | } 642 | 643 | impl fmt::Debug for BusReader { 644 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 645 | f.debug_struct("BusReader") 646 | .field("bus", &self.bus) 647 | .field("head", &self.head) 648 | .field("leaving", &self.leaving) 649 | .field("waiting", &self.waiting) 650 | .field("closed", &self.closed) 651 | .finish() 652 | } 653 | } 654 | 655 | impl BusReader { 656 | /// Attempts to read a broadcast from the bus. 657 | /// 658 | /// If the bus is empty, the behavior depends on `block`. If false, 659 | /// `Err(mpsc::RecvTimeoutError::Timeout)` is returned. Otherwise, the current thread will be 660 | /// parked until there is another broadcast on the bus, at which point the receive will be 661 | /// performed. 662 | fn recv_inner(&mut self, block: RecvCondition) -> Result { 663 | if self.closed { 664 | return Err(std_mpsc::RecvTimeoutError::Disconnected); 665 | } 666 | 667 | let start = match block { 668 | RecvCondition::Timeout(_) => Some(time::Instant::now()), 669 | _ => None, 670 | }; 671 | 672 | let spintime = time::Duration::new(0, SPINTIME); 673 | 674 | let mut was_closed = false; 675 | let mut sw = SpinWait::new(); 676 | let mut first = true; 677 | loop { 678 | let tail = self.bus.tail.load(atomic::Ordering::Acquire); 679 | if tail != self.head { 680 | break; 681 | } 682 | 683 | // buffer is empty, check whether it's closed. 684 | // relaxed is fine since Bus.drop does an acquire/release on tail 685 | if self.bus.closed.load(atomic::Ordering::Relaxed) { 686 | // we need to check again that there's nothing in the bus, otherwise we might have 687 | // missed a write between when we did the read of .tail above and when we read 688 | // .closed here 689 | if !was_closed { 690 | was_closed = true; 691 | continue; 692 | } 693 | 694 | // the bus is closed, and we didn't miss anything! 695 | self.closed = true; 696 | return Err(std_mpsc::RecvTimeoutError::Disconnected); 697 | } 698 | 699 | // not closed, should we block? 700 | if let RecvCondition::Try = block { 701 | return Err(std_mpsc::RecvTimeoutError::Timeout); 702 | } 703 | 704 | // park and tell writer to notify on write 705 | if first { 706 | if let Err(..) = self.waiting.send((thread::current(), self.head)) { 707 | // writer has gone away, but somehow we _just_ missed the close signal (in 708 | // self.bus.closed). iterate again to ensure the channel is _actually_ empty. 709 | atomic::fence(atomic::Ordering::SeqCst); 710 | continue; 711 | } 712 | first = false; 713 | } 714 | 715 | if !sw.spin() { 716 | match block { 717 | RecvCondition::Timeout(t) => { 718 | match t.checked_sub(start.as_ref().unwrap().elapsed()) { 719 | Some(left) => { 720 | if left < spintime { 721 | thread::park_timeout(left); 722 | } else { 723 | thread::park_timeout(spintime); 724 | } 725 | } 726 | None => { 727 | // So, the wake-up thread is still going to try to wake us up later 728 | // since we sent thread::current() above, but that's fine. 729 | return Err(std_mpsc::RecvTimeoutError::Timeout); 730 | } 731 | } 732 | } 733 | RecvCondition::Block => { 734 | thread::park_timeout(spintime); 735 | } 736 | RecvCondition::Try => unreachable!(), 737 | } 738 | } 739 | } 740 | 741 | let head = self.head; 742 | let ret = self.bus.ring[head].take(); 743 | 744 | // safe because len is read-only 745 | self.head = (head + 1) % self.bus.len; 746 | Ok(ret) 747 | } 748 | 749 | /// Attempts to return a pending broadcast on this receiver without blocking. 750 | /// 751 | /// This method will never block the caller in order to wait for data to become available. 752 | /// Instead, this will always return immediately with a possible option of pending data on the 753 | /// channel. 754 | /// 755 | /// If the corresponding bus has been dropped, and all broadcasts have been received, this 756 | /// method will return with a disconnected error. 757 | /// 758 | /// This method is useful for a flavor of "optimistic check" before deciding to block on a 759 | /// receiver. 760 | /// 761 | /// ```rust 762 | /// use bus::Bus; 763 | /// use std::thread; 764 | /// 765 | /// let mut tx = Bus::new(10); 766 | /// let mut rx = tx.add_rx(); 767 | /// 768 | /// // spawn a thread that will broadcast at some point 769 | /// let j = thread::spawn(move || { 770 | /// tx.broadcast(true); 771 | /// }); 772 | /// 773 | /// loop { 774 | /// match rx.try_recv() { 775 | /// Ok(val) => { 776 | /// assert_eq!(val, true); 777 | /// break; 778 | /// } 779 | /// Err(..) => { 780 | /// // maybe we can do other useful work here 781 | /// // or we can just busy-loop 782 | /// thread::yield_now() 783 | /// }, 784 | /// } 785 | /// } 786 | /// 787 | /// j.join().unwrap(); 788 | /// ``` 789 | pub fn try_recv(&mut self) -> Result { 790 | self.recv_inner(RecvCondition::Try).map_err(|e| match e { 791 | std_mpsc::RecvTimeoutError::Disconnected => std_mpsc::TryRecvError::Disconnected, 792 | std_mpsc::RecvTimeoutError::Timeout => std_mpsc::TryRecvError::Empty, 793 | }) 794 | } 795 | 796 | /// Read another broadcast message from the bus, and block if none are available. 797 | /// 798 | /// This function will always block the current thread if there is no data available and it's 799 | /// possible for more broadcasts to be sent. Once a broadcast is sent on the corresponding 800 | /// `Bus`, then this receiver will wake up and return that message. 801 | /// 802 | /// If the corresponding `Bus` has been dropped, or it is dropped while this call is blocking, 803 | /// this call will wake up and return `Err` to indicate that no more messages can ever be 804 | /// received on this channel. However, since channels are buffered, messages sent before the 805 | /// disconnect will still be properly received. 806 | pub fn recv(&mut self) -> Result { 807 | match self.recv_inner(RecvCondition::Block) { 808 | Ok(val) => Ok(val), 809 | Err(std_mpsc::RecvTimeoutError::Disconnected) => Err(std_mpsc::RecvError), 810 | _ => unreachable!("blocking recv_inner can't fail"), 811 | } 812 | } 813 | 814 | /// Attempts to wait for a value from the bus, returning an error if the corresponding channel 815 | /// has hung up, or if it waits more than `timeout`. 816 | /// 817 | /// This function will always block the current thread if there is no data available and it's 818 | /// possible for more broadcasts to be sent. Once a message is sent on the corresponding `Bus`, 819 | /// then this receiver will wake up and return that message. 820 | /// 821 | /// If the corresponding `Bus` has been dropped, or it is dropped while this call is blocking, 822 | /// this call will wake up and return Err to indicate that no more messages can ever be 823 | /// received on this channel. However, since channels are buffered, messages sent before the 824 | /// disconnect will still be properly received. 825 | /// 826 | /// # Examples 827 | /// 828 | /// ```rust 829 | /// use bus::Bus; 830 | /// use std::sync::mpsc::RecvTimeoutError; 831 | /// use std::time::Duration; 832 | /// 833 | /// let mut tx = Bus::::new(10); 834 | /// let mut rx = tx.add_rx(); 835 | /// 836 | /// let timeout = Duration::from_millis(100); 837 | /// assert_eq!(Err(RecvTimeoutError::Timeout), rx.recv_timeout(timeout)); 838 | /// ``` 839 | pub fn recv_timeout( 840 | &mut self, 841 | timeout: time::Duration, 842 | ) -> Result { 843 | self.recv_inner(RecvCondition::Timeout(timeout)) 844 | } 845 | } 846 | 847 | impl BusReader { 848 | /// Returns an iterator that will block waiting for broadcasts. It will return `None` when the 849 | /// bus has been closed (i.e., the `Bus` has been dropped). 850 | pub fn iter(&mut self) -> BusIter<'_, T> { 851 | BusIter(self) 852 | } 853 | } 854 | 855 | impl Drop for BusReader { 856 | #[allow(unused_must_use)] 857 | fn drop(&mut self) { 858 | // we allow not checking the result here because the writer might have gone away, which 859 | // would result in an error, but is okay nonetheless. 860 | self.leaving.send(self.head); 861 | } 862 | } 863 | 864 | /// An iterator over messages on a receiver. This iterator will block whenever `next` is called, 865 | /// waiting for a new message, and `None` will be returned when the corresponding channel has been 866 | /// closed. 867 | pub struct BusIter<'a, T>(&'a mut BusReader); 868 | 869 | /// An owning iterator over messages on a receiver. This iterator will block whenever `next` is 870 | /// called, waiting for a new message, and `None` will be returned when the corresponding bus has 871 | /// been closed. 872 | pub struct BusIntoIter(BusReader); 873 | 874 | impl<'a, T: Clone + Sync> IntoIterator for &'a mut BusReader { 875 | type Item = T; 876 | type IntoIter = BusIter<'a, T>; 877 | fn into_iter(self) -> BusIter<'a, T> { 878 | BusIter(self) 879 | } 880 | } 881 | 882 | impl IntoIterator for BusReader { 883 | type Item = T; 884 | type IntoIter = BusIntoIter; 885 | fn into_iter(self) -> BusIntoIter { 886 | BusIntoIter(self) 887 | } 888 | } 889 | 890 | impl<'a, T: Clone + Sync> Iterator for BusIter<'a, T> { 891 | type Item = T; 892 | fn next(&mut self) -> Option { 893 | self.0.recv().ok() 894 | } 895 | } 896 | 897 | impl Iterator for BusIntoIter { 898 | type Item = T; 899 | fn next(&mut self) -> Option { 900 | self.0.recv().ok() 901 | } 902 | } 903 | 904 | struct AtomicOption { 905 | ptr: atomic::AtomicPtr, 906 | _marker: PhantomData>>, 907 | } 908 | 909 | impl fmt::Debug for AtomicOption { 910 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 911 | f.debug_struct("AtomicOption") 912 | .field("ptr", &self.ptr) 913 | .finish() 914 | } 915 | } 916 | 917 | unsafe impl Send for AtomicOption {} 918 | unsafe impl Sync for AtomicOption {} 919 | 920 | impl AtomicOption { 921 | fn empty() -> Self { 922 | Self { 923 | ptr: atomic::AtomicPtr::new(ptr::null_mut()), 924 | _marker: PhantomData, 925 | } 926 | } 927 | 928 | fn swap(&self, val: Option>) -> Option> { 929 | let old = match val { 930 | Some(val) => self.ptr.swap(Box::into_raw(val), atomic::Ordering::AcqRel), 931 | // Acquire is needed to synchronize with the store of a non-null ptr, but since a null ptr 932 | // will never be dereferenced, there is no need to synchronize the store of a null ptr. 933 | None => self.ptr.swap(ptr::null_mut(), atomic::Ordering::Acquire), 934 | }; 935 | if old.is_null() { 936 | None 937 | } else { 938 | // SAFETY: 939 | // - AcqRel/Acquire ensures that it does not read a pointer to potentially invalid memory. 940 | // - We've checked that old is not null. 941 | // - We do not store invalid pointers other than null in self.ptr. 942 | Some(unsafe { Box::from_raw(old) }) 943 | } 944 | } 945 | 946 | fn take(&self) -> Option> { 947 | self.swap(None) 948 | } 949 | } 950 | 951 | impl Drop for AtomicOption { 952 | fn drop(&mut self) { 953 | drop(self.take()); 954 | } 955 | } 956 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate bus; 2 | 3 | use std::sync::mpsc; 4 | use std::time; 5 | 6 | #[test] 7 | fn it_works() { 8 | let mut c = bus::Bus::new(10); 9 | let mut r1 = c.add_rx(); 10 | let mut r2 = c.add_rx(); 11 | assert_eq!(c.try_broadcast(true), Ok(())); 12 | assert_eq!(r1.try_recv(), Ok(true)); 13 | assert_eq!(r2.try_recv(), Ok(true)); 14 | } 15 | 16 | #[test] 17 | fn debug() { 18 | let mut c = bus::Bus::new(10); 19 | println!("{:?}", c); 20 | let mut r = c.add_rx(); 21 | println!("{:?}", c); 22 | println!("{:?}", r); 23 | assert_eq!(c.try_broadcast(true), Ok(())); 24 | println!("{:?}", c); 25 | assert_eq!(r.try_recv(), Ok(true)); 26 | println!("{:?}", c); 27 | } 28 | 29 | #[test] 30 | fn debug_not_inner() { 31 | // Foo does not implement Debug 32 | #[derive(Clone, Copy, PartialEq, Eq)] 33 | struct Foo; 34 | 35 | let mut c = bus::Bus::new(10); 36 | println!("{:?}", c); 37 | let mut r = c.add_rx(); 38 | println!("{:?}", c); 39 | println!("{:?}", r); 40 | assert!(matches!(c.try_broadcast(Foo), Ok(()))); 41 | println!("{:?}", c); 42 | assert!(matches!(r.try_recv(), Ok(Foo))); 43 | println!("{:?}", c); 44 | } 45 | 46 | #[test] 47 | fn it_fails_when_full() { 48 | let mut c = bus::Bus::new(1); 49 | let r1 = c.add_rx(); 50 | assert_eq!(c.try_broadcast(true), Ok(())); 51 | assert_eq!(c.try_broadcast(false), Err(false)); 52 | drop(r1); 53 | } 54 | 55 | #[test] 56 | fn it_succeeds_when_not_full() { 57 | let mut c = bus::Bus::new(1); 58 | let mut r1 = c.add_rx(); 59 | assert_eq!(c.try_broadcast(true), Ok(())); 60 | assert_eq!(c.try_broadcast(false), Err(false)); 61 | assert_eq!(r1.try_recv(), Ok(true)); 62 | assert_eq!(c.try_broadcast(true), Ok(())); 63 | } 64 | 65 | #[test] 66 | fn it_fails_when_empty() { 67 | let mut c = bus::Bus::::new(10); 68 | let mut r1 = c.add_rx(); 69 | assert_eq!(r1.try_recv(), Err(mpsc::TryRecvError::Empty)); 70 | } 71 | 72 | #[test] 73 | fn it_reads_when_full() { 74 | let mut c = bus::Bus::new(1); 75 | let mut r1 = c.add_rx(); 76 | assert_eq!(c.try_broadcast(true), Ok(())); 77 | assert_eq!(r1.try_recv(), Ok(true)); 78 | } 79 | 80 | #[test] 81 | #[cfg_attr(miri, ignore)] 82 | fn it_iterates() { 83 | use std::thread; 84 | 85 | let mut tx = bus::Bus::new(2); 86 | let mut rx = tx.add_rx(); 87 | let j = thread::spawn(move || { 88 | for i in 0..1_000 { 89 | tx.broadcast(i); 90 | } 91 | }); 92 | 93 | let mut ii = 0; 94 | for i in rx.iter() { 95 | assert_eq!(i, ii); 96 | ii += 1; 97 | } 98 | 99 | j.join().unwrap(); 100 | assert_eq!(ii, 1_000); 101 | assert_eq!(rx.try_recv(), Err(mpsc::TryRecvError::Disconnected)); 102 | } 103 | 104 | #[test] 105 | #[cfg_attr(miri, ignore)] 106 | fn aggressive_iteration() { 107 | for _ in 0..1_000 { 108 | use std::thread; 109 | 110 | let mut tx = bus::Bus::new(2); 111 | let mut rx = tx.add_rx(); 112 | let j = thread::spawn(move || { 113 | for i in 0..1_000 { 114 | tx.broadcast(i); 115 | } 116 | }); 117 | 118 | let mut ii = 0; 119 | for i in rx.iter() { 120 | assert_eq!(i, ii); 121 | ii += 1; 122 | } 123 | 124 | j.join().unwrap(); 125 | assert_eq!(ii, 1_000); 126 | assert_eq!(rx.try_recv(), Err(mpsc::TryRecvError::Disconnected)); 127 | } 128 | } 129 | 130 | #[test] 131 | fn it_detects_closure() { 132 | let mut tx = bus::Bus::new(1); 133 | let mut rx = tx.add_rx(); 134 | assert_eq!(tx.try_broadcast(true), Ok(())); 135 | assert_eq!(rx.try_recv(), Ok(true)); 136 | assert_eq!(rx.try_recv(), Err(mpsc::TryRecvError::Empty)); 137 | drop(tx); 138 | assert_eq!(rx.try_recv(), Err(mpsc::TryRecvError::Disconnected)); 139 | } 140 | 141 | #[test] 142 | fn it_recvs_after_close() { 143 | let mut tx = bus::Bus::new(1); 144 | let mut rx = tx.add_rx(); 145 | assert_eq!(tx.try_broadcast(true), Ok(())); 146 | drop(tx); 147 | assert_eq!(rx.try_recv(), Ok(true)); 148 | assert_eq!(rx.try_recv(), Err(mpsc::TryRecvError::Disconnected)); 149 | } 150 | 151 | #[test] 152 | fn it_handles_leaves() { 153 | let mut c = bus::Bus::new(1); 154 | let mut r1 = c.add_rx(); 155 | let r2 = c.add_rx(); 156 | assert_eq!(c.try_broadcast(true), Ok(())); 157 | drop(r2); 158 | assert_eq!(r1.try_recv(), Ok(true)); 159 | assert_eq!(c.try_broadcast(true), Ok(())); 160 | } 161 | 162 | #[test] 163 | #[cfg_attr(miri, ignore)] 164 | fn it_runs_blocked_writes() { 165 | use std::thread; 166 | 167 | let mut c = Box::new(bus::Bus::new(1)); 168 | let mut r1 = c.add_rx(); 169 | c.broadcast(true); // this is fine 170 | 171 | // buffer is now full 172 | assert_eq!(c.try_broadcast(false), Err(false)); 173 | // start other thread that blocks 174 | let c = thread::spawn(move || { 175 | c.broadcast(false); 176 | }); 177 | // unblock sender by receiving 178 | assert_eq!(r1.try_recv(), Ok(true)); 179 | // drop r1 to release other thread and safely drop c 180 | drop(r1); 181 | c.join().unwrap(); 182 | } 183 | 184 | #[test] 185 | #[cfg_attr(miri, ignore)] 186 | fn it_runs_blocked_reads() { 187 | use std::sync::mpsc; 188 | use std::thread; 189 | 190 | let mut tx = Box::new(bus::Bus::new(1)); 191 | let mut rx = tx.add_rx(); 192 | // buffer is now empty 193 | assert_eq!(rx.try_recv(), Err(mpsc::TryRecvError::Empty)); 194 | // start other thread that blocks 195 | let c = thread::spawn(move || { 196 | rx.recv().unwrap(); 197 | }); 198 | // unblock receiver by broadcasting 199 | tx.broadcast(true); 200 | // check that thread now finished 201 | c.join().unwrap(); 202 | } 203 | 204 | #[test] 205 | #[cfg_attr(miri, ignore)] 206 | fn it_can_count_to_10000() { 207 | use std::thread; 208 | 209 | let mut c = bus::Bus::new(2); 210 | let mut r1 = c.add_rx(); 211 | let j = thread::spawn(move || { 212 | for i in 0..10_000 { 213 | c.broadcast(i); 214 | } 215 | }); 216 | 217 | for i in 0..10_000 { 218 | assert_eq!(r1.recv(), Ok(i)); 219 | } 220 | 221 | j.join().unwrap(); 222 | assert_eq!(r1.try_recv(), Err(mpsc::TryRecvError::Disconnected)); 223 | } 224 | 225 | #[test] 226 | #[cfg_attr(miri, ignore)] 227 | fn test_busy() { 228 | use std::thread; 229 | 230 | // start a bus with limited space 231 | let mut bus = bus::Bus::new(1); 232 | 233 | // first receiver only receives 5 items 234 | let mut rx1 = bus.add_rx(); 235 | let t1 = thread::spawn(move || { 236 | for _ in 0..5 { 237 | rx1.recv().unwrap(); 238 | } 239 | drop(rx1); 240 | }); 241 | 242 | // second receiver receives 10 items 243 | let mut rx2 = bus.add_rx(); 244 | let t2 = thread::spawn(move || { 245 | for _ in 0..10 { 246 | rx2.recv().unwrap(); 247 | } 248 | drop(rx2); 249 | }); 250 | 251 | // let receivers start 252 | std::thread::sleep(time::Duration::from_millis(500)); 253 | 254 | // try to send 25 items -- should work fine 255 | for i in 0..25 { 256 | std::thread::sleep(time::Duration::from_millis(100)); 257 | match bus.try_broadcast(i) { 258 | Ok(_) => (), 259 | Err(e) => println!("Broadcast failed {}", e), 260 | } 261 | } 262 | 263 | // done sending -- wait for receivers (which should already be done) 264 | t1.join().unwrap(); 265 | t2.join().unwrap(); 266 | assert!(true); 267 | } 268 | --------------------------------------------------------------------------------