#ifndef CALLBACK_BRIDGE_H #define CALLBACK_BRIDGE_H #include #include #include #include #define COMMA , template class CallbackBridge { public: CallbackBridge(v8::Local, bool); virtual ~CallbackBridge(); // Executes the callback T operator()(std::vector); protected: // We will expose a bridge object to the JS callback that wraps this instance so we don't loose context. // This is the V8 constructor for such objects. static Nan::MaybeLocal get_wrapper_constructor(); static void async_gone(uv_handle_t *handle); static NAN_METHOD(New); static NAN_METHOD(ReturnCallback); static Nan::Persistent wrapper_constructor; Nan::Persistent wrapper; // The callback that will get called in the main thread after the worker thread used for the sass // compilation step makes a call to uv_async_send() static void dispatched_async_uv_callback(uv_async_t*); // The V8 values sent to our ReturnCallback must be read on the main thread not the sass worker thread. // This gives a chance to specialized subclasses to transform those values into whatever makes sense to // sass before we resume the worker thread. virtual T post_process_return_value(v8::Local) const =0; virtual std::vector> pre_process_args(std::vector) const =0; Nan::Callback* callback; Nan::AsyncResource* async_resource; bool is_sync; uv_mutex_t cv_mutex; uv_cond_t condition_variable; uv_async_t *async; std::vector argv; bool has_returned; T return_value; }; template Nan::Persistent CallbackBridge::wrapper_constructor; template CallbackBridge::CallbackBridge(v8::Local callback, bool is_sync) : callback(new Nan::Callback(callback)), is_sync(is_sync) { /* * This is invoked from the main JavaScript thread. * V8 context is available. */ Nan::HandleScope scope; uv_mutex_init(&this->cv_mutex); uv_cond_init(&this->condition_variable); if (!is_sync) { this->async = new uv_async_t; this->async->data = (void*) this; uv_async_init(uv_default_loop(), this->async, (uv_async_cb) dispatched_async_uv_callback); this->async_resource = new Nan::AsyncResource("node-sass:CallbackBridge"); } v8::Local func = CallbackBridge::get_wrapper_constructor().ToLocalChecked(); wrapper.Reset(Nan::NewInstance(func).ToLocalChecked()); Nan::SetInternalFieldPointer(Nan::New(wrapper), 0, this); } template CallbackBridge::~CallbackBridge() { delete this->callback; this->wrapper.Reset(); uv_cond_destroy(&this->condition_variable); uv_mutex_destroy(&this->cv_mutex); if (!is_sync) { uv_close((uv_handle_t*)this->async, &async_gone); delete this->async_resource; } } template T CallbackBridge::operator()(std::vector argv) { // argv.push_back(wrapper); if (this->is_sync) { /* * This is invoked from the main JavaScript thread. * V8 context is available. * * Establish Local<> scope for all functions * from types invoked by pre_process_args() and * post_process_args(). */ Nan::HandleScope scope; Nan::TryCatch try_catch; std::vector> argv_v8 = pre_process_args(argv); if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } argv_v8.push_back(Nan::New(wrapper)); return this->post_process_return_value( Nan::Call(*this->callback, argv_v8.size(), &argv_v8[0]).ToLocalChecked() ); } else { /* * This is invoked from the worker thread. * No V8 context and functions available. * Just wait for response from asynchronously * scheduled JavaScript code * * XXX Issue #1048: We block here even if the * event loop stops and the callback * would never be executed. * XXX Issue #857: By waiting here we occupy * one of the threads taken from the * uv threadpool. Might deadlock if * async I/O executed from JavaScript callbacks. */ this->argv = argv; uv_mutex_lock(&this->cv_mutex); this->has_returned = false; uv_async_send(this->async); while (!this->has_returned) { uv_cond_wait(&this->condition_variable, &this->cv_mutex); } uv_mutex_unlock(&this->cv_mutex); return this->return_value; } } template void CallbackBridge::dispatched_async_uv_callback(uv_async_t *req) { CallbackBridge* bridge = static_cast(req->data); /* * Function scheduled via uv_async mechanism, therefore * it is invoked from the main JavaScript thread. * V8 context is available. * * Establish Local<> scope for all functions * from types invoked by pre_process_args() and * post_process_args(). */ Nan::HandleScope scope; Nan::TryCatch try_catch; std::vector> argv_v8 = bridge->pre_process_args(bridge->argv); if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } argv_v8.push_back(Nan::New(bridge->wrapper)); bridge->callback->Call(argv_v8.size(), &argv_v8[0], bridge->async_resource); if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } } template NAN_METHOD(CallbackBridge::ReturnCallback) { /* * Callback function invoked by the user code. * It is invoked from the main JavaScript thread. * V8 context is available. * * Implicit Local<> handle scope created by NAN_METHOD(.) */ CallbackBridge* bridge = static_cast*>(Nan::GetInternalFieldPointer(info.This(), 0)); Nan::TryCatch try_catch; bridge->return_value = bridge->post_process_return_value(info[0]); { uv_mutex_lock(&bridge->cv_mutex); bridge->has_returned = true; uv_mutex_unlock(&bridge->cv_mutex); } uv_cond_broadcast(&bridge->condition_variable); if (try_catch.HasCaught()) { Nan::FatalException(try_catch); } } template Nan::MaybeLocal CallbackBridge::get_wrapper_constructor() { /* Uses handle scope created in the CallbackBridge constructor */ if (wrapper_constructor.IsEmpty()) { v8::Local tpl = Nan::New(New); tpl->SetClassName(Nan::New("CallbackBridge").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); Nan::SetPrototypeTemplate(tpl, "success", Nan::New(ReturnCallback) ); wrapper_constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked()); } return Nan::New(wrapper_constructor); } template NAN_METHOD(CallbackBridge::New) { info.GetReturnValue().Set(info.This()); } template void CallbackBridge::async_gone(uv_handle_t *handle) { delete (uv_async_t *)handle; } #endif