From 3f1d7167fac6282b716cbbaa8428a2ccdfcc8177 Mon Sep 17 00:00:00 2001 From: Pauan Date: Fri, 2 Feb 2018 15:33:36 -1000 Subject: [PATCH 01/40] Initial work on adding in Promises --- Cargo.toml | 5 +-- src/lib.rs | 5 +++ src/webapi/error.rs | 7 ++++ src/webcore/mod.rs | 1 + src/webcore/promise.rs | 79 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/webcore/promise.rs diff --git a/Cargo.toml b/Cargo.toml index 5a037971..71d429f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,14 +18,15 @@ serde = { version = "1", optional = true } serde_json = { version = "1", optional = true } clippy = { version = "0.0", optional = true } stdweb-derive = { path = "stdweb-derive" } +futures = { version = "0.1.18", optional = true } [dev-dependencies] serde_json = "1" serde_derive = "1" [features] -default = ["serde", "serde_json"] -dev = ["serde", "serde_json", "clippy"] +default = ["serde", "serde_json", "futures"] +dev = ["serde", "serde_json", "futures", "clippy"] serde-support = ["serde", "serde_json"] nightly = [] web_test = [] diff --git a/src/lib.rs b/src/lib.rs index c8d58981..111356b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,9 @@ extern crate stdweb_internal_macros; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] pub use stdweb_internal_macros::js_export; +#[cfg(any(test, feature = "futures"))] +extern crate futures; + #[macro_use] extern crate stdweb_derive; @@ -128,6 +131,8 @@ pub use webcore::instance_of::InstanceOf; pub use webcore::reference_type::ReferenceType; pub use webcore::serialization::JsSerialize; +pub use webcore::promise::PromiseFuture; + #[cfg(feature = "serde")] /// A module with serde-related APIs. pub mod serde { diff --git a/src/webapi/error.rs b/src/webapi/error.rs index 9b7ff295..546b765a 100644 --- a/src/webapi/error.rs +++ b/src/webapi/error.rs @@ -36,6 +36,13 @@ pub trait IError: ReferenceType { #[reference(instance_of = "Error")] pub struct Error( Reference ); +impl Error { + #[inline] + pub fn new( description: &str ) -> Self { + js!( return new Error( @{description} ); ).try_into().unwrap() + } +} + // Error specification: // https://www.ecma-international.org/ecma-262/6.0/#sec-error-objects diff --git a/src/webcore/mod.rs b/src/webcore/mod.rs index 5b989657..4b56463f 100644 --- a/src/webcore/mod.rs +++ b/src/webcore/mod.rs @@ -15,6 +15,7 @@ pub mod unsafe_typed_array; pub mod once; pub mod instance_of; pub mod reference_type; +pub mod promise; #[cfg(feature = "nightly")] pub mod void { diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs new file mode 100644 index 00000000..bad46932 --- /dev/null +++ b/src/webcore/promise.rs @@ -0,0 +1,79 @@ +use std; +use std::marker::PhantomData; +use {Value, Once}; +use unstable::{TryInto, TryFrom}; +use webcore::value::ConversionError; +use web::error::Error as JSError; +use std::error::Error; +use futures::{future, Future, Poll}; +use futures::sync::oneshot::channel; + + +// TODO split this into Promise and PromiseFuture +pub struct PromiseFuture< A > { + promise: Value, + future: Box< Future< Item = A, Error = JSError > >, + phantom: PhantomData< A >, +} + + +impl< A > std::fmt::Debug for PromiseFuture< A > { + fn fmt( &self, formatter: &mut std::fmt::Formatter ) -> std::fmt::Result { + write!( formatter, "PromiseFuture {:?}", self.promise ) + } +} + + +impl< A > Future for PromiseFuture< A > { + type Item = A; + type Error = JSError; + + fn poll (&mut self ) -> Poll { + self.future.poll() + } +} + + +// TODO this should probably check instanceof Promise +impl< A: TryFrom< Value > > TryFrom< Value > for PromiseFuture< A > where A: 'static, A::Error: Error { + type Error = ConversionError; + + fn try_from( v: Value ) -> Result< Self, Self::Error > { + match v { + Value::Reference( ref r ) => { + let ( sender, receiver ) = channel(); + + let callback = |value: Value, success: bool| { + let value: Result< A, JSError > = if success { + let value: Result< A, A::Error > = value.try_into(); + value.map_err( |e| JSError::new( e.description() ) ) + } else { + let value: Result< JSError, ConversionError > = value.try_into(); + value.map_err( |e| JSError::new( e.description() ) ).and_then( Err ) + }; + + // TODO is this correct ? + match sender.send( value ) { + Ok( _ ) => {}, + Err( _ ) => {}, + }; + }; + + Ok( PromiseFuture { + promise: js! { + var callback = @{Once( callback )}; + + return @{r}.then( function (value) { + callback( value, true ); + }, function (value) { + callback( value, false ); + } ); + }, + future: Box::new( receiver.map_err( |x| JSError::new( x.description() ) ).and_then( future::result ) ), + phantom: PhantomData, + } ) + }, + other => Err( ConversionError::Custom( format!( "Expected Promise but got: {:?}", other ) ) ), + } + } +} \ No newline at end of file From 1b00354a6d8b57273c11f2ea7fc03327f8a2cb43 Mon Sep 17 00:00:00 2001 From: Pauan Date: Fri, 2 Feb 2018 15:46:53 -1000 Subject: [PATCH 02/40] Fixing minor nit --- src/webcore/promise.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index bad46932..e792f20f 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -28,7 +28,7 @@ impl< A > Future for PromiseFuture< A > { type Item = A; type Error = JSError; - fn poll (&mut self ) -> Poll { + fn poll (&mut self ) -> Poll< Self::Item, Self::Error > { self.future.poll() } } From f605bab6e5b778a0e92419f4b8fff4fa77288ed8 Mon Sep 17 00:00:00 2001 From: Pauan Date: Fri, 2 Feb 2018 17:18:19 -1000 Subject: [PATCH 03/40] Major refactoring --- src/lib.rs | 2 +- src/webcore/promise.rs | 122 ++++++++++++++++++++++++++--------------- 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 111356b9..26210ff0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,7 @@ pub use webcore::instance_of::InstanceOf; pub use webcore::reference_type::ReferenceType; pub use webcore::serialization::JsSerialize; -pub use webcore::promise::PromiseFuture; +pub use webcore::promise::{Promise, PromiseFuture}; #[cfg(feature = "serde")] /// A module with serde-related APIs. diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index e792f20f..8a859141 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -1,29 +1,94 @@ use std; +use std::error::Error; use std::marker::PhantomData; -use {Value, Once}; -use unstable::{TryInto, TryFrom}; -use webcore::value::ConversionError; +use webcore::once::Once; +use webcore::value::{Value, Reference, ConversionError}; +use webcore::try_from::{TryInto, TryFrom}; use web::error::Error as JSError; -use std::error::Error; use futures::{future, Future, Poll}; use futures::sync::oneshot::channel; -// TODO split this into Promise and PromiseFuture +pub struct Promise( Reference ); + +reference_boilerplate! { + Promise, + instanceof Promise +} + +impl Promise { + pub fn done< A, B >( &self, callback: B ) -> Self + where A: TryFrom< Value >, + A::Error: Error, + B: FnOnce( Result< A, JSError > ) + 'static { + + let callback = |value: Value, success: bool| { + let value: Result< A, JSError > = if success { + let value: Result< A, A::Error > = value.try_into(); + value.map_err( |e| JSError::new( e.description() ) ) + } else { + let value: Result< JSError, ConversionError > = value.try_into(); + value.map_err( |e| JSError::new( e.description() ) ).and_then( Err ) + }; + + callback( value ); + }; + + (js! { + var callback = @{Once( callback )}; + + return @{self}.then( function (value) { + callback( value, true ); + }, function (value) { + callback( value, false ); + } ); + }).try_into().unwrap() + } + + // We can't use the IntoFuture trait because Promise doesn't have a type argument + // TODO explain more why we can't use the IntoFuture trait + pub fn to_future< A >( &self ) -> PromiseFuture< A > + where A: TryFrom< Value > + 'static, + A::Error: Error { + + let ( sender, receiver ) = channel(); + + PromiseFuture { + promise: self.done( |value| { + // TODO is this correct ? + match sender.send( value ) { + Ok( _ ) => {}, + Err( _ ) => {}, + }; + } ), + future: Box::new( receiver.map_err( |x| JSError::new( x.description() ) ).and_then( future::result ) ), + phantom: PhantomData, + } + } +} + + pub struct PromiseFuture< A > { - promise: Value, + promise: Promise, future: Box< Future< Item = A, Error = JSError > >, phantom: PhantomData< A >, } +/*impl< A > PromiseFuture< A > { + pub fn new< B >( callback: B ) -> Self + where B: FnOnce( FnOnce( A ), FnOnce( JSError ) ) { + js!( return new Promise( @{Once( callback )} ); ).try_from().unwrap() + } +}*/ + + impl< A > std::fmt::Debug for PromiseFuture< A > { fn fmt( &self, formatter: &mut std::fmt::Formatter ) -> std::fmt::Result { write!( formatter, "PromiseFuture {:?}", self.promise ) } } - impl< A > Future for PromiseFuture< A > { type Item = A; type Error = JSError; @@ -33,47 +98,14 @@ impl< A > Future for PromiseFuture< A > { } } +impl< A > TryFrom< Value > for PromiseFuture< A > + where A: TryFrom< Value > + 'static, + A::Error: Error { -// TODO this should probably check instanceof Promise -impl< A: TryFrom< Value > > TryFrom< Value > for PromiseFuture< A > where A: 'static, A::Error: Error { type Error = ConversionError; fn try_from( v: Value ) -> Result< Self, Self::Error > { - match v { - Value::Reference( ref r ) => { - let ( sender, receiver ) = channel(); - - let callback = |value: Value, success: bool| { - let value: Result< A, JSError > = if success { - let value: Result< A, A::Error > = value.try_into(); - value.map_err( |e| JSError::new( e.description() ) ) - } else { - let value: Result< JSError, ConversionError > = value.try_into(); - value.map_err( |e| JSError::new( e.description() ) ).and_then( Err ) - }; - - // TODO is this correct ? - match sender.send( value ) { - Ok( _ ) => {}, - Err( _ ) => {}, - }; - }; - - Ok( PromiseFuture { - promise: js! { - var callback = @{Once( callback )}; - - return @{r}.then( function (value) { - callback( value, true ); - }, function (value) { - callback( value, false ); - } ); - }, - future: Box::new( receiver.map_err( |x| JSError::new( x.description() ) ).and_then( future::result ) ), - phantom: PhantomData, - } ) - }, - other => Err( ConversionError::Custom( format!( "Expected Promise but got: {:?}", other ) ) ), - } + let promise: Promise = v.try_into()?; + Ok( promise.to_future() ) } } \ No newline at end of file From ebaaa3563d06c37d4beb606153a89b9d36acdb8c Mon Sep 17 00:00:00 2001 From: Pauan Date: Fri, 2 Feb 2018 21:32:33 -1000 Subject: [PATCH 04/40] Changing the done method to no longer return a new Promise --- src/webcore/promise.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 8a859141..101d4292 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -17,7 +17,7 @@ reference_boilerplate! { } impl Promise { - pub fn done< A, B >( &self, callback: B ) -> Self + pub fn done< A, B >( &self, callback: B ) where A: TryFrom< Value >, A::Error: Error, B: FnOnce( Result< A, JSError > ) + 'static { @@ -34,15 +34,16 @@ impl Promise { callback( value ); }; - (js! { + js! { @(no_return) var callback = @{Once( callback )}; - return @{self}.then( function (value) { + // TODO don't swallow any errors thrown inside callback + @{self}.then( function (value) { callback( value, true ); }, function (value) { callback( value, false ); } ); - }).try_into().unwrap() + } } // We can't use the IntoFuture trait because Promise doesn't have a type argument @@ -53,14 +54,15 @@ impl Promise { let ( sender, receiver ) = channel(); + self.done( |value| { + // TODO is this correct ? + match sender.send( value ) { + Ok( _ ) => {}, + Err( _ ) => {}, + }; + } ); + PromiseFuture { - promise: self.done( |value| { - // TODO is this correct ? - match sender.send( value ) { - Ok( _ ) => {}, - Err( _ ) => {}, - }; - } ), future: Box::new( receiver.map_err( |x| JSError::new( x.description() ) ).and_then( future::result ) ), phantom: PhantomData, } @@ -69,7 +71,6 @@ impl Promise { pub struct PromiseFuture< A > { - promise: Promise, future: Box< Future< Item = A, Error = JSError > >, phantom: PhantomData< A >, } @@ -85,7 +86,7 @@ pub struct PromiseFuture< A > { impl< A > std::fmt::Debug for PromiseFuture< A > { fn fmt( &self, formatter: &mut std::fmt::Formatter ) -> std::fmt::Result { - write!( formatter, "PromiseFuture {:?}", self.promise ) + write!( formatter, "PromiseFuture" ) } } From f2ab6d84b00ca5d3efb5ac8e9a77312fe17ff38d Mon Sep 17 00:00:00 2001 From: Pauan Date: Fri, 2 Feb 2018 21:42:43 -1000 Subject: [PATCH 05/40] Adding in promisify method --- src/webcore/promise.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 101d4292..ac34fd4a 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -17,6 +17,10 @@ reference_boilerplate! { } impl Promise { + pub fn promisify( input: Value ) -> Promise { + js!( return Promise.resolve( @{input} ); ).try_into().unwrap() + } + pub fn done< A, B >( &self, callback: B ) where A: TryFrom< Value >, A::Error: Error, @@ -94,7 +98,7 @@ impl< A > Future for PromiseFuture< A > { type Item = A; type Error = JSError; - fn poll (&mut self ) -> Poll< Self::Item, Self::Error > { + fn poll( &mut self ) -> Poll< Self::Item, Self::Error > { self.future.poll() } } From 61cb604f1139ddbaa9e0f851c6669e9b8f6ad42c Mon Sep 17 00:00:00 2001 From: Pauan Date: Sat, 3 Feb 2018 11:12:23 -1000 Subject: [PATCH 06/40] Changing to une unsync::oneshot --- src/webcore/promise.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index ac34fd4a..312e0f7c 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -6,7 +6,7 @@ use webcore::value::{Value, Reference, ConversionError}; use webcore::try_from::{TryInto, TryFrom}; use web::error::Error as JSError; use futures::{future, Future, Poll}; -use futures::sync::oneshot::channel; +use futures::unsync::oneshot::channel; pub struct Promise( Reference ); From bc3809a964e2a8e5473ef5e5a0ccd86f260d1417 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 4 Feb 2018 10:07:39 -1000 Subject: [PATCH 07/40] Removing Box from PromiseFuture --- src/webcore/promise.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 312e0f7c..6cad046e 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -5,8 +5,8 @@ use webcore::once::Once; use webcore::value::{Value, Reference, ConversionError}; use webcore::try_from::{TryInto, TryFrom}; use web::error::Error as JSError; -use futures::{future, Future, Poll}; -use futures::unsync::oneshot::channel; +use futures::{Future, Poll, Async}; +use futures::unsync::oneshot::{Receiver, channel}; pub struct Promise( Reference ); @@ -67,7 +67,7 @@ impl Promise { } ); PromiseFuture { - future: Box::new( receiver.map_err( |x| JSError::new( x.description() ) ).and_then( future::result ) ), + future: receiver, phantom: PhantomData, } } @@ -75,7 +75,7 @@ impl Promise { pub struct PromiseFuture< A > { - future: Box< Future< Item = A, Error = JSError > >, + future: Receiver< Result< A, JSError > >, phantom: PhantomData< A >, } @@ -99,7 +99,12 @@ impl< A > Future for PromiseFuture< A > { type Error = JSError; fn poll( &mut self ) -> Poll< Self::Item, Self::Error > { - self.future.poll() + match self.future.poll() { + Ok( Async::Ready( Ok( a ) ) ) => Ok( Async::Ready( a ) ), + Ok( Async::Ready( Err( e ) ) ) => Err( e ), + Ok( Async::NotReady ) => Ok( Async::NotReady ), + Err( e ) => Err( JSError::new( e.description() ) ), + } } } From b6560753f12f879bcc793cb2b3b44dc1f0451457 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 4 Feb 2018 15:09:11 -1000 Subject: [PATCH 08/40] Initial work on the Promise Executor --- src/webcore/mod.rs | 1 + src/webcore/promise.rs | 43 ++++++++++++- src/webcore/promise_executor.rs | 111 ++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/webcore/promise_executor.rs diff --git a/src/webcore/mod.rs b/src/webcore/mod.rs index 4b56463f..07afb752 100644 --- a/src/webcore/mod.rs +++ b/src/webcore/mod.rs @@ -16,6 +16,7 @@ pub mod once; pub mod instance_of; pub mod reference_type; pub mod promise; +pub mod promise_executor; #[cfg(feature = "nightly")] pub mod void { diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 6cad046e..478b8b9c 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -7,6 +7,7 @@ use webcore::try_from::{TryInto, TryFrom}; use web::error::Error as JSError; use futures::{Future, Poll, Async}; use futures::unsync::oneshot::{Receiver, channel}; +use webcore::promise_executor::spawn; pub struct Promise( Reference ); @@ -80,6 +81,21 @@ pub struct PromiseFuture< A > { } +impl PromiseFuture< () > { + pub fn spawn< B >( future: B ) where + B: Future< Item = (), Error = JSError > + 'static { + + spawn( future.map_err( |e| { + // TODO better error handling + js! { @(no_return) + console.error( @{e} ); + } + + () + } ) ); + } +} + /*impl< A > PromiseFuture< A > { pub fn new< B >( callback: B ) -> Self where B: FnOnce( FnOnce( A ), FnOnce( JSError ) ) { @@ -118,4 +134,29 @@ impl< A > TryFrom< Value > for PromiseFuture< A > let promise: Promise = v.try_into()?; Ok( promise.to_future() ) } -} \ No newline at end of file +} + + +#[cfg(test)] +mod tests { + use webcore::promise::PromiseFuture; + use webcore::try_from::TryInto; + use futures::Future; + use webcore::value::Null; + + #[test] + fn wait() { + let future: PromiseFuture< Null > = js!( return new Promise( function ( success, failure ) { + setTimeout( function () { + success( null ); + }, 1000 ); + } ); ).try_into().unwrap(); + + PromiseFuture::spawn( future.map( |x| { + println!( "Timeout done! {:#?}", x ); + () + } ) ); + + //println!("{:#?}", future.wait()); + } +} diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs new file mode 100644 index 00000000..89ca6e91 --- /dev/null +++ b/src/webcore/promise_executor.rs @@ -0,0 +1,111 @@ +use futures::future::{Future, lazy, ExecuteError, Executor, IntoFuture}; +use futures::executor::{self, Notify, Spawn}; +use futures::Async; +use std::result::Result as StdResult; +use std::cell::{Cell, RefCell}; + + +struct SpawnedTask { + ref_count: Cell< usize >, + spawn: RefCell< Spawn< Box< Future + 'static > > >, +} + +impl SpawnedTask { + fn new< F >( future: F ) -> Self + where F: Future< Item = (), Error = () > + 'static { + Self { + ref_count: Cell::new( 1 ), + spawn: RefCell::new( executor::spawn( Box::new( future.fuse() ) + as Box< Future + 'static> ) ), + } + } + + fn execute_spawn( spawned_ptr: *const SpawnedTask ) { + let spawned = unsafe { &*spawned_ptr }; + + // This is probably suboptimal, as a resubmission of the same Task while it + // is being executed results in a panic. It is not entirely clear if a Task + // is allowed to do that, but I would expect that this is valid behavior, as + // the notification could happen while the Task is still executing, in a + // truly multi-threaded situation. So we probably have to deal with it here + // at some point too. This already happened in the IntervalStream, so that + // should be cleaned up then as well then. The easiest solution is to try to + // lock it instead and if it fails, increment a counter. The one that + // initially blocked the RefCell then just reexecutes the Task until the + // Task is finished or the counter reaches 0. + + if spawned.spawn.borrow_mut().poll_future_notify( &CORE, spawned_ptr as usize ) != Ok( Async::NotReady ) { + SpawnedTask::decrement_ref_count( spawned_ptr as usize ); + } + } + + fn decrement_ref_count( id: usize ) { + let count = { + let spawned_ptr = id as *const SpawnedTask; + let spawned = unsafe { &*spawned_ptr }; + let mut count = spawned.ref_count.get(); + count -= 1; + spawned.ref_count.set( count ); + println!("Drop {}", count); + count + }; + + if count == 0 { + let spawned_ptr = id as *mut SpawnedTask; + unsafe { Box::from_raw( spawned_ptr ) }; + } + } +} + + +static CORE: &Core = &Core; + +struct Core; + +impl< F > Executor< F > for Core where + F: Future< Item = (), Error = () > + 'static { + fn execute( &self, future: F ) -> StdResult< (), ExecuteError< F > > { + println!("Execute"); + + let spawned_ptr = Box::into_raw( Box::new( SpawnedTask::new( future ) ) ); + + SpawnedTask::execute_spawn( spawned_ptr ); + + println!("Execute End"); + + Ok( () ) + } +} + +impl Notify for Core { + fn notify( &self, spawned_id: usize ) { + println!("Notify"); + + let spawned_ptr = spawned_id as *const SpawnedTask; + + SpawnedTask::execute_spawn( spawned_ptr ); + + println!("Notify End"); + } + + fn clone_id( &self, id: usize ) -> usize { + let spawned_ptr = id as *const SpawnedTask; + let spawned = unsafe { &*spawned_ptr }; + let mut count = spawned.ref_count.get(); + count += 1; + spawned.ref_count.set( count ); + println!("Clone {}", count); + id + } + + fn drop_id( &self, id: usize ) { + SpawnedTask::decrement_ref_count( id ); + } +} + + +#[inline] +pub fn spawn< F >( future: F ) where + F: Future< Item = (), Error = () > + 'static { + CORE.execute( future ).ok(); +} \ No newline at end of file From 7cb66f992fc4277b012648e60284831dc740fa70 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 4 Feb 2018 15:56:36 -1000 Subject: [PATCH 09/40] Adding in promise example --- examples/promise/Cargo.toml | 8 ++++++++ examples/promise/README.md | 0 examples/promise/src/main.rs | 30 ++++++++++++++++++++++++++++++ src/webcore/promise_executor.rs | 2 +- 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 examples/promise/Cargo.toml create mode 100644 examples/promise/README.md create mode 100644 examples/promise/src/main.rs diff --git a/examples/promise/Cargo.toml b/examples/promise/Cargo.toml new file mode 100644 index 00000000..5ad4d05a --- /dev/null +++ b/examples/promise/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "promise" +version = "0.1.0" +authors = ["Pauan "] + +[dependencies] +stdweb = { path = "../.." } +futures = "0.1.18" \ No newline at end of file diff --git a/examples/promise/README.md b/examples/promise/README.md new file mode 100644 index 00000000..e69de29b diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs new file mode 100644 index 00000000..84bd089a --- /dev/null +++ b/examples/promise/src/main.rs @@ -0,0 +1,30 @@ +#[macro_use] +extern crate stdweb; +extern crate futures; + +use futures::Future; +use stdweb::unstable::{TryInto}; +use stdweb::{Null, PromiseFuture}; + + +fn sleep( ms: u32 ) -> PromiseFuture< Null > { + js!( return new Promise( function ( success, failure ) { + setTimeout( function () { + success( null ); + }, @{ms} ); + } ); ).try_into().unwrap() +} + + +fn main() { + stdweb::initialize(); + + PromiseFuture::spawn( + sleep( 5000 ).inspect( |_| println!( "Timeout 1 done!") ).join( + sleep( 5000 ).inspect( |_| println!( "Timeout 2 done!" ) ) ) + .and_then( |_| + sleep( 5000 ).inspect( |_| println!( "Timeout 3 done!") ) ).map( |_| () ) + ); + + stdweb::event_loop(); +} diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index 89ca6e91..ed3ed04d 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -1,4 +1,4 @@ -use futures::future::{Future, lazy, ExecuteError, Executor, IntoFuture}; +use futures::future::{Future, ExecuteError, Executor}; use futures::executor::{self, Notify, Spawn}; use futures::Async; use std::result::Result as StdResult; From f416d9659c17d4b9d6403b9be1cf8186b16e36e0 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 4 Feb 2018 16:57:04 -1000 Subject: [PATCH 10/40] Various cleanup --- examples/promise/src/main.rs | 13 ++++++++++--- src/webapi/error.rs | 1 + src/webcore/promise.rs | 33 +++++++-------------------------- src/webcore/promise_executor.rs | 10 ---------- 4 files changed, 18 insertions(+), 39 deletions(-) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index 84bd089a..055fd803 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -16,14 +16,21 @@ fn sleep( ms: u32 ) -> PromiseFuture< Null > { } +fn log( a: &str ) { + js! { @(no_return) + console.log( @{a} ); + } +} + + fn main() { stdweb::initialize(); PromiseFuture::spawn( - sleep( 5000 ).inspect( |_| println!( "Timeout 1 done!") ).join( - sleep( 5000 ).inspect( |_| println!( "Timeout 2 done!" ) ) ) + sleep( 5000 ).inspect( |_| log( "Timeout 1 done!") ).join( + sleep( 5000 ).inspect( |_| log( "Timeout 2 done!" ) ) ) .and_then( |_| - sleep( 5000 ).inspect( |_| println!( "Timeout 3 done!") ) ).map( |_| () ) + sleep( 5000 ).inspect( |_| log( "Timeout 3 done!") ) ).map( |_| () ) ); stdweb::event_loop(); diff --git a/src/webapi/error.rs b/src/webapi/error.rs index 546b765a..6391a989 100644 --- a/src/webapi/error.rs +++ b/src/webapi/error.rs @@ -37,6 +37,7 @@ pub trait IError: ReferenceType { pub struct Error( Reference ); impl Error { + /// #[inline] pub fn new( description: &str ) -> Self { js!( return new Error( @{description} ); ).try_into().unwrap() diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 478b8b9c..b564d2c2 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -10,6 +10,7 @@ use futures::unsync::oneshot::{Receiver, channel}; use webcore::promise_executor::spawn; +/// pub struct Promise( Reference ); reference_boilerplate! { @@ -18,10 +19,12 @@ reference_boilerplate! { } impl Promise { + /// pub fn promisify( input: Value ) -> Promise { js!( return Promise.resolve( @{input} ); ).try_into().unwrap() } + /// pub fn done< A, B >( &self, callback: B ) where A: TryFrom< Value >, A::Error: Error, @@ -51,6 +54,7 @@ impl Promise { } } + /// // We can't use the IntoFuture trait because Promise doesn't have a type argument // TODO explain more why we can't use the IntoFuture trait pub fn to_future< A >( &self ) -> PromiseFuture< A > @@ -75,6 +79,7 @@ impl Promise { } +/// pub struct PromiseFuture< A > { future: Receiver< Result< A, JSError > >, phantom: PhantomData< A >, @@ -82,6 +87,7 @@ pub struct PromiseFuture< A > { impl PromiseFuture< () > { + /// pub fn spawn< B >( future: B ) where B: Future< Item = (), Error = JSError > + 'static { @@ -134,29 +140,4 @@ impl< A > TryFrom< Value > for PromiseFuture< A > let promise: Promise = v.try_into()?; Ok( promise.to_future() ) } -} - - -#[cfg(test)] -mod tests { - use webcore::promise::PromiseFuture; - use webcore::try_from::TryInto; - use futures::Future; - use webcore::value::Null; - - #[test] - fn wait() { - let future: PromiseFuture< Null > = js!( return new Promise( function ( success, failure ) { - setTimeout( function () { - success( null ); - }, 1000 ); - } ); ).try_into().unwrap(); - - PromiseFuture::spawn( future.map( |x| { - println!( "Timeout done! {:#?}", x ); - () - } ) ); - - //println!("{:#?}", future.wait()); - } -} +} \ No newline at end of file diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index ed3ed04d..0cfa9682 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -46,7 +46,6 @@ impl SpawnedTask { let mut count = spawned.ref_count.get(); count -= 1; spawned.ref_count.set( count ); - println!("Drop {}", count); count }; @@ -65,27 +64,19 @@ struct Core; impl< F > Executor< F > for Core where F: Future< Item = (), Error = () > + 'static { fn execute( &self, future: F ) -> StdResult< (), ExecuteError< F > > { - println!("Execute"); - let spawned_ptr = Box::into_raw( Box::new( SpawnedTask::new( future ) ) ); SpawnedTask::execute_spawn( spawned_ptr ); - println!("Execute End"); - Ok( () ) } } impl Notify for Core { fn notify( &self, spawned_id: usize ) { - println!("Notify"); - let spawned_ptr = spawned_id as *const SpawnedTask; SpawnedTask::execute_spawn( spawned_ptr ); - - println!("Notify End"); } fn clone_id( &self, id: usize ) -> usize { @@ -94,7 +85,6 @@ impl Notify for Core { let mut count = spawned.ref_count.get(); count += 1; spawned.ref_count.set( count ); - println!("Clone {}", count); id } From 5c479e41c09723b4924db1716afcf53d40ff7dc1 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 4 Feb 2018 18:11:47 -1000 Subject: [PATCH 11/40] Adding in documentation --- src/webapi/error.rs | 2 + src/webcore/promise.rs | 88 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/webapi/error.rs b/src/webapi/error.rs index 6391a989..d5481752 100644 --- a/src/webapi/error.rs +++ b/src/webapi/error.rs @@ -37,7 +37,9 @@ pub trait IError: ReferenceType { pub struct Error( Reference ); impl Error { + /// Creates a new `Error` with the specified `description`. /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) #[inline] pub fn new( description: &str ) -> Self { js!( return new Error( @{description} ); ).try_into().unwrap() diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index b564d2c2..25f1ca55 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -10,7 +10,11 @@ use futures::unsync::oneshot::{Receiver, channel}; use webcore::promise_executor::spawn; +/// A `Promise` object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. /// +/// In most situations you shouldn't use this, use [`PromiseFuture`](struct.PromiseFuture.html) instead. +/// +/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) pub struct Promise( Reference ); reference_boilerplate! { @@ -19,12 +23,62 @@ reference_boilerplate! { } impl Promise { + /// This function should rarely be needed, use [`PromiseFuture`](struct.PromiseFuture.html) instead. + /// + /// This function is used for two different purposes: + /// + /// 1. If you have a JavaScript value which is not a `Promise` but you want to wrap it in a `Promise`, you can use `Promise::promisify(value)`. + /// In this situation, it is recommended to use [`futures::future::ok`](https://docs.rs/futures/0.1.18/futures/future/fn.ok.html) instead. + /// + /// 2. If you have a JavaScript value which is a Promise-like object (it has a `then` method) but it isn't a true `Promise`, you can use + /// `Promise::promisify(value)` to convert it into a true `Promise`. This situation is rare, but it can happen if you are using a Promise + /// library such as jQuery or Bluebird. + /// + /// # Examples + /// + /// Convert a JavaScript value to a `Promise`: + /// + /// ```rust + /// Promise::promisify(js!( return 5; )) + /// ``` /// + /// Convert a Promise-like object to a `Promise`: + /// + /// ```rust + /// // jQuery Promise + /// Promise::promisify(js!( return $.get("test.php"); )) + /// + /// // Bluebird Promise + /// Promise::promisify(js!( return bluebird_promise.timeout(1000); )) + /// ``` + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) pub fn promisify( input: Value ) -> Promise { js!( return Promise.resolve( @{input} ); ).try_into().unwrap() } + /// This method is usually not needed, use [`PromiseFuture`](struct.PromiseFuture.html) instead. + /// + /// When the `Promise` either succeeds or fails, it calls the `callback` with the result. + /// + /// It does not wait for the `Promise` to succeed / fail (it does not block the thread). + /// + /// The `callback` is guaranteed to be called asynchronously even if the `Promise` is already succeeded / failed. /// + /// If the `Promise` never succeeds / fails then the `callback` will never be called, and it will leak memory. + /// + /// # Examples + /// + /// ```rust + /// promise.done(|result| { + /// match result { + /// Ok(success) => { ... }, + /// Err(error) => { ... }, + /// } + /// }); + /// ``` + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) pub fn done< A, B >( &self, callback: B ) where A: TryFrom< Value >, A::Error: Error, @@ -54,7 +108,15 @@ impl Promise { } } + /// This method should rarely be needed, instead use [`value.try_into()`](unstable/trait.TryInto.html) to convert directly from a [`Value`](enum.Value.html) into a [`PromiseFuture`](struct.PromiseFuture.html). /// + /// This method converts the `Promise` into a [`PromiseFuture`](struct.PromiseFuture.html), so that it can be used as a Rust [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html). + /// + /// # Examples + /// + /// ```rust + /// promise.to_future().map(|x| x + 1) + /// ``` // We can't use the IntoFuture trait because Promise doesn't have a type argument // TODO explain more why we can't use the IntoFuture trait pub fn to_future< A >( &self ) -> PromiseFuture< A > @@ -79,7 +141,17 @@ impl Promise { } +/// This allows you to use a JavaScript [`Promise`](struct.Promise.html) as if it is a Rust [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html). +/// +/// The preferred way to create a `PromiseFuture` is to use [`value.try_into()`](unstable/trait.TryInto.html) on a JavaScript [`Value`](enum.Value.html). /// +/// # Examples +/// +/// Convert a JavaScript `Promise` into a `PromiseFuture`: +/// +/// ```rust +/// let future: PromiseFuture = js!( return Promise.resolve("foo"); ).try_into().unwrap(); +/// ``` pub struct PromiseFuture< A > { future: Receiver< Result< A, JSError > >, phantom: PhantomData< A >, @@ -87,7 +159,21 @@ pub struct PromiseFuture< A > { impl PromiseFuture< () > { + /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and immediately returns. This does not block the current thread. + /// + /// If the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) errors it will print the error to the console. /// + /// This function is normally called once in `main`, it is usually not needed to call it multiple times. + /// + /// # Examples + /// + /// ```rust + /// fn main() { + /// create_some_future() + /// .inspect(|x| println!("Future finished: {:#?}", x)) + /// .spawn() + /// } + /// ``` pub fn spawn< B >( future: B ) where B: Future< Item = (), Error = JSError > + 'static { @@ -140,4 +226,4 @@ impl< A > TryFrom< Value > for PromiseFuture< A > let promise: Promise = v.try_into()?; Ok( promise.to_future() ) } -} \ No newline at end of file +} From 244701af59219485a20d92bf961285f30f8936a8 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 4 Feb 2018 18:12:15 -1000 Subject: [PATCH 12/40] Removing some unused code --- src/webcore/promise.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 25f1ca55..887226f8 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -157,7 +157,6 @@ pub struct PromiseFuture< A > { phantom: PhantomData< A >, } - impl PromiseFuture< () > { /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and immediately returns. This does not block the current thread. /// @@ -188,14 +187,6 @@ impl PromiseFuture< () > { } } -/*impl< A > PromiseFuture< A > { - pub fn new< B >( callback: B ) -> Self - where B: FnOnce( FnOnce( A ), FnOnce( JSError ) ) { - js!( return new Promise( @{Once( callback )} ); ).try_from().unwrap() - } -}*/ - - impl< A > std::fmt::Debug for PromiseFuture< A > { fn fmt( &self, formatter: &mut std::fmt::Formatter ) -> std::fmt::Result { write!( formatter, "PromiseFuture" ) From 2a3385f76d7f075810819fb300d48ce98f97d7d2 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 4 Feb 2018 20:40:02 -1000 Subject: [PATCH 13/40] Renaming JSError to Error --- src/webcore/promise.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 887226f8..6ebee0b7 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -1,10 +1,10 @@ use std; -use std::error::Error; +use std::error::Error as _Error; use std::marker::PhantomData; use webcore::once::Once; use webcore::value::{Value, Reference, ConversionError}; use webcore::try_from::{TryInto, TryFrom}; -use web::error::Error as JSError; +use web::error::Error; use futures::{Future, Poll, Async}; use futures::unsync::oneshot::{Receiver, channel}; use webcore::promise_executor::spawn; @@ -81,16 +81,16 @@ impl Promise { /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) pub fn done< A, B >( &self, callback: B ) where A: TryFrom< Value >, - A::Error: Error, - B: FnOnce( Result< A, JSError > ) + 'static { + A::Error: std::error::Error, + B: FnOnce( Result< A, Error > ) + 'static { let callback = |value: Value, success: bool| { - let value: Result< A, JSError > = if success { + let value: Result< A, Error > = if success { let value: Result< A, A::Error > = value.try_into(); - value.map_err( |e| JSError::new( e.description() ) ) + value.map_err( |e| Error::new( e.description() ) ) } else { - let value: Result< JSError, ConversionError > = value.try_into(); - value.map_err( |e| JSError::new( e.description() ) ).and_then( Err ) + let value: Result< Error, ConversionError > = value.try_into(); + value.map_err( |e| Error::new( e.description() ) ).and_then( Err ) }; callback( value ); @@ -121,7 +121,7 @@ impl Promise { // TODO explain more why we can't use the IntoFuture trait pub fn to_future< A >( &self ) -> PromiseFuture< A > where A: TryFrom< Value > + 'static, - A::Error: Error { + A::Error: std::error::Error { let ( sender, receiver ) = channel(); @@ -153,7 +153,7 @@ impl Promise { /// let future: PromiseFuture = js!( return Promise.resolve("foo"); ).try_into().unwrap(); /// ``` pub struct PromiseFuture< A > { - future: Receiver< Result< A, JSError > >, + future: Receiver< Result< A, Error > >, phantom: PhantomData< A >, } @@ -174,7 +174,7 @@ impl PromiseFuture< () > { /// } /// ``` pub fn spawn< B >( future: B ) where - B: Future< Item = (), Error = JSError > + 'static { + B: Future< Item = (), Error = Error > + 'static { spawn( future.map_err( |e| { // TODO better error handling @@ -195,21 +195,21 @@ impl< A > std::fmt::Debug for PromiseFuture< A > { impl< A > Future for PromiseFuture< A > { type Item = A; - type Error = JSError; + type Error = Error; fn poll( &mut self ) -> Poll< Self::Item, Self::Error > { match self.future.poll() { Ok( Async::Ready( Ok( a ) ) ) => Ok( Async::Ready( a ) ), Ok( Async::Ready( Err( e ) ) ) => Err( e ), Ok( Async::NotReady ) => Ok( Async::NotReady ), - Err( e ) => Err( JSError::new( e.description() ) ), + Err( e ) => Err( Error::new( e.description() ) ), } } } impl< A > TryFrom< Value > for PromiseFuture< A > where A: TryFrom< Value > + 'static, - A::Error: Error { + A::Error: std::error::Error { type Error = ConversionError; From eb30017311f6218c1ce709bc5f8071ed2658f6a8 Mon Sep 17 00:00:00 2001 From: Pauan Date: Mon, 5 Feb 2018 12:42:49 -1000 Subject: [PATCH 14/40] Adding in copyright license and attribution --- src/webcore/promise_executor.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index 0cfa9682..a4f0bf69 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -1,3 +1,31 @@ +//! This code was originally written by @CryZe +//! https://github.com/CryZe/stdweb-io/blob/9b8429e2452a699e8280cca50ea48f7e6af30c41/src/core.rs +//! +//! It is provided under the following license: +//! +//! MIT License +//! +//! Copyright (c) 2017 Christopher Serr +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in all +//! copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. + + use futures::future::{Future, ExecuteError, Executor}; use futures::executor::{self, Notify, Spawn}; use futures::Async; @@ -98,4 +126,4 @@ impl Notify for Core { pub fn spawn< F >( future: F ) where F: Future< Item = (), Error = () > + 'static { CORE.execute( future ).ok(); -} \ No newline at end of file +} From a256a72af6a75460e528730a8ec429961e53763d Mon Sep 17 00:00:00 2001 From: Pauan Date: Mon, 5 Feb 2018 12:58:51 -1000 Subject: [PATCH 15/40] Adding in links to the official spec --- src/webcore/promise.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 6ebee0b7..bfe28925 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -15,6 +15,7 @@ use webcore::promise_executor::spawn; /// In most situations you shouldn't use this, use [`PromiseFuture`](struct.PromiseFuture.html) instead. /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +// https://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects pub struct Promise( Reference ); reference_boilerplate! { @@ -53,6 +54,9 @@ impl Promise { /// ``` /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) + // https://www.ecma-international.org/ecma-262/6.0/#sec-promise.resolve + // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-resolve-functions + // https://www.ecma-international.org/ecma-262/6.0/#sec-promiseresolvethenablejob pub fn promisify( input: Value ) -> Promise { js!( return Promise.resolve( @{input} ); ).try_into().unwrap() } @@ -79,6 +83,7 @@ impl Promise { /// ``` /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) + // https://www.ecma-international.org/ecma-262/6.0/#sec-performpromisethen pub fn done< A, B >( &self, callback: B ) where A: TryFrom< Value >, A::Error: std::error::Error, From 73fd2a6d26cec856d79a554b8c07c66e5a808740 Mon Sep 17 00:00:00 2001 From: Pauan Date: Mon, 5 Feb 2018 13:07:36 -1000 Subject: [PATCH 16/40] Fixing erroneous documentation --- src/webcore/promise.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index bfe28925..691cd0c7 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -173,9 +173,10 @@ impl PromiseFuture< () > { /// /// ```rust /// fn main() { - /// create_some_future() - /// .inspect(|x| println!("Future finished: {:#?}", x)) - /// .spawn() + /// PromiseFuture::spawn( + /// create_some_future() + /// .inspect(|x| println!("Future finished: {:#?}", x)) + /// ) /// } /// ``` pub fn spawn< B >( future: B ) where From d598941925ecb62d3d741cf52eb151173c863210 Mon Sep 17 00:00:00 2001 From: Pauan Date: Mon, 5 Feb 2018 13:08:09 -1000 Subject: [PATCH 17/40] Fixing minor nit --- src/webcore/promise.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 691cd0c7..1f0b52ad 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -163,7 +163,7 @@ pub struct PromiseFuture< A > { } impl PromiseFuture< () > { - /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and immediately returns. This does not block the current thread. + /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and then immediately returns. This does not block the current thread. /// /// If the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) errors it will print the error to the console. /// @@ -176,7 +176,7 @@ impl PromiseFuture< () > { /// PromiseFuture::spawn( /// create_some_future() /// .inspect(|x| println!("Future finished: {:#?}", x)) - /// ) + /// ); /// } /// ``` pub fn spawn< B >( future: B ) where From 13bebf89c1a3a3e3eda86d66138cf8b32cc8cc01 Mon Sep 17 00:00:00 2001 From: Pauan Date: Mon, 5 Feb 2018 13:34:11 -1000 Subject: [PATCH 18/40] Adding in spawn_print function --- src/webcore/promise.rs | 69 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 1f0b52ad..395072ff 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -163,23 +163,33 @@ pub struct PromiseFuture< A > { } impl PromiseFuture< () > { - /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and then immediately returns. This does not block the current thread. - /// - /// If the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) errors it will print the error to the console. + /// This is identical to [`spawn`](#method.spawn), except that if the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) errors it will print the error to the console. See the documentation of [`spawn`](#method.spawn) for more details. /// - /// This function is normally called once in `main`, it is usually not needed to call it multiple times. + /// If you want to handle all errors yourself, then use [`spawn`](#method.spawn), but if you simply want to print the errors to the console, then use `spawn_print`. /// /// # Examples /// + /// Asynchronously run a future in `main`: + /// /// ```rust /// fn main() { - /// PromiseFuture::spawn( + /// PromiseFuture::spawn_print( + /// create_some_future() + /// ); + /// } + /// ``` + /// + /// Inspect the output value of the future: + /// + /// ```rust + /// fn main() { + /// PromiseFuture::spawn_print( /// create_some_future() /// .inspect(|x| println!("Future finished: {:#?}", x)) /// ); /// } /// ``` - pub fn spawn< B >( future: B ) where + pub fn spawn_print< B >( future: B ) where B: Future< Item = (), Error = Error > + 'static { spawn( future.map_err( |e| { @@ -191,6 +201,53 @@ impl PromiseFuture< () > { () } ) ); } + + /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and then immediately returns. This does not block the current thread. + /// + /// This function is normally called once in `main`, it is usually not needed to call it multiple times. + /// + /// Because the error happens asynchronously, the only way to catch it is to use a [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) method, such as [`map_err`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map_err). + /// + /// And the only way to retrieve the value of the future is to use the various [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) methods, such as [`map`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map) or [`inspect`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.inspect). + /// + /// # Examples + /// + /// Asynchronously run a future in `main`: + /// + /// ```rust + /// fn main() { + /// PromiseFuture::spawn( + /// create_some_future() + /// ); + /// } + /// ``` + /// + /// Inspect the output value of the future: + /// + /// ```rust + /// fn main() { + /// PromiseFuture::spawn( + /// create_some_future() + /// .inspect(|x| println!("Future finished: {:#?}", x)) + /// ); + /// } + /// ``` + /// + /// Catch errors and handle them: + /// + /// ```rust + /// fn main() { + /// PromiseFuture::spawn( + /// create_some_future() + /// .map_err(|e| handle_error_somehow(e)) + /// ); + /// } + /// ``` + #[inline] + pub fn spawn< B >( future: B ) where + B: Future< Item = (), Error = () > + 'static { + spawn( future ); + } } impl< A > std::fmt::Debug for PromiseFuture< A > { From 1ab6c2c8a9610adc44c8ff0c028b88447319dd0c Mon Sep 17 00:00:00 2001 From: Pauan Date: Mon, 5 Feb 2018 13:34:32 -1000 Subject: [PATCH 19/40] Changing the promise example to use spawn_print --- examples/promise/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index 055fd803..827f81e9 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -26,7 +26,7 @@ fn log( a: &str ) { fn main() { stdweb::initialize(); - PromiseFuture::spawn( + PromiseFuture::spawn_print( sleep( 5000 ).inspect( |_| log( "Timeout 1 done!") ).join( sleep( 5000 ).inspect( |_| log( "Timeout 2 done!" ) ) ) .and_then( |_| From 56eafa1a5086b2f19a86afd7e98d83f036a086b8 Mon Sep 17 00:00:00 2001 From: Pauan Date: Tue, 6 Feb 2018 14:17:50 -1000 Subject: [PATCH 20/40] Removing PromiseFuture::spawn_print --- examples/promise/src/main.rs | 8 +++-- src/webapi/error.rs | 10 +++++++ src/webcore/promise.rs | 57 ++++++++---------------------------- 3 files changed, 29 insertions(+), 46 deletions(-) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index 827f81e9..a62e8bd0 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -4,6 +4,7 @@ extern crate futures; use futures::Future; use stdweb::unstable::{TryInto}; +use stdweb::web::error::Error; use stdweb::{Null, PromiseFuture}; @@ -26,11 +27,14 @@ fn log( a: &str ) { fn main() { stdweb::initialize(); - PromiseFuture::spawn_print( + PromiseFuture::spawn( sleep( 5000 ).inspect( |_| log( "Timeout 1 done!") ).join( sleep( 5000 ).inspect( |_| log( "Timeout 2 done!" ) ) ) .and_then( |_| - sleep( 5000 ).inspect( |_| log( "Timeout 3 done!") ) ).map( |_| () ) + sleep( 5000 ).inspect( |_| log( "Timeout 3 done!") ) ) + .and_then( |_| + futures::future::err( Error::new( "Testing error!" ) ) ) + .map_err( |e| e.print() ) ); stdweb::event_loop(); diff --git a/src/webapi/error.rs b/src/webapi/error.rs index d5481752..546399bf 100644 --- a/src/webapi/error.rs +++ b/src/webapi/error.rs @@ -44,6 +44,16 @@ impl Error { pub fn new( description: &str ) -> Self { js!( return new Error( @{description} ); ).try_into().unwrap() } + + /// Prints the `Error` to the console (this prints the error's description and also its stack trace). + /// + /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Console/error) + #[inline] + pub fn print( &self ) { + js! { @(no_return) + console.error( @{self} ); + } + } } // Error specification: diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 395072ff..b5a366ac 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -163,61 +163,29 @@ pub struct PromiseFuture< A > { } impl PromiseFuture< () > { - /// This is identical to [`spawn`](#method.spawn), except that if the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) errors it will print the error to the console. See the documentation of [`spawn`](#method.spawn) for more details. + /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and then immediately returns. + /// This does not block the current thread. The only way to retrieve the value of the future is to use the various + /// [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) methods, such as + /// [`map`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map) or + /// [`inspect`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.inspect). /// - /// If you want to handle all errors yourself, then use [`spawn`](#method.spawn), but if you simply want to print the errors to the console, then use `spawn_print`. + /// This function requires you to handle all errors yourself. Because the errors happen asynchronously, the only way to catch them is + /// to use a [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) method, such as + /// [`map_err`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map_err). /// - /// # Examples - /// - /// Asynchronously run a future in `main`: - /// - /// ```rust - /// fn main() { - /// PromiseFuture::spawn_print( - /// create_some_future() - /// ); - /// } - /// ``` - /// - /// Inspect the output value of the future: - /// - /// ```rust - /// fn main() { - /// PromiseFuture::spawn_print( - /// create_some_future() - /// .inspect(|x| println!("Future finished: {:#?}", x)) - /// ); - /// } - /// ``` - pub fn spawn_print< B >( future: B ) where - B: Future< Item = (), Error = Error > + 'static { - - spawn( future.map_err( |e| { - // TODO better error handling - js! { @(no_return) - console.error( @{e} ); - } - - () - } ) ); - } - - /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and then immediately returns. This does not block the current thread. + /// It is very common to want to print the errors to the console. You can do that by using `.map_err(|e| e.print())` /// /// This function is normally called once in `main`, it is usually not needed to call it multiple times. /// - /// Because the error happens asynchronously, the only way to catch it is to use a [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) method, such as [`map_err`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map_err). - /// - /// And the only way to retrieve the value of the future is to use the various [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) methods, such as [`map`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map) or [`inspect`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.inspect). - /// /// # Examples /// - /// Asynchronously run a future in `main`: + /// Asynchronously run a future in `main`, printing any errors to the console: /// /// ```rust /// fn main() { /// PromiseFuture::spawn( /// create_some_future() + /// .map_err(|e| e.print()) /// ); /// } /// ``` @@ -229,11 +197,12 @@ impl PromiseFuture< () > { /// PromiseFuture::spawn( /// create_some_future() /// .inspect(|x| println!("Future finished: {:#?}", x)) + /// .map_err(|e| e.print()) /// ); /// } /// ``` /// - /// Catch errors and handle them: + /// Catch errors and handle them yourself: /// /// ```rust /// fn main() { From 405b3b6e1b53358cff64d69026560cd029db6213 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Wed, 7 Feb 2018 02:43:01 +0100 Subject: [PATCH 21/40] Fix Resubmission Panic in Executor The Task Executor panicked when you resubmit a future through its task notifier while it is being executed higher up in the stack. This is now fixed by using a resubmission counter that acts like a queue where each submission will queue up one execution, and if it can be executed, the token is consumed and additional executions of other submissions in the queue are attempted. --- src/webcore/promise_executor.rs | 119 ++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 50 deletions(-) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index a4f0bf69..e932f5a4 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -1,31 +1,3 @@ -//! This code was originally written by @CryZe -//! https://github.com/CryZe/stdweb-io/blob/9b8429e2452a699e8280cca50ea48f7e6af30c41/src/core.rs -//! -//! It is provided under the following license: -//! -//! MIT License -//! -//! Copyright (c) 2017 Christopher Serr -//! -//! Permission is hereby granted, free of charge, to any person obtaining a copy -//! of this software and associated documentation files (the "Software"), to deal -//! in the Software without restriction, including without limitation the rights -//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//! copies of the Software, and to permit persons to whom the Software is -//! furnished to do so, subject to the following conditions: -//! -//! The above copyright notice and this permission notice shall be included in all -//! copies or substantial portions of the Software. -//! -//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//! SOFTWARE. - - use futures::future::{Future, ExecuteError, Executor}; use futures::executor::{self, Notify, Spawn}; use futures::Async; @@ -35,6 +7,7 @@ use std::cell::{Cell, RefCell}; struct SpawnedTask { ref_count: Cell< usize >, + resubmission_count: Cell< usize >, spawn: RefCell< Spawn< Box< Future + 'static > > >, } @@ -43,34 +16,74 @@ impl SpawnedTask { where F: Future< Item = (), Error = () > + 'static { Self { ref_count: Cell::new( 1 ), + resubmission_count: Cell::new( 0 ), spawn: RefCell::new( executor::spawn( Box::new( future.fuse() ) as Box< Future + 'static> ) ), } } - fn execute_spawn( spawned_ptr: *const SpawnedTask ) { - let spawned = unsafe { &*spawned_ptr }; - - // This is probably suboptimal, as a resubmission of the same Task while it - // is being executed results in a panic. It is not entirely clear if a Task - // is allowed to do that, but I would expect that this is valid behavior, as - // the notification could happen while the Task is still executing, in a - // truly multi-threaded situation. So we probably have to deal with it here - // at some point too. This already happened in the IntervalStream, so that - // should be cleaned up then as well then. The easiest solution is to try to - // lock it instead and if it fails, increment a counter. The one that - // initially blocked the RefCell then just reexecutes the Task until the - // Task is finished or the counter reaches 0. - - if spawned.spawn.borrow_mut().poll_future_notify( &CORE, spawned_ptr as usize ) != Ok( Async::NotReady ) { - SpawnedTask::decrement_ref_count( spawned_ptr as usize ); + unsafe fn execute_spawn( spawned_ptr: *const SpawnedTask ) { + let spawned = &*spawned_ptr; + + // Queue up the task for execution. + spawned + .resubmission_count + .set( spawned.resubmission_count.get() + 1 ); + + loop { + // Here we try to take an execution token from the queue and execute + // the future. This may not be possible, as the future may already + // be executed somewhere higher up in the stack. We know there is at + // least one execution scheduled, so it's styled more like a + // do-while loop. + + // The usage of the lock needs to be contained in a scope that is + // separate from the decrement_ref_count, as that can deallocate the + // whole spawned task, causing a segfault when the RefCell is trying + // to release its borrow. That's why the poll_future_notify call is + // contained inside the map. + + let result = spawned + .spawn + .try_borrow_mut() + .map( |mut s| s.poll_future_notify( &CORE, spawned_ptr as usize ) ); + + if let Ok( result ) = result { + // We were able to successfully execute the future, allowing us + // to dequeue one resubmission token. + spawned + .resubmission_count + .set( spawned.resubmission_count.get() - 1 ); + + if result != Ok( Async::NotReady ) { + SpawnedTask::decrement_ref_count( spawned_ptr as usize ); + + // Return out early. The whole object might be deallocated + // at this point, so it would be very dangerous to touch + // anything else. + return; + } + + if spawned.resubmission_count.get() == 0 { + // Looks like there is no additional executions queued up. + // We can end the execution loop here. + return; + } + } else { + // We failed to execute the Task as it is already being executed + // higher up in the stack. We don't consume our execution token, + // and just leave it for the Task execution higher up to + // consume. We can't do anything anymore, so we yield execution + // back to the caller. + return; + } } } - fn decrement_ref_count( id: usize ) { + unsafe fn decrement_ref_count( id: usize ) { let count = { let spawned_ptr = id as *const SpawnedTask; - let spawned = unsafe { &*spawned_ptr }; + let spawned = &*spawned_ptr; let mut count = spawned.ref_count.get(); count -= 1; spawned.ref_count.set( count ); @@ -79,7 +92,7 @@ impl SpawnedTask { if count == 0 { let spawned_ptr = id as *mut SpawnedTask; - unsafe { Box::from_raw( spawned_ptr ) }; + Box::from_raw( spawned_ptr ); } } } @@ -94,7 +107,9 @@ impl< F > Executor< F > for Core where fn execute( &self, future: F ) -> StdResult< (), ExecuteError< F > > { let spawned_ptr = Box::into_raw( Box::new( SpawnedTask::new( future ) ) ); - SpawnedTask::execute_spawn( spawned_ptr ); + unsafe { + SpawnedTask::execute_spawn( spawned_ptr ); + } Ok( () ) } @@ -104,7 +119,9 @@ impl Notify for Core { fn notify( &self, spawned_id: usize ) { let spawned_ptr = spawned_id as *const SpawnedTask; - SpawnedTask::execute_spawn( spawned_ptr ); + unsafe { + SpawnedTask::execute_spawn( spawned_ptr ); + } } fn clone_id( &self, id: usize ) -> usize { @@ -117,7 +134,9 @@ impl Notify for Core { } fn drop_id( &self, id: usize ) { - SpawnedTask::decrement_ref_count( id ); + unsafe { + SpawnedTask::decrement_ref_count( id ); + } } } From 8bd5ba78fbded3ab8cf6ff9087bb862373e7991f Mon Sep 17 00:00:00 2001 From: Pauan Date: Tue, 6 Feb 2018 20:52:55 -1000 Subject: [PATCH 22/40] Minor refactoring of promise_executor.rs --- src/webcore/promise_executor.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index e932f5a4..893652eb 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -69,6 +69,7 @@ impl SpawnedTask { // We can end the execution loop here. return; } + } else { // We failed to execute the Task as it is already being executed // higher up in the stack. We don't consume our execution token, @@ -80,6 +81,15 @@ impl SpawnedTask { } } + unsafe fn increment_ref_count( id: usize ) -> usize { + let spawned_ptr = id as *const SpawnedTask; + let spawned = &*spawned_ptr; + let mut count = spawned.ref_count.get(); + count += 1; + spawned.ref_count.set( count ); + id + } + unsafe fn decrement_ref_count( id: usize ) { let count = { let spawned_ptr = id as *const SpawnedTask; @@ -92,6 +102,8 @@ impl SpawnedTask { if count == 0 { let spawned_ptr = id as *mut SpawnedTask; + + // This causes the SpawnedTask to be dropped Box::from_raw( spawned_ptr ); } } @@ -125,12 +137,9 @@ impl Notify for Core { } fn clone_id( &self, id: usize ) -> usize { - let spawned_ptr = id as *const SpawnedTask; - let spawned = unsafe { &*spawned_ptr }; - let mut count = spawned.ref_count.get(); - count += 1; - spawned.ref_count.set( count ); - id + unsafe { + SpawnedTask::increment_ref_count( id ) + } } fn drop_id( &self, id: usize ) { From a344b2e7c3083ea337cd0dbc50ea90df21b188c9 Mon Sep 17 00:00:00 2001 From: Pauan Date: Wed, 7 Feb 2018 20:59:55 -1000 Subject: [PATCH 23/40] Adding in tests for the Executor, and also fixing a deadlock with the Executor --- examples/promise/src/main.rs | 58 +++++++++++++++++++++++++++++++++ src/webcore/promise_executor.rs | 42 ++++++++++++------------ 2 files changed, 79 insertions(+), 21 deletions(-) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index a62e8bd0..bbc28891 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -24,9 +24,67 @@ fn log( a: &str ) { } +struct MyFuture { + count: u32, + receiver: futures::unsync::oneshot::Receiver< () >, +} + +impl MyFuture { + fn new() -> Self { + let ( sender, receiver ) = futures::unsync::oneshot::channel(); + + let callback = || { + log( "setTimeout done" ); + sender.send( () ).unwrap(); + }; + + js! { @(no_return) + setTimeout( @{stdweb::Once( callback )}, 1000 ); + } + + Self { + count: 0, + receiver, + } + } +} + +impl Future for MyFuture { + type Item = u32; + type Error = (); + + fn poll( &mut self ) -> futures::Poll< Self::Item, Self::Error > { + self.count += 1; + + let task = futures::task::current(); + + log( "Task notification 1" ); + task.notify(); + + log( "Task notification 2" ); + task.notify(); + + log( "Task notification done" ); + + match self.receiver.poll() { + Ok( futures::Async::Ready( () ) ) => Ok( futures::Async::Ready( self.count ) ), + Ok( futures::Async::NotReady ) => Ok( futures::Async::NotReady ), + Err( _ ) => Err( () ), + } + } +} + + fn main() { stdweb::initialize(); + PromiseFuture::spawn( + MyFuture::new().map( |x| { + log( &format!( "MyFuture count: {}", x ) ); + () + } ) + ); + PromiseFuture::spawn( sleep( 5000 ).inspect( |_| log( "Timeout 1 done!") ).join( sleep( 5000 ).inspect( |_| log( "Timeout 2 done!" ) ) ) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index 893652eb..10200dac 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -3,6 +3,7 @@ use futures::executor::{self, Notify, Spawn}; use futures::Async; use std::result::Result as StdResult; use std::cell::{Cell, RefCell}; +use Once; struct SpawnedTask { @@ -30,7 +31,9 @@ impl SpawnedTask { .resubmission_count .set( spawned.resubmission_count.get() + 1 ); - loop { + unsafe fn run( spawned_ptr: *const SpawnedTask ) { + let spawned = &*spawned_ptr; + // Here we try to take an execution token from the queue and execute // the future. This may not be possible, as the future may already // be executed somewhere higher up in the stack. We know there is at @@ -55,30 +58,27 @@ impl SpawnedTask { .resubmission_count .set( spawned.resubmission_count.get() - 1 ); - if result != Ok( Async::NotReady ) { + if let Ok( Async::NotReady ) = result { + // If there are more queued executions, then we execute them on the next event tick. + // This is necessary because the Future might be waiting for an event on the event loop. + if spawned.resubmission_count.get() != 0 { + let callback = move || run( spawned_ptr ); + + // TODO setTimeout isn't available in all JavaScript environments + js! { @(no_return) + setTimeout( @{Once( callback )}, 0 ); + } + } + + } else { + // The whole object might be deallocated at this point, so + // it would be very dangerous to touch anything else. SpawnedTask::decrement_ref_count( spawned_ptr as usize ); - - // Return out early. The whole object might be deallocated - // at this point, so it would be very dangerous to touch - // anything else. - return; } - - if spawned.resubmission_count.get() == 0 { - // Looks like there is no additional executions queued up. - // We can end the execution loop here. - return; - } - - } else { - // We failed to execute the Task as it is already being executed - // higher up in the stack. We don't consume our execution token, - // and just leave it for the Task execution higher up to - // consume. We can't do anything anymore, so we yield execution - // back to the caller. - return; } } + + run( spawned_ptr ); } unsafe fn increment_ref_count( id: usize ) -> usize { From 560bcc101ea6658efd81ffe32fbe37da199bc3cc Mon Sep 17 00:00:00 2001 From: Pauan Date: Wed, 7 Feb 2018 22:16:40 -1000 Subject: [PATCH 24/40] Fixing memory unsafety --- examples/promise/src/main.rs | 20 ++++++++++---------- src/webcore/promise_executor.rs | 22 ++++++++++++++++++---- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index bbc28891..132721da 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -35,11 +35,16 @@ impl MyFuture { let callback = || { log( "setTimeout done" ); - sender.send( () ).unwrap(); + + log( &format!("Sending {:#?}", sender.send( () ) ) ); }; + log( "setTimeout started" ); + js! { @(no_return) - setTimeout( @{stdweb::Once( callback )}, 1000 ); + setTimeout( function () { + @{stdweb::Once( callback )}(); + }, 1000 ); } Self { @@ -58,14 +63,9 @@ impl Future for MyFuture { let task = futures::task::current(); - log( "Task notification 1" ); task.notify(); - - log( "Task notification 2" ); task.notify(); - log( "Task notification done" ); - match self.receiver.poll() { Ok( futures::Async::Ready( () ) ) => Ok( futures::Async::Ready( self.count ) ), Ok( futures::Async::NotReady ) => Ok( futures::Async::NotReady ), @@ -86,10 +86,10 @@ fn main() { ); PromiseFuture::spawn( - sleep( 5000 ).inspect( |_| log( "Timeout 1 done!") ).join( - sleep( 5000 ).inspect( |_| log( "Timeout 2 done!" ) ) ) + sleep( 2000 ).inspect( |_| log( "Timeout 1 done!") ).join( + sleep( 2000 ).inspect( |_| log( "Timeout 2 done!" ) ) ) .and_then( |_| - sleep( 5000 ).inspect( |_| log( "Timeout 3 done!") ) ) + sleep( 1000 ).inspect( |_| log( "Timeout 3 done!") ) ) .and_then( |_| futures::future::err( Error::new( "Testing error!" ) ) ) .map_err( |e| e.print() ) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index 10200dac..e5efc8cb 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -2,11 +2,13 @@ use futures::future::{Future, ExecuteError, Executor}; use futures::executor::{self, Notify, Spawn}; use futures::Async; use std::result::Result as StdResult; +use std::rc::Rc; use std::cell::{Cell, RefCell}; use Once; struct SpawnedTask { + is_alive: Rc< Cell< bool > >, ref_count: Cell< usize >, resubmission_count: Cell< usize >, spawn: RefCell< Spawn< Box< Future + 'static > > >, @@ -16,6 +18,7 @@ impl SpawnedTask { fn new< F >( future: F ) -> Self where F: Future< Item = (), Error = () > + 'static { Self { + is_alive: Rc::new( Cell::new( true ) ), ref_count: Cell::new( 1 ), resubmission_count: Cell::new( 0 ), spawn: RefCell::new( executor::spawn( Box::new( future.fuse() ) @@ -62,11 +65,20 @@ impl SpawnedTask { // If there are more queued executions, then we execute them on the next event tick. // This is necessary because the Future might be waiting for an event on the event loop. if spawned.resubmission_count.get() != 0 { - let callback = move || run( spawned_ptr ); + let is_alive = spawned.is_alive.clone(); + + let callback = move || { + // Don't run the SpawnedTask if it's dropped + if is_alive.get() { + run( spawned_ptr ); + } + }; // TODO setTimeout isn't available in all JavaScript environments js! { @(no_return) - setTimeout( @{Once( callback )}, 0 ); + setTimeout( function () { + @{Once( callback )}(); + }, 0 ); } } @@ -103,8 +115,10 @@ impl SpawnedTask { if count == 0 { let spawned_ptr = id as *mut SpawnedTask; - // This causes the SpawnedTask to be dropped - Box::from_raw( spawned_ptr ); + // This causes the SpawnedTask to be dropped at the end of the scope + let task = Box::from_raw( spawned_ptr ); + + task.is_alive.set( false ); } } } From b100726529869ee700c4024896ef3895d310342e Mon Sep 17 00:00:00 2001 From: Pauan Date: Wed, 7 Feb 2018 22:47:08 -1000 Subject: [PATCH 25/40] Adding in some code testing for panics --- examples/promise/src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index 132721da..1894ff95 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -5,7 +5,7 @@ extern crate futures; use futures::Future; use stdweb::unstable::{TryInto}; use stdweb::web::error::Error; -use stdweb::{Null, PromiseFuture}; +use stdweb::{Null, Promise, PromiseFuture}; fn sleep( ms: u32 ) -> PromiseFuture< Null > { @@ -78,6 +78,13 @@ impl Future for MyFuture { fn main() { stdweb::initialize(); + let promise: Promise = js!( return Promise.resolve(null); ).try_into().unwrap(); + + promise.done( |result: Result< Null, Error >| { + log( &format!( "Promise result: {:#?}", result ) ); + panic!( "Testing panic!" ); + } ); + PromiseFuture::spawn( MyFuture::new().map( |x| { log( &format!( "MyFuture count: {}", x ) ); From 79d6c0b4830388aea893c621f176e11d89023b29 Mon Sep 17 00:00:00 2001 From: Pauan Date: Fri, 9 Feb 2018 02:15:59 -1000 Subject: [PATCH 26/40] Removing setTimeout from the Executor, it also now ignores multiple calls to task.notify() --- examples/promise/src/main.rs | 57 +++++++++++--- src/webcore/promise_executor.rs | 128 ++++++++++++++++++-------------- 2 files changed, 117 insertions(+), 68 deletions(-) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index 1894ff95..82e48f0b 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -25,12 +25,14 @@ fn log( a: &str ) { struct MyFuture { + polls: u32, count: u32, + done: bool, receiver: futures::unsync::oneshot::Receiver< () >, } impl MyFuture { - fn new() -> Self { + fn new( count: u32 ) -> Self { let ( sender, receiver ) = futures::unsync::oneshot::channel(); let callback = || { @@ -48,7 +50,9 @@ impl MyFuture { } Self { - count: 0, + polls: 0, + count: count, + done: false, receiver, } } @@ -59,17 +63,46 @@ impl Future for MyFuture { type Error = (); fn poll( &mut self ) -> futures::Poll< Self::Item, Self::Error > { - self.count += 1; + self.polls += 1; - let task = futures::task::current(); + if !self.done { + match self.receiver.poll() { + Ok( futures::Async::Ready( () ) ) => self.done = true, - task.notify(); - task.notify(); + Ok( futures::Async::NotReady ) => {}, - match self.receiver.poll() { - Ok( futures::Async::Ready( () ) ) => Ok( futures::Async::Ready( self.count ) ), - Ok( futures::Async::NotReady ) => Ok( futures::Async::NotReady ), - Err( _ ) => Err( () ), + Err( _ ) => self.done = true, + } + } + + if self.done { + if self.count == 0 { + Ok( futures::Async::Ready( self.polls ) ) + + } else { + self.count -= 1; + + let task = futures::task::current(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + + Ok( futures::Async::NotReady ) + } + + } else { + Ok( futures::Async::NotReady ) } } } @@ -86,9 +119,9 @@ fn main() { } ); PromiseFuture::spawn( - MyFuture::new().map( |x| { + MyFuture::new( 5 ).map( |x| { log( &format!( "MyFuture count: {}", x ) ); - () + assert_eq!( x, 7 ); } ) ); diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index e5efc8cb..5b45bce9 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -2,95 +2,112 @@ use futures::future::{Future, ExecuteError, Executor}; use futures::executor::{self, Notify, Spawn}; use futures::Async; use std::result::Result as StdResult; -use std::rc::Rc; use std::cell::{Cell, RefCell}; -use Once; +#[derive(Clone, Copy)] +enum TaskState { + Idle, + Running, + Queued, +} + struct SpawnedTask { - is_alive: Rc< Cell< bool > >, + state: Cell< TaskState >, ref_count: Cell< usize >, - resubmission_count: Cell< usize >, - spawn: RefCell< Spawn< Box< Future + 'static > > >, + spawn: RefCell< Spawn< Box< Future< Item = (), Error = () > + 'static > > >, } impl SpawnedTask { fn new< F >( future: F ) -> Self where F: Future< Item = (), Error = () > + 'static { Self { - is_alive: Rc::new( Cell::new( true ) ), + state: Cell::new( TaskState::Idle ), ref_count: Cell::new( 1 ), - resubmission_count: Cell::new( 0 ), - spawn: RefCell::new( executor::spawn( Box::new( future.fuse() ) - as Box< Future + 'static> ) ), + spawn: RefCell::new( executor::spawn( + Box::new( future ) as Box< Future< Item = (), Error = () > + 'static > + ) ), } } + // These are the situations that this algorithm needs to worry about: + // + // execute -> poll + // execute -> poll + poll + // execute -> poll + notify -> repoll + // execute -> poll + notify + notify -> repoll + // execute -> poll + notify -> repoll -> async notify -> repoll + // execute -> poll -> async notify -> repoll + // execute -> poll -> async notify -> repoll + notify -> repoll + // execute -> poll -> async notify -> repoll -> async notify -> repoll + // unsafe fn execute_spawn( spawned_ptr: *const SpawnedTask ) { let spawned = &*spawned_ptr; - // Queue up the task for execution. - spawned - .resubmission_count - .set( spawned.resubmission_count.get() + 1 ); - - unsafe fn run( spawned_ptr: *const SpawnedTask ) { - let spawned = &*spawned_ptr; - - // Here we try to take an execution token from the queue and execute - // the future. This may not be possible, as the future may already - // be executed somewhere higher up in the stack. We know there is at - // least one execution scheduled, so it's styled more like a - // do-while loop. - + loop { + // This is necessary because the Future might call `task.notify()` inside of `poll`. + // If that happens, we need to re-run `poll` again. So we use `state` to indicate + // whether we need to re-run `poll` or not. + spawned.state.set( TaskState::Running ); + + // Here we try to call `poll` on the future. This may not be possible, + // as the future may already be executed somewhere higher up in the stack. + // We know there is at least one execution scheduled, so it's styled more + // like a do-while loop. + // // The usage of the lock needs to be contained in a scope that is // separate from the decrement_ref_count, as that can deallocate the // whole spawned task, causing a segfault when the RefCell is trying // to release its borrow. That's why the poll_future_notify call is // contained inside the map. - let result = spawned .spawn .try_borrow_mut() .map( |mut s| s.poll_future_notify( &CORE, spawned_ptr as usize ) ); + // TODO test this if let Ok( result ) = result { - // We were able to successfully execute the future, allowing us - // to dequeue one resubmission token. - spawned - .resubmission_count - .set( spawned.resubmission_count.get() - 1 ); - if let Ok( Async::NotReady ) = result { - // If there are more queued executions, then we execute them on the next event tick. - // This is necessary because the Future might be waiting for an event on the event loop. - if spawned.resubmission_count.get() != 0 { - let is_alive = spawned.is_alive.clone(); - - let callback = move || { - // Don't run the SpawnedTask if it's dropped - if is_alive.get() { - run( spawned_ptr ); - } - }; - - // TODO setTimeout isn't available in all JavaScript environments - js! { @(no_return) - setTimeout( function () { - @{Once( callback )}(); - }, 0 ); - } + // The `poll` method called `task.notify()` so we have to re-run `poll` again. + if let TaskState::Queued = spawned.state.get() { + continue; + + } else { + // The Future isn't ready yet, but it will asynchronously + // call `task.notify()` when it is ready. + // + // When that happens, the `notify` function will call + // `execute_spawn` again. + spawned.state.set( TaskState::Idle ); } } else { + // This ensures that the Future will never be polled again. + spawned.state.set( TaskState::Running ); + // The whole object might be deallocated at this point, so // it would be very dangerous to touch anything else. SpawnedTask::decrement_ref_count( spawned_ptr as usize ); } } + + return; } + } + + unsafe fn notify( spawned_ptr: *const SpawnedTask ) { + let spawned = &*spawned_ptr; + + // This causes it to re-run `poll` again, even if `task.notify()` was called inside of `poll`. + // + // IMPORTANT: If the Future calls `notify` multiple times within `poll`, it will only re-run + // `poll` once! + let state = spawned.state.replace( TaskState::Queued ); - run( spawned_ptr ); + // This only happens when `task.notify()` is called asynchronously. + if let TaskState::Idle = state { + SpawnedTask::execute_spawn( spawned_ptr ); + } } unsafe fn increment_ref_count( id: usize ) -> usize { @@ -115,10 +132,11 @@ impl SpawnedTask { if count == 0 { let spawned_ptr = id as *mut SpawnedTask; - // This causes the SpawnedTask to be dropped at the end of the scope - let task = Box::from_raw( spawned_ptr ); + // This causes the SpawnedTask to be dropped at the end of the scope. + let spawned = Box::from_raw( spawned_ptr ); - task.is_alive.set( false ); + // This ensures that the Future will never be polled again. + spawned.state.set( TaskState::Running ); } } } @@ -143,10 +161,8 @@ impl< F > Executor< F > for Core where impl Notify for Core { fn notify( &self, spawned_id: usize ) { - let spawned_ptr = spawned_id as *const SpawnedTask; - unsafe { - SpawnedTask::execute_spawn( spawned_ptr ); + SpawnedTask::notify( spawned_id as *const SpawnedTask ); } } @@ -167,5 +183,5 @@ impl Notify for Core { #[inline] pub fn spawn< F >( future: F ) where F: Future< Item = (), Error = () > + 'static { - CORE.execute( future ).ok(); + CORE.execute( future ).unwrap(); } From 7c8bd261c33430c74e94b69839d48d59f7b5c827 Mon Sep 17 00:00:00 2001 From: Pauan Date: Fri, 9 Feb 2018 03:28:45 -1000 Subject: [PATCH 27/40] Renaming promisify to convert --- src/webcore/promise.rs | 43 ++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index b5a366ac..a73b1123 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -24,41 +24,52 @@ reference_boilerplate! { } impl Promise { + // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-resolve-functions + fn is_promise_like( input: &Value ) -> bool { + (js! { + var input = @{input}; + // This emulates the `Type(input) is Object` and `IsCallable(input.then)` ECMAScript abstract operations. + return Object( input ) === input && + typeof input.then === "function"; + }).try_into().unwrap() + } + /// This function should rarely be needed, use [`PromiseFuture`](struct.PromiseFuture.html) instead. /// - /// This function is used for two different purposes: + /// This function is needed if you have a JavaScript value which is a Promise-like object + /// (it has a `then` method) but it isn't a true `Promise`. /// - /// 1. If you have a JavaScript value which is not a `Promise` but you want to wrap it in a `Promise`, you can use `Promise::promisify(value)`. - /// In this situation, it is recommended to use [`futures::future::ok`](https://docs.rs/futures/0.1.18/futures/future/fn.ok.html) instead. + /// That situation is rare, but it can happen if you are using a Promise library such as jQuery or + /// Bluebird. /// - /// 2. If you have a JavaScript value which is a Promise-like object (it has a `then` method) but it isn't a true `Promise`, you can use - /// `Promise::promisify(value)` to convert it into a true `Promise`. This situation is rare, but it can happen if you are using a Promise - /// library such as jQuery or Bluebird. - /// - /// # Examples + /// In that situation you can use `Promise::convert(value)` to convert it into a true `Promise`. /// - /// Convert a JavaScript value to a `Promise`: + /// If the `input` isn't a Promise-like object then it returns `None`. /// - /// ```rust - /// Promise::promisify(js!( return 5; )) - /// ``` + /// # Examples /// /// Convert a Promise-like object to a `Promise`: /// /// ```rust /// // jQuery Promise - /// Promise::promisify(js!( return $.get("test.php"); )) + /// Promise::convert(js!( return $.get("test.php"); )) /// /// // Bluebird Promise - /// Promise::promisify(js!( return bluebird_promise.timeout(1000); )) + /// Promise::convert(js!( return bluebird_promise.timeout(1000); )) /// ``` /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) // https://www.ecma-international.org/ecma-262/6.0/#sec-promise.resolve // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-resolve-functions // https://www.ecma-international.org/ecma-262/6.0/#sec-promiseresolvethenablejob - pub fn promisify( input: Value ) -> Promise { - js!( return Promise.resolve( @{input} ); ).try_into().unwrap() + pub fn convert( input: Value ) -> Option< Self > { + // TODO this can probably be made more efficient + if Promise::is_promise_like( &input ) { + Some( js!( return Promise.resolve( @{input} ); ).try_into().unwrap() ) + + } else { + None + } } /// This method is usually not needed, use [`PromiseFuture`](struct.PromiseFuture.html) instead. From 410cca6e63c1bb4fdb05a073fa3ed8c2bea7816c Mon Sep 17 00:00:00 2001 From: Pauan Date: Fri, 9 Feb 2018 04:13:06 -1000 Subject: [PATCH 28/40] Adding in TODO note --- src/webcore/promise_executor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index 5b45bce9..a1dee8ba 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -14,6 +14,7 @@ enum TaskState { struct SpawnedTask { state: Cell< TaskState >, + // TODO use Rc instead ? ref_count: Cell< usize >, spawn: RefCell< Spawn< Box< Future< Item = (), Error = () > + 'static > > >, } From f8b0118c2072d23642c02ff8a8ad2d5c43aaad03 Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Fri, 9 Feb 2018 23:16:51 +0000 Subject: [PATCH 29/40] Alternative executor implementation --- src/webcore/promise_executor.rs | 183 ++++++++------------------------ 1 file changed, 44 insertions(+), 139 deletions(-) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index a1dee8ba..2964aed2 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -3,180 +3,85 @@ use futures::executor::{self, Notify, Spawn}; use futures::Async; use std::result::Result as StdResult; use std::cell::{Cell, RefCell}; - - -#[derive(Clone, Copy)] -enum TaskState { - Idle, - Running, - Queued, +use std::rc::Rc; +use webcore::once::Once; + +// This functionality should really be in libstd, because the implementation +// looks stupid. +unsafe fn clone_raw(ptr: *const T) -> Rc { + let result = Rc::from_raw(ptr); + ::std::mem::forget(result.clone()); + result } +// Typing this out is tedious +type BoxedFuture = Box< Future< Item = (), Error = () > + 'static >; + struct SpawnedTask { - state: Cell< TaskState >, - // TODO use Rc instead ? - ref_count: Cell< usize >, - spawn: RefCell< Spawn< Box< Future< Item = (), Error = () > + 'static > > >, + is_queued: Cell< bool >, + spawn: RefCell< Option< Spawn< BoxedFuture > > >, } impl SpawnedTask { - fn new< F >( future: F ) -> Self + fn new< F >( future: F ) -> Rc where F: Future< Item = (), Error = () > + 'static { - Self { - state: Cell::new( TaskState::Idle ), - ref_count: Cell::new( 1 ), - spawn: RefCell::new( executor::spawn( - Box::new( future ) as Box< Future< Item = (), Error = () > + 'static > - ) ), - } + Rc::new(Self { + is_queued: Cell::new( false ), + spawn: RefCell::new( Some( executor::spawn( + Box::new( future ) as BoxedFuture + ) ) ), + }) } - // These are the situations that this algorithm needs to worry about: - // - // execute -> poll - // execute -> poll + poll - // execute -> poll + notify -> repoll - // execute -> poll + notify + notify -> repoll - // execute -> poll + notify -> repoll -> async notify -> repoll - // execute -> poll -> async notify -> repoll - // execute -> poll -> async notify -> repoll + notify -> repoll - // execute -> poll -> async notify -> repoll -> async notify -> repoll - // - unsafe fn execute_spawn( spawned_ptr: *const SpawnedTask ) { - let spawned = &*spawned_ptr; - - loop { - // This is necessary because the Future might call `task.notify()` inside of `poll`. - // If that happens, we need to re-run `poll` again. So we use `state` to indicate - // whether we need to re-run `poll` or not. - spawned.state.set( TaskState::Running ); - - // Here we try to call `poll` on the future. This may not be possible, - // as the future may already be executed somewhere higher up in the stack. - // We know there is at least one execution scheduled, so it's styled more - // like a do-while loop. - // - // The usage of the lock needs to be contained in a scope that is - // separate from the decrement_ref_count, as that can deallocate the - // whole spawned task, causing a segfault when the RefCell is trying - // to release its borrow. That's why the poll_future_notify call is - // contained inside the map. - let result = spawned - .spawn - .try_borrow_mut() - .map( |mut s| s.poll_future_notify( &CORE, spawned_ptr as usize ) ); - - // TODO test this - if let Ok( result ) = result { - if let Ok( Async::NotReady ) = result { - // The `poll` method called `task.notify()` so we have to re-run `poll` again. - if let TaskState::Queued = spawned.state.get() { - continue; + fn poll(&self) { + // Clear `is_queued` flag + self.is_queued.set(false); - } else { - // The Future isn't ready yet, but it will asynchronously - // call `task.notify()` when it is ready. - // - // When that happens, the `notify` function will call - // `execute_spawn` again. - spawned.state.set( TaskState::Idle ); - } + let mut spawn = self.spawn.borrow_mut(); - } else { - // This ensures that the Future will never be polled again. - spawned.state.set( TaskState::Running ); - - // The whole object might be deallocated at this point, so - // it would be very dangerous to touch anything else. - SpawnedTask::decrement_ref_count( spawned_ptr as usize ); - } + // Take the future so that if we panic it gets dropped + if let Some(mut spawn_future) = spawn.take() { + if spawn_future.poll_future_notify( &&Core, self as *const _ as usize ) == Ok(Async::NotReady) { + // Future was not ready, so put it back + *spawn = Some(spawn_future); } - - return; - } - } - - unsafe fn notify( spawned_ptr: *const SpawnedTask ) { - let spawned = &*spawned_ptr; - - // This causes it to re-run `poll` again, even if `task.notify()` was called inside of `poll`. - // - // IMPORTANT: If the Future calls `notify` multiple times within `poll`, it will only re-run - // `poll` once! - let state = spawned.state.replace( TaskState::Queued ); - - // This only happens when `task.notify()` is called asynchronously. - if let TaskState::Idle = state { - SpawnedTask::execute_spawn( spawned_ptr ); } } - unsafe fn increment_ref_count( id: usize ) -> usize { - let spawned_ptr = id as *const SpawnedTask; - let spawned = &*spawned_ptr; - let mut count = spawned.ref_count.get(); - count += 1; - spawned.ref_count.set( count ); - id - } - - unsafe fn decrement_ref_count( id: usize ) { - let count = { - let spawned_ptr = id as *const SpawnedTask; - let spawned = &*spawned_ptr; - let mut count = spawned.ref_count.get(); - count -= 1; - spawned.ref_count.set( count ); - count - }; - - if count == 0 { - let spawned_ptr = id as *mut SpawnedTask; - - // This causes the SpawnedTask to be dropped at the end of the scope. - let spawned = Box::from_raw( spawned_ptr ); - - // This ensures that the Future will never be polled again. - spawned.state.set( TaskState::Running ); + fn notify( spawned: Rc ) { + // If not already queued + if !spawned.is_queued.replace(true) { + // Poll on next micro-task + js! { @(no_return) + Promise.resolve().then(function() { + @{ Once(move || spawned.poll()) }(); + }); + } } } } - -static CORE: &Core = &Core; - struct Core; impl< F > Executor< F > for Core where F: Future< Item = (), Error = () > + 'static { fn execute( &self, future: F ) -> StdResult< (), ExecuteError< F > > { - let spawned_ptr = Box::into_raw( Box::new( SpawnedTask::new( future ) ) ); - - unsafe { - SpawnedTask::execute_spawn( spawned_ptr ); - } - + SpawnedTask::notify( SpawnedTask::new( future ) ); Ok( () ) } } impl Notify for Core { fn notify( &self, spawned_id: usize ) { - unsafe { - SpawnedTask::notify( spawned_id as *const SpawnedTask ); - } + SpawnedTask::notify(unsafe { clone_raw(spawned_id as *const _) }) } fn clone_id( &self, id: usize ) -> usize { - unsafe { - SpawnedTask::increment_ref_count( id ) - } + unsafe { Rc::into_raw(clone_raw(id as *const SpawnedTask)) as usize } } fn drop_id( &self, id: usize ) { - unsafe { - SpawnedTask::decrement_ref_count( id ); - } + unsafe { Rc::from_raw(id as *const SpawnedTask) }; } } @@ -184,5 +89,5 @@ impl Notify for Core { #[inline] pub fn spawn< F >( future: F ) where F: Future< Item = (), Error = () > + 'static { - CORE.execute( future ).unwrap(); + Core.execute( future ).unwrap(); } From 75209659f6249bf24bff265a84ba4bf27802444e Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Fri, 9 Feb 2018 23:23:49 +0000 Subject: [PATCH 30/40] Reorder clearing of `is_queued` to optimize notifying finished tasks --- src/webcore/promise_executor.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index 2964aed2..94a3909f 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -34,13 +34,13 @@ impl SpawnedTask { } fn poll(&self) { - // Clear `is_queued` flag - self.is_queued.set(false); - let mut spawn = self.spawn.borrow_mut(); // Take the future so that if we panic it gets dropped if let Some(mut spawn_future) = spawn.take() { + // Clear `is_queued` flag + self.is_queued.set(false); + if spawn_future.poll_future_notify( &&Core, self as *const _ as usize ) == Ok(Async::NotReady) { // Future was not ready, so put it back *spawn = Some(spawn_future); From 61f91aac55113d89ffe7e5c89e92a8a9a5878a19 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sat, 10 Feb 2018 02:09:34 -1000 Subject: [PATCH 31/40] Adding in RefCell example --- examples/promise/src/main.rs | 159 +++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index 82e48f0b..599d2114 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -1,3 +1,161 @@ +/*#[cfg(target_arch = "wasm32")] +#[macro_use] +extern crate stdweb; +extern crate futures; + +#[cfg(not(target_arch = "wasm32"))] +extern crate tokio; + +use std::rc::Rc; +use std::cell::RefCell; +use futures::Future; +use futures::{Poll, Async}; +use futures::task::{current, Task}; + +#[cfg(target_arch = "wasm32")] +fn main() { + use stdweb::{PromiseFuture}; + + struct TaskA { + shared_state: Rc>, + task_b: Task, + } + + impl Future for TaskA { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + js! { console.log("Poll TaskA"); } + + let foo = self.shared_state.borrow_mut(); + + js! { console.log(@{format!("TaskA 1: {:#?}", foo)}); } + + self.task_b.notify(); + + js! { console.log(@{format!("TaskA 2: {:#?}", foo)}); } + + Ok(Async::NotReady) + } + } + + struct TaskB { + shared_state: Rc>, + initialized: bool, + } + + impl Future for TaskB { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + js! { console.log("Poll TaskB"); } + + if !self.initialized { + self.initialized = true; + + let task_b = current(); + + let foo = self.shared_state.borrow(); + + js! { console.log(@{format!("TaskB 1: {:#?}", foo)}); } + + PromiseFuture::spawn(TaskA { + shared_state: self.shared_state.clone(), + task_b: task_b, + }); + } + + let foo = self.shared_state.borrow(); + + js! { console.log(@{format!("TaskB 1: {:#?}", foo)}); } + + Ok(Async::NotReady) + } + } + + PromiseFuture::spawn(TaskB { + shared_state: Rc::new(RefCell::new(0)), + initialized: false + }); +} + + +#[cfg(not(target_arch = "wasm32"))] +fn main() { + use tokio::executor::current_thread; + + struct TaskA { + shared_state: Rc>, + task_b: Task, + } + + impl Future for TaskA { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + println!("Poll TaskA"); + + let foo = self.shared_state.borrow_mut(); + + println!("TaskA 1: {:#?}", foo); + + self.task_b.notify(); + + println!("TaskA 2: {:#?}", foo); + + Ok(Async::NotReady) + } + } + + struct TaskB { + shared_state: Rc>, + initialized: bool, + } + + impl Future for TaskB { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + println!("Poll TaskB"); + + if !self.initialized { + self.initialized = true; + + let task_b = current(); + + let foo = self.shared_state.borrow(); + + println!("TaskB 1: {:#?}", foo); + + current_thread::spawn(TaskA { + shared_state: self.shared_state.clone(), + task_b: task_b, + }); + } + + let foo = self.shared_state.borrow(); + + println!("TaskB 1: {:#?}", foo); + + Ok(Async::NotReady) + } + } + + current_thread::run(|_| { + current_thread::spawn(TaskB { + shared_state: Rc::new(RefCell::new(0)), + initialized: false + }); + }); +}*/ + + + + #[macro_use] extern crate stdweb; extern crate futures; @@ -137,3 +295,4 @@ fn main() { stdweb::event_loop(); } + From 50a456ecb9095dbdcdb9fc880aa3f6fd1069e25a Mon Sep 17 00:00:00 2001 From: Pauan Date: Sat, 10 Feb 2018 02:12:24 -1000 Subject: [PATCH 32/40] Fixing nits --- src/webcore/promise_executor.rs | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index 94a3909f..bc124a71 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -8,9 +8,9 @@ use webcore::once::Once; // This functionality should really be in libstd, because the implementation // looks stupid. -unsafe fn clone_raw(ptr: *const T) -> Rc { - let result = Rc::from_raw(ptr); - ::std::mem::forget(result.clone()); +unsafe fn clone_raw< T >( ptr: *const T ) -> Rc< T > { + let result = Rc::from_raw( ptr ); + ::std::mem::forget( result.clone() ); result } @@ -19,43 +19,43 @@ type BoxedFuture = Box< Future< Item = (), Error = () > + 'static >; struct SpawnedTask { is_queued: Cell< bool >, - spawn: RefCell< Option< Spawn< BoxedFuture > > >, + spawn: RefCell< Option< Spawn< BoxedFuture > > >, } impl SpawnedTask { - fn new< F >( future: F ) -> Rc + fn new< F >( future: F ) -> Rc< Self > where F: Future< Item = (), Error = () > + 'static { - Rc::new(Self { + Rc::new( Self { is_queued: Cell::new( false ), spawn: RefCell::new( Some( executor::spawn( Box::new( future ) as BoxedFuture ) ) ), - }) + } ) } fn poll(&self) { let mut spawn = self.spawn.borrow_mut(); // Take the future so that if we panic it gets dropped - if let Some(mut spawn_future) = spawn.take() { + if let Some( mut spawn_future ) = spawn.take() { // Clear `is_queued` flag - self.is_queued.set(false); + self.is_queued.set( false ); - if spawn_future.poll_future_notify( &&Core, self as *const _ as usize ) == Ok(Async::NotReady) { + if spawn_future.poll_future_notify( &&Core, self as *const _ as usize ) == Ok( Async::NotReady ) { // Future was not ready, so put it back - *spawn = Some(spawn_future); + *spawn = Some( spawn_future ); } } } - fn notify( spawned: Rc ) { + fn notify( spawned: Rc< SpawnedTask > ) { // If not already queued - if !spawned.is_queued.replace(true) { + if !spawned.is_queued.replace( true ) { // Poll on next micro-task js! { @(no_return) - Promise.resolve().then(function() { - @{ Once(move || spawned.poll()) }(); - }); + Promise.resolve().then( function () { + @{Once( move || spawned.poll() )}(); + } ); } } } @@ -73,15 +73,15 @@ impl< F > Executor< F > for Core where impl Notify for Core { fn notify( &self, spawned_id: usize ) { - SpawnedTask::notify(unsafe { clone_raw(spawned_id as *const _) }) + SpawnedTask::notify( unsafe { clone_raw( spawned_id as *const _ ) } ) } fn clone_id( &self, id: usize ) -> usize { - unsafe { Rc::into_raw(clone_raw(id as *const SpawnedTask)) as usize } + unsafe { Rc::into_raw( clone_raw( id as *const SpawnedTask ) ) as usize } } fn drop_id( &self, id: usize ) { - unsafe { Rc::from_raw(id as *const SpawnedTask) }; + unsafe { Rc::from_raw( id as *const SpawnedTask ) }; } } From 48924f94d14de638f010f39a2db609c5056258ad Mon Sep 17 00:00:00 2001 From: Pauan Date: Sat, 10 Feb 2018 03:11:28 -1000 Subject: [PATCH 33/40] Enabling the RefCell example --- examples/promise/src/main.rs | 275 +++++++++++++---------------------- 1 file changed, 100 insertions(+), 175 deletions(-) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index 599d2114..10327519 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -1,21 +1,25 @@ -/*#[cfg(target_arch = "wasm32")] #[macro_use] extern crate stdweb; extern crate futures; -#[cfg(not(target_arch = "wasm32"))] -extern crate tokio; - +use futures::Future; +use stdweb::unstable::{TryInto}; +use stdweb::web::error::Error; +use stdweb::{Null, Promise, PromiseFuture}; use std::rc::Rc; use std::cell::RefCell; -use futures::Future; use futures::{Poll, Async}; use futures::task::{current, Task}; -#[cfg(target_arch = "wasm32")] -fn main() { - use stdweb::{PromiseFuture}; +fn log( a: &str ) { + js! { @(no_return) + console.log( @{a} ); + } +} + + +fn test_refcell() { struct TaskA { shared_state: Rc>, task_b: Task, @@ -82,199 +86,99 @@ fn main() { } -#[cfg(not(target_arch = "wasm32"))] -fn main() { - use tokio::executor::current_thread; - - struct TaskA { - shared_state: Rc>, - task_b: Task, - } - - impl Future for TaskA { - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll { - println!("Poll TaskA"); - - let foo = self.shared_state.borrow_mut(); - - println!("TaskA 1: {:#?}", foo); - - self.task_b.notify(); - - println!("TaskA 2: {:#?}", foo); - - Ok(Async::NotReady) - } - } - - struct TaskB { - shared_state: Rc>, - initialized: bool, - } - - impl Future for TaskB { - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll { - println!("Poll TaskB"); - - if !self.initialized { - self.initialized = true; - - let task_b = current(); - - let foo = self.shared_state.borrow(); - - println!("TaskB 1: {:#?}", foo); - - current_thread::spawn(TaskA { - shared_state: self.shared_state.clone(), - task_b: task_b, - }); - } - - let foo = self.shared_state.borrow(); - - println!("TaskB 1: {:#?}", foo); - - Ok(Async::NotReady) - } - } - - current_thread::run(|_| { - current_thread::spawn(TaskB { - shared_state: Rc::new(RefCell::new(0)), - initialized: false - }); - }); -}*/ - - - - -#[macro_use] -extern crate stdweb; -extern crate futures; - -use futures::Future; -use stdweb::unstable::{TryInto}; -use stdweb::web::error::Error; -use stdweb::{Null, Promise, PromiseFuture}; - +fn test_panic() { + let promise: Promise = js!( return Promise.resolve(null); ).try_into().unwrap(); -fn sleep( ms: u32 ) -> PromiseFuture< Null > { - js!( return new Promise( function ( success, failure ) { - setTimeout( function () { - success( null ); - }, @{ms} ); - } ); ).try_into().unwrap() + promise.done( |result: Result< Null, Error >| { + log( &format!( "Promise result: {:#?}", result ) ); + panic!( "Testing panic!" ); + } ); } -fn log( a: &str ) { - js! { @(no_return) - console.log( @{a} ); +fn test_notify() { + struct MyFuture { + polls: u32, + count: u32, + done: bool, + receiver: futures::unsync::oneshot::Receiver< () >, } -} + impl MyFuture { + fn new( count: u32 ) -> Self { + let ( sender, receiver ) = futures::unsync::oneshot::channel(); -struct MyFuture { - polls: u32, - count: u32, - done: bool, - receiver: futures::unsync::oneshot::Receiver< () >, -} - -impl MyFuture { - fn new( count: u32 ) -> Self { - let ( sender, receiver ) = futures::unsync::oneshot::channel(); + let callback = || { + log( "setTimeout done" ); - let callback = || { - log( "setTimeout done" ); + log( &format!("Sending {:#?}", sender.send( () ) ) ); + }; - log( &format!("Sending {:#?}", sender.send( () ) ) ); - }; + log( "setTimeout started" ); - log( "setTimeout started" ); - - js! { @(no_return) - setTimeout( function () { - @{stdweb::Once( callback )}(); - }, 1000 ); - } + js! { @(no_return) + setTimeout( function () { + @{stdweb::Once( callback )}(); + }, 1000 ); + } - Self { - polls: 0, - count: count, - done: false, - receiver, + Self { + polls: 0, + count: count, + done: false, + receiver, + } } } -} -impl Future for MyFuture { - type Item = u32; - type Error = (); + impl Future for MyFuture { + type Item = u32; + type Error = (); - fn poll( &mut self ) -> futures::Poll< Self::Item, Self::Error > { - self.polls += 1; + fn poll( &mut self ) -> futures::Poll< Self::Item, Self::Error > { + self.polls += 1; - if !self.done { - match self.receiver.poll() { - Ok( futures::Async::Ready( () ) ) => self.done = true, + if !self.done { + match self.receiver.poll() { + Ok( futures::Async::Ready( () ) ) => self.done = true, - Ok( futures::Async::NotReady ) => {}, + Ok( futures::Async::NotReady ) => {}, - Err( _ ) => self.done = true, + Err( _ ) => self.done = true, + } } - } - if self.done { - if self.count == 0 { - Ok( futures::Async::Ready( self.polls ) ) + if self.done { + if self.count == 0 { + Ok( futures::Async::Ready( self.polls ) ) + + } else { + self.count -= 1; + + let task = futures::task::current(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + task.notify(); + + Ok( futures::Async::NotReady ) + } } else { - self.count -= 1; - - let task = futures::task::current(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - task.notify(); - Ok( futures::Async::NotReady ) } - - } else { - Ok( futures::Async::NotReady ) } } -} - - -fn main() { - stdweb::initialize(); - - let promise: Promise = js!( return Promise.resolve(null); ).try_into().unwrap(); - - promise.done( |result: Result< Null, Error >| { - log( &format!( "Promise result: {:#?}", result ) ); - panic!( "Testing panic!" ); - } ); PromiseFuture::spawn( MyFuture::new( 5 ).map( |x| { @@ -282,6 +186,17 @@ fn main() { assert_eq!( x, 7 ); } ) ); +} + + +fn test_timeout() { + fn sleep( ms: u32 ) -> PromiseFuture< Null > { + js!( return new Promise( function ( success, failure ) { + setTimeout( function () { + success( null ); + }, @{ms} ); + } ); ).try_into().unwrap() + } PromiseFuture::spawn( sleep( 2000 ).inspect( |_| log( "Timeout 1 done!") ).join( @@ -292,6 +207,16 @@ fn main() { futures::future::err( Error::new( "Testing error!" ) ) ) .map_err( |e| e.print() ) ); +} + + +fn main() { + stdweb::initialize(); + + test_refcell(); + test_panic(); + test_notify(); + test_timeout(); stdweb::event_loop(); } From 329a6e302f14d7feaf68d21b48841b7375eed3de Mon Sep 17 00:00:00 2001 From: Pauan Date: Sat, 10 Feb 2018 04:00:54 -1000 Subject: [PATCH 34/40] Fixing minor nit --- src/webcore/promise_executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webcore/promise_executor.rs b/src/webcore/promise_executor.rs index bc124a71..ba093417 100644 --- a/src/webcore/promise_executor.rs +++ b/src/webcore/promise_executor.rs @@ -33,7 +33,7 @@ impl SpawnedTask { } ) } - fn poll(&self) { + fn poll( &self ) { let mut spawn = self.spawn.borrow_mut(); // Take the future so that if we panic it gets dropped From eef311b7d9a4f82e9b6b5511a72be196295f5b2e Mon Sep 17 00:00:00 2001 From: Pauan Date: Sat, 10 Feb 2018 16:29:40 -1000 Subject: [PATCH 35/40] Changing to use the new ReferenceType deriving --- src/webcore/promise.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index a73b1123..57529685 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -16,13 +16,10 @@ use webcore::promise_executor::spawn; /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects +#[derive(Clone, Debug, ReferenceType)] +#[reference(instance_of = "Promise")] pub struct Promise( Reference ); -reference_boilerplate! { - Promise, - instanceof Promise -} - impl Promise { // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-resolve-functions fn is_promise_like( input: &Value ) -> bool { From 73d9f85b93a027b6cb5991f31312076b0f81e8e1 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sat, 10 Feb 2018 17:52:59 -1000 Subject: [PATCH 36/40] Fixing minor nit --- src/webcore/promise.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 57529685..07060128 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -113,9 +113,9 @@ impl Promise { var callback = @{Once( callback )}; // TODO don't swallow any errors thrown inside callback - @{self}.then( function (value) { + @{self}.then( function ( value ) { callback( value, true ); - }, function (value) { + }, function ( value ) { callback( value, false ); } ); } From 960f737c29dc119f8973abc19324b0287941a6f6 Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 11 Feb 2018 06:58:14 -1000 Subject: [PATCH 37/40] Changing PromiseFuture to allow for any type for the error --- examples/promise/src/main.rs | 23 ++++++++++++- src/webcore/promise.rs | 63 ++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/examples/promise/src/main.rs b/examples/promise/src/main.rs index 10327519..793e0386 100644 --- a/examples/promise/src/main.rs +++ b/examples/promise/src/main.rs @@ -19,6 +19,26 @@ fn log( a: &str ) { } +fn test_error_conversion() { + let a: PromiseFuture< Null, String > = js!( return Promise.reject( "hi!" ); ).try_into().unwrap(); + + PromiseFuture::spawn( + a.map( |_| () ).map_err( |x| { + log( &format!( "String error: {:#?}", x ) ); + } ) + ); + + let _a: PromiseFuture< Null, Error > = js!( return Promise.resolve( null ); ).try_into().unwrap(); + log( "Null works" ); + + let _a: PromiseFuture< Null, Error > = js!( return Promise.reject( new Error( "hi!" ) ); ).try_into().unwrap(); + log( "Error works" ); + + //let _a: PromiseFuture< Null, SyntaxError > = js!( return Promise.reject( new Error( "hi!" ) ); ).try_into().unwrap(); + //log( "Error conversion fails" ); +} + + fn test_refcell() { struct TaskA { shared_state: Rc>, @@ -190,7 +210,7 @@ fn test_notify() { fn test_timeout() { - fn sleep( ms: u32 ) -> PromiseFuture< Null > { + fn sleep( ms: u32 ) -> PromiseFuture< Null, Error > { js!( return new Promise( function ( success, failure ) { setTimeout( function () { success( null ); @@ -217,6 +237,7 @@ fn main() { test_panic(); test_notify(); test_timeout(); + test_error_conversion(); stdweb::event_loop(); } diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index 07060128..f6513ffe 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -1,6 +1,4 @@ use std; -use std::error::Error as _Error; -use std::marker::PhantomData; use webcore::once::Once; use webcore::value::{Value, Reference, ConversionError}; use webcore::try_from::{TryInto, TryFrom}; @@ -92,18 +90,23 @@ impl Promise { /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) // https://www.ecma-international.org/ecma-262/6.0/#sec-performpromisethen - pub fn done< A, B >( &self, callback: B ) + pub fn done< A, B, F >( &self, callback: F ) where A: TryFrom< Value >, - A::Error: std::error::Error, - B: FnOnce( Result< A, Error > ) + 'static { + B: TryFrom< Value >, + // TODO these Debug constraints are only needed because of unwrap + A::Error: std::fmt::Debug, + B::Error: std::fmt::Debug, + F: FnOnce( Result< A, B > ) + 'static { let callback = |value: Value, success: bool| { - let value: Result< A, Error > = if success { - let value: Result< A, A::Error > = value.try_into(); - value.map_err( |e| Error::new( e.description() ) ) + let value: Result< A, B > = if success { + // TODO figure out a way to avoid the unwrap + let value: A = value.try_into().unwrap(); + Ok( value ) } else { - let value: Result< Error, ConversionError > = value.try_into(); - value.map_err( |e| Error::new( e.description() ) ).and_then( Err ) + // TODO figure out a way to avoid the unwrap + let value: B = value.try_into().unwrap(); + Err( value ) }; callback( value ); @@ -132,9 +135,12 @@ impl Promise { /// ``` // We can't use the IntoFuture trait because Promise doesn't have a type argument // TODO explain more why we can't use the IntoFuture trait - pub fn to_future< A >( &self ) -> PromiseFuture< A > + pub fn to_future< A, B >( &self ) -> PromiseFuture< A, B > where A: TryFrom< Value > + 'static, - A::Error: std::error::Error { + B: TryFrom< Value > + 'static, + // TODO remove these later + A::Error: std::fmt::Debug, + B::Error: std::fmt::Debug { let ( sender, receiver ) = channel(); @@ -148,7 +154,6 @@ impl Promise { PromiseFuture { future: receiver, - phantom: PhantomData, } } } @@ -163,14 +168,13 @@ impl Promise { /// Convert a JavaScript `Promise` into a `PromiseFuture`: /// /// ```rust -/// let future: PromiseFuture = js!( return Promise.resolve("foo"); ).try_into().unwrap(); +/// let future: PromiseFuture = js!( return Promise.resolve("foo"); ).try_into().unwrap(); /// ``` -pub struct PromiseFuture< A > { - future: Receiver< Result< A, Error > >, - phantom: PhantomData< A >, +pub struct PromiseFuture< A, B > { + future: Receiver< Result< A, B > >, } -impl PromiseFuture< () > { +impl PromiseFuture< (), () > { /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and then immediately returns. /// This does not block the current thread. The only way to retrieve the value of the future is to use the various /// [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) methods, such as @@ -227,29 +231,32 @@ impl PromiseFuture< () > { } } -impl< A > std::fmt::Debug for PromiseFuture< A > { +impl< A, B > std::fmt::Debug for PromiseFuture< A, B > { fn fmt( &self, formatter: &mut std::fmt::Formatter ) -> std::fmt::Result { write!( formatter, "PromiseFuture" ) } } -impl< A > Future for PromiseFuture< A > { +impl< A, B > Future for PromiseFuture< A, B > { type Item = A; - type Error = Error; + type Error = B; fn poll( &mut self ) -> Poll< Self::Item, Self::Error > { - match self.future.poll() { - Ok( Async::Ready( Ok( a ) ) ) => Ok( Async::Ready( a ) ), - Ok( Async::Ready( Err( e ) ) ) => Err( e ), - Ok( Async::NotReady ) => Ok( Async::NotReady ), - Err( e ) => Err( Error::new( e.description() ) ), + // TODO maybe remove this unwrap ? + match self.future.poll().unwrap() { + Async::Ready( Ok( a ) ) => Ok( Async::Ready( a ) ), + Async::Ready( Err( e ) ) => Err( e ), + Async::NotReady => Ok( Async::NotReady ), } } } -impl< A > TryFrom< Value > for PromiseFuture< A > +impl< A, B > TryFrom< Value > for PromiseFuture< A, B > where A: TryFrom< Value > + 'static, - A::Error: std::error::Error { + B: TryFrom< Value > + 'static, + // TODO remove this later + A::Error: std::fmt::Debug, + B::Error: std::fmt::Debug { type Error = ConversionError; From 4475f8b1c00a28a222b6dc97c6d945181c1892dc Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 11 Feb 2018 07:05:19 -1000 Subject: [PATCH 38/40] Renaming Promise::convert to Promise::from_thenable --- src/webcore/promise.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index f6513ffe..e2497271 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -20,7 +20,7 @@ pub struct Promise( Reference ); impl Promise { // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-resolve-functions - fn is_promise_like( input: &Value ) -> bool { + fn is_thenable( input: &Value ) -> bool { (js! { var input = @{input}; // This emulates the `Type(input) is Object` and `IsCallable(input.then)` ECMAScript abstract operations. @@ -37,7 +37,7 @@ impl Promise { /// That situation is rare, but it can happen if you are using a Promise library such as jQuery or /// Bluebird. /// - /// In that situation you can use `Promise::convert(value)` to convert it into a true `Promise`. + /// In that situation you can use `Promise::from_thenable(value)` to convert it into a true `Promise`. /// /// If the `input` isn't a Promise-like object then it returns `None`. /// @@ -47,19 +47,20 @@ impl Promise { /// /// ```rust /// // jQuery Promise - /// Promise::convert(js!( return $.get("test.php"); )) + /// Promise::from_thenable(js!( return $.get("test.php"); )) /// /// // Bluebird Promise - /// Promise::convert(js!( return bluebird_promise.timeout(1000); )) + /// Promise::from_thenable(js!( return bluebird_promise.timeout(1000); )) /// ``` /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) // https://www.ecma-international.org/ecma-262/6.0/#sec-promise.resolve // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-resolve-functions // https://www.ecma-international.org/ecma-262/6.0/#sec-promiseresolvethenablejob - pub fn convert( input: Value ) -> Option< Self > { + // TODO change this later to use &Reference + pub fn from_thenable( input: Value ) -> Option< Self > { // TODO this can probably be made more efficient - if Promise::is_promise_like( &input ) { + if Promise::is_thenable( &input ) { Some( js!( return Promise.resolve( @{input} ); ).try_into().unwrap() ) } else { From ef37ec4ddfffd1b730cf7c2d8d6d3fa064b1645d Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 11 Feb 2018 07:09:32 -1000 Subject: [PATCH 39/40] Minor improvement to the docs --- src/webcore/promise.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index e2497271..cd642a34 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -169,6 +169,9 @@ impl Promise { /// Convert a JavaScript `Promise` into a `PromiseFuture`: /// /// ```rust +/// use stdweb::PromiseFuture; +/// use stdweb::web::error::Error; +/// /// let future: PromiseFuture = js!( return Promise.resolve("foo"); ).try_into().unwrap(); /// ``` pub struct PromiseFuture< A, B > { From eef539d498c457dfc54c0eebc630731aa65e340b Mon Sep 17 00:00:00 2001 From: Pauan Date: Sun, 11 Feb 2018 07:38:12 -1000 Subject: [PATCH 40/40] Fixing the cfg-gating for futures --- src/lib.rs | 7 +- src/webcore/mod.rs | 5 ++ src/webcore/promise.rs | 124 +++------------------------------- src/webcore/promise_future.rs | 118 ++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 118 deletions(-) create mode 100644 src/webcore/promise_future.rs diff --git a/src/lib.rs b/src/lib.rs index 26210ff0..37cc6862 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ extern crate stdweb_internal_macros; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] pub use stdweb_internal_macros::js_export; -#[cfg(any(test, feature = "futures"))] +#[cfg(feature = "futures")] extern crate futures; #[macro_use] @@ -131,7 +131,10 @@ pub use webcore::instance_of::InstanceOf; pub use webcore::reference_type::ReferenceType; pub use webcore::serialization::JsSerialize; -pub use webcore::promise::{Promise, PromiseFuture}; +pub use webcore::promise::Promise; + +#[cfg(feature = "futures")] +pub use webcore::promise_future::PromiseFuture; #[cfg(feature = "serde")] /// A module with serde-related APIs. diff --git a/src/webcore/mod.rs b/src/webcore/mod.rs index 07afb752..fcfa525b 100644 --- a/src/webcore/mod.rs +++ b/src/webcore/mod.rs @@ -16,6 +16,11 @@ pub mod once; pub mod instance_of; pub mod reference_type; pub mod promise; + +#[cfg(feature = "futures")] +pub mod promise_future; + +#[cfg(feature = "futures")] pub mod promise_executor; #[cfg(feature = "nightly")] diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index cd642a34..075c2883 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -1,11 +1,13 @@ use std; use webcore::once::Once; -use webcore::value::{Value, Reference, ConversionError}; +use webcore::value::{Value, Reference}; use webcore::try_from::{TryInto, TryFrom}; -use web::error::Error; -use futures::{Future, Poll, Async}; -use futures::unsync::oneshot::{Receiver, channel}; -use webcore::promise_executor::spawn; + +#[cfg(feature = "futures")] +use futures::unsync::oneshot::channel; + +#[cfg(feature = "futures")] +use super::promise_future::PromiseFuture; /// A `Promise` object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. @@ -136,6 +138,7 @@ impl Promise { /// ``` // We can't use the IntoFuture trait because Promise doesn't have a type argument // TODO explain more why we can't use the IntoFuture trait + #[cfg(feature = "futures")] pub fn to_future< A, B >( &self ) -> PromiseFuture< A, B > where A: TryFrom< Value > + 'static, B: TryFrom< Value > + 'static, @@ -158,114 +161,3 @@ impl Promise { } } } - - -/// This allows you to use a JavaScript [`Promise`](struct.Promise.html) as if it is a Rust [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html). -/// -/// The preferred way to create a `PromiseFuture` is to use [`value.try_into()`](unstable/trait.TryInto.html) on a JavaScript [`Value`](enum.Value.html). -/// -/// # Examples -/// -/// Convert a JavaScript `Promise` into a `PromiseFuture`: -/// -/// ```rust -/// use stdweb::PromiseFuture; -/// use stdweb::web::error::Error; -/// -/// let future: PromiseFuture = js!( return Promise.resolve("foo"); ).try_into().unwrap(); -/// ``` -pub struct PromiseFuture< A, B > { - future: Receiver< Result< A, B > >, -} - -impl PromiseFuture< (), () > { - /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and then immediately returns. - /// This does not block the current thread. The only way to retrieve the value of the future is to use the various - /// [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) methods, such as - /// [`map`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map) or - /// [`inspect`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.inspect). - /// - /// This function requires you to handle all errors yourself. Because the errors happen asynchronously, the only way to catch them is - /// to use a [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) method, such as - /// [`map_err`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map_err). - /// - /// It is very common to want to print the errors to the console. You can do that by using `.map_err(|e| e.print())` - /// - /// This function is normally called once in `main`, it is usually not needed to call it multiple times. - /// - /// # Examples - /// - /// Asynchronously run a future in `main`, printing any errors to the console: - /// - /// ```rust - /// fn main() { - /// PromiseFuture::spawn( - /// create_some_future() - /// .map_err(|e| e.print()) - /// ); - /// } - /// ``` - /// - /// Inspect the output value of the future: - /// - /// ```rust - /// fn main() { - /// PromiseFuture::spawn( - /// create_some_future() - /// .inspect(|x| println!("Future finished: {:#?}", x)) - /// .map_err(|e| e.print()) - /// ); - /// } - /// ``` - /// - /// Catch errors and handle them yourself: - /// - /// ```rust - /// fn main() { - /// PromiseFuture::spawn( - /// create_some_future() - /// .map_err(|e| handle_error_somehow(e)) - /// ); - /// } - /// ``` - #[inline] - pub fn spawn< B >( future: B ) where - B: Future< Item = (), Error = () > + 'static { - spawn( future ); - } -} - -impl< A, B > std::fmt::Debug for PromiseFuture< A, B > { - fn fmt( &self, formatter: &mut std::fmt::Formatter ) -> std::fmt::Result { - write!( formatter, "PromiseFuture" ) - } -} - -impl< A, B > Future for PromiseFuture< A, B > { - type Item = A; - type Error = B; - - fn poll( &mut self ) -> Poll< Self::Item, Self::Error > { - // TODO maybe remove this unwrap ? - match self.future.poll().unwrap() { - Async::Ready( Ok( a ) ) => Ok( Async::Ready( a ) ), - Async::Ready( Err( e ) ) => Err( e ), - Async::NotReady => Ok( Async::NotReady ), - } - } -} - -impl< A, B > TryFrom< Value > for PromiseFuture< A, B > - where A: TryFrom< Value > + 'static, - B: TryFrom< Value > + 'static, - // TODO remove this later - A::Error: std::fmt::Debug, - B::Error: std::fmt::Debug { - - type Error = ConversionError; - - fn try_from( v: Value ) -> Result< Self, Self::Error > { - let promise: Promise = v.try_into()?; - Ok( promise.to_future() ) - } -} diff --git a/src/webcore/promise_future.rs b/src/webcore/promise_future.rs new file mode 100644 index 00000000..6b4801c9 --- /dev/null +++ b/src/webcore/promise_future.rs @@ -0,0 +1,118 @@ +use std; +use webcore::value::{Value, ConversionError}; +use webcore::try_from::{TryInto, TryFrom}; +use futures::{Future, Poll, Async}; +use futures::unsync::oneshot::Receiver; +use webcore::promise_executor::spawn; +use super::promise::Promise; + + +/// This allows you to use a JavaScript [`Promise`](struct.Promise.html) as if it is a Rust [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html). +/// +/// The preferred way to create a `PromiseFuture` is to use [`value.try_into()`](unstable/trait.TryInto.html) on a JavaScript [`Value`](enum.Value.html). +/// +/// # Examples +/// +/// Convert a JavaScript `Promise` into a `PromiseFuture`: +/// +/// ```rust +/// use stdweb::PromiseFuture; +/// use stdweb::web::error::Error; +/// +/// let future: PromiseFuture = js!( return Promise.resolve("foo"); ).try_into().unwrap(); +/// ``` +pub struct PromiseFuture< A, B > { + pub(crate) future: Receiver< Result< A, B > >, +} + +impl PromiseFuture< (), () > { + /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) and then immediately returns. + /// This does not block the current thread. The only way to retrieve the value of the future is to use the various + /// [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) methods, such as + /// [`map`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map) or + /// [`inspect`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.inspect). + /// + /// This function requires you to handle all errors yourself. Because the errors happen asynchronously, the only way to catch them is + /// to use a [`Future`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html) method, such as + /// [`map_err`](https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.map_err). + /// + /// It is very common to want to print the errors to the console. You can do that by using `.map_err(|e| e.print())` + /// + /// This function is normally called once in `main`, it is usually not needed to call it multiple times. + /// + /// # Examples + /// + /// Asynchronously run a future in `main`, printing any errors to the console: + /// + /// ```rust + /// fn main() { + /// PromiseFuture::spawn( + /// create_some_future() + /// .map_err(|e| e.print()) + /// ); + /// } + /// ``` + /// + /// Inspect the output value of the future: + /// + /// ```rust + /// fn main() { + /// PromiseFuture::spawn( + /// create_some_future() + /// .inspect(|x| println!("Future finished: {:#?}", x)) + /// .map_err(|e| e.print()) + /// ); + /// } + /// ``` + /// + /// Catch errors and handle them yourself: + /// + /// ```rust + /// fn main() { + /// PromiseFuture::spawn( + /// create_some_future() + /// .map_err(|e| handle_error_somehow(e)) + /// ); + /// } + /// ``` + #[inline] + pub fn spawn< B >( future: B ) where + B: Future< Item = (), Error = () > + 'static { + spawn( future ); + } +} + +impl< A, B > std::fmt::Debug for PromiseFuture< A, B > { + fn fmt( &self, formatter: &mut std::fmt::Formatter ) -> std::fmt::Result { + write!( formatter, "PromiseFuture" ) + } +} + +impl< A, B > Future for PromiseFuture< A, B > { + type Item = A; + type Error = B; + + fn poll( &mut self ) -> Poll< Self::Item, Self::Error > { + // TODO maybe remove this unwrap ? + match self.future.poll().unwrap() { + Async::Ready( Ok( a ) ) => Ok( Async::Ready( a ) ), + Async::Ready( Err( e ) ) => Err( e ), + Async::NotReady => Ok( Async::NotReady ), + } + } +} + +impl< A, B > TryFrom< Value > for PromiseFuture< A, B > + where A: TryFrom< Value > + 'static, + B: TryFrom< Value > + 'static, + // TODO remove this later + A::Error: std::fmt::Debug, + B::Error: std::fmt::Debug { + + type Error = ConversionError; + + fn try_from( v: Value ) -> Result< Self, Self::Error > { + let promise: Promise = v.try_into()?; + Ok( promise.to_future() ) + } +}