I was giving v8 the top of the stack instead of the bottom of the stack so it couldn't figure out where to stop. This change fixes that. I tested this using an infinite recussion function with really small stacks (8kb) and large ones (512k) and I'm getting reasonable results. Now the hard part is picking the right stack size. I'm going to go with 64k as it's the default stack node picked for libeio threads.
Put FLS in the Coroutine object instead of with the thread in a vector. This technically means we are no longer calling destructors on outstanding keys when the keys are deleted, but that was already the case with multiple threads. The up side is that no one really deletes TLS keys unless the program is terminating anyway.
It seems that if you have v8 compiled as a shared library instead of a static library sometimes it can get initialized before coroutines.so, even with LD_PRELOAD. I'm not really sure why this is the case, but this means we need to run the hooks much earlier. So now everything that goes through pthread_getspecific is fiber-local, instead of the way it used to be where only keys created after coroutine.cc is initialized were fiber-specific.
If you attempt to run() a fiber that's already running from inside another fiber you would get a segfault. This is because even though the fiber isn't running, it hasn't yielded either. Thus where to resume execution is unclear. This should throw an exception. Closes gh-8
Child processes were getting the LD_PRELOAD carried over from node which is probably undesirable. Now when coroutine.so loads it nulls out those environment variables. Since it's loaded already they don't need to be around. Also fix a related linking error.
This fixes the build and adds an npm package. I had to add LD_LIBRARY_PATH to fiber-shim because it fails to load if coroutine.so is not in your current working directory. This also uses a better heuristic for determining if fiber-shim was used. Also the library is now just called `fibers` when used in the context of node.
When running a debug version of v8 and your application terminates while there are still yielding fibers there's an abort trap that gets caught and a scary error message. v8 is trying to cleanup resources while node-fibers.cc is getting unloaded and it's all-around bad news. This quick and dirty hack prevents v8 from cleaning up, which actually doesn't make a huge difference since the application is terminating anyway.
Not only was reset() just pointing to run(), there were problems in UnwindStack() which would make it seg fault in some cases. We were reusing `yielded` to store the exception thrown for zombified fibers, but that gets disposed aggressively (and rightfully so!) which leads to us returning a disposed pointer in the case where you re-call yield() from an unwinding fiber. And then in some cases v8 decided to garbage collect it already and that's when the fun stops.
This adds two new methods reset(), and throwInto(). Both will throw exceptions into a yielding fiber, however reset() will take care of catching the thrown exception and gracefully returning. throwInto() will likely throw your exception right back out. reset() can be used in cases where infinite loop generators are used extensively. Leaving destruction of those fibers to v8 is generally inefficient. throwInto() could be used to notify a fiber that you don't care about its task anymore. This also defers unwinding of orphaned fibers until after garbage collection has finished. This greatly increases the speed at which orphaned fibers are collected.
Instantiating new coroutines is quite expensive. Between allocating a new stack, getcontext(), and makecontext() we spend a good amount of time redoing work. The stack allocation cost is mitigated a little bit with the pool allocator, however it can't beat full object recylcing. This implements a pool of finished coroutines which are then reset and then returned again via `create_fiber`.
There were a lot of problems with garbage collection before, particularly with infinite loop generators. The problem is that the handle to the fiber was always held in a deadlock, since we pass it as the `this` context to the Fiber entry. So now `yield()` is a global, to avoid this problem. Additionally we are now giving v8 proper hints for externally-allocated memory, so it can handle garbage collection better. Also the coroutine library can free finished coroutines correctly now. Incidentally, node-fibers doesn't use any node-specific API's anymore; if you change the node.h include to be v8.h, it will still compile and run just fine.