diff --git a/crates/neon-runtime/src/lib.rs b/crates/neon-runtime/src/lib.rs index b31649030..40f9e5fb4 100644 --- a/crates/neon-runtime/src/lib.rs +++ b/crates/neon-runtime/src/lib.rs @@ -16,3 +16,4 @@ pub mod mem; pub mod fun; pub mod convert; pub mod class; +pub mod task; diff --git a/crates/neon-runtime/src/neon.cc b/crates/neon-runtime/src/neon.cc index 9db944868..e2f05dec5 100644 --- a/crates/neon-runtime/src/neon.cc +++ b/crates/neon-runtime/src/neon.cc @@ -6,6 +6,7 @@ #include "neon.h" #include "neon_string.h" #include "neon_class_metadata.h" +#include "neon_task.h" extern "C" void Neon_Call_SetReturn(v8::FunctionCallbackInfo *info, v8::Local value) { info->GetReturnValue().Set(value); @@ -527,3 +528,9 @@ extern "C" void Neon_Error_ThrowSyntaxErrorFromCString(const char *msg) { extern "C" bool Neon_Mem_SameHandle(v8::Local v1, v8::Local v2) { return v1 == v2; } + +extern "C" void Neon_Task_Schedule(void *task, Neon_TaskPerformCallback perform, Neon_TaskCompleteCallback complete, v8::Local callback) { + v8::Isolate *isolate = v8::Isolate::GetCurrent(); + neon::Task *internal_task = new neon::Task(isolate, task, perform, complete, callback); + neon::queue_task(internal_task); +} diff --git a/crates/neon-runtime/src/neon.h b/crates/neon-runtime/src/neon.h index 4e0bd5bb1..797d116d3 100644 --- a/crates/neon-runtime/src/neon.h +++ b/crates/neon-runtime/src/neon.h @@ -151,6 +151,11 @@ extern "C" { void Neon_Error_ThrowSyntaxErrorFromCString(const char *msg); bool Neon_Mem_SameHandle(v8::Local v1, v8::Local v2); + + typedef void* (*Neon_TaskPerformCallback)(void *); + typedef void (*Neon_TaskCompleteCallback)(void *, void *, v8::Local *out); + + void Neon_Task_Schedule(void *task, Neon_TaskPerformCallback perform, Neon_TaskCompleteCallback complete, v8::Local callback); } #endif diff --git a/crates/neon-runtime/src/neon_task.h b/crates/neon-runtime/src/neon_task.h new file mode 100644 index 000000000..afb7da702 --- /dev/null +++ b/crates/neon-runtime/src/neon_task.h @@ -0,0 +1,101 @@ +#ifndef NEON_TASK_H_ +#define NEON_TASK_H_ + +#include +#include "neon.h" +#include "v8.h" + +namespace neon { + +class Task { +public: + Task(v8::Isolate *isolate, + void *rust_task, + Neon_TaskPerformCallback perform, + Neon_TaskCompleteCallback complete, + v8::Local callback) + : isolate_(isolate), + rust_task_(rust_task), + perform_(perform), + complete_(complete) + { + request_.data = this; + result_ = nullptr; + // Save the callback to be invoked when the task completes. + callback_.Reset(isolate, callback); + // Save the context (aka realm) to be used when invoking the callback. + context_.Reset(isolate, isolate->GetCurrentContext()); + } + + void execute() { + result_ = perform_(rust_task_); + } + + void complete() { + // Ensure that we have all the proper scopes installed on the C++ stack before + // invoking the callback, and use the context (i.e. realm) we saved with the task. + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope(isolate_); + v8::Local context = v8::Local::New(isolate_, context_); + v8::Context::Scope context_scope(context); + + v8::Local argv[2]; + + argv[0] = v8::Null(isolate_); + argv[1] = v8::Undefined(isolate_); + + { + v8::TryCatch trycatch(isolate_); + + v8::Local completion; + + complete_(rust_task_, result_, &completion); + + if (trycatch.HasCaught()) { + argv[0] = trycatch.Exception(); + } else { + argv[1] = completion; + } + } + + v8::Local callback = v8::Local::New(isolate_, callback_); + callback->Call(context, v8::Null(isolate_), 2, argv); + } + + void *get_result() { + return result_; + } + + uv_work_t request_; + +private: + v8::Isolate *isolate_; + void *rust_task_; + Neon_TaskPerformCallback perform_; + Neon_TaskCompleteCallback complete_; + void *result_; + v8::Persistent callback_; + v8::Persistent context_; +}; + +void execute_task(uv_work_t *request) { + Task *task = static_cast(request->data); + task->execute(); +} + +void complete_task(uv_work_t *request) { + Task *task = static_cast(request->data); + task->complete(); + delete task; +} + +void queue_task(Task *task) { + uv_queue_work(uv_default_loop(), + &task->request_, + execute_task, + (uv_after_work_cb)complete_task); +} + +} + +#endif diff --git a/crates/neon-runtime/src/task.rs b/crates/neon-runtime/src/task.rs new file mode 100644 index 000000000..a569bf61e --- /dev/null +++ b/crates/neon-runtime/src/task.rs @@ -0,0 +1,15 @@ +//! Facilities for running background tasks in the libuv thread pool. + +use raw::Local; +use std::os::raw::c_void; + +extern "C" { + + /// Schedules a background task. + #[link_name = "Neon_Task_Schedule"] + pub fn schedule(task: *mut c_void, + perform: unsafe extern fn(*mut c_void) -> *mut c_void, + complete: unsafe extern fn(*mut c_void, *mut c_void, &mut Local), + callback: Local); + +} diff --git a/src/internal/vm.rs b/src/internal/vm.rs index 390428c88..13c11fb93 100644 --- a/src/internal/vm.rs +++ b/src/internal/vm.rs @@ -41,6 +41,7 @@ pub trait IsolateInternal { fn to_raw(self) -> *mut raw::Isolate; fn from_raw(ptr: *mut raw::Isolate) -> Self; fn class_map(&mut self) -> &mut ClassMap; + fn current() -> Isolate; } pub struct ClassMap { @@ -86,6 +87,12 @@ impl IsolateInternal for Isolate { } unsafe { mem::transmute(ptr) } } + + fn current() -> Isolate { + unsafe { + mem::transmute(neon_runtime::call::current_isolate()) + } + } } extern "C" fn drop_class_map(map: Box) { diff --git a/src/lib.rs b/src/lib.rs index 3b272c6ad..43bc33d23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ pub mod mem; pub mod vm; pub mod scope; pub mod js; +pub mod task; #[doc(hidden)] pub mod macro_internal; diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 000000000..b5c1c480f --- /dev/null +++ b/src/task.rs @@ -0,0 +1,53 @@ +use std::marker::{Send, Sized}; +use std::mem; +use std::os::raw::c_void; + +use js::{Value, JsFunction}; +use mem::Handle; +use internal::mem::Managed; +use internal::scope::{Scope, RootScope, RootScopeInternal}; +use internal::vm::{JsResult, Isolate, IsolateInternal}; +use neon_runtime; +use neon_runtime::raw; + +pub trait Task: Send + Sized { + type Output: Send; + type Error: Send; + type JsEvent: Value; + + fn perform(&self) -> Result; + + fn complete<'a, T: Scope<'a>>(self, scope: &'a mut T, result: Result) -> JsResult; + + fn schedule(self, callback: Handle) { + let boxed_self = Box::new(self); + let self_raw = Box::into_raw(boxed_self); + let callback_raw = callback.to_raw(); + unsafe { + neon_runtime::task::schedule(mem::transmute(self_raw), + perform_task::, + complete_task::, + callback_raw); + } + } +} + +unsafe extern "C" fn perform_task(task: *mut c_void) -> *mut c_void { + let task: Box = Box::from_raw(mem::transmute(task)); + let result = task.perform(); + Box::into_raw(task); + mem::transmute(Box::into_raw(Box::new(result))) +} + +unsafe extern "C" fn complete_task(task: *mut c_void, result: *mut c_void, out: &mut raw::Local) { + let result: Result = *Box::from_raw(mem::transmute(result)); + let task: Box = Box::from_raw(mem::transmute(task)); + + // The neon::Task::complete() method installs an outer v8::HandleScope + // that is responsible for managing the out pointer, so it's safe to + // create the RootScope here without creating a local v8::HandleScope. + let mut scope = RootScope::new(Isolate::current()); + if let Ok(result) = task.complete(&mut scope, result) { + *out = result.to_raw(); + } +} diff --git a/tests/lib/tasks.js b/tests/lib/tasks.js new file mode 100644 index 000000000..66eb27bf1 --- /dev/null +++ b/tests/lib/tasks.js @@ -0,0 +1,30 @@ +var addon = require('../native'); +var assert = require('chai').assert; + +describe('Task', function() { + it('completes a successful task', function (done) { + addon.perform_async_task((err, n) => { + if (err) { + done(err); + } else if (n === 17) { + done(); + } else { + done(new Error("not 17 but: " + n)); + } + }); + }); + + it('completes a failing task', function (done) { + addon.perform_failing_task((err, n) => { + if (err) { + if (err.message === 'I am a failing task') { + done(); + } else { + done(new Error("expected error message 'I am a failing task', got: " + err.message)); + } + } else { + done(new Error("expected task to fail, got: " + n)); + } + }); + }); +}); diff --git a/tests/native/src/js/tasks.rs b/tests/native/src/js/tasks.rs new file mode 100644 index 000000000..75c6957ea --- /dev/null +++ b/tests/native/src/js/tasks.rs @@ -0,0 +1,49 @@ +use neon::vm::{Call, JsResult}; +use neon::scope::{Scope}; +use neon::js::{JsUndefined, JsNumber, JsFunction}; +use neon::js::error::{Kind, JsError}; +use neon::task::Task; + +struct SuccessTask; + +impl Task for SuccessTask { + type Output = i32; + type Error = String; + type JsEvent = JsNumber; + + fn perform(&self) -> Result { + Ok(17) + } + + fn complete<'a, T: Scope<'a>>(self, scope: &'a mut T, result: Result) -> JsResult { + Ok(JsNumber::new(scope, result.unwrap() as f64)) + } +} + +pub fn perform_async_task(call: Call) -> JsResult { + let f = call.arguments.require(call.scope, 0)?.check::()?; + SuccessTask.schedule(f); + Ok(JsUndefined::new()) +} + +struct FailureTask; + +impl Task for FailureTask { + type Output = i32; + type Error = String; + type JsEvent = JsNumber; + + fn perform(&self) -> Result { + Err(format!("I am a failing task")) + } + + fn complete<'a, T: Scope<'a>>(self, _: &'a mut T, result: Result) -> JsResult { + JsError::throw(Kind::Error, &result.unwrap_err()) + } +} + +pub fn perform_failing_task(call: Call) -> JsResult { + let f = call.arguments.require(call.scope, 0)?.check::()?; + FailureTask.schedule(f); + Ok(JsUndefined::new()) +} diff --git a/tests/native/src/lib.rs b/tests/native/src/lib.rs index b1fa1738b..5a46f32cb 100644 --- a/tests/native/src/lib.rs +++ b/tests/native/src/lib.rs @@ -8,6 +8,7 @@ mod js { pub mod objects; pub mod functions; pub mod classes; + pub mod tasks; } use js::strings::return_js_string; @@ -16,53 +17,57 @@ use js::arrays::*; use js::objects::*; use js::functions::*; use js::classes::*; +use js::tasks::*; use neon::mem::Handle; use neon::js::{JsFunction, Object}; use neon::js::class::{Class, JsClass}; register_module!(m, { - try!(m.export("return_js_string", return_js_string)); + m.export("return_js_string", return_js_string)?; - try!(m.export("return_js_number", return_js_number)); - try!(m.export("return_large_js_number", return_large_js_number)); - try!(m.export("return_negative_js_number", return_negative_js_number)); - try!(m.export("return_float_js_number", return_float_js_number)); - try!(m.export("return_negative_float_js_number", return_negative_float_js_number)); - try!(m.export("accept_and_return_js_number", accept_and_return_js_number)); - try!(m.export("accept_and_return_large_js_number", accept_and_return_large_js_number)); - try!(m.export("accept_and_return_float_js_number", accept_and_return_float_js_number)); - try!(m.export("accept_and_return_negative_js_number", accept_and_return_negative_js_number)); + m.export("return_js_number", return_js_number)?; + m.export("return_large_js_number", return_large_js_number)?; + m.export("return_negative_js_number", return_negative_js_number)?; + m.export("return_float_js_number", return_float_js_number)?; + m.export("return_negative_float_js_number", return_negative_float_js_number)?; + m.export("accept_and_return_js_number", accept_and_return_js_number)?; + m.export("accept_and_return_large_js_number", accept_and_return_large_js_number)?; + m.export("accept_and_return_float_js_number", accept_and_return_float_js_number)?; + m.export("accept_and_return_negative_js_number", accept_and_return_negative_js_number)?; - try!(m.export("return_js_array", return_js_array)); - try!(m.export("return_js_array_with_integer", return_js_array_with_integer)); - try!(m.export("return_js_array_with_string", return_js_array_with_string)); + m.export("return_js_array", return_js_array)?; + m.export("return_js_array_with_integer", return_js_array_with_integer)?; + m.export("return_js_array_with_string", return_js_array_with_string)?; - try!(m.export("return_js_object", return_js_object)); - try!(m.export("return_js_object_with_integer", return_js_object_with_integer)); - try!(m.export("return_js_object_with_string", return_js_object_with_string)); - try!(m.export("return_js_object_with_mixed_content", return_js_object_with_mixed_content)); + m.export("return_js_object", return_js_object)?; + m.export("return_js_object_with_integer", return_js_object_with_integer)?; + m.export("return_js_object_with_string", return_js_object_with_string)?; + m.export("return_js_object_with_mixed_content", return_js_object_with_mixed_content)?; - try!(m.export("return_js_function", return_js_function)); - try!(m.export("call_js_function", call_js_function)); - try!(m.export("construct_js_function", construct_js_function)); + m.export("return_js_function", return_js_function)?; + m.export("call_js_function", call_js_function)?; + m.export("construct_js_function", construct_js_function)?; - try!(m.export("check_string_and_number", check_string_and_number)); + m.export("check_string_and_number", check_string_and_number)?; - try!(m.export("panic", panic)); - try!(m.export("panic_after_throw", panic_after_throw)); + m.export("perform_async_task", perform_async_task)?; + m.export("perform_failing_task", perform_failing_task)?; - let class: Handle> = try!(JsUser::class(m.scope)); - let constructor: Handle> = try!(class.constructor(m.scope)); - try!(m.exports.set("User", constructor)); + m.export("panic", panic)?; + m.export("panic_after_throw", panic_after_throw)?; - let class: Handle> = try!(JsPanickyAllocator::class(m.scope)); - let constructor: Handle> = try!(class.constructor(m.scope)); - try!(m.exports.set("PanickyAllocator", constructor)); + let class: Handle> = JsUser::class(m.scope)?; + let constructor: Handle> = class.constructor(m.scope)?; + m.exports.set("User", constructor)?; - let class: Handle> = try!(JsPanickyConstructor::class(m.scope)); - let constructor: Handle> = try!(class.constructor(m.scope)); - try!(m.exports.set("PanickyConstructor", constructor)); + let class: Handle> = JsPanickyAllocator::class(m.scope)?; + let constructor: Handle> = class.constructor(m.scope)?; + m.exports.set("PanickyAllocator", constructor)?; + + let class: Handle> = JsPanickyConstructor::class(m.scope)?; + let constructor: Handle> = class.constructor(m.scope)?; + m.exports.set("PanickyConstructor", constructor)?; Ok(()) });