Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task API for running background tasks in the libuv thread pool #214

Merged
merged 3 commits into from
Aug 21, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/neon-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ pub mod mem;
pub mod fun;
pub mod convert;
pub mod class;
pub mod task;
7 changes: 7 additions & 0 deletions crates/neon-runtime/src/neon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<v8::Value> *info, v8::Local<v8::Value> value) {
info->GetReturnValue().Set(value);
Expand Down Expand Up @@ -527,3 +528,9 @@ extern "C" void Neon_Error_ThrowSyntaxErrorFromCString(const char *msg) {
extern "C" bool Neon_Mem_SameHandle(v8::Local<v8::Value> v1, v8::Local<v8::Value> v2) {
return v1 == v2;
}

extern "C" void Neon_Task_Schedule(void *task, Neon_TaskPerformCallback perform, Neon_TaskCompleteCallback complete, v8::Local<v8::Function> callback) {
v8::Isolate *isolate = v8::Isolate::GetCurrent();
neon::Task *internal_task = new neon::Task(isolate, task, perform, complete, callback);
neon::queue_task(internal_task);
}
5 changes: 5 additions & 0 deletions crates/neon-runtime/src/neon.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ extern "C" {
void Neon_Error_ThrowSyntaxErrorFromCString(const char *msg);

bool Neon_Mem_SameHandle(v8::Local<v8::Value> v1, v8::Local<v8::Value> v2);

typedef void* (*Neon_TaskPerformCallback)(void *);
typedef void (*Neon_TaskCompleteCallback)(void *, void *, v8::Local<v8::Value> *out);

void Neon_Task_Schedule(void *task, Neon_TaskPerformCallback perform, Neon_TaskCompleteCallback complete, v8::Local<v8::Function> callback);
}

#endif
93 changes: 93 additions & 0 deletions crates/neon-runtime/src/neon_task.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#ifndef NEON_TASK_H_
#define NEON_TASK_H_

#include <nan.h>
#include <uv.h>
#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<v8::Function> callback)
: isolate_(isolate),
rust_task_(rust_task),
perform_(perform),
complete_(complete)
{
request_.data = this;
result_ = nullptr;
callback_.Reset(isolate, callback);
}

~Task() {
callback_.Reset();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly necessary, ~Persistent() does that by default.

}

void execute() {
result_ = perform_(rust_task_);
}

void complete() {
Nan::HandleScope scope;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should enter an Isolate::Scope and Context::Scope here:

v8::Isolate::Scope isolate_scope(isolate_);
v8::HandleScope handle_scope(isolate_);  // Or Nan::HandleScope
v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
v8::Context::Scope context_scope(context);
// ...

Store the context when the task is created because it can change.

Using v8::Isolate::GetCurrentContext() in this method is not sound because it's possible no context has been entered or it's a different one from the one that created the task.


v8::TryCatch trycatch(isolate_);

v8::Local<v8::Value> argv[2];
v8::Local<v8::Value> completion;

complete_(rust_task_, result_, &completion);

if (trycatch.HasCaught()) {
argv[0] = trycatch.Exception();
argv[1] = v8::Undefined(isolate_);
} else {
argv[0] = v8::Null(isolate_);
argv[1] = completion;
}

v8::Local<v8::Function> callback = v8::Local<v8::Function>::New(isolate_, callback_);
callback->Call(isolate_->GetCurrentContext(), v8::Null(isolate_), 2, argv);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If try_catch.HasCaught() is true, there is still an exception pending here and you can't call into the VM. If you scope the TryCatch in curly braces, it will have been cleared.

Whether it's a good idea to catch exceptions and continue is up for debate, though.

}

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<v8::Function> callback_;
};

void execute_task(uv_work_t *request) {
Task *task = static_cast<Task*>(request->data);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, we use a container_of-like macro for that in libuv and node.js. Saves one level of pointer indirection.

task->execute();
}

void complete_task(uv_work_t *request) {
Task *task = static_cast<Task*>(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
15 changes: 15 additions & 0 deletions crates/neon-runtime/src/task.rs
Original file line number Diff line number Diff line change
@@ -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);

}
7 changes: 7 additions & 0 deletions src/internal/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<ClassMap>) {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
53 changes: 53 additions & 0 deletions src/task.rs
Original file line number Diff line number Diff line change
@@ -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<Self::Output, Self::Error>;

fn complete<'a, T: Scope<'a>>(self, scope: &'a mut T, result: Result<Self::Output, Self::Error>) -> JsResult<Self::JsEvent>;

fn schedule(self, callback: Handle<JsFunction>) {
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::<Self>,
complete_task::<Self>,
callback_raw);
}
}
}

unsafe extern "C" fn perform_task<T: Task>(task: *mut c_void) -> *mut c_void {
let task: Box<T> = 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<T: Task>(task: *mut c_void, result: *mut c_void, out: &mut raw::Local) {
let result: Result<T::Output, T::Error> = *Box::from_raw(mem::transmute(result));
let task: Box<T> = 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();
}
}