Skip to content

Commit 8bb7fd9

Browse files
khueytaiki-e
authored andcommitted
Add WeakShared.
This makes implementing a cache with futures much easier. With the current architecture a cache that uses Shareds cannot honor the "drop as cancellation" feature of futures. And the cache is forced to either poll futures itself to ensure they're polled to completion or to leave half-polled futures dangling inside it (potentially consuming resources like sockets or database connections). WeakShared is to Shared as Weak is to Arc. If any copy of the underlying Shared exists that has not be dropped or polled to completion the WeakShared can be upgraded to it. This makes it possible to construct a cache that does not entrain futures, thus honoring the "drop as cancellation" feature.
1 parent c4f7349 commit 8bb7fd9

File tree

5 files changed

+65
-4
lines changed

5 files changed

+65
-4
lines changed

futures-util/src/future/future/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ pub use self::remote_handle::{Remote, RemoteHandle};
114114
mod shared;
115115
#[cfg(feature = "std")]
116116
#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
117-
pub use self::shared::Shared;
117+
pub use self::shared::{Shared, WeakShared};
118118

119119
impl<T: ?Sized> FutureExt for T where T: Future {}
120120

futures-util/src/future/future/shared.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::fmt;
77
use std::pin::Pin;
88
use std::sync::atomic::AtomicUsize;
99
use std::sync::atomic::Ordering::{Acquire, SeqCst};
10-
use std::sync::{Arc, Mutex};
10+
use std::sync::{Arc, Mutex, Weak};
1111

1212
/// Future for the [`shared`](super::FutureExt::shared) method.
1313
#[must_use = "futures do nothing unless you `.await` or poll them"]
@@ -26,6 +26,9 @@ struct Notifier {
2626
wakers: Mutex<Option<Slab<Option<Waker>>>>,
2727
}
2828

29+
/// A weak reference to a [`Shared`] that can be upgraded much like an `Arc`.
30+
pub struct WeakShared<Fut: Future>(Weak<Inner<Fut>>);
31+
2932
// The future itself is polled behind the `Arc`, so it won't be moved
3033
// when `Shared` is moved.
3134
impl<Fut: Future> Unpin for Shared<Fut> {}
@@ -45,6 +48,12 @@ impl<Fut: Future> fmt::Debug for Inner<Fut> {
4548
}
4649
}
4750

51+
impl<Fut: Future> fmt::Debug for WeakShared<Fut> {
52+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53+
f.debug_struct("WeakShared").finish()
54+
}
55+
}
56+
4857
enum FutureOrOutput<Fut: Future> {
4958
Future(Fut),
5059
Output(Fut::Output),
@@ -107,6 +116,16 @@ where
107116
}
108117
None
109118
}
119+
120+
/// Creates a new [`WeakShared`] for this [`Shared`].
121+
///
122+
/// Returns [`None`] if it has already been polled to completion.
123+
pub fn downgrade(&self) -> Option<WeakShared<Fut>> {
124+
if let Some(inner) = self.inner.as_ref() {
125+
return Some(WeakShared(Arc::downgrade(inner)));
126+
}
127+
None
128+
}
110129
}
111130

112131
impl<Fut> Inner<Fut>
@@ -314,3 +333,17 @@ impl ArcWake for Notifier {
314333
}
315334
}
316335
}
336+
337+
impl<Fut: Future> WeakShared<Fut>
338+
{
339+
/// Attempts to upgrade this [`WeakShared`] into a [`Shared`].
340+
///
341+
/// Returns [`None`] if all clones of the [`Shared`] have been dropped or polled
342+
/// to completion.
343+
pub fn upgrade(&self) -> Option<Shared<Fut>> {
344+
Some(Shared {
345+
inner: Some(self.0.upgrade()?),
346+
waker_key: NULL_WAKER_KEY,
347+
})
348+
}
349+
}

futures-util/src/future/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub use self::future::CatchUnwind;
2828
pub use self::future::{Remote, RemoteHandle};
2929

3030
#[cfg(feature = "std")]
31-
pub use self::future::Shared;
31+
pub use self::future::{Shared, WeakShared};
3232

3333
mod try_future;
3434
pub use self::try_future::{

futures/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ pub mod future {
301301
#[cfg(feature = "std")]
302302
pub use futures_util::future::{
303303
Remote, RemoteHandle,
304-
CatchUnwind, Shared,
304+
CatchUnwind, Shared, WeakShared,
305305
};
306306
}
307307

futures/tests/shared.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,34 @@ fn peek() {
143143
}
144144
}
145145

146+
#[test]
147+
fn downgrade() {
148+
use futures::channel::oneshot;
149+
use futures::executor::block_on;
150+
use futures::future::FutureExt;
151+
152+
let (tx, rx) = oneshot::channel::<i32>();
153+
let shared = rx.shared();
154+
// Since there are outstanding `Shared`s, we can get a `WeakShared`.
155+
let weak = shared.downgrade().unwrap();
156+
// It should upgrade fine right now.
157+
let mut shared2 = weak.upgrade().unwrap();
158+
159+
tx.send(42).unwrap();
160+
assert_eq!(block_on(shared).unwrap(), 42);
161+
162+
// We should still be able to get a new `WeakShared` and upgrade it
163+
// because `shared2` is outstanding.
164+
assert!(shared2.downgrade().is_some());
165+
assert!(weak.upgrade().is_some());
166+
167+
assert_eq!(block_on(&mut shared2).unwrap(), 42);
168+
// Now that all `Shared`s have been exhausted, we should not be able
169+
// to get a new `WeakShared` or upgrade an existing one.
170+
assert!(weak.upgrade().is_none());
171+
assert!(shared2.downgrade().is_none());
172+
}
173+
146174
#[test]
147175
fn dont_clone_in_single_owner_shared_future() {
148176
use futures::channel::oneshot;

0 commit comments

Comments
 (0)