This commit is contained in:
darenhsu
2022-07-17 13:16:16 +08:00
parent 84759556ff
commit befd344ab0
28070 changed files with 4008428 additions and 1 deletions
+32
View File
@@ -0,0 +1,32 @@
sudo: false
language: node_js
node_js:
- "6"
- "8"
- "10"
- "12"
cache:
directories:
- node_modules
install:
- npm install
script:
- npm run test
env:
- CXX=g++-4.8 CC=gcc-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
notifications:
email: false
+18
View File
@@ -0,0 +1,18 @@
<!--
Thank you for reporting an issue about fibers. Please make sure you've read the first section of the README because it contains valuable information about common issues.
Some common issues:
- If you are having problems with an odd-numbered version of nodejs please try again with an even-numbered version instead. The nodejs team doesn't do a good job of communicating this but odd-numbered versions are basically beta versions.
- If you are having problems with a Meteor project please stop right now and go read their documentation. You most likely are trying to use a version of nodejs that is not supported by Meteor.
- Find the npm version badge on the front page of this project's github. If the version of fibers you're trying to install is not the latest version then please stop and update your dependencies to require the latest version. This often happens when attempting to install another npm module which depends on fibers.
- If your error says "Can't wait without a fiber" then please go read the documentation again. You need a top-level Fiber or Future before calling `wait`.
- If you're using Webpack then don't. Fibers can't be used with Webpack.
If none of these apply then feel free to open a ticket with a bug report or question. PLEASE INCLUDE ENOUGH INFORMATION IN YOUR REPORT. You are a software engineer! Ask yourself: "if someone was reporting an issue about something that I wrote what information would be helpful?". That is, please include your nodejs version, your operating system version, your compiler version, what you've tried so far to fix the issue.. things like that. If some specific code is triggering the issue then please include a full example of the code that can be run on its own. Please no "snippets", if I can't run the code without modifications then I can't help you.
-->
+18
View File
@@ -0,0 +1,18 @@
Copyright 2011 Marcel Laverdet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
+599
View File
@@ -0,0 +1,599 @@
fibers(1) -- Fiber support for v8 and Node
==========================================
[![npm version](https://badgen.now.sh/npm/v/fibers)](https://www.npmjs.com/package/fibers)
[![isc license](https://badgen.now.sh/npm/license/fibers)](https://github.com/laverdet/node-fibers/blob/master/LICENSE)
[![travis build](https://badgen.now.sh/travis/laverdet/node-fibers)](https://travis-ci.org/laverdet/node-fibers)
[![npm downloads](https://badgen.now.sh/npm/dm/fibers)](https://www.npmjs.com/package/fibers)
Fibers, sometimes called [coroutines](https://en.wikipedia.org/wiki/Coroutine), are a powerful tool which expose an API to jump between multiple call stacks from within a single thread. This can be useful to make code written for a synchronous library play nicely in an asynchronous environment.
INSTALLING
----------
[![NPM](https://nodei.co/npm/fibers.png)](https://www.npmjs.com/package/fibers)
### via npm
* `npm install fibers`
* You're done! (see "supported platforms" below if you run into errors)
### from source
* `git clone git://github.com/laverdet/node-fibers.git`
* `cd node-fibers`
* `npm install`
Note: node-fibers uses [node-gyp](https://github.com/TooTallNate/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`.
### meteor users please read this
If you're trying to get meteor running and you ended up at this page you're
probably doing something wrong. Please uninstall all versions of NodeJS and
Meteor, then start over. See
[meteor#5124](https://github.com/meteor/meteor/issues/5124) for more
information.
### supported platforms
If you are running nodejs version 10.x or 12.x on Linux, OS X, or Windows (7 or later) then you
should be able to install fibers from npm just fine. If you are running nodejs v8.x then you will
need to use `npm install fibers@3`. If you are running nodejs v6.x then you will need to use `npm
install fibers@2`. For nodejs v4.x you can use `npm install fibers@1`. If you are running an older
(or newer) version of node or some other operating system you will have to compile fibers on your
system.
(special thanks to [Jeroen Janssen](https://github.com/japj) for his work on fibers in Windows)
If you do end up needing to compile fibers first make sure you have node-gyp installed as a global
dependency (`npm install -g node-gyp`), and that you have setup your build environment by following
the instructions at [node-gyp](https://github.com/TooTallNate/node-gyp). Ubuntu-flavored Linux users
may need to run `sudo apt-get install g++` as well.
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
```javascript
var Fiber = require('fibers');
function sleep(ms) {
var fiber = Fiber.current;
setTimeout(function() {
fiber.run();
}, ms);
Fiber.yield();
}
Fiber(function() {
console.log('wait... ' + new Date);
sleep(1000);
console.log('ok... ' + new Date);
}).run();
console.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
```javascript
var Fiber = require('fibers');
var inc = Fiber(function(start) {
var total = start;
while (true) {
total += Fiber.yield(total);
}
});
for (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](http://wiki.ecmascript.org/doku.php?id=harmony:generators) Fibonacci
example.
$ cat fibonacci.js
```javascript
var Fiber = require('fibers');
// Generator function. Returns a function which returns incrementing
// Fibonacci numbers with each call.
function 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);
}
// Initialize a new Fibonacci sequence and iterate up to 1597
var seq = Fibonacci();
for (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
```javascript
var Fiber = require('fibers');
var 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!');
});
try {
while (true) {
fn.run();
}
} catch(e) {
console.log('safely caught that error!');
console.log(e.stack);
}
console.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](https://github.com/laverdet/node-fibers/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
```javascript
var Future = require('fibers/future');
var fs = Future.wrap(require('fs'));
Future.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);
}
}).detach();
```
$ 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
```javascript
var Future = require('fibers/future'), wait = Future.wait;
// This function returns a future which resolves after a timeout. This
// demonstrates manually resolving futures.
function sleep(ms) {
var future = new Future;
setTimeout(function() {
future.return();
}, ms);
return future;
}
// You can create functions which automatically run in their own fiber and
// return futures that resolve when the fiber returns (this probably sounds
// confusing.. just play with it to understand).
var calcTimerDelta = function(ms) {
var start = new Date;
sleep(ms).wait();
return new Date - start;
}.future(); // <-- important!
// And futures also include node-friendly callbacks if you don't want to use
// wait()
calcTimerDelta(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:
```javascript
/**
* 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 Fiber.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;
/**
* `Fiber.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.
*
* Note also that `yield` is a reserved word in Javascript. This is normally
* not an issue, however some code linters may complain. Rest assured that it
* will run fine now and in future versions of Javascript.
*/
Fiber.yield = function(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]
}
```
Future's definition looks something like this:
```javascript
/**
* Returns a future-function which, when run, starts running the target
* function and returns a future for the result.
*
* Example usage:
* var funcy = function(arg) {
* return arg+1;
* }.future();
*
* funcy(1).wait(); // returns 2
*/
Function.prototype.future = function() { ... }
/**
* Future object, instantiated with the new operator.
*/
function Future() {}
/**
* Wrap a node-style async function to return a future in place of using a callback.
*
* fn - the function or object to wrap
* array - indicates that this callback will return more than 1 argument after `err`. For example,
* `child_process.exec()` returns [err, stdout, stderr]
* suffix - appends a string to every method that was overridden, if you passed an object
*
* Example usage: Future.wrap(asyncFunction)(arg1).wait()
*/
Future.wrap = function(fn, multi, suffix) { ... }
/**
* Invoke a function that will be run in its own fiber context and return a future to its return
* value.
*
* Example:
* Future.task(function() {
* // You can safely `wait` on stuff here
* }).detach();
*/
Future.task = function(fn) { ... }
/**
* Wait 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
* you want to wait on a single future you're better off calling future.wait() on the instance.
*
* Example usage: Future.wait(aFuture, anotherFuture)
*/
Future.wait = function(/* ... */) { ... }
/**
* Return the value of this future. If the future hasn't resolved yet this will throw an error.
*/
Future.prototype.get = function() { ... }
/**
* Mark this future as returned. All pending callbacks will be invoked immediately.
*
* value - the value to return when get() or wait() is called.
*
* Example usage: aFuture.return(value)
*/
Future.prototype.return = function(value) { ... }
/**
* Throw from this future as returned. All pending callbacks will be invoked immediately.
* Note that execution will continue normally after running this method,
* so make sure you exit appropriately after running throw()
*
* error - the error to throw when get() or wait() is called.
*
* Example usage: aFuture.throw(new Error("Something borked"))
*/
Future.prototype.throw = function(error) { ... }
/**
* "detach" this future. Basically this is useful if you want to run a task in a future, you
* aren't interested in its return value, but if it throws you don't want the exception to be
* lost. If this fiber throws, an exception will be thrown to the event loop and node will
* probably fall down.
*/
Future.prototype.detach = function() { ... }
/**
* Returns whether or not this future has resolved yet.
*/
Future.prototype.isResolved = function() { ... }
/**
* Returns a node-style function which will mark this future as resolved when called.
*
* Example usage:
* var errback = aFuture.resolver();
* asyncFunction(arg1, arg2, etc, errback)
* var result = aFuture.wait();
*/
Future.prototype.resolver = function() { ... }
/**
* Waits for this future to resolve and then invokes a callback.
*
* If only one argument is passed it is a standard function(err, val){} errback.
*
* If two arguments are passed, the first argument is a future which will be thrown to in the case
* of error, and the second is a function(val){} callback.
*/
Future.prototype.resolve = function(/* errback or future, callback */) { ... }
/**
* Propogate results to another future.
*
* Example usage: future1.proxy(future2) // future2 gets automatically resolved with however future1 resolves
*/
Future.prototype.proxy = function(future) { ... }
/**
* Differs from its functional counterpart in that it actually resolves the future. Thus if the
* future threw, future.wait() will throw.
*/
Future.prototype.wait = function() { ... }
/**
* Support for converting a Future to and from ES6 Promises.
*/
Future.fromPromise = function(promise) { ... }
Future.prototype.promise = 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:
```javascript
var fiber = Fiber(function() {
while (true) {
try {
Fiber.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
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:
```javascript
var fiber = Fiber(function() {
var that = Fiber.current;
Fiber.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
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`.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Generated Vendored Executable
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env node
"use strict";
var fs = require('fs');
global.Fiber = require('../fibers');
global.Future = require('../future');
// Start the repl
var vm = require('vm');
var domain = require('domain');
var repl = require('repl').start('node> ', null, fiberEval, true, true);
function fiberEval(code, context, file, cb) {
if (/^\([ \r\n\t+]\)$/.test(code)) {
return cb(false, undefined);
}
// Parses?
try {
new Function(code);
} catch (err) {
return cb(err, false);
}
// Run in fiber
Future.task(function() {
// Save history
var last;
repl.rli.history = repl.rli.history.slice(0, 50).filter(function(item) {
try {
return item !== last;
} finally {
last = item;
}
});
fs.writeFile(process.env.HOME+ '/.node-history', JSON.stringify(repl.rli.history), function(){});
// Run user code
var d = domain.create();
d.run(function() {
cb(null, vm.runInThisContext(code, file));
});
d.on('error', function(err) {
console.error('\nUnhandled error: '+ err.stack);
});
}).resolve(cb);
}
// Load history
try {
repl.rli.history = JSON.parse(fs.readFileSync(process.env.HOME+ '/.node-history', 'utf-8'));
} catch (err) {}
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+68
View File
@@ -0,0 +1,68 @@
{
'target_defaults': {
'default_configuration': 'Release',
'configurations': {
'Release': {
'cflags': [ '-O3' ],
'xcode_settings': {
'GCC_OPTIMIZATION_LEVEL': '3',
'GCC_GENERATE_DEBUGGING_SYMBOLS': 'NO',
},
'msvs_settings': {
'VCCLCompilerTool': {
'Optimization': 3,
'FavorSizeOrSpeed': 1,
},
},
}
},
},
'targets': [
{
'target_name': 'fibers',
'sources': [
'src/fibers.cc',
'src/coroutine.cc',
'src/libcoro/coro.c',
# Rebuild on header changes
'src/coroutine.h',
'src/libcoro/coro.h',
],
'cflags!': ['-ansi'],
'conditions': [
['OS == "win"',
{'defines': ['CORO_FIBER', 'WINDOWS']},
# else
{
'defines': ['USE_CORO', 'CORO_GUARDPAGES=1'],
'ldflags': ['-pthread'],
}
],
['OS == "linux"',
{
'cflags_c': [ '-std=gnu11' ],
'variables': {
'USE_MUSL': '<!(ldd --version 2>&1 | head -n1 | grep "musl" | wc -l)',
},
'conditions': [
['<(USE_MUSL) == 1',
{'defines': ['CORO_ASM', '__MUSL__']},
{'defines': ['CORO_UCONTEXT']}
],
],
},
],
['OS == "solaris" or OS == "sunos" or OS == "freebsd" or OS == "aix"', {'defines': ['CORO_UCONTEXT']}],
['OS == "mac"', {'defines': ['CORO_ASM']}],
['OS == "openbsd"', {'defines': ['CORO_ASM']}],
['target_arch == "arm" or target_arch == "arm64"',
{
# There's been problems getting real fibers working on arm
'defines': ['CORO_PTHREAD'],
'defines!': ['CORO_UCONTEXT', 'CORO_SJLJ', 'CORO_ASM'],
},
],
],
},
],
}
Generated Vendored Executable
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env node
var cp = require('child_process'),
fs = require('fs'),
path = require('path'),
detectLibc = require('detect-libc');
// Parse args
var force = false, debug = false;
var
arch = process.arch,
platform = process.platform;
var args = process.argv.slice(2).filter(function(arg) {
if (arg === '-f') {
force = true;
return false;
} else if (arg.substring(0, 13) === '--target_arch') {
arch = arg.substring(14);
} else if (arg === '--debug') {
debug = true;
}
return true;
});
if (!debug) {
args.push('--release');
}
if (!{ia32: true, x64: true, arm: true, arm64: true, ppc: true, ppc64: true, s390: true, s390x: true}.hasOwnProperty(arch)) {
console.error('Unsupported (?) architecture: `'+ arch+ '`');
process.exit(1);
}
// Test for pre-built library
var modPath = platform+ '-'+ arch+ '-'+ process.versions.modules+ ((platform === 'linux') ? '-'+ detectLibc.family : '');
if (!force) {
try {
fs.statSync(path.join(__dirname, 'bin', modPath, 'fibers.node'));
console.log('`'+ modPath+ '` exists; testing');
cp.execFile(process.execPath, ['quick-test'], function(err, stdout, stderr) {
if (err || stdout !== 'pass' || stderr) {
console.log('Problem with the binary; manual build incoming');
build();
} else {
console.log('Binary is fine; exiting');
}
});
} catch (ex) {
// Stat failed
build();
}
} else {
build();
}
// Build it
function build() {
if (process.versions.electron) {
args.push('--target='+ process.versions.electron, '--dist-url=https://atom.io/download/atom-shell');
}
cp.spawn(
process.platform === 'win32' ? 'node-gyp.cmd' : 'node-gyp',
['rebuild'].concat(args),
{stdio: [process.stdin, process.stdout, process.stderr]})
.on('exit', function(err) {
if (err) {
console.error(
'node-gyp exited with code: '+ err+ '\n'+
'Please make sure you are using a supported platform and node version. If you\n'+
'would like to compile fibers on this machine please make sure you have setup your\n'+
'build environment--\n'+
'Windows + OS X instructions here: https://github.com/nodejs/node-gyp\n'+
'Ubuntu users please run: `sudo apt-get install g++ build-essential`\n'+
'RHEL users please run: `yum install gcc-c++` and `yum groupinstall \'Development Tools\'` \n'+
'Alpine users please run: `sudo apk add python make g++`'
);
return process.exit(err);
}
afterBuild();
})
.on('error', function(err) {
console.error(
'node-gyp not found! Please ensure node-gyp is in your PATH--\n'+
'Try running: `sudo npm install -g node-gyp`'
);
console.log(err.message);
process.exit(1);
});
}
// Move it to expected location
function afterBuild() {
var targetPath = path.join(__dirname, 'build', debug ? 'Debug' : 'Release', 'fibers.node');
var installPath = path.join(__dirname, 'bin', modPath, 'fibers.node');
try {
fs.mkdirSync(path.join(__dirname, 'bin', modPath));
} catch (ex) {}
try {
fs.statSync(targetPath);
} catch (ex) {
console.error('Build succeeded but target not found');
process.exit(1);
}
fs.renameSync(targetPath, installPath);
console.log('Installed in `'+ installPath+ '`');
if (process.versions.electron) {
process.nextTick(function() {
require('electron').app.quit();
});
}
}
+324
View File
@@ -0,0 +1,324 @@
# We borrow heavily from the kernel build setup, though we are simpler since
# we don't have Kconfig tweaking settings on us.
# The implicit make rules have it looking for RCS files, among other things.
# We instead explicitly write all the rules we care about.
# It's even quicker (saves ~200ms) to pass -r on the command line.
MAKEFLAGS=-r
# The source directory tree.
srcdir := ..
abs_srcdir := $(abspath $(srcdir))
# The name of the builddir.
builddir_name ?= .
# The V=1 flag on command line makes us verbosely print command lines.
ifdef V
quiet=
else
quiet=quiet_
endif
# Specify BUILDTYPE=Release on the command line for a release build.
BUILDTYPE ?= Release
# Directory all our build output goes into.
# Note that this must be two directories beneath src/ for unit tests to pass,
# as they reach into the src/ directory for data with relative paths.
builddir ?= $(builddir_name)/$(BUILDTYPE)
abs_builddir := $(abspath $(builddir))
depsdir := $(builddir)/.deps
# Object output directory.
obj := $(builddir)/obj
abs_obj := $(abspath $(obj))
# We build up a list of every single one of the targets so we can slurp in the
# generated dependency rule Makefiles in one pass.
all_deps :=
CC.target ?= $(CC)
CFLAGS.target ?= $(CPPFLAGS) $(CFLAGS)
CXX.target ?= $(CXX)
CXXFLAGS.target ?= $(CPPFLAGS) $(CXXFLAGS)
LINK.target ?= $(LINK)
LDFLAGS.target ?= $(LDFLAGS)
AR.target ?= $(AR)
# C++ apps need to be linked with g++.
LINK ?= $(CXX.target)
# TODO(evan): move all cross-compilation logic to gyp-time so we don't need
# to replicate this environment fallback in make as well.
CC.host ?= gcc
CFLAGS.host ?= $(CPPFLAGS_host) $(CFLAGS_host)
CXX.host ?= g++
CXXFLAGS.host ?= $(CPPFLAGS_host) $(CXXFLAGS_host)
LINK.host ?= $(CXX.host)
LDFLAGS.host ?=
AR.host ?= ar
# Define a dir function that can handle spaces.
# http://www.gnu.org/software/make/manual/make.html#Syntax-of-Functions
# "leading spaces cannot appear in the text of the first argument as written.
# These characters can be put into the argument value by variable substitution."
empty :=
space := $(empty) $(empty)
# http://stackoverflow.com/questions/1189781/using-make-dir-or-notdir-on-a-path-with-spaces
replace_spaces = $(subst $(space),?,$1)
unreplace_spaces = $(subst ?,$(space),$1)
dirx = $(call unreplace_spaces,$(dir $(call replace_spaces,$1)))
# Flags to make gcc output dependency info. Note that you need to be
# careful here to use the flags that ccache and distcc can understand.
# We write to a dep file on the side first and then rename at the end
# so we can't end up with a broken dep file.
depfile = $(depsdir)/$(call replace_spaces,$@).d
DEPFLAGS = -MMD -MF $(depfile).raw
# We have to fixup the deps output in a few ways.
# (1) the file output should mention the proper .o file.
# ccache or distcc lose the path to the target, so we convert a rule of
# the form:
# foobar.o: DEP1 DEP2
# into
# path/to/foobar.o: DEP1 DEP2
# (2) we want missing files not to cause us to fail to build.
# We want to rewrite
# foobar.o: DEP1 DEP2 \
# DEP3
# to
# DEP1:
# DEP2:
# DEP3:
# so if the files are missing, they're just considered phony rules.
# We have to do some pretty insane escaping to get those backslashes
# and dollar signs past make, the shell, and sed at the same time.
# Doesn't work with spaces, but that's fine: .d files have spaces in
# their names replaced with other characters.
define fixup_dep
# The depfile may not exist if the input file didn't have any #includes.
touch $(depfile).raw
# Fixup path as in (1).
sed -e "s|^$(notdir $@)|$@|" $(depfile).raw >> $(depfile)
# Add extra rules as in (2).
# We remove slashes and replace spaces with new lines;
# remove blank lines;
# delete the first line and append a colon to the remaining lines.
sed -e 's|\\||' -e 'y| |\n|' $(depfile).raw |\
grep -v '^$$' |\
sed -e 1d -e 's|$$|:|' \
>> $(depfile)
rm $(depfile).raw
endef
# Command definitions:
# - cmd_foo is the actual command to run;
# - quiet_cmd_foo is the brief-output summary of the command.
quiet_cmd_cc = CC($(TOOLSET)) $@
cmd_cc = $(CC.$(TOOLSET)) $(GYP_CFLAGS) $(DEPFLAGS) $(CFLAGS.$(TOOLSET)) -c -o $@ $<
quiet_cmd_cxx = CXX($(TOOLSET)) $@
cmd_cxx = $(CXX.$(TOOLSET)) $(GYP_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $<
quiet_cmd_touch = TOUCH $@
cmd_touch = touch $@
quiet_cmd_copy = COPY $@
# send stderr to /dev/null to ignore messages when linking directories.
cmd_copy = rm -rf "$@" && cp -af "$<" "$@"
quiet_cmd_alink = AR($(TOOLSET)) $@
cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^)
quiet_cmd_alink_thin = AR($(TOOLSET)) $@
cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) crsT $@ $(filter %.o,$^)
# Due to circular dependencies between libraries :(, we wrap the
# special "figure out circular dependencies" flags around the entire
# input list during linking.
quiet_cmd_link = LINK($(TOOLSET)) $@
cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ -Wl,--start-group $(LD_INPUTS) $(LIBS) -Wl,--end-group
# We support two kinds of shared objects (.so):
# 1) shared_library, which is just bundling together many dependent libraries
# into a link line.
# 2) loadable_module, which is generating a module intended for dlopen().
#
# They differ only slightly:
# In the former case, we want to package all dependent code into the .so.
# In the latter case, we want to package just the API exposed by the
# outermost module.
# This means shared_library uses --whole-archive, while loadable_module doesn't.
# (Note that --whole-archive is incompatible with the --start-group used in
# normal linking.)
# Other shared-object link notes:
# - Set SONAME to the library filename so our binaries don't reference
# the local, absolute paths used on the link command-line.
quiet_cmd_solink = SOLINK($(TOOLSET)) $@
cmd_solink = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -o $@ -Wl,--whole-archive $(LD_INPUTS) -Wl,--no-whole-archive $(LIBS)
quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
cmd_solink_module = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -o $@ -Wl,--start-group $(filter-out FORCE_DO_CMD, $^) -Wl,--end-group $(LIBS)
# Define an escape_quotes function to escape single quotes.
# This allows us to handle quotes properly as long as we always use
# use single quotes and escape_quotes.
escape_quotes = $(subst ','\'',$(1))
# This comment is here just to include a ' to unconfuse syntax highlighting.
# Define an escape_vars function to escape '$' variable syntax.
# This allows us to read/write command lines with shell variables (e.g.
# $LD_LIBRARY_PATH), without triggering make substitution.
escape_vars = $(subst $$,$$$$,$(1))
# Helper that expands to a shell command to echo a string exactly as it is in
# make. This uses printf instead of echo because printf's behaviour with respect
# to escape sequences is more portable than echo's across different shells
# (e.g., dash, bash).
exact_echo = printf '%s\n' '$(call escape_quotes,$(1))'
# Helper to compare the command we're about to run against the command
# we logged the last time we ran the command. Produces an empty
# string (false) when the commands match.
# Tricky point: Make has no string-equality test function.
# The kernel uses the following, but it seems like it would have false
# positives, where one string reordered its arguments.
# arg_check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
# $(filter-out $(cmd_$@), $(cmd_$(1))))
# We instead substitute each for the empty string into the other, and
# say they're equal if both substitutions produce the empty string.
# .d files contain ? instead of spaces, take that into account.
command_changed = $(or $(subst $(cmd_$(1)),,$(cmd_$(call replace_spaces,$@))),\
$(subst $(cmd_$(call replace_spaces,$@)),,$(cmd_$(1))))
# Helper that is non-empty when a prerequisite changes.
# Normally make does this implicitly, but we force rules to always run
# so we can check their command lines.
# $? -- new prerequisites
# $| -- order-only dependencies
prereq_changed = $(filter-out FORCE_DO_CMD,$(filter-out $|,$?))
# Helper that executes all postbuilds until one fails.
define do_postbuilds
@E=0;\
for p in $(POSTBUILDS); do\
eval $$p;\
E=$$?;\
if [ $$E -ne 0 ]; then\
break;\
fi;\
done;\
if [ $$E -ne 0 ]; then\
rm -rf "$@";\
exit $$E;\
fi
endef
# do_cmd: run a command via the above cmd_foo names, if necessary.
# Should always run for a given target to handle command-line changes.
# Second argument, if non-zero, makes it do asm/C/C++ dependency munging.
# Third argument, if non-zero, makes it do POSTBUILDS processing.
# Note: We intentionally do NOT call dirx for depfile, since it contains ? for
# spaces already and dirx strips the ? characters.
define do_cmd
$(if $(or $(command_changed),$(prereq_changed)),
@$(call exact_echo, $($(quiet)cmd_$(1)))
@mkdir -p "$(call dirx,$@)" "$(dir $(depfile))"
$(if $(findstring flock,$(word 1,$(cmd_$1))),
@$(cmd_$(1))
@echo " $(quiet_cmd_$(1)): Finished",
@$(cmd_$(1))
)
@$(call exact_echo,$(call escape_vars,cmd_$(call replace_spaces,$@) := $(cmd_$(1)))) > $(depfile)
@$(if $(2),$(fixup_dep))
$(if $(and $(3), $(POSTBUILDS)),
$(call do_postbuilds)
)
)
endef
# Declare the "all" target first so it is the default,
# even though we don't have the deps yet.
.PHONY: all
all:
# make looks for ways to re-generate included makefiles, but in our case, we
# don't have a direct way. Explicitly telling make that it has nothing to do
# for them makes it go faster.
%.d: ;
# Use FORCE_DO_CMD to force a target to run. Should be coupled with
# do_cmd.
.PHONY: FORCE_DO_CMD
FORCE_DO_CMD:
TOOLSET := target
# Suffix rules, putting all outputs into $(obj).
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.c FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cpp FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cxx FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.S FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.s FORCE_DO_CMD
@$(call do_cmd,cc,1)
# Try building from generated source, too.
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.c FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cpp FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cxx FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.S FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.s FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.c FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.cpp FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.cxx FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.S FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.s FORCE_DO_CMD
@$(call do_cmd,cc,1)
ifeq ($(strip $(foreach prefix,$(NO_LOAD),\
$(findstring $(join ^,$(prefix)),\
$(join ^,fibers.target.mk)))),)
include fibers.target.mk
endif
quiet_cmd_regen_makefile = ACTION Regenerating $@
cmd_regen_makefile = cd $(srcdir); /root/.nvm/versions/node/v13.7.0/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "--toplevel-dir=." -I/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series/node_modules/fibers/build/config.gypi -I/root/.nvm/versions/node/v13.7.0/lib/node_modules/npm/node_modules/node-gyp/addon.gypi -I/root/.cache/node-gyp/13.7.0/include/node/common.gypi "--depth=." "-Goutput_dir=." "--generator-output=build" "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/root/.cache/node-gyp/13.7.0" "-Dnode_gyp_dir=/root/.nvm/versions/node/v13.7.0/lib/node_modules/npm/node_modules/node-gyp" "-Dnode_lib_file=/root/.cache/node-gyp/13.7.0/<(target_arch)/node.lib" "-Dmodule_root_dir=/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series/node_modules/fibers" "-Dnode_engine=v8" binding.gyp
Makefile: $(srcdir)/../../../../../../../root/.nvm/versions/node/v13.7.0/lib/node_modules/npm/node_modules/node-gyp/addon.gypi $(srcdir)/../../../../../../../root/.cache/node-gyp/13.7.0/include/node/common.gypi $(srcdir)/build/config.gypi $(srcdir)/binding.gyp
$(call do_cmd,regen_makefile)
# "all" is a concatenation of the "all" targets from all the included
# sub-makefiles. This is just here to clarify.
all:
# Add in dependency-tracking rules. $(all_deps) is the list of every single
# target in our tree. Only consider the ones with .d (dependency) info:
d_files := $(wildcard $(foreach f,$(all_deps),$(depsdir)/$(f).d))
ifneq ($(d_files),)
include $(d_files)
endif
+1
View File
@@ -0,0 +1 @@
cmd_Release/fibers.node := rm -rf "Release/fibers.node" && cp -af "Release/obj.target/fibers.node" "Release/fibers.node"
@@ -0,0 +1 @@
cmd_Release/obj.target/fibers.node := g++ -shared -pthread -rdynamic -m64 -pthread -Wl,-soname=fibers.node -o Release/obj.target/fibers.node -Wl,--start-group Release/obj.target/fibers/src/fibers.o Release/obj.target/fibers/src/coroutine.o Release/obj.target/fibers/src/libcoro/coro.o -Wl,--end-group
@@ -0,0 +1,21 @@
cmd_Release/obj.target/fibers/src/coroutine.o := g++ '-DNODE_GYP_MODULE_NAME=fibers' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-D__STDC_FORMAT_MACROS' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DUSE_CORO' '-DCORO_GUARDPAGES=1' '-DCORO_UCONTEXT' '-DBUILDING_NODE_EXTENSION' -I/root/.cache/node-gyp/13.7.0/include/node -I/root/.cache/node-gyp/13.7.0/src -I/root/.cache/node-gyp/13.7.0/deps/openssl/config -I/root/.cache/node-gyp/13.7.0/deps/openssl/openssl/include -I/root/.cache/node-gyp/13.7.0/deps/uv/include -I/root/.cache/node-gyp/13.7.0/deps/zlib -I/root/.cache/node-gyp/13.7.0/deps/v8/include -fPIC -pthread -Wall -Wextra -Wno-unused-parameter -m64 -O3 -O3 -fno-omit-frame-pointer -fno-rtti -fno-exceptions -std=gnu++1y -MMD -MF ./Release/.deps/Release/obj.target/fibers/src/coroutine.o.d.raw -c -o Release/obj.target/fibers/src/coroutine.o ../src/coroutine.cc
Release/obj.target/fibers/src/coroutine.o: ../src/coroutine.cc \
../src/coroutine.h /root/.cache/node-gyp/13.7.0/include/node/node.h \
/root/.cache/node-gyp/13.7.0/include/node/v8.h \
/root/.cache/node-gyp/13.7.0/include/node/v8-internal.h \
/root/.cache/node-gyp/13.7.0/include/node/v8-version.h \
/root/.cache/node-gyp/13.7.0/include/node/v8config.h \
/root/.cache/node-gyp/13.7.0/include/node/v8-platform.h \
/root/.cache/node-gyp/13.7.0/include/node/node_version.h \
../src/libcoro/coro.h ../src/v8-version.h
../src/coroutine.cc:
../src/coroutine.h:
/root/.cache/node-gyp/13.7.0/include/node/node.h:
/root/.cache/node-gyp/13.7.0/include/node/v8.h:
/root/.cache/node-gyp/13.7.0/include/node/v8-internal.h:
/root/.cache/node-gyp/13.7.0/include/node/v8-version.h:
/root/.cache/node-gyp/13.7.0/include/node/v8config.h:
/root/.cache/node-gyp/13.7.0/include/node/v8-platform.h:
/root/.cache/node-gyp/13.7.0/include/node/node_version.h:
../src/libcoro/coro.h:
../src/v8-version.h:
@@ -0,0 +1,23 @@
cmd_Release/obj.target/fibers/src/fibers.o := g++ '-DNODE_GYP_MODULE_NAME=fibers' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-D__STDC_FORMAT_MACROS' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DUSE_CORO' '-DCORO_GUARDPAGES=1' '-DCORO_UCONTEXT' '-DBUILDING_NODE_EXTENSION' -I/root/.cache/node-gyp/13.7.0/include/node -I/root/.cache/node-gyp/13.7.0/src -I/root/.cache/node-gyp/13.7.0/deps/openssl/config -I/root/.cache/node-gyp/13.7.0/deps/openssl/openssl/include -I/root/.cache/node-gyp/13.7.0/deps/uv/include -I/root/.cache/node-gyp/13.7.0/deps/zlib -I/root/.cache/node-gyp/13.7.0/deps/v8/include -fPIC -pthread -Wall -Wextra -Wno-unused-parameter -m64 -O3 -O3 -fno-omit-frame-pointer -fno-rtti -fno-exceptions -std=gnu++1y -MMD -MF ./Release/.deps/Release/obj.target/fibers/src/fibers.o.d.raw -c -o Release/obj.target/fibers/src/fibers.o ../src/fibers.cc
Release/obj.target/fibers/src/fibers.o: ../src/fibers.cc \
../src/coroutine.h /root/.cache/node-gyp/13.7.0/include/node/node.h \
/root/.cache/node-gyp/13.7.0/include/node/v8.h \
/root/.cache/node-gyp/13.7.0/include/node/v8-internal.h \
/root/.cache/node-gyp/13.7.0/include/node/v8-version.h \
/root/.cache/node-gyp/13.7.0/include/node/v8config.h \
/root/.cache/node-gyp/13.7.0/include/node/v8-platform.h \
/root/.cache/node-gyp/13.7.0/include/node/node_version.h \
../src/libcoro/coro.h ../src/v8-version.h \
/root/.cache/node-gyp/13.7.0/include/node/node_version.h
../src/fibers.cc:
../src/coroutine.h:
/root/.cache/node-gyp/13.7.0/include/node/node.h:
/root/.cache/node-gyp/13.7.0/include/node/v8.h:
/root/.cache/node-gyp/13.7.0/include/node/v8-internal.h:
/root/.cache/node-gyp/13.7.0/include/node/v8-version.h:
/root/.cache/node-gyp/13.7.0/include/node/v8config.h:
/root/.cache/node-gyp/13.7.0/include/node/v8-platform.h:
/root/.cache/node-gyp/13.7.0/include/node/node_version.h:
../src/libcoro/coro.h:
../src/v8-version.h:
/root/.cache/node-gyp/13.7.0/include/node/node_version.h:
@@ -0,0 +1,5 @@
cmd_Release/obj.target/fibers/src/libcoro/coro.o := cc '-DNODE_GYP_MODULE_NAME=fibers' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-D__STDC_FORMAT_MACROS' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DUSE_CORO' '-DCORO_GUARDPAGES=1' '-DCORO_UCONTEXT' '-DBUILDING_NODE_EXTENSION' -I/root/.cache/node-gyp/13.7.0/include/node -I/root/.cache/node-gyp/13.7.0/src -I/root/.cache/node-gyp/13.7.0/deps/openssl/config -I/root/.cache/node-gyp/13.7.0/deps/openssl/openssl/include -I/root/.cache/node-gyp/13.7.0/deps/uv/include -I/root/.cache/node-gyp/13.7.0/deps/zlib -I/root/.cache/node-gyp/13.7.0/deps/v8/include -fPIC -pthread -Wall -Wextra -Wno-unused-parameter -m64 -O3 -O3 -fno-omit-frame-pointer -std=gnu11 -MMD -MF ./Release/.deps/Release/obj.target/fibers/src/libcoro/coro.o.d.raw -c -o Release/obj.target/fibers/src/libcoro/coro.o ../src/libcoro/coro.c
Release/obj.target/fibers/src/libcoro/coro.o: ../src/libcoro/coro.c \
../src/libcoro/coro.h
../src/libcoro/coro.c:
../src/libcoro/coro.h:
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
# This file is generated by gyp; do not edit.
export builddir_name ?= ./build/.
.PHONY: all
all:
$(MAKE) fibers
+197
View File
@@ -0,0 +1,197 @@
# Do not edit. File was generated by node-gyp's "configure" step
{
"target_defaults": {
"cflags": [],
"default_configuration": "Release",
"defines": [],
"include_dirs": [],
"libraries": []
},
"variables": {
"asan": 0,
"build_v8_with_gn": "false",
"coverage": "false",
"debug_nghttp2": "false",
"enable_lto": "false",
"enable_pgo_generate": "false",
"enable_pgo_use": "false",
"force_dynamic_crt": 0,
"gas_version": "2.27",
"host_arch": "x64",
"icu_data_in": "../../deps/icu-tmp/icudt65l.dat",
"icu_endianness": "l",
"icu_gyp_path": "tools/icu/icu-generic.gyp",
"icu_path": "deps/icu-small",
"icu_small": "false",
"icu_ver_major": "65",
"is_debug": 0,
"llvm_version": "0.0",
"napi_build_version": "5",
"node_byteorder": "little",
"node_debug_lib": "false",
"node_enable_d8": "false",
"node_install_npm": "true",
"node_module_version": 79,
"node_no_browser_globals": "false",
"node_prefix": "/",
"node_release_urlbase": "https://nodejs.org/download/release/",
"node_report": "true",
"node_shared": "false",
"node_shared_cares": "false",
"node_shared_http_parser": "false",
"node_shared_libuv": "false",
"node_shared_nghttp2": "false",
"node_shared_openssl": "false",
"node_shared_zlib": "false",
"node_tag": "",
"node_target_type": "executable",
"node_use_bundled_v8": "true",
"node_use_dtrace": "false",
"node_use_etw": "false",
"node_use_node_code_cache": "true",
"node_use_node_snapshot": "true",
"node_use_openssl": "true",
"node_use_v8_platform": "true",
"node_with_ltcg": "false",
"node_without_node_options": "false",
"openssl_fips": "",
"openssl_is_fips": "false",
"shlib_suffix": "so.79",
"target_arch": "x64",
"v8_enable_31bit_smis_on_64bit_arch": 0,
"v8_enable_gdbjit": 0,
"v8_enable_i18n_support": 1,
"v8_enable_inspector": 1,
"v8_enable_pointer_compression": 0,
"v8_no_strict_aliasing": 1,
"v8_optimized_debug": 1,
"v8_promise_internal_field_count": 1,
"v8_random_seed": 0,
"v8_trace_maps": 0,
"v8_use_siphash": 1,
"want_separate_host_toolset": 0,
"nodedir": "/root/.cache/node-gyp/13.7.0",
"standalone_static_library": 1,
"dry_run": "",
"legacy_bundling": "",
"save_dev": "",
"browser": "",
"commit_hooks": "true",
"only": "",
"viewer": "man",
"also": "",
"rollback": "true",
"sign_git_commit": "",
"audit": "true",
"usage": "",
"globalignorefile": "/root/.nvm/versions/node/v13.7.0/etc/npmignore",
"init_author_url": "",
"maxsockets": "50",
"shell": "/bin/bash",
"metrics_registry": "https://registry.npmjs.org/",
"parseable": "",
"shrinkwrap": "true",
"init_license": "ISC",
"timing": "",
"if_present": "",
"cache_max": "Infinity",
"init_author_email": "",
"sign_git_tag": "",
"cert": "",
"git_tag_version": "true",
"local_address": "",
"long": "",
"preid": "",
"fetch_retries": "2",
"registry": "https://registry.npmjs.org/",
"key": "",
"message": "%s",
"versions": "",
"globalconfig": "/root/.nvm/versions/node/v13.7.0/etc/npmrc",
"always_auth": "",
"logs_max": "10",
"prefer_online": "",
"cache_lock_retries": "10",
"global_style": "",
"update_notifier": "true",
"audit_level": "low",
"heading": "npm",
"fetch_retry_mintimeout": "10000",
"offline": "",
"read_only": "",
"searchlimit": "20",
"access": "",
"json": "",
"allow_same_version": "",
"description": "true",
"engine_strict": "",
"https_proxy": "",
"init_module": "/root/.npm-init.js",
"userconfig": "/root/.npmrc",
"cidr": "",
"node_version": "13.7.0",
"user": "",
"auth_type": "legacy",
"editor": "vi",
"ignore_prepublish": "",
"save": "true",
"script_shell": "",
"tag": "latest",
"before": "",
"global": "",
"progress": "true",
"ham_it_up": "",
"optional": "true",
"searchstaleness": "900",
"bin_links": "true",
"force": "",
"save_prod": "",
"searchopts": "",
"depth": "Infinity",
"node_gyp": "/root/.nvm/versions/node/v13.7.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js",
"rebuild_bundle": "true",
"sso_poll_frequency": "500",
"unicode": "true",
"fetch_retry_maxtimeout": "60000",
"ca": "",
"save_prefix": "^",
"scripts_prepend_node_path": "warn-only",
"sso_type": "oauth",
"strict_ssl": "true",
"tag_version_prefix": "v",
"dev": "",
"fetch_retry_factor": "10",
"group": "",
"save_exact": "",
"cache_lock_stale": "60000",
"prefer_offline": "",
"version": "",
"cache_min": "10",
"otp": "",
"cache": "/root/.npm",
"searchexclude": "",
"color": "true",
"package_lock": "true",
"fund": "true",
"package_lock_only": "",
"save_optional": "",
"user_agent": "npm/6.14.4 node/v13.7.0 linux x64",
"ignore_scripts": "",
"cache_lock_wait": "10000",
"production": "",
"save_bundle": "",
"send_metrics": "",
"init_version": "1.0.0",
"node_options": "",
"umask": "0022",
"scope": "",
"git": "git",
"init_author_name": "",
"onload_script": "",
"tmp": "/tmp",
"unsafe_perm": "",
"format_package_lock": "true",
"link": "",
"prefix": "/root/.nvm/versions/node/v13.7.0"
}
}
+179
View File
@@ -0,0 +1,179 @@
# This file is generated by gyp; do not edit.
TOOLSET := target
TARGET := fibers
DEFS_Debug := \
'-DNODE_GYP_MODULE_NAME=fibers' \
'-DUSING_UV_SHARED=1' \
'-DUSING_V8_SHARED=1' \
'-DV8_DEPRECATION_WARNINGS=1' \
'-DV8_DEPRECATION_WARNINGS' \
'-DV8_IMMINENT_DEPRECATION_WARNINGS' \
'-D_LARGEFILE_SOURCE' \
'-D_FILE_OFFSET_BITS=64' \
'-D__STDC_FORMAT_MACROS' \
'-DOPENSSL_NO_PINSHARED' \
'-DOPENSSL_THREADS' \
'-DUSE_CORO' \
'-DCORO_GUARDPAGES=1' \
'-DCORO_UCONTEXT' \
'-DBUILDING_NODE_EXTENSION' \
'-DDEBUG' \
'-D_DEBUG' \
'-DV8_ENABLE_CHECKS'
# Flags passed to all source files.
CFLAGS_Debug := \
-fPIC \
-pthread \
-Wall \
-Wextra \
-Wno-unused-parameter \
-m64 \
-g \
-O0
# Flags passed to only C files.
CFLAGS_C_Debug := \
-std=gnu11
# Flags passed to only C++ files.
CFLAGS_CC_Debug := \
-fno-rtti \
-fno-exceptions \
-std=gnu++1y
INCS_Debug := \
-I/root/.cache/node-gyp/13.7.0/include/node \
-I/root/.cache/node-gyp/13.7.0/src \
-I/root/.cache/node-gyp/13.7.0/deps/openssl/config \
-I/root/.cache/node-gyp/13.7.0/deps/openssl/openssl/include \
-I/root/.cache/node-gyp/13.7.0/deps/uv/include \
-I/root/.cache/node-gyp/13.7.0/deps/zlib \
-I/root/.cache/node-gyp/13.7.0/deps/v8/include
DEFS_Release := \
'-DNODE_GYP_MODULE_NAME=fibers' \
'-DUSING_UV_SHARED=1' \
'-DUSING_V8_SHARED=1' \
'-DV8_DEPRECATION_WARNINGS=1' \
'-DV8_DEPRECATION_WARNINGS' \
'-DV8_IMMINENT_DEPRECATION_WARNINGS' \
'-D_LARGEFILE_SOURCE' \
'-D_FILE_OFFSET_BITS=64' \
'-D__STDC_FORMAT_MACROS' \
'-DOPENSSL_NO_PINSHARED' \
'-DOPENSSL_THREADS' \
'-DUSE_CORO' \
'-DCORO_GUARDPAGES=1' \
'-DCORO_UCONTEXT' \
'-DBUILDING_NODE_EXTENSION'
# Flags passed to all source files.
CFLAGS_Release := \
-fPIC \
-pthread \
-Wall \
-Wextra \
-Wno-unused-parameter \
-m64 \
-O3 \
-O3 \
-fno-omit-frame-pointer
# Flags passed to only C files.
CFLAGS_C_Release := \
-std=gnu11
# Flags passed to only C++ files.
CFLAGS_CC_Release := \
-fno-rtti \
-fno-exceptions \
-std=gnu++1y
INCS_Release := \
-I/root/.cache/node-gyp/13.7.0/include/node \
-I/root/.cache/node-gyp/13.7.0/src \
-I/root/.cache/node-gyp/13.7.0/deps/openssl/config \
-I/root/.cache/node-gyp/13.7.0/deps/openssl/openssl/include \
-I/root/.cache/node-gyp/13.7.0/deps/uv/include \
-I/root/.cache/node-gyp/13.7.0/deps/zlib \
-I/root/.cache/node-gyp/13.7.0/deps/v8/include
OBJS := \
$(obj).target/$(TARGET)/src/fibers.o \
$(obj).target/$(TARGET)/src/coroutine.o \
$(obj).target/$(TARGET)/src/libcoro/coro.o
# Add to the list of files we specially track dependencies for.
all_deps += $(OBJS)
# CFLAGS et al overrides must be target-local.
# See "Target-specific Variable Values" in the GNU Make manual.
$(OBJS): TOOLSET := $(TOOLSET)
$(OBJS): GYP_CFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE))
$(OBJS): GYP_CXXFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE))
# Suffix rules, putting all outputs into $(obj).
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.c FORCE_DO_CMD
@$(call do_cmd,cc,1)
# Try building from generated source, too.
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.c FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.c FORCE_DO_CMD
@$(call do_cmd,cc,1)
# End of this set of suffix rules
### Rules for final target.
LDFLAGS_Debug := \
-pthread \
-rdynamic \
-m64 \
-pthread
LDFLAGS_Release := \
-pthread \
-rdynamic \
-m64 \
-pthread
LIBS :=
$(obj).target/fibers.node: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
$(obj).target/fibers.node: LIBS := $(LIBS)
$(obj).target/fibers.node: TOOLSET := $(TOOLSET)
$(obj).target/fibers.node: $(OBJS) FORCE_DO_CMD
$(call do_cmd,solink_module)
all_deps += $(obj).target/fibers.node
# Add target alias
.PHONY: fibers
fibers: $(builddir)/fibers.node
# Copy this to the executable output path.
$(builddir)/fibers.node: TOOLSET := $(TOOLSET)
$(builddir)/fibers.node: $(obj).target/fibers.node FORCE_DO_CMD
$(call do_cmd,copy)
all_deps += $(builddir)/fibers.node
# Short alias for building this executable.
.PHONY: fibers.node
fibers.node: $(obj).target/fibers.node $(builddir)/fibers.node
# Add executable to "all" target.
.PHONY: all
all: $(builddir)/fibers.node
+105
View File
@@ -0,0 +1,105 @@
if (process.fiberLib) {
module.exports = process.fiberLib;
} else {
var fs = require('fs'), path = require('path'), detectLibc = require('detect-libc');
// Seed random numbers [gh-82]
Math.random();
// Look for binary for this platform
var modPath = path.join(__dirname, 'bin', process.platform+ '-'+ process.arch+ '-'+ process.versions.modules+
((process.platform === 'linux') ? '-'+ detectLibc.family : ''), 'fibers');
try {
// Pull in fibers implementation
process.fiberLib = module.exports = require(modPath).Fiber;
} catch (ex) {
// No binary!
console.error(
'## There is an issue with `node-fibers` ##\n'+
'`'+ modPath+ '.node` is missing.\n\n'+
'Try running this to fix the issue: '+ process.execPath+ ' '+ __dirname.replace(' ', '\\ ')+ '/build'
);
console.error(ex.stack || ex.message || ex);
throw new Error('Missing binary. See message above.');
}
setupAsyncHacks(module.exports);
}
function setupAsyncHacks(Fiber) {
// Older (or newer?) versions of node may not support this API
try {
var aw = process.binding('async_wrap');
var getAsyncIdStackSize;
if (aw.asyncIdStackSize instanceof Function) {
getAsyncIdStackSize = aw.asyncIdStackSize;
} else if (aw.constants.kStackLength !== undefined) {
getAsyncIdStackSize = function(kStackLength) {
return function() {
return aw.async_hook_fields[kStackLength];
};
}(aw.constants.kStackLength);
} else {
throw new Error('Couldn\'t figure out how to get async stack size');
}
if (!aw.popAsyncIds || !aw.pushAsyncIds) {
throw new Error('Push/pop do not exist');
}
var kExecutionAsyncId;
if (aw.constants.kExecutionAsyncId === undefined) {
kExecutionAsyncId = aw.constants.kCurrentAsyncId;
} else {
kExecutionAsyncId = aw.constants.kExecutionAsyncId;
}
var kTriggerAsyncId;
if (aw.constants.kTriggerAsyncId === undefined) {
kTriggerAsyncId = aw.constants.kCurrentTriggerId;
} else {
kTriggerAsyncId = aw.constants.kTriggerAsyncId;
}
var asyncIds = aw.async_id_fields || aw.async_uid_fields;
function getAndClearStack() {
var ii = getAsyncIdStackSize();
var stack = new Array(ii);
for (; ii > 0; --ii) {
var asyncId = asyncIds[kExecutionAsyncId];
stack[ii - 1] = {
asyncId: asyncId,
triggerId: asyncIds[kTriggerAsyncId],
};
aw.popAsyncIds(asyncId);
}
return stack;
}
function restoreStack(stack) {
for (var ii = 0; ii < stack.length; ++ii) {
aw.pushAsyncIds(stack[ii].asyncId, stack[ii].triggerId);
}
}
function wrapFunction(fn) {
return function() {
var stack = getAndClearStack();
try {
return fn.apply(this, arguments);
} finally {
restoreStack(stack);
}
}
}
// Monkey patch methods which may long jump
Fiber.yield = wrapFunction(Fiber.yield);
Fiber.prototype.run = wrapFunction(Fiber.prototype.run);
Fiber.prototype.throwInto = wrapFunction(Fiber.prototype.throwInto);
} catch (err) {
return;
}
}
+475
View File
@@ -0,0 +1,475 @@
"use strict";
var Fiber = require('./fibers');
var util = require('util');
module.exports = Future;
Function.prototype.future = function(detach) {
var fn = this;
var ret = function() {
var future = new FiberFuture(fn, this, arguments);
if (detach) {
future.detach();
}
return future;
};
ret.toString = function() {
return '<<Future '+ fn+ '.future()>>';
};
return ret;
};
function Future() {}
/**
* Run a function(s) in a future context, and return a future to their return value. This is useful
* for instances where you want a closure to be able to `.wait()`. This also lets you wait for
* mulitple parallel opertions to run.
*/
Future.task = function(fn) {
if (arguments.length === 1) {
return fn.future()();
} else {
var future = new Future, pending = arguments.length, error, values = new Array(arguments.length);
for (var ii = 0; ii < arguments.length; ++ii) {
arguments[ii].future()().resolve(function(ii, err, val) {
if (err) {
error = err;
}
values[ii] = val;
if (--pending === 0) {
if (error) {
future.throw(error);
} else {
future.return(values);
}
}
}.bind(null, ii));
}
return future;
}
};
/**
* Wrap node-style async functions to instead return futures. This assumes that the last parameter
* of the function is a callback.
*
* If a single function is passed a future-returning function is created. If an object is passed a
* new object is returned with all functions wrapped.
*
* The value that is returned from the invocation of the underlying function is assigned to the
* property `_` on the future. This is useful for functions like `execFile` which take a callback,
* but also return meaningful information.
*
* `multi` indicates that this callback will return more than 1 argument after `err`. For example,
* `child_process.exec()`
*
* `suffix` will append a string to every method that was overridden, if you pass an object to
* `Future.wrap()`. Default is 'Future'.
*
* var readFileFuture = Future.wrap(require('fs').readFile);
* var fs = Future.wrap(require('fs'));
* fs.readFileFuture('example.txt').wait();
*/
Future.wrap = function(fnOrObject, multi, suffix, stop) {
if (typeof fnOrObject === 'object') {
var wrapped = Object.create(fnOrObject);
for (var ii in fnOrObject) {
if (wrapped[ii] instanceof Function) {
wrapped[suffix === undefined ? ii+ 'Future' : ii+ suffix] = Future.wrap(wrapped[ii], multi, suffix, stop);
}
}
return wrapped;
} else if (typeof fnOrObject === 'function') {
var fn = function() {
var future = new Future;
var args = Array.prototype.slice.call(arguments);
if (multi) {
var cb = future.resolver();
args.push(function(err) {
cb(err, Array.prototype.slice.call(arguments, 1));
});
} else {
args.push(future.resolver());
}
future._ = fnOrObject.apply(this, args);
return future;
}
// Modules like `request` return a function that has more functions as properties. Handle this
// in some kind of reasonable way.
if (!stop) {
var proto = Object.create(fnOrObject);
for (var ii in fnOrObject) {
if (fnOrObject.hasOwnProperty(ii) && fnOrObject[ii] instanceof Function) {
proto[ii] = proto[ii];
}
}
fn.__proto__ = Future.wrap(proto, multi, suffix, true);
}
return fn;
}
};
/**
* Wait 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
* you want to wait on a single future you're better off calling future.wait() on the instance.
*/
Future.wait = function wait(/* ... */) {
// Normalize arguments + pull out a FiberFuture for reuse if possible
var futures = [], singleFiberFuture;
for (var ii = 0; ii < arguments.length; ++ii) {
var arg = arguments[ii];
if (arg instanceof Future) {
// Ignore already resolved fibers
if (arg.isResolved()) {
continue;
}
// Look for fiber reuse
if (!singleFiberFuture && arg instanceof FiberFuture && !arg.started) {
singleFiberFuture = arg;
continue;
}
futures.push(arg);
} else if (arg instanceof Array) {
for (var jj = 0; jj < arg.length; ++jj) {
var aarg = arg[jj];
if (aarg instanceof Future) {
// Ignore already resolved fibers
if (aarg.isResolved()) {
continue;
}
// Look for fiber reuse
if (!singleFiberFuture && aarg instanceof FiberFuture && !aarg.started) {
singleFiberFuture = aarg;
continue;
}
futures.push(aarg);
} else {
throw new Error(aarg+ ' is not a future');
}
}
} else {
throw new Error(arg+ ' is not a future');
}
}
// Resumes current fiber
var fiber = Fiber.current;
if (!fiber) {
throw new Error('Can\'t wait without a fiber');
}
// Resolve all futures
var pending = futures.length + (singleFiberFuture ? 1 : 0);
function cb() {
if (!--pending) {
fiber.run();
}
}
for (var ii = 0; ii < futures.length; ++ii) {
futures[ii].resolve(cb);
}
// Reusing a fiber?
if (singleFiberFuture) {
singleFiberFuture.started = true;
try {
singleFiberFuture.return(
singleFiberFuture.fn.apply(singleFiberFuture.context, singleFiberFuture.args));
} catch(e) {
singleFiberFuture.throw(e);
}
--pending;
}
// Yield this fiber
if (pending) {
Fiber.yield();
}
};
/**
* Return a Future that waits on an ES6 Promise.
*/
Future.fromPromise = function(promise) {
var future = new Future;
promise.then(function(val) {
future.return(val);
}, function(err) {
future.throw(err);
});
return future;
};
Future.prototype = {
/**
* Return the value of this future. If the future hasn't resolved yet this will throw an error.
*/
get: function() {
if (!this.resolved) {
throw new Error('Future must resolve before value is ready');
} else if (this.error) {
// Link the stack traces up
var error = this.error;
var localStack = {};
Error.captureStackTrace(localStack, Future.prototype.get);
var futureStack = Object.getOwnPropertyDescriptor(error, 'futureStack');
if (!futureStack) {
futureStack = Object.getOwnPropertyDescriptor(error, 'stack');
if (futureStack) {
Object.defineProperty(error, 'futureStack', futureStack);
}
}
if (futureStack && futureStack.get) {
Object.defineProperty(error, 'stack', {
get: function() {
var stack = futureStack.get.apply(error);
if (stack) {
stack = stack.split('\n');
return [stack[0]]
.concat(localStack.stack.split('\n').slice(1))
.concat(' - - - - -')
.concat(stack.slice(1))
.join('\n');
} else {
return localStack.stack;
}
},
set: function(stack) {
Object.defineProperty(error, 'stack', {
value: stack,
configurable: true,
enumerable: false,
writable: true,
});
},
configurable: true,
enumerable: false,
});
}
throw error;
} else {
return this.value;
}
},
/**
* Mark this future as returned. All pending callbacks will be invoked immediately.
*/
"return": function(value) {
if (this.resolved) {
throw new Error('Future resolved more than once');
}
this.value = value;
this.resolved = true;
var callbacks = this.callbacks;
if (callbacks) {
delete this.callbacks;
for (var ii = 0; ii < callbacks.length; ++ii) {
try {
var ref = callbacks[ii];
if (ref[1]) {
ref[1](value);
} else {
ref[0](undefined, value);
}
} catch(ex) {
// console.log('Resolve cb threw', String(ex.stack || ex.message || ex));
process.nextTick(function() {
throw(ex);
});
}
}
}
},
/**
* Throw from this future as returned. All pending callbacks will be invoked immediately.
*/
"throw": function(error) {
if (this.resolved) {
throw new Error('Future resolved more than once');
} else if (!error) {
throw new Error('Must throw non-empty error');
}
this.error = error;
this.resolved = true;
var callbacks = this.callbacks;
if (callbacks) {
delete this.callbacks;
for (var ii = 0; ii < callbacks.length; ++ii) {
try {
var ref = callbacks[ii];
if (ref[1]) {
ref[0].throw(error);
} else {
ref[0](error);
}
} catch(ex) {
// console.log('Resolve cb threw', String(ex.stack || ex.message || ex));
process.nextTick(function() {
throw(ex);
});
}
}
}
},
/**
* "detach" this future. Basically this is useful if you want to run a task in a future, you
* aren't interested in its return value, but if it throws you don't want the exception to be
* lost. If this fiber throws, an exception will be thrown to the event loop and node will
* probably fall down.
*/
detach: function() {
this.resolve(function(err) {
if (err) {
throw err;
}
});
},
/**
* Returns whether or not this future has resolved yet.
*/
isResolved: function() {
return this.resolved === true;
},
/**
* Returns a node-style function which will mark this future as resolved when called.
*/
resolver: function() {
return function(err, val) {
if (err) {
this.throw(err);
} else {
this.return(val);
}
}.bind(this);
},
/**
* Waits for this future to resolve and then invokes a callback.
*
* If two arguments are passed, the first argument is a future which will be thrown to in the case
* of error, and the second is a function(val){} callback.
*
* If only one argument is passed it is a standard function(err, val){} callback.
*/
resolve: function(arg1, arg2) {
if (this.resolved) {
if (arg2) {
if (this.error) {
arg1.throw(this.error);
} else {
arg2(this.value);
}
} else {
arg1(this.error, this.value);
}
} else {
(this.callbacks = this.callbacks || []).push([arg1, arg2]);
}
return this;
},
/**
* Resolve only in the case of success
*/
resolveSuccess: function(cb) {
this.resolve(function(err, val) {
if (err) {
return;
}
cb(val);
});
return this;
},
/**
* Propogate results to another future.
*/
proxy: function(future) {
this.resolve(function(err, val) {
if (err) {
future.throw(err);
} else {
future.return(val);
}
});
},
/**
* Propogate only errors to an another future or array of futures.
*/
proxyErrors: function(futures) {
this.resolve(function(err) {
if (!err) {
return;
}
if (futures instanceof Array) {
for (var ii = 0; ii < futures.length; ++ii) {
futures[ii].throw(err);
}
} else {
futures.throw(err);
}
});
return this;
},
/**
* Returns an ES6 Promise
*/
promise: function() {
var that = this;
return new Promise(function(resolve, reject) {
that.resolve(function(err, val) {
if (err) {
reject(err);
} else {
resolve(val);
}
});
});
},
/**
* Differs from its functional counterpart in that it actually resolves the future. Thus if the
* future threw, future.wait() will throw.
*/
wait: function() {
if (this.isResolved()) {
return this.get();
}
Future.wait(this);
return this.get();
},
};
/**
* A function call which loads inside a fiber automatically and returns a future.
*/
function FiberFuture(fn, context, args) {
this.fn = fn;
this.context = context;
this.args = args;
this.started = false;
var that = this;
process.nextTick(function() {
if (!that.started) {
that.started = true;
Fiber(function() {
try {
that.return(fn.apply(context, args));
} catch(e) {
that.throw(e);
}
}).run();
}
});
}
util.inherits(FiberFuture, Future);
+69
View File
@@ -0,0 +1,69 @@
{
"_args": [
[
"fibers@4.0.3",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "fibers@4.0.3",
"_id": "fibers@4.0.3",
"_inBundle": false,
"_integrity": "sha512-MW5VrDtTOLpKK7lzw4qD7Z9tXaAhdOmOED5RHzg3+HjUk+ibkjVW0Py2ERtdqgTXaerLkVkBy2AEmJiT6RMyzg==",
"_location": "/fibers",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "fibers@4.0.3",
"name": "fibers",
"escapedName": "fibers",
"rawSpec": "4.0.3",
"saveSpec": null,
"fetchSpec": "4.0.3"
},
"_requiredBy": [
"/@nuxtjs/vuetify"
],
"_resolved": "https://registry.npmjs.org/fibers/-/fibers-4.0.3.tgz",
"_spec": "4.0.3",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Marcel Laverdet",
"email": "marcel@laverdet.com",
"url": "https://github.com/laverdet/"
},
"bugs": {
"url": "https://github.com/laverdet/node-fibers/issues"
},
"dependencies": {
"detect-libc": "^1.0.3"
},
"description": "Cooperative multi-tasking for Javascript",
"engines": {
"node": ">=10.0.0"
},
"homepage": "https://github.com/laverdet/node-fibers",
"keywords": [
"fiber",
"fibers",
"coroutine",
"thread",
"async",
"parallel",
"worker",
"future",
"promise"
],
"license": "MIT",
"main": "fibers",
"name": "fibers",
"repository": {
"type": "git",
"url": "git://github.com/laverdet/node-fibers.git"
},
"scripts": {
"install": "node build.js || nodejs build.js",
"test": "node test.js || nodejs test.js"
},
"version": "4.0.3"
}
+7
View File
@@ -0,0 +1,7 @@
"use strict"
var Fiber = require('./fibers');
var fiber = Fiber(function() {
process.stdout.write(Fiber.yield());
});
fiber.run();
fiber.run('pass');
+327
View File
@@ -0,0 +1,327 @@
#include "coroutine.h"
#include "v8-version.h"
#include <assert.h>
#ifndef WINDOWS
#include <pthread.h>
#else
#include <windows.h>
#include <intrin.h>
// Stub pthreads into Windows approximations
#define pthread_t HANDLE
#define pthread_create(thread, attr, fn, arg) !((*thread)=CreateThread(NULL, 0, &(fn), arg, 0, NULL))
#define pthread_join(thread, arg) WaitForSingleObject((thread), INFINITE)
#define pthread_key_t DWORD
#define pthread_key_create(key, dtor) (*key)=TlsAlloc()
#define pthread_setspecific(key, val) TlsSetValue((key), (val))
#define pthread_getspecific(key) TlsGetValue((key))
#endif
#include <stdexcept>
#include <stack>
#include <vector>
using namespace std;
const size_t v8_tls_keys = 3;
static std::vector<void*> fls_data_pool;
static pthread_key_t coro_thread_key = 0;
static pthread_key_t isolate_key = 0x7777;
static pthread_key_t thread_id_key = 0x7777;
static pthread_key_t thread_data_key = 0x7777;
static size_t stack_size = 0;
static size_t coroutines_created_ = 0;
static vector<Coroutine*> fiber_pool;
static Coroutine* delete_me = NULL;
size_t Coroutine::pool_size = 120;
static bool can_poke(void* addr) {
#ifdef WINDOWS
MEMORY_BASIC_INFORMATION mbi;
if (!VirtualQueryEx(GetCurrentProcess(), addr, &mbi, sizeof(mbi))) {
return false;
}
if (!(mbi.State & MEM_COMMIT)) {
return false;
}
return true;
#else
// TODO?
return addr > (void*)0x1000;
#endif
}
#ifdef USE_V8_SYMBOLS
// ** This no longer works as of v8 7.3.262; `thread_id_key_` moved to a static variable inside a
// function in an anonymous namespace.
// Some distributions of node, most notably Ubuntu, strip the v8 internal symbols and so we don't
// have access to this stuff. In most cases we will use the more complicated `find_thread_id_key`
// below, since it tends to work on more platforms.
namespace v8 {
namespace base {
class Thread {
public: typedef int32_t LocalStorageKey;
};
}
namespace internal {
class Isolate {
public:
static base::Thread::LocalStorageKey isolate_key_;
static base::Thread::LocalStorageKey per_isolate_thread_data_key_;
static base::Thread::LocalStorageKey thread_id_key_;
};
}
}
#endif
#ifndef WINDOWS
static void* find_thread_id_key(void* arg)
#else
static DWORD __stdcall find_thread_id_key(LPVOID arg)
#endif
{
v8::Isolate* isolate = static_cast<v8::Isolate*>(arg);
assert(isolate != NULL);
v8::Locker locker(isolate);
isolate->Enter();
// First pass-- find isolate thread key
#ifdef __MUSL__
// 128 is default max key in musl
for (pthread_key_t ii = 1; ii < 128; ++ii) {
#else
for (pthread_key_t ii = coro_thread_key; ii > 0; --ii) {
#endif
void* tls = pthread_getspecific(ii - 1);
if (tls == isolate) {
isolate_key = ii - 1;
break;
}
}
assert(isolate_key != 0x7777);
// Second pass-- find data key
int thread_id = 0;
#ifdef __MUSL__
for (pthread_key_t ii = 0; ii < 128; ++ii) {
#else
for (pthread_key_t ii = isolate_key + 1; ii < coro_thread_key; ++ii) {
#endif
void* tls = pthread_getspecific(ii);
if (can_poke(tls) && *(void**)tls == isolate) {
// First member of per-thread data is the isolate
thread_data_key = ii;
// Second member is the thread id
thread_id = *(int*)((void**)tls + 1);
break;
}
}
assert(thread_data_key != 0x7777);
// Third pass-- find thread id key
#ifdef __MUSL__
for (pthread_key_t ii = 0; ii < 128; ++ii) {
#else
for (pthread_key_t ii = isolate_key + 1; ii < coro_thread_key; ++ii) {
#endif
int tls = static_cast<int>(reinterpret_cast<intptr_t>(pthread_getspecific(ii)));
if (tls == thread_id) {
thread_id_key = ii;
break;
}
}
assert(thread_id_key != 0x7777);
isolate->Exit();
return NULL;
}
/**
* Coroutine class definition
*/
void Coroutine::init(v8::Isolate* isolate) {
v8::Unlocker unlocker(isolate);
pthread_key_create(&coro_thread_key, NULL);
pthread_setspecific(coro_thread_key, &current());
#ifdef USE_V8_SYMBOLS
isolate_key = v8::internal::Isolate::isolate_key_;
thread_data_key = v8::internal::Isolate::per_isolate_thread_data_key_;
thread_id_key = v8::internal::Isolate::thread_id_key_;
#else
pthread_t thread;
pthread_create(&thread, NULL, find_thread_id_key, isolate);
pthread_join(thread, NULL);
#endif
}
Coroutine& Coroutine::current() {
Coroutine* current = static_cast<Coroutine*>(pthread_getspecific(coro_thread_key));
if (!current) {
current = new Coroutine;
pthread_setspecific(coro_thread_key, current);
}
return *current;
}
void Coroutine::set_stack_size(unsigned int size) {
assert(!stack_size);
stack_size = size;
}
size_t Coroutine::coroutines_created() {
return coroutines_created_;
}
void Coroutine::trampoline(void* that) {
#ifdef CORO_PTHREAD
pthread_setspecific(coro_thread_key, that);
#endif
#ifdef CORO_FIBER
// I can't figure out how to get the precise base of the stack in Windows. Since CreateFiber
// creates the stack automatically we don't have access to the base. We can however grab the
// current esp position, and use that as an approximation. Padding is added for safety since the
// base is slightly different.
static_cast<Coroutine*>(that)->stack_base = (size_t*)_AddressOfReturnAddress() - stack_size + 16;
#endif
if (!fls_data_pool.empty()) {
pthread_setspecific(thread_data_key, fls_data_pool.back());
pthread_setspecific(thread_id_key, fls_data_pool[fls_data_pool.size() - 2]);
pthread_setspecific(isolate_key, fls_data_pool[fls_data_pool.size() - 3]);
fls_data_pool.resize(fls_data_pool.size() - 3);
}
while (true) {
static_cast<Coroutine*>(that)->entry(const_cast<void*>(static_cast<Coroutine*>(that)->arg));
}
}
Coroutine::Coroutine() :
fls_data(v8_tls_keys),
entry(NULL),
arg(NULL) {
stack.sptr = NULL;
coro_create(&context, NULL, NULL, NULL, 0);
}
Coroutine::Coroutine(entry_t& entry, void* arg) :
fls_data(v8_tls_keys),
entry(entry),
arg(arg) {
}
Coroutine::~Coroutine() {
if (stack.sptr) {
coro_stack_free(&stack);
}
#ifdef CORO_FIBER
if (context.fiber)
#endif
(void)coro_destroy(&context);
}
Coroutine* Coroutine::create_fiber(entry_t* entry, void* arg) {
if (!fiber_pool.empty()) {
Coroutine* fiber = fiber_pool.back();
fiber_pool.pop_back();
fiber->reset(entry, arg);
return fiber;
}
Coroutine* coro = new Coroutine(*entry, arg);
if (!coro_stack_alloc(&coro->stack, stack_size)) {
delete coro;
return NULL;
}
coro_create(&coro->context, trampoline, coro, coro->stack.sptr, coro->stack.ssze);
#ifdef CORO_FIBER
// Stupid hack. libcoro's project structure combined with Windows's CreateFiber functions makes
// it difficult to catch this error. Sometimes Windows will return `ERROR_NOT_ENOUGH_MEMORY` or
// `ERROR_COMMITMENT_LIMIT` if it can't make any more fibers. However, `coro_stack_alloc` returns
// success unconditionally on Windows so we have to detect the error here, after the call to
// `coro_create`.
if (!coro->context.fiber) {
delete coro;
return NULL;
}
#endif
++coroutines_created_;
return coro;
}
void Coroutine::reset(entry_t* entry, void* arg) {
assert(entry != NULL);
this->entry = entry;
this->arg = arg;
}
void Coroutine::transfer(Coroutine& next) {
assert(this != &next);
#ifndef CORO_PTHREAD
fls_data[0] = pthread_getspecific(isolate_key);
fls_data[1] = pthread_getspecific(thread_id_key);
fls_data[2] = pthread_getspecific(thread_data_key);
pthread_setspecific(isolate_key, next.fls_data[0]);
pthread_setspecific(thread_id_key, next.fls_data[1]);
pthread_setspecific(thread_data_key, next.fls_data[2]);
pthread_setspecific(coro_thread_key, &next);
#endif
coro_transfer(&context, &next.context);
#ifndef CORO_PTHREAD
pthread_setspecific(coro_thread_key, this);
#endif
}
void Coroutine::run() {
Coroutine& current = Coroutine::current();
assert(!delete_me);
assert(&current != this);
current.transfer(*this);
if (delete_me) {
// This means finish() was called on the coroutine and the pool was full so this coroutine needs
// to be deleted. We can't delete from inside finish(), because that would deallocate the
// current stack. However we CAN delete here, we just have to be very careful.
assert(delete_me == this);
assert(&current != this);
delete_me = NULL;
delete this;
}
}
void Coroutine::finish(Coroutine& next, v8::Isolate* isolate) {
{
assert(&next != this);
assert(&current() == this);
if (fiber_pool.size() < pool_size) {
fiber_pool.push_back(this);
} else {
#if V8_MAJOR_VERSION > 4 || (V8_MAJOR_VERSION == 4 && V8_MINOR_VERSION >= 10)
// Clean up isolate data
isolate->DiscardThreadSpecificMetadata();
#else
// If not supported, then we can mitigate v8's leakage by saving these thread locals.
fls_data_pool.reserve(fls_data_pool.size() + 3);
fls_data_pool.push_back(pthread_getspecific(isolate_key));
fls_data_pool.push_back(pthread_getspecific(thread_id_key));
fls_data_pool.push_back(pthread_getspecific(thread_data_key));
#endif
// Can't delete right now because we're currently on this stack!
assert(delete_me == NULL);
delete_me = this;
}
}
this->transfer(next);
}
void* Coroutine::bottom() const {
#ifdef CORO_FIBER
return stack_base;
#else
return stack.sptr;
#endif
}
size_t Coroutine::size() const {
return sizeof(Coroutine) + stack_size * sizeof(void*);
}
+95
View File
@@ -0,0 +1,95 @@
#include <node.h>
#include <stdlib.h>
#include <vector>
#include "libcoro/coro.h"
class Coroutine {
public:
typedef void(entry_t)(void*);
private:
#ifdef CORO_FIBER
void* stack_base;
#endif
coro_context context;
coro_stack stack;
std::vector<void*> fls_data;
entry_t* entry;
void* arg;
~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();
/**
* This constructor will actually create a new fiber context. Execution does not begin
* until you call run() for the first time.
*/
Coroutine(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);
static void trampoline(void* that);
void transfer(Coroutine& next);
public:
static size_t pool_size;
/**
* Returns the currently-running fiber.
*/
static Coroutine& current();
/**
* Create a new fiber.
*/
static Coroutine* create_fiber(entry_t* entry, void* arg = NULL);
/**
* Initialize the library.
*/
static void init(v8::Isolate* isolate);
/**
* Set the size of coroutines created by this library. Since coroutines are pooled the stack
* size is global instead of per-coroutine. Stack is measured in sizeof(void*), so
* set_stack_size(128) -> 512 bytes or 1kb
*/
static void set_stack_size(unsigned int size);
/**
* Get the number of coroutines that have been created.
*/
static size_t coroutines_created();
/**
* Start or resume execution in this fiber. Note there is no explicit yield() function,
* you must manually run another fiber.
*/
void run();
/**
* 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, v8::Isolate* isolate);
/**
* 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;
};
+930
View File
@@ -0,0 +1,930 @@
#include "coroutine.h"
#include "v8-version.h"
#include <assert.h>
#include <node.h>
#include <node_version.h>
#include <vector>
#include <iostream>
#define THROW(x, m) return uni::Return(uni::ThrowException(Isolate::GetCurrent(), x(uni::NewLatin1String(Isolate::GetCurrent(), m))), args)
using namespace std;
using namespace v8;
// Handle legacy V8 API
namespace uni {
#if V8_AT_LEAST(5, 3)
// Actually 5.2.244
// ..or maybe actually 5.2.49
template <void (*F)(void*), class P>
void WeakCallbackShim(const WeakCallbackInfo<P>& data) {
F(data.GetParameter());
}
template <void (*F)(void*), class T, typename P>
void MakeWeak(Isolate* isolate, Persistent<T>& handle, P* val) {
handle.SetWeak(val, WeakCallbackShim<F, P>, WeakCallbackType::kFinalizer);
}
#elif V8_AT_LEAST(3, 26)
template <void (*F)(void*), class T, typename P>
void WeakCallbackShim(const v8::WeakCallbackData<T, P>& data) {
F(data.GetParameter());
}
template <void (*F)(void*), class T, typename P>
void MakeWeak(Isolate* isolate, Persistent<T>& handle, P* val) {
handle.SetWeak(val, WeakCallbackShim<F>);
}
#else
template <void (*F)(void*)>
void WeakCallbackShim(Persistent<Value> value, void* data) {
F(data);
}
template <void (*F)(void*), class T, typename P>
void MakeWeak(Isolate* isolate, Persistent<T>& handle, P* val) {
handle.MakeWeak(val, WeakCallbackShim<F>);
}
#endif
#if V8_AT_LEAST(3, 28)
class TryCatch : public v8::TryCatch {
public: TryCatch(Isolate* isolate) : v8::TryCatch(isolate) {}
};
#else
class TryCatch : public v8::TryCatch {
public: TryCatch(Isolate* isolate) : v8::TryCatch() {}
};
#endif
#if V8_AT_LEAST(4, 4)
Local<String> NewLatin1String(Isolate* isolate, const char* string) {
return String::NewFromOneByte(isolate, (const uint8_t*)string, NewStringType::kNormal).ToLocalChecked();
}
Local<String> NewLatin1Symbol(Isolate* isolate, const char* string) {
return String::NewFromOneByte(isolate, (const uint8_t*)string, NewStringType::kNormal).ToLocalChecked();
}
#elif V8_AT_LEAST(3, 26)
Handle<String> NewLatin1String(Isolate* isolate, const char* string) {
return String::NewFromOneByte(isolate, (const uint8_t*)string);
}
Handle<String> NewLatin1Symbol(Isolate* isolate, const char* string) {
return String::NewFromOneByte(isolate, (const uint8_t*)string);
}
#else
Handle<String> NewLatin1String(Isolate* isolate, const char* string) {
return String::New(string);
}
Handle<String> NewLatin1Symbol(Isolate* isolate, const char* string) {
return String::NewSymbol(string);
}
#endif
#if V8_AT_LEAST(4, 4)
Local<Function> GetFunction(Local<FunctionTemplate> tmpl) {
return tmpl->GetFunction(Isolate::GetCurrent()->GetCurrentContext()).ToLocalChecked();
}
Local<Value> Call(Local<Function> fn, Local<Object> recv, int argc, Local<Value> argv[]) {
Local<Value> result;
if (fn->Call(Isolate::GetCurrent()->GetCurrentContext(), recv, argc, argv).ToLocal(&result)) {
return result;
} else {
return {};
}
}
Local<Object> NewInstance(Isolate* isolate, Local<Function> fn, int argc, Local<Value> argv[]) {
return fn->NewInstance(isolate->GetCurrentContext(), argc, argv).ToLocalChecked();
}
#else
Local<Function> GetFunction(Local<FunctionTemplate> tmpl) {
return tmpl->GetFunction();
}
Local<Value> Call(Local<Function> fn, Local<Object> recv, int argc, Local<Value> argv[]) {
return fn->Call(recv, argc, argv);
}
Handle<Object> NewInstance(Isolate* isolate, Local<Function> fn, int argc, Local<Value> argv[]) {
return fn->NewInstance(argc, argv).ToLocalChecked();
}
#endif
#if V8_AT_LEAST(4, 4)
Local<Number> ToNumber(Local<Value> value) {
return value->ToNumber(Isolate::GetCurrent()->GetCurrentContext()).ToLocalChecked();
}
#else
Handle<Number> ToNumber(Local<Value> value) {
return value->ToNumber();
}
#endif
#if V8_AT_LEAST(6, 1)
Local<Value> GetStackTrace(TryCatch* try_catch, Local<Context> context) {
return try_catch->StackTrace(context).ToLocalChecked();
}
#else
Local<Value> GetStackTrace(TryCatch* try_catch, Handle<Context> context) {
return try_catch->StackTrace();
}
#endif
// Workaround for v8 issue #1180
// http://code.google.com/p/v8/issues/detail?id=1180
// NOTE: it's not clear if this is still necessary (perhaps Isolate::SetStackLimit could be used?)
#if V8_AT_LEAST(6, 1)
void fixStackLimit(Isolate* isolate, Local<Context> context) {
Script::Compile(context, uni::NewLatin1String(isolate, "void 0;")).ToLocalChecked();
}
#else
void fixStackLimit(Isolate* isolate, Handle<Context> context) {
Script::Compile(uni::NewLatin1String(isolate, "void 0;"));
}
#endif
#if V8_AT_LEAST(3, 26)
// Node v0.11.13+
typedef PropertyCallbackInfo<Value> GetterCallbackInfo;
typedef PropertyCallbackInfo<void> SetterCallbackInfo;
typedef void FunctionType;
typedef FunctionCallbackInfo<v8::Value> Arguments;
class HandleScope {
v8::HandleScope scope;
public: HandleScope(Isolate* isolate) : scope(isolate) {}
};
template <class T>
void Reset(Isolate* isolate, Persistent<T>& persistent, Local<T> handle) {
persistent.Reset(isolate, handle);
}
template <class T>
void Dispose(Isolate* isolate, Persistent<T>& handle) {
handle.Reset();
}
template <class T>
void ClearWeak(Isolate* isolate, Persistent<T>& handle) {
handle.ClearWeak(isolate);
}
template <class T>
void SetInternalPointer(Local<T> handle, int index, void* val) {
handle->SetAlignedPointerInInternalField(index, val);
}
template <class T>
void* GetInternalPointer(Local<T> handle, int index) {
return handle->GetAlignedPointerFromInternalField(index);
}
template <class T>
Local<T> Deref(Isolate* isolate, Persistent<T>& handle) {
return Local<T>::New(isolate, handle);
}
template <class T>
void Return(Local<T> handle, const Arguments& args) {
args.GetReturnValue().Set(handle);
}
template <class T>
void Return(Local<T> handle, GetterCallbackInfo info) {
info.GetReturnValue().Set(handle);
}
template <class T>
void Return(Persistent<T>& handle, GetterCallbackInfo info) {
info.GetReturnValue().Set(Local<T>::New(Isolate::GetCurrent(), handle));
}
Local<Value> ThrowException(Isolate* isolate, Local<Value> exception) {
return isolate->ThrowException(exception);
}
Local<Context> GetCurrentContext(Isolate* isolate) {
return isolate->GetCurrentContext();
}
Local<Primitive> Undefined(Isolate* isolate) {
return v8::Undefined(isolate);
}
Local<Boolean> NewBoolean(Isolate* isolate, bool value) {
return Boolean::New(isolate, value);
}
Local<Number> NewNumber(Isolate* isolate, double value) {
return Number::New(isolate, value);
}
Local<FunctionTemplate> NewFunctionTemplate(
Isolate* isolate,
FunctionCallback callback,
Local<Value> data = Local<Value>(),
Local<Signature> signature = Local<Signature>(),
int length = 0
) {
return FunctionTemplate::New(isolate, callback, data, signature, length);
}
Local<Signature> NewSignature(
Isolate* isolate,
Local<FunctionTemplate> receiver = Local<FunctionTemplate>()
) {
return Signature::New(isolate, receiver);
}
class ReverseIsolateScope {
Isolate* isolate;
public:
explicit inline ReverseIsolateScope(Isolate* isolate) : isolate(isolate) {
isolate->Exit();
}
inline ~ReverseIsolateScope() {
isolate->Enter();
}
};
void AdjustAmountOfExternalAllocatedMemory(Isolate* isolate, int64_t change_in_bytes) {
isolate->AdjustAmountOfExternalAllocatedMemory(change_in_bytes);
}
#else
// Node v0.10.x and lower
typedef AccessorInfo GetterCallbackInfo;
typedef AccessorInfo SetterCallbackInfo;
typedef Handle<Value> FunctionType;
typedef Arguments Arguments;
class HandleScope {
v8::HandleScope scope;
public: HandleScope(Isolate* isolate) {}
};
template <class T>
void Reset(Isolate* isolate, Persistent<T>& persistent, Handle<T> handle) {
persistent = Persistent<T>::New(handle);
}
template <class T>
void Dispose(Isolate* isolate, Persistent<T>& handle) {
handle.Dispose();
}
template <class T>
void ClearWeak(Isolate* isolate, Persistent<T>& handle) {
handle.ClearWeak();
}
template <class T>
void SetInternalPointer(Handle<T> handle, int index, void* val) {
handle->SetPointerInInternalField(index, val);
}
template <class T>
void* GetInternalPointer(Handle<T> handle, int index) {
return handle->GetPointerFromInternalField(index);
}
template <class T>
Handle<T> Deref(Isolate* isolate, Persistent<T>& handle) {
return Local<T>::New(handle);
}
Handle<Value> Return(Handle<Value> handle, GetterCallbackInfo info) {
return handle;
}
Handle<Value> Return(Handle<Value> handle, const Arguments& args) {
return handle;
}
Handle<Value> ThrowException(Isolate* isolate, Handle<Value> exception) {
return ThrowException(exception);
}
Handle<Context> GetCurrentContext(Isolate* isolate) {
return Context::GetCurrent();
}
Handle<Primitive> Undefined(Isolate* isolate) {
return v8::Undefined();
}
Handle<Boolean> NewBoolean(Isolate* isolate, bool value) {
return Boolean::New(value);
}
Handle<Number> NewNumber(Isolate* isolate, double value) {
return Number::New(value);
}
Handle<FunctionTemplate> NewFunctionTemplate(
Isolate* isolate,
InvocationCallback callback,
Handle<Value> data = Handle<Value>(),
Handle<Signature> signature = Handle<Signature>(),
int length = 0
) {
return FunctionTemplate::New(callback, data, signature);
}
Handle<Signature> NewSignature(
Isolate* isolate,
Handle<FunctionTemplate> receiver = Handle<FunctionTemplate>(),
int argc = 0,
Handle<FunctionTemplate> argv[] = 0
) {
return Signature::New(receiver, argc, argv);
}
class ReverseIsolateScope {
public: explicit inline ReverseIsolateScope(Isolate* isolate) {}
};
void AdjustAmountOfExternalAllocatedMemory(Isolate* isolate, int64_t change_in_bytes) {
V8::AdjustAmountOfExternalAllocatedMemory(change_in_bytes);
}
#endif
#if V8_AT_LEAST(6, 1)
void SetAccessor(
Isolate* isolate, Local<Object> object, Local<String> name,
FunctionType (*getter)(Local<String>, const GetterCallbackInfo&),
void (*setter)(Local<String> property, Local<Value> value, const SetterCallbackInfo&) = 0
) {
object->SetAccessor(isolate->GetCurrentContext(), name, (AccessorNameGetterCallback)getter, (AccessorNameSetterCallback)setter).ToChecked();
}
#elif V8_AT_LEAST(4, 4)
void SetAccessor(
Isolate* isolate, Local<Object> object, Local<String> name,
FunctionType (*getter)(Local<String>, const GetterCallbackInfo&),
void (*setter)(Local<String> property, Local<Value> value, const SetterCallbackInfo&) = 0
) {
object->SetAccessor(isolate->GetCurrentContext(), name, (AccessorNameGetterCallback)getter, (AccessorNameSetterCallback)setter);
}
#else
void SetAccessor(
Isolate* isolate, Local<Object> object, Local<String> name,
FunctionType (*getter)(Local<String>, const GetterCallbackInfo&),
void (*setter)(Local<String> property, Local<Value> value, const SetterCallbackInfo&) = 0
) {
object->SetAccessor(name, (AccessorNameGetterCallback)getter, (AccessorNameSetterCallback)setter);
}
#endif
#if V8_AT_LEAST(3, 29)
// This was actually added in 3.29.67
void SetStackGuard(Isolate* isolate, void* guard) {
isolate->SetStackLimit(reinterpret_cast<uintptr_t>(guard));
}
#elif V8_AT_LEAST(3, 26)
void SetStackGuard(Isolate* isolate, void* guard) {
ResourceConstraints constraints;
constraints.set_stack_limit(reinterpret_cast<uint32_t*>(guard));
v8::SetResourceConstraints(isolate, &constraints);
}
#else
// Extra padding for old versions of v8. Shit's fucked.
void SetStackGuard(Isolate* isolate, void* guard) {
ResourceConstraints constraints;
constraints.set_stack_limit(
reinterpret_cast<uint32_t*>(guard) + 18 * 1024
);
v8::SetResourceConstraints(&constraints);
}
#endif
}
class Fiber {
private:
static Locker* global_locker; // Node does not use locks or threads, so we need a global lock
static Persistent<FunctionTemplate> tmpl;
static Persistent<Function> fiber_object;
static Fiber* current;
static vector<Fiber*> orphaned_fibers;
static Persistent<Value> fatal_stack;
Isolate* isolate;
Persistent<Object> handle;
Persistent<Function> cb;
Persistent<Context> v8_context;
Persistent<Value> zombie_exception;
Persistent<Value> yielded;
bool yielded_exception;
Coroutine* entry_fiber;
Coroutine* this_fiber;
bool started;
bool yielding;
bool zombie;
bool resetting;
static Fiber& Unwrap(Local<Object> handle) {
assert(!handle.IsEmpty());
assert(handle->InternalFieldCount() == 1);
return *static_cast<Fiber*>(uni::GetInternalPointer(handle, 0));
}
Fiber(Local<Object> handle, Local<Function> cb, Local<Context> v8_context) :
isolate(Isolate::GetCurrent()),
started(false),
yielding(false),
zombie(false),
resetting(false) {
uni::Reset(isolate, this->handle, handle);
uni::Reset(isolate, this->cb, cb);
uni::Reset(isolate, this->v8_context, v8_context);
MakeWeak();
uni::SetInternalPointer(handle, 0, this);
}
virtual ~Fiber() {
assert(!this->started);
uni::Dispose(isolate, handle);
uni::Dispose(isolate, cb);
uni::Dispose(isolate, v8_context);
}
/**
* Call MakeWeak if it's ok for v8 to garbage collect this Fiber.
* i.e. After fiber completes, while yielded, or before started
*/
void MakeWeak() {
uni::MakeWeak<WeakCallback>(isolate, handle, (void*)this);
}
/**
* And call ClearWeak if it's not ok for v8 to garbage collect this Fiber.
* i.e. While running.
*/
void ClearWeak() {
handle.ClearWeak();
}
/**
* Called when there are no more references to this object in Javascript. If this happens and
* the fiber is currently suspended we'll unwind the fiber's stack by throwing exceptions in
* order to clear all references.
*/
static void WeakCallback(void* data) {
Fiber& that = *static_cast<Fiber*>(data);
#if !V8_AT_LEAST(7, 4)
// Deprecated in 0781f42b6
assert(that.handle.IsNearDeath());
#endif
assert(current != &that);
// We'll unwind running fibers later... doing it from the garbage collector is bad news.
if (that.started) {
assert(that.yielding);
orphaned_fibers.push_back(&that);
that.ClearWeak();
return;
}
delete &that;
}
/**
* When the v8 garbage collector notifies us about dying fibers instead of unwindng their
* stack as soon as possible we put them aside to unwind later. Unwinding from the garbage
* collector leads to exponential time garbage collections if there are many orphaned Fibers,
* there's also the possibility of running out of stack space. It's generally bad news.
*
* So instead we have this function to clean up all the fibers after the garbage collection
* has finished.
*/
static void DestroyOrphans() {
if (orphaned_fibers.empty()) {
return;
}
vector<Fiber*> orphans(orphaned_fibers);
orphaned_fibers.clear();
for (vector<Fiber*>::iterator ii = orphans.begin(); ii != orphans.end(); ++ii) {
Fiber& that = **ii;
that.UnwindStack();
if (that.yielded_exception) {
// If you throw an exception from a fiber that's being garbage collected there's no way
// to bubble that exception up to the application.
auto stack(uni::Deref(that.isolate, fatal_stack));
cerr <<
"An exception was thrown from a Fiber which was being garbage collected. This error "
"can not be gracefully recovered from. The only acceptable behavior is to terminate "
"this application. The exception appears below:\n\n"
<<*stack <<"\n";
exit(1);
} else {
uni::Dispose(that.isolate, fatal_stack);
}
uni::Dispose(that.isolate, that.yielded);
that.MakeWeak();
}
}
/**
* Instantiate a new Fiber object. When a fiber is created it only grabs a handle to the
* callback; it doesn't create any new contexts until run() is called.
*/
static uni::FunctionType New(const uni::Arguments& args) {
if (args.Length() != 1) {
THROW(Exception::TypeError, "Fiber expects 1 argument");
} else if (!args[0]->IsFunction()) {
THROW(Exception::TypeError, "Fiber expects a function");
} else if (!args.IsConstructCall()) {
Local<Value> argv[1] = { args[0] };
return uni::Return(uni::NewInstance(Isolate::GetCurrent(), uni::GetFunction(uni::Deref(Isolate::GetCurrent(), tmpl)), 1, argv), args);
}
Local<Function> fn = Local<Function>::Cast(args[0]);
new Fiber(args.This(), fn, uni::GetCurrentContext(Isolate::GetCurrent()));
return uni::Return(args.This(), args);
}
/**
* Begin or resume the current fiber. If the fiber is not currently running a new context will
* be created and the callback will start. Otherwise we switch back into the exist context.
*/
static uni::FunctionType Run(const uni::Arguments& args) {
Fiber& that = Unwrap(args.Holder());
// There seems to be no better place to put this check..
DestroyOrphans();
if (that.started && !that.yielding) {
THROW(Exception::Error, "This Fiber is already running");
} else if (args.Length() > 1) {
THROW(Exception::TypeError, "run() excepts 1 or no arguments");
}
if (!that.started) {
// Create a new context with entry point `Fiber::RunFiber()`.
void** data = new void*[2];
data[0] = (void*)&args;
data[1] = &that;
that.this_fiber = Coroutine::create_fiber((void (*)(void*))RunFiber, data);
if (!that.this_fiber) {
delete[] data;
THROW(Exception::RangeError, "Out of memory");
}
that.started = true;
} else {
// If the fiber is currently running put the first parameter to `run()` on `yielded`, then
// the pending call to `yield()` will return that value. `yielded` in this case is just a
// misnomer, we're just reusing the same handle.
that.yielded_exception = false;
if (args.Length()) {
uni::Reset(that.isolate, that.yielded, args[0]);
} else {
uni::Reset<Value>(that.isolate, that.yielded, uni::Undefined(that.isolate));
}
}
that.SwapContext();
return uni::Return(that.ReturnYielded(), args);
}
/**
* Throw an exception into a currently yielding fiber.
*/
static uni::FunctionType ThrowInto(const uni::Arguments& args) {
Fiber& that = Unwrap(args.Holder());
if (!that.yielding) {
THROW(Exception::Error, "This Fiber is not yielding");
} else if (args.Length() == 0) {
uni::Reset<Value>(that.isolate, that.yielded, uni::Undefined(that.isolate));
} else if (args.Length() == 1) {
uni::Reset(that.isolate, that.yielded, args[0]);
} else {
THROW(Exception::TypeError, "throwInto() expects 1 or no arguments");
}
that.yielded_exception = true;
that.SwapContext();
return uni::Return(that.ReturnYielded(), args);
}
/**
* Unwinds a currently running fiber. If the fiber is not running then this function has no
* effect.
*/
static uni::FunctionType Reset(const uni::Arguments& args) {
Fiber& that = Unwrap(args.Holder());
if (!that.started) {
return uni::Return(uni::Undefined(that.isolate), args);
} else if (!that.yielding) {
THROW(Exception::Error, "This Fiber is not yielding");
} else if (args.Length()) {
THROW(Exception::TypeError, "reset() expects no arguments");
}
that.resetting = true;
that.UnwindStack();
that.resetting = false;
that.MakeWeak();
Local<Value> val = uni::Deref(that.isolate, that.yielded);
uni::Dispose(that.isolate, that.yielded);
if (that.yielded_exception) {
return uni::Return(uni::ThrowException(that.isolate, val), args);
} else {
return uni::Return(val, args);
}
}
/**
* Turns the fiber into a zombie and unwinds its whole stack.
*
* After calling this function you must either destroy this fiber or call MakeWeak() or it will
* be leaked.
*/
void UnwindStack() {
assert(!zombie);
assert(started);
assert(yielding);
zombie = true;
// Setup an exception which will be thrown and rethrown from Fiber::Yield()
Local<Value> zombie_exception = Exception::Error(uni::NewLatin1String(isolate, "This Fiber is a zombie"));
uni::Reset(isolate, this->zombie_exception, zombie_exception);
uni::Reset(isolate, yielded, zombie_exception);
yielded_exception = true;
// Swap context back to Fiber::Yield() which will throw an exception to unwind the stack.
// Futher calls to yield from this fiber will rethrow the same exception.
SwapContext();
assert(!started);
zombie = false;
// Make sure this is the exception we threw
if (yielded_exception && yielded == zombie_exception) {
yielded_exception = false;
uni::Dispose(isolate, yielded);
uni::Reset<Value>(isolate, yielded, uni::Undefined(isolate));
}
uni::Dispose(isolate, this->zombie_exception);
}
/**
* Common logic between Run(), ThrowInto(), and UnwindStack(). This is essentially just a
* wrapper around this->fiber->() which also handles all the bookkeeping needed.
*/
void SwapContext() {
entry_fiber = &Coroutine::current();
Fiber* last_fiber = current;
current = this;
// This will jump into either `RunFiber()` or `Yield()`, depending on if the fiber was
// already running.
{
Unlocker unlocker(isolate);
uni::ReverseIsolateScope isolate_scope(isolate);
this_fiber->run();
}
// At this point the fiber either returned or called `yield()`.
current = last_fiber;
}
/**
* Grabs and resets this fiber's yielded value.
*/
Local<Value> ReturnYielded() {
Local<Value> val = uni::Deref(isolate, yielded);
uni::Dispose(isolate, yielded);
if (yielded_exception) {
return uni::ThrowException(isolate, val);
} else {
return val;
}
}
/**
* This is the entry point for a new fiber, from `run()`.
*/
static void RunFiber(void** data) {
const uni::Arguments* args = (const uni::Arguments*)data[0];
Fiber& that = *(Fiber*)data[1];
delete[] data;
// New C scope so that the stack-allocated objects will be destroyed before calling
// Coroutine::finish, because that function may not return, in which case the destructors in
// this function won't be called.
{
Locker locker(that.isolate);
Isolate::Scope isolate_scope(that.isolate);
uni::HandleScope scope(that.isolate);
// Set the stack guard for this "thread"; allow 6k of padding past the JS limit for
// native v8 code to run
uni::SetStackGuard(that.isolate, reinterpret_cast<char*>(that.this_fiber->bottom()) + 1024 * 6);
uni::TryCatch try_catch(that.isolate);
that.ClearWeak();
Local<Context> v8_context = uni::Deref(that.isolate, that.v8_context);
v8_context->Enter();
uni::fixStackLimit(that.isolate, v8_context);
Local<Value> yielded;
if (args->Length()) {
Local<Value> argv[1] = { (*args)[0] };
yielded = uni::Call(uni::Deref(that.isolate, that.cb), v8_context->Global(), 1, argv);
} else {
yielded = uni::Call(uni::Deref(that.isolate, that.cb), v8_context->Global(), 0, NULL);
}
if (try_catch.HasCaught()) {
uni::Reset(that.isolate, that.yielded, try_catch.Exception());
that.yielded_exception = true;
if (that.zombie && !that.resetting && !uni::Deref(that.isolate, that.yielded)->StrictEquals(uni::Deref(that.isolate, that.zombie_exception))) {
// Throwing an exception from a garbage sweep
uni::Reset(that.isolate, fatal_stack, uni::GetStackTrace(&try_catch, v8_context));
}
} else {
uni::Reset(that.isolate, that.yielded, yielded);
that.yielded_exception = false;
}
// Don't make weak until after notifying the garbage collector. Otherwise it may try and
// free this very fiber!
if (!that.zombie) {
that.MakeWeak();
}
// Now safe to leave the context, this stack is done with JS.
v8_context->Exit();
}
// The function returned (instead of yielding).
that.started = false;
that.this_fiber->finish(*that.entry_fiber, that.isolate);
}
/**
* Yield control back to the function that called `run()`. The first parameter to this function
* is returned from `run()`. The context is saved, to be later resumed from `run()`.
* note: sigh, there is a #define Yield() in WinBase.h on Windows
*/
static uni::FunctionType Yield_(const uni::Arguments& args) {
if (current == NULL) {
THROW(Exception::Error, "yield() called with no fiber running");
}
Fiber& that = *current;
if (that.zombie) {
return uni::Return(uni::ThrowException(that.isolate, uni::Deref(that.isolate, that.zombie_exception)), args);
} else if (args.Length() == 0) {
uni::Reset<Value>(that.isolate, that.yielded, Undefined(that.isolate));
} else if (args.Length() == 1) {
uni::Reset(that.isolate, that.yielded, args[0]);
} else {
THROW(Exception::TypeError, "yield() expects 1 or no arguments");
}
that.yielded_exception = false;
// While not running this can be garbage collected if no one has a handle.
that.MakeWeak();
// Return control back to `Fiber::run()`. While control is outside this function we mark it as
// ok to garbage collect. If no one ever has a handle to resume the function it's harmful to
// keep the handle around.
{
Unlocker unlocker(that.isolate);
uni::ReverseIsolateScope isolate_scope(that.isolate);
that.yielding = true;
that.entry_fiber->run();
that.yielding = false;
}
// Now `run()` has been called again.
// Don't garbage collect anymore!
that.ClearWeak();
// Return the yielded value
return uni::Return(that.ReturnYielded(), args);
}
/**
* Getters for `started`, and `current`.
*/
static uni::FunctionType GetStarted(Local<String> property, const uni::GetterCallbackInfo& info) {
if (info.This().IsEmpty() || info.This()->InternalFieldCount() != 1) {
return uni::Return(uni::Undefined(Isolate::GetCurrent()), info);
}
Fiber& that = Unwrap(info.This());
return uni::Return(uni::NewBoolean(that.isolate, that.started), info);
}
static uni::FunctionType GetCurrent(Local<String> property, const uni::GetterCallbackInfo& info) {
if (current) {
return uni::Return(current->handle, info);
} else {
return uni::Return(uni::Undefined(Isolate::GetCurrent()), info);
}
}
/**
* Allow access to coroutine pool size
*/
static uni::FunctionType GetPoolSize(Local<String> property, const uni::GetterCallbackInfo& info) {
return uni::Return(uni::NewNumber(Isolate::GetCurrent(), Coroutine::pool_size), info);
}
static void SetPoolSize(Local<String> property, Local<Value> value, const uni::SetterCallbackInfo& info) {
Coroutine::pool_size = uni::ToNumber(value)->Value();
}
/**
* Return number of fibers that have been created
*/
static uni::FunctionType GetFibersCreated(Local<String> property, const uni::GetterCallbackInfo& info) {
return uni::Return(uni::NewNumber(Isolate::GetCurrent(), Coroutine::coroutines_created()), info);
}
public:
/**
* Initialize the Fiber library.
*/
static void Init(Local<Object> target) {
// Use a locker which won't get destroyed when this library gets unloaded. This is a hack
// to prevent v8 from trying to clean up this "thread" while the whole application is
// shutting down. TODO: There's likely a better way to accomplish this, but since the
// application is going down lost memory isn't the end of the world. But with a regular lock
// there's seg faults when node shuts down.
Isolate* isolate = Isolate::GetCurrent();
Local<Context> context = isolate->GetCurrentContext();
global_locker = new Locker(isolate);
current = NULL;
// Fiber constructor
Local<FunctionTemplate> tmpl = uni::NewFunctionTemplate(isolate, New);
uni::Reset(isolate, Fiber::tmpl, tmpl);
tmpl->SetClassName(uni::NewLatin1Symbol(isolate, "Fiber"));
// Guard which only allows these methods to be called on a fiber; prevents
// `fiber.run.call({})` from seg faulting.
Local<Signature> sig = uni::NewSignature(isolate, tmpl);
tmpl->InstanceTemplate()->SetInternalFieldCount(1);
// Fiber.prototype
Local<ObjectTemplate> proto = tmpl->PrototypeTemplate();
proto->Set(uni::NewLatin1Symbol(isolate, "reset"),
uni::NewFunctionTemplate(isolate, Reset, Local<Value>(), sig));
proto->Set(uni::NewLatin1Symbol(isolate, "run"),
uni::NewFunctionTemplate(isolate, Run, Local<Value>(), sig));
proto->Set(uni::NewLatin1Symbol(isolate, "throwInto"),
uni::NewFunctionTemplate(isolate, ThrowInto, Local<Value>(), sig));
proto->SetAccessor(uni::NewLatin1Symbol(isolate, "started"), GetStarted);
// Global yield() function
Local<Function> yield = uni::GetFunction(uni::NewFunctionTemplate(isolate, Yield_));
Local<String> sym_yield = uni::NewLatin1Symbol(isolate, "yield");
target->Set(context, sym_yield, yield).FromJust();
// Fiber properties
Local<Function> fn = uni::GetFunction(tmpl);
fn->Set(context, sym_yield, yield).FromJust();
uni::SetAccessor(isolate, fn, uni::NewLatin1Symbol(isolate, "current"), GetCurrent);
uni::SetAccessor(isolate, fn, uni::NewLatin1Symbol(isolate, "poolSize"), GetPoolSize, SetPoolSize);
uni::SetAccessor(isolate, fn, uni::NewLatin1Symbol(isolate, "fibersCreated"), GetFibersCreated);
// Global Fiber
target->Set(context, uni::NewLatin1Symbol(isolate, "Fiber"), fn).FromJust();
uni::Reset(isolate, fiber_object, fn);
}
};
Persistent<FunctionTemplate> Fiber::tmpl;
Persistent<Function> Fiber::fiber_object;
Locker* Fiber::global_locker;
Fiber* Fiber::current = NULL;
vector<Fiber*> Fiber::orphaned_fibers;
Persistent<Value> Fiber::fatal_stack;
bool did_init = false;
#if !NODE_VERSION_AT_LEAST(0,10,0)
extern "C"
#endif
void init(Local<Object> target) {
Isolate* isolate = Isolate::GetCurrent();
Local<Context> context = isolate->GetCurrentContext();
if (did_init || !target->Get(context, uni::NewLatin1Symbol(isolate, "Fiber")).ToLocalChecked()->IsUndefined()) {
// Oh god. Node will call init() twice even though the library was loaded only once. See Node
// issue #2621 (no fix).
return;
}
did_init = true;
uni::HandleScope scope(isolate);
Coroutine::init(isolate);
Fiber::Init(target);
// Default stack size of either 512k or 1M. Perhaps make this configurable by the run time?
Coroutine::set_stack_size(128 * 1024);
}
NODE_MODULE(fibers, init)
+26
View File
@@ -0,0 +1,26 @@
Copyright (c) 2000-2009 Marc Alexander Lehmann <schmorp@schmorp.de>
Redistribution and use in source and binary forms, with or without modifica-
tion, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
Alternatively, the following files carry an additional notice that
explicitly allows relicensing under the GPLv2: coro.c, coro.h.
+6
View File
@@ -0,0 +1,6 @@
Configuration, documentation etc. is provided in the coro.h file. Please
note that the file conftest.c in this distribution is under the GPL. It is
not needed for proper operation of this library though, for that, coro.h
and coro.c suffice.
Marc Lehmann <schmorp@schmorp.de>
+154
View File
@@ -0,0 +1,154 @@
/*
* This file was taken from pth-1.40/aclocal.m4
* The original copyright is below.
*
* GNU Pth - The GNU Portable Threads
* Copyright (c) 1999-2001 Ralf S. Engelschall <rse@engelschall.com>
*
* This file is part of GNU Pth, a non-preemptive thread scheduling
* library which can be found at http://www.gnu.org/software/pth/.
*
* This file is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA, or contact Marc Lehmann <schmorp@schmorp.de>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(TEST_sigstack) || defined(TEST_sigaltstack)
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#endif
#if defined(TEST_makecontext)
#include <ucontext.h>
#endif
union alltypes {
long l;
double d;
void *vp;
void (*fp)(void);
char *cp;
};
static volatile char *handler_addr = (char *)0xDEAD;
#if defined(TEST_sigstack) || defined(TEST_sigaltstack)
static volatile int handler_done = 0;
void handler(int sig)
{
char garbage[1024];
int i;
auto int dummy;
for (i = 0; i < 1024; i++)
garbage[i] = 'X';
handler_addr = (char *)&dummy;
handler_done = 1;
return;
}
#endif
#if defined(TEST_makecontext)
static ucontext_t uc_handler;
static ucontext_t uc_main;
void handler(void)
{
char garbage[1024];
int i;
auto int dummy;
for (i = 0; i < 1024; i++)
garbage[i] = 'X';
handler_addr = (char *)&dummy;
swapcontext(&uc_handler, &uc_main);
return;
}
#endif
int main(int argc, char *argv[])
{
FILE *f;
char *skaddr;
char *skbuf;
int sksize;
char result[1024];
int i;
sksize = 32768;
skbuf = (char *)malloc(sksize*2+2*sizeof(union alltypes));
if (skbuf == NULL)
exit(1);
for (i = 0; i < sksize*2+2*sizeof(union alltypes); i++)
skbuf[i] = 'A';
skaddr = skbuf+sizeof(union alltypes);
#if defined(TEST_sigstack) || defined(TEST_sigaltstack)
{
struct sigaction sa;
#if defined(TEST_sigstack)
struct sigstack ss;
#elif defined(TEST_sigaltstack) && defined(HAVE_STACK_T)
stack_t ss;
#else
struct sigaltstack ss;
#endif
#if defined(TEST_sigstack)
ss.ss_sp = (void *)(skaddr + sksize);
ss.ss_onstack = 0;
if (sigstack(&ss, NULL) < 0)
exit(1);
#elif defined(TEST_sigaltstack)
ss.ss_sp = (void *)(skaddr + sksize);
ss.ss_size = sksize;
ss.ss_flags = 0;
if (sigaltstack(&ss, NULL) < 0)
exit(1);
#endif
memset((void *)&sa, 0, sizeof(struct sigaction));
sa.sa_handler = handler;
sa.sa_flags = SA_ONSTACK;
sigemptyset(&sa.sa_mask);
sigaction(SIGUSR1, &sa, NULL);
kill(getpid(), SIGUSR1);
while (!handler_done)
/*nop*/;
}
#endif
#if defined(TEST_makecontext)
{
if (getcontext(&uc_handler) != 0)
exit(1);
uc_handler.uc_link = NULL;
uc_handler.uc_stack.ss_sp = (void *)(skaddr + sksize);
uc_handler.uc_stack.ss_size = sksize;
uc_handler.uc_stack.ss_flags = 0;
makecontext(&uc_handler, handler, 1);
swapcontext(&uc_main, &uc_handler);
}
#endif
if (handler_addr == (char *)0xDEAD)
exit(1);
if (handler_addr < skaddr+sksize) {
/* stack was placed into lower area */
if (*(skaddr+sksize) != 'A')
sprintf(result, "(skaddr)+(sksize)-%d,(sksize)-%d",
sizeof(union alltypes), sizeof(union alltypes));
else
strcpy(result, "(skaddr)+(sksize),(sksize)");
}
else {
/* stack was placed into higher area */
if (*(skaddr+sksize*2) != 'A')
sprintf(result, "(skaddr),(sksize)-%d", sizeof(union alltypes));
else
strcpy(result, "(skaddr),(sksize)");
}
printf("%s\n", result);
exit(0);
}
+706
View File
@@ -0,0 +1,706 @@
/*
* Copyright (c) 2001-2011 Marc Alexander Lehmann <schmorp@schmorp.de>
*
* Redistribution and use in source and binary forms, with or without modifica-
* tion, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
* CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
* CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
* ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License ("GPL") version 2 or any later version,
* in which case the provisions of the GPL are applicable instead of
* the above. If you wish to allow the use of your version of this file
* only under the terms of the GPL and not to allow others to use your
* version of this file under the BSD license, indicate your decision
* by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL. If you do not delete the
* provisions above, a recipient may use your version of this file under
* either the BSD or the GPL.
*
* This library is modelled strictly after Ralf S. Engelschalls article at
* http://www.gnu.org/software/pth/rse-pmt.ps. So most of the credit must
* go to Ralf S. Engelschall <rse@engelschall.com>.
*/
#include "coro.h"
#include <stddef.h>
#include <string.h>
/*****************************************************************************/
/* ucontext/setjmp/asm backends */
/*****************************************************************************/
#if CORO_UCONTEXT || CORO_SJLJ || CORO_LOSER || CORO_LINUX || CORO_IRIX || CORO_ASM
# if CORO_UCONTEXT
# include <stddef.h>
# endif
# if !defined(STACK_ADJUST_PTR)
# if __sgi
/* IRIX is decidedly NON-unix */
# define STACK_ADJUST_PTR(sp,ss) ((char *)(sp) + (ss) - 8)
# define STACK_ADJUST_SIZE(sp,ss) ((ss) - 8)
# elif (__i386__ && CORO_LINUX) || (_M_IX86 && CORO_LOSER)
# define STACK_ADJUST_PTR(sp,ss) ((char *)(sp) + (ss))
# define STACK_ADJUST_SIZE(sp,ss) (ss)
# elif (__amd64__ && CORO_LINUX) || ((_M_AMD64 || _M_IA64) && CORO_LOSER)
# define STACK_ADJUST_PTR(sp,ss) ((char *)(sp) + (ss) - 8)
# define STACK_ADJUST_SIZE(sp,ss) (ss)
# else
# define STACK_ADJUST_PTR(sp,ss) (sp)
# define STACK_ADJUST_SIZE(sp,ss) (ss)
# endif
# endif
# include <stdlib.h>
# if CORO_SJLJ
# include <stdio.h>
# include <signal.h>
# include <unistd.h>
# endif
static coro_func coro_init_func;
static void *coro_init_arg;
static coro_context *new_coro, *create_coro;
static void
coro_init (void)
{
volatile coro_func func = coro_init_func;
volatile void *arg = coro_init_arg;
coro_transfer (new_coro, create_coro);
#if __GCC_HAVE_DWARF2_CFI_ASM && __amd64
asm (".cfi_undefined rip");
#endif
func ((void *)arg);
/* the new coro returned. bad. just abort() for now */
abort ();
}
# if CORO_SJLJ
static volatile int trampoline_done;
/* trampoline signal handler */
static void
trampoline (int sig)
{
if (coro_setjmp (new_coro->env))
coro_init (); /* start it */
else
trampoline_done = 1;
}
# endif
# if CORO_ASM
#if _WIN32 || __CYGWIN__
#define CORO_WIN_TIB 1
#endif
asm (
"\t.text\n"
#if _WIN32 || __CYGWIN__ || __APPLE__
"\t.globl _coro_transfer\n"
"_coro_transfer:\n"
#else
"\t.globl coro_transfer\n"
"coro_transfer:\n"
#endif
/* windows, of course, gives a shit on the amd64 ABI and uses different registers */
/* http://blogs.msdn.com/freik/archive/2005/03/17/398200.aspx */
#if __amd64
#if _WIN32 || __CYGWIN__
#define NUM_SAVED 29
"\tsubq $168, %rsp\t" /* one dummy qword to improve alignment */
"\tmovaps %xmm6, (%rsp)\n"
"\tmovaps %xmm7, 16(%rsp)\n"
"\tmovaps %xmm8, 32(%rsp)\n"
"\tmovaps %xmm9, 48(%rsp)\n"
"\tmovaps %xmm10, 64(%rsp)\n"
"\tmovaps %xmm11, 80(%rsp)\n"
"\tmovaps %xmm12, 96(%rsp)\n"
"\tmovaps %xmm13, 112(%rsp)\n"
"\tmovaps %xmm14, 128(%rsp)\n"
"\tmovaps %xmm15, 144(%rsp)\n"
"\tpushq %rsi\n"
"\tpushq %rdi\n"
"\tpushq %rbp\n"
"\tpushq %rbx\n"
"\tpushq %r12\n"
"\tpushq %r13\n"
"\tpushq %r14\n"
"\tpushq %r15\n"
#if CORO_WIN_TIB
"\tpushq %fs:0x0\n"
"\tpushq %fs:0x8\n"
"\tpushq %fs:0xc\n"
#endif
"\tmovq %rsp, (%rcx)\n"
"\tmovq (%rdx), %rsp\n"
#if CORO_WIN_TIB
"\tpopq %fs:0xc\n"
"\tpopq %fs:0x8\n"
"\tpopq %fs:0x0\n"
#endif
"\tpopq %r15\n"
"\tpopq %r14\n"
"\tpopq %r13\n"
"\tpopq %r12\n"
"\tpopq %rbx\n"
"\tpopq %rbp\n"
"\tpopq %rdi\n"
"\tpopq %rsi\n"
"\tmovaps (%rsp), %xmm6\n"
"\tmovaps 16(%rsp), %xmm7\n"
"\tmovaps 32(%rsp), %xmm8\n"
"\tmovaps 48(%rsp), %xmm9\n"
"\tmovaps 64(%rsp), %xmm10\n"
"\tmovaps 80(%rsp), %xmm11\n"
"\tmovaps 96(%rsp), %xmm12\n"
"\tmovaps 112(%rsp), %xmm13\n"
"\tmovaps 128(%rsp), %xmm14\n"
"\tmovaps 144(%rsp), %xmm15\n"
"\taddq $168, %rsp\n"
#else
#define NUM_SAVED 6
"\tpushq %rbp\n"
"\tpushq %rbx\n"
"\tpushq %r12\n"
"\tpushq %r13\n"
"\tpushq %r14\n"
"\tpushq %r15\n"
"\tmovq %rsp, (%rdi)\n"
"\tmovq (%rsi), %rsp\n"
"\tpopq %r15\n"
"\tpopq %r14\n"
"\tpopq %r13\n"
"\tpopq %r12\n"
"\tpopq %rbx\n"
"\tpopq %rbp\n"
#endif
"\tpopq %rcx\n"
"\tjmpq *%rcx\n"
#elif __i386
#define NUM_SAVED 4
"\tpushl %ebp\n"
"\tpushl %ebx\n"
"\tpushl %esi\n"
"\tpushl %edi\n"
#if CORO_WIN_TIB
#undef NUM_SAVED
#define NUM_SAVED 7
"\tpushl %fs:0\n"
"\tpushl %fs:4\n"
"\tpushl %fs:8\n"
#endif
"\tmovl %esp, (%eax)\n"
"\tmovl (%edx), %esp\n"
#if CORO_WIN_TIB
"\tpopl %fs:8\n"
"\tpopl %fs:4\n"
"\tpopl %fs:0\n"
#endif
"\tpopl %edi\n"
"\tpopl %esi\n"
"\tpopl %ebx\n"
"\tpopl %ebp\n"
"\tpopl %ecx\n"
"\tjmpl *%ecx\n"
#else
#error unsupported architecture
#endif
);
# endif
void
coro_create (coro_context *ctx, coro_func coro, void *arg, void *sptr, size_t ssize)
{
coro_context nctx;
# if CORO_SJLJ
stack_t ostk, nstk;
struct sigaction osa, nsa;
sigset_t nsig, osig;
# endif
if (!coro)
return;
coro_init_func = coro;
coro_init_arg = arg;
new_coro = ctx;
create_coro = &nctx;
# if CORO_SJLJ
/* we use SIGUSR2. first block it, then fiddle with it. */
sigemptyset (&nsig);
sigaddset (&nsig, SIGUSR2);
sigprocmask (SIG_BLOCK, &nsig, &osig);
nsa.sa_handler = trampoline;
sigemptyset (&nsa.sa_mask);
nsa.sa_flags = SA_ONSTACK;
if (sigaction (SIGUSR2, &nsa, &osa))
{
perror ("sigaction");
abort ();
}
/* set the new stack */
nstk.ss_sp = STACK_ADJUST_PTR (sptr, ssize); /* yes, some platforms (IRIX) get this wrong. */
nstk.ss_size = STACK_ADJUST_SIZE (sptr, ssize);
nstk.ss_flags = 0;
if (sigaltstack (&nstk, &ostk) < 0)
{
perror ("sigaltstack");
abort ();
}
trampoline_done = 0;
kill (getpid (), SIGUSR2);
sigfillset (&nsig); sigdelset (&nsig, SIGUSR2);
while (!trampoline_done)
sigsuspend (&nsig);
sigaltstack (0, &nstk);
nstk.ss_flags = SS_DISABLE;
if (sigaltstack (&nstk, 0) < 0)
perror ("sigaltstack");
sigaltstack (0, &nstk);
if (~nstk.ss_flags & SS_DISABLE)
abort ();
if (~ostk.ss_flags & SS_DISABLE)
sigaltstack (&ostk, 0);
sigaction (SIGUSR2, &osa, 0);
sigprocmask (SIG_SETMASK, &osig, 0);
# elif CORO_LOSER
coro_setjmp (ctx->env);
#if __CYGWIN__ && __i386
ctx->env[8] = (long) coro_init;
ctx->env[7] = (long) ((char *)sptr + ssize) - sizeof (long);
#elif __CYGWIN__ && __x86_64
ctx->env[7] = (long) coro_init;
ctx->env[6] = (long) ((char *)sptr + ssize) - sizeof (long);
#elif defined __MINGW32__
ctx->env[5] = (long) coro_init;
ctx->env[4] = (long) ((char *)sptr + ssize) - sizeof (long);
#elif defined _M_IX86
((_JUMP_BUFFER *)&ctx->env)->Eip = (long) coro_init;
((_JUMP_BUFFER *)&ctx->env)->Esp = (long) STACK_ADJUST_PTR (sptr, ssize) - sizeof (long);
#elif defined _M_AMD64
((_JUMP_BUFFER *)&ctx->env)->Rip = (__int64) coro_init;
((_JUMP_BUFFER *)&ctx->env)->Rsp = (__int64) STACK_ADJUST_PTR (sptr, ssize) - sizeof (__int64);
#elif defined _M_IA64
((_JUMP_BUFFER *)&ctx->env)->StIIP = (__int64) coro_init;
((_JUMP_BUFFER *)&ctx->env)->IntSp = (__int64) STACK_ADJUST_PTR (sptr, ssize) - sizeof (__int64);
#else
#error "microsoft libc or architecture not supported"
#endif
# elif CORO_LINUX
coro_setjmp (ctx->env);
#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined (JB_PC) && defined (JB_SP)
ctx->env[0].__jmpbuf[JB_PC] = (long) coro_init;
ctx->env[0].__jmpbuf[JB_SP] = (long) STACK_ADJUST_PTR (sptr, ssize) - sizeof (long);
#elif __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined (__mc68000__)
ctx->env[0].__jmpbuf[0].__aregs[0] = (long int)coro_init;
ctx->env[0].__jmpbuf[0].__sp = (int *) ((char *)sptr + ssize) - sizeof (long);
#elif defined (__GNU_LIBRARY__) && defined (__i386__)
ctx->env[0].__jmpbuf[0].__pc = (char *) coro_init;
ctx->env[0].__jmpbuf[0].__sp = (void *) ((char *)sptr + ssize) - sizeof (long);
#elif defined (__GNU_LIBRARY__) && defined (__amd64__)
ctx->env[0].__jmpbuf[JB_PC] = (long) coro_init;
ctx->env[0].__jmpbuf[0].__sp = (void *) ((char *)sptr + ssize) - sizeof (long);
#else
#error "linux libc or architecture not supported"
#endif
# elif CORO_IRIX
coro_setjmp (ctx->env, 0);
ctx->env[JB_PC] = (__uint64_t)coro_init;
ctx->env[JB_SP] = (__uint64_t)STACK_ADJUST_PTR (sptr, ssize) - sizeof (long);
# elif CORO_ASM
ctx->sp = (void **)(ssize + (char *)sptr);
*--ctx->sp = (void *)abort; /* needed for alignment only */
*--ctx->sp = (void *)coro_init;
#if CORO_WIN_TIB
*--ctx->sp = 0; /* ExceptionList */
*--ctx->sp = (char *)sptr + ssize; /* StackBase */
*--ctx->sp = sptr; /* StackLimit */
#endif
ctx->sp -= NUM_SAVED;
memset (ctx->sp, 0, sizeof (*ctx->sp) * NUM_SAVED);
# elif CORO_UCONTEXT
getcontext (&(ctx->uc));
ctx->uc.uc_link = 0;
ctx->uc.uc_stack.ss_sp = sptr;
ctx->uc.uc_stack.ss_size = (size_t)ssize;
ctx->uc.uc_stack.ss_flags = 0;
makecontext (&(ctx->uc), (void (*)())coro_init, 0);
# endif
coro_transfer (create_coro, new_coro);
}
/*****************************************************************************/
/* pthread backend */
/*****************************************************************************/
#elif CORO_PTHREAD
/* this mutex will be locked by the running coroutine */
pthread_mutex_t coro_mutex = PTHREAD_MUTEX_INITIALIZER;
struct coro_init_args
{
coro_func func;
void *arg;
coro_context *self, *main;
};
static pthread_t null_tid;
/* I'd so love to cast pthread_mutex_unlock to void (*)(void *)... */
static void
mutex_unlock_wrapper (void *arg)
{
pthread_mutex_unlock ((pthread_mutex_t *)arg);
}
static void *
coro_init (void *args_)
{
struct coro_init_args *args = (struct coro_init_args *)args_;
coro_func func = args->func;
void *arg = args->arg;
pthread_mutex_lock (&coro_mutex);
/* we try to be good citizens and use deferred cancellation and cleanup handlers */
pthread_cleanup_push (mutex_unlock_wrapper, &coro_mutex);
coro_transfer (args->self, args->main);
func (arg);
pthread_cleanup_pop (1);
return 0;
}
void
coro_transfer (coro_context *prev, coro_context *next)
{
pthread_cond_signal (&next->cv);
pthread_cond_wait (&prev->cv, &coro_mutex);
#if __FreeBSD__ /* freebsd is of course broken and needs manual testcancel calls... yay... */
pthread_testcancel ();
#endif
}
void
coro_create (coro_context *ctx, coro_func coro, void *arg, void *sptr, size_t ssize)
{
static coro_context nctx;
static int once;
if (!once)
{
once = 1;
pthread_mutex_lock (&coro_mutex);
pthread_cond_init (&nctx.cv, 0);
null_tid = pthread_self ();
}
pthread_cond_init (&ctx->cv, 0);
if (coro)
{
pthread_attr_t attr;
struct coro_init_args args;
args.func = coro;
args.arg = arg;
args.self = ctx;
args.main = &nctx;
pthread_attr_init (&attr);
#if __UCLIBC__
/* exists, but is borked */
/*pthread_attr_setstacksize (&attr, (size_t)ssize);*/
#elif __CYGWIN__
/* POSIX, not here */
pthread_attr_setstacksize (&attr, (size_t)ssize);
#else
pthread_attr_setstack (&attr, sptr, (size_t)ssize);
#endif
pthread_attr_setscope (&attr, PTHREAD_SCOPE_PROCESS);
pthread_create (&ctx->id, &attr, coro_init, &args);
coro_transfer (args.main, args.self);
}
else
ctx->id = null_tid;
}
void
coro_destroy (coro_context *ctx)
{
if (!pthread_equal (ctx->id, null_tid))
{
pthread_cancel (ctx->id);
pthread_mutex_unlock (&coro_mutex);
pthread_join (ctx->id, 0);
pthread_mutex_lock (&coro_mutex);
}
pthread_cond_destroy (&ctx->cv);
}
/*****************************************************************************/
/* fiber backend */
/*****************************************************************************/
#elif CORO_FIBER
#define WIN32_LEAN_AND_MEAN
#if _WIN32_WINNT < 0x0400
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#include <windows.h>
VOID CALLBACK
coro_init (PVOID arg)
{
coro_context *ctx = (coro_context *)arg;
ctx->coro (ctx->arg);
}
void
coro_transfer (coro_context *prev, coro_context *next)
{
if (!prev->fiber)
{
prev->fiber = GetCurrentFiber ();
if (prev->fiber == 0 || prev->fiber == (void *)0x1e00)
prev->fiber = ConvertThreadToFiber (0);
}
SwitchToFiber (next->fiber);
}
void
coro_create (coro_context *ctx, coro_func coro, void *arg, void *sptr, size_t ssize)
{
ctx->fiber = 0;
ctx->coro = coro;
ctx->arg = arg;
if (!coro)
return;
ctx->fiber = CreateFiber (ssize, coro_init, ctx);
}
void
coro_destroy (coro_context *ctx)
{
DeleteFiber (ctx->fiber);
}
#else
#error unsupported backend
#endif
/*****************************************************************************/
/* stack management */
/*****************************************************************************/
#if CORO_STACKALLOC
#include <stdlib.h>
#ifndef _WIN32
# include <unistd.h>
#endif
#if CORO_USE_VALGRIND
# include <valgrind/valgrind.h>
#endif
#if _POSIX_MAPPED_FILES
# include <sys/mman.h>
# define CORO_MMAP 1
# ifndef MAP_ANONYMOUS
# ifdef MAP_ANON
# define MAP_ANONYMOUS MAP_ANON
# else
# undef CORO_MMAP
# endif
# endif
# include <limits.h>
#else
# undef CORO_MMAP
#endif
#if _POSIX_MEMORY_PROTECTION
# ifndef CORO_GUARDPAGES
# define CORO_GUARDPAGES 4
# endif
#else
# undef CORO_GUARDPAGES
#endif
#if !CORO_MMAP
# undef CORO_GUARDPAGES
#endif
#if !__i386 && !__x86_64 && !__powerpc && !__m68k && !__alpha && !__mips && !__sparc64
# undef CORO_GUARDPAGES
#endif
#ifndef CORO_GUARDPAGES
# define CORO_GUARDPAGES 0
#endif
#if !PAGESIZE
#if !CORO_MMAP
#define PAGESIZE 4096
#else
static size_t
coro_pagesize (void)
{
static size_t pagesize;
if (!pagesize)
pagesize = sysconf (_SC_PAGESIZE);
return pagesize;
}
#define PAGESIZE coro_pagesize ()
#endif
#endif
int
coro_stack_alloc (struct coro_stack *stack, unsigned int size)
{
if (!size)
size = 256 * 1024;
stack->sptr = 0;
stack->ssze = ((size_t)size * sizeof (void *) + PAGESIZE - 1) / PAGESIZE * PAGESIZE;
#if CORO_FIBER
stack->sptr = (void *)stack;
return 1;
#else
size_t ssze = stack->ssze + CORO_GUARDPAGES * PAGESIZE;
void *base;
#if CORO_MMAP
int mflags = MAP_PRIVATE | MAP_ANONYMOUS;
#if defined(__OpenBSD__) || defined(__FreeBSD__)
mflags |= MAP_STACK;
#endif
/* mmap supposedly does allocate-on-write for us */
base = mmap (0, ssze, PROT_READ | PROT_WRITE, mflags, -1, 0);
if (base == (void *)-1)
{
return 0;
}
#if CORO_GUARDPAGES
mprotect (base, CORO_GUARDPAGES * PAGESIZE, PROT_NONE);
#endif
base = (void*)((char *)base + CORO_GUARDPAGES * PAGESIZE);
#else
base = malloc (ssze);
if (!base)
return 0;
#endif
#if CORO_USE_VALGRIND
stack->valgrind_id = VALGRIND_STACK_REGISTER ((char *)base, ((char *)base) + ssze - CORO_GUARDPAGES * PAGESIZE);
#endif
stack->sptr = base;
return 1;
#endif
}
void
coro_stack_free (struct coro_stack *stack)
{
#if CORO_FIBER
/* nop */
#else
#if CORO_USE_VALGRIND
VALGRIND_STACK_DEREGISTER (stack->valgrind_id);
#endif
#if CORO_MMAP
if (stack->sptr)
munmap ((void*)((char *)stack->sptr - CORO_GUARDPAGES * PAGESIZE),
stack->ssze + CORO_GUARDPAGES * PAGESIZE);
#else
free (stack->sptr);
#endif
#endif
}
#endif
+420
View File
@@ -0,0 +1,420 @@
/*
* Copyright (c) 2001-2012 Marc Alexander Lehmann <schmorp@schmorp.de>
*
* Redistribution and use in source and binary forms, with or without modifica-
* tion, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
* CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
* CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
* ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License ("GPL") version 2 or any later version,
* in which case the provisions of the GPL are applicable instead of
* the above. If you wish to allow the use of your version of this file
* only under the terms of the GPL and not to allow others to use your
* version of this file under the BSD license, indicate your decision
* by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL. If you do not delete the
* provisions above, a recipient may use your version of this file under
* either the BSD or the GPL.
*
* This library is modelled strictly after Ralf S. Engelschalls article at
* http://www.gnu.org/software/pth/rse-pmt.ps. So most of the credit must
* go to Ralf S. Engelschall <rse@engelschall.com>.
*
* This coroutine library is very much stripped down. You should either
* build your own process abstraction using it or - better - just use GNU
* Portable Threads, http://www.gnu.org/software/pth/.
*
*/
/*
* 2006-10-26 Include stddef.h on OS X to work around one of its bugs.
* Reported by Michael_G_Schwern.
* 2006-11-26 Use _setjmp instead of setjmp on GNU/Linux.
* 2007-04-27 Set unwind frame info if gcc 3+ and ELF is detected.
* Use _setjmp instead of setjmp on _XOPEN_SOURCE >= 600.
* 2007-05-02 Add assembly versions for x86 and amd64 (to avoid reliance
* on SIGUSR2 and sigaltstack in Crossfire).
* 2008-01-21 Disable CFI usage on anything but GNU/Linux.
* 2008-03-02 Switched to 2-clause BSD license with GPL exception.
* 2008-04-04 New (but highly unrecommended) pthreads backend.
* 2008-04-24 Reinstate CORO_LOSER (had wrong stack adjustments).
* 2008-10-30 Support assembly method on x86 with and without frame pointer.
* 2008-11-03 Use a global asm statement for CORO_ASM, idea by pippijn.
* 2008-11-05 Hopefully fix misaligned stacks with CORO_ASM/SETJMP.
* 2008-11-07 rbp wasn't saved in CORO_ASM on x86_64.
* introduce coro_destroy, which is a nop except for pthreads.
* speed up CORO_PTHREAD. Do no longer leak threads either.
* coro_create now allows one to create source coro_contexts.
* do not rely on makecontext passing a void * correctly.
* try harder to get _setjmp/_longjmp.
* major code cleanup/restructuring.
* 2008-11-10 the .cfi hacks are no longer needed.
* 2008-11-16 work around a freebsd pthread bug.
* 2008-11-19 define coro_*jmp symbols for easier porting.
* 2009-06-23 tentative win32-backend support for mingw32 (Yasuhiro Matsumoto).
* 2010-12-03 tentative support for uclibc (which lacks all sorts of things).
* 2011-05-30 set initial callee-saved-registers to zero with CORO_ASM.
* use .cfi_undefined rip on linux-amd64 for better backtraces.
* 2011-06-08 maybe properly implement weird windows amd64 calling conventions.
* 2011-07-03 rely on __GCC_HAVE_DWARF2_CFI_ASM for cfi detection.
* 2011-08-08 cygwin trashes stacks, use pthreads with double stack on cygwin.
* 2012-12-04 reduce misprediction penalty for x86/amd64 assembly switcher.
* 2012-12-05 experimental fiber backend (allocates stack twice).
* 2012-12-07 API version 3 - add coro_stack_alloc/coro_stack_free.
* 2012-12-21 valgrind stack registering was broken.
*/
#ifndef CORO_H
#define CORO_H
#if __cplusplus
extern "C" {
#endif
/*
* This library consists of only three files
* coro.h, coro.c and LICENSE (and optionally README)
*
* It implements what is known as coroutines, in a hopefully
* portable way.
*
* All compiletime symbols must be defined both when including coro.h
* (using libcoro) as well as when compiling coro.c (the implementation).
*
* You can manually specify which flavour you want. If you don't define
* any of these, libcoro tries to choose a safe and fast default:
*
* -DCORO_UCONTEXT
*
* This flavour uses SUSv2's get/set/swap/makecontext functions that
* unfortunately only some unices support, and is quite slow.
*
* -DCORO_SJLJ
*
* This flavour uses SUSv2's setjmp/longjmp and sigaltstack functions to
* do it's job. Coroutine creation is much slower than UCONTEXT, but
* context switching is a bit cheaper. It should work on almost all unices.
*
* -DCORO_LINUX
*
* CORO_SJLJ variant.
* Old GNU/Linux systems (<= glibc-2.1) only work with this implementation
* (it is very fast and therefore recommended over other methods, but
* doesn't work with anything newer).
*
* -DCORO_LOSER
*
* CORO_SJLJ variant.
* Microsoft's highly proprietary platform doesn't support sigaltstack, and
* this selects a suitable workaround for this platform. It might not work
* with your compiler though - it has only been tested with MSVC 6.
*
* -DCORO_FIBER
*
* Slower, but probably more portable variant for the Microsoft operating
* system, using fibers. Ignores the passed stack and allocates it internally.
* Also, due to bugs in cygwin, this does not work with cygwin.
*
* -DCORO_IRIX
*
* CORO_SJLJ variant.
* For SGI's version of Microsoft's NT ;)
*
* -DCORO_ASM
*
* Hand coded assembly, known to work only on a few architectures/ABI:
* GCC + x86/IA32 and amd64/x86_64 + GNU/Linux and a few BSDs. Fastest choice,
* if it works.
*
* -DCORO_PTHREAD
*
* Use the pthread API. You have to provide <pthread.h> and -lpthread.
* This is likely the slowest backend, and it also does not support fork(),
* so avoid it at all costs.
*
* If you define neither of these symbols, coro.h will try to autodetect
* the best/safest model. To help with the autodetection, you should check
* (e.g. using autoconf) and define the following symbols: HAVE_UCONTEXT_H
* / HAVE_SETJMP_H / HAVE_SIGALTSTACK.
*/
/*
* Changes when the API changes incompatibly.
* This is ONLY the API version - there is no ABI compatibility between releases.
*
* Changes in API version 2:
* replaced bogus -DCORO_LOOSE with grammatically more correct -DCORO_LOSER
* Changes in API version 3:
* introduced stack management (CORO_STACKALLOC)
*/
#define CORO_VERSION 3
#include <stddef.h>
/*
* This is the type for the initialization function of a new coroutine.
*/
typedef void (*coro_func)(void *);
/*
* A coroutine state is saved in the following structure. Treat it as an
* opaque type. errno and sigmask might be saved, but don't rely on it,
* implement your own switching primitive if you need that.
*/
typedef struct coro_context coro_context;
/*
* This function creates a new coroutine. Apart from a pointer to an
* uninitialised coro_context, it expects a pointer to the entry function
* and the single pointer value that is given to it as argument.
*
* Allocating/deallocating the stack is your own responsibility.
*
* As a special case, if coro, arg, sptr and ssze are all zero,
* then an "empty" coro_context will be created that is suitable
* as an initial source for coro_transfer.
*
* This function is not reentrant, but putting a mutex around it
* will work.
*/
void coro_create (coro_context *ctx, /* an uninitialised coro_context */
coro_func coro, /* the coroutine code to be executed */
void *arg, /* a single pointer passed to the coro */
void *sptr, /* start of stack area */
size_t ssze); /* size of stack area in bytes */
/*
* The following prototype defines the coroutine switching function. It is
* sometimes implemented as a macro, so watch out.
*
* This function is thread-safe and reentrant.
*/
#if 0
void coro_transfer (coro_context *prev, coro_context *next);
#endif
/*
* The following prototype defines the coroutine destroy function. It
* is sometimes implemented as a macro, so watch out. It also serves no
* purpose unless you want to use the CORO_PTHREAD backend, where it is
* used to clean up the thread. You are responsible for freeing the stack
* and the context itself.
*
* This function is thread-safe and reentrant.
*/
#if 0
void coro_destroy (coro_context *ctx);
#endif
/*****************************************************************************/
/* optional stack management */
/*****************************************************************************/
/*
* You can disable all of the stack management functions by
* defining CORO_STACKALLOC to 0. Otherwise, they are enabled by default.
*
* If stack management is enabled, you can influence the implementation via these
* symbols:
*
* -DCORO_USE_VALGRIND
*
* If defined, then libcoro will include valgrind/valgrind.h and register
* and unregister stacks with valgrind.
*
* -DCORO_GUARDPAGES=n
*
* libcoro will try to use the specified number of guard pages to protect against
* stack overflow. If n is 0, then the feature will be disabled. If it isn't
* defined, then libcoro will choose a suitable default. If guardpages are not
* supported on the platform, then the feature will be silently disabled.
*/
#ifndef CORO_STACKALLOC
# define CORO_STACKALLOC 1
#endif
#if CORO_STACKALLOC
/*
* The only allowed operations on these struct members is to read the
* "sptr" and "ssze" members to pass it to coro_create, to read the "sptr"
* member to see if it is false, in which case the stack isn't allocated,
* and to set the "sptr" member to 0, to indicate to coro_stack_free to
* not actually do anything.
*/
struct coro_stack
{
void *sptr;
size_t ssze;
#if CORO_USE_VALGRIND
int valgrind_id;
#endif
};
/*
* Try to allocate a stack of at least the given size and return true if
* successful, or false otherwise.
*
* The size is *NOT* specified in bytes, but in units of sizeof (void *),
* i.e. the stack is typically 4(8) times larger on 32 bit(64 bit) platforms
* then the size passed in.
*
* If size is 0, then a "suitable" stack size is chosen (usually 1-2MB).
*/
int coro_stack_alloc (struct coro_stack *stack, unsigned int size);
/*
* Free the stack allocated by coro_stack_alloc again. It is safe to
* call this function on the coro_stack structure even if coro_stack_alloc
* failed.
*/
void coro_stack_free (struct coro_stack *stack);
#endif
/*
* That was it. No other user-serviceable parts below here.
*/
/*****************************************************************************/
#if !defined CORO_LOSER && !defined CORO_UCONTEXT \
&& !defined CORO_SJLJ && !defined CORO_LINUX \
&& !defined CORO_IRIX && !defined CORO_ASM \
&& !defined CORO_PTHREAD && !defined CORO_FIBER
# if defined WINDOWS && (defined __i386 || (__x86_64 || defined _M_IX86 || defined _M_AMD64)
# define CORO_ASM 1
# elif defined WINDOWS || defined _WIN32
# define CORO_LOSER 1 /* you don't win with windoze */
# elif __linux && (__i386 || (__x86_64 && !__ILP32))
# define CORO_ASM 1
# elif __APPLE__ && (__i386 || (__x86_64 && !__ILP32))
# define CORO_ASM 1
# elif defined HAVE_UCONTEXT_H
# define CORO_UCONTEXT 1
# elif defined HAVE_SETJMP_H && defined HAVE_SIGALTSTACK
# define CORO_SJLJ 1
# else
error unknown or unsupported architecture
# endif
#endif
/*****************************************************************************/
#if CORO_UCONTEXT
# include <ucontext.h>
struct coro_context
{
ucontext_t uc;
};
# define coro_transfer(p,n) swapcontext (&((p)->uc), &((n)->uc))
# define coro_destroy(ctx) (void *)(ctx)
#elif CORO_SJLJ || CORO_LOSER || CORO_LINUX || CORO_IRIX
# if defined(CORO_LINUX) && !defined(_GNU_SOURCE)
# define _GNU_SOURCE /* for glibc */
# endif
# if !CORO_LOSER
# include <unistd.h>
# endif
/* solaris is hopelessly borked, it expands _XOPEN_UNIX to nothing */
# if __sun
# undef _XOPEN_UNIX
# define _XOPEN_UNIX 1
# endif
# include <setjmp.h>
# if _XOPEN_UNIX > 0 || defined (_setjmp)
# define coro_jmp_buf jmp_buf
# define coro_setjmp(env) _setjmp (env)
# define coro_longjmp(env) _longjmp ((env), 1)
# elif CORO_LOSER
# define coro_jmp_buf jmp_buf
# define coro_setjmp(env) setjmp (env)
# define coro_longjmp(env) longjmp ((env), 1)
# else
# define coro_jmp_buf sigjmp_buf
# define coro_setjmp(env) sigsetjmp (env, 0)
# define coro_longjmp(env) siglongjmp ((env), 1)
# endif
struct coro_context
{
coro_jmp_buf env;
};
# define coro_transfer(p,n) do { if (!coro_setjmp ((p)->env)) coro_longjmp ((n)->env); } while (0)
# define coro_destroy(ctx) (void *)(ctx)
#elif CORO_ASM
struct coro_context
{
void **sp; /* must be at offset 0 */
};
void __attribute__ ((__noinline__, __regparm__(2)))
coro_transfer (coro_context *prev, coro_context *next);
# define coro_destroy(ctx) (void *)(ctx)
#elif CORO_PTHREAD
# include <pthread.h>
extern pthread_mutex_t coro_mutex;
struct coro_context
{
pthread_cond_t cv;
pthread_t id;
};
void coro_transfer (coro_context *prev, coro_context *next);
void coro_destroy (coro_context *ctx);
#elif CORO_FIBER
struct coro_context
{
void *fiber;
/* only used for initialisation */
coro_func coro;
void *arg;
};
void coro_transfer (coro_context *prev, coro_context *next);
void coro_destroy (coro_context *ctx);
#endif
#if __cplusplus
}
#endif
#endif
+14
View File
@@ -0,0 +1,14 @@
// These macros weren't added until v8 version 4.4
#ifndef V8_MAJOR_VERSION
#if NODE_MODULE_VERSION <= 11
#define V8_MAJOR_VERSION 3
#define V8_MINOR_VERSION 14
#elif V8_MAJOR_VERSION <= 14
#define V8_MAJOR_VERSION 3
#define V8_MINOR_VERSION 28
#else
#error v8 version macros missing
#endif
#endif
#define V8_AT_LEAST(major, minor) (V8_MAJOR_VERSION > major || (V8_MAJOR_VERSION == major && V8_MINOR_VERSION >= minor))
Generated Vendored Executable
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env node
var fs = require('fs');
var spawn = require('child_process').spawn;
var path = require('path');
var ret = 0;
function runTest(test, cb) {
var env = {};
for (var ii in process.env) {
env[ii] = process.env[ii];
}
env.NODE_PATH = __dirname;
var args = [];
if (process.versions.modules >= 57 && process.versions.modules < 59) {
// Node v8 requires forcing async hook checks. In Node v9 (>=59) and beyond,
// async hooks checks are on by default (and the param no longer exists).
args.push('--force-async-hooks-checks');
}
args.push(path.join('test', test));
var proc = spawn(process.execPath, args, { env: env });
proc.stdout.setEncoding('utf8');
proc.stderr.setEncoding('utf8');
var stdout = '', stderr = '';
proc.stdout.on('data', function(data) {
stdout += data;
});
proc.stderr.on('data', function(data) {
stderr += data;
});
proc.stdin.end();
proc.on('exit', function(code) {
if (stdout !== 'pass\n' || stderr !== '') {
ret = 1;
console.error(
test+ ': *fail*\n'+
'code: '+ code+ '\n'+
'stderr: '+ stderr+ '\n'+
'stdout: '+ stdout
);
} else if (code !== 0) {
ret = 1;
console.error(test+ ': fail ('+ code+ ')');
} else {
console.log(test+ ': '+ 'pass');
}
cb();
});
}
var cb = function() {
process.exit(ret);
};
fs.readdirSync('./test').reverse().forEach(function(file) {
cb = new function(cb) {
return function(err) {
if (err) return cb(err);
runTest(file, cb);
};
}(cb);
});
cb();
+12
View File
@@ -0,0 +1,12 @@
// gh-8
var Fiber = require('fibers');
try {
Fiber(function() {
var that = Fiber.current;
Fiber(function(){
that.run();
}).run();
}).run();
} catch(err) {
console.log('pass');
}
+38
View File
@@ -0,0 +1,38 @@
'use strict';
// This test must be run with --force-async-hooks-checks
if (process.versions.modules < 57) {
console.log('pass');
return;
}
const { AsyncResource } = require('async_hooks');
const Fiber = require('fibers');
class TestResource extends AsyncResource {
constructor() {
super('TestResource');
}
run(cb) {
// In the v8 API, only emitBefore() and emitAfter() are available
if (process.versions.modules < 59) {
this.emitBefore();
cb();
this.emitAfter();
} else {
// In v9 and higher, emitBefore() and emitAfter() are deperecated in favor of runInAsyncScope().
this.runInAsyncScope(cb);
}
}
}
let tmp = Fiber(function() {
let resource = new TestResource;
resource.run(function() {
Fiber.yield();
});
});
tmp.run();
setTimeout(function() {
tmp.run();
console.log('pass');
}, 5);
+7
View File
@@ -0,0 +1,7 @@
var Fiber = require('fibers');
try {
Fiber.prototype.run.call(null);
} catch (err) {
console.log('pass');
}
+27
View File
@@ -0,0 +1,27 @@
// gh-20
var Fiber = require('fibers');
function main() {
var proc = require('child_process').spawn(
process.execPath,
[process.argv[1], 'child'],
{env: process.env}
);
function ondata(data) {
process.stdout.write(data+ '');
}
proc.stdout.on('data', ondata);
proc.stderr.on('data', ondata);
}
function child() {
var fn = Fiber(function() {
Fiber.yield('pa');
return 'ss';
});
var r1 = fn.run();
var r2 = fn.run();
console.log(r1+ r2);
}
process.argv[2] === 'child' ? child() : main();
+49
View File
@@ -0,0 +1,49 @@
"use strict";
var Fiber = require('fibers');
Fiber.poolSize = 100;
let v8 = /^([0-9]+)\.([0-9]+)/.exec(process.versions.v8);
if (v8[1] > 4 || (v8[1] == 4 && v8[2] >= 10)) {
// Vague benchmark of fiber performance, lower is better
function bench() {
var d = new Date;
for (var ii = 0; ii < 100; ++ii) {
var fibers = [];
for (var jj = 0; jj < Fiber.poolSize; ++jj) {
var fiber = Fiber(function() {
Fiber.yield();
});
fiber.run();
fibers.push(fiber);
}
fibers.map(function(fiber) {
fiber.run();
});
}
return new Date - d;
}
// Run initial benchmark
var ts1 = Math.min(bench(), bench());
// Dirty up isolate list
var fibers = [];
for (var ii = 0; ii < Fiber.poolSize + 1000; ++ii) {
let fiber = Fiber(function() {
Fiber.yield();
});
fiber.run();
fibers.push(fiber);
}
fibers.map(function(fiber) {
fiber.run();
});
// Test again
var ts2 = Math.min(bench(), bench());
console.log(ts1 * 2 < ts2 ? 'fail' : 'pass');
} else {
// Feature is not supported
console.log('pass');
}
+13
View File
@@ -0,0 +1,13 @@
var Fiber = require('fibers');
var current;
Fiber(function() {
current = Fiber.current;
Fiber.yield();
console.log('pass');
}).run();
if (current) {
current.run();
} else {
console.log('fail');
}
+14
View File
@@ -0,0 +1,14 @@
// gh-1
var Fiber = require('fibers');
if (process.platform == 'win32') {
// There is a problem with running this from a script. Not fibers related.
console.log('pass');
} else {
Fiber(function() {
require('child_process').exec('echo pass', function(err, stdout) {
if (err) console.log(err);
process.stdout.write(stdout);
});
}).run();
}
+15
View File
@@ -0,0 +1,15 @@
var Fiber = require('fibers');
if (!process.stdout.write('pass\n')) {
process.stdout.on('drain', go);
} else {
go();
}
function go() {
// Windows needs some time to flush the output and I can't figure out a better way
setTimeout(function() {
Fiber(function() {
process.exit();
}).run();
console.log('fail');
}, 10);
}
+24
View File
@@ -0,0 +1,24 @@
var Fiber = require('fibers');
function Fibonacci() {
return Fiber.prototype.run.bind(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;
}
}));
}
var seq = Fibonacci(), results = [];
for (var ii = seq(); ii <= 1597; ii = seq()) {
results.push(ii);
}
if (results+ '' !== '0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597') {
throw new Error;
}
console.log('pass');
+8
View File
@@ -0,0 +1,8 @@
// gh-16
var Fiber = require('fibers');
Fiber(function() {
Fiber(function() {
Fiber(function() {}).run();
}).run();
}).run();
console.log('pass');
+52
View File
@@ -0,0 +1,52 @@
var Fiber = require('fibers');
var Future = require('future');
// Possible outputs:
// pass: exception is thrown and caught in uncaughtException
// fail: exception is thrown and not caught
// no output: process dies
var thrown = false;
var caught = false;
var async = function(continuation) {
process.nextTick(function() {
continuation();
});
}
process.on('uncaughtException', function(err) {
if (err.message === 'Catch me if you can') {
caught = true;
} else {
throw err;
}
});
// This fiber's job is to throw an exception after yielding.
Fiber(function() {
// yield and resume via Future.wait() and its cb() helper
var sync = Future.wrap(async)();
sync.wait();
// this should get rethrown to the main event loop
thrown = true;
throw new Error('Catch me if you can');
}).run();
// This fiber's job is to make sure the process is still alive after the
// exception was thrown.
Fiber(function() {
// wait for other fiber to throw exception and yield
while (!thrown) {
var sync = Future.wrap(async)();
sync.wait();
}
// wait once more to allow exception to get caught
process.nextTick(function() {
// see if we have noticed the exception we expect to
console.log(caught ? 'pass' : 'fail');
});
}).run();
+26
View File
@@ -0,0 +1,26 @@
var Future;
try {
Future = require('fibers/future');
} catch (err) {
Future = require('future');
}
function Timer(ms) {
var future = new Future;
function ret() {
future.return();
}
ms ? setTimeout(ret, ms) : process.nextTick(ret);
return future;
}
~function() {
var timer = new Timer(10), tick = new Timer;
Future.wait(timer, tick);
timer.get();
tick.get();
return 'pass';
}.future()().resolve(function(err, val) {
if (err) throw err;
console.log(val);
});
+7
View File
@@ -0,0 +1,7 @@
// gh-3
var Fiber = require('fibers');
try {
Fiber.yield();
} catch(err) {
console.log('pass');
}
+16
View File
@@ -0,0 +1,16 @@
var Fiber = require('fibers');
for (var jj = 0; jj < 10; ++jj) {
var fibers = [];
for (var ii = 0; ii < 200; ++ii) {
var fn = Fiber(function() {
Fiber.yield();
});
fn.run();
fibers.push(fn);
}
for (var ii = 0; ii < fibers.length; ++ii) {
fibers[ii].run();
}
}
console.log('pass');
+8
View File
@@ -0,0 +1,8 @@
// gh-10
var Fiber = require('fibers');
var title = process.title;
Fiber(function() {
process.title = 'pass';
}).run();
console.log(process.title === 'pass' || process.title === title ? 'pass' : 'fail');
+12
View File
@@ -0,0 +1,12 @@
var Fiber = require('fibers');
try {
Fiber(function() {
function foo() {
var hello = Math.random();
foo();
}
foo();
}).run();
} catch (err) {
err.name === 'RangeError' && console.log('pass');
}
Generated Vendored Executable
+45
View File
@@ -0,0 +1,45 @@
var Fiber = require('fibers');
// Calculate how far we can go recurse without hitting the JS stack limit
function calculateStackSpace() {
var max = 0;
function testRecursion(ii) {
++max;
testRecursion(ii + 1);
}
try {
testRecursion();
} catch (err) {}
return max;
}
// Invoke a RepExp operation that eats a lot of stack space
function pathologicRegExp(preStack) {
function fn() {
var foo = '';
for (var ii = 0; ii < 1024; ++ii) {
foo += 'a';
}
new RegExp(foo, 'g');
}
// Recurse to the limit and then invoke a stack-heavy C++ operation
function wasteStack(ii) {
ii ? wasteStack(ii - 1) : fn();
}
wasteStack(preStack);
}
Fiber(function() {
// Ensure that this doesn't ruin everything while in a fiber
var max = calculateStackSpace();
for (var stack = max; stack > 0; --stack) {
try {
pathologicRegExp(stack);
break;
} catch (err) {}
}
}).run();
console.log('pass');
+8
View File
@@ -0,0 +1,8 @@
// gh-12
var Fiber = require('fibers');
Fiber(function() {
if (!Fiber.current.started) {
throw new Error;
}
}).run();
console.log('pass');
+14
View File
@@ -0,0 +1,14 @@
var Fiber = require('fibers');
var ii;
var fn = Fiber(function() {
for (ii = 0; ii < 1000; ++ii) {
try {
Fiber.yield();
} catch (err) {}
}
});
fn.run();
fn.reset();
ii === 1000 && console.log('pass');