├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── README.tpl ├── src └── lib.rs └── tests └── lease.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | # always test things that aren't pushes (like PRs) 9 | # never test tags or pushes to non-master branches (wait for PR) 10 | # https://github.com/travis-ci/travis-ci/issues/2200#issuecomment-441395545) 11 | if: type != push OR (tag IS blank AND branch = master) 12 | 13 | # an entry in stage=test will be generated for each rust/os combination. 14 | # each entry will run these commands. 15 | script: 16 | - cargo test 17 | jobs: 18 | allow_failures: 19 | - rust: nightly 20 | include: 21 | - &check 22 | stage: check # do a pre-screen to make sure this is even worth testing 23 | script: cargo check --all-targets 24 | rust: stable 25 | os: linux 26 | - <<: *check # also test oldest known-good stable 27 | rust: 1.39.0 28 | - stage: test # then run the tests 29 | rust: stable 30 | os: osx 31 | - rust: stable 32 | os: windows 33 | - &linux 34 | stage: test 35 | rust: stable 36 | os: linux 37 | - <<: *linux 38 | rust: beta 39 | - <<: *linux 40 | rust: nightly 41 | - stage: lint # we lint on beta to future-proof 42 | name: "Rust: beta, rustfmt" 43 | rust: beta 44 | os: linux 45 | script: 46 | - rustup component add rustfmt-preview 47 | - cargo fmt -v -- --check 48 | - name: "Rust: nightly, rustfmt" # and on nightly with allow_fail 49 | rust: nightly 50 | os: linux 51 | script: 52 | - rustup component add rustfmt-preview 53 | - cargo fmt -v -- --check 54 | - name: "Rust: beta, clippy" 55 | rust: beta 56 | os: linux 57 | script: 58 | - rustup component add clippy-preview 59 | - touch ./src/lib.rs && cargo clippy -- -D warnings 60 | - name: "Rust: nightly, clippy" 61 | rust: nightly 62 | os: linux 63 | script: 64 | - rustup component add clippy-preview 65 | - touch ./src/lib.rs && cargo clippy -- -D warnings 66 | - <<: *linux 67 | stage: coverage 68 | rust: nightly 69 | env: CACHE_NAME=coverage 70 | sudo: required 71 | script: 72 | - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --force cargo-tarpaulin || true 73 | - cargo tarpaulin --out Xml 74 | - bash <(curl -s https://codecov.io/bash) 75 | - cargo clean -p async-lease # ensure we don't cache build for coverage 76 | stages: 77 | - check 78 | - test 79 | - lint 80 | - coverage 81 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-lease" 3 | version = "0.2.0-alpha.1" 4 | authors = ["Jon Gjengset "] 5 | edition = "2018" 6 | license = "MIT" 7 | homepage = "https://github.com/jonhoo/async-lease" 8 | repository = "https://github.com/jonhoo/async-lease" 9 | description = "Essentially a futures-oriented Arc>>" 10 | categories = ["asynchronous"] 11 | 12 | [badges] 13 | travis-ci = { repository = "jonhoo/async-lease" } 14 | codecov = { repository = "jonhoo/async-lease", branch = "master", service = "github" } 15 | maintenance = { status = "passively-maintained" } 16 | 17 | [dependencies] 18 | tokio-sync = "0.2.0-alpha.6" 19 | 20 | [dev-dependencies] 21 | tokio-test = "0.2.0-alpha.6" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-lease 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/async-lease.svg)](https://crates.io/crates/async-lease) 4 | [![Documentation](https://docs.rs/async-lease/badge.svg)](https://docs.rs/async-lease/) 5 | [![Build Status](https://travis-ci.com/jonhoo/async-lease.svg?branch=master)](https://travis-ci.com/jonhoo/async-lease) 6 | [![Codecov](https://codecov.io/github/jonhoo/async-lease/coverage.svg?branch=master)](https://codecov.io/gh/jonhoo/async-lease) 7 | 8 | An asynchronous, atomic option type intended for use with methods that move `self`. 9 | 10 | This module provides `Lease`, a type that acts similarly to an asynchronous `Mutex`, with one 11 | major difference: it expects you to move the leased item _by value_, and then _return it_ when 12 | you are done. You can think of a `Lease` as an atomic, asynchronous `Option` type, in which we 13 | can `take` the value only if no-one else has currently taken it, and where we are notified when 14 | the value has returned so we can try to take it again. 15 | 16 | This type is intended for use with methods that take `self` by value, and _eventually_, at some 17 | later point in time, return that `Self` for future use. This tends to happen particularly often 18 | in future-related contexts. For example, consider the following method for a hypothetical, 19 | non-pipelined connection type: 20 | 21 | ```rust 22 | impl Connection { 23 | fn get(self, key: i64) -> impl Future; 24 | } 25 | ``` 26 | 27 | Let's say you want to expose an interface that does _not_ consume `self`, but instead has a 28 | `poll_ready` method that checks whether the connection is ready to receive another request: 29 | 30 | ```rust 31 | impl MyConnection { 32 | fn poll_ready(&mut self) -> Poll<(), Error = Error>; 33 | fn call(&mut self, key: i64) -> impl Future; 34 | } 35 | ``` 36 | 37 | `Lease` allows you to do this. Specifically, `poll_ready` attempts to acquire the lease using 38 | `Lease::poll_acquire`, and `call` _transfers_ that lease into the returned future. When the 39 | future eventually resolves, we _restore_ the leased value so that `poll_ready` returns `Ready` 40 | again to anyone else who may want to take the value. The example above would thus look like 41 | this: 42 | 43 | ```rust 44 | impl MyConnection { 45 | fn poll_ready(&mut self) -> Poll<(), Error = Error> { 46 | self.lease.poll_acquire() 47 | } 48 | 49 | fn call(&mut self, key: i64) -> impl Future { 50 | // We want to transfer the lease into the future 51 | // and leave behind an unacquired lease. 52 | let mut lease = self.lease.transfer(); 53 | lease.take().get(key).map(move |(v, connection)| { 54 | // Give back the connection for other callers. 55 | // After this, `poll_ready` may return `Ok(Ready)` again. 56 | lease.restore(connection); 57 | // And yield just the value. 58 | v 59 | }) 60 | } 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # {{crate}} 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/async-lease.svg)](https://crates.io/crates/async-lease) 4 | [![Documentation](https://docs.rs/async-lease/badge.svg)](https://docs.rs/async-lease/) 5 | [![Build Status](https://travis-ci.com/jonhoo/async-lease.svg?branch=master)](https://travis-ci.com/jonhoo/async-lease) 6 | [![Codecov](https://codecov.io/github/jonhoo/async-lease/coverage.svg?branch=master)](https://codecov.io/gh/jonhoo/async-lease) 7 | 8 | {{readme}} 9 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An asynchronous, atomic option type intended for use with methods that move `self`. 2 | //! 3 | //! This module provides `Lease`, a type that acts similarly to an asynchronous `Mutex`, with one 4 | //! major difference: it expects you to move the leased item _by value_, and then _return it_ when 5 | //! you are done. You can think of a `Lease` as an atomic, asynchronous `Option` type, in which we 6 | //! can `take` the value only if no-one else has currently taken it, and where we are notified when 7 | //! the value has returned so we can try to take it again. 8 | //! 9 | //! This type is intended for use with methods that take `self` by value, and _eventually_, at some 10 | //! later point in time, return that `Self` for future use. This tends to happen particularly often 11 | //! in future-related contexts. For example, consider the following method for a hypothetical, 12 | //! non-pipelined connection type: 13 | //! 14 | //! ```rust,ignore 15 | //! impl Connection { 16 | //! fn get(self, key: i64) -> impl Future; 17 | //! } 18 | //! ``` 19 | //! 20 | //! Let's say you want to expose an interface that does _not_ consume `self`, but instead has a 21 | //! `poll_ready` method that checks whether the connection is ready to receive another request: 22 | //! 23 | //! ```rust,ignore 24 | //! impl MyConnection { 25 | //! fn poll_ready(&mut self) -> Poll<(), Error = Error>; 26 | //! fn call(&mut self, key: i64) -> impl Future; 27 | //! } 28 | //! ``` 29 | //! 30 | //! `Lease` allows you to do this. Specifically, `poll_ready` attempts to acquire the lease using 31 | //! `Lease::poll_acquire`, and `call` _transfers_ that lease into the returned future. When the 32 | //! future eventually resolves, we _restore_ the leased value so that `poll_ready` returns `Ready` 33 | //! again to anyone else who may want to take the value. The example above would thus look like 34 | //! this: 35 | //! 36 | //! ```rust,no_run 37 | //! # use std::future::Future; 38 | //! # use std::task::{Poll, Context}; 39 | //! # struct X; impl X { fn get(self, _: i64) -> impl Future { async move { (0, self) } } } 40 | //! # struct MyConnection { lease: async_lease::Lease } 41 | //! impl MyConnection { 42 | //! fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<()> { 43 | //! self.lease.poll_acquire(cx) 44 | //! } 45 | //! 46 | //! fn call(&mut self, key: i64) -> impl Future { 47 | //! // We want to transfer the lease into the future 48 | //! // and leave behind an unacquired lease. 49 | //! let mut lease = self.lease.transfer(); 50 | //! let fut = lease.take().get(key); 51 | //! async move { 52 | //! let (v, connection) = fut.await; 53 | //! 54 | //! // Give back the connection for other callers. 55 | //! // After this, `poll_ready` may return `Ok(Ready)` again. 56 | //! lease.restore(connection); 57 | //! 58 | //! // And yield just the value. 59 | //! v 60 | //! } 61 | //! } 62 | //! } 63 | //! ``` 64 | #![warn( 65 | unused_extern_crates, 66 | missing_debug_implementations, 67 | missing_docs, 68 | unreachable_pub 69 | )] 70 | 71 | use std::cell::UnsafeCell; 72 | use std::ops::{Deref, DerefMut}; 73 | use std::sync::Arc; 74 | use std::task::{Context, Poll}; 75 | use tokio_sync::semaphore; 76 | 77 | /// A handle to a leasable value. 78 | /// 79 | /// Use `poll_acquire` to acquire the lease, `take` to grab the leased value, and `restore` to 80 | /// return the leased value when you're done with it. 81 | /// 82 | /// The code will panic if you attempt to access the `S` behind a `Lease` through `Deref` or 83 | /// `DerefMut` without having acquired the lease through `poll_acquire` first, or if you have 84 | /// called `take`. 85 | /// 86 | /// The code will also panic if you attempt to drop a `Lease` without first restoring the leased 87 | /// value. 88 | #[derive(Debug)] 89 | pub struct Lease { 90 | inner: Arc>, 91 | permit: semaphore::Permit, 92 | } 93 | 94 | // As long as T: Send, it's fine to send Lease to other threads. 95 | // If T was not Send, sending a Lease would be bad, since you can access T through Lease. 96 | unsafe impl Send for Lease where T: Send {} 97 | 98 | #[derive(Debug)] 99 | struct State { 100 | c: UnsafeCell>, 101 | s: semaphore::Semaphore, 102 | } 103 | 104 | impl Lease { 105 | fn option(&mut self) -> &mut Option { 106 | unsafe { &mut *self.inner.c.get() } 107 | } 108 | 109 | /// Try to acquire the lease. 110 | /// 111 | /// If the lease is not available, the current task is notified once it is. 112 | pub fn poll_acquire(&mut self, cx: &mut Context<'_>) -> Poll<()> { 113 | match self.permit.poll_acquire(cx, &self.inner.s) { 114 | Poll::Ready(Ok(())) => Poll::Ready(()), 115 | Poll::Ready(Err(_)) => { 116 | // The semaphore was closed. but, we never explicitly close it, and we have a 117 | // handle to it through the Arc, which means that this can never happen. 118 | unreachable!() 119 | } 120 | Poll::Pending => Poll::Pending, 121 | } 122 | } 123 | 124 | /// Release the lease (if it has been acquired). 125 | /// 126 | /// This provides a way of "undoing" a call to `poll_ready` should you decide you don't need 127 | /// the lease after all, or if you only needed access by reference. 128 | /// 129 | /// This method will panic if you attempt to release the lease after you have called `take`. 130 | pub fn release(&mut self) { 131 | if self.permit.is_acquired() { 132 | // We need this check in case the reason we get here is that we already hit this 133 | // assertion, and `release` is being called _again_ because `self` is being dropped. 134 | if !::std::thread::panicking() { 135 | assert!( 136 | self.option().is_some(), 137 | "attempted to release the lease without restoring the value" 138 | ); 139 | } 140 | self.permit.release(&self.inner.s); 141 | } 142 | } 143 | 144 | /// Leave behind a non-acquired lease in place of this one, and return this acquired lease. 145 | /// 146 | /// This allows you to move a previously acquired lease into another context (like a `Future`) 147 | /// where you will later `restore` the leased value. 148 | /// 149 | /// This method will panic if you attempt to call it without first having acquired the lease. 150 | pub fn transfer(&mut self) -> Self { 151 | assert!(self.permit.is_acquired()); 152 | let mut transferred = self.clone(); 153 | ::std::mem::swap(self, &mut transferred); 154 | transferred 155 | } 156 | 157 | /// Take the leased value. 158 | /// 159 | /// This method will panic if you attempt to call it without first having acquired the lease. 160 | /// 161 | /// Note that you _must_ call `restore` on this lease before you drop it to return the leased 162 | /// value to other waiting clients. If you do not, dropping the lease will panic. 163 | pub fn take(&mut self) -> T { 164 | assert!(self.permit.is_acquired()); 165 | self.option() 166 | .take() 167 | .expect("attempted to call take(), but leased value has not been restored") 168 | } 169 | 170 | /// Restore the leased value. 171 | /// 172 | /// This method will panic if you attempt to call it without first having acquired the lease. 173 | /// 174 | /// Note that once you return the leased value, the lease is no longer considered acquired. 175 | pub fn restore(&mut self, state: T) { 176 | assert!(self.permit.is_acquired()); 177 | unsafe { *self.inner.c.get() = Some(state) }; 178 | // Finally, we can now release the permit since we're done with the connection 179 | self.release(); 180 | } 181 | } 182 | 183 | impl Drop for Lease { 184 | fn drop(&mut self) { 185 | self.release(); 186 | } 187 | } 188 | 189 | impl Deref for Lease { 190 | type Target = T; 191 | fn deref(&self) -> &Self::Target { 192 | assert!(self.permit.is_acquired()); 193 | let s = unsafe { &*self.inner.c.get() }; 194 | s.as_ref() 195 | .expect("attempted to deref lease after calling take()") 196 | } 197 | } 198 | 199 | impl DerefMut for Lease { 200 | fn deref_mut(&mut self) -> &mut Self::Target { 201 | assert!(self.permit.is_acquired()); 202 | let s = unsafe { &mut *self.inner.c.get() }; 203 | s.as_mut() 204 | .expect("attempted to deref_mut lease after calling take()") 205 | } 206 | } 207 | 208 | impl From for Lease { 209 | fn from(s: T) -> Self { 210 | Self { 211 | inner: Arc::new(State { 212 | c: UnsafeCell::new(Some(s)), 213 | s: semaphore::Semaphore::new(1), 214 | }), 215 | permit: semaphore::Permit::new(), 216 | } 217 | } 218 | } 219 | 220 | impl Clone for Lease { 221 | fn clone(&self) -> Self { 222 | Self { 223 | inner: self.inner.clone(), 224 | permit: semaphore::Permit::new(), 225 | } 226 | } 227 | } 228 | 229 | impl Default for Lease 230 | where 231 | T: Default, 232 | { 233 | fn default() -> Self { 234 | Self::from(T::default()) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /tests/lease.rs: -------------------------------------------------------------------------------- 1 | use async_lease::Lease; 2 | use tokio_test::*; 3 | 4 | #[test] 5 | fn default() { 6 | task::mock(|cx| { 7 | let mut l: Lease = Lease::default(); 8 | assert_ready!(l.poll_acquire(cx)); 9 | assert_eq!(&*l, &false); 10 | assert_eq!(l.take(), false); 11 | l.restore(true); 12 | }); 13 | } 14 | 15 | #[test] 16 | fn straight_execution() { 17 | task::mock(|cx| { 18 | let mut l = Lease::from(100); 19 | 20 | // We can immediately acquire the lease and take the value 21 | assert_ready!(l.poll_acquire(cx)); 22 | assert_eq!(&*l, &100); 23 | assert_eq!(l.take(), 100); 24 | l.restore(99); 25 | 26 | // We can immediately acquire again since the value was returned 27 | assert_ready!(l.poll_acquire(cx)); 28 | assert_eq!(l.take(), 99); 29 | l.restore(98); 30 | 31 | // Dropping the lease is okay since we returned the value 32 | drop(l); 33 | }); 34 | } 35 | 36 | #[test] 37 | fn drop_while_acquired_ok() { 38 | task::mock(|cx| { 39 | let mut l = Lease::from(100); 40 | assert_ready!(l.poll_acquire(cx)); 41 | 42 | // Dropping the lease while it is still acquired shouldn't 43 | // be an issue since we haven't taken the leased value. 44 | drop(l); 45 | }); 46 | } 47 | 48 | #[test] 49 | #[should_panic] 50 | fn take_twice() { 51 | task::mock(|cx| { 52 | let mut l = Lease::from(100); 53 | 54 | assert_ready!(l.poll_acquire(cx)); 55 | assert_eq!(l.take(), 100); 56 | l.take(); // should panic 57 | }); 58 | } 59 | 60 | #[test] 61 | #[should_panic] 62 | fn mut_after_take() { 63 | task::mock(|cx| { 64 | let mut l = Lease::from(100); 65 | 66 | assert_ready!(l.poll_acquire(cx)); 67 | // at this point we have the lease, so we can mutate directly 68 | *l = 99; 69 | // then we can take 70 | assert_eq!(l.take(), 99); 71 | // but now we shouldn't be allowed to mutate any more! 72 | *l = 98; 73 | }); 74 | } 75 | 76 | #[test] 77 | #[should_panic] 78 | fn take_wo_acquire() { 79 | let mut l = Lease::from(100); 80 | l.take(); // should panic 81 | } 82 | 83 | #[test] 84 | #[should_panic] 85 | fn drop_without_restore() { 86 | task::mock(|cx| { 87 | let mut l = Lease::from(100); 88 | assert_ready!(l.poll_acquire(cx)); 89 | assert_eq!(l.take(), 100); 90 | drop(l); // should panic 91 | }); 92 | } 93 | 94 | #[test] 95 | #[should_panic] 96 | fn release_after_take() { 97 | task::mock(|cx| { 98 | let mut l = Lease::from(100); 99 | assert_ready!(l.poll_acquire(cx)); 100 | assert_eq!(l.take(), 100); 101 | l.release(); // should panic 102 | }); 103 | } 104 | 105 | #[test] 106 | fn transfer_lease() { 107 | task::mock(|cx| { 108 | let mut l = Lease::from(100); 109 | 110 | assert_ready!(l.poll_acquire(cx)); 111 | 112 | // We should be able to transfer the acquired lease 113 | let mut l2 = l.transfer(); 114 | // And then use it as normal 115 | assert_eq!(&*l2, &100); 116 | assert_eq!(l2.take(), 100); 117 | l2.restore(99); 118 | 119 | // Dropping the transferred lease is okay since we returned the value 120 | drop(l2); 121 | 122 | // Once the transferred lease has been restored, we can acquire the lease again 123 | assert_ready!(l.poll_acquire(cx)); 124 | assert_eq!(l.take(), 99); 125 | l.restore(98); 126 | }); 127 | } 128 | 129 | #[test] 130 | fn readiness() { 131 | let mut task = task::MockTask::new(); 132 | 133 | let mut l = Lease::from(100); 134 | task.enter(|cx| { 135 | assert_ready!(l.poll_acquire(cx)); 136 | }); 137 | let mut l2 = l.transfer(); 138 | 139 | // We can't now acquire the lease since it's already held in l2 140 | task.enter(|cx| { 141 | assert_pending!(l.poll_acquire(cx)); 142 | }); 143 | 144 | // But once l2 restores the value, we can acquire it 145 | l2.restore(99); 146 | assert!(task.is_woken()); 147 | task.enter(|cx| { 148 | assert_ready!(l.poll_acquire(cx)); 149 | }); 150 | } 151 | --------------------------------------------------------------------------------