meteor/node-fibers

Name: node-fibers

Owner: Meteor

Description: Fiber/coroutine support for v8 and node.

Created: 2015-02-09 19:42:02.0

Updated: 2016-11-01 10:05:00.0

Pushed: 2015-02-09 19:53:06.0

Homepage:

Size: 316

Language: C

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

fibers(1) – Fiber support for v8 and Node

INSTALLING
via npm
from source

Note: node-fibers uses node-gyp for building. To manually invoke the build process, you can use node-gyp rebuild. This will put the compiled extension in build/Release/fibers.node. However, when you do require('fibers'), it will expect the module to be in, for example, bin/linux-x64-v8-3.11/fibers.node. You can manually put the module here every time you build, or you can use the included build script. Either npm install or node build -f will do this for you. If you are going to be hacking on node-fibers, it may be worthwhile to first do node-gyp configure and then for subsequent rebuilds you can just do node-gyp build which will be faster than a full npm install or node-gyp rebuild.

important!

It's recommended that you use node 0.6.18 or higher with node-fibers. Using other versions may lead to instability during high loads.

other notes

Unlike most NodeJS projects, node-fibers is a C++ project. Some extra work is required to compile node-fibers, but pretty much every platform is supported in some way. Binary distributions in 32 and 64-bit forms are provided in npm for Linux, OS X, and Windows (special thanks to Jeroen Janssen for his work on fibers in Windows).

Support for Solaris, FreeBSD, and OpenBSD is provided by compiling the extension on your system during install time via node-gyp. If your operating system isn't listed here you may have luck copying the build process for one of the other OS's, assuming you are running a POSIX-like OS.

node 0.6.x is required to run this release of node-fibers. Older versions of node (0.4.x) are supported in older releases of node-fibers. See the 0.5.x branch of node-fibers for documentation.

EXAMPLES

The examples below describe basic use of Fiber, but note that it is not recommended to use Fiber without an abstraction in between your code and fibers. See “FUTURES” below for additional information.

Sleep

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
Fiber = require('fibers');

tion sleep(ms) {
var fiber = Fiber.current;
setTimeout(function() {
    fiber.run();
}, ms);
Fiber.yield();


r(function() {
console.log('wait... ' + new Date);
sleep(1000);
console.log('ok... ' + new Date);
un();
ole.log('back in main');
$ node 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)
Incremental Generator

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(). Again, the node event loop is never blocked while this script is running.

$ cat generator.js
Fiber = require('fibers');

inc = Fiber(function(start) {
var total = start;
while (true) {
    total += Fiber.yield(total);
}


(var ii = inc.run(1); ii <= 10; ii = inc.run(1)) {
console.log(ii);

$ node generator.js
1
2
3
4
5
6
7
8
9
10
Fibonacci Generator

Expanding on the incremental generator above, we can create a generator which returns a new Fibonacci number with each invocation. You can compare this with the ECMAScript Harmony Generator Fibonacci example.

$ cat fibonacci.js
Fiber = require('fibers');

enerator function. Returns a function which returns incrementing
ibonacci numbers with each call.
tion Fibonacci() {
// Create a new fiber which yields sequential Fibonacci numbers
var fiber = Fiber(function() {
    Fiber.yield(0); // F(0) -> 0
    var prev = 0, curr = 1;
    while (true) {
        Fiber.yield(curr);
        var tmp = prev + curr;
        prev = curr;
        curr = tmp;
    }
});
// Return a bound handle to `run` on this fiber
return fiber.run.bind(fiber);


nitialize a new Fibonacci sequence and iterate up to 1597
seq = Fibonacci();
(var ii = seq(); ii <= 1597; ii = seq()) {
console.log(ii);

$ node fibonacci.js
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
Basic Exceptions

Fibers are exception-safe; exceptions will continue travelling through fiber boundaries:

$ cat error.js
Fiber = require('fibers');

fn = Fiber(function() {
console.log('async work here...');
Fiber.yield();
console.log('still working...');
Fiber.yield();
console.log('just a little bit more...');
Fiber.yield();
throw new Error('oh crap!');


{
while (true) {
    fn.run();
}
tch(e) {
console.log('safely caught that error!');
console.log(e.stack);

ole.log('done!');
$ node 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!
FUTURES

Using the Fiber class without an abstraction in between your code and the raw API is not recommended. Fiber is meant to implement the smallest amount of functionality in order make possible many different programming patterns. This makes the Fiber class relatively lousy to work with directly, but extremely powerful when coupled with a decent abstraction. There is no right answer for which abstraction is right for you and your project. Included with node-fibers is an implementation of “futures” which is fiber-aware. Usage of this library is documented below. There are several other externally-maintained options which can be found on the wiki. You should feel encouraged to be creative with fibers and build a solution which works well with your project. For instance, Future is not a good abstraction to use if you want to build a generator function (see Fibonacci example above).

Using Future to wrap existing node functions. At no point is the node event loop blocked:

$ cat ls.js
Future = require('fibers/future');
fs = Future.wrap(require('fs'));

re.task(function() {
// Get a list of files in the directory
var fileNames = fs.readdirFuture('.').wait();
console.log('Found '+ fileNames.length+ ' files');

// Stat each file
var stats = [];
for (var ii = 0; ii < fileNames.length; ++ii) {
    stats.push(fs.statFuture(fileNames[ii]));
}
stats.map(function(f) {
    f.wait()
});

// Print file size
for (var ii = 0; ii < fileNames.length; ++ii) {
    console.log(fileNames[ii]+ ': '+ stats[ii].get().size);
}
etach();
$ node ls.js 
Found 11 files
bin: 4096
fibers.js: 1708
.gitignore: 37
README.md: 8664
future.js: 5833
.git: 4096
LICENSE: 1054
src: 4096
ls.js: 860
Makefile: 436
package.json: 684

The future API is designed to make it easy to move between classic callback-style code and fiber-aware waiting code:

$ cat sleep.js
Future = require('fibers/future'), wait = Future.wait;

his function returns a future which resolves after a timeout. This
emonstrates manually resolving futures.
tion sleep(ms) {
var future = new Future;
setTimeout(function() {
    future.return();
}, ms);
return future;


ou can create functions which automatically run in their own fiber and
eturn futures that resolve when the fiber returns (this probably sounds
onfusing.. just play with it to understand).
calcTimerDelta = function(ms) {
var start = new Date;
sleep(ms).wait();
return new Date - start;
ture(); // <-- important!

nd futures also include node-friendly callbacks if you don't want to use
ait()
TimerDelta(2000).resolve(function(err, val) {
console.log('Set timer for 2000ms, waited '+ val+ 'ms');

$ node sleep.js
Set timer for 2000ms, waited 2009ms
API DOCUMENTATION

Fiber's definition looks something like this:


nstantiate a new Fiber. You may invoke this either as a function or as
 constructor; the behavior is the same.

hen run() is called on this fiber for the first time, `fn` will be
nvoked as the first frame on a new stack. Execution will continue on
his new stack until `fn` returns, or Fiber.yield() is called.

fter the function returns the fiber is reset to original state and
ay be restarted with another call to run().

tion 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).

ee "Garbage Collection" for more information on responsible use of
Fiber.current`.

r.current = undefined;


Fiber.yield()` will halt execution of the current fiber and return control
ack to original caller of run(). If an argument is supplied to yield(),
un() will return that value.

hen run() is called again, yield() will return.

ote that this function is a global to allow for correct garbage
ollection. This results in no loss of functionality because it is only
alid to yield from the currently running fiber anyway.

ote also that `yield` is a reserved word in Javascript. This is normally
ot an issue, however some code linters may complain. Rest assured that it
ill run fine now and in future versions of Javascript.

r.yield = function(param) {
[native code]



un() will start execution of this Fiber, or if it is currently yielding,
t will resume execution. If an argument is supplied, this argument will
e passed to the fiber, either as the first parameter to the main
unction [if the fiber has not been started] or as the return value of
ield() [if the fiber is currently yielding].

his function will return either the parameter passed to yield(), or the
eturned value from the fiber's main function.

r.prototype.run = function(param) {
[native code]



eset() will terminate a running Fiber and restore it to its original
tate, as if it had returned execution.

his is accomplished by causing yield() to throw an exception, and any
uther calls to yield() will also throw an exception. This continues
ntil the fiber has completely unwound and returns.

f the fiber returns a value it will be returned by reset().

f the fiber is not running, reset() will have no effect.

r.prototype.reset = function() {
[native code]



hrowInto() will cause a currently yielding fiber's yield() call to
hrow instead of return gracefully. This can be useful for notifying a
iber that you are no longer interested in its task, and that it should
ive up.

ote that if the fiber does not handle the exception it will continue to
ubble up and throwInto() will throw the exception right back at you.

r.prototype.throwInto = function(exception) {
[native code]

Future's definition looks something like this:


eturns a future-function which, when run, starts running the target
unction and returns a future for the result.

xample usage: 
ar funcy = function(arg) {
 return arg+1;
.future();

uncy(1).wait(); // returns 2

tion.prototype.future = function() { ... }


uture object, instantiated with the new operator.

tion Future() {}


rap a node-style async function to return a future in place of using a callback.

n - the function or object to wrap
rray - indicates that this callback will return more than 1 argument after `err`. For example,
       `child_process.exec()` returns [err, stdout, stderr]
uffix - appends a string to every method that was overridden, if you passed an object

xample usage: Future.wrap(asyncFunction)(arg1).wait()

re.wrap = function(fn, multi, suffix) { ... }


nvoke a function that will be run in its own fiber context and return a future to its return
alue.

xample:
uture.task(function() {
 // You can safely `wait` on stuff here
).detach();

re.task = function(fn) { ... }


ait on a series of futures and then return. If the futures throw an exception this function
won't/ throw it back. You can get the value of the future by calling get() on it directly. If
ou want to wait on a single future you're better off calling future.wait() on the instance.

xample usage: Future.wait(aFuture, anotherFuture)

re.wait = function(/* ... */) { ... }


eturn the value of this future. If the future hasn't resolved yet this will throw an error.

re.prototype.get = function() { ... }


ark this future as returned. All pending callbacks will be invoked immediately.

alue - the value to return when get() or wait() is called.

xample usage: aFuture.return(value)

re.prototype.return = function(value) { ... }


hrow from this future as returned. All pending callbacks will be invoked immediately.
ote that execution will continue normally after running this method, 
o make sure you exit appropriately after running throw()

rror - the error to throw when get() or wait() is called.

xample usage: aFuture.throw(new Error("Something borked"))

re.prototype.throw = function(error) { ... }


detach" this future. Basically this is useful if you want to run a task in a future, you
ren't interested in its return value, but if it throws you don't want the exception to be
ost. If this fiber throws, an exception will be thrown to the event loop and node will
robably fall down.

re.prototype.detach = function() { ... }


eturns whether or not this future has resolved yet.

re.prototype.isResolved = function() { ... }


eturns a node-style function which will mark this future as resolved when called.

xample usage: 
 var errback = aFuture.resolver();
 asyncFunction(arg1, arg2, etc, errback)
 var result = aFuture.wait();

re.prototype.resolver = function() { ... }


aits for this future to resolve and then invokes a callback.

f only one argument is passed it is a standard function(err, val){} errback.

f two arguments are passed, the first argument is a future which will be thrown to in the case
f error, and the second is a function(val){} callback.

re.prototype.resolve = function(/* errback or future, callback */) { ... }


ropogate results to another future.

xample usage: future1.proxy(future2) // future2 gets automatically resolved with however future1 resolves

re.prototype.proxy = function(future) { ... }


iffers from its functional counterpart in that it actually resolves the future. Thus if the
uture threw, future.wait() will throw.

re.prototype.wait = function() { ... }
GARBAGE COLLECTION

If you intend to build generators, iterators, or “lazy lists”, you should be aware that all fibers must eventually unwind. This is implemented by causing yield() to throw unconditionally when the library is trying to unwind your fiber– either because reset() was called, or all handles to the fiber were lost and v8 wants to delete it.

Something like this will, at some point, cause an infinite loop in your application:

fiber = Fiber(function() {
while (true) {
    try {
        Fiber.yield();
    } catch(e) {}
}

r.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 causing all calls to yield() to throw. However, if you catch these exceptions and continue anyway, an infinite loop will occur.

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:

fiber = Fiber(function() {
var that = Fiber.current;
Fiber.yield();

r.run();
r = 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 still outstanding, v8 will never garbage collect it and the stack will remain in memory until the application exits.

Thus, you should take care when grabbing references to Fiber.current.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.