Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Send and 'static bounds to the init futures of get_or_(try_)insert_with #32

Merged
merged 4 commits into from
Sep 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ on:
# Run against the last commit on the default branch on Friday at 8pm (UTC?)
- cron: '0 20 * * 5'

env:
RUSTFLAGS: '--cfg skeptic'

jobs:
test:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -59,13 +56,26 @@ jobs:
with:
command: test
args: --release
env:
RUSTFLAGS: '--cfg skeptic'

- name: Run tests (future)
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != '1.45.2' }}
with:
command: test
args: --release --features future
env:
RUSTFLAGS: '--cfg skeptic'

- name: Run UI tests (future, trybuild)
uses: actions-rs/cargo@v1
if: ${{ matrix.rust == 'stable' }}
with:
command: test
args: ui_trybuild --release --features future
env:
RUSTFLAGS: '--cfg trybuild'

- name: Run Rustfmt
uses: actions-rs/cargo@v1
Expand Down
24 changes: 13 additions & 11 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
{
"rust-analyzer.cargo.features": ["future"],
"cSpell.words": [
"CLFU",
"Deque",
"Deques",
"Hasher",
"Kawano",
"MSRV",
"Moka",
"RUSTFLAGS",
"Ristretto",
"Tatsuya",
"Upsert",
"actix",
"ahash",
"benmanes",
"CLFU",
"clippy",
"cpus",
"deqs",
"Deque",
"Deques",
"else's",
"getrandom",
"Hasher",
"Kawano",
"Moka",
"mpsc",
"MSRV",
"nanos",
"nocapture",
"peekable",
"preds",
"reqwest",
"Ristretto",
"runtimes",
"rustdoc",
"RUSTFLAGS",
"rustfmt",
"semver",
"structs",
"Tatsuya",
"thiserror",
"toolchain",
"trybuild",
"unsync",
"Upsert",
"usize"
]
}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Fixed

- Fix `get_or_insert_with` and `get_or_try_insert_with` methods in `future::Cache`
can lead undefined behavior as accepting `init` future that is not `Send` or
`'static`. ([#31][gh-issue-0031])
- Fix `usize` overflow on big cache capacity. ([#28][gh-pull-0028])

### Added
Expand Down Expand Up @@ -93,6 +96,7 @@

[caffeine-git]: https://github.com/ben-manes/caffeine

[gh-issue-0030]: https://github.com/moka-rs/moka/issues/30/
[gh-pull-0030]: https://github.com/moka-rs/moka/pull/30/
[gh-pull-0028]: https://github.com/moka-rs/moka/pull/28/
[gh-pull-0022]: https://github.com/moka-rs/moka/pull/22/
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@ reqwest = "0.11"
skeptic = "0.13"
tokio = { version = "1", features = ["rt-multi-thread", "macros" ] }

[target.'cfg(trybuild)'.dev-dependencies]
trybuild = "1.0"

[target.'cfg(skeptic)'.build-dependencies]
skeptic = "0.13"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ To run all tests including `future` feature and doc tests on the README, use the
following command:

```console
$ RUSTFLAGS='--cfg skeptic' cargo test --all-features
$ RUSTFLAGS='--cfg skeptic -cfg trybuild' cargo test --all-features
```


Expand Down
9 changes: 6 additions & 3 deletions src/future/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ where
/// // will call `get_or_insert_with` at the same time, the `init` async
/// // block must be resolved only once.
/// let value = my_cache
/// .get_or_insert_with("key1", async {
/// .get_or_insert_with("key1", async move {
/// println!("Task {} inserting a value.", task_id);
/// Arc::new(vec![0u8; TEN_MIB])
/// })
Expand Down Expand Up @@ -344,7 +344,10 @@ where
/// Task 2 got the value. (len: 10485760)
/// ```
///
pub async fn get_or_insert_with(&self, key: K, init: impl Future<Output = V>) -> V {
pub async fn get_or_insert_with<F>(&self, key: K, init: F) -> V
where
F: Future<Output = V> + Send + 'static,
{
let hash = self.base.hash(&key);
let key = Arc::new(key);
self.get_or_insert_with_hash_and_fun(key, hash, init).await
Expand Down Expand Up @@ -445,7 +448,7 @@ where
init: F,
) -> Result<V, Arc<Box<dyn Error + Send + Sync + 'static>>>
where
F: Future<Output = Result<V, Box<dyn Error + Send + Sync + 'static>>>,
F: Future<Output = Result<V, Box<dyn Error + Send + Sync + 'static>>> + Send + 'static,
{
let hash = self.base.hash(&key);
let key = Arc::new(key);
Expand Down
17 changes: 17 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,20 @@ pub mod unsync;
pub(crate) mod common;

pub use common::error::PredicateError;

#[cfg(test)]
mod tests {
// #[cfg(trybuild)]
// #[test]
// fn ui_trybuild() {
// let t = trybuild::TestCases::new();
// t.compile_fail("tests/ui/default/*.rs");
// }

#[cfg(all(trybuild, feature = "future"))]
#[test]
fn ui_trybuild_future() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/future/*.rs");
}
}
24 changes: 24 additions & 0 deletions tests/ui/future/get_with_non_send_future.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// https://github.com/moka-rs/moka/issues/31

use moka::future::Cache;
use std::rc::Rc;

#[tokio::main]
async fn main() {
let cache: Cache<_, String> = Cache::new(100);

// Rc is !Send.
let data = Rc::new("zero".to_string());
let data1 = Rc::clone(&data);

cache
.get_or_insert_with(0, async move {
// A data race may occur.
// The async block can be executed by a different thread
// but Rc's internal reference counters are not thread safe.
data1.to_string()
})
.await;

println!("{:?}", data);
}
12 changes: 12 additions & 0 deletions tests/ui/future/get_with_non_send_future.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error: future cannot be sent between threads safely
--> $DIR/get_with_non_send_future.rs:15:10
|
15 | .get_or_insert_with(0, async move {
| ^^^^^^^^^^^^^^^^^^ future created by async block is not `Send`
|
= help: within `impl Future`, the trait `Send` is not implemented for `Rc<String>`
note: captured value is not `Send`
--> $DIR/get_with_non_send_future.rs:19:13
|
19 | data1.to_string()
| ^^^^^ has type `Rc<String>` which is not `Send`
25 changes: 25 additions & 0 deletions tests/ui/future/get_with_non_static_future.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// https://github.com/moka-rs/moka/issues/31

use moka::future::Cache;

#[tokio::main]
async fn main() {
let cache: Cache<_, String> = Cache::new(100);

let data = "zero".to_string();
{
// Not 'static.
let data_ref = &data;

cache
.get_or_insert_with(0, async {
// This may become a dangling pointer.
// The async block can be executed by a different thread so
// the captured reference `data_ref` may outlive its value.
data_ref.to_string()
})
.await;
}

println!("{:?}", data);
}
41 changes: 41 additions & 0 deletions tests/ui/future/get_with_non_static_future.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
error[E0597]: `data` does not live long enough
--> $DIR/get_with_non_static_future.rs:12:24
|
12 | let data_ref = &data;
| ^^^^^ borrowed value does not live long enough
...
15 | .get_or_insert_with(0, async {
| ____________________________________-
16 | | // This may become a dangling pointer.
17 | | // The async block can be executed by a different thread so
18 | | // the captured reference `data_ref` may outlive its value.
19 | | data_ref.to_string()
20 | | })
| |_____________- argument requires that `data` is borrowed for `'static`
...
25 | }
| - `data` dropped here while still borrowed

error[E0373]: async block may outlive the current function, but it borrows `data_ref`, which is owned by the current function
--> $DIR/get_with_non_static_future.rs:15:42
|
15 | .get_or_insert_with(0, async {
| __________________________________________^
16 | | // This may become a dangling pointer.
17 | | // The async block can be executed by a different thread so
18 | | // the captured reference `data_ref` may outlive its value.
19 | | data_ref.to_string()
| | -------- `data_ref` is borrowed here
20 | | })
| |_____________^ may outlive borrowed value `data_ref`
|
= note: async blocks are not executed immediately and must either take a reference or ownership of outside variables they use
help: to force the async block to take ownership of `data_ref` (and any other referenced variables), use the `move` keyword
|
15 | .get_or_insert_with(0, async move {
16 | // This may become a dangling pointer.
17 | // The async block can be executed by a different thread so
18 | // the captured reference `data_ref` may outlive its value.
19 | data_ref.to_string()
20 | })
|