Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' into v8-hook

Conflicts:
	src/coroutine.cc
	src/coroutine.h
  • Loading branch information...
commit 32a3f668d39ea085893a500cb1b464bb03d51e78 2 parents f0df79f + cfcc64e
@laverdet authored
View
488 README.md
@@ -6,7 +6,7 @@ INSTALLING
To install node-fibers:
- npm install fibers
+ npm install fibers
Only Linux and OS X environments are supported. Windows support is theoretically
possible, but not planned.
@@ -27,259 +27,259 @@ This is a quick example of how you can write sleep() with fibers. Note that
while the sleep() call is blocking inside the fiber, node is able to handle
other events.
- $ cat sleep.js
- require('fibers');
- var print = require('util').print;
+ $ cat sleep.js
+ require('fibers');
+ var print = require('util').print;
- function sleep(ms) {
- var fiber = Fiber.current;
- setTimeout(function() {
- fiber.run();
- }, ms);
- yield();
- }
+ function sleep(ms) {
+ var fiber = Fiber.current;
+ setTimeout(function() {
+ fiber.run();
+ }, ms);
+ yield();
+ }
- Fiber(function() {
- print('wait... ' + new Date + '\n');
- sleep(1000);
- print('ok... ' + new Date + '\n');
- }).run();
- print('back in main\n');
+ Fiber(function() {
+ print('wait... ' + new Date + '\n');
+ sleep(1000);
+ print('ok... ' + new Date + '\n');
+ }).run();
+ print('back in main\n');
- $ ./node-fibers sleep.js
- wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
- back in main
- ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)
+ $ ./node-fibers sleep.js
+ wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
+ back in main
+ ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)
Yielding execution will resume back in the fiber right where you left off. You
can also pass values back and forth through yield() and run().
- $ cat generator.js
- require('fibers');
- var print = require('util').print;
-
- var inc = Fiber(function(start) {
- var total = start;
- while (true) {
- total += yield(total);
- }
- });
-
- for (var ii = inc.run(1); ii <= 10; ii = inc.run(1)) {
- print(ii + '\n');
- }
-
- $ ./node-fibers generator.js
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
+ $ cat generator.js
+ require('fibers');
+ var print = require('util').print;
+
+ var inc = Fiber(function(start) {
+ var total = start;
+ while (true) {
+ total += yield(total);
+ }
+ });
+
+ for (var ii = inc.run(1); ii <= 10; ii = inc.run(1)) {
+ print(ii + '\n');
+ }
+
+ $ ./node-fibers generator.js
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
Fibers are exception-safe; exceptions will continue travelling through fiber
boundaries:
- $ cat error.js
- require('fibers');
- var print = require('util').print;
-
- var fn = Fiber(function() {
- print('async work here...\n');
- yield();
- print('still working...\n');
- yield();
- print('just a little bit more...\n');
- yield();
- throw new Error('oh crap!');
- });
-
- try {
- while (true) {
- fn.run();
- }
- } catch(e) {
- print('safely caught that error!\n');
- print(e.stack + '\n');
- }
- print('done!\n');
-
- $ ./node-fibers error.js
- async work here...
- still working...
- just a little bit more...
- safely caught that error!
- Error: oh crap!
- at error.js:11:9
- done!
+ $ cat error.js
+ require('fibers');
+ var print = require('util').print;
+
+ var fn = Fiber(function() {
+ print('async work here...\n');
+ yield();
+ print('still working...\n');
+ yield();
+ print('just a little bit more...\n');
+ yield();
+ throw new Error('oh crap!');
+ });
+
+ try {
+ while (true) {
+ fn.run();
+ }
+ } catch(e) {
+ print('safely caught that error!\n');
+ print(e.stack + '\n');
+ }
+ print('done!\n');
+
+ $ ./node-fibers error.js
+ async work here...
+ still working...
+ just a little bit more...
+ safely caught that error!
+ Error: oh crap!
+ at error.js:11:9
+ done!
You can use fibers to provide synchronous adapters on top of asynchronous
functions:
- $ cat adapter.js
- require('fibers');
- var print = require('util').print;
-
- // This function runs an asynchronous function from within a fiber as if it
- // were synchronous.
- function asyncAsSync(fn /* ... */) {
- var args = [].slice.call(arguments, 1);
- var fiber = Fiber.current;
-
- function cb(err, ret) {
- if (err) {
- fiber.throwInto(new Error(err));
- } else {
- fiber.run(ret);
- }
- }
-
- // Little-known JS features: a function's `length` property is the number
- // of arguments it takes. Node convention is that the last parameter to
- // most asynchronous is a `function callback(err, ret) {}`.
- args[fn.length - 1] = cb;
- fn.apply(null, args);
-
- return yield();
- }
-
- var fs = require('fs');
- var Buffer = require('buffer').Buffer;
- Fiber(function() {
- // These are all async functions (fs.open, fs.write, fs.close) but we can
- // use them as if they're synchronous.
- print('opening /tmp/hello\n');
- var file = asyncAsSync(fs.open, '/tmp/hello', 'w');
- var buffer = new Buffer(5);
- buffer.write('hello');
- print('writing to file\n');
- asyncAsSync(fs.write, file, buffer, 0, buffer.length);
- print('closing file\n');
- asyncAsSync(fs.close, file);
-
- // This is a synchronous function. But note that while this function is
- // running node is totally blocking. Using `asyncAsSync` leaves node
- // available to handle more events.
- var data = fs.readFileSync('/tmp/hello');
- print('file contents: ' +data +'\n');
-
- // Errors made simple using the magic of exceptions
- try {
- print('deleting /tmp/hello2\n');
- asyncAsSync(fs.unlink, '/tmp/hello2');
- } catch(e) {
- print('caught this exception: ' +e.message +'\n');
- }
-
- // Cleanup :)
- print('deleting /tmp/hello\n');
- asyncAsSync(fs.unlink, '/tmp/hello');
- }).run();
- print('returning control to node event loop\n');
-
- $ ./node-fibers adapter.js
- opening /tmp/hello
- returning control to node event loop
- writing to file
- closing file
- file contents: hello
- deleting /tmp/hello2
- caught this exception: Error: ENOENT, No such file or directory '/tmp/hello2'
- deleting /tmp/hello
+ $ cat adapter.js
+ require('fibers');
+ var print = require('util').print;
+
+ // This function runs an asynchronous function from within a fiber as if it
+ // were synchronous.
+ function asyncAsSync(fn /* ... */) {
+ var args = [].slice.call(arguments, 1);
+ var fiber = Fiber.current;
+
+ function cb(err, ret) {
+ if (err) {
+ fiber.throwInto(new Error(err));
+ } else {
+ fiber.run(ret);
+ }
+ }
+
+ // Little-known JS features: a function's `length` property is the number
+ // of arguments it takes. Node convention is that the last parameter to
+ // most asynchronous is a `function callback(err, ret) {}`.
+ args[fn.length - 1] = cb;
+ fn.apply(null, args);
+
+ return yield();
+ }
+
+ var fs = require('fs');
+ var Buffer = require('buffer').Buffer;
+ Fiber(function() {
+ // These are all async functions (fs.open, fs.write, fs.close) but we can
+ // use them as if they're synchronous.
+ print('opening /tmp/hello\n');
+ var file = asyncAsSync(fs.open, '/tmp/hello', 'w');
+ var buffer = new Buffer(5);
+ buffer.write('hello');
+ print('writing to file\n');
+ asyncAsSync(fs.write, file, buffer, 0, buffer.length);
+ print('closing file\n');
+ asyncAsSync(fs.close, file);
+
+ // This is a synchronous function. But note that while this function is
+ // running node is totally blocking. Using `asyncAsSync` leaves node
+ // available to handle more events.
+ var data = fs.readFileSync('/tmp/hello');
+ print('file contents: ' +data +'\n');
+
+ // Errors made simple using the magic of exceptions
+ try {
+ print('deleting /tmp/hello2\n');
+ asyncAsSync(fs.unlink, '/tmp/hello2');
+ } catch(e) {
+ print('caught this exception: ' +e.message +'\n');
+ }
+
+ // Cleanup :)
+ print('deleting /tmp/hello\n');
+ asyncAsSync(fs.unlink, '/tmp/hello');
+ }).run();
+ print('returning control to node event loop\n');
+
+ $ ./node-fibers adapter.js
+ opening /tmp/hello
+ returning control to node event loop
+ writing to file
+ closing file
+ file contents: hello
+ deleting /tmp/hello2
+ caught this exception: Error: ENOENT, No such file or directory '/tmp/hello2'
+ deleting /tmp/hello
DOCUMENTATION
-------------
Fiber's definition looks something like this:
- /**
- * Instantiate a new Fiber. You may invoke this either as a function or as
- * a constructor; the behavior is the same.
- *
- * When run() is called on this fiber for the first time, `fn` will be
- * invoked as the first frame on a new stack. Execution will continue on
- * this new stack until `fn` returns, or yield() is called.
- *
- * After the function returns the fiber is reset to original state and
- * may be restarted with another call to run().
- */
- function Fiber(fn) {
- [native code]
- }
-
- /**
- * `Fiber.current` will contain the currently-running Fiber. It will be
- * `undefined` if there is no fiber (i.e. the main stack of execution).
- *
- * See "Garbage Collection" for more information on responsible use of
- * `Fiber.current`.
- */
- Fiber.current = undefined;
-
- /**
- * yield() will halt execution of the current fiber and return control back
- * to original caller of run(). If an argument is supplied to yield, run()
- * will return that value.
- *
- * When run() is called again, yield() will return.
- *
- * Note that this function is a global to allow for correct garbage
- * collection. This results in no loss of functionality because it is only
- * valid to yield from the currently running fiber anyway.
- */
- function yield(param) {
- [native code]
- }
-
- /**
- * run() will start execution of this Fiber, or if it is currently yielding,
- * it will resume execution. If an argument is supplied, this argument will
- * be passed to the fiber, either as the first parameter to the main
- * function [if the fiber has not been started] or as the return value of
- * yield() [if the fiber is currently yielding].
- *
- * This function will return either the parameter passed to yield(), or the
- * returned value from the fiber's main function.
- */
- Fiber.prototype.run = function(param) {
- [native code]
- }
-
- /**
- * reset() will terminate a running Fiber and restore it to its original
- * state, as if it had returned execution.
- *
- * This is accomplished by causing yield() to throw an exception, and any
- * futher calls to yield() will also throw an exception. This continues
- * until the fiber has completely unwound and returns.
- *
- * If the fiber returns a value it will be returned by reset().
- *
- * If the fiber is not running, reset() will have no effect.
- */
- Fiber.prototype.reset = function() {
- [native code]
- }
-
- /**
- * throwInto() will cause a currently yielding fiber's yield() call to
- * throw instead of return gracefully. This can be useful for notifying a
- * fiber that you are no longer interested in its task, and that it should
- * give up.
- *
- * Note that if the fiber does not handle the exception it will continue to
- * bubble up and throwInto() will throw the exception right back at you.
- */
- Fiber.prototype.throwInto = function(exception) {
- [native code]
- }
+ /**
+ * Instantiate a new Fiber. You may invoke this either as a function or as
+ * a constructor; the behavior is the same.
+ *
+ * When run() is called on this fiber for the first time, `fn` will be
+ * invoked as the first frame on a new stack. Execution will continue on
+ * this new stack until `fn` returns, or yield() is called.
+ *
+ * After the function returns the fiber is reset to original state and
+ * may be restarted with another call to run().
+ */
+ function Fiber(fn) {
+ [native code]
+ }
+
+ /**
+ * `Fiber.current` will contain the currently-running Fiber. It will be
+ * `undefined` if there is no fiber (i.e. the main stack of execution).
+ *
+ * See "Garbage Collection" for more information on responsible use of
+ * `Fiber.current`.
+ */
+ Fiber.current = undefined;
+
+ /**
+ * yield() will halt execution of the current fiber and return control back
+ * to original caller of run(). If an argument is supplied to yield, run()
+ * will return that value.
+ *
+ * When run() is called again, yield() will return.
+ *
+ * Note that this function is a global to allow for correct garbage
+ * collection. This results in no loss of functionality because it is only
+ * valid to yield from the currently running fiber anyway.
+ */
+ function yield(param) {
+ [native code]
+ }
+
+ /**
+ * run() will start execution of this Fiber, or if it is currently yielding,
+ * it will resume execution. If an argument is supplied, this argument will
+ * be passed to the fiber, either as the first parameter to the main
+ * function [if the fiber has not been started] or as the return value of
+ * yield() [if the fiber is currently yielding].
+ *
+ * This function will return either the parameter passed to yield(), or the
+ * returned value from the fiber's main function.
+ */
+ Fiber.prototype.run = function(param) {
+ [native code]
+ }
+
+ /**
+ * reset() will terminate a running Fiber and restore it to its original
+ * state, as if it had returned execution.
+ *
+ * This is accomplished by causing yield() to throw an exception, and any
+ * futher calls to yield() will also throw an exception. This continues
+ * until the fiber has completely unwound and returns.
+ *
+ * If the fiber returns a value it will be returned by reset().
+ *
+ * If the fiber is not running, reset() will have no effect.
+ */
+ Fiber.prototype.reset = function() {
+ [native code]
+ }
+
+ /**
+ * throwInto() will cause a currently yielding fiber's yield() call to
+ * throw instead of return gracefully. This can be useful for notifying a
+ * fiber that you are no longer interested in its task, and that it should
+ * give up.
+ *
+ * Note that if the fiber does not handle the exception it will continue to
+ * bubble up and throwInto() will throw the exception right back at you.
+ */
+ Fiber.prototype.throwInto = function(exception) {
+ [native code]
+ }
GARBAGE COLLECTION
@@ -294,14 +294,14 @@ to delete it.
Something like this will, at some point, cause an infinite loop in your
application:
- var fiber = Fiber(function() {
- while (true) {
- try {
- yield();
- } catch(e) {}
- }
- });
- fiber.run();
+ var fiber = Fiber(function() {
+ while (true) {
+ try {
+ yield();
+ } catch(e) {}
+ }
+ });
+ fiber.run();
If you either call reset() on this fiber, or the v8 garbage collector decides it
is no longer in use, the fiber library will attempt to unwind the fiber by
@@ -312,12 +312,12 @@ There are other garbage collection issues that occur with misuse of fiber
handles. If you grab a handle to a fiber from within itself, you should make
sure that the fiber eventually unwinds. This application will leak memory:
- var fiber = Fiber(function() {
- var that = Fiber.current;
- yield();
- }
- fiber.run();
- fiber = undefined;
+ var fiber = Fiber(function() {
+ var that = Fiber.current;
+ yield();
+ }
+ fiber.run();
+ fiber = undefined;
There is no way to get back into the fiber that was started, however it's
impossible for v8's garbage collector to detect this. With a handle to the fiber
View
28 bin/fiber-shim
@@ -2,23 +2,23 @@
# assume fibers.node and coroutine.so are in same directory
if [[ -e `dirname "$0"`/../src/fibers.node ]]; then
- FIBERS_ROOT=`dirname "$0"`/../src
+ FIBERS_ROOT=`dirname "$0"`/../src
else
- FIBERS_ROOT=`node --vars | egrep ^NODE_PREFIX: | cut -c14-`/lib/node/.npm/fibers/active/package/src
- if [[ ! -e "$FIBERS_ROOT/fibers.node" ]]; then
- echo "Could not find the coroutine shim!" 1>&2
- exit 1
- fi
+ FIBERS_ROOT=`node --vars | egrep ^NODE_PREFIX: | cut -c14-`/lib/node/.npm/fibers/active/package/src
+ if [[ ! -e "$FIBERS_ROOT/fibers.node" ]]; then
+ echo "Could not find the coroutine shim!" 1>&2
+ exit 1
+ fi
fi
UNAME=`uname`
if [[ "$UNAME" == "Linux" ]]; then
- FIBER_SHIM=1 \
- LD_PRELOAD="$FIBERS_ROOT/coroutine.so" \
- "$@"
+ FIBER_SHIM=1 \
+ LD_PRELOAD="$FIBERS_ROOT/coroutine.so" \
+ "$@"
elif [[ "$UNAME" == "Darwin" ]]; then
- FIBER_SHIM=1 \
- DYLD_INSERT_LIBRARIES="$FIBERS_ROOT/coroutine.dylib" \
- DYLD_FORCE_FLAT_NAMESPACE=1 \
- DYLD_LIBRARY_PATH="$FIBERS_ROOT" \
- "$@"
+ FIBER_SHIM=1 \
+ DYLD_INSERT_LIBRARIES="$FIBERS_ROOT/coroutine.dylib" \
+ DYLD_FORCE_FLAT_NAMESPACE=1 \
+ DYLD_LIBRARY_PATH="$FIBERS_ROOT" \
+ "$@"
fi
View
26 fibers.js
@@ -1,20 +1,20 @@
var fs = require('fs');
if (fs.statSync(process.execPath).mtime >
- fs.statSync(__dirname + '/src/fibers.node').mtime) {
- throw new Error(
- '`node` has a newer mtime than `fiber`; it is possible your build is out of date. This ' +
- 'could happen if you upgrade node. Try `npm rebuild fibers` to rebuild. If that doesn\'t ' +
- 'work you could consider running `touch ' + __dirname + 'src/fibers` and maybe there won\'t ' +
- 'be a problem.');
+ fs.statSync(__dirname + '/src/fibers.node').mtime) {
+ throw new Error(
+ '`node` has a newer mtime than `fiber`; it is possible your build is out of date. This ' +
+ 'could happen if you upgrade node. Try `npm rebuild fibers` to rebuild. If that doesn\'t ' +
+ 'work you could consider running `touch ' + __dirname + 'src/fibers` and maybe there won\'t ' +
+ 'be a problem.');
} else if (!process.env.FIBER_SHIM) {
- throw new Error(
- 'Fiber support was not enabled when you ran node. To enable support for fibers, please run ' +
- 'node with the included `node-fibers` script. For example, instead of running:\n\n' +
- ' node script.js\n\n' +
- 'You should run:\n\n' +
- ' node-fibers script.js\n\n' +
- 'You will not be able to use Fiber without this support enabled.');
+ throw new Error(
+ 'Fiber support was not enabled when you ran node. To enable support for fibers, please run ' +
+ 'node with the included `node-fibers` script. For example, instead of running:\n\n' +
+ ' node script.js\n\n' +
+ 'You should run:\n\n' +
+ ' node-fibers script.js\n\n' +
+ 'You will not be able to use Fiber without this support enabled.');
}
require('./src/fibers');
View
46 package.json
@@ -1,25 +1,25 @@
{
- "name": "fibers",
- "version": "0.1.4",
- "description": "Cooperative multi-tasking for Javascript; or, the closest thing to a thread you'll see in node",
- "keywords": ["fiber", "fibers", "coroutine", "thread", "async", "parallel", "worker"],
- "homepage": "https://github.com/laverdet/node-fibers",
- "author": "Marcel Laverdet <marcel@laverdet.com> (https://github.com/laverdet/)",
- "main": "fibers",
- "scripts": {
- "install": "make clean all"
- },
- "bin": {
- "node-fibers": "./bin/node-fibers",
- "fiber-shim": "./bin/fiber-shim"
- },
- "man": "./man/fibers.1",
- "repository": {
- "type": "git",
- "url": "git://github.com/laverdet/node-fibers.git"
- },
- "os": ["macos", "linux"],
- "engines": {
- "node": ">=0.2.0"
- }
+ "name": "fibers",
+ "version": "0.1.6",
+ "description": "Cooperative multi-tasking for Javascript; or, the closest thing to a thread you'll see in node",
+ "keywords": ["fiber", "fibers", "coroutine", "thread", "async", "parallel", "worker"],
+ "homepage": "https://github.com/laverdet/node-fibers",
+ "author": "Marcel Laverdet <marcel@laverdet.com> (https://github.com/laverdet/)",
+ "main": "fibers",
+ "scripts": {
+ "install": "make clean all"
+ },
+ "bin": {
+ "node-fibers": "./bin/node-fibers",
+ "fiber-shim": "./bin/fiber-shim"
+ },
+ "man": "./man/fibers.1",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/laverdet/node-fibers.git"
+ },
+ "os": ["macos", "linux"],
+ "engines": {
+ "node": ">=0.2.0"
+ }
}
View
8 src/Makefile
@@ -1,12 +1,12 @@
include platform.mk
ifeq ($(NODE_PLATFORM), linux)
- CPP_DYFLAGS = -fPIC -shared
- CPP_NODEFLAGS = -fPIC -shared -Wl,-Bdynamic
+ CPP_DYFLAGS = -fPIC -shared
+ CPP_NODEFLAGS = -fPIC -shared -Wl,-Bdynamic
endif
ifeq ($(NODE_PLATFORM), darwin)
- CPP_DYFLAGS = -dynamiclib
- CPP_NODEFLAGS = -bundle -undefined dynamic_lookup
+ CPP_DYFLAGS = -dynamiclib
+ CPP_NODEFLAGS = -bundle -undefined dynamic_lookup
endif
COROUTINE_SO_FULL := $(shell echo `pwd`/$(COROUTINE_SO))
View
442 src/coroutine.cc
@@ -13,46 +13,43 @@
// No matter what I give it, it seems the stack size is always the same. And then if the actual
// amount of memory allocated for the stack is too small it seg faults. It seems 265k is as low as
// I can go without fixing the underlying bug.
-#define STACK_SIZE (1024 * 265)
+#define STACK_SIZE (1024 * 64)
#define MAX_POOL_SIZE 120
#include <iostream>
using namespace std;
+typedef void(*pthread_dtor_t)(void*);
static pthread_key_t thread_key;
static bool did_hook_v8 = false;
/**
- * LocalThread is only used internally for this library. It keeps track of all the fibers this
+ * LocalLocalThread is only used internally for this library. It keeps track of all the fibers this
* thread is currently running, and handles all the fiber-local storage logic. We store a handle to
- * a LocalThread object in TLS, and then it emulates TLS on top of fibers.
+ * a LocalLocalThread object in TLS, and then it emulates TLS on top of fibers.
*/
class LocalThread {
- private:
- static size_t fls_key;
- size_t fiber_ids;
- stack<size_t> freed_fiber_ids;
- vector<vector<void*> > fls_data;
- vector<Coroutine*> fiber_pool;
-
- LocalThread() : fiber_ids(1), fls_data(1), delete_me(NULL) {
- current_fiber = new Coroutine(*this, 0);
- }
-
- ~LocalThread() {
- assert(freed_fiber_ids.size() == fiber_ids);
- for (size_t ii = 0; ii < fiber_pool.size(); ++ii) {
- delete fiber_pool[ii];
- }
- }
+ private:
+ static vector<pthread_dtor_t> dtors;
+ vector<Coroutine*> fiber_pool;
+
+ LocalThread() : delete_me(NULL) {
+ current_fiber = new Coroutine(*this);
+ }
+
+ ~LocalThread() {
+ for (size_t ii = 0; ii < fiber_pool.size(); ++ii) {
+ delete fiber_pool[ii];
+ }
+ }
- public:
- volatile Coroutine* current_fiber;
- volatile Coroutine* delete_me;
+ public:
+ volatile Coroutine* current_fiber;
+ volatile Coroutine* delete_me;
- static void free(void* that) {
- delete static_cast<LocalThread*>(that);
- }
+ static void free(void* that) {
+ delete static_cast<LocalThread*>(that);
+ }
static LocalThread& get() {
LocalThread* thread = static_cast<LocalThread*>(pthread_getspecific(thread_key));
@@ -61,79 +58,88 @@ class LocalThread {
pthread_setspecific(thread_key, thread);
}
return *thread;
- }
-
- void coroutine_fls_dtor(Coroutine& fiber) {
- bool did_delete;
- vector<void*>& fiber_data = fls_data[fiber.id];
- do {
- did_delete = false;
- for (size_t ii = 0; ii < fiber_data.size(); ++ii) {
- if (fiber_data[ii]) {
- fiber_data[ii] = NULL;
- }
- }
- } while (did_delete);
- }
-
- void fiber_did_finish(Coroutine& fiber) {
- if (fiber_pool.size() < MAX_POOL_SIZE) {
- fiber_pool.push_back(&fiber);
- } else {
- freed_fiber_ids.push(fiber.id);
- coroutine_fls_dtor(fiber);
- // Can't delete right now because we're currently on this stack!
- assert(delete_me == NULL);
- delete_me = &fiber;
- }
- }
-
- Coroutine& create_fiber(Coroutine::entry_t& entry, void* arg) {
- size_t id;
- if (!fiber_pool.empty()) {
- Coroutine& fiber = *fiber_pool.back();
- fiber_pool.pop_back();
- fiber.reset(entry, arg);
- return fiber;
- }
-
- if (!freed_fiber_ids.empty()) {
- id = freed_fiber_ids.top();
- freed_fiber_ids.pop();
- } else {
- fls_data.resize(fls_data.size() + 1);
- id = fiber_ids++;
- }
- return *new Coroutine(*this, id, entry, arg);
- }
-
- void* get_specific(pthread_key_t key) {
- if (fls_data[current_fiber->id].size() <= key) {
- return NULL;
- }
- return fls_data[current_fiber->id][key];
- }
-
- void set_specific(pthread_key_t key, const void* data) {
- if (fls_data[current_fiber->id].size() <= key) {
- fls_data[current_fiber->id].resize(key + 1);
- }
- fls_data[current_fiber->id][key] = (void*)data;
- }
-
- static pthread_key_t key_create() {
- return fls_key++;
- }
+ }
+
+ void coroutine_fls_dtor(Coroutine& fiber) {
+ bool did_delete;
+ do {
+ did_delete = false;
+ for (size_t ii = 0; ii < fiber.fls_data.size(); ++ii) {
+ if (fiber.fls_data[ii]) {
+ if (dtors[ii]) {
+ void* tmp = fiber.fls_data[ii];
+ fiber.fls_data[ii] = NULL;
+ dtors[ii](tmp);
+ did_delete = true;
+ } else {
+ fiber.fls_data[ii] = NULL;
+ }
+ }
+ }
+ } while (did_delete);
+ }
+
+ void fiber_did_finish(Coroutine& fiber) {
+ if (fiber_pool.size() < MAX_POOL_SIZE) {
+ fiber_pool.push_back(&fiber);
+ } else {
+ coroutine_fls_dtor(fiber);
+ // Can't delete right now because we're currently on this stack!
+ assert(delete_me == NULL);
+ delete_me = &fiber;
+ }
+ }
+
+ Coroutine& create_fiber(Coroutine::entry_t& entry, void* arg) {
+ if (!fiber_pool.empty()) {
+ Coroutine& fiber = *fiber_pool.back();
+ fiber_pool.pop_back();
+ fiber.reset(entry, arg);
+ return fiber;
+ }
+
+ return *new Coroutine(*this, entry, arg);
+ }
+
+ void* get_specific(pthread_key_t key) {
+ if (const_cast<Coroutine*>(current_fiber)->fls_data.size() <= key) {
+ return NULL;
+ }
+ return const_cast<Coroutine*>(current_fiber)->fls_data[key];
+ }
+
+ void set_specific(pthread_key_t key, const void* data) {
+ if (const_cast<Coroutine*>(current_fiber)->fls_data.size() <= key) {
+ const_cast<Coroutine*>(current_fiber)->fls_data.resize(key + 1);
+ }
+ const_cast<Coroutine*>(current_fiber)->fls_data[key] = (void*)data;
+ }
+
+ static pthread_key_t key_create(pthread_dtor_t dtor) {
+ dtors.push_back(dtor);
+ return dtors.size() - 1; // TODO: This is NOT thread-safe! =O
+ }
+
+ void key_delete(pthread_key_t key) {
+ if (!dtors[key]) {
+ return;
+ }
+ // This doesn't call the dtor on all threads / fibers. Do I really care?
+ if (get_specific(key)) {
+ dtors[key](get_specific(key));
+ set_specific(key, NULL);
+ }
+ }
};
-size_t LocalThread::fls_key = 0;
+vector<pthread_dtor_t> LocalThread::dtors;
/**
* Coroutine class definition
*/
void Coroutine::trampoline(Coroutine &that) {
- while (true) {
- that.entry(const_cast<void*>(that.arg));
- }
+ while (true) {
+ that.entry(const_cast<void*>(that.arg));
+ }
}
Coroutine& Coroutine::current() {
@@ -141,21 +147,20 @@ Coroutine& Coroutine::current() {
}
const bool Coroutine::is_local_storage_enabled() {
- return did_hook_v8;
+ return did_hook_v8;
}
-Coroutine::Coroutine(LocalThread& t, size_t id) : thread(t), id(id) {}
-
-Coroutine::Coroutine(LocalThread& t, size_t id, entry_t& entry, void* arg) :
- thread(t),
- id(id),
- stack(STACK_SIZE),
- entry(entry),
- arg(arg) {
- getcontext(&context);
- context.uc_stack.ss_size = STACK_SIZE;
- context.uc_stack.ss_sp = &stack[0];
- makecontext(&context, (void(*)(void))trampoline, 1, this);
+Coroutine::Coroutine(LocalThread& t) : thread(t) {}
+
+Coroutine::Coroutine(LocalThread& t, entry_t& entry, void* arg) :
+ thread(t),
+ stack(STACK_SIZE),
+ entry(entry),
+ arg(arg) {
+ getcontext(&context);
+ context.uc_stack.ss_size = STACK_SIZE;
+ context.uc_stack.ss_sp = &stack[0];
+ makecontext(&context, (void(*)(void))trampoline, 1, this);
}
Coroutine& Coroutine::create_fiber(entry_t* entry, void* arg) {
@@ -163,128 +168,129 @@ Coroutine& Coroutine::create_fiber(entry_t* entry, void* arg) {
}
void Coroutine::reset(entry_t* entry, void* arg) {
- this->entry = entry;
- this->arg = arg;
+ this->entry = entry;
+ this->arg = arg;
}
void Coroutine::run() volatile {
- Coroutine& current = *const_cast<Coroutine*>(thread.current_fiber);
- assert(&current != this);
- thread.current_fiber = this;
- if (thread.delete_me) {
- assert(this != thread.delete_me);
- delete thread.delete_me;
- thread.delete_me = NULL;
- }
- swapcontext(&current.context, const_cast<ucontext_t*>(&context));
- thread.current_fiber = &current;
+ Coroutine& current = *const_cast<Coroutine*>(thread.current_fiber);
+ assert(&current != this);
+ if (thread.delete_me) {
+ assert(this != thread.delete_me);
+ assert(&current != thread.delete_me);
+ delete thread.delete_me;
+ thread.delete_me = NULL;
+ }
+ thread.current_fiber = this;
+ swapcontext(&current.context, const_cast<ucontext_t*>(&context));
}
void Coroutine::finish(Coroutine& next) {
- this->thread.fiber_did_finish(*this);
- swapcontext(&context, &next.context);
+ this->thread.fiber_did_finish(*this);
+ thread.current_fiber = &next;
+ swapcontext(&context, &next.context);
}
void* Coroutine::bottom() const {
- return (char*)&stack[0] - STACK_SIZE;
+ return (char*)&stack[0];
}
size_t Coroutine::size() const {
- return sizeof(Coroutine) + STACK_SIZE;
+ return sizeof(Coroutine) + STACK_SIZE;
}
/**
* v8 hooks. Trick v8threads.cc into thinking that coroutines are actually threads.
*/
namespace v8 { namespace internal {
- /**
- * ThreadHandle is just a handle to a thread. It's important to note that ThreadHandle does NOT
- * own the underlying thread. We shim it make IsSelf() respect fibers.
- */
- class ThreadHandle {
- enum Kind { SELF, INVALID };
-
- /**
- * PlatformData is a class with a couple of non-virtual methods and a single platform-specific
- * handle. PlatformData is stored as pointer in the ThreadHandle class so we can add new members
- * to the end and no one will mess with those members.
- */
- class PlatformData {
- public:
- pthread_t _;
- Coroutine* coroutine;
- void Initialize(ThreadHandle::Kind kind);
- };
- ThreadHandle(Kind kind);
- void Initialize(Kind kind);
- bool IsSelf() const;
-
- /**
- * ThreadHandle's first member is PlatformData* data_. Since all pointers have the same size
- * as long as v8 doesn't change the layout of this class this is safe.
- */
- PlatformData* data;
- };
-
- /**
- * Thread is an abstract class which manages creating and running new threads. In most cases v8
- * won't actually create any new threads. Notable uses of the Thread class include the profiler,
- * debugger, and the preemption thread.
- *
- * All of the methods we override are static and are just simple wrappers around pthread
- * functions.
- *
- * Note that Thread extends ThreadHandle, but this has no bearing on our implementation.
- */
- class Thread : public ThreadHandle {
- public:
- enum LocalStorageKey {};
- static LocalStorageKey CreateThreadLocalKey();
- static void DeleteThreadLocalKey(LocalStorageKey key);
- static void* GetThreadLocal(LocalStorageKey key);
- static void SetThreadLocal(LocalStorageKey key, void* value);
- };
-
- /**
- * Override the constructor to instantiate our own PlatformData.
- */
- ThreadHandle::ThreadHandle(Kind kind) {
- data = new PlatformData;
- Initialize(kind);
- }
-
- void ThreadHandle::Initialize(Kind kind) {
- data->Initialize(kind);
- data->coroutine = kind == SELF ? &Coroutine::current() : NULL;
- }
-
- /**
- * Fool v8 into thinking it's in a new thread.
- */
- bool ThreadHandle::IsSelf() const {
- return data->coroutine == &Coroutine::current();
- }
-
- /**
- * All thread-specific functions will just go to the fiber-local storage engine in this source
- * file.
- */
- Thread::LocalStorageKey Thread::CreateThreadLocalKey() {
- did_hook_v8 = true;
- return static_cast<LocalStorageKey>(LocalThread::key_create());
- }
-
- void Thread::DeleteThreadLocalKey(LocalStorageKey key) {
- // Kidding me?
- }
-
- void* Thread::GetThreadLocal(Thread::LocalStorageKey key) {
- return LocalThread::get().get_specific(static_cast<pthread_key_t>(key));
- }
-
- void Thread::SetThreadLocal(Thread::LocalStorageKey key, void* value) {
- LocalThread::get().set_specific(static_cast<pthread_key_t>(key), value);
- }
+ /**
+ * ThreadHandle is just a handle to a thread. It's important to note that ThreadHandle does NOT
+ * own the underlying thread. We shim it make IsSelf() respect fibers.
+ */
+ class ThreadHandle {
+ enum Kind { SELF, INVALID };
+
+ /**
+ * PlatformData is a class with a couple of non-virtual methods and a single platform-specific
+ * handle. PlatformData is stored as pointer in the ThreadHandle class so we can add new members
+ * to the end and no one will mess with those members.
+ */
+ class PlatformData {
+ public:
+ pthread_t _;
+ Coroutine* coroutine;
+ void Initialize(Kind kind);
+ };
+ ThreadHandle(Kind kind);
+ void Initialize(Kind kind);
+ bool IsSelf() const;
+
+ /**
+ * ThreadHandle's first member is PlatformData* data_. Since all pointers have the same size
+ * as long as v8 doesn't change the layout of this class this is safe.
+ */
+ PlatformData* data;
+ };
+
+ /**
+ * Thread is an abstract class which manages creating and running new threads. In most cases v8
+ * won't actually create any new threads. Notable uses of the Thread class include the profiler,
+ * debugger, and the preemption thread.
+ *
+ * All of the methods we override are static and are just simple wrappers around pthread
+ * functions.
+ *
+ * Note that Thread extends ThreadHandle, but this has no bearing on our implementation.
+ */
+ class Thread : public ThreadHandle {
+ public:
+ enum LocalStorageKey {};
+ static LocalStorageKey CreateThreadLocalKey();
+ static void DeleteThreadLocalKey(LocalStorageKey key);
+ static void* GetThreadLocal(LocalStorageKey key);
+ static void SetThreadLocal(LocalStorageKey key, void* value);
+ };
+
+ /**
+ * Override the constructor to instantiate our own PlatformData.
+ */
+ ThreadHandle::ThreadHandle(Kind kind) {
+ data = new PlatformData;
+ Initialize(kind);
+ }
+
+ void ThreadHandle::Initialize(Kind kind) {
+ data->Initialize(kind);
+ data->coroutine = kind == SELF ? &Coroutine::current() : NULL;
+ }
+
+ /**
+ * Fool v8 into thinking it's in a new thread.
+ */
+ bool ThreadHandle::IsSelf() const {
+ return data->coroutine == &Coroutine::current();
+ }
+
+ /**
+ * All thread-specific functions will just go to the fiber-local storage engine in this source
+ * file.
+ */
+ Thread::LocalStorageKey Thread::CreateThreadLocalKey() {
+ did_hook_v8 = true;
+ return static_cast<LocalStorageKey>(LocalThread::key_create(NULL));
+ }
+
+ void Thread::DeleteThreadLocalKey(LocalStorageKey key) {
+ // Kidding me?
+ }
+
+ void* Thread::GetThreadLocal(Thread::LocalStorageKey key) {
+ return LocalThread::get().get_specific(static_cast<pthread_key_t>(key));
+ }
+
+ void Thread::SetThreadLocal(Thread::LocalStorageKey key, void* value) {
+ LocalThread::get().set_specific(static_cast<pthread_key_t>(key), value);
+ }
}}
/**
@@ -292,13 +298,13 @@ namespace v8 { namespace internal {
* it's possible the TLS functions have been called, so we need to clean up that mess.
*/
class Loader {
- public: Loader() {
- cout <<"hello\n";
- pthread_key_create(&thread_key, LocalThread::free);
-
- // Undo fiber-shim so that child processes don't get shimmed as well.
- setenv("DYLD_INSERT_LIBRARIES", "", 1);
- setenv("LD_PRELOAD", "", 1);
- }
+ public: Loader() {
+ pthread_key_create(&thread_key, LocalThread::free);
+
+ // Undo fiber-shim so that child processes don't get shimmed as well. This also seems to prevent
+ // this library from being loaded multiple times.
+ setenv("DYLD_INSERT_LIBRARIES", "", 1);
+ setenv("LD_PRELOAD", "", 1);
+ }
};
Loader loader;
View
130 src/coroutine.h
@@ -8,81 +8,81 @@
#include <vector>
class Coroutine {
- public:
- friend class LocalThread;
- typedef void(entry_t)(void*);
+ public:
+ friend class LocalThread;
+ typedef void(entry_t)(void*);
- private:
- // vector<char> will 0 out the memory first which is not necessary; this hack lets us get
- // around that, as there is no constructor.
- struct char_noinit { char x; };
- class LocalThread& thread;
- size_t id;
- ucontext_t context;
- std::vector<char_noinit, __gnu_cxx::__pool_alloc<char_noinit> > stack;
- volatile entry_t* entry;
- volatile void* arg;
+ private:
+ // vector<char> will 0 out the memory first which is not necessary; this hack lets us get
+ // around that, as there is no constructor.
+ struct char_noinit { char x; };
+ class LocalThread& thread;
+ ucontext_t context;
+ std::vector<char_noinit, __gnu_cxx::__pool_alloc<char_noinit> > stack;
+ std::vector<void*> fls_data;
+ volatile entry_t* entry;
+ volatile void* arg;
- static void trampoline(Coroutine& that);
- ~Coroutine() {}
+ static void trampoline(Coroutine& that);
+ ~Coroutine() {}
- /**
- * Constructor for currently running "fiber". This is really just original thread, but we
- * need a way to get back into the main thread after yielding to a fiber. Basically this
- * shouldn't be called from anywhere.
- */
- Coroutine(LocalThread& t, size_t id);
+ /**
+ * Constructor for currently running "fiber". This is really just original thread, but we
+ * need a way to get back into the main thread after yielding to a fiber. Basically this
+ * shouldn't be called from anywhere.
+ */
+ Coroutine(LocalThread& t);
- /**
- * This constructor will actually create a new fiber context. Execution does not begin
- * until you call run() for the first time.
- */
- Coroutine(LocalThread& t, size_t id, entry_t& entry, void* arg);
+ /**
+ * This constructor will actually create a new fiber context. Execution does not begin
+ * until you call run() for the first time.
+ */
+ Coroutine(LocalThread& t, entry_t& entry, void* arg);
- /**
- * Resets the context of this coroutine from the start. Used to recyle old coroutines.
- */
- void reset(entry_t* entry, void* arg);
+ /**
+ * Resets the context of this coroutine from the start. Used to recyle old coroutines.
+ */
+ void reset(entry_t* entry, void* arg);
- public:
- /**
- * Returns the currently-running fiber.
- */
- static Coroutine& current();
+ public:
+ /**
+ * Returns the currently-running fiber.
+ */
+ static Coroutine& current();
- /**
- * Create a new fiber.
- */
- static Coroutine& create_fiber(entry_t* entry, void* arg = NULL);
+ /**
+ * Create a new fiber.
+ */
+ static Coroutine& create_fiber(entry_t* entry, void* arg = NULL);
- /**
- * Is Coroutine-local storage via pthreads enabled? The Coroutine library should work fine
- * without this, but programs that are not aware of coroutines may panic if they make
- * assumptions about the stack. In order to enable this you must LD_PRELOAD (or equivalent)
- * this library.
- */
- static const bool is_local_storage_enabled();
+ /**
+ * Is Coroutine-local storage via pthreads enabled? The Coroutine library should work fine
+ * without this, but programs that are not aware of coroutines may panic if they make
+ * assumptions about the stack. In order to enable this you must LD_PRELOAD (or equivalent)
+ * this library.
+ */
+ static const bool is_local_storage_enabled();
- /**
- * Start or resume execution in this fiber. Note there is no explicit yield() function,
- * you must manually run another fiber.
- */
- void run() volatile;
+ /**
+ * Start or resume execution in this fiber. Note there is no explicit yield() function,
+ * you must manually run another fiber.
+ */
+ void run() volatile;
- /**
- * Finish this coroutine.. This will halt execution of this coroutine and resume execution
- * of `next`. If you do not call this function, and instead just return from `entry` the
- * application will exit. This function may or may not actually return.
- */
- void finish(Coroutine& next);
+ /**
+ * Finish this coroutine.. This will halt execution of this coroutine and resume execution
+ * of `next`. If you do not call this function, and instead just return from `entry` the
+ * application will exit. This function may or may not actually return.
+ */
+ void finish(Coroutine& next);
- /**
- * Returns address of the lowest usable byte in this Coroutine's stack.
- */
- void* bottom() const;
+ /**
+ * Returns address of the lowest usable byte in this Coroutine's stack.
+ */
+ void* bottom() const;
- /**
- * Returns the size this Coroutine takes up in the heap.
- */
- size_t size() const;
+ /**
+ * Returns the size this Coroutine takes up in the heap.
+ */
+ size_t size() const;
};
View
926 src/fibers.cc
@@ -13,465 +13,465 @@ using namespace v8;
class Fiber {
#define Unwrap(target, handle) \
- assert(!handle.IsEmpty()); \
- if (!handle->IsObject() || handle->GetHiddenValue(fiber_token).IsEmpty()) { \
- THROW(Exception::TypeError, "Illegal invocation"); \
- } \
- assert(handle->InternalFieldCount() == 1); \
- target = *static_cast<Fiber*>(handle->GetPointerFromInternalField(0));
-
- private:
- static Locker* locker; // Node does not use locks or threads, so we need a global lock
- static Persistent<FunctionTemplate> tmpl;
- static volatile Fiber* current;
- static vector<Fiber*> orphaned_fibers;
- static Persistent<Value> fatal_stack;
- static Persistent<String> fiber_token;
-
- Persistent<Object> handle;
- Persistent<Function> cb;
- Persistent<Context> v8_context;
- Persistent<Value> zombie_exception;
- Persistent<Value> yielded;
- volatile bool yielded_exception;
- volatile Coroutine* entry_fiber;
- Coroutine* this_fiber;
- volatile bool started;
- volatile bool yielding;
- volatile bool zombie;
- volatile bool resetting;
-
- Fiber(Persistent<Object> handle, Persistent<Function> cb, Persistent<Context> v8_context) :
- handle(handle),
- cb(cb),
- v8_context(v8_context),
- started(false),
- yielding(false),
- zombie(false),
- resetting(false) {
- MakeWeak();
- handle->SetPointerInInternalField(0, this);
- }
-
- virtual ~Fiber() {
- assert(!this->started);
- handle.Dispose();
- cb.Dispose();
- v8_context.Dispose();
- }
-
- /**
- * 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() {
- handle.MakeWeak(this, WeakCallback);
- }
-
- /**
- * 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(Persistent<Value> value, void *data) {
- Fiber& that = *static_cast<Fiber*>(data);
- assert(that.handle == value);
- assert(value.IsNearDeath());
- 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.
- String::Utf8Value stack(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 {
- fatal_stack.Dispose();
- }
-
- that.yielded.Dispose();
- 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 Handle<Value> New(const Arguments& args) {
-
- HandleScope scope;
- 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()) {
- Handle<Value> argv[1] = { args[0] };
- return tmpl->GetFunction()->NewInstance(1, argv);
- }
-
- Handle<Function> fn = Handle<Function>::Cast(args[0]);
- args.This()->SetHiddenValue(fiber_token, Boolean::New(true));
- new Fiber(
- Persistent<Object>::New(args.This()),
- Persistent<Function>::New(fn),
- Persistent<Context>::New(Context::GetCurrent()));
- return args.This();
- }
-
- /**
- * 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 Handle<Value> Run(const Arguments& args) {
- HandleScope scope;
- Unwrap(Fiber& that, args.This());
-
- // 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()`.
- that.started = true;
- void** data = new void*[2];
- data[0] = (void*)&args;
- data[1] = &that;
- that.this_fiber = &Coroutine::create_fiber((void (*)(void*))RunFiber, data);
- V8::AdjustAmountOfExternalAllocatedMemory(that.this_fiber->size());
- } 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()) {
- that.yielded = Persistent<Value>::New(args[0]);
- } else {
- that.yielded = Persistent<Value>::New(Undefined());
- }
- }
- that.SwapContext();
- return that.ReturnYielded();
- }
-
- /**
- * Throw an exception into a currently yielding fiber.
- */
- static Handle<Value> ThrowInto(const Arguments& args) {
- HandleScope scope;
- Unwrap(Fiber& that, args.This());
-
- if (!that.yielding) {
- THROW(Exception::Error, "This Fiber is not yielding");
- } else if (args.Length() == 0) {
- that.yielded = Persistent<Value>::New(Undefined());
- } else if (args.Length() == 1) {
- that.yielded = Persistent<Value>::New(args[0]);
- } else {
- THROW(Exception::TypeError, "throwInto() expects 1 or no arguments");
- }
- that.yielded_exception = true;
- that.SwapContext();
- return that.ReturnYielded();
- }
-
- /**
- * Unwinds a currently running fiber. If the fiber is not running then this function has no
- * effect.
- */
- static Handle<Value> Reset(const Arguments& args) {
- HandleScope scope;
- Unwrap(Fiber& that, args.This());
-
- if (!that.started) {
- return Undefined();
- } 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();
-
- Handle<Value> val = that.yielded;
- that.yielded.Dispose();
- if (that.yielded_exception) {
- return ThrowException(val);
- } else {
- return val;
- }
- }
-
- /**
- * 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);
- HandleScope scope;
- zombie = true;
-
- // Setup an exception which will be thrown and rethrown from Fiber::Yield()
- Local<Value> zombie_exception = Exception::Error(String::New("This Fiber is a zombie"));
- this->zombie_exception = Persistent<Value>::New(zombie_exception);
- yielded = Persistent<Value>::New(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;
- yielded.Dispose();
- yielded = Persistent<Value>::New(Undefined());
- }
- this->zombie_exception.Dispose();
- }
-
- /**
- * 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 = const_cast<Fiber*>(current);
- current = this;
-
- // This will jump into either `RunFiber()` or `Yield()`, depending on if the fiber was
- // already running.
- {
- Unlocker unlocker;
- this_fiber->run();
- }
-
- // At this point the fiber either returned or called `yield()`.
- current = last_fiber;
- }
-
- /**
- * Grabs and resets this fiber's yielded value.
- */
- Handle<Value> ReturnYielded() {
- HandleScope scope;
- Handle<Value> val = yielded;
- yielded.Dispose();
- if (yielded_exception) {
- return ThrowException(val);
- } else {
- return val;
- }
- }
-
- /**
- * This is the entry point for a new fiber, from `run()`.
- */
- static void RunFiber(void** data) {
- const Arguments* args = (const Arguments*)data[0];
- Fiber& that = *(Fiber*)data[1];
- delete[] data;
-
- {
- Locker locker;
- HandleScope scope;
-
- // Set stack guard for this "thread"
- ResourceConstraints constraints;
- constraints.set_stack_limit((uint32_t*)that.this_fiber->bottom());
- SetResourceConstraints(&constraints);
-
- TryCatch try_catch;
- that.ClearWeak();
- that.v8_context->Enter();
-
- if (args->Length()) {
- Handle<Value> argv[1] = { (*args)[0] };
- that.yielded = Persistent<Value>::New(that.cb->Call(that.v8_context->Global(), 1, argv));
- } else {
- that.yielded = Persistent<Value>::New(that.cb->Call(that.v8_context->Global(), 0, NULL));
- }
-
- if (try_catch.HasCaught()) {
- that.yielded.Dispose();
- that.yielded = Persistent<Value>::New(try_catch.Exception());
- that.yielded_exception = true;
- if (that.zombie && !that.resetting && that.yielded != that.zombie_exception) {
- // Throwing an exception from a garbage sweep
- fatal_stack = Persistent<Value>::New(try_catch.StackTrace());
- }
- } else {
- that.yielded_exception = false;
- }
-
- // Do not invoke the garbage collector if there's no context on the stack. It will seg fault
- // otherwise.
- V8::AdjustAmountOfExternalAllocatedMemory(-that.this_fiber->size());
-
- // 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.
- that.v8_context->Exit();
- }
-
- // The function returned (instead of yielding).
- that.started = false;
- that.this_fiber->finish(*const_cast<Coroutine*>(that.entry_fiber));
- }
-
- /**
- * 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()`.
- */
- static Handle<Value> Yield(const Arguments& args) {
- HandleScope scope;
-
- if (current == NULL) {
- THROW(Exception::Error, "yield() called with no fiber running");
- }
-
- Fiber& that = *const_cast<Fiber*>(current);
-
- if (that.zombie) {
- return ThrowException(that.zombie_exception);
- } else if (args.Length() == 0) {
- that.yielded = Persistent<Value>::New(Undefined());
- } else if (args.Length() == 1) {
- that.yielded = Persistent<Value>::New(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.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 that.ReturnYielded();
- }
-
- /**
- * Getters for `started`, and `current`.
- */
- static Handle<Value> GetStarted(Local<String> property, const AccessorInfo& info) {
- Unwrap(Fiber& that, info.This());
- return Boolean::New(that.started);
- }
-
- static Handle<Value> GetCurrent(Local<String> property, const AccessorInfo& info) {
- if (current) {
- return const_cast<Fiber*>(current)->handle;
- } else {
- return Undefined();
- }
- }
-
- public:
- /**
- * Initialize the Fiber library.
- */
- static void Init(Handle<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.
- Fiber::locker = new Locker;
- current = NULL;
- HandleScope scope;
- tmpl = Persistent<FunctionTemplate>::New(FunctionTemplate::New(New));
- tmpl->SetClassName(String::NewSymbol("Fiber"));
-
- fiber_token = Persistent<String>::New(String::NewSymbol("is_fiber"));
- tmpl->InstanceTemplate()->SetInternalFieldCount(1);
-
- Handle<ObjectTemplate> proto = tmpl->PrototypeTemplate();
- proto->Set(String::NewSymbol("reset"), FunctionTemplate::New(Reset));
- proto->Set(String::NewSymbol("run"), FunctionTemplate::New(Run));
- proto->Set(String::NewSymbol("throwInto"), FunctionTemplate::New(ThrowInto));
- proto->SetAccessor(String::NewSymbol("started"), GetStarted);
-
- Handle<Function> fn = tmpl->GetFunction();
- fn->SetAccessor(String::NewSymbol("current"), GetCurrent);
- target->Set(String::NewSymbol("Fiber"), fn);
- target->Set(String::NewSymbol("yield"), FunctionTemplate::New(Yield)->GetFunction());
- }
+ assert(!handle.IsEmpty()); \
+ if (!handle->IsObject() || handle->GetHiddenValue(fiber_token).IsEmpty()) { \
+ THROW(Exception::TypeError, "Illegal invocation"); \
+ } \
+ assert(handle->InternalFieldCount() == 1); \
+ target = *static_cast<Fiber*>(handle->GetPointerFromInternalField(0));
+
+ private:
+ static Locker* locker; // Node does not use locks or threads, so we need a global lock
+ static Persistent<FunctionTemplate> tmpl;
+ static volatile Fiber* current;
+ static vector<Fiber*> orphaned_fibers;
+ static Persistent<Value> fatal_stack;
+ static Persistent<String> fiber_token;
+
+ Persistent<Object> handle;
+ Persistent<Function> cb;
+ Persistent<Context> v8_context;
+ Persistent<Value> zombie_exception;
+ Persistent<Value> yielded;
+ volatile bool yielded_exception;
+ volatile Coroutine* entry_fiber;
+ Coroutine* this_fiber;
+ volatile bool started;
+ volatile bool yielding;
+ volatile bool zombie;
+ volatile bool resetting;
+
+ Fiber(Persistent<Object> handle, Persistent<Function> cb, Persistent<Context> v8_context) :
+ handle(handle),
+ cb(cb),
+ v8_context(v8_context),
+ started(false),
+ yielding(false),
+ zombie(false),
+ resetting(false) {
+ MakeWeak();
+ handle->SetPointerInInternalField(0, this);
+ }
+
+ virtual ~Fiber() {
+ assert(!this->started);
+ handle.Dispose();
+ cb.Dispose();
+ v8_context.Dispose();
+ }
+
+ /**
+ * 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() {
+ handle.MakeWeak(this, WeakCallback);
+ }
+
+ /**
+ * 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(Persistent<Value> value, void *data) {
+ Fiber& that = *static_cast<Fiber*>(data);
+ assert(that.handle == value);
+ assert(value.IsNearDeath());
+ 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.
+ String::Utf8Value stack(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 {
+ fatal_stack.Dispose();
+ }
+
+ that.yielded.Dispose();
+ 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 Handle<Value> New(const Arguments& args) {
+
+ HandleScope scope;
+ 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()) {
+ Handle<Value> argv[1] = { args[0] };
+ return tmpl->GetFunction()->NewInstance(1, argv);
+ }
+
+ Handle<Function> fn = Handle<Function>::Cast(args[0]);
+ args.This()->SetHiddenValue(fiber_token, Boolean::New(true));
+ new Fiber(
+ Persistent<Object>::New(args.This()),
+ Persistent<Function>::New(fn),
+ Persistent<Context>::New(Context::GetCurrent()));
+ return args.This();
+ }
+
+ /**
+ * 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 Handle<Value> Run(const Arguments& args) {
+ HandleScope scope;
+ Unwrap(Fiber& that, args.This());
+
+ // 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()`.
+ that.started = true;
+ void** data = new void*[2];
+ data[0] = (void*)&args;
+ data[1] = &that;
+ that.this_fiber = &Coroutine::create_fiber((void (*)(void*))RunFiber, data);
+ V8::AdjustAmountOfExternalAllocatedMemory(that.this_fiber->size());
+ } 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()) {
+ that.yielded = Persistent<Value>::New(args[0]);
+ } else {
+ that.yielded = Persistent<Value>::New(Undefined());
+ }
+ }
+ that.SwapContext();
+ return that.ReturnYielded();
+ }
+
+ /**
+ * Throw an exception into a currently yielding fiber.
+ */
+ static Handle<Value> ThrowInto(const Arguments& args) {
+ HandleScope scope;
+ Unwrap(Fiber& that, args.This());
+
+ if (!that.yielding) {
+ THROW(Exception::Error, "This Fiber is not yielding");
+ } else if (args.Length() == 0) {
+ that.yielded = Persistent<Value>::New(Undefined());
+ } else if (args.Length() == 1) {
+ that.yielded = Persistent<Value>::New(args[0]);
+ } else {
+ THROW(Exception::TypeError, "throwInto() expects 1 or no arguments");
+ }
+ that.yielded_exception = true;
+ that.SwapContext();
+ return that.ReturnYielded();
+ }
+
+ /**
+ * Unwinds a currently running fiber. If the fiber is not running then this function has no
+ * effect.
+ */
+ static Handle<Value> Reset(const Arguments& args) {
+ HandleScope scope;
+ Unwrap(Fiber& that, args.This());
+
+ if (!that.started) {
+ return Undefined();
+ } 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();
+
+ Handle<Value> val = that.yielded;
+ that.yielded.Dispose();
+ if (that.yielded_exception) {
+ return ThrowException(val);
+ } else {
+ return val;
+ }
+ }
+
+ /**
+ * 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);
+ HandleScope scope;
+ zombie = true;
+
+ // Setup an exception which will be thrown and rethrown from Fiber::Yield()
+ Local<Value> zombie_exception = Exception::Error(String::New("This Fiber is a zombie"));
+ this->zombie_exception = Persistent<Value>::New(zombie_exception);
+ yielded = Persistent<Value>::New(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;
+ yielded.Dispose();
+ yielded = Persistent<Value>::New(Undefined());
+ }
+ this->zombie_exception.Dispose();
+ }
+
+ /**
+ * 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 = const_cast<Fiber*>(current);
+ current = this;
+
+ // This will jump into either `RunFiber()` or `Yield()`, depending on if the fiber was
+ // already running.
+ {
+ Unlocker unlocker;
+ this_fiber->run();
+ }
+
+ // At this point the fiber either returned or called `yield()`.
+ current = last_fiber;
+ }
+
+ /**
+ * Grabs and resets this fiber's yielded value.
+ */
+ Handle<Value> ReturnYielded() {
+ HandleScope scope;
+ Handle<Value> val = yielded;
+ yielded.Dispose();
+ if (yielded_exception) {
+ return ThrowException(val);
+ } else {
+ return val;
+ }
+ }
+
+ /**
+ * This is the entry point for a new fiber, from `run()`.
+ */
+ static void RunFiber(void** data) {
+ const Arguments* args = (const Arguments*)data[0];
+ Fiber& that = *(Fiber*)data[1];
+ delete[] data;
+
+ {
+ Locker locker;
+ HandleScope scope;
+
+ // Set the stack guard for this "thread"; 512 bytes of padding (?)
+ ResourceConstraints constraints;
+ constraints.set_stack_limit((uint32_t*)that.this_fiber->bottom() + 512);
+ SetResourceConstraints(&constraints);
+
+ TryCatch try_catch;
+ that.ClearWeak();
+ that.v8_context->Enter();
+
+ if (args->Length()) {
+ Handle<Value> argv[1] = { (*args)[0] };
+ that.yielded = Persistent<Value>::New(that.cb->Call(that.v8_context->Global(), 1, argv));
+ } else {
+ that.yielded = Persistent<Value>::New(that.cb->Call(that.v8_context->Global(), 0, NULL));
+ }
+
+ if (try_catch.HasCaught()) {
+ that.yielded.Dispose();
+ that.yielded = Persistent<Value>::New(try_catch.Exception());
+ that.yielded_exception = true;
+ if (that.zombie && !that.resetting && that.yielded != that.zombie_exception) {
+ // Throwing an exception from a garbage sweep
+ fatal_stack = Persistent<Value>::New(try_catch.StackTrace());
+ }
+ } else {
+ that.yielded_exception = false;
+ }
+
+ // Do not invoke the garbage collector if there's no context on the stack. It will seg fault
+ // otherwise.
+ V8::AdjustAmountOfExternalAllocatedMemory(-that.this_fiber->size());
+
+ // 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.
+ that.v8_context->Exit();
+ }
+
+ // The function returned (instead of yielding).
+ that.started = false;
+ that.this_fiber->finish(*const_cast<Coroutine*>(that.entry_fiber));
+ }
+
+ /**
+ * 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()`.
+ */
+ static Handle<Value> Yield(const Arguments& args) {
+ HandleScope scope;
+
+ if (current == NULL) {
+ THROW(Exception::Error, "yield() called with no fiber running");
+ }
+
+ Fiber& that = *const_cast<Fiber*>(current);
+
+ if (that.zombie) {
+ return ThrowException(that.zombie_exception);
+ } else if (args.Length() == 0) {
+ that.yielded = Persistent<Value>::New(Undefined());
+ } else if (args.Length() == 1) {
+ that.yielded = Persistent<Value>::New(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.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 that.ReturnYielded();
+ }
+
+ /**
+ * Getters for `started`, and `current`.
+ */
+ static Handle<Value> GetStarted(Local<String> property, const AccessorInfo& info) {
+ Unwrap(Fiber& that, info.This());
+ return Boolean::New(that.started);
+ }
+
+ static Handle<Value> GetCurrent(Local<String> property, const AccessorInfo& info) {
+ if (current) {
+ return const_cast<Fiber*>(current)->handle;
+ } else {
+ return Undefined();
+ }
+ }
+
+ public:
+ /**
+ * Initialize the Fiber library.
+ */
+ static void Init(Handle<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.
+ Fiber::locker = new Locker;
+ current = NULL;
+ HandleScope scope;
+ tmpl = Persistent<FunctionTemplate>::New(FunctionTemplate::New(New));
+ tmpl->SetClassName(String::NewSymbol("Fiber"));
+
+ fiber_token = Persistent<String>::New(String::NewSymbol("is_fiber"));
+ tmpl->InstanceTemplate()->SetInternalFieldCount(1);
+
+ Handle<ObjectTemplate> proto = tmpl->PrototypeTemplate();
+ proto->Set(String::NewSymbol("reset"), FunctionTemplate::New(Reset));
+ proto->Set(String::NewSymbol("run"), FunctionTemplate::New(Run));
+ proto->Set(String::NewSymbol("throwInto"), FunctionTemplate::New(ThrowInto));
+ proto->SetAccessor(String::NewSymbol("started"), GetStarted);
+
+ Handle<Function> fn = tmpl->GetFunction();
+ fn->SetAccessor(String::NewSymbol("current"), GetCurrent);
+ target->Set(String::NewSymbol("Fiber"), fn);
+ target->Set(String::NewSymbol("yield"), FunctionTemplate::New(Yield)->GetFunction());
+ }
};
Persistent<FunctionTemplate> Fiber::tmpl;
@@ -482,8 +482,8 @@ Persistent<Value> Fiber::fatal_stack;
Persistent<String> Fiber::fiber_token;
extern "C" void init(Handle<Object> target) {
- HandleScope scope;
- Handle<Object> global = Context::GetCurrent()->Global();
- assert(Coroutine::is_local_storage_enabled());
- Fiber::Init(global);
+ HandleScope scope;
+ Handle<Object> global = Context::GetCurrent()->Global();
+ assert(Coroutine::is_local_storage_enabled());
+ Fiber::Init(global);
}
View
14 src/platform.mk
@@ -5,24 +5,24 @@ NODE_BITS := $(shell file `which node` | egrep -o '[0-9]{2}-bit' | cut -c-2)
CPPFLAGS = -Wall -I$(NODE_PREFIX)/include -I$(NODE_PREFIX)/include/node
ifdef DEBUG
- CPPFLAGS += -ggdb -O0
+ CPPFLAGS += -ggdb -O0
else
- CPPFLAGS += -g -O3 -minline-all-stringops
+ CPPFLAGS += -g -O3 -minline-all-stringops
endif
ifeq ($(NODE_BITS), )
- CPPFLAGS += -m32
+ CPPFLAGS += -m32
endif
ifeq ($(NODE_BITS), 32)
- CPPFLAGS += -m32
+ CPPFLAGS += -m32
endif
ifeq ($(NODE_BITS), 64)
- CPPFLAGS += -m64
+ CPPFLAGS += -m64
endif
ifeq ($(NODE_PLATFORM), linux)
- COROUTINE_SO = coroutine.so
+ COROUTINE_SO = coroutine.so
endif
ifeq ($(NODE_PLATFORM), darwin)
- COROUTINE_SO = coroutine.dylib
+ COROUTINE_SO = coroutine.dylib
endif
Please sign in to comment.