#include "coroutine.h" #include "v8-version.h" #include #include #include #include #include #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 WeakCallbackShim(const WeakCallbackInfo

& data) { F(data.GetParameter()); } template void MakeWeak(Isolate* isolate, Persistent& handle, P* val) { handle.SetWeak(val, WeakCallbackShim, WeakCallbackType::kFinalizer); } #elif V8_AT_LEAST(3, 26) template void WeakCallbackShim(const v8::WeakCallbackData& data) { F(data.GetParameter()); } template void MakeWeak(Isolate* isolate, Persistent& handle, P* val) { handle.SetWeak(val, WeakCallbackShim); } #else template void WeakCallbackShim(Persistent value, void* data) { F(data); } template void MakeWeak(Isolate* isolate, Persistent& handle, P* val) { handle.MakeWeak(val, WeakCallbackShim); } #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 NewLatin1String(Isolate* isolate, const char* string) { return String::NewFromOneByte(isolate, (const uint8_t*)string, NewStringType::kNormal).ToLocalChecked(); } Local NewLatin1Symbol(Isolate* isolate, const char* string) { return String::NewFromOneByte(isolate, (const uint8_t*)string, NewStringType::kNormal).ToLocalChecked(); } #elif V8_AT_LEAST(3, 26) Handle NewLatin1String(Isolate* isolate, const char* string) { return String::NewFromOneByte(isolate, (const uint8_t*)string); } Handle NewLatin1Symbol(Isolate* isolate, const char* string) { return String::NewFromOneByte(isolate, (const uint8_t*)string); } #else Handle NewLatin1String(Isolate* isolate, const char* string) { return String::New(string); } Handle NewLatin1Symbol(Isolate* isolate, const char* string) { return String::NewSymbol(string); } #endif #if V8_AT_LEAST(4, 4) Local GetFunction(Local tmpl) { return tmpl->GetFunction(Isolate::GetCurrent()->GetCurrentContext()).ToLocalChecked(); } Local Call(Local fn, Local recv, int argc, Local argv[]) { Local result; if (fn->Call(Isolate::GetCurrent()->GetCurrentContext(), recv, argc, argv).ToLocal(&result)) { return result; } else { return {}; } } Local NewInstance(Isolate* isolate, Local fn, int argc, Local argv[]) { return fn->NewInstance(isolate->GetCurrentContext(), argc, argv).ToLocalChecked(); } #else Local GetFunction(Local tmpl) { return tmpl->GetFunction(); } Local Call(Local fn, Local recv, int argc, Local argv[]) { return fn->Call(recv, argc, argv); } Handle NewInstance(Isolate* isolate, Local fn, int argc, Local argv[]) { return fn->NewInstance(argc, argv).ToLocalChecked(); } #endif #if V8_AT_LEAST(4, 4) Local ToNumber(Local value) { return value->ToNumber(Isolate::GetCurrent()->GetCurrentContext()).ToLocalChecked(); } #else Handle ToNumber(Local value) { return value->ToNumber(); } #endif #if V8_AT_LEAST(6, 1) Local GetStackTrace(TryCatch* try_catch, Local context) { return try_catch->StackTrace(context).ToLocalChecked(); } #else Local GetStackTrace(TryCatch* try_catch, Handle 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) { Script::Compile(context, uni::NewLatin1String(isolate, "void 0;")).ToLocalChecked(); } #else void fixStackLimit(Isolate* isolate, Handle context) { Script::Compile(uni::NewLatin1String(isolate, "void 0;")); } #endif #if V8_AT_LEAST(3, 26) // Node v0.11.13+ typedef PropertyCallbackInfo GetterCallbackInfo; typedef PropertyCallbackInfo SetterCallbackInfo; typedef void FunctionType; typedef FunctionCallbackInfo Arguments; class HandleScope { v8::HandleScope scope; public: HandleScope(Isolate* isolate) : scope(isolate) {} }; template void Reset(Isolate* isolate, Persistent& persistent, Local handle) { persistent.Reset(isolate, handle); } template void Dispose(Isolate* isolate, Persistent& handle) { handle.Reset(); } template void ClearWeak(Isolate* isolate, Persistent& handle) { handle.ClearWeak(isolate); } template void SetInternalPointer(Local handle, int index, void* val) { handle->SetAlignedPointerInInternalField(index, val); } template void* GetInternalPointer(Local handle, int index) { return handle->GetAlignedPointerFromInternalField(index); } template Local Deref(Isolate* isolate, Persistent& handle) { return Local::New(isolate, handle); } template void Return(Local handle, const Arguments& args) { args.GetReturnValue().Set(handle); } template void Return(Local handle, GetterCallbackInfo info) { info.GetReturnValue().Set(handle); } template void Return(Persistent& handle, GetterCallbackInfo info) { info.GetReturnValue().Set(Local::New(Isolate::GetCurrent(), handle)); } Local ThrowException(Isolate* isolate, Local exception) { return isolate->ThrowException(exception); } Local GetCurrentContext(Isolate* isolate) { return isolate->GetCurrentContext(); } Local Undefined(Isolate* isolate) { return v8::Undefined(isolate); } Local NewBoolean(Isolate* isolate, bool value) { return Boolean::New(isolate, value); } Local NewNumber(Isolate* isolate, double value) { return Number::New(isolate, value); } Local NewFunctionTemplate( Isolate* isolate, FunctionCallback callback, Local data = Local(), Local signature = Local(), int length = 0 ) { return FunctionTemplate::New(isolate, callback, data, signature, length); } Local NewSignature( Isolate* isolate, Local receiver = Local() ) { 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 FunctionType; typedef Arguments Arguments; class HandleScope { v8::HandleScope scope; public: HandleScope(Isolate* isolate) {} }; template void Reset(Isolate* isolate, Persistent& persistent, Handle handle) { persistent = Persistent::New(handle); } template void Dispose(Isolate* isolate, Persistent& handle) { handle.Dispose(); } template void ClearWeak(Isolate* isolate, Persistent& handle) { handle.ClearWeak(); } template void SetInternalPointer(Handle handle, int index, void* val) { handle->SetPointerInInternalField(index, val); } template void* GetInternalPointer(Handle handle, int index) { return handle->GetPointerFromInternalField(index); } template Handle Deref(Isolate* isolate, Persistent& handle) { return Local::New(handle); } Handle Return(Handle handle, GetterCallbackInfo info) { return handle; } Handle Return(Handle handle, const Arguments& args) { return handle; } Handle ThrowException(Isolate* isolate, Handle exception) { return ThrowException(exception); } Handle GetCurrentContext(Isolate* isolate) { return Context::GetCurrent(); } Handle Undefined(Isolate* isolate) { return v8::Undefined(); } Handle NewBoolean(Isolate* isolate, bool value) { return Boolean::New(value); } Handle NewNumber(Isolate* isolate, double value) { return Number::New(value); } Handle NewFunctionTemplate( Isolate* isolate, InvocationCallback callback, Handle data = Handle(), Handle signature = Handle(), int length = 0 ) { return FunctionTemplate::New(callback, data, signature); } Handle NewSignature( Isolate* isolate, Handle receiver = Handle(), int argc = 0, Handle 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, Local name, FunctionType (*getter)(Local, const GetterCallbackInfo&), void (*setter)(Local property, Local 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, Local name, FunctionType (*getter)(Local, const GetterCallbackInfo&), void (*setter)(Local property, Local value, const SetterCallbackInfo&) = 0 ) { object->SetAccessor(isolate->GetCurrentContext(), name, (AccessorNameGetterCallback)getter, (AccessorNameSetterCallback)setter); } #else void SetAccessor( Isolate* isolate, Local object, Local name, FunctionType (*getter)(Local, const GetterCallbackInfo&), void (*setter)(Local property, Local 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(guard)); } #elif V8_AT_LEAST(3, 26) void SetStackGuard(Isolate* isolate, void* guard) { ResourceConstraints constraints; constraints.set_stack_limit(reinterpret_cast(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(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 tmpl; static Persistent fiber_object; static Fiber* current; static vector orphaned_fibers; static Persistent fatal_stack; Isolate* isolate; Persistent handle; Persistent cb; Persistent v8_context; Persistent zombie_exception; Persistent yielded; bool yielded_exception; Coroutine* entry_fiber; Coroutine* this_fiber; bool started; bool yielding; bool zombie; bool resetting; static Fiber& Unwrap(Local handle) { assert(!handle.IsEmpty()); assert(handle->InternalFieldCount() == 1); return *static_cast(uni::GetInternalPointer(handle, 0)); } Fiber(Local handle, Local cb, Local 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(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(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 orphans(orphaned_fibers); orphaned_fibers.clear(); for (vector::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 argv[1] = { args[0] }; return uni::Return(uni::NewInstance(Isolate::GetCurrent(), uni::GetFunction(uni::Deref(Isolate::GetCurrent(), tmpl)), 1, argv), args); } Local fn = Local::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(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(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 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 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(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 ReturnYielded() { Local 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(that.this_fiber->bottom()) + 1024 * 6); uni::TryCatch try_catch(that.isolate); that.ClearWeak(); Local v8_context = uni::Deref(that.isolate, that.v8_context); v8_context->Enter(); uni::fixStackLimit(that.isolate, v8_context); Local yielded; if (args->Length()) { Local 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(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 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 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 property, const uni::GetterCallbackInfo& info) { return uni::Return(uni::NewNumber(Isolate::GetCurrent(), Coroutine::pool_size), info); } static void SetPoolSize(Local property, Local 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 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 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 = isolate->GetCurrentContext(); global_locker = new Locker(isolate); current = NULL; // Fiber constructor Local 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 sig = uni::NewSignature(isolate, tmpl); tmpl->InstanceTemplate()->SetInternalFieldCount(1); // Fiber.prototype Local proto = tmpl->PrototypeTemplate(); proto->Set(uni::NewLatin1Symbol(isolate, "reset"), uni::NewFunctionTemplate(isolate, Reset, Local(), sig)); proto->Set(uni::NewLatin1Symbol(isolate, "run"), uni::NewFunctionTemplate(isolate, Run, Local(), sig)); proto->Set(uni::NewLatin1Symbol(isolate, "throwInto"), uni::NewFunctionTemplate(isolate, ThrowInto, Local(), sig)); proto->SetAccessor(uni::NewLatin1Symbol(isolate, "started"), GetStarted); // Global yield() function Local yield = uni::GetFunction(uni::NewFunctionTemplate(isolate, Yield_)); Local sym_yield = uni::NewLatin1Symbol(isolate, "yield"); target->Set(context, sym_yield, yield).FromJust(); // Fiber properties Local 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 Fiber::tmpl; Persistent Fiber::fiber_object; Locker* Fiber::global_locker; Fiber* Fiber::current = NULL; vector Fiber::orphaned_fibers; Persistent Fiber::fatal_stack; bool did_init = false; #if !NODE_VERSION_AT_LEAST(0,10,0) extern "C" #endif void init(Local target) { Isolate* isolate = Isolate::GetCurrent(); Local 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)