diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000..9a5f865
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,25 @@
+codecov:
+ require_ci_to_pass: false
+
+ignore:
+ - benchmark/
+ - src/abort.rs
+
+coverage:
+ status:
+ project: # Overall project status
+ default:
+ target: auto
+ if_not_found: success
+ only_pulls: false
+ patch: # Status for the patch in pull requests
+ default:
+ target: auto
+ if_not_found: success
+ only_pulls: true
+ changes: false # Whether to comment on the coverage changes in pull requests
+
+comment:
+ layout: "header, diff, files, footer"
+ behavior: default
+ require_changes: false
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
new file mode 100644
index 0000000..fd90028
--- /dev/null
+++ b/.github/workflows/benchmark.yml
@@ -0,0 +1,64 @@
+name: Benchmark
+
+on:
+ push:
+ branches:
+ - main
+ paths-ignore:
+ - 'README'
+ - 'COPYRIGHT'
+ - 'LICENSE-*'
+ - '**.md'
+ - '**.txt'
+ pull_request:
+ paths-ignore:
+ - 'README'
+ - 'COPYRIGHT'
+ - 'LICENSE-*'
+ - '**.md'
+ - '**.txt'
+
+env:
+ CARGO_TERM_COLOR: always
+ RUSTFLAGS: -Dwarnings
+ RUST_BACKTRACE: 1
+ nightly: nightly
+ stable: stable
+
+jobs:
+ benchmark:
+ name: benchmark
+ strategy:
+ matrix:
+ os:
+ - ubuntu-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Cache cargo build and registry
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: ${{ runner.os }}-test-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-test-
+ - name: Install Rust
+ # --no-self-update is necessary because the windows environment cannot self-update rustup.exe.
+ run: rustup update stable --no-self-update && rustup default stable
+ - name: Cache ~/.cargo
+ uses: actions/cache@v3
+ with:
+ path: ~/.cargo
+ key: ${{ runner.os }}-coverage-dotcargo
+ - name: Run test
+ run: |
+ cd benchmark
+ cargo bench
+ - name: Upload benchmark results
+ uses: actions/upload-artifact@v4
+ with:
+ name: benchmark-results
+ path: benchmark/target/criterion
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c6ba154..95aad26 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,6 +18,8 @@ on:
- '**.md'
- '**.txt'
+ workflow_dispatch:
+
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
@@ -63,7 +65,7 @@ jobs:
- name: Install cargo-hack
run: cargo install cargo-hack
- name: Apply clippy lints
- run: cargo hack clippy --each-feature
+ run: cargo hack clippy --each-feature --exclude-no-default-features
# Run tests on some extra platforms
cross:
@@ -104,13 +106,13 @@ jobs:
run: |
cargo install cross
cross build --target ${{ matrix.target }}
- if: matrix.target != 'wasm32-unknown-unknown'
- # # WASM support
- # - name: cargo build --target ${{ matrix.target }}
- # run: |
- # rustup target add ${{ matrix.target }}
- # cargo build --features js --target ${{ matrix.target }}
- # if: matrix.target == 'wasm32-unknown-unknown'
+ if: matrix.target != 'wasm32-unknown-unknown' && matrix.target != 'wasm32-wasi'
+ # WASM support
+ - name: cargo build --target ${{ matrix.target }}
+ run: |
+ rustup target add ${{ matrix.target }}
+ cargo build --target ${{ matrix.target }}
+ if: matrix.target == 'wasm32-unknown-unknown' || matrix.target == 'wasm32-wasi'
# - name: cargo build --target ${{ matrix.target }}
# run: |
# rustup target add ${{ matrix.target }}
@@ -149,7 +151,7 @@ jobs:
path: ~/.cargo
key: ${{ runner.os }}-coverage-dotcargo
- name: Run build
- run: cargo hack build --feature-powerset
+ run: cargo hack build --feature-powerset --exclude-no-default-features
test:
name: test
@@ -183,7 +185,7 @@ jobs:
path: ~/.cargo
key: ${{ runner.os }}-coverage-dotcargo
- name: Run test
- run: cargo hack test --feature-powerset
+ run: cargo hack test --tests --feature-powerset --exclude-no-default-features --exclude-all-features
sanitizer:
name: sanitizer
@@ -191,8 +193,6 @@ jobs:
matrix:
os:
- ubuntu-latest
- - macos-latest
- - windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
@@ -210,8 +210,6 @@ jobs:
run: rustup update $nightly && rustup default $nightly
- name: Install rust-src
run: rustup component add rust-src
- - name: Install cargo-hack
- run: cargo install cargo-hack
- name: ASAN / LSAN / TSAN
run: ci/sanitizer.sh
@@ -236,8 +234,6 @@ jobs:
key: ${{ runner.os }}-miri-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-miri-
- - name: Install cargo-hack
- run: cargo install cargo-hack
- name: Miri
run: ci/miri.sh
loom:
@@ -263,71 +259,8 @@ jobs:
${{ runner.os }}-loom-
- name: Install Rust
run: rustup update $nightly && rustup default $nightly
- - name: Install cargo-hack
- run: cargo install cargo-hack
- name: Loom tests
- run: RUSTFLAGS="--cfg loom -Dwarnings" cargo hack test --test loom
-
- # valgrind
- valgrind:
- name: valgrind
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - name: Cache cargo build and registry
- uses: actions/cache@v3
- with:
- path: |
- ~/.cargo/registry
- ~/.cargo/git
- target
- key: ubuntu-latest-valgrind-${{ hashFiles('**/Cargo.lock') }}
- restore-keys: |
- ubuntu-latest-valgrind-
-
- - name: Install Rust ${{ env.stable }}
- uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.stable }}
- override: true
- - name: Install Valgrind
- run: |
- sudo apt-get update -y
- sudo apt-get install -y valgrind
- # Compile tests
- # - name: cargo build foo
- # run: cargo build --bin foo
- # working-directory: integration
-
- # Run with valgrind
- # - name: Run valgrind foo
- # run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/debug/foo
- # working-directory: integration
-
- docs:
- name: docs
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - name: Cache cargo build and registry
- uses: actions/cache@v3
- with:
- path: |
- ~/.cargo/registry
- ~/.cargo/git
- target
- key: ubuntu-latest-docs-${{ hashFiles('**/Cargo.lock') }}
- restore-keys: |
- ubuntu-latest-docs-
- - uses: actions-rs/toolchain@v1
- with:
- toolchain: ${{ env.nightly }}
- override: true
- - name: "doc --lib --all-features"
- run: cargo doc --lib --no-deps --all-features
- env:
- RUSTFLAGS: --cfg docsrs
- RUSTDOCFLAGS: --cfg docsrs -Dwarnings
+ run: cargo test --tests --features loom
coverage:
name: coverage
@@ -339,9 +272,7 @@ jobs:
- cross
- test
- sanitizer
- - miri
- loom
- - docs
steps:
- uses: actions/checkout@v3
- name: Install latest nightly
@@ -367,9 +298,9 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: tarpaulin
- args: --all-features --run-types tests --run-types doctests --workspace --out xml
+ args: --run-types tests --run-types doctests --workspace --out xml
- name: Upload to codecov.io
- uses: codecov/codecov-action@v3.1.1
+ uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
diff --git a/.github/workflows/loc.yml b/.github/workflows/loc.yml
new file mode 100644
index 0000000..941eb62
--- /dev/null
+++ b/.github/workflows/loc.yml
@@ -0,0 +1,58 @@
+name: loc
+
+on:
+ push:
+ branches:
+ - main
+ paths-ignore:
+ - 'README.md'
+ - 'COPYRIGHT'
+ - 'LICENSE*'
+ - '**.md'
+ - '**.txt'
+ - 'art'
+ pull_request:
+ paths-ignore:
+ - 'README.md'
+ - 'COPYRIGHT'
+ - 'LICENSE*'
+ - '**.md'
+ - '**.txt'
+ - 'art'
+ workflow_dispatch:
+
+jobs:
+ loc:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install Rust
+ run: |
+ rustup update stable && rustup default stable
+ rustup component add clippy
+ rustup component add rustfmt
+
+ - name: Install tokeit
+ run: |
+ cargo install tokeit --force
+
+ - name: Count total lines of code
+ run: |
+ tokeit
+ - name: Upload total loc to GitHub Gist
+ uses: actions/github-script@v6
+ with:
+ github-token: ${{ secrets.GIST_PAT }}
+ script: |
+ const fs = require('fs');
+ const output = fs.readFileSync('tokeit.json', 'utf8');
+ const gistId = '327b2a8aef9003246e45c6e47fe63937';
+ await github.rest.gists.update({
+ gist_id: gistId,
+ files: {
+ "objectpool": {
+ content: output
+ }
+ }
+ });
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd7a668..b025a4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,6 @@
-# UNRELEASED
-# 0.1.2 (January 6th, 2022)
-
-FEATURES
+# CHANGELOG
+## 0.1.0
+## UNRELEASED
diff --git a/Cargo.toml b/Cargo.toml
index 583f5a6..701de24 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,37 +1,26 @@
[package]
-name = "template-rs"
-version = "0.1.6"
+name = "objectpool"
+version = "0.1.0"
edition = "2021"
-repository = "https://github.com/al8n/template-rs"
-homepage = "https://github.com/al8n/template-rs"
-documentation = "https://docs.rs/template-rs"
-description = "A template for creating Rust open-source repo on GitHub"
-license = "MIT/Apache-2.0"
-rust-version = "1.73"
-
-[[bench]]
-path = "benches/foo.rs"
-name = "foo"
-harness = false
+repository = "https://github.com/al8n/objectpool"
+homepage = "https://github.com/al8n/objectpool"
+documentation = "https://docs.rs/objectpool"
+description = "Yet another lock-free object pool, support no_std"
+license = "MIT OR Apache-2.0"
+rust-version = "1.56"
+keywords = ["atomic", "object-pool", "lockfree-object-pool", "non-blocking", "lock-free"]
+categories = ["concurrency", "memory-management", "data-structures", "development-tools", "no-std"]
+exclude = ["benchmark"]
[features]
-default = []
+default = ["std"]
+alloc = ["crossbeam-queue/alloc"]
+std = ["crossbeam-queue/default"]
[dependencies]
+crossbeam-queue = { version = "0.3", default-features = false }
-[dev-dependencies]
-criterion = "0.5"
-tempfile = "3"
-
-[profile.bench]
-opt-level = 3
-debug = false
-codegen-units = 1
-lto = 'thin'
-incremental = false
-debug-assertions = false
-overflow-checks = false
-rpath = false
+loom = { version = "0.7", optional = true }
[package.metadata.docs.rs]
all-features = true
diff --git a/README-zh_CN.md b/README-zh_CN.md
index 21d7466..bfdf383 100644
--- a/README-zh_CN.md
+++ b/README-zh_CN.md
@@ -1,50 +1,91 @@
-
template-rs
+Object Pool
-开源Rust代码库GitHub模版
+Yet another lock-free object pool, support `no_std`.
-[
][Github-url]
-[
][CI-url]
-[
][codecov-url]
+[
][Github-url]
+
+[
][CI-url]
+[
][codecov-url]
-[
][doc-url]
-[
][crates-url]
-[
][rustc-url]
+[
][doc-url]
+[
][crates-url]
+[
][crates-url]
+
-[
][license-apache-url]
-[
][license-mit-url]
-
-[English][en-url] | 简体中文
+English | [简体中文][zh-cn-url]
+## Features
+
+- Lock free, backed by concurrent safe queue.
+- Builtin reference counter, no `Arc` wrapper required.
+- Simple APIs and support `no_std` environment.
+- Automatically put object back to pool.
+
## Installation
-```toml
-[dependencies]
-template_rs = "0.1"
+
+- `std`
+
+ ```toml
+ [dependencies]
+ objectpool = "0.1"
+ ```
+
+- `no_std`
+
+ ```toml
+ [dependencies]
+ objectpool = { version = "0.1", default-features = false, features = ["alloc"] }
+ ```
+
+## Tests
+
+- `test`:
+
+ ```sh
+ cargo test
+ ```
+
+- `miri`:
+
+ ```sh
+ cargo miri test
+ ```
+
+- `loom`:
+
+ ```sh
+ cargo test --tests --features loom
+ ```
+
+## Benchmarks
+
+For detailed reports, you can see the latest Benchmark GitHub Action, and download the Artifacts.
+
+```sh
+cargo bench
```
-## Features
-- [x] 更快的创建GitHub开源Rust代码库
+## Why this crate?
+
+I need an object pool that supports the `no_std` environment. Unfortunately, none of the [`object-pool`](https://crates.io/crates/object-pool), [`lockfree-object-pool`](https://crates.io/crates/lockfree-object-pool) and [`sharded-slab`](https://crates.io/crates/sharded-slab) support `no_std`.
#### License
-`Template-rs` is under the terms of both the MIT license and the
+`objectpool` is under the terms of both the MIT license and the
Apache License (Version 2.0).
See [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT) for details.
-Copyright (c) 2021 Al Liu.
-
-[Github-url]: https://github.com/al8n/template-rs/
-[CI-url]: https://github.com/al8n/template/actions/workflows/template.yml
-[doc-url]: https://docs.rs/template-rs
-[crates-url]: https://crates.io/crates/template-rs
-[codecov-url]: https://app.codecov.io/gh/al8n/template-rs/
-[license-url]: https://opensource.org/licenses/Apache-2.0
-[rustc-url]: https://github.com/rust-lang/rust/blob/master/RELEASES.md
-[license-apache-url]: https://opensource.org/licenses/Apache-2.0
-[license-mit-url]: https://opensource.org/licenses/MIT
-[en-url]: https://github.com/al8n/template-rs/tree/main/README.md
+Copyright (c) 2024 Al Liu.
+
+[Github-url]: https://github.com/al8n/objectpool/
+[CI-url]: https://github.com/al8n/objectpool/actions/workflows/ci.yml
+[doc-url]: https://docs.rs/objectpool
+[crates-url]: https://crates.io/crates/objectpool
+[codecov-url]: https://app.codecov.io/gh/al8n/objectpool/
+[zh-cn-url]: https://github.com/al8n/objectpool/tree/main/README-zh_CN.md
diff --git a/README.md b/README.md
index 218fb4f..bfdf383 100644
--- a/README.md
+++ b/README.md
@@ -1,45 +1,91 @@
-
template-rs
+Object Pool
-A template for creating Rust open-source GitHub repo.
+Yet another lock-free object pool, support `no_std`.
-[
][Github-url]
-[
][CI-url]
-[
][codecov-url]
-
-[
][doc-url]
-[
][crates-url]
-[
][crates-url]
+[
][Github-url]
+
+[
][CI-url]
+[
][codecov-url]
+[
][doc-url]
+[
][crates-url]
+[
][crates-url]
English | [简体中文][zh-cn-url]
+## Features
+
+- Lock free, backed by concurrent safe queue.
+- Builtin reference counter, no `Arc` wrapper required.
+- Simple APIs and support `no_std` environment.
+- Automatically put object back to pool.
+
## Installation
-```toml
-[dependencies]
-template_rs = "0.1"
+
+- `std`
+
+ ```toml
+ [dependencies]
+ objectpool = "0.1"
+ ```
+
+- `no_std`
+
+ ```toml
+ [dependencies]
+ objectpool = { version = "0.1", default-features = false, features = ["alloc"] }
+ ```
+
+## Tests
+
+- `test`:
+
+ ```sh
+ cargo test
+ ```
+
+- `miri`:
+
+ ```sh
+ cargo miri test
+ ```
+
+- `loom`:
+
+ ```sh
+ cargo test --tests --features loom
+ ```
+
+## Benchmarks
+
+For detailed reports, you can see the latest Benchmark GitHub Action, and download the Artifacts.
+
+```sh
+cargo bench
```
-## Features
-- [x] Create a Rust open-source repo fast
+## Why this crate?
+
+I need an object pool that supports the `no_std` environment. Unfortunately, none of the [`object-pool`](https://crates.io/crates/object-pool), [`lockfree-object-pool`](https://crates.io/crates/lockfree-object-pool) and [`sharded-slab`](https://crates.io/crates/sharded-slab) support `no_std`.
#### License
-`Template-rs` is under the terms of both the MIT license and the
+`objectpool` is under the terms of both the MIT license and the
Apache License (Version 2.0).
See [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT) for details.
-Copyright (c) 2021 Al Liu.
+Copyright (c) 2024 Al Liu.
-[Github-url]: https://github.com/al8n/template-rs/
-[CI-url]: https://github.com/al8n/template-rs/actions/workflows/ci.yml
-[doc-url]: https://docs.rs/template-rs
-[crates-url]: https://crates.io/crates/template-rs
-[codecov-url]: https://app.codecov.io/gh/al8n/template-rs/
-[zh-cn-url]: https://github.com/al8n/template-rs/tree/main/README-zh_CN.md
+[Github-url]: https://github.com/al8n/objectpool/
+[CI-url]: https://github.com/al8n/objectpool/actions/workflows/ci.yml
+[doc-url]: https://docs.rs/objectpool
+[crates-url]: https://crates.io/crates/objectpool
+[codecov-url]: https://app.codecov.io/gh/al8n/objectpool/
+[zh-cn-url]: https://github.com/al8n/objectpool/tree/main/README-zh_CN.md
diff --git a/benches/foo.rs b/benches/foo.rs
deleted file mode 100644
index f328e4d..0000000
--- a/benches/foo.rs
+++ /dev/null
@@ -1 +0,0 @@
-fn main() {}
diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml
new file mode 100644
index 0000000..1b50b91
--- /dev/null
+++ b/benchmark/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "bench-tools"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[[bench]]
+path = "benches/bench.rs"
+name = "bench"
+harness = false
+
+[dev-dependencies]
+criterion = "0.5.1"
+objectpool = { path = "../" }
+sharded-slab = "0.1"
+object-pool = "0.5"
+criterion-plot = "0.5"
+lockfree-object-pool = "0.1"
\ No newline at end of file
diff --git a/benchmark/README.md b/benchmark/README.md
new file mode 100644
index 0000000..0d16d2b
--- /dev/null
+++ b/benchmark/README.md
@@ -0,0 +1,2 @@
+
+The benchmark code is copied from https://github.com/EVaillant/lockfree-object-pool.
\ No newline at end of file
diff --git a/benchmark/benches/bench.rs b/benchmark/benches/bench.rs
new file mode 100644
index 0000000..e9f1797
--- /dev/null
+++ b/benchmark/benches/bench.rs
@@ -0,0 +1,519 @@
+#[macro_use]
+extern crate criterion;
+
+use criterion::Criterion;
+
+
+
+#[macro_use]
+mod bench_generic;
+
+struct Vec4096 {
+ _data: Vec,
+}
+
+impl Default for Vec4096 {
+ fn default() -> Self {
+ Self {
+ _data: Vec::with_capacity(16 * 1024),
+ }
+ }
+}
+
+impl sharded_slab::Clear for Vec4096 {
+ fn clear(&mut self) {}
+}
+
+fn bench_alloc(c: &mut Criterion) {
+ let mut group = c.benchmark_group("allocation");
+ bench_alloc_impl_!(
+ group,
+ "crate 'objectpool' bounded",
+ objectpool::Pool::>::bounded(32, || Vec::with_capacity(4096), |_| {}),
+ 4
+ );
+
+ bench_alloc_impl_!(
+ group,
+ "crate 'objectpool' unbounded",
+ objectpool::Pool::>::unbounded(|| Vec::with_capacity(4096), |_| {}),
+ 4
+ );
+
+ bench_alloc_impl_!(
+ group,
+ "crate 'lockfree-object-pool' none object poll",
+ lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024)),
+ 1
+ );
+ bench_alloc_impl_!(
+ group,
+ "crate 'lockfree-object-pool' mutex object poll",
+ lockfree_object_pool::MutexObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ ),
+ 1
+ );
+ bench_alloc_impl_!(
+ group,
+ "crate 'lockfree-object-pool' spin_lock object poll",
+ lockfree_object_pool::SpinLockObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ ),
+ 1
+ );
+ bench_alloc_impl_!(
+ group,
+ "crate 'lockfree-object-pool' linear object poll",
+ lockfree_object_pool::LinearObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ ),
+ 1
+ );
+ bench_alloc_impl_!(
+ group,
+ "crate 'object-pool'",
+ object_pool::Pool::>::new(32, || Vec::with_capacity(4096)),
+ 2
+ );
+ bench_alloc_impl_!(
+ group,
+ "crate 'sharded-slab'",
+ sharded_slab::Pool::::new(),
+ 3
+ );
+ group.finish();
+}
+
+fn bench_free(c: &mut Criterion) {
+ let mut group = c.benchmark_group("free");
+ bench_free_impl_!(
+ group,
+ "crate 'objectpool' bounded",
+ objectpool::Pool::bounded(32, || Vec::::with_capacity(16 * 1024), |_| {}),
+ 4
+ );
+
+ bench_free_impl_!(
+ group,
+ "crate 'objectpool' unbounded",
+ objectpool::Pool::unbounded(|| Vec::::with_capacity(16 * 1024), |_| {}),
+ 4
+ );
+
+ bench_free_impl_!(
+ group,
+ "crate 'lockfree-object-pool' none object poll",
+ lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024)),
+ 1
+ );
+ bench_free_impl_!(
+ group,
+ "crate 'lockfree-object-pool' mutex object poll",
+ lockfree_object_pool::MutexObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ ),
+ 1
+ );
+ bench_free_impl_!(
+ group,
+ "crate 'lockfree-object-pool' spin_lock object poll",
+ lockfree_object_pool::SpinLockObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ ),
+ 1
+ );
+ bench_free_impl_!(
+ group,
+ "crate 'lockfree-object-pool' linear object poll",
+ lockfree_object_pool::LinearObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ ),
+ 1
+ );
+ bench_free_impl_!(
+ group,
+ "crate 'object-pool'",
+ object_pool::Pool::>::new(32, || Vec::with_capacity(4096)),
+ 2
+ );
+ bench_free_impl_!(
+ group,
+ "crate 'sharded-slab'",
+ sharded_slab::Pool::::new(),
+ 3
+ );
+ group.finish();
+}
+
+fn bench_reuse(c: &mut Criterion) {
+ const VEC_SIZE: usize = 16384;
+ const BATCH_SIZE: usize = 8192;
+
+ let mut group = c.benchmark_group("reuse");
+
+ group.bench_function("crate 'objectpool' bounded", |b| {
+ b.iter_batched(
+ || {
+ let pool =
+ objectpool::Pool::bounded(VEC_SIZE, || Vec::::with_capacity(16 * 1024), |_| {});
+ let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.get()).collect();
+ drop(v);
+ (pool, Vec::with_capacity(VEC_SIZE))
+ },
+ |(pool, mut vec)| {
+ for index in 0..BATCH_SIZE {
+ vec.insert(index, criterion::black_box(pool.get()));
+ }
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ });
+
+ group.bench_function("crate 'objectpool' unbounded", |b| {
+ b.iter_batched(
+ || {
+ let pool = objectpool::Pool::unbounded(|| Vec::::with_capacity(16 * 1024), |_| {});
+ let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.get()).collect();
+ drop(v);
+ (pool, Vec::with_capacity(VEC_SIZE))
+ },
+ |(pool, mut vec)| {
+ for index in 0..BATCH_SIZE {
+ vec.insert(index, criterion::black_box(pool.get()));
+ }
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ });
+
+ group.bench_function("none object poll", |b| {
+ b.iter_batched(
+ || {
+ (
+ lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024)),
+ Vec::with_capacity(VEC_SIZE),
+ )
+ },
+ |(pool, mut vec)| {
+ for index in 0..BATCH_SIZE {
+ vec.insert(index, criterion::black_box(pool.pull()));
+ }
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ });
+
+ group.bench_function("mutex object poll", |b| {
+ b.iter_batched(
+ || {
+ let pool = lockfree_object_pool::MutexObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {},
+ );
+ let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.pull()).collect();
+ drop(v);
+ (pool, Vec::with_capacity(VEC_SIZE))
+ },
+ |(pool, mut vec)| {
+ for index in 0..BATCH_SIZE {
+ vec.insert(index, criterion::black_box(pool.pull()));
+ }
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ });
+
+ group.bench_function("spin_lock object poll", |b| {
+ b.iter_batched(
+ || {
+ let pool = lockfree_object_pool::SpinLockObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {},
+ );
+ let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.pull()).collect();
+ drop(v);
+ (pool, Vec::with_capacity(VEC_SIZE))
+ },
+ |(pool, mut vec)| {
+ for index in 0..BATCH_SIZE {
+ vec.insert(index, criterion::black_box(pool.pull()));
+ }
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ });
+
+ group.bench_function("linear object poll", |b| {
+ b.iter_batched(
+ || {
+ let pool = lockfree_object_pool::LinearObjectPool::new(
+ || Vec::::with_capacity(16 * 1024),
+ |_v| {},
+ );
+ let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.pull()).collect();
+ drop(v);
+ (pool, Vec::with_capacity(VEC_SIZE))
+ },
+ |(pool, mut vec)| {
+ for index in 0..BATCH_SIZE {
+ vec.insert(index, criterion::black_box(pool.pull()));
+ }
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ });
+
+ group.bench_function("crate 'object-pool'", |b| {
+ b.iter_batched(
+ || {
+ let pool = object_pool::Pool::new(VEC_SIZE, || Vec::::with_capacity(16 * 1024));
+ let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.try_pull().unwrap()).collect();
+ drop(v);
+ (pool, Vec::with_capacity(VEC_SIZE))
+ },
+ |(pool, mut vec)| {
+ for index in 0..BATCH_SIZE {
+ vec.insert(index, criterion::black_box(pool.try_pull().unwrap()));
+ }
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ });
+}
+
+fn bench_alloc_mt(c: &mut Criterion) {
+ let mut group = c.benchmark_group("multi thread allocation");
+ bench_alloc_mt_impl_!(
+ group,
+ "crate 'objectpool' bounded",
+ objectpool::Pool::>::bounded(32, || Vec::with_capacity(4096), |_| {}),
+ 4
+ );
+
+ bench_alloc_mt_impl_!(
+ group,
+ "crate 'objectpool' unbounded",
+ objectpool::Pool::>::unbounded(|| Vec::with_capacity(4096), |_| {}),
+ 4
+ );
+
+ bench_alloc_mt_impl_!(
+ group,
+ "none object poll",
+ Arc::new(lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024))),
+ 1
+ );
+ bench_alloc_mt_impl_!(
+ group,
+ "mutex object poll",
+ Arc::new(lockfree_object_pool::MutexObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ )),
+ 1
+ );
+ bench_alloc_mt_impl_!(
+ group,
+ "spin_lock object poll",
+ Arc::new(lockfree_object_pool::SpinLockObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ )),
+ 1
+ );
+ bench_alloc_mt_impl_!(
+ group,
+ "linear object poll",
+ Arc::new(lockfree_object_pool::LinearObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ )),
+ 1
+ );
+ bench_alloc_mt_impl_!(
+ group,
+ "crate 'object-pool'",
+ Arc::new(object_pool::Pool::>::new(32, || Vec::with_capacity(4096))),
+ 2
+ );
+ bench_alloc_mt_impl_!(
+ group,
+ "crate 'sharded-slab'",
+ Arc::new(sharded_slab::Pool::::new()),
+ 3
+ );
+ group.finish();
+}
+
+fn bench_free_mt(c: &mut Criterion) {
+ let mut group = c.benchmark_group("multi thread free");
+ bench_free_mt_impl_!(
+ group,
+ "crate 'objectpool' bounded",
+ objectpool::Pool::>::bounded(32, || Vec::with_capacity(4096), |_| {}),
+ 4
+ );
+
+ bench_free_mt_impl_!(
+ group,
+ "crate 'objectpool' unbounded",
+ objectpool::Pool::>::unbounded(|| Vec::with_capacity(4096), |_| {}),
+ 4
+ );
+
+ bench_free_mt_impl_!(
+ group,
+ "none object poll",
+ Arc::new(lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024))),
+ 1
+ );
+ bench_free_mt_impl_!(
+ group,
+ "mutex object poll",
+ Arc::new(lockfree_object_pool::MutexObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ )),
+ 1
+ );
+ bench_free_mt_impl_!(
+ group,
+ "spin_lock object poll",
+ Arc::new(lockfree_object_pool::SpinLockObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ )),
+ 1
+ );
+ bench_free_mt_impl_!(
+ group,
+ "linear object poll",
+ Arc::new(lockfree_object_pool::LinearObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ )),
+ 1
+ );
+ bench_free_mt_impl_!(
+ group,
+ "crate 'object-pool'",
+ Arc::new(object_pool::Pool::>::new(32, || Vec::with_capacity(4096))),
+ 2
+ );
+ bench_free_mt_impl_!(
+ group,
+ "crate 'sharded-slab'",
+ Arc::new(sharded_slab::Pool::::new()),
+ 3
+ );
+ group.finish();
+}
+
+fn bench_forward_multi_thread(c: &mut Criterion, nb_writter: usize, nb_readder: usize) {
+ let mut group = c.benchmark_group(format!(
+ "forward msg from pull (nb_writter:{} nb_readder:{})",
+ nb_writter, nb_readder
+ ));
+ bench_forward_impl_!(
+ group,
+ "crate 'objectpool' bounded",
+ objectpool::Pool::>::bounded(32, || Vec::with_capacity(16 * 1024), |_v| {}),
+ nb_readder,
+ nb_writter,
+ 4
+ );
+
+ bench_forward_impl_!(
+ group,
+ "crate 'objectpool' unbounded",
+ objectpool::Pool::>::unbounded(|| Vec::with_capacity(16 * 1024), |_v| {}),
+ nb_readder,
+ nb_writter,
+ 4
+ );
+
+ bench_forward_impl_!(
+ group,
+ "none object poll",
+ Arc::new(lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024))),
+ nb_readder,
+ nb_writter,
+ 1
+ );
+ bench_forward_impl_!(
+ group,
+ "mutex object poll",
+ Arc::new(lockfree_object_pool::MutexObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ )),
+ nb_readder,
+ nb_writter,
+ 1
+ );
+ bench_forward_impl_!(
+ group,
+ "spin_lock object poll",
+ Arc::new(lockfree_object_pool::SpinLockObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ )),
+ nb_readder,
+ nb_writter,
+ 1
+ );
+ bench_forward_impl_!(
+ group,
+ "linear object poll",
+ Arc::new(lockfree_object_pool::LinearObjectPool::>::new(
+ || Vec::with_capacity(16 * 1024),
+ |_v| {}
+ )),
+ nb_readder,
+ nb_writter,
+ 1
+ );
+ bench_forward_impl_!(
+ group,
+ "crate 'sharded-slab'",
+ Arc::new(sharded_slab::Pool::::new()),
+ nb_readder,
+ nb_writter,
+ 3
+ );
+ group.finish();
+}
+
+fn bench_forward_multi_thread55(c: &mut Criterion) {
+ bench_forward_multi_thread(c, 5, 5);
+}
+
+fn bench_forward_multi_thread15(c: &mut Criterion) {
+ bench_forward_multi_thread(c, 1, 5);
+}
+
+fn bench_forward_multi_thread51(c: &mut Criterion) {
+ bench_forward_multi_thread(c, 5, 1);
+}
+
+fn bench_forward_multi_thread11(c: &mut Criterion) {
+ bench_forward_multi_thread(c, 1, 1);
+}
+
+criterion_group!(
+ forward_multi_thread,
+ bench_forward_multi_thread55,
+ bench_forward_multi_thread15,
+ bench_forward_multi_thread51,
+ bench_forward_multi_thread11
+);
+criterion_group!(multi_thread, bench_alloc_mt, bench_free_mt);
+criterion_group!(mono_thread, bench_alloc, bench_free, bench_reuse);
+criterion_main!(mono_thread, multi_thread, forward_multi_thread);
diff --git a/benchmark/benches/bench_generic.rs b/benchmark/benches/bench_generic.rs
new file mode 100644
index 0000000..2fa46a3
--- /dev/null
+++ b/benchmark/benches/bench_generic.rs
@@ -0,0 +1,214 @@
+#[macro_export]
+macro_rules! pull_ {
+ ($pool:ident, 1) => {
+ $pool.pull()
+ };
+ ($pool:ident, 2) => {
+ $pool.pull(|| Vec::with_capacity(4096))
+ };
+ ($pool:ident, 3) => {
+ $pool.create().unwrap()
+ };
+ ($pool:ident, 4) => {
+ $pool.get()
+ };
+}
+
+#[macro_export]
+macro_rules! pull_forward_ {
+ ($pool:ident, 1) => {
+ $pool.pull_owned()
+ };
+ ($pool:ident, 3) => {
+ $pool.clone().create_owned().unwrap()
+ };
+ ($pool:ident, 4) => {
+ $pool.get_owned()
+ };
+}
+
+#[macro_export]
+macro_rules! bench_alloc_impl_ {
+ ($group:expr, $name:literal, $expression:expr, $pull_impl:tt) => {
+ $group.bench_function($name, |b| {
+ let pool = $expression;
+ let mut items = Vec::new();
+ b.iter(|| {
+ items.push(pull_!(pool, $pull_impl));
+ });
+ });
+ };
+}
+
+#[macro_export]
+macro_rules! bench_free_impl_ {
+ ($group:expr, $name:literal, $expression:expr, $pull_impl:tt) => {
+ $group.bench_function($name, |b| {
+ b.iter_custom(|iter| {
+ use std::time::Instant;
+ let pool = $expression;
+ let mut items = Vec::new();
+ for _ in 0..iter {
+ items.push(pull_!(pool, $pull_impl));
+ }
+ let start = Instant::now();
+ items.clear();
+ start.elapsed()
+ });
+ });
+ };
+}
+
+#[macro_export]
+macro_rules! bench_alloc_mt_impl_ {
+ ($group:expr, $name:literal, $expression:expr, $pull_impl:tt) => {
+ $group.bench_function($name, |b| {
+ b.iter_custom(|iter| {
+ use std::sync::Arc;
+ use std::sync::Barrier;
+ use std::thread;
+ use std::time::Instant;
+
+ let pool = $expression;
+ let start_barrier = Arc::new(Barrier::new(6));
+ let stop_barrier = Arc::new(Barrier::new(6));
+ let mut children = Vec::new();
+ for _ in 0..5 {
+ let pool = pool.clone();
+ let start_barrier = Arc::clone(&start_barrier);
+ let stop_barrier = Arc::clone(&stop_barrier);
+ let child = thread::spawn(move || {
+ let mut items = Vec::with_capacity(iter as usize);
+ start_barrier.wait();
+ for _ in 0..iter {
+ items.push(pull_!(pool, $pull_impl));
+ }
+ stop_barrier.wait();
+ });
+ children.push(child);
+ }
+
+ start_barrier.wait();
+ let start = Instant::now();
+ stop_barrier.wait();
+ let duration = start.elapsed() / 5;
+
+ for child in children {
+ child.join().unwrap();
+ }
+
+ duration
+ });
+ });
+ };
+}
+
+#[macro_export]
+macro_rules! bench_free_mt_impl_ {
+ ($group:expr, $name:literal, $expression:expr, $pull_impl:tt) => {
+ $group.bench_function($name, |b| {
+ b.iter_custom(|iter| {
+ use std::sync::Arc;
+ use std::sync::Barrier;
+ use std::thread;
+ use std::time::Instant;
+ let pool = $expression;
+ let start_barrier = Arc::new(Barrier::new(6));
+ let stop_barrier = Arc::new(Barrier::new(6));
+ let mut children = Vec::new();
+ for _ in 0..5 {
+ let pool = pool.clone();
+ let start_barrier = Arc::clone(&start_barrier);
+ let stop_barrier = Arc::clone(&stop_barrier);
+ let child = thread::spawn(move || {
+ let mut items = Vec::with_capacity(iter as usize);
+ for _ in 0..iter {
+ items.push(pull_!(pool, $pull_impl));
+ }
+ start_barrier.wait();
+ items.clear();
+ stop_barrier.wait();
+ });
+ children.push(child);
+ }
+
+ start_barrier.wait();
+ let start = Instant::now();
+ stop_barrier.wait();
+ let duration = start.elapsed() / 5;
+
+ for child in children {
+ child.join().unwrap();
+ }
+
+ duration
+ });
+ });
+ };
+}
+
+#[macro_export]
+macro_rules! bench_forward_impl_ {
+ ($group:expr, $name:literal, $expression:expr, $nb_readder:ident, $nb_writter:ident, $pull_impl:tt) => {
+ $group.bench_function($name, |b| {
+ b.iter_custom(|iter| {
+ use bench_tools::Queue;
+ use std::sync::Arc;
+ use std::sync::Barrier;
+ use std::thread;
+ use std::time::Instant;
+
+ let pool = $expression;
+
+ let queue = Arc::new(Queue::new());
+ let start_barrier = Arc::new(Barrier::new($nb_readder + $nb_writter + 1));
+ let stop_reader_barrier = Arc::new(Barrier::new($nb_readder + 1));
+ let stop_writer_barrier = Arc::new(Barrier::new($nb_writter + 1));
+ let mut children = Vec::new();
+ for _ in 0..$nb_readder {
+ let queue = Arc::clone(&queue);
+ let start_barrier = Arc::clone(&start_barrier);
+ let stop_reader_barrier = Arc::clone(&stop_reader_barrier);
+ let child = thread::spawn(move || {
+ start_barrier.wait();
+ loop {
+ let elt = queue.pop();
+ if elt.is_none() {
+ break;
+ }
+ }
+ stop_reader_barrier.wait();
+ });
+ children.push(child);
+ }
+ for _ in 0..$nb_writter {
+ let pool = pool.clone();
+ let queue = Arc::clone(&queue);
+ let start_barrier = Arc::clone(&start_barrier);
+ let stop_writer_barrier = Arc::clone(&stop_writer_barrier);
+ let child = thread::spawn(move || {
+ start_barrier.wait();
+ for _ in 0..iter {
+ queue.push(pull_forward_!(pool, $pull_impl))
+ }
+ stop_writer_barrier.wait();
+ });
+ children.push(child);
+ }
+
+ start_barrier.wait();
+ let start = Instant::now();
+ stop_writer_barrier.wait();
+ queue.stop();
+ stop_reader_barrier.wait();
+ let duration = start.elapsed() / 5;
+
+ for child in children {
+ child.join().unwrap();
+ }
+
+ duration
+ });
+ });
+ };
+}
diff --git a/benchmark/src/lib.rs b/benchmark/src/lib.rs
new file mode 100644
index 0000000..670efa3
--- /dev/null
+++ b/benchmark/src/lib.rs
@@ -0,0 +1,57 @@
+use std::collections::VecDeque;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Condvar, Mutex};
+
+pub struct Queue {
+ data: Mutex>,
+ condvar: Condvar,
+ stop: AtomicBool,
+}
+
+impl Queue {
+ pub fn new() -> Self {
+ Self {
+ data: Mutex::new(VecDeque::new()),
+ condvar: Condvar::new(),
+ stop: AtomicBool::new(false),
+ }
+ }
+
+ pub fn push(&self, value: T) {
+ let mut datas = self.data.lock().unwrap();
+ let empty = datas.is_empty();
+ datas.push_back(value);
+ if empty {
+ self.condvar.notify_all();
+ }
+ }
+
+ pub fn stop(&self) {
+ let _datas = self.data.lock().unwrap();
+ self.stop.store(true, Ordering::Relaxed);
+ self.condvar.notify_all();
+ }
+
+ pub fn pop(&self) -> Option {
+ let mut datas = self.data.lock().unwrap();
+ if !datas.is_empty() {
+ datas.pop_front()
+ } else if self.stop.load(Ordering::Relaxed) {
+ None
+ } else {
+ self
+ .condvar
+ .wait_while(datas, |datas| {
+ datas.is_empty() && !self.stop.load(Ordering::Relaxed)
+ })
+ .unwrap()
+ .pop_front()
+ }
+ }
+}
+
+impl Default for Queue {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/ci/miri.sh b/ci/miri.sh
index 7ea1a2c..35ec5f4 100755
--- a/ci/miri.sh
+++ b/ci/miri.sh
@@ -7,5 +7,7 @@ cargo miri setup
export MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-symbolic-alignment-check"
-cargo hack miri test --each-feature
-
+cargo miri test --tests --target x86_64-unknown-linux-gnu
+cargo miri test --tests --target aarch64-unknown-linux-gnu
+cargo miri test --tests --target i686-unknown-linux-gnu
+cargo miri test --tests --target powerpc64-unknown-linux-gnu
diff --git a/ci/sanitizer.sh b/ci/sanitizer.sh
index a21beb3..c8b9eee 100755
--- a/ci/sanitizer.sh
+++ b/ci/sanitizer.sh
@@ -4,14 +4,14 @@ set -ex
export ASAN_OPTIONS="detect_odr_violation=0 detect_leaks=0"
-# Run address sanitizer with cargo-hack
+# Run address sanitizer
RUSTFLAGS="-Z sanitizer=address" \
-cargo hack test --lib --each-feature
+cargo test --tests --target x86_64-unknown-linux-gnu
-# Run leak sanitizer with cargo-hack
+# Run leak sanitizer
RUSTFLAGS="-Z sanitizer=leak" \
-cargo hack test --lib --each-feature
+cargo test --tests --target x86_64-unknown-linux-gnu
-# Run thread sanitizer with cargo-hack
+# Run thread sanitizer
RUSTFLAGS="-Z sanitizer=thread" \
-cargo hack -Zbuild-std test --lib --each-feature
+cargo -Zbuild-std test --tests --target x86_64-unknown-linux-gnu
diff --git a/examples/foo.rs b/examples/foo.rs
deleted file mode 100644
index f328e4d..0000000
--- a/examples/foo.rs
+++ /dev/null
@@ -1 +0,0 @@
-fn main() {}
diff --git a/src/abort.rs b/src/abort.rs
new file mode 100644
index 0000000..14a6d64
--- /dev/null
+++ b/src/abort.rs
@@ -0,0 +1,20 @@
+#[inline(never)]
+#[cold]
+pub(super) fn abort() -> ! {
+ #[cfg(feature = "std")]
+ {
+ std::process::abort()
+ }
+
+ #[cfg(not(feature = "std"))]
+ {
+ struct Abort;
+ impl Drop for Abort {
+ fn drop(&mut self) {
+ panic!();
+ }
+ }
+ let _a = Abort;
+ panic!("abort");
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index dbdf2a0..515d93b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,19 +1,730 @@
-//! A template for creating Rust open-source repo on GitHub
+#![doc = include_str!("../README.md")]
+#![cfg_attr(not(any(feature = "std", test)), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, allow(unused_attributes))]
#![deny(missing_docs)]
-/// template
-pub fn it_works() -> usize {
- 4
+#[cfg(not(feature = "std"))]
+extern crate alloc as std;
+
+#[cfg(feature = "std")]
+extern crate std;
+
+#[cfg(not(any(feature = "std", feature = "alloc")))]
+compile_error!("`objectpool` requires either the 'std' or 'alloc' feature to be enabled.");
+
+use core::{mem::ManuallyDrop, ptr::NonNull};
+
+use crossbeam_queue::{ArrayQueue, SegQueue};
+
+#[cfg(not(feature = "loom"))]
+use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
+
+#[cfg(feature = "loom")]
+use loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
+
+#[cfg(not(feature = "std"))]
+use std::boxed::Box;
+
+mod abort;
+
+/// A reusable `T`.
+pub struct ReusableObject {
+ pool: Pool,
+ obj: ManuallyDrop,
+}
+
+impl AsRef for ReusableObject {
+ fn as_ref(&self) -> &T {
+ &self.obj
+ }
+}
+
+impl AsMut for ReusableObject {
+ fn as_mut(&mut self) -> &mut T {
+ &mut self.obj
+ }
+}
+
+impl core::ops::Deref for ReusableObject {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.obj
+ }
+}
+
+impl core::ops::DerefMut for ReusableObject {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.obj
+ }
+}
+
+impl Drop for ReusableObject {
+ fn drop(&mut self) {
+ // SAFETY: The object is dropped, we never reuse the ManuallyDrop again.
+ unsafe {
+ self.pool.attach(ManuallyDrop::take(&mut self.obj));
+ }
+ }
+}
+
+/// A reusable `T`.
+pub struct ReusableObjectRef<'a, T> {
+ pool: &'a Pool,
+ obj: ManuallyDrop,
+}
+
+impl<'a, T> AsRef for ReusableObjectRef<'a, T> {
+ fn as_ref(&self) -> &T {
+ &self.obj
+ }
+}
+
+impl<'a, T> AsMut for ReusableObjectRef<'a, T> {
+ fn as_mut(&mut self) -> &mut T {
+ &mut self.obj
+ }
+}
+
+impl<'a, T> core::ops::Deref for ReusableObjectRef<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.obj
+ }
+}
+
+impl<'a, T> core::ops::DerefMut for ReusableObjectRef<'a, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.obj
+ }
+}
+
+impl<'a, T> Drop for ReusableObjectRef<'a, T> {
+ fn drop(&mut self) {
+ // SAFETY: The object is dropped, we never reuse the ManuallyDrop again.
+ unsafe {
+ self.pool.attach(ManuallyDrop::take(&mut self.obj));
+ }
+ }
+}
+
+// It is ok to have a large enum variant here because the enum will always be `Box::into_raw(Box::new(_))`
+#[allow(clippy::large_enum_variant)]
+enum Backed {
+ Bounded(ArrayQueue),
+ Unbounded(SegQueue),
+}
+
+struct Queue {
+ refs: AtomicUsize,
+ queue: Backed,
+}
+
+impl Queue {
+ #[inline]
+ fn bounded(queue: ArrayQueue) -> Self {
+ Self {
+ refs: AtomicUsize::new(1),
+ queue: Backed::Bounded(queue),
+ }
+ }
+
+ #[inline]
+ fn unbounded(queue: SegQueue) -> Self {
+ Self {
+ refs: AtomicUsize::new(1),
+ queue: Backed::Unbounded(queue),
+ }
+ }
+
+ #[inline]
+ fn push(&self, obj: T) {
+ match &self.queue {
+ Backed::Bounded(queue) => {
+ let _ = queue.push(obj);
+ }
+ Backed::Unbounded(queue) => queue.push(obj),
+ }
+ }
+
+ #[inline]
+ fn pop(&self) -> Option {
+ match &self.queue {
+ Backed::Bounded(queue) => queue.pop(),
+ Backed::Unbounded(queue) => queue.pop(),
+ }
+ }
+}
+
+/// Lock-free object pool.
+pub struct Pool {
+ refs: AtomicPtr<()>,
+ queue: *mut Queue,
+ new: NonNull T + Send + Sync + 'static>,
+ reset: NonNull,
+}
+
+unsafe impl Send for Pool {}
+unsafe impl Sync for Pool {}
+
+impl Pool {
+ /// Create a new pool with the given capacity.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use objectpool::Pool;
+ ///
+ /// let pool = Pool::::bounded(10, Default::default, |_v| {});
+ /// ```
+ #[inline]
+ pub fn bounded(
+ capacity: usize,
+ new: impl Fn() -> T + Send + Sync + 'static,
+ reset: impl Fn(&mut T) + Send + Sync + 'static,
+ ) -> Self {
+ let queue = Queue::bounded(ArrayQueue::::new(capacity));
+ Self::new(queue, new, reset)
+ }
+
+ /// Create a new pool with the unbounded capacity.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use objectpool::Pool;
+ ///
+ /// let pool = Pool::::unbounded(Default::default, |_v| {});
+ /// ```
+ #[inline]
+ pub fn unbounded(
+ new: impl Fn() -> T + Send + Sync + 'static,
+ reset: impl Fn(&mut T) + Send + Sync + 'static,
+ ) -> Self {
+ let queue = Queue::unbounded(SegQueue::::new());
+ Self::new(queue, new, reset)
+ }
+
+ /// Get an object from the pool.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use objectpool::Pool;
+ ///
+ /// let pool = Pool::::bounded(10, Default::default, |_v| {});
+ ///
+ /// let mut obj = pool.get();
+ ///
+ /// assert_eq!(*obj, 0);
+ ///
+ /// *obj = 42;
+ /// drop(obj);
+ /// ```
+ #[inline]
+ pub fn get(&self) -> ReusableObjectRef {
+ ReusableObjectRef {
+ pool: self,
+ obj: ManuallyDrop::new(self.queue().pop().unwrap_or_else(|| self.new_object())),
+ }
+ }
+
+ /// Get an object from the pool.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use objectpool::Pool;
+ ///
+ /// let pool = Pool::::bounded(10, Default::default, |_v| {});
+ ///
+ /// let mut obj = pool.get_owned();
+ ///
+ /// assert_eq!(*obj, 0);
+ ///
+ /// *obj = 42;
+ /// drop(obj);
+ /// ```
+ #[inline]
+ pub fn get_owned(&self) -> ReusableObject {
+ ReusableObject {
+ pool: self.clone(),
+ obj: ManuallyDrop::new(self.queue().pop().unwrap_or_else(|| self.new_object())),
+ }
+ }
+
+ /// Get an object from the pool with a fallback.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use objectpool::Pool;
+ ///
+ /// let pool = Pool::::bounded(10, Default::default, |_| {});
+ ///
+ /// let mut obj = pool.get_or_else(|| 42);
+ ///
+ /// assert_eq!(*obj, 42);
+ /// ```
+ #[inline]
+ pub fn get_or_else(&self, fallback: impl Fn() -> T) -> ReusableObjectRef {
+ ReusableObjectRef {
+ pool: self,
+ obj: ManuallyDrop::new(self.queue().pop().unwrap_or_else(fallback)),
+ }
+ }
+
+ /// Get an object from the pool with a fallback.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use objectpool::Pool;
+ ///
+ /// let pool = Pool::::bounded(10, Default::default, |_| {});
+ ///
+ /// let mut obj = pool.get_owned_or_else(|| 42);
+ ///
+ /// assert_eq!(*obj, 42);
+ /// ```
+ #[inline]
+ pub fn get_owned_or_else(&self, fallback: impl Fn() -> T) -> ReusableObject {
+ ReusableObject {
+ pool: self.clone(),
+ obj: ManuallyDrop::new(self.queue().pop().unwrap_or_else(fallback)),
+ }
+ }
+
+ /// Clear the pool.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use objectpool::Pool;
+ ///
+ /// let pool = Pool::::bounded(10, Default::default, |v| {});
+ ///
+ /// let mut obj = pool.get();
+ /// *obj = 42;
+ /// drop(obj);
+ ///
+ /// pool.clear();
+ /// ```
+ #[inline]
+ pub fn clear(&self) {
+ while self.queue().pop().is_some() {}
+ }
+
+ #[inline]
+ fn new(
+ queue: Queue,
+ new: impl Fn() -> T + Send + Sync + 'static,
+ reset: impl Fn(&mut T) + Send + Sync + 'static,
+ ) -> Self {
+ let ptr = Box::into_raw(Box::new(queue));
+
+ unsafe {
+ Self {
+ queue: ptr,
+ refs: AtomicPtr::new(ptr as *mut ()),
+ // SAFETY: Box::new is safe because the closure is 'static.
+ new: NonNull::new_unchecked(Box::into_raw(Box::new(new))),
+ // SAFETY: Box::new is safe because the closure is 'static.
+ reset: NonNull::new_unchecked(Box::into_raw(Box::new(reset))),
+ }
+ }
+ }
+
+ /// Return an object to the pool.
+ #[inline]
+ fn attach(&self, mut obj: T) {
+ self.reset_object(&mut obj);
+ self.queue().push(obj);
+ }
+
+ #[inline]
+ fn new_object(&self) -> T {
+ // SAFETY: The new closure is 'static and the pointer is valid until the last Pool instance is droped.
+ let constructor = unsafe { &*(self.new.as_ptr()) };
+ constructor()
+ }
+
+ #[inline]
+ fn reset_object(&self, obj: &mut T) {
+ // SAFETY: The reset closure is 'static and the pointer is valid until the last Pool instance is droped.
+ let resetter = unsafe { &*(self.reset.as_ptr()) };
+ resetter(obj);
+ }
+
+ #[inline]
+ fn queue(&self) -> &Queue {
+ // SAFETY: The pointer is valid until the last Pool instance is droped.
+ unsafe { &*self.queue }
+ }
+}
+
+impl Clone for Pool {
+ fn clone(&self) -> Self {
+ unsafe {
+ let shared: *mut Queue = self.refs.load(Ordering::Relaxed).cast();
+
+ let old_size = (*shared).refs.fetch_add(1, Ordering::Release);
+ if old_size > usize::MAX >> 1 {
+ abort::abort();
+ }
+
+ // SAFETY: The ptr is always non-null, and the data is only deallocated when the
+ // last Pool is dropped.
+ Self {
+ refs: AtomicPtr::new(shared as *mut ()),
+ queue: self.queue,
+ new: self.new,
+ reset: self.reset,
+ }
+ }
+ }
+}
+
+impl Drop for Pool {
+ fn drop(&mut self) {
+ unsafe {
+ self.refs.with_mut(|shared| {
+ let shared: *mut Queue = shared.cast();
+ // `Shared` storage... follow the drop steps from Arc.
+ if (*shared).refs.fetch_sub(1, Ordering::Release) != 1 {
+ return;
+ }
+
+ // This fence is needed to prevent reordering of use of the data and
+ // deletion of the data. Because it is marked `Release`, the decreasing
+ // of the reference count synchronizes with this `Acquire` fence. This
+ // means that use of the data happens before decreasing the reference
+ // count, which happens before this fence, which happens before the
+ // deletion of the data.
+ //
+ // As explained in the [Boost documentation][1],
+ //
+ // > It is important to enforce any possible access to the object in one
+ // > thread (through an existing reference) to *happen before* deleting
+ // > the object in a different thread. This is achieved by a "release"
+ // > operation after dropping a reference (any access to the object
+ // > through this reference must obviously happened before), and an
+ // > "acquire" operation before deleting the object.
+ //
+ // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html)
+ //
+ // Thread sanitizer does not support atomic fences. Use an atomic load
+ // instead.
+ (*shared).refs.load(Ordering::Acquire);
+
+ // Drop the data
+ let _ = Box::from_raw(shared);
+ let _ = Box::from_raw(self.new.as_ptr());
+ let _ = Box::from_raw(self.reset.as_ptr());
+ });
+ }
+ }
+}
+
+#[cfg(not(feature = "loom"))]
+trait AtomicMut {
+ fn with_mut(&mut self, f: F) -> R
+ where
+ F: FnOnce(&mut *mut T) -> R;
+}
+
+#[cfg(not(feature = "loom"))]
+impl AtomicMut for AtomicPtr {
+ fn with_mut(&mut self, f: F) -> R
+ where
+ F: FnOnce(&mut *mut T) -> R,
+ {
+ f(self.get_mut())
+ }
}
#[cfg(test)]
mod tests {
use super::*;
+ #[cfg(not(feature = "std"))]
+ use std::{vec, vec::Vec};
+
+ #[cfg(all(feature = "std", not(feature = "loom")))]
+ use std::thread;
+
+ #[cfg(all(feature = "std", feature = "loom", not(miri)))]
+ use loom::thread;
+
+ fn create_pool(cap: usize) -> Pool> {
+ Pool::bounded(cap, Vec::new, |val| {
+ val.clear();
+ })
+ }
+
+ fn basic_get_and_put_in() {
+ let pool = create_pool(10);
+
+ // Get a new object from the pool
+ let mut obj = pool.get();
+ assert_eq!(*obj, Vec::new());
+
+ // Modify and return the object
+ obj.push(42);
+ drop(obj);
+
+ // Get the object back from the pool
+ let obj = pool.get();
+ assert_eq!(*obj, Vec::new());
+ }
+
#[test]
- fn test_works() {
- assert_eq!(it_works(), 4);
+ fn basic_get_and_put() {
+ #[cfg(feature = "loom")]
+ loom::model(basic_get_and_put_in);
+
+ #[cfg(not(feature = "loom"))]
+ basic_get_and_put_in();
+ }
+
+ fn get_or_else_in() {
+ let pool = create_pool(10);
+
+ // Get an object from the pool with a fallback
+ let mut obj = pool.get_or_else(|| vec![42]);
+ assert_eq!(*obj, [42]);
+
+ // Modify and return the object
+ obj.push(43);
+ drop(obj);
+
+ // Get the object back from the pool with a fallback
+ let obj = pool.get_or_else(|| vec![42]);
+ assert_eq!(*obj, []);
+
+ let _objs = (0..10)
+ .map(|_| pool.get_or_else(|| vec![42]))
+ .collect::>();
+
+ let obj = pool.get_or_else(|| vec![42]);
+ assert_eq!(*obj, [42]);
+ }
+
+ #[test]
+ fn get_or_else() {
+ #[cfg(feature = "loom")]
+ loom::model(get_or_else_in);
+
+ #[cfg(not(feature = "loom"))]
+ get_or_else_in();
+ }
+
+ fn pool_clone_in() {
+ let pool = create_pool(10);
+ let pool_clone = pool.clone();
+
+ // Get an object from the cloned pool
+ let mut obj = pool_clone.get();
+ assert_eq!(*obj, []);
+
+ // Modify and return the object
+ obj.push(42);
+ drop(obj);
+
+ // Get the object back from the original pool
+ let obj = pool.get();
+ assert_eq!(*obj, []);
+ }
+
+ #[test]
+ fn pool_clone() {
+ #[cfg(feature = "loom")]
+ loom::model(pool_clone_in);
+
+ #[cfg(not(feature = "loom"))]
+ pool_clone_in();
+ }
+
+ #[cfg(feature = "std")]
+ fn multi_threaded_access_in() {
+ #[cfg(not(any(feature = "loom", miri)))]
+ const OUTER: usize = 10;
+ #[cfg(any(feature = "loom", miri))]
+ const OUTER: usize = 2;
+
+ #[cfg(not(any(feature = "loom", miri)))]
+ const INNER: usize = 100;
+ #[cfg(any(feature = "loom", miri))]
+ const INNER: usize = 10;
+
+ let pool = create_pool(10);
+
+ let mut handles = vec![];
+
+ for _ in 0..OUTER {
+ let pool = pool.clone();
+ let handle = thread::spawn(move || {
+ for i in 0..INNER {
+ let mut obj = pool.get();
+ obj.push(i as u8);
+ drop(obj);
+ }
+ });
+ handles.push(handle);
+ }
+
+ for handle in handles {
+ handle.join().expect("Thread panicked");
+ }
+
+ // Check that the pool is still functional after multi-threaded access
+ let obj = pool.get();
+ assert_eq!(*obj, []);
+ }
+
+ #[test]
+ #[cfg(feature = "std")]
+ fn multi_threaded_access() {
+ #[cfg(all(feature = "std", not(feature = "loom")))]
+ multi_threaded_access_in();
+
+ #[cfg(all(feature = "std", feature = "loom"))]
+ loom::model(multi_threaded_access_in);
+ }
+
+ fn custom_new_and_reset_in() {
+ let pool = Pool::bounded(
+ 10,
+ || 100, // new closure that creates an i32 with value 100
+ |val: &mut i32| {
+ *val = 200;
+ }, // reset closure that resets the value to 200
+ );
+
+ // Get a new object from the pool
+ let mut obj = pool.get();
+ assert_eq!(*obj, 100);
+
+ // Modify and return the object
+ *obj = 42;
+ drop(obj);
+
+ // Get the object back from the pool
+ let obj = pool.get();
+ assert_eq!(*obj, 200);
+ }
+
+ #[test]
+ fn custom_new_and_reset() {
+ #[cfg(feature = "loom")]
+ loom::model(custom_new_and_reset_in);
+
+ #[cfg(not(feature = "loom"))]
+ custom_new_and_reset_in();
+ }
+
+ #[cfg(not(feature = "loom"))]
+ fn stress_test_in() {
+ let pool = create_pool(10);
+
+ for _ in 0..1_000_000 {
+ let mut obj = pool.get();
+ obj.push(42);
+ }
+
+ // Check that the pool is still functional after stress test
+ let obj = pool.get();
+ assert_eq!(*obj, []);
+ }
+
+ #[test]
+ #[cfg(not(feature = "loom"))]
+ fn stress_test() {
+ stress_test_in();
+ }
+
+ fn test_reusable_object_in() {
+ let pool = create_pool(10);
+
+ {
+ let mut obj = pool.get();
+ obj.push(42);
+ assert_eq!(*obj, [42]);
+ // obj goes out of scope and is returned to the pool
+ }
+
+ // Get the object back from the pool
+ let obj = pool.get();
+ assert_eq!(*obj, []);
+ }
+
+ #[test]
+ fn test_reusable_object() {
+ #[cfg(feature = "loom")]
+ loom::model(test_reusable_object_in);
+
+ #[cfg(not(feature = "loom"))]
+ test_reusable_object_in();
+ }
+
+ fn test_reset_on_put_in() {
+ let pool = create_pool(10);
+
+ let mut obj = pool.get();
+ obj.push(123);
+ drop(obj); // Object is returned to the pool and reset
+
+ // Get the object back from the pool
+ let obj = pool.get();
+ assert_eq!(*obj, []); // Ensure that the object was reset
+ }
+
+ #[test]
+ fn test_reset_on_put() {
+ #[cfg(feature = "loom")]
+ loom::model(test_reset_on_put_in);
+
+ #[cfg(not(feature = "loom"))]
+ test_reset_on_put_in();
+ }
+
+ fn test_as_ref_in() {
+ let pool = create_pool(10);
+
+ let mut obj = pool.get();
+ obj.push(42);
+
+ {
+ let obj_ref = obj.as_ref();
+ assert_eq!(*obj_ref, [42]);
+ }
+
+ {
+ let obj_mut = obj.as_mut();
+ obj_mut.push(43);
+ }
+
+ let mut obj = pool.get_owned();
+ obj.push(42);
+ {
+ let obj_ref = obj.as_ref();
+ assert_eq!(*obj_ref, [42]);
+ }
+
+ {
+ let obj_mut = obj.as_mut();
+ obj_mut.push(43);
+ }
+ }
+
+ #[test]
+ fn test_as_ref() {
+ #[cfg(feature = "loom")]
+ loom::model(test_as_ref_in);
+
+ #[cfg(not(feature = "loom"))]
+ test_as_ref_in();
}
}
diff --git a/tests/foo.rs b/tests/foo.rs
deleted file mode 100644
index 8b13789..0000000
--- a/tests/foo.rs
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/tests/test_bounded.rs b/tests/test_bounded.rs
new file mode 100644
index 0000000..b40b2ee
--- /dev/null
+++ b/tests/test_bounded.rs
@@ -0,0 +1,20 @@
+use objectpool::Pool;
+
+#[macro_use]
+mod test_generic;
+
+fn make_pool() -> Pool {
+ Pool::::bounded(10, Default::default, |v| {
+ *v = 0;
+ })
+}
+
+#[cfg(not(feature = "loom"))]
+fn make_recycle_pool() -> Pool {
+ Pool::::bounded(10, Default::default, |_v| {})
+}
+
+test_generic_01!(test_bounded_01, make_pool());
+test_generic_02!(test_bounded_02, make_pool());
+#[cfg(not(feature = "loom"))]
+test_recycle_generic_01!(test_bounded_recycle_01, make_recycle_pool());
diff --git a/tests/test_generic.rs b/tests/test_generic.rs
new file mode 100644
index 0000000..52e6338
--- /dev/null
+++ b/tests/test_generic.rs
@@ -0,0 +1,91 @@
+#[macro_export]
+macro_rules! test_generic_01 {
+ ($name:ident, $expression:expr) => {
+ #[test]
+ fn $name() {
+ fn run() {
+ let pool = $expression;
+ for _ in 0..2 {
+ let mut v = pool.get();
+ assert_eq!(*v, 0);
+ *v += 1;
+ }
+ }
+
+ #[cfg(not(feature = "loom"))]
+ run();
+
+ #[cfg(feature = "loom")]
+ loom::model(run);
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! test_generic_02 {
+ ($name:ident, $expression:expr) => {
+ #[test]
+ #[cfg(not(feature = "loom"))]
+ fn $name() {
+ use std::sync::mpsc;
+ use std::sync::Arc;
+ use std::thread;
+
+ let pool = Arc::new($expression);
+
+ let (tx, rx) = mpsc::channel();
+ let mut children = Vec::new();
+
+ for id in 0..5 {
+ let thread_tx = tx.clone();
+ let thread_pool = Arc::clone(&pool);
+
+ let child = thread::spawn(move || {
+ let mut msg = thread_pool.get();
+ *msg = id;
+ thread_tx.send(*msg).unwrap();
+ });
+ children.push(child);
+ }
+
+ let mut msgs = Vec::new();
+ for _ in 0..5 {
+ let msg = rx.recv().unwrap();
+ if !msgs.contains(&msg) && msg < 5 {
+ msgs.push(msg);
+ }
+ }
+ assert_eq!(msgs.len(), 5);
+
+ for child in children {
+ child.join().unwrap();
+ }
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! test_recycle_generic_01 {
+ ($name:ident, $expression:expr) => {
+ #[test]
+ #[cfg(not(feature = "loom"))]
+ fn $name() {
+ fn run() {
+ let pool = $expression;
+
+ let mut item1 = pool.get();
+ *item1 = 5;
+ drop(item1);
+
+ let item2 = pool.get();
+ assert_eq!(*item2, 5);
+ }
+
+ #[cfg(not(feature = "loom"))]
+ run();
+
+ #[cfg(feature = "loom")]
+ loom::model(run);
+ }
+ };
+}
diff --git a/tests/test_unbounded.rs b/tests/test_unbounded.rs
new file mode 100644
index 0000000..7790365
--- /dev/null
+++ b/tests/test_unbounded.rs
@@ -0,0 +1,20 @@
+use objectpool::Pool;
+
+#[macro_use]
+mod test_generic;
+
+fn make_pool() -> Pool {
+ Pool::::unbounded(Default::default, |v| {
+ *v = 0;
+ })
+}
+
+#[cfg(not(feature = "loom"))]
+fn make_recycle_pool() -> Pool {
+ Pool::::unbounded(Default::default, |_v| {})
+}
+
+test_generic_01!(test_unbounded_01, make_pool());
+test_generic_02!(test_unbounded_02, make_pool());
+#[cfg(not(feature = "loom"))]
+test_recycle_generic_01!(test_unbounded_recycle_01, make_recycle_pool());