From 2cf485ce8ca75237d922f6ccb43722c02544c3c9 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 19 Sep 2024 14:44:03 -0400 Subject: [PATCH 1/7] feat(wasm): Add util creates for runtime,future and time primitives for wasm --- Cargo.toml | 2 +- russh-util/Cargo.toml | 25 +++++++ russh-util/src/future.rs | 157 +++++++++++++++++++++++++++++++++++++++ russh-util/src/lib.rs | 5 ++ russh-util/src/time.rs | 49 ++++++++++++ 5 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 russh-util/Cargo.toml create mode 100644 russh-util/src/future.rs create mode 100644 russh-util/src/lib.rs create mode 100644 russh-util/src/time.rs diff --git a/Cargo.toml b/Cargo.toml index 8ba84e34..2b0a41ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["russh-keys", "russh", "russh-config", "cryptovec", "pageant"] +members = ["russh-keys", "russh", "russh-config", "cryptovec", "pageant", "russh-util"] [patch.crates-io] russh = { path = "russh" } diff --git a/russh-util/Cargo.toml b/russh-util/Cargo.toml new file mode 100644 index 00000000..17f92d7d --- /dev/null +++ b/russh-util/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "russh-util" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = "0.4.38" + +[dev-dependencies] +futures-executor = "0.3.13" +static_assertions = "1.1.0" + + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4.43" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1.17", features = [ + "io-util", + "macros", + "sync", + "rt-multi-thread", + "rt", +] } diff --git a/russh-util/src/future.rs b/russh-util/src/future.rs new file mode 100644 index 00000000..8ed93378 --- /dev/null +++ b/russh-util/src/future.rs @@ -0,0 +1,157 @@ +use std::alloc::Layout; +use std::fmt; +use std::future::{self, Future}; +use std::mem::{self, ManuallyDrop}; +use std::pin::Pin; +use std::ptr; +use std::task::{Context, Poll}; + +/// A reusable `Pin + Send + 'a>>`. +/// +/// This type lets you replace the future stored in the box without +/// reallocating when the size and alignment permits this. +pub struct ReusableBoxFuture<'a, T> { + boxed: Pin + Send + 'a>>, +} + +impl<'a, T> ReusableBoxFuture<'a, T> { + /// Create a new `ReusableBoxFuture` containing the provided future. + pub fn new(future: F) -> Self + where + F: Future + Send + 'a, + { + Self { + boxed: Box::pin(future), + } + } + + /// 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 + 'a, + { + if let Err(future) = self.try_set(future) { + *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 + 'a, + { + // If we try to inline the contents of this function, the type checker complains because + // the bound `T: 'a` is not satisfied in the call to `pending()`. But by putting it in an + // inner function that doesn't have `T` as a generic parameter, we implicitly get the bound + // `F::Output: 'a` transitively through `F: 'a`, allowing us to call `pending()`. + #[inline(always)] + fn real_try_set<'a, F>( + this: &mut ReusableBoxFuture<'a, F::Output>, + future: F, + ) -> Result<(), F> + where + F: Future + Send + 'a, + { + // future::Pending is a ZST so this never allocates. + let boxed = mem::replace(&mut this.boxed, Box::pin(future::pending())); + reuse_pin_box(boxed, future, |boxed| this.boxed = Pin::from(boxed)) + } + + real_try_set(self, future) + } + + /// Get a pinned reference to the underlying future. + pub fn get_pin(&mut self) -> Pin<&mut (dyn Future + Send)> { + 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<'_, T> { + 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 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<'_, T> {} + +impl fmt::Debug for ReusableBoxFuture<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ReusableBoxFuture").finish() + } +} + +fn reuse_pin_box(boxed: Pin>, new_value: U, callback: F) -> Result +where + F: FnOnce(Box) -> O, +{ + let layout = Layout::for_value::(&*boxed); + if layout != Layout::new::() { + return Err(new_value); + } + + // SAFETY: We don't ever construct a non-pinned reference to the old `T` from now on, and we + // always drop the `T`. + let raw: *mut T = Box::into_raw(unsafe { Pin::into_inner_unchecked(boxed) }); + + // When dropping the old value panics, we still want to call `callback` — so move the rest of + // the code into a guard type. + let guard = CallOnDrop::new(|| { + let raw: *mut U = raw.cast::(); + unsafe { raw.write(new_value) }; + + // SAFETY: + // - `T` and `U` have the same layout. + // - `raw` comes from a `Box` that uses the same allocator as this one. + // - `raw` points to a valid instance of `U` (we just wrote it in). + let boxed = unsafe { Box::from_raw(raw) }; + + callback(boxed) + }); + + // Drop the old value. + unsafe { ptr::drop_in_place(raw) }; + + // Run the rest of the code. + Ok(guard.call()) +} + +struct CallOnDrop O> { + f: ManuallyDrop, +} + +impl O> CallOnDrop { + fn new(f: F) -> Self { + let f = ManuallyDrop::new(f); + Self { f } + } + fn call(self) -> O { + let mut this = ManuallyDrop::new(self); + let f = unsafe { ManuallyDrop::take(&mut this.f) }; + f() + } +} + +impl O> Drop for CallOnDrop { + fn drop(&mut self) { + let f = unsafe { ManuallyDrop::take(&mut self.f) }; + f(); + } +} \ No newline at end of file diff --git a/russh-util/src/lib.rs b/russh-util/src/lib.rs new file mode 100644 index 00000000..09725d85 --- /dev/null +++ b/russh-util/src/lib.rs @@ -0,0 +1,5 @@ +#![deny(dead_code)] // To be removed when full wasm support is added. + +pub mod future; +pub mod runtime; +pub mod time; \ No newline at end of file diff --git a/russh-util/src/time.rs b/russh-util/src/time.rs new file mode 100644 index 00000000..5fc4ebb9 --- /dev/null +++ b/russh-util/src/time.rs @@ -0,0 +1,49 @@ + + +#[cfg(not(target_arch = "wasm32"))] +pub use std_time::Instant; + + +#[cfg(target_arch = "wasm32")] +pub use wasm::Instant; + + + +mod wasm { + #[derive(Debug, Clone, Copy)] + pub struct Instant { + inner: chrono::DateTime, + } + + impl Instant { + pub fn now() -> Self { + Instant { + inner: chrono::Utc::now(), + } + } + + pub fn duration_since(&self, earlier: Instant) -> std::time::Duration { + (self.inner - earlier.inner).to_std().expect("Duration is negative") + } + } +} + +mod std_time { + + #[derive(Debug, Clone, Copy)] + pub struct Instant { + inner: std::time::Instant, + } + + impl Instant { + pub fn now() -> Self { + Instant { + inner: std::time::Instant::now(), + } + } + + pub fn duration_since(&self, earlier: Instant) -> std::time::Duration { + self.inner.duration_since(earlier.inner) + } + } +} \ No newline at end of file From 77bea6214a7573a0a53376d2847267246067c4b5 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 19 Sep 2024 14:45:03 -0400 Subject: [PATCH 2/7] Add comments --- russh-util/src/future.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/russh-util/src/future.rs b/russh-util/src/future.rs index 8ed93378..00afd93f 100644 --- a/russh-util/src/future.rs +++ b/russh-util/src/future.rs @@ -1,3 +1,5 @@ +/// This file is a copy of the `ReusableBoxFuture` type from the `tokio-util` crate. + use std::alloc::Layout; use std::fmt; use std::future::{self, Future}; From e1e9e8ca871d8cadb582fe693121d9ed7a195ade Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 19 Sep 2024 14:45:38 -0400 Subject: [PATCH 3/7] Create runtime.rs --- russh-util/src/runtime.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 russh-util/src/runtime.rs diff --git a/russh-util/src/runtime.rs b/russh-util/src/runtime.rs new file mode 100644 index 00000000..341e1c42 --- /dev/null +++ b/russh-util/src/runtime.rs @@ -0,0 +1,16 @@ +use std::future::Future; + +pub fn spawn(future: F) +where + F: Future + 'static + Send, +{ + #[cfg(target_arch = "wasm32")] + { + wasm_bindgen_futures::spawn_local(future); + } + + #[cfg(not(target_arch = "wasm32"))] + { + tokio::spawn(future); + } +} From c9667a6958a25780772fa1c765b558cd30822ea4 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 19 Sep 2024 14:50:56 -0400 Subject: [PATCH 4/7] allow dead code --- russh-util/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/russh-util/src/lib.rs b/russh-util/src/lib.rs index 09725d85..1c59e05e 100644 --- a/russh-util/src/lib.rs +++ b/russh-util/src/lib.rs @@ -1,4 +1,4 @@ -#![deny(dead_code)] // To be removed when full wasm support is added. +#![allow(dead_code)] // To be removed when full wasm support is added. pub mod future; pub mod runtime; From 0d7ad5cd63aa3b68d2b1a75ceee3e21404edf477 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 19 Sep 2024 14:51:45 -0400 Subject: [PATCH 5/7] fmt --- russh-util/src/future.rs | 3 +-- russh-util/src/lib.rs | 2 +- russh-util/src/time.rs | 13 +++++-------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/russh-util/src/future.rs b/russh-util/src/future.rs index 00afd93f..dab5f8d3 100644 --- a/russh-util/src/future.rs +++ b/russh-util/src/future.rs @@ -1,5 +1,4 @@ /// This file is a copy of the `ReusableBoxFuture` type from the `tokio-util` crate. - use std::alloc::Layout; use std::fmt; use std::future::{self, Future}; @@ -156,4 +155,4 @@ impl O> Drop for CallOnDrop { let f = unsafe { ManuallyDrop::take(&mut self.f) }; f(); } -} \ No newline at end of file +} diff --git a/russh-util/src/lib.rs b/russh-util/src/lib.rs index 1c59e05e..a92f8aee 100644 --- a/russh-util/src/lib.rs +++ b/russh-util/src/lib.rs @@ -2,4 +2,4 @@ pub mod future; pub mod runtime; -pub mod time; \ No newline at end of file +pub mod time; diff --git a/russh-util/src/time.rs b/russh-util/src/time.rs index 5fc4ebb9..80b7a864 100644 --- a/russh-util/src/time.rs +++ b/russh-util/src/time.rs @@ -1,14 +1,9 @@ - - #[cfg(not(target_arch = "wasm32"))] pub use std_time::Instant; - #[cfg(target_arch = "wasm32")] pub use wasm::Instant; - - mod wasm { #[derive(Debug, Clone, Copy)] pub struct Instant { @@ -23,13 +18,15 @@ mod wasm { } pub fn duration_since(&self, earlier: Instant) -> std::time::Duration { - (self.inner - earlier.inner).to_std().expect("Duration is negative") + (self.inner - earlier.inner) + .to_std() + .expect("Duration is negative") } } } mod std_time { - + #[derive(Debug, Clone, Copy)] pub struct Instant { inner: std::time::Instant, @@ -46,4 +43,4 @@ mod std_time { self.inner.duration_since(earlier.inner) } } -} \ No newline at end of file +} From 25c0847d32c9bf8a0bff60cc529418196eadf114 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 19 Sep 2024 14:54:43 -0400 Subject: [PATCH 6/7] Update time.rs --- russh-util/src/time.rs | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/russh-util/src/time.rs b/russh-util/src/time.rs index 80b7a864..b26d0982 100644 --- a/russh-util/src/time.rs +++ b/russh-util/src/time.rs @@ -1,5 +1,5 @@ #[cfg(not(target_arch = "wasm32"))] -pub use std_time::Instant; +pub use std::time::Instant; #[cfg(target_arch = "wasm32")] pub use wasm::Instant; @@ -24,23 +24,3 @@ mod wasm { } } } - -mod std_time { - - #[derive(Debug, Clone, Copy)] - pub struct Instant { - inner: std::time::Instant, - } - - impl Instant { - pub fn now() -> Self { - Instant { - inner: std::time::Instant::now(), - } - } - - pub fn duration_since(&self, earlier: Instant) -> std::time::Duration { - self.inner.duration_since(earlier.inner) - } - } -} From 686cd892ae1d27b21db7a28defcd69cc97086fa1 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 20 Sep 2024 09:29:24 -0400 Subject: [PATCH 7/7] update cargo.toml --- russh-util/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/russh-util/Cargo.toml b/russh-util/Cargo.toml index 17f92d7d..e44901c2 100644 --- a/russh-util/Cargo.toml +++ b/russh-util/Cargo.toml @@ -2,6 +2,7 @@ name = "russh-util" version = "0.1.0" edition = "2021" +rust-version = "1.65" [dependencies] chrono = "0.4.38"