├── .gitignore
├── .npmignore
├── .npmrc
├── README.md
├── binding.gyp
├── lib
├── Pool.js
├── Thread.js
├── index.js
├── preload.js
└── util.js
├── package.json
├── src
├── NativeUtil.cc
├── NativeUtil.h
├── Worker.cc
├── Worker.h
├── threads.cc
└── util.h
├── test
├── buffer.js
├── catch.js
├── console.js
├── dlopen.js
├── error.js
├── locks.js
├── native.cc
├── performance.js
├── pi.js
├── references.js
└── sending.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | preload.build.js
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | webpack.config.js
2 | lib/preload.js
3 | lib/util-format.js
4 | build
5 | README.md
6 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![npm][download-badge]][npm]
2 | [![David][dep-badge]][dep-link]
3 |
4 | [![NPM][large-badge]][stats-link]
5 |
6 | # threads [![Version Badge][version-badge]][npm]
7 |
8 | These are real threads that don't crash **and** run your js without exploding.
9 |
10 | ```javascript
11 | const Thread = require('threads');
12 | const assert = require('assert');
13 |
14 | const t = new Thread((a, b) => a + b, 1, 2);
15 |
16 | t.join().then((r) => {
17 | assert(r === 3);
18 | });
19 | ```
20 |
21 | TODO:
22 |
23 | - [X] Console in thread
24 | - [X] Buffer in thread (freoss/buffer)
25 | - [ ] Allow passing "references" instead of copies
26 |
27 |
28 | ---
29 |
30 |
31 | # Docs
32 |
33 |
34 |
35 | ## Thread
36 | **Kind**: global class
37 |
38 | * [Thread](#Thread)
39 | * [new Thread(fn, props, references)](#new_Thread_new)
40 | * _instance_
41 | * [.send(value)](#Thread+send)
42 | * [.join()](#Thread+join) ⇒ Promise.<\*>
43 | * [.terminate()](#Thread+terminate)
44 | * [.lock()](#Thread+lock) ⇒ boolean
45 | * [.unlock()](#Thread+unlock)
46 | * _inner_
47 | * [~Context](#Thread..Context) : Object
48 | * [~fnCallback](#Thread..fnCallback) : function
49 |
50 |
51 |
52 | ### new Thread(fn, props, references)
53 | Create a thread
54 |
55 |
56 | | Param | Type | Description |
57 | | --- | --- | --- |
58 | | fn | [fnCallback
](#Thread..fnCallback) | Function that will run in a new thread |
59 | | props | Array.<\*>
| Values to pass to the thread callback |
60 | | references | Object
| References to functions |
61 |
62 |
63 |
64 | ### thread.send(value)
65 | Send a value to the thread
66 |
67 | **Kind**: instance method of [Thread
](#Thread)
68 |
69 | | Param | Type | Description |
70 | | --- | --- | --- |
71 | | value | \*
| Value to send |
72 |
73 |
74 |
75 | ### thread.join() ⇒ Promise.<\*>
76 | Return a promise that resolves when the thread finishes with the return value
77 | or rejects when there is an execution error in the thread.
78 |
79 | **Kind**: instance method of [Thread
](#Thread)
80 |
81 |
82 | ### thread.terminate()
83 | Terminate the thread
84 |
85 | **Kind**: instance method of [Thread
](#Thread)
86 |
87 |
88 | ### thread.lock() ⇒ boolean
89 | Lock the thread's context's mutex, analogous to std::mutex::try_lock
90 |
91 | **Kind**: instance method of [Thread
](#Thread)
92 | **Returns**: boolean
- If the lock was successfully obtained
93 |
94 |
95 | ### thread.unlock()
96 | Unlock the thread context's mutex, analogous to std::mutex::unlock
97 |
98 | **Kind**: instance method of [Thread
](#Thread)
99 |
100 |
101 | ### Thread~Context : Object
102 | **Kind**: inner typedef of [Thread
](#Thread)
103 | **Properties**
104 |
105 | | Name | Type |
106 | | --- | --- |
107 | | on | function
|
108 | | send | function
|
109 | | terminate | function
|
110 | | lock | function
|
111 | | unlock | function
|
112 |
113 |
114 |
115 | ### Thread~fnCallback : function
116 | **Kind**: inner typedef of [Thread
](#Thread)
117 |
118 | | Param | Type | Description |
119 | | --- | --- | --- |
120 | | ...args | args
| Arguments from the [Thread](#Thread) constructor |
121 | | context | [Context
](#Thread..Context) | |
122 |
123 | [npm]: https://npmjs.org/package/@snek/threads
124 | [large-badge]: https://nodei.co/npm/@snek/threads.png?downloads=true&downloadRank=true&stars=true
125 | [stats-link]: https://nodei.co/npm/@snek/threads/
126 | [version-badge]: https://versionbadge.now.sh/npm/@snek/threads.svg
127 | [download-badge]: https://img.shields.io/npm/dt/@snek/threads.svg?maxAge=3600
128 | [dep-badge]: https://david-dm.org/devsnek/threads.svg
129 | [dep-link]: https://david-dm.org/devsnek/threads
130 |
--------------------------------------------------------------------------------
/binding.gyp:
--------------------------------------------------------------------------------
1 | {
2 | 'target_defaults': {
3 | 'default_configuration': 'Release',
4 | 'configurations': {
5 | 'Release': {
6 | 'xcode_settings': {
7 | 'GCC_OPTIMIZATION_LEVEL': '3',
8 | 'GCC_GENERATE_DEBUGGING_SYMBOLS': 'NO',
9 | },
10 | 'msvs_settings': {
11 | 'VCCLCompilerTool': {
12 | 'Optimization': 3,
13 | 'FavorSizeOrSpeed': 1,
14 | },
15 | },
16 | },
17 | 'Debug': {
18 | 'xcode_settings': {
19 | 'GCC_OPTIMIZATION_LEVEL': '0',
20 | 'GCC_GENERATE_DEBUGGING_SYMBOLS': 'YES',
21 | },
22 | 'msvs_settings': {
23 | 'VCCLCompilerTool': {
24 | 'Optimization': 0,
25 | 'FavorSizeOrSpeed': 0,
26 | },
27 | },
28 | },
29 | },
30 | },
31 | 'targets': [
32 | {
33 | 'target_name': 'threads',
34 | 'cflags_cc': [ '-std=c++14' ],
35 | 'cflags_cc!': [ '-fno-exceptions', '-fno-rtti' ],
36 | 'xcode_settings': {
37 | 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
38 | 'GCC_ENABLE_CPP_RTTI': 'YES',
39 | 'CLANG_CXX_LANGUAGE_STANDARD': 'c++14',
40 | },
41 | 'msvs_settings': {
42 | 'VCCLCompilerTool': {
43 | 'ExceptionHandling': '1',
44 | 'RuntimeTypeInfo': 'true',
45 | 'AdditionalOptions': [ '/GR' ],
46 | },
47 | },
48 | 'msvs_disabled_warnings': [ 4068 ], # Unknown pragma
49 | 'conditions': [
50 | [ 'OS == "win"',
51 | { 'defines': [ 'NOMINMAX' ] },
52 | ],
53 | ],
54 | 'sources': [
55 | 'src/NativeUtil.cc',
56 | 'src/Worker.cc',
57 | 'src/threads.cc',
58 | ],
59 | },
60 | ],
61 | }
62 |
--------------------------------------------------------------------------------
/lib/Pool.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Thread = require('.');
4 |
5 | const kFn = Symbol('fn');
6 |
7 | class Pool {
8 | constructor(fn) {
9 | this[kFn] = fn;
10 | }
11 |
12 | run(count, args) {
13 | return Array.from({ length: count }, () => new Thread(this[kFn], ...args).join());
14 | }
15 | }
16 |
17 | module.exports = Pool;
18 |
--------------------------------------------------------------------------------
/lib/Thread.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { Worker, constants, setPreload } = require('bindings')('threads');
4 | const fs = require('fs');
5 | const path = require('path');
6 | const EventEmitter = require('events');
7 | const util = require('util');
8 |
9 | setPreload(fs.readFileSync(path.resolve(__dirname, '..', 'preload.build.js')).toString());
10 |
11 | const kWorker = Symbol('kWorker');
12 | const stateKeys = Object.keys(constants);
13 |
14 | /**
15 | * @typedef {Object} Thread~Context
16 | * @prop {Function} on
17 | * @prop {Function} send
18 | * @prop {Function} terminate
19 | * @prop {Function} lock
20 | * @prop {Function} unlock
21 | */
22 |
23 | /**
24 | * @callback Thread~fnCallback
25 | * @param {...args} args Arguments from the {@link Thread} constructor
26 | * @param {Thread~Context} context
27 | */
28 |
29 | class Thread extends EventEmitter {
30 | /**
31 | * Create a thread
32 | * @param {Thread~fnCallback} fn Function that will run in a new thread
33 | * @param {Array<*>} props Values to pass to the thread callback
34 | * @param {Object} references References to functions
35 | */
36 | constructor(fn, props = [], references = {}) {
37 | super();
38 |
39 | if (typeof props === 'object' && !Array.isArray(props)) {
40 | references = props;
41 | props = [];
42 | }
43 |
44 | if (!Array.isArray(props))
45 | throw new TypeError('props must be an array');
46 |
47 |
48 | for (const key of Object.keys(references)) {
49 | const v = references[key];
50 | if (!v || (typeof v !== 'object' && typeof v !== 'function'))
51 | throw new TypeError('references must be functions or objects');
52 | }
53 |
54 | const worker = new Worker(`(${fn})`, props, references);
55 |
56 | Object.defineProperties(this, {
57 | [kWorker]: {
58 | value: worker,
59 | writable: false,
60 | enumerable: false,
61 | configurable: false,
62 | },
63 | id: {
64 | value: worker.getId(),
65 | writable: false,
66 | enumerable: true,
67 | configurable: false,
68 | },
69 | });
70 |
71 | const messageBuffer = [false, undefined];
72 | const tickHandle = () => {
73 | worker.checkOutgoingMessages(messageBuffer);
74 | if (messageBuffer[0] === true) {
75 | this.emit('message', messageBuffer[1]);
76 | messageBuffer[0] = false;
77 | }
78 |
79 | if (worker.getState() === constants.running)
80 | process.nextTick(tickHandle);
81 | };
82 | this.on('newListener', (event) => {
83 | if (event !== 'message')
84 | return;
85 |
86 | process.nextTick(tickHandle);
87 | });
88 | }
89 |
90 | get state() {
91 | return stateKeys[this[kWorker].getState()];
92 | }
93 |
94 | /**
95 | * Send a value to the thread
96 | * @param {*} value Value to send
97 | */
98 | send(value) {
99 | this[kWorker].send(value);
100 | }
101 |
102 | /**
103 | * Return a promise that resolves when the thread finishes with the return value
104 | * or rejects when there is an execution error in the thread.
105 | * @returns {Promise<*>}
106 | */
107 | join() {
108 | return this[kWorker].getPromise();
109 | }
110 |
111 | /**
112 | * Terminate the thread
113 | */
114 | terminate() {
115 | this[kWorker].terminate();
116 | }
117 |
118 | /**
119 | * Lock the thread's context's mutex, analogous to std::mutex::try_lock
120 | * @returns {boolean} If the lock was successfully obtained
121 | */
122 | lock() {
123 | return this[kWorker].lock();
124 | }
125 |
126 | /**
127 | * Unlock the thread context's mutex, analogous to std::mutex::unlock
128 | */
129 | unlock() {
130 | this[kWorker].unlock();
131 | }
132 |
133 | [util.inspect.custom]() {
134 | return `Thread ${this.id.toString().padStart(2, 0)} { ${this.state} }`;
135 | }
136 | }
137 |
138 | module.exports = Thread;
139 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Thread = require('./Thread');
4 | const Pool = require('./Pool');
5 |
6 | Thread.Pool = Pool;
7 |
8 | module.exports = Thread;
9 |
--------------------------------------------------------------------------------
/lib/preload.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | global.Buffer = require('../node_modules/buffer/index.js').Buffer;
4 |
5 | {
6 | const _console = global._console;
7 | delete global._console;
8 |
9 | const { formatWithOptions } = require('./util');
10 |
11 | global.console = {};
12 | const methods = ['log', 'debug', 'info', 'warn', 'error'];
13 | for (const method of methods)
14 | global.console[method] = (...args) => _console(method, formatWithOptions({ colors: true }, ...args));
15 | }
16 |
17 | {
18 | const _hrtime = global.performance._hrtime;
19 | delete global.performance._hrtime;
20 | const hrValues = new Uint32Array(3);
21 |
22 | global.performance.now = () => {
23 | _hrtime(hrValues);
24 | return (((hrValues[0] * 0x100000000) + hrValues[1]) * 1000) + (hrValues[2] / 1e6);
25 | };
26 | }
27 |
28 | /*
29 | {
30 | const dlopen = global._util.dlopen;
31 | global.dlopen = (path) => {
32 | const module = { exports: {} };
33 | dlopen(module, path);
34 | return module.exports;
35 | };
36 | }
37 | */
38 |
39 | delete global._util;
40 |
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const {
4 | getPromiseDetails,
5 | kPending,
6 | kRejected,
7 | } = global._util;
8 |
9 | const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
10 | const regExpToString = RegExp.prototype.toString;
11 | const dateToISOString = Date.prototype.toISOString;
12 | const errorToString = Error.prototype.toString;
13 |
14 | /* eslint-disable */
15 | const strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c]/;
16 | const strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c]/g;
17 | /* eslint-enable */
18 | const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
19 | const colorRegExp = /\u001b\[\d\d?m/g;
20 | const numberRegExp = /^(0|[1-9][0-9]*)$/;
21 |
22 | const readableRegExps = {};
23 |
24 | const MIN_LINE_LENGTH = 16;
25 |
26 | // Escaped special characters. Use empty strings to fill up unused entries.
27 | const meta = [
28 | '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004',
29 | '\\u0005', '\\u0006', '\\u0007', '\\b', '\\t',
30 | '\\n', '\\u000b', '\\f', '\\r', '\\u000e',
31 | '\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013',
32 | '\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018',
33 | '\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d',
34 | '\\u001e', '\\u001f', '', '', '',
35 | '', '', '', '', "\\'", '', '', '', '', '',
36 | '', '', '', '', '', '', '', '', '', '',
37 | '', '', '', '', '', '', '', '', '', '',
38 | '', '', '', '', '', '', '', '', '', '',
39 | '', '', '', '', '', '', '', '', '', '',
40 | '', '', '', '', '', '', '', '\\\\',
41 | ];
42 |
43 | const escapeFn = (str) => meta[str.charCodeAt(0)];
44 |
45 | // Escape control characters, single quotes and the backslash.
46 | // This is similar to JSON stringify escaping.
47 | function strEscape(str) {
48 | // Some magic numbers that worked out fine while benchmarking with v8 6.0
49 | if (str.length < 5000 && !strEscapeSequencesRegExp.test(str))
50 | return `'${str}'`;
51 | if (str.length > 100)
52 | return `'${str.replace(strEscapeSequencesReplacer, escapeFn)}'`;
53 | var result = '';
54 | var last = 0;
55 | for (var i = 0; i < str.length; i++) {
56 | const point = str.charCodeAt(i);
57 | if (point === 39 || point === 92 || point < 32) {
58 | if (last === i)
59 | result += meta[point];
60 | else
61 | result += `${str.slice(last, i)}${meta[point]}`;
62 |
63 | last = i + 1;
64 | }
65 | }
66 | if (last === 0)
67 | result = str;
68 | else if (last !== i)
69 | result += str.slice(last);
70 |
71 | return `'${result}'`;
72 | }
73 |
74 | const inspectDefaultOptions = Object.seal({
75 | showHidden: false,
76 | depth: 2,
77 | colors: false,
78 | showProxy: false,
79 | maxArrayLength: 100,
80 | breakLength: 60,
81 | compact: true,
82 | });
83 |
84 | /* eslint-disable prefer-rest-params */
85 |
86 | const emptyOptions = {};
87 | function format(...args) {
88 | return formatWithOptions(emptyOptions, ...args);
89 | }
90 |
91 | function formatWithOptions(inspectOptions, f) {
92 | var i, tempStr;
93 | if (typeof f !== 'string') {
94 | if (arguments.length === 1)
95 | return '';
96 | var res = '';
97 | for (i = 1; i < arguments.length - 1; i++) {
98 | res += inspect(arguments[i], inspectOptions);
99 | res += ' ';
100 | }
101 | res += inspect(arguments[i], inspectOptions);
102 | return res;
103 | }
104 |
105 | if (arguments.length === 2)
106 | return f;
107 |
108 | var str = '';
109 | var a = 2;
110 | var lastPos = 0;
111 | for (i = 0; i < f.length - 1; i++) {
112 | if (f.charCodeAt(i) === 37) { // '%'
113 | const nextChar = f.charCodeAt(++i);
114 | if (a !== arguments.length) {
115 | switch (nextChar) {
116 | case 115: // 's'
117 | tempStr = String(arguments[a++]);
118 | break;
119 | case 106: // 'j'
120 | tempStr = tryStringify(arguments[a++]);
121 | break;
122 | case 100: // 'd'
123 | tempStr = `${Number(arguments[a++])}`;
124 | break;
125 | case 79: // 'O'
126 | tempStr = inspect(arguments[a++], inspectOptions);
127 | break;
128 | case 111: { // 'o'
129 | const opts = Object.assign({}, inspectOptions, {
130 | showHidden: true,
131 | showProxy: true,
132 | });
133 | tempStr = inspect(arguments[a++], opts);
134 | break;
135 | }
136 | case 105: // 'i'
137 | tempStr = `${parseInt(arguments[a++])}`;
138 | break;
139 | case 102: // 'f'
140 | tempStr = `${parseFloat(arguments[a++])}`;
141 | break;
142 | case 37: // '%'
143 | str += f.slice(lastPos, i);
144 | lastPos = i + 1;
145 | continue;
146 | default: // any other character is not a correct placeholder
147 | continue;
148 | }
149 | if (lastPos !== i - 1)
150 | str += f.slice(lastPos, i - 1);
151 | str += tempStr;
152 | lastPos = i + 1;
153 | } else if (nextChar === 37) {
154 | str += f.slice(lastPos, i);
155 | lastPos = i + 1;
156 | }
157 | }
158 | }
159 | if (lastPos === 0)
160 | str = f;
161 | else if (lastPos < f.length)
162 | str += f.slice(lastPos);
163 | while (a < arguments.length) {
164 | const x = arguments[a++];
165 | if ((typeof x !== 'object' && typeof x !== 'symbol') || x === null)
166 | str += ` ${x}`;
167 | else
168 | str += ` ${inspect(x, inspectOptions)}`;
169 | }
170 | return str;
171 | }
172 |
173 | var CIRCULAR_ERROR_MESSAGE;
174 |
175 | function tryStringify(arg) {
176 | try {
177 | return JSON.stringify(arg);
178 | } catch (err) {
179 | // Populate the circular error message lazily
180 | if (!CIRCULAR_ERROR_MESSAGE) {
181 | try {
182 | const a = {};
183 | a.a = a;
184 | JSON.stringify(a);
185 | } catch (e) {
186 | CIRCULAR_ERROR_MESSAGE = e.message;
187 | }
188 | }
189 | if (err.name === 'TypeError' && err.message === CIRCULAR_ERROR_MESSAGE)
190 | return '[Circular]';
191 | throw err;
192 | }
193 | }
194 |
195 | module.exports = { format, formatWithOptions, inspect };
196 |
197 | function inspect(value, opts, arg2, arg3) {
198 | // Default options
199 | const ctx = {
200 | seen: [],
201 | stylize: stylizeNoColor,
202 | showHidden: inspectDefaultOptions.showHidden,
203 | depth: inspectDefaultOptions.depth,
204 | colors: inspectDefaultOptions.colors,
205 | showProxy: inspectDefaultOptions.showProxy,
206 | maxArrayLength: inspectDefaultOptions.maxArrayLength,
207 | breakLength: inspectDefaultOptions.breakLength,
208 | indentationLvl: 0,
209 | compact: inspectDefaultOptions.compact,
210 | };
211 | // Legacy...
212 | if (arguments.length > 2) {
213 | if (arg2 !== undefined)
214 | ctx.depth = arg2;
215 |
216 | if (arguments.length > 3 && arg3 !== undefined)
217 | ctx.colors = arg3;
218 | }
219 | // Set user-specified options
220 | if (typeof opts === 'boolean') {
221 | ctx.showHidden = opts;
222 | } else if (opts) {
223 | const optKeys = Object.keys(opts);
224 | for (var i = 0; i < optKeys.length; i++)
225 | ctx[optKeys[i]] = opts[optKeys[i]];
226 | }
227 | if (ctx.colors)
228 | ctx.stylize = stylizeWithColor;
229 | if (ctx.maxArrayLength === null)
230 | ctx.maxArrayLength = Infinity;
231 | return formatValue(ctx, value, ctx.depth);
232 | }
233 |
234 | // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
235 | inspect.colors = Object.assign(Object.create(null), {
236 | bold: [1, 22],
237 | italic: [3, 23],
238 | underline: [4, 24],
239 | inverse: [7, 27],
240 | white: [37, 39],
241 | grey: [90, 39],
242 | black: [30, 39],
243 | blue: [34, 39],
244 | cyan: [36, 39],
245 | green: [32, 39],
246 | magenta: [35, 39],
247 | red: [31, 39],
248 | yellow: [33, 39],
249 | });
250 |
251 | inspect.styles = Object.assign(Object.create(null), {
252 | special: 'cyan',
253 | bigint: 'blue',
254 | number: 'blue',
255 | boolean: 'yellow',
256 | undefined: 'grey',
257 | null: 'bold',
258 | string: 'green',
259 | symbol: 'green',
260 | date: 'magenta',
261 | // "name": intentionally not styling
262 | regexp: 'red',
263 | });
264 |
265 | function stylizeNoColor(str) {
266 | return str;
267 | }
268 |
269 | function stylizeWithColor(str, styleType) {
270 | const style = inspect.styles[styleType];
271 | if (style !== undefined) {
272 | const color = inspect.colors[style];
273 | return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`;
274 | }
275 | return str;
276 | }
277 |
278 | function formatValue(ctx, value, recurseTimes, ln) {
279 | // Primitive types cannot have properties
280 | if (typeof value !== 'object' && typeof value !== 'function')
281 | return formatPrimitive(ctx.stylize, value, ctx);
282 |
283 | if (value === null)
284 | return ctx.stylize('null', 'null');
285 |
286 | var keys;
287 | var symbols = Object.getOwnPropertySymbols(value);
288 |
289 | // Look up the keys of the object.
290 | if (ctx.showHidden) {
291 | keys = Object.getOwnPropertyNames(value);
292 | } else {
293 | keys = Object.keys(value);
294 | if (symbols.length !== 0)
295 | symbols = symbols.filter((key) => propertyIsEnumerable.call(value, key));
296 | }
297 |
298 | const keyLength = keys.length + symbols.length;
299 |
300 | const { constructor, tag } = getIdentificationOf(value);
301 | var prefix = '';
302 | if (constructor && tag && constructor !== tag)
303 | prefix = `${constructor} [${tag}] `;
304 | else if (constructor)
305 | prefix = `${constructor} `;
306 | else if (tag)
307 | prefix = `[${tag}] `;
308 | var base = '';
309 | var formatter = formatObject;
310 | var braces;
311 | var noIterator = true;
312 | var raw;
313 | // Iterators and the rest are split to reduce checks
314 | if (value[Symbol.iterator]) {
315 | noIterator = false;
316 | if (Array.isArray(value)) {
317 | // Only set the constructor for non ordinary ("Array [...]") arrays.
318 | braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']'];
319 | if (value.length === 0 && keyLength === 0)
320 | return `${braces[0]}]`;
321 | formatter = formatArray;
322 | } else if (isSet(value)) {
323 | if (value.size === 0 && keyLength === 0)
324 | return `${prefix}{}`;
325 | braces = [`${prefix}{`, '}'];
326 | formatter = formatSet;
327 | } else if (isMap(value)) {
328 | if (value.size === 0 && keyLength === 0)
329 | return `${prefix}{}`;
330 | braces = [`${prefix}{`, '}'];
331 | formatter = formatMap;
332 | } else if (isTypedArray(value)) {
333 | braces = [`${prefix}[`, ']'];
334 | formatter = formatTypedArray;
335 | } else {
336 | // Check for boxed strings with valueOf()
337 | // The .valueOf() call can fail for a multitude of reasons
338 | try {
339 | raw = value.valueOf();
340 | } catch (e) { /* ignore */ }
341 | if (typeof raw === 'string') {
342 | const formatted = formatPrimitive(stylizeNoColor, raw, ctx);
343 | if (keyLength === raw.length)
344 | return ctx.stylize(`[String: ${formatted}]`, 'string');
345 | base = `[String: ${formatted}]`;
346 | // For boxed Strings, we have to remove the 0-n indexed entries,
347 | // since they just noisy up the output and are redundant
348 | // Make boxed primitive Strings look like such
349 | keys = keys.slice(value.length);
350 | braces = ['{', '}'];
351 | } else {
352 | noIterator = true;
353 | }
354 | }
355 | }
356 | if (noIterator) {
357 | braces = ['{', '}'];
358 | if (prefix === 'Object ') {
359 | // Object fast path
360 | if (keyLength === 0)
361 | return '{}';
362 | } else if (typeof value === 'function') {
363 | const name =
364 | `${constructor || tag}${value.name ? `: ${value.name}` : ''}`;
365 | if (keyLength === 0)
366 | return ctx.stylize(`[${name}]`, 'special');
367 | base = `[${name}]`;
368 | } else if (isRegExp(value)) {
369 | // Make RegExps say that they are RegExps
370 | if (keyLength === 0 || recurseTimes < 0)
371 | return ctx.stylize(regExpToString.call(value), 'regexp');
372 | base = `${regExpToString.call(value)}`;
373 | } else if (isDate(value)) {
374 | if (keyLength === 0) {
375 | if (Number.isNaN(value.getTime()))
376 | return ctx.stylize(value.toString(), 'date');
377 | return ctx.stylize(dateToISOString.call(value), 'date');
378 | }
379 | // Make dates with properties first say the date
380 | base = `${dateToISOString.call(value)}`;
381 | } else if (isError(value)) {
382 | // Make error with message first say the error
383 | if (keyLength === 0)
384 | return formatError(value);
385 | base = `${formatError(value)}`;
386 | } else if (isAnyArrayBuffer(value)) {
387 | // Fast path for ArrayBuffer and SharedArrayBuffer.
388 | // Can't do the same for DataView because it has a non-primitive
389 | // .buffer property that we need to recurse for.
390 | if (keyLength === 0) {
391 | return `${prefix
392 | }{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
393 | }
394 | braces[0] = `${prefix}{`;
395 | keys.unshift('byteLength');
396 | } else if (isDataView(value)) {
397 | braces[0] = `${prefix}{`;
398 | // .buffer goes last, it's not a primitive like the others.
399 | keys.unshift('byteLength', 'byteOffset', 'buffer');
400 | } else if (isPromise(value)) {
401 | braces[0] = `${prefix}{`;
402 | formatter = formatPromise;
403 | } else {
404 | // Check boxed primitives other than string with valueOf()
405 | // NOTE: `Date` has to be checked first!
406 | // The .valueOf() call can fail for a multitude of reasons
407 | try {
408 | raw = value.valueOf();
409 | } catch (e) { /* ignore */ }
410 | if (typeof raw === 'number') {
411 | // Make boxed primitive Numbers look like such
412 | const formatted = formatPrimitive(stylizeNoColor, raw);
413 | if (keyLength === 0)
414 | return ctx.stylize(`[Number: ${formatted}]`, 'number');
415 | base = `[Number: ${formatted}]`;
416 | } else if (typeof raw === 'boolean') {
417 | // Make boxed primitive Booleans look like such
418 | const formatted = formatPrimitive(stylizeNoColor, raw);
419 | if (keyLength === 0)
420 | return ctx.stylize(`[Boolean: ${formatted}]`, 'boolean');
421 | base = `[Boolean: ${formatted}]`;
422 | } else if (typeof raw === 'symbol') {
423 | const formatted = formatPrimitive(stylizeNoColor, raw);
424 | return ctx.stylize(`[Symbol: ${formatted}]`, 'symbol');
425 | } else if (keyLength === 0) {
426 | if (isExternal(value))
427 | return ctx.stylize('[External]', 'special');
428 | return `${prefix}{}`;
429 | } else {
430 | braces[0] = `${prefix}{`;
431 | }
432 | }
433 | }
434 | // Using an array here is actually better for the average case than using
435 | // a Set. `seen` will only check for the depth and will never grow too large.
436 | if (ctx.seen.indexOf(value) !== -1)
437 | return ctx.stylize('[Circular]', 'special');
438 | if (recurseTimes != null) { // eslint-disable-line eqeqeq
439 | if (recurseTimes < 0)
440 | return ctx.stylize(`[${constructor || tag || 'Object'}]`, 'special');
441 | recurseTimes -= 1;
442 | }
443 |
444 | ctx.seen.push(value);
445 | const output = formatter(ctx, value, recurseTimes, keys);
446 |
447 | for (var i = 0; i < symbols.length; i++)
448 | output.push(formatProperty(ctx, value, recurseTimes, symbols[i], 0));
449 |
450 | ctx.seen.pop();
451 |
452 | return reduceToSingleString(ctx, output, base, braces, ln);
453 | }
454 |
455 | function isMap(o) {
456 | return o instanceof Map;
457 | }
458 |
459 | function isSet(o) {
460 | return o instanceof Set;
461 | }
462 |
463 | function isExternal() {
464 | return false;
465 | }
466 |
467 | function isPromise(o) {
468 | return o instanceof Promise;
469 | }
470 |
471 | function isRegExp(o) {
472 | return o instanceof RegExp;
473 | }
474 |
475 | function isTypedArray(o) {
476 | for (const type of [
477 | Int8Array,
478 | Uint8Array,
479 | Uint8ClampedArray,
480 | Int16Array,
481 | Uint16Array,
482 | Int32Array,
483 | Uint32Array,
484 | Float32Array,
485 | Float64Array,
486 | ]) {
487 | if (o instanceof type)
488 | return true;
489 | }
490 |
491 | return false;
492 | }
493 |
494 | function isDate(o) {
495 | return o instanceof Date;
496 | }
497 |
498 | function isError(o) {
499 | return o instanceof Error;
500 | }
501 |
502 | function isDataView(o) {
503 | return ArrayBuffer.isView(o);
504 | }
505 |
506 | function isAnyArrayBuffer(o) {
507 | return o instanceof ArrayBuffer;
508 | }
509 |
510 | function formatNumber(fn, value) {
511 | // Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0.
512 | if (Object.is(value, -0))
513 | return fn('-0', 'number');
514 | return fn(`${value}`, 'number');
515 | }
516 |
517 | function formatPrimitive(fn, value, ctx) {
518 | if (typeof value === 'string') {
519 | if (ctx.compact === false &&
520 | value.length > MIN_LINE_LENGTH &&
521 | ctx.indentationLvl + value.length > ctx.breakLength) {
522 | // eslint-disable-next-line max-len
523 | const minLineLength = Math.max(ctx.breakLength - ctx.indentationLvl, MIN_LINE_LENGTH);
524 | // eslint-disable-next-line max-len
525 | const averageLineLength = Math.ceil(value.length / Math.ceil(value.length / minLineLength));
526 | const divisor = Math.max(averageLineLength, MIN_LINE_LENGTH);
527 | var res = '';
528 | if (readableRegExps[divisor] === undefined) {
529 | // Build a new RegExp that naturally breaks text into multiple lines.
530 | //
531 | // Rules
532 | // 1. Greedy match all text up the max line length that ends with a
533 | // whitespace or the end of the string.
534 | // 2. If none matches, non-greedy match any text up to a whitespace or
535 | // the end of the string.
536 | //
537 | // eslint-disable-next-line max-len, no-unescaped-regexp-dot
538 | readableRegExps[divisor] = new RegExp(`(.|\\n){1,${divisor}}(\\s|$)|(\\n|.)+?(\\s|$)`, 'gm');
539 | }
540 | const indent = ' '.repeat(ctx.indentationLvl);
541 | const matches = value.match(readableRegExps[divisor]);
542 | if (matches.length > 1) {
543 | res += `${fn(strEscape(matches[0]), 'string')} +\n`;
544 | for (var i = 1; i < matches.length - 1; i++)
545 | res += `${indent} ${fn(strEscape(matches[i]), 'string')} +\n`;
546 |
547 | res += `${indent} ${fn(strEscape(matches[i]), 'string')}`;
548 | return res;
549 | }
550 | }
551 | return fn(strEscape(value), 'string');
552 | }
553 | if (typeof value === 'bigint') // eslint-disable-line valid-typeof
554 | return fn(`${value}n`, 'bigint');
555 | if (typeof value === 'number')
556 | return formatNumber(fn, value);
557 | if (typeof value === 'boolean')
558 | return fn(`${value}`, 'boolean');
559 | if (typeof value === 'undefined')
560 | return fn('undefined', 'undefined');
561 | // es6 symbol primitive
562 | return fn(value.toString(), 'symbol');
563 | }
564 |
565 | function formatError(value) {
566 | return value.stack || `[${errorToString.call(value)}]`;
567 | }
568 |
569 | function formatObject(ctx, value, recurseTimes, keys) {
570 | const len = keys.length;
571 | const output = new Array(len);
572 | for (var i = 0; i < len; i++)
573 | output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 0);
574 | return output;
575 | }
576 |
577 | // The array is sparse and/or has extra keys
578 | function formatSpecialArray(ctx, value, recurseTimes, keys, maxLength, valLen) {
579 | const output = [];
580 | const keyLen = keys.length;
581 | var visibleLength = 0;
582 | var i = 0;
583 | if (keyLen !== 0 && numberRegExp.test(keys[0])) {
584 | for (const key of keys) {
585 | if (visibleLength === maxLength)
586 | break;
587 | const index = +key;
588 | // Arrays can only have up to 2^32 - 1 entries
589 | if (index > (2 ** 32) - 2)
590 | break;
591 | if (i !== index) {
592 | if (!numberRegExp.test(key))
593 | break;
594 | const emptyItems = index - i;
595 | const ending = emptyItems > 1 ? 's' : '';
596 | const message = `<${emptyItems} empty item${ending}>`;
597 | output.push(ctx.stylize(message, 'undefined'));
598 | i = index;
599 | if (++visibleLength === maxLength)
600 | break;
601 | }
602 | output.push(formatProperty(ctx, value, recurseTimes, key, 1));
603 | visibleLength++;
604 | i++;
605 | }
606 | }
607 | if (i < valLen && visibleLength !== maxLength) {
608 | const len = valLen - i;
609 | const ending = len > 1 ? 's' : '';
610 | const message = `<${len} empty item${ending}>`;
611 | output.push(ctx.stylize(message, 'undefined'));
612 | i = valLen;
613 | if (keyLen === 0)
614 | return output;
615 | }
616 | const remaining = valLen - i;
617 | if (remaining > 0)
618 | output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`);
619 |
620 | if (ctx.showHidden && keys[keyLen - 1] === 'length') {
621 | // No extra keys
622 | output.push(formatProperty(ctx, value, recurseTimes, 'length', 2));
623 | } else if (valLen === 0 || (keyLen > valLen && keys[valLen - 1] === `${valLen - 1}`)) {
624 | // The array is not sparse
625 | for (i = valLen; i < keyLen; i++)
626 | output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2));
627 | } else if (keys[keyLen - 1] !== `${valLen - 1}`) {
628 | const extra = [];
629 | // Only handle special keys
630 | var key;
631 | for (i = keys.length - 1; i >= 0; i--) {
632 | key = keys[i];
633 | if (numberRegExp.test(key) && +key < (2 ** 32) - 1)
634 | break;
635 | extra.push(formatProperty(ctx, value, recurseTimes, key, 2));
636 | }
637 | for (i = extra.length - 1; i >= 0; i--)
638 | output.push(extra[i]);
639 | }
640 | return output;
641 | }
642 |
643 | function formatArray(ctx, value, recurseTimes, keys) {
644 | const len = Math.min(Math.max(0, ctx.maxArrayLength), value.length);
645 | const hidden = ctx.showHidden ? 1 : 0;
646 | const valLen = value.length;
647 | const keyLen = keys.length - hidden;
648 | if (keyLen !== valLen || keys[keyLen - 1] !== `${valLen - 1}`)
649 | return formatSpecialArray(ctx, value, recurseTimes, keys, len, valLen);
650 |
651 | const remaining = valLen - len;
652 | const output = new Array(len + (remaining > 0 ? 1 : 0) + hidden);
653 | for (var i = 0; i < len; i++)
654 | output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 1);
655 | if (remaining > 0)
656 | output[i++] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`;
657 | if (ctx.showHidden === true)
658 | output[i] = formatProperty(ctx, value, recurseTimes, 'length', 2);
659 | return output;
660 | }
661 |
662 | function formatTypedArray(ctx, value, recurseTimes, keys) {
663 | const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), value.length);
664 | const remaining = value.length - maxLength;
665 | const output = new Array(maxLength + (remaining > 0 ? 1 : 0));
666 | for (var i = 0; i < maxLength; ++i)
667 | output[i] = formatNumber(ctx.stylize, value[i]);
668 | if (remaining > 0)
669 | output[i] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`;
670 | if (ctx.showHidden) {
671 | // .buffer goes last, it's not a primitive like the others.
672 | const extraKeys = [
673 | 'BYTES_PER_ELEMENT',
674 | 'length',
675 | 'byteLength',
676 | 'byteOffset',
677 | 'buffer',
678 | ];
679 | for (i = 0; i < extraKeys.length; i++) {
680 | const str = formatValue(ctx, value[extraKeys[i]], recurseTimes);
681 | output.push(`[${extraKeys[i]}]: ${str}`);
682 | }
683 | }
684 | // TypedArrays cannot have holes. Therefore it is safe to assume that all
685 | // extra keys are indexed after value.length.
686 | for (i = value.length; i < keys.length; i++)
687 | output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2));
688 |
689 | return output;
690 | }
691 |
692 | function formatSet(ctx, value, recurseTimes, keys) {
693 | const output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0));
694 | var i = 0;
695 | for (const v of value)
696 | output[i++] = formatValue(ctx, v, recurseTimes);
697 | // With `showHidden`, `length` will display as a hidden property for
698 | // arrays. For consistency's sake, do the same for `size`, even though this
699 | // property isn't selected by Object.getOwnPropertyNames().
700 | if (ctx.showHidden)
701 | output[i++] = `[size]: ${ctx.stylize(`${value.size}`, 'number')}`;
702 | for (var n = 0; n < keys.length; n++)
703 | output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0);
704 |
705 | return output;
706 | }
707 |
708 | function formatMap(ctx, value, recurseTimes, keys) {
709 | const output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0));
710 | var i = 0;
711 | for (const [k, v] of value) {
712 | output[i++] = `${formatValue(ctx, k, recurseTimes)} => ${
713 | formatValue(ctx, v, recurseTimes)}`;
714 | }
715 | // See comment in formatSet
716 | if (ctx.showHidden)
717 | output[i++] = `[size]: ${ctx.stylize(`${value.size}`, 'number')}`;
718 | for (var n = 0; n < keys.length; n++)
719 | output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0);
720 |
721 | return output;
722 | }
723 |
724 | function formatPromise(ctx, value, recurseTimes, keys) {
725 | var output;
726 | const [state, result] = getPromiseDetails(value);
727 | if (state === kPending) {
728 | output = [''];
729 | } else {
730 | const str = formatValue(ctx, result, recurseTimes);
731 | output = [state === kRejected ? ` ${str}` : str];
732 | }
733 | for (var n = 0; n < keys.length; n++)
734 | output.push(formatProperty(ctx, value, recurseTimes, keys[n], 0));
735 | return output;
736 | }
737 |
738 | function formatProperty(ctx, value, recurseTimes, key, array) {
739 | var name, str;
740 | const desc = Object.getOwnPropertyDescriptor(value, key) ||
741 | { value: value[key], enumerable: true };
742 | if (desc.value !== undefined) {
743 | const diff = array !== 0 || ctx.compact === false ? 2 : 3;
744 | ctx.indentationLvl += diff;
745 | str = formatValue(ctx, desc.value, recurseTimes, array === 0);
746 | ctx.indentationLvl -= diff;
747 | } else if (desc.get !== undefined) {
748 | if (desc.set !== undefined)
749 | str = ctx.stylize('[Getter/Setter]', 'special');
750 | else
751 | str = ctx.stylize('[Getter]', 'special');
752 | } else if (desc.set !== undefined) {
753 | str = ctx.stylize('[Setter]', 'special');
754 | } else {
755 | str = ctx.stylize('undefined', 'undefined');
756 | }
757 | if (array === 1)
758 | return str;
759 |
760 | if (typeof key === 'symbol')
761 | name = `[${ctx.stylize(key.toString(), 'symbol')}]`;
762 | else if (desc.enumerable === false)
763 | name = `[${key}]`;
764 | else if (keyStrRegExp.test(key))
765 | name = ctx.stylize(key, 'name');
766 | else
767 | name = ctx.stylize(strEscape(key), 'string');
768 |
769 |
770 | return `${name}: ${str}`;
771 | }
772 |
773 | function reduceToSingleString(ctx, output, base, braces, addLn) {
774 | const breakLength = ctx.breakLength;
775 | var i = 0;
776 | if (ctx.compact === false) {
777 | const indentation = ' '.repeat(ctx.indentationLvl);
778 | var res = `${base ? `${base} ` : ''}${braces[0]}\n${indentation} `;
779 | for (; i < output.length - 1; i++)
780 | res += `${output[i]},\n${indentation} `;
781 |
782 | res += `${output[i]}\n${indentation}${braces[1]}`;
783 | return res;
784 | }
785 | if (output.length * 2 <= breakLength) {
786 | var length = 0;
787 | for (; i < output.length && length <= breakLength; i++) {
788 | if (ctx.colors)
789 | length += output[i].replace(colorRegExp, '').length + 1;
790 | else
791 | length += output[i].length + 1;
792 | }
793 | if (length <= breakLength) {
794 | return `${braces[0]}${base ? ` ${base}` : ''} ${join(output, ', ')} ${
795 | braces[1]}`;
796 | }
797 | }
798 | // If the opening "brace" is too large, like in the case of "Set {",
799 | // we need to force the first item to be on the next line or the
800 | // items will not line up correctly.
801 | const indentation = ' '.repeat(ctx.indentationLvl);
802 | const extraLn = addLn === true ? `\n${indentation}` : '';
803 | const ln = base === '' && braces[0].length === 1 ?
804 | ' ' : `${base ? ` ${base}` : base}\n${indentation} `;
805 | const str = join(output, `,\n${indentation} `);
806 | return `${extraLn}${braces[0]}${ln}${str} ${braces[1]}`;
807 | }
808 |
809 | function getIdentificationOf(obj) {
810 | const original = obj;
811 | let constructor;
812 | let tag;
813 |
814 | while (obj) {
815 | if (constructor === undefined) {
816 | const desc = Object.getOwnPropertyDescriptor(obj, 'constructor');
817 | if (desc !== undefined &&
818 | typeof desc.value === 'function' &&
819 | desc.value.name !== '')
820 | constructor = desc.value.name;
821 | }
822 |
823 | if (tag === undefined) {
824 | const desc = Object.getOwnPropertyDescriptor(obj, Symbol.toStringTag);
825 | if (desc !== undefined) {
826 | if (typeof desc.value === 'string') {
827 | tag = desc.value;
828 | } else if (desc.get !== undefined) {
829 | tag = desc.get.call(original);
830 | if (typeof tag !== 'string') // eslint-disable-line max-depth
831 | tag = undefined;
832 | }
833 | }
834 | }
835 |
836 | if (constructor !== undefined && tag !== undefined)
837 | break;
838 |
839 | obj = Object.getPrototypeOf(obj);
840 | }
841 |
842 | return { constructor, tag };
843 | }
844 |
845 | function join(output, separator) {
846 | var str = '';
847 | if (output.length !== 0) {
848 | for (var i = 0; i < output.length - 1; i++) {
849 | // It is faster not to use a template string here
850 | str += output[i];
851 | str += separator;
852 | }
853 | str += output[i];
854 | }
855 | return str;
856 | }
857 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@snek/threads",
3 | "version": "0.0.2",
4 | "description": "thread like a pro",
5 | "main": "lib/index.js",
6 | "dependencies": {
7 | "bindings": "^1.3.0"
8 | },
9 | "devDependencies": {
10 | "buffer": "^5.0.8",
11 | "uglifyjs-webpack-plugin": "^1.1.4",
12 | "webpack": "^3.10.0"
13 | },
14 | "scripts": {
15 | "build": "node-gyp rebuild",
16 | "build:preload": "webpack",
17 | "prepublishOnly": "webpack"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/devsnek/threads.git"
22 | },
23 | "author": "snek ",
24 | "license": "MIT",
25 | "gypfile": true,
26 | "bugs": {
27 | "url": "https://github.com/devsnek/threads/issues"
28 | },
29 | "homepage": "https://github.com/devsnek/threads#readme"
30 | }
31 |
--------------------------------------------------------------------------------
/src/NativeUtil.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include "util.h"
3 |
4 | using namespace v8;
5 |
6 | static void GetPromiseDetails(const FunctionCallbackInfo& info) {
7 | Isolate* isolate = info.GetIsolate();
8 |
9 | Local ret = Array::New(isolate, 2);
10 | info.GetReturnValue().Set(ret);
11 |
12 | if (!info[0]->IsPromise())
13 | return;
14 |
15 | Local promise = info[0].As();
16 |
17 | int state = promise->State();
18 | ret->Set(0, Integer::New(isolate, state));
19 | if (state != Promise::PromiseState::kPending)
20 | ret->Set(1, promise->Result());
21 | }
22 |
23 | Local