#include "coroutine.h"
|
#include "v8-version.h"
|
#include <assert.h>
|
#include <node.h>
|
#include <node_version.h>
|
|
#include <vector>
|
#include <iostream>
|
|
#define THROW(x, m) return uni::Return(uni::ThrowException(Isolate::GetCurrent(), x(uni::NewLatin1String(Isolate::GetCurrent(), m))), args)
|
|
using namespace std;
|
using namespace v8;
|
|
// Handle legacy V8 API
|
namespace uni {
|
#if V8_AT_LEAST(5, 3)
|
// Actually 5.2.244
|
// ..or maybe actually 5.2.49
|
template <void (*F)(void*), class P>
|
void WeakCallbackShim(const WeakCallbackInfo<P>& data) {
|
F(data.GetParameter());
|
}
|
|
template <void (*F)(void*), class T, typename P>
|
void MakeWeak(Isolate* isolate, Persistent<T>& handle, P* val) {
|
handle.SetWeak(val, WeakCallbackShim<F, P>, WeakCallbackType::kFinalizer);
|
}
|
#elif V8_AT_LEAST(3, 26)
|
template <void (*F)(void*), class T, typename P>
|
void WeakCallbackShim(const v8::WeakCallbackData<T, P>& data) {
|
F(data.GetParameter());
|
}
|
|
template <void (*F)(void*), class T, typename P>
|
void MakeWeak(Isolate* isolate, Persistent<T>& handle, P* val) {
|
handle.SetWeak(val, WeakCallbackShim<F>);
|
}
|
#else
|
template <void (*F)(void*)>
|
void WeakCallbackShim(Persistent<Value> value, void* data) {
|
F(data);
|
}
|
template <void (*F)(void*), class T, typename P>
|
void MakeWeak(Isolate* isolate, Persistent<T>& handle, P* val) {
|
handle.MakeWeak(val, WeakCallbackShim<F>);
|
}
|
#endif
|
|
#if V8_AT_LEAST(3, 28)
|
class TryCatch : public v8::TryCatch {
|
public: TryCatch(Isolate* isolate) : v8::TryCatch(isolate) {}
|
};
|
#else
|
class TryCatch : public v8::TryCatch {
|
public: TryCatch(Isolate* isolate) : v8::TryCatch() {}
|
};
|
#endif
|
|
#if V8_AT_LEAST(4, 4)
|
Local<String> NewLatin1String(Isolate* isolate, const char* string) {
|
return String::NewFromOneByte(isolate, (const uint8_t*)string, NewStringType::kNormal).ToLocalChecked();
|
}
|
|
Local<String> NewLatin1Symbol(Isolate* isolate, const char* string) {
|
return String::NewFromOneByte(isolate, (const uint8_t*)string, NewStringType::kNormal).ToLocalChecked();
|
}
|
#elif V8_AT_LEAST(3, 26)
|
Handle<String> NewLatin1String(Isolate* isolate, const char* string) {
|
return String::NewFromOneByte(isolate, (const uint8_t*)string);
|
}
|
|
Handle<String> NewLatin1Symbol(Isolate* isolate, const char* string) {
|
return String::NewFromOneByte(isolate, (const uint8_t*)string);
|
}
|
#else
|
Handle<String> NewLatin1String(Isolate* isolate, const char* string) {
|
return String::New(string);
|
}
|
|
Handle<String> NewLatin1Symbol(Isolate* isolate, const char* string) {
|
return String::NewSymbol(string);
|
}
|
#endif
|
|
#if V8_AT_LEAST(4, 4)
|
Local<Function> GetFunction(Local<FunctionTemplate> tmpl) {
|
return tmpl->GetFunction(Isolate::GetCurrent()->GetCurrentContext()).ToLocalChecked();
|
}
|
|
Local<Value> Call(Local<Function> fn, Local<Object> recv, int argc, Local<Value> argv[]) {
|
Local<Value> result;
|
if (fn->Call(Isolate::GetCurrent()->GetCurrentContext(), recv, argc, argv).ToLocal(&result)) {
|
return result;
|
} else {
|
return {};
|
}
|
}
|
|
Local<Object> NewInstance(Isolate* isolate, Local<Function> fn, int argc, Local<Value> argv[]) {
|
return fn->NewInstance(isolate->GetCurrentContext(), argc, argv).ToLocalChecked();
|
}
|
#else
|
Local<Function> GetFunction(Local<FunctionTemplate> tmpl) {
|
return tmpl->GetFunction();
|
}
|
|
Local<Value> Call(Local<Function> fn, Local<Object> recv, int argc, Local<Value> argv[]) {
|
return fn->Call(recv, argc, argv);
|
}
|
|
Handle<Object> NewInstance(Isolate* isolate, Local<Function> fn, int argc, Local<Value> argv[]) {
|
return fn->NewInstance(argc, argv).ToLocalChecked();
|
}
|
#endif
|
|
#if V8_AT_LEAST(4, 4)
|
Local<Number> ToNumber(Local<Value> value) {
|
return value->ToNumber(Isolate::GetCurrent()->GetCurrentContext()).ToLocalChecked();
|
}
|
#else
|
Handle<Number> ToNumber(Local<Value> value) {
|
return value->ToNumber();
|
}
|
#endif
|
|
#if V8_AT_LEAST(6, 1)
|
Local<Value> GetStackTrace(TryCatch* try_catch, Local<Context> context) {
|
return try_catch->StackTrace(context).ToLocalChecked();
|
}
|
#else
|
Local<Value> GetStackTrace(TryCatch* try_catch, Handle<Context> context) {
|
return try_catch->StackTrace();
|
}
|
#endif
|
|
// Workaround for v8 issue #1180
|
// http://code.google.com/p/v8/issues/detail?id=1180
|
// NOTE: it's not clear if this is still necessary (perhaps Isolate::SetStackLimit could be used?)
|
#if V8_AT_LEAST(6, 1)
|
void fixStackLimit(Isolate* isolate, Local<Context> context) {
|
Script::Compile(context, uni::NewLatin1String(isolate, "void 0;")).ToLocalChecked();
|
}
|
#else
|
void fixStackLimit(Isolate* isolate, Handle<Context> context) {
|
Script::Compile(uni::NewLatin1String(isolate, "void 0;"));
|
}
|
#endif
|
|
#if V8_AT_LEAST(3, 26)
|
// Node v0.11.13+
|
typedef PropertyCallbackInfo<Value> GetterCallbackInfo;
|
typedef PropertyCallbackInfo<void> SetterCallbackInfo;
|
typedef void FunctionType;
|
typedef FunctionCallbackInfo<v8::Value> Arguments;
|
|
class HandleScope {
|
v8::HandleScope scope;
|
public: HandleScope(Isolate* isolate) : scope(isolate) {}
|
};
|
|
template <class T>
|
void Reset(Isolate* isolate, Persistent<T>& persistent, Local<T> handle) {
|
persistent.Reset(isolate, handle);
|
}
|
template <class T>
|
void Dispose(Isolate* isolate, Persistent<T>& handle) {
|
handle.Reset();
|
}
|
template <class T>
|
void ClearWeak(Isolate* isolate, Persistent<T>& handle) {
|
handle.ClearWeak(isolate);
|
}
|
|
template <class T>
|
void SetInternalPointer(Local<T> handle, int index, void* val) {
|
handle->SetAlignedPointerInInternalField(index, val);
|
}
|
template <class T>
|
void* GetInternalPointer(Local<T> handle, int index) {
|
return handle->GetAlignedPointerFromInternalField(index);
|
}
|
|
template <class T>
|
Local<T> Deref(Isolate* isolate, Persistent<T>& handle) {
|
return Local<T>::New(isolate, handle);
|
}
|
|
template <class T>
|
void Return(Local<T> handle, const Arguments& args) {
|
args.GetReturnValue().Set(handle);
|
}
|
template <class T>
|
void Return(Local<T> handle, GetterCallbackInfo info) {
|
info.GetReturnValue().Set(handle);
|
}
|
template <class T>
|
void Return(Persistent<T>& handle, GetterCallbackInfo info) {
|
info.GetReturnValue().Set(Local<T>::New(Isolate::GetCurrent(), handle));
|
}
|
|
Local<Value> ThrowException(Isolate* isolate, Local<Value> exception) {
|
return isolate->ThrowException(exception);
|
}
|
|
Local<Context> GetCurrentContext(Isolate* isolate) {
|
return isolate->GetCurrentContext();
|
}
|
|
Local<Primitive> Undefined(Isolate* isolate) {
|
return v8::Undefined(isolate);
|
}
|
|
Local<Boolean> NewBoolean(Isolate* isolate, bool value) {
|
return Boolean::New(isolate, value);
|
}
|
|
Local<Number> NewNumber(Isolate* isolate, double value) {
|
return Number::New(isolate, value);
|
}
|
|
Local<FunctionTemplate> NewFunctionTemplate(
|
Isolate* isolate,
|
FunctionCallback callback,
|
Local<Value> data = Local<Value>(),
|
Local<Signature> signature = Local<Signature>(),
|
int length = 0
|
) {
|
return FunctionTemplate::New(isolate, callback, data, signature, length);
|
}
|
|
Local<Signature> NewSignature(
|
Isolate* isolate,
|
Local<FunctionTemplate> receiver = Local<FunctionTemplate>()
|
) {
|
return Signature::New(isolate, receiver);
|
}
|
|
class ReverseIsolateScope {
|
Isolate* isolate;
|
public:
|
explicit inline ReverseIsolateScope(Isolate* isolate) : isolate(isolate) {
|
isolate->Exit();
|
}
|
inline ~ReverseIsolateScope() {
|
isolate->Enter();
|
}
|
};
|
|
void AdjustAmountOfExternalAllocatedMemory(Isolate* isolate, int64_t change_in_bytes) {
|
isolate->AdjustAmountOfExternalAllocatedMemory(change_in_bytes);
|
}
|
#else
|
// Node v0.10.x and lower
|
typedef AccessorInfo GetterCallbackInfo;
|
typedef AccessorInfo SetterCallbackInfo;
|
typedef Handle<Value> FunctionType;
|
typedef Arguments Arguments;
|
|
class HandleScope {
|
v8::HandleScope scope;
|
public: HandleScope(Isolate* isolate) {}
|
};
|
|
template <class T>
|
void Reset(Isolate* isolate, Persistent<T>& persistent, Handle<T> handle) {
|
persistent = Persistent<T>::New(handle);
|
}
|
template <class T>
|
void Dispose(Isolate* isolate, Persistent<T>& handle) {
|
handle.Dispose();
|
}
|
|
template <class T>
|
void ClearWeak(Isolate* isolate, Persistent<T>& handle) {
|
handle.ClearWeak();
|
}
|
|
template <class T>
|
void SetInternalPointer(Handle<T> handle, int index, void* val) {
|
handle->SetPointerInInternalField(index, val);
|
}
|
template <class T>
|
void* GetInternalPointer(Handle<T> handle, int index) {
|
return handle->GetPointerFromInternalField(index);
|
}
|
|
template <class T>
|
Handle<T> Deref(Isolate* isolate, Persistent<T>& handle) {
|
return Local<T>::New(handle);
|
}
|
|
Handle<Value> Return(Handle<Value> handle, GetterCallbackInfo info) {
|
return handle;
|
}
|
|
Handle<Value> Return(Handle<Value> handle, const Arguments& args) {
|
return handle;
|
}
|
|
Handle<Value> ThrowException(Isolate* isolate, Handle<Value> exception) {
|
return ThrowException(exception);
|
}
|
|
Handle<Context> GetCurrentContext(Isolate* isolate) {
|
return Context::GetCurrent();
|
}
|
|
Handle<Primitive> Undefined(Isolate* isolate) {
|
return v8::Undefined();
|
}
|
|
Handle<Boolean> NewBoolean(Isolate* isolate, bool value) {
|
return Boolean::New(value);
|
}
|
|
Handle<Number> NewNumber(Isolate* isolate, double value) {
|
return Number::New(value);
|
}
|
|
Handle<FunctionTemplate> NewFunctionTemplate(
|
Isolate* isolate,
|
InvocationCallback callback,
|
Handle<Value> data = Handle<Value>(),
|
Handle<Signature> signature = Handle<Signature>(),
|
int length = 0
|
) {
|
return FunctionTemplate::New(callback, data, signature);
|
}
|
|
Handle<Signature> NewSignature(
|
Isolate* isolate,
|
Handle<FunctionTemplate> receiver = Handle<FunctionTemplate>(),
|
int argc = 0,
|
Handle<FunctionTemplate> argv[] = 0
|
) {
|
return Signature::New(receiver, argc, argv);
|
}
|
|
class ReverseIsolateScope {
|
public: explicit inline ReverseIsolateScope(Isolate* isolate) {}
|
};
|
|
void AdjustAmountOfExternalAllocatedMemory(Isolate* isolate, int64_t change_in_bytes) {
|
V8::AdjustAmountOfExternalAllocatedMemory(change_in_bytes);
|
}
|
#endif
|
|
#if V8_AT_LEAST(6, 1)
|
void SetAccessor(
|
Isolate* isolate, Local<Object> object, Local<String> name,
|
FunctionType (*getter)(Local<String>, const GetterCallbackInfo&),
|
void (*setter)(Local<String> property, Local<Value> value, const SetterCallbackInfo&) = 0
|
) {
|
object->SetAccessor(isolate->GetCurrentContext(), name, (AccessorNameGetterCallback)getter, (AccessorNameSetterCallback)setter).ToChecked();
|
}
|
#elif V8_AT_LEAST(4, 4)
|
void SetAccessor(
|
Isolate* isolate, Local<Object> object, Local<String> name,
|
FunctionType (*getter)(Local<String>, const GetterCallbackInfo&),
|
void (*setter)(Local<String> property, Local<Value> value, const SetterCallbackInfo&) = 0
|
) {
|
object->SetAccessor(isolate->GetCurrentContext(), name, (AccessorNameGetterCallback)getter, (AccessorNameSetterCallback)setter);
|
}
|
#else
|
void SetAccessor(
|
Isolate* isolate, Local<Object> object, Local<String> name,
|
FunctionType (*getter)(Local<String>, const GetterCallbackInfo&),
|
void (*setter)(Local<String> property, Local<Value> value, const SetterCallbackInfo&) = 0
|
) {
|
object->SetAccessor(name, (AccessorNameGetterCallback)getter, (AccessorNameSetterCallback)setter);
|
}
|
#endif
|
|
#if V8_AT_LEAST(3, 29)
|
// This was actually added in 3.29.67
|
void SetStackGuard(Isolate* isolate, void* guard) {
|
isolate->SetStackLimit(reinterpret_cast<uintptr_t>(guard));
|
}
|
#elif V8_AT_LEAST(3, 26)
|
void SetStackGuard(Isolate* isolate, void* guard) {
|
ResourceConstraints constraints;
|
constraints.set_stack_limit(reinterpret_cast<uint32_t*>(guard));
|
v8::SetResourceConstraints(isolate, &constraints);
|
}
|
#else
|
// Extra padding for old versions of v8. Shit's fucked.
|
void SetStackGuard(Isolate* isolate, void* guard) {
|
ResourceConstraints constraints;
|
constraints.set_stack_limit(
|
reinterpret_cast<uint32_t*>(guard) + 18 * 1024
|
);
|
v8::SetResourceConstraints(&constraints);
|
}
|
#endif
|
}
|
|
class Fiber {
|
|
private:
|
static Locker* global_locker; // Node does not use locks or threads, so we need a global lock
|
static Persistent<FunctionTemplate> tmpl;
|
static Persistent<Function> fiber_object;
|
static Fiber* current;
|
static vector<Fiber*> orphaned_fibers;
|
static Persistent<Value> fatal_stack;
|
|
Isolate* isolate;
|
Persistent<Object> handle;
|
Persistent<Function> cb;
|
Persistent<Context> v8_context;
|
Persistent<Value> zombie_exception;
|
Persistent<Value> yielded;
|
bool yielded_exception;
|
Coroutine* entry_fiber;
|
Coroutine* this_fiber;
|
bool started;
|
bool yielding;
|
bool zombie;
|
bool resetting;
|
|
static Fiber& Unwrap(Local<Object> handle) {
|
assert(!handle.IsEmpty());
|
assert(handle->InternalFieldCount() == 1);
|
return *static_cast<Fiber*>(uni::GetInternalPointer(handle, 0));
|
}
|
|
Fiber(Local<Object> handle, Local<Function> cb, Local<Context> v8_context) :
|
isolate(Isolate::GetCurrent()),
|
started(false),
|
yielding(false),
|
zombie(false),
|
resetting(false) {
|
uni::Reset(isolate, this->handle, handle);
|
uni::Reset(isolate, this->cb, cb);
|
uni::Reset(isolate, this->v8_context, v8_context);
|
|
MakeWeak();
|
uni::SetInternalPointer(handle, 0, this);
|
}
|
|
virtual ~Fiber() {
|
assert(!this->started);
|
uni::Dispose(isolate, handle);
|
uni::Dispose(isolate, cb);
|
uni::Dispose(isolate, v8_context);
|
}
|
|
/**
|
* Call MakeWeak if it's ok for v8 to garbage collect this Fiber.
|
* i.e. After fiber completes, while yielded, or before started
|
*/
|
void MakeWeak() {
|
uni::MakeWeak<WeakCallback>(isolate, handle, (void*)this);
|
}
|
|
/**
|
* And call ClearWeak if it's not ok for v8 to garbage collect this Fiber.
|
* i.e. While running.
|
*/
|
void ClearWeak() {
|
handle.ClearWeak();
|
}
|
|
/**
|
* Called when there are no more references to this object in Javascript. If this happens and
|
* the fiber is currently suspended we'll unwind the fiber's stack by throwing exceptions in
|
* order to clear all references.
|
*/
|
static void WeakCallback(void* data) {
|
Fiber& that = *static_cast<Fiber*>(data);
|
#if !V8_AT_LEAST(7, 4)
|
// Deprecated in 0781f42b6
|
assert(that.handle.IsNearDeath());
|
#endif
|
assert(current != &that);
|
|
// We'll unwind running fibers later... doing it from the garbage collector is bad news.
|
if (that.started) {
|
assert(that.yielding);
|
orphaned_fibers.push_back(&that);
|
that.ClearWeak();
|
return;
|
}
|
|
delete &that;
|
}
|
|
/**
|
* When the v8 garbage collector notifies us about dying fibers instead of unwindng their
|
* stack as soon as possible we put them aside to unwind later. Unwinding from the garbage
|
* collector leads to exponential time garbage collections if there are many orphaned Fibers,
|
* there's also the possibility of running out of stack space. It's generally bad news.
|
*
|
* So instead we have this function to clean up all the fibers after the garbage collection
|
* has finished.
|
*/
|
static void DestroyOrphans() {
|
if (orphaned_fibers.empty()) {
|
return;
|
}
|
vector<Fiber*> orphans(orphaned_fibers);
|
orphaned_fibers.clear();
|
|
for (vector<Fiber*>::iterator ii = orphans.begin(); ii != orphans.end(); ++ii) {
|
Fiber& that = **ii;
|
that.UnwindStack();
|
|
if (that.yielded_exception) {
|
// If you throw an exception from a fiber that's being garbage collected there's no way
|
// to bubble that exception up to the application.
|
auto stack(uni::Deref(that.isolate, fatal_stack));
|
cerr <<
|
"An exception was thrown from a Fiber which was being garbage collected. This error "
|
"can not be gracefully recovered from. The only acceptable behavior is to terminate "
|
"this application. The exception appears below:\n\n"
|
<<*stack <<"\n";
|
exit(1);
|
} else {
|
uni::Dispose(that.isolate, fatal_stack);
|
}
|
|
uni::Dispose(that.isolate, that.yielded);
|
that.MakeWeak();
|
}
|
}
|
|
/**
|
* Instantiate a new Fiber object. When a fiber is created it only grabs a handle to the
|
* callback; it doesn't create any new contexts until run() is called.
|
*/
|
static uni::FunctionType New(const uni::Arguments& args) {
|
if (args.Length() != 1) {
|
THROW(Exception::TypeError, "Fiber expects 1 argument");
|
} else if (!args[0]->IsFunction()) {
|
THROW(Exception::TypeError, "Fiber expects a function");
|
} else if (!args.IsConstructCall()) {
|
Local<Value> argv[1] = { args[0] };
|
return uni::Return(uni::NewInstance(Isolate::GetCurrent(), uni::GetFunction(uni::Deref(Isolate::GetCurrent(), tmpl)), 1, argv), args);
|
}
|
|
Local<Function> fn = Local<Function>::Cast(args[0]);
|
new Fiber(args.This(), fn, uni::GetCurrentContext(Isolate::GetCurrent()));
|
return uni::Return(args.This(), args);
|
}
|
|
/**
|
* Begin or resume the current fiber. If the fiber is not currently running a new context will
|
* be created and the callback will start. Otherwise we switch back into the exist context.
|
*/
|
static uni::FunctionType Run(const uni::Arguments& args) {
|
Fiber& that = Unwrap(args.Holder());
|
|
// There seems to be no better place to put this check..
|
DestroyOrphans();
|
|
if (that.started && !that.yielding) {
|
THROW(Exception::Error, "This Fiber is already running");
|
} else if (args.Length() > 1) {
|
THROW(Exception::TypeError, "run() excepts 1 or no arguments");
|
}
|
|
if (!that.started) {
|
// Create a new context with entry point `Fiber::RunFiber()`.
|
void** data = new void*[2];
|
data[0] = (void*)&args;
|
data[1] = &that;
|
that.this_fiber = Coroutine::create_fiber((void (*)(void*))RunFiber, data);
|
if (!that.this_fiber) {
|
delete[] data;
|
THROW(Exception::RangeError, "Out of memory");
|
}
|
that.started = true;
|
} else {
|
// If the fiber is currently running put the first parameter to `run()` on `yielded`, then
|
// the pending call to `yield()` will return that value. `yielded` in this case is just a
|
// misnomer, we're just reusing the same handle.
|
that.yielded_exception = false;
|
if (args.Length()) {
|
uni::Reset(that.isolate, that.yielded, args[0]);
|
} else {
|
uni::Reset<Value>(that.isolate, that.yielded, uni::Undefined(that.isolate));
|
}
|
}
|
that.SwapContext();
|
return uni::Return(that.ReturnYielded(), args);
|
}
|
|
/**
|
* Throw an exception into a currently yielding fiber.
|
*/
|
static uni::FunctionType ThrowInto(const uni::Arguments& args) {
|
Fiber& that = Unwrap(args.Holder());
|
|
if (!that.yielding) {
|
THROW(Exception::Error, "This Fiber is not yielding");
|
} else if (args.Length() == 0) {
|
uni::Reset<Value>(that.isolate, that.yielded, uni::Undefined(that.isolate));
|
} else if (args.Length() == 1) {
|
uni::Reset(that.isolate, that.yielded, args[0]);
|
} else {
|
THROW(Exception::TypeError, "throwInto() expects 1 or no arguments");
|
}
|
that.yielded_exception = true;
|
that.SwapContext();
|
return uni::Return(that.ReturnYielded(), args);
|
}
|
|
/**
|
* Unwinds a currently running fiber. If the fiber is not running then this function has no
|
* effect.
|
*/
|
static uni::FunctionType Reset(const uni::Arguments& args) {
|
Fiber& that = Unwrap(args.Holder());
|
|
if (!that.started) {
|
return uni::Return(uni::Undefined(that.isolate), args);
|
} else if (!that.yielding) {
|
THROW(Exception::Error, "This Fiber is not yielding");
|
} else if (args.Length()) {
|
THROW(Exception::TypeError, "reset() expects no arguments");
|
}
|
|
that.resetting = true;
|
that.UnwindStack();
|
that.resetting = false;
|
that.MakeWeak();
|
|
Local<Value> val = uni::Deref(that.isolate, that.yielded);
|
uni::Dispose(that.isolate, that.yielded);
|
if (that.yielded_exception) {
|
return uni::Return(uni::ThrowException(that.isolate, val), args);
|
} else {
|
return uni::Return(val, args);
|
}
|
}
|
|
/**
|
* Turns the fiber into a zombie and unwinds its whole stack.
|
*
|
* After calling this function you must either destroy this fiber or call MakeWeak() or it will
|
* be leaked.
|
*/
|
void UnwindStack() {
|
assert(!zombie);
|
assert(started);
|
assert(yielding);
|
zombie = true;
|
|
// Setup an exception which will be thrown and rethrown from Fiber::Yield()
|
Local<Value> zombie_exception = Exception::Error(uni::NewLatin1String(isolate, "This Fiber is a zombie"));
|
uni::Reset(isolate, this->zombie_exception, zombie_exception);
|
uni::Reset(isolate, yielded, zombie_exception);
|
yielded_exception = true;
|
|
// Swap context back to Fiber::Yield() which will throw an exception to unwind the stack.
|
// Futher calls to yield from this fiber will rethrow the same exception.
|
SwapContext();
|
assert(!started);
|
zombie = false;
|
|
// Make sure this is the exception we threw
|
if (yielded_exception && yielded == zombie_exception) {
|
yielded_exception = false;
|
uni::Dispose(isolate, yielded);
|
uni::Reset<Value>(isolate, yielded, uni::Undefined(isolate));
|
}
|
uni::Dispose(isolate, this->zombie_exception);
|
}
|
|
/**
|
* Common logic between Run(), ThrowInto(), and UnwindStack(). This is essentially just a
|
* wrapper around this->fiber->() which also handles all the bookkeeping needed.
|
*/
|
void SwapContext() {
|
|
entry_fiber = &Coroutine::current();
|
Fiber* last_fiber = current;
|
current = this;
|
|
// This will jump into either `RunFiber()` or `Yield()`, depending on if the fiber was
|
// already running.
|
{
|
Unlocker unlocker(isolate);
|
uni::ReverseIsolateScope isolate_scope(isolate);
|
this_fiber->run();
|
}
|
|
// At this point the fiber either returned or called `yield()`.
|
current = last_fiber;
|
}
|
|
/**
|
* Grabs and resets this fiber's yielded value.
|
*/
|
Local<Value> ReturnYielded() {
|
Local<Value> val = uni::Deref(isolate, yielded);
|
uni::Dispose(isolate, yielded);
|
if (yielded_exception) {
|
return uni::ThrowException(isolate, val);
|
} else {
|
return val;
|
}
|
}
|
|
/**
|
* This is the entry point for a new fiber, from `run()`.
|
*/
|
static void RunFiber(void** data) {
|
const uni::Arguments* args = (const uni::Arguments*)data[0];
|
Fiber& that = *(Fiber*)data[1];
|
delete[] data;
|
|
// New C scope so that the stack-allocated objects will be destroyed before calling
|
// Coroutine::finish, because that function may not return, in which case the destructors in
|
// this function won't be called.
|
{
|
Locker locker(that.isolate);
|
Isolate::Scope isolate_scope(that.isolate);
|
uni::HandleScope scope(that.isolate);
|
|
// Set the stack guard for this "thread"; allow 6k of padding past the JS limit for
|
// native v8 code to run
|
uni::SetStackGuard(that.isolate, reinterpret_cast<char*>(that.this_fiber->bottom()) + 1024 * 6);
|
|
uni::TryCatch try_catch(that.isolate);
|
that.ClearWeak();
|
Local<Context> v8_context = uni::Deref(that.isolate, that.v8_context);
|
v8_context->Enter();
|
|
uni::fixStackLimit(that.isolate, v8_context);
|
|
Local<Value> yielded;
|
if (args->Length()) {
|
Local<Value> argv[1] = { (*args)[0] };
|
yielded = uni::Call(uni::Deref(that.isolate, that.cb), v8_context->Global(), 1, argv);
|
} else {
|
yielded = uni::Call(uni::Deref(that.isolate, that.cb), v8_context->Global(), 0, NULL);
|
}
|
|
if (try_catch.HasCaught()) {
|
uni::Reset(that.isolate, that.yielded, try_catch.Exception());
|
that.yielded_exception = true;
|
if (that.zombie && !that.resetting && !uni::Deref(that.isolate, that.yielded)->StrictEquals(uni::Deref(that.isolate, that.zombie_exception))) {
|
// Throwing an exception from a garbage sweep
|
uni::Reset(that.isolate, fatal_stack, uni::GetStackTrace(&try_catch, v8_context));
|
}
|
} else {
|
uni::Reset(that.isolate, that.yielded, yielded);
|
that.yielded_exception = false;
|
}
|
|
// Don't make weak until after notifying the garbage collector. Otherwise it may try and
|
// free this very fiber!
|
if (!that.zombie) {
|
that.MakeWeak();
|
}
|
|
// Now safe to leave the context, this stack is done with JS.
|
v8_context->Exit();
|
}
|
|
// The function returned (instead of yielding).
|
that.started = false;
|
that.this_fiber->finish(*that.entry_fiber, that.isolate);
|
}
|
|
/**
|
* Yield control back to the function that called `run()`. The first parameter to this function
|
* is returned from `run()`. The context is saved, to be later resumed from `run()`.
|
* note: sigh, there is a #define Yield() in WinBase.h on Windows
|
*/
|
static uni::FunctionType Yield_(const uni::Arguments& args) {
|
if (current == NULL) {
|
THROW(Exception::Error, "yield() called with no fiber running");
|
}
|
|
Fiber& that = *current;
|
|
if (that.zombie) {
|
return uni::Return(uni::ThrowException(that.isolate, uni::Deref(that.isolate, that.zombie_exception)), args);
|
} else if (args.Length() == 0) {
|
uni::Reset<Value>(that.isolate, that.yielded, Undefined(that.isolate));
|
} else if (args.Length() == 1) {
|
uni::Reset(that.isolate, that.yielded, args[0]);
|
} else {
|
THROW(Exception::TypeError, "yield() expects 1 or no arguments");
|
}
|
that.yielded_exception = false;
|
|
// While not running this can be garbage collected if no one has a handle.
|
that.MakeWeak();
|
|
// Return control back to `Fiber::run()`. While control is outside this function we mark it as
|
// ok to garbage collect. If no one ever has a handle to resume the function it's harmful to
|
// keep the handle around.
|
{
|
Unlocker unlocker(that.isolate);
|
uni::ReverseIsolateScope isolate_scope(that.isolate);
|
that.yielding = true;
|
that.entry_fiber->run();
|
that.yielding = false;
|
}
|
// Now `run()` has been called again.
|
|
// Don't garbage collect anymore!
|
that.ClearWeak();
|
|
// Return the yielded value
|
return uni::Return(that.ReturnYielded(), args);
|
}
|
|
/**
|
* Getters for `started`, and `current`.
|
*/
|
static uni::FunctionType GetStarted(Local<String> property, const uni::GetterCallbackInfo& info) {
|
if (info.This().IsEmpty() || info.This()->InternalFieldCount() != 1) {
|
return uni::Return(uni::Undefined(Isolate::GetCurrent()), info);
|
}
|
Fiber& that = Unwrap(info.This());
|
return uni::Return(uni::NewBoolean(that.isolate, that.started), info);
|
}
|
|
static uni::FunctionType GetCurrent(Local<String> property, const uni::GetterCallbackInfo& info) {
|
if (current) {
|
return uni::Return(current->handle, info);
|
} else {
|
return uni::Return(uni::Undefined(Isolate::GetCurrent()), info);
|
}
|
}
|
|
/**
|
* Allow access to coroutine pool size
|
*/
|
static uni::FunctionType GetPoolSize(Local<String> property, const uni::GetterCallbackInfo& info) {
|
return uni::Return(uni::NewNumber(Isolate::GetCurrent(), Coroutine::pool_size), info);
|
}
|
|
static void SetPoolSize(Local<String> property, Local<Value> value, const uni::SetterCallbackInfo& info) {
|
Coroutine::pool_size = uni::ToNumber(value)->Value();
|
}
|
|
/**
|
* Return number of fibers that have been created
|
*/
|
static uni::FunctionType GetFibersCreated(Local<String> property, const uni::GetterCallbackInfo& info) {
|
return uni::Return(uni::NewNumber(Isolate::GetCurrent(), Coroutine::coroutines_created()), info);
|
}
|
|
public:
|
/**
|
* Initialize the Fiber library.
|
*/
|
static void Init(Local<Object> target) {
|
// Use a locker which won't get destroyed when this library gets unloaded. This is a hack
|
// to prevent v8 from trying to clean up this "thread" while the whole application is
|
// shutting down. TODO: There's likely a better way to accomplish this, but since the
|
// application is going down lost memory isn't the end of the world. But with a regular lock
|
// there's seg faults when node shuts down.
|
Isolate* isolate = Isolate::GetCurrent();
|
Local<Context> context = isolate->GetCurrentContext();
|
global_locker = new Locker(isolate);
|
current = NULL;
|
|
// Fiber constructor
|
Local<FunctionTemplate> tmpl = uni::NewFunctionTemplate(isolate, New);
|
uni::Reset(isolate, Fiber::tmpl, tmpl);
|
tmpl->SetClassName(uni::NewLatin1Symbol(isolate, "Fiber"));
|
|
// Guard which only allows these methods to be called on a fiber; prevents
|
// `fiber.run.call({})` from seg faulting.
|
Local<Signature> sig = uni::NewSignature(isolate, tmpl);
|
tmpl->InstanceTemplate()->SetInternalFieldCount(1);
|
|
// Fiber.prototype
|
Local<ObjectTemplate> proto = tmpl->PrototypeTemplate();
|
proto->Set(uni::NewLatin1Symbol(isolate, "reset"),
|
uni::NewFunctionTemplate(isolate, Reset, Local<Value>(), sig));
|
proto->Set(uni::NewLatin1Symbol(isolate, "run"),
|
uni::NewFunctionTemplate(isolate, Run, Local<Value>(), sig));
|
proto->Set(uni::NewLatin1Symbol(isolate, "throwInto"),
|
uni::NewFunctionTemplate(isolate, ThrowInto, Local<Value>(), sig));
|
proto->SetAccessor(uni::NewLatin1Symbol(isolate, "started"), GetStarted);
|
|
// Global yield() function
|
Local<Function> yield = uni::GetFunction(uni::NewFunctionTemplate(isolate, Yield_));
|
Local<String> sym_yield = uni::NewLatin1Symbol(isolate, "yield");
|
target->Set(context, sym_yield, yield).FromJust();
|
|
// Fiber properties
|
Local<Function> fn = uni::GetFunction(tmpl);
|
fn->Set(context, sym_yield, yield).FromJust();
|
uni::SetAccessor(isolate, fn, uni::NewLatin1Symbol(isolate, "current"), GetCurrent);
|
uni::SetAccessor(isolate, fn, uni::NewLatin1Symbol(isolate, "poolSize"), GetPoolSize, SetPoolSize);
|
uni::SetAccessor(isolate, fn, uni::NewLatin1Symbol(isolate, "fibersCreated"), GetFibersCreated);
|
|
// Global Fiber
|
target->Set(context, uni::NewLatin1Symbol(isolate, "Fiber"), fn).FromJust();
|
uni::Reset(isolate, fiber_object, fn);
|
}
|
};
|
|
Persistent<FunctionTemplate> Fiber::tmpl;
|
Persistent<Function> Fiber::fiber_object;
|
Locker* Fiber::global_locker;
|
Fiber* Fiber::current = NULL;
|
vector<Fiber*> Fiber::orphaned_fibers;
|
Persistent<Value> Fiber::fatal_stack;
|
bool did_init = false;
|
|
#if !NODE_VERSION_AT_LEAST(0,10,0)
|
extern "C"
|
#endif
|
void init(Local<Object> target) {
|
Isolate* isolate = Isolate::GetCurrent();
|
Local<Context> context = isolate->GetCurrentContext();
|
if (did_init || !target->Get(context, uni::NewLatin1Symbol(isolate, "Fiber")).ToLocalChecked()->IsUndefined()) {
|
// Oh god. Node will call init() twice even though the library was loaded only once. See Node
|
// issue #2621 (no fix).
|
return;
|
}
|
did_init = true;
|
uni::HandleScope scope(isolate);
|
Coroutine::init(isolate);
|
Fiber::Init(target);
|
// Default stack size of either 512k or 1M. Perhaps make this configurable by the run time?
|
Coroutine::set_stack_size(128 * 1024);
|
}
|
|
NODE_MODULE(fibers, init)
|