From da6105d92b26a9c73a677d5b5bda91ab328fa32f Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 17:55:06 +0100 Subject: [PATCH 01/11] util: add ReusableBoxFuture utility --- tokio-util/src/sync/mod.rs | 3 + tokio-util/src/sync/reusable_box.rs | 188 ++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 tokio-util/src/sync/reusable_box.rs diff --git a/tokio-util/src/sync/mod.rs b/tokio-util/src/sync/mod.rs index 7a0637d32a6..85ef1b78340 100644 --- a/tokio-util/src/sync/mod.rs +++ b/tokio-util/src/sync/mod.rs @@ -7,3 +7,6 @@ mod intrusive_double_linked_list; mod poll_semaphore; pub use poll_semaphore::PollSemaphore; + +mod reusable_box; +pub use reusable_box::ReusableBoxFuture; diff --git a/tokio-util/src/sync/reusable_box.rs b/tokio-util/src/sync/reusable_box.rs new file mode 100644 index 00000000000..42d90677004 --- /dev/null +++ b/tokio-util/src/sync/reusable_box.rs @@ -0,0 +1,188 @@ +use std::future::Future; +use std::task::{Context, Poll}; +use std::pin::Pin; +use std::{fmt, panic}; +use std::panic::AssertUnwindSafe; +use std::ptr::{self, NonNull}; +use std::alloc::{self, alloc, dealloc, Layout}; + +/// A resuable `Pin + Send>>`. +/// +/// This type lets you replace the future stored in the box without +/// reallocating when the size and alignment permits this. +pub struct ReusableBoxFuture { + boxed: NonNull + Send>, + layout: Layout, +} + +impl ReusableBoxFuture { + /// Create a new `ReusableBoxFuture` containing the provided future. + pub fn new(future: F) -> Self + where + F: Future + Send + 'static, + { + let layout = Layout::for_value(&future); + let boxed: *mut F = unsafe { alloc(layout) as *mut F }; + + match NonNull::new(boxed) { + Some(boxed) => { + unsafe { + ptr::write(boxed.as_ptr(), future); + } + + Self { + boxed, + layout, + } + }, + None => { + alloc::handle_alloc_error(layout); + }, + } + } + + /// Replace the future currently stored in this box. + /// + /// This reallocates if and only if the layout of the provided future is + /// different from the layout of the currently stored future. + pub fn set(&mut self, future: F) + where + F: Future + Send + 'static, + { + let layout = Layout::for_value(&future); + + if layout == self.layout { + // SAFETY: We just checked that the layout of F is correct. + unsafe { + self.set_same_layout(future); + } + } else { + *self = Self::new(future); + } + } + + /// Replace the future currently stored in this box. + /// + /// This function never reallocates, but returns an error if the provided + /// future has a different size or alignment from the currently stored + /// future. + pub fn try_set(&mut self, future: F) -> Result<(), F> + where + F: Future + Send + 'static, + { + let layout = Layout::for_value(&future); + + if layout == self.layout { + // SAFETY: We just checked that the layout of F is correct. + unsafe { + self.set_same_layout(future); + } + + Ok(()) + } else { + Err(future) + } + } + + /// Set the current future. + /// + /// # Safety + /// + /// This function requires that the layout of the provided future is the + /// same as `self.layout`. + unsafe fn set_same_layout(&mut self, future: F) + where + F: Future + Send + 'static, + { + // Drop the existing future, catching any panics. + let result = panic::catch_unwind(AssertUnwindSafe(|| { + ptr::drop_in_place(self.boxed.as_ptr()); + })); + + // Overwrite the future behind the pointer. This is safe because the + // allocation was allocated with the same size and alignment as the type F. + let self_ptr: *mut F = self.boxed.as_ptr() as *mut F; + ptr::write(self_ptr, future); + + // Update the vtable of self.boxed. The pointer is not null because we + // just got it from self.boxed, which is not null. + self.boxed = NonNull::new_unchecked(self_ptr); + + // If the old future's destructor panicked, resume unwinding. + match result { + Ok(()) => {}, + Err(payload) => { + panic::resume_unwind(payload); + }, + } + } + + /// Get a pinned reference to the underlying future. + pub fn get_pin(&mut self) -> Pin<&mut (dyn Future + Send)> { + unsafe { + Pin::new_unchecked(self.boxed.as_mut()) + } + } + + /// Poll the future stored inside this box. + pub fn poll(&mut self, cx: &mut Context<'_>) -> Poll { + self.get_pin().poll(cx) + } +} + +impl Future for ReusableBoxFuture { + type Output = T; + + /// Poll the future stored inside this box. + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::into_inner(self).get_pin().poll(cx) + } +} + +// The future stored inside ReusableBoxFuture must be Send. +unsafe impl Send for ReusableBoxFuture {} + +// The only method called on self.boxed is poll, which takes &mut self, so this +// struct being Sync does not permit any invalid access to the Future, even if +// the future is not Sync. +unsafe impl Sync for ReusableBoxFuture {} + +// Just like a Pin> is always Unpin, so is this type. +impl Unpin for ReusableBoxFuture {} + +impl Drop for ReusableBoxFuture { + fn drop(&mut self) { + struct DeallocateOnDrop { + ptr: *mut u8, + layout: Layout, + } + impl Drop for DeallocateOnDrop { + fn drop(&mut self) { + unsafe { + dealloc(self.ptr, self.layout); + } + } + } + + // This object guarantees that the memory is deallocated even if the + // destructor panics. + let dealloc_on_drop = DeallocateOnDrop { + ptr: self.boxed.as_ptr() as *mut u8, + layout: self.layout, + }; + + unsafe { + ptr::drop_in_place(self.boxed.as_ptr()); + } + + // Explicitly call the destructor. Note that this also happens if + // drop_in_place panics. + drop(dealloc_on_drop); + } +} + +impl fmt::Debug for ReusableBoxFuture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ReusableBoxFuture").finish() + } +} From 8166110b6f87e99b2dc1c7dc1dfeb7854950e4c9 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 18:00:38 +0100 Subject: [PATCH 02/11] rustfmt --- tokio-util/src/sync/reusable_box.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/tokio-util/src/sync/reusable_box.rs b/tokio-util/src/sync/reusable_box.rs index 42d90677004..939e94e473c 100644 --- a/tokio-util/src/sync/reusable_box.rs +++ b/tokio-util/src/sync/reusable_box.rs @@ -1,10 +1,10 @@ +use std::alloc::{self, alloc, dealloc, Layout}; use std::future::Future; -use std::task::{Context, Poll}; -use std::pin::Pin; -use std::{fmt, panic}; use std::panic::AssertUnwindSafe; +use std::pin::Pin; use std::ptr::{self, NonNull}; -use std::alloc::{self, alloc, dealloc, Layout}; +use std::task::{Context, Poll}; +use std::{fmt, panic}; /// A resuable `Pin + Send>>`. /// @@ -30,14 +30,11 @@ impl ReusableBoxFuture { ptr::write(boxed.as_ptr(), future); } - Self { - boxed, - layout, - } - }, + Self { boxed, layout } + } None => { alloc::handle_alloc_error(layout); - }, + } } } @@ -110,18 +107,16 @@ impl ReusableBoxFuture { // If the old future's destructor panicked, resume unwinding. match result { - Ok(()) => {}, + Ok(()) => {} Err(payload) => { panic::resume_unwind(payload); - }, + } } } /// Get a pinned reference to the underlying future. pub fn get_pin(&mut self) -> Pin<&mut (dyn Future + Send)> { - unsafe { - Pin::new_unchecked(self.boxed.as_mut()) - } + unsafe { Pin::new_unchecked(self.boxed.as_mut()) } } /// Poll the future stored inside this box. From 1676615b1bf0fd2c623f10abaf2657702a71ffdd Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 18:27:49 +0100 Subject: [PATCH 03/11] Add tests --- tokio-util/tests/reusable_box.rs | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tokio-util/tests/reusable_box.rs diff --git a/tokio-util/tests/reusable_box.rs b/tokio-util/tests/reusable_box.rs new file mode 100644 index 00000000000..fe2638db578 --- /dev/null +++ b/tokio-util/tests/reusable_box.rs @@ -0,0 +1,79 @@ +use futures::future::FutureExt; +use std::alloc::Layout; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio_util::sync::ReusableBoxFuture; + +#[test] +fn test_different_futures() { + let fut = async move { + 10 + }; + // Not zero sized! + assert_eq!(Layout::for_value(&fut).size(), 1); + + let mut b = ReusableBoxFuture::new(fut); + + assert_eq!(b.get_pin().now_or_never(), Some(10)); + + b.try_set(async move { + 20 + }).unwrap_or_else(|_| panic!("incorrect size")); + + assert_eq!(b.get_pin().now_or_never(), Some(20)); + + b.try_set(async move { + 30 + }).unwrap_or_else(|_| panic!("incorrect size")); + + assert_eq!(b.get_pin().now_or_never(), Some(30)); +} + +#[test] +fn test_different_sizes() { + let fut1 = async move { + 10 + }; + let val = [0u32; 1000]; + let fut2 = async move { + val[0] + }; + let fut3 = ZeroSizedFuture {}; + + assert_eq!(Layout::for_value(&fut1).size(), 1); + assert_eq!(Layout::for_value(&fut2).size(), 4004); + assert_eq!(Layout::for_value(&fut3).size(), 0); + + let mut b = ReusableBoxFuture::new(fut1); + assert_eq!(b.get_pin().now_or_never(), Some(10)); + b.set(fut2); + assert_eq!(b.get_pin().now_or_never(), Some(0)); + b.set(fut3); + assert_eq!(b.get_pin().now_or_never(), Some(5)); +} + +struct ZeroSizedFuture {} +impl Future for ZeroSizedFuture { + type Output = u32; + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + Poll::Ready(5) + } +} + +#[test] +fn test_zero_sized() { + let fut = ZeroSizedFuture {}; + // Zero sized! + assert_eq!(Layout::for_value(&fut).size(), 0); + + let mut b = ReusableBoxFuture::new(fut); + + assert_eq!(b.get_pin().now_or_never(), Some(5)); + assert_eq!(b.get_pin().now_or_never(), Some(5)); + + b.try_set(ZeroSizedFuture {}).unwrap_or_else(|_| panic!("incorrect size")); + + assert_eq!(b.get_pin().now_or_never(), Some(5)); + assert_eq!(b.get_pin().now_or_never(), Some(5)); +} From 1b0f4ebd2515b40f890409f1b1efd3f65bcf2ce9 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 18:29:11 +0100 Subject: [PATCH 04/11] rustfmta again --- tokio-util/tests/reusable_box.rs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/tokio-util/tests/reusable_box.rs b/tokio-util/tests/reusable_box.rs index fe2638db578..c8f6da02ae2 100644 --- a/tokio-util/tests/reusable_box.rs +++ b/tokio-util/tests/reusable_box.rs @@ -7,9 +7,7 @@ use tokio_util::sync::ReusableBoxFuture; #[test] fn test_different_futures() { - let fut = async move { - 10 - }; + let fut = async move { 10 }; // Not zero sized! assert_eq!(Layout::for_value(&fut).size(), 1); @@ -17,28 +15,22 @@ fn test_different_futures() { assert_eq!(b.get_pin().now_or_never(), Some(10)); - b.try_set(async move { - 20 - }).unwrap_or_else(|_| panic!("incorrect size")); + b.try_set(async move { 20 }) + .unwrap_or_else(|_| panic!("incorrect size")); assert_eq!(b.get_pin().now_or_never(), Some(20)); - b.try_set(async move { - 30 - }).unwrap_or_else(|_| panic!("incorrect size")); + b.try_set(async move { 30 }) + .unwrap_or_else(|_| panic!("incorrect size")); assert_eq!(b.get_pin().now_or_never(), Some(30)); } #[test] fn test_different_sizes() { - let fut1 = async move { - 10 - }; + let fut1 = async move { 10 }; let val = [0u32; 1000]; - let fut2 = async move { - val[0] - }; + let fut2 = async move { val[0] }; let fut3 = ZeroSizedFuture {}; assert_eq!(Layout::for_value(&fut1).size(), 1); @@ -72,7 +64,8 @@ fn test_zero_sized() { assert_eq!(b.get_pin().now_or_never(), Some(5)); assert_eq!(b.get_pin().now_or_never(), Some(5)); - b.try_set(ZeroSizedFuture {}).unwrap_or_else(|_| panic!("incorrect size")); + b.try_set(ZeroSizedFuture {}) + .unwrap_or_else(|_| panic!("incorrect size")); assert_eq!(b.get_pin().now_or_never(), Some(5)); assert_eq!(b.get_pin().now_or_never(), Some(5)); From 69a2702e43582048a6d860d1e53a5261216ed7ce Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 18:48:25 +0100 Subject: [PATCH 05/11] Use Box instead of alloc --- tokio-util/src/sync/reusable_box.rs | 45 +++++------------------------ 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/tokio-util/src/sync/reusable_box.rs b/tokio-util/src/sync/reusable_box.rs index 939e94e473c..4bfed0682d2 100644 --- a/tokio-util/src/sync/reusable_box.rs +++ b/tokio-util/src/sync/reusable_box.rs @@ -1,4 +1,4 @@ -use std::alloc::{self, alloc, dealloc, Layout}; +use std::alloc::Layout; use std::future::Future; use std::panic::AssertUnwindSafe; use std::pin::Pin; @@ -22,20 +22,14 @@ impl ReusableBoxFuture { F: Future + Send + 'static, { let layout = Layout::for_value(&future); - let boxed: *mut F = unsafe { alloc(layout) as *mut F }; + let boxed: Box + Send> = Box::new(future); - match NonNull::new(boxed) { - Some(boxed) => { - unsafe { - ptr::write(boxed.as_ptr(), future); - } + let boxed = Box::into_raw(boxed); - Self { boxed, layout } - } - None => { - alloc::handle_alloc_error(layout); - } - } + // SAFETY: Box::into_raw does not return null pointers. + let boxed = unsafe { NonNull::new_unchecked(boxed) }; + + Self { boxed, layout } } /// Replace the future currently stored in this box. @@ -147,32 +141,9 @@ impl Unpin for ReusableBoxFuture {} impl Drop for ReusableBoxFuture { fn drop(&mut self) { - struct DeallocateOnDrop { - ptr: *mut u8, - layout: Layout, - } - impl Drop for DeallocateOnDrop { - fn drop(&mut self) { - unsafe { - dealloc(self.ptr, self.layout); - } - } - } - - // This object guarantees that the memory is deallocated even if the - // destructor panics. - let dealloc_on_drop = DeallocateOnDrop { - ptr: self.boxed.as_ptr() as *mut u8, - layout: self.layout, - }; - unsafe { - ptr::drop_in_place(self.boxed.as_ptr()); + drop(Box::from_raw(self.boxed.as_ptr())); } - - // Explicitly call the destructor. Note that this also happens if - // drop_in_place panics. - drop(dealloc_on_drop); } } From 5c9e6ab82ff665fa8a7ccdd6ecadb89054bd2be3 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 19:00:56 +0100 Subject: [PATCH 06/11] fix typo --- tokio-util/src/sync/reusable_box.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio-util/src/sync/reusable_box.rs b/tokio-util/src/sync/reusable_box.rs index 4bfed0682d2..fc1ab74b3a5 100644 --- a/tokio-util/src/sync/reusable_box.rs +++ b/tokio-util/src/sync/reusable_box.rs @@ -6,7 +6,7 @@ use std::ptr::{self, NonNull}; use std::task::{Context, Poll}; use std::{fmt, panic}; -/// A resuable `Pin + Send>>`. +/// A reusable `Pin + Send>>`. /// /// This type lets you replace the future stored in the box without /// reallocating when the size and alignment permits this. From 6127c715a5a4cb29b44a02d3bb2052a7c4953041 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 19:14:01 +0100 Subject: [PATCH 07/11] Use Layout::new --- tokio-util/src/sync/reusable_box.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tokio-util/src/sync/reusable_box.rs b/tokio-util/src/sync/reusable_box.rs index fc1ab74b3a5..305308b38d7 100644 --- a/tokio-util/src/sync/reusable_box.rs +++ b/tokio-util/src/sync/reusable_box.rs @@ -21,7 +21,7 @@ impl ReusableBoxFuture { where F: Future + Send + 'static, { - let layout = Layout::for_value(&future); + let layout = Layout::new::(); let boxed: Box + Send> = Box::new(future); let boxed = Box::into_raw(boxed); @@ -40,7 +40,7 @@ impl ReusableBoxFuture { where F: Future + Send + 'static, { - let layout = Layout::for_value(&future); + let layout = Layout::new::(); if layout == self.layout { // SAFETY: We just checked that the layout of F is correct. @@ -61,7 +61,7 @@ impl ReusableBoxFuture { where F: Future + Send + 'static, { - let layout = Layout::for_value(&future); + let layout = Layout::new::(); if layout == self.layout { // SAFETY: We just checked that the layout of F is correct. From 08b4a3d7ba1173abb63f9df1a9b5f89206049196 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 19:31:08 +0100 Subject: [PATCH 08/11] Deduplicate code in set and try_set --- tokio-util/src/sync/reusable_box.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tokio-util/src/sync/reusable_box.rs b/tokio-util/src/sync/reusable_box.rs index 305308b38d7..c8481272a52 100644 --- a/tokio-util/src/sync/reusable_box.rs +++ b/tokio-util/src/sync/reusable_box.rs @@ -40,15 +40,11 @@ impl ReusableBoxFuture { where F: Future + Send + 'static, { - let layout = Layout::new::(); - - if layout == self.layout { - // SAFETY: We just checked that the layout of F is correct. - unsafe { - self.set_same_layout(future); + match self.try_set(future) { + Ok(()) => {} + Err(future) => { + *self = Self::new(future); } - } else { - *self = Self::new(future); } } From eca36a89a03bba5e222b609ef82868d8a65b8306 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 19:36:25 +0100 Subject: [PATCH 09/11] Use if let instead of match --- tokio-util/src/sync/reusable_box.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tokio-util/src/sync/reusable_box.rs b/tokio-util/src/sync/reusable_box.rs index c8481272a52..af49304e184 100644 --- a/tokio-util/src/sync/reusable_box.rs +++ b/tokio-util/src/sync/reusable_box.rs @@ -40,11 +40,8 @@ impl ReusableBoxFuture { where F: Future + Send + 'static, { - match self.try_set(future) { - Ok(()) => {} - Err(future) => { - *self = Self::new(future); - } + if let Err(future) = self.try_set(future) { + *self = Self::new(future); } } From a95b1a6493b61996d849a21ff45b8646201d68d8 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 20:02:34 +0100 Subject: [PATCH 10/11] Get layout from vtable of trait object --- tokio-util/src/sync/reusable_box.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tokio-util/src/sync/reusable_box.rs b/tokio-util/src/sync/reusable_box.rs index af49304e184..16efbfdd915 100644 --- a/tokio-util/src/sync/reusable_box.rs +++ b/tokio-util/src/sync/reusable_box.rs @@ -12,7 +12,6 @@ use std::{fmt, panic}; /// reallocating when the size and alignment permits this. pub struct ReusableBoxFuture { boxed: NonNull + Send>, - layout: Layout, } impl ReusableBoxFuture { @@ -21,7 +20,6 @@ impl ReusableBoxFuture { where F: Future + Send + 'static, { - let layout = Layout::new::(); let boxed: Box + Send> = Box::new(future); let boxed = Box::into_raw(boxed); @@ -29,7 +27,7 @@ impl ReusableBoxFuture { // SAFETY: Box::into_raw does not return null pointers. let boxed = unsafe { NonNull::new_unchecked(boxed) }; - Self { boxed, layout } + Self { boxed } } /// Replace the future currently stored in this box. @@ -54,9 +52,15 @@ impl ReusableBoxFuture { where F: Future + Send + 'static, { - let layout = Layout::new::(); - - if layout == self.layout { + // SAFETY: The pointer is not dangling. + let self_layout = { + let dyn_future: &(dyn Future + Send) = unsafe { + self.boxed.as_ref() + }; + Layout::for_value(dyn_future) + }; + + if Layout::new::() == self_layout { // SAFETY: We just checked that the layout of F is correct. unsafe { self.set_same_layout(future); @@ -103,6 +107,8 @@ impl ReusableBoxFuture { /// Get a pinned reference to the underlying future. pub fn get_pin(&mut self) -> Pin<&mut (dyn Future + Send)> { + // SAFETY: The user of this box cannot move the box, and we do not move it + // either. unsafe { Pin::new_unchecked(self.boxed.as_mut()) } } From a395490f011d69aae29c17871194b4a934fd04b7 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 23 Jan 2021 20:04:06 +0100 Subject: [PATCH 11/11] rustfmt --- tokio-util/src/sync/reusable_box.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tokio-util/src/sync/reusable_box.rs b/tokio-util/src/sync/reusable_box.rs index 16efbfdd915..0fbc49f064a 100644 --- a/tokio-util/src/sync/reusable_box.rs +++ b/tokio-util/src/sync/reusable_box.rs @@ -54,9 +54,7 @@ impl ReusableBoxFuture { { // SAFETY: The pointer is not dangling. let self_layout = { - let dyn_future: &(dyn Future + Send) = unsafe { - self.boxed.as_ref() - }; + let dyn_future: &(dyn Future + Send) = unsafe { self.boxed.as_ref() }; Layout::for_value(dyn_future) };