workers: classify.

This commit is contained in:
Christopher Jeffrey 2017-11-16 20:26:28 -08:00
parent f313ca166d
commit bad24a6f31
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
9 changed files with 2051 additions and 2058 deletions

View File

@ -10,40 +10,42 @@ const assert = require('assert');
const EventEmitter = require('events'); const EventEmitter = require('events');
/** /**
* Child
* Represents a child process. * Represents a child process.
* @alias module:workers.Child * @alias module:workers.Child
* @constructor * @extends EventEmitter
* @ignore * @ignore
*/
class Child extends EventEmitter {
/**
* Represents a child process.
* @constructor
* @param {String} file * @param {String} file
*/ */
function Child(file) { constructor(file) {
if (!(this instanceof Child)) super();
return new Child(file);
EventEmitter.call(this);
this.init(file); this.init(file);
} }
Object.setPrototypeOf(Child.prototype, EventEmitter.prototype); /**
/**
* Test whether child process support is available. * Test whether child process support is available.
* @returns {Boolean} * @returns {Boolean}
*/ */
Child.hasSupport = function hasSupport() { static hasSupport() {
return typeof global.postMessage === 'function'; return typeof global.postMessage === 'function';
}; }
/** /**
* Initialize child process. Bind to events. * Initialize child process. Bind to events.
* @private * @private
* @param {String} file * @param {String} file
*/ */
Child.prototype.init = function init(file) { init(file) {
this.child = new global.Worker(file); this.child = new global.Worker(file);
this.child.onerror = (event) => { this.child.onerror = (event) => {
@ -64,15 +66,15 @@ Child.prototype.init = function init(file) {
} }
this.emit('data', data); this.emit('data', data);
}; };
}; }
/** /**
* Send data to child process. * Send data to child process.
* @param {Buffer} data * @param {Buffer} data
* @returns {Boolean} * @returns {Boolean}
*/ */
Child.prototype.write = function write(data) { write(data) {
if (this.child.postMessage.length === 2) { if (this.child.postMessage.length === 2) {
data.__proto__ = Uint8Array.prototype; data.__proto__ = Uint8Array.prototype;
this.child.postMessage({ data }, [data]); this.child.postMessage({ data }, [data]);
@ -80,16 +82,17 @@ Child.prototype.write = function write(data) {
this.child.postMessage(data.toString('hex')); this.child.postMessage(data.toString('hex'));
} }
return true; return true;
}; }
/** /**
* Destroy the child process. * Destroy the child process.
*/ */
Child.prototype.destroy = function destroy() { destroy() {
this.child.terminate(); this.child.terminate();
this.emit('exit', 15 | 0x80, 'SIGTERM'); this.emit('exit', 15 | 0x80, 'SIGTERM');
}; }
}
/* /*
* Expose * Expose

View File

@ -14,42 +14,45 @@ const children = new Set();
let exitBound = false; let exitBound = false;
/** /**
* Child
* Represents a child process. * Represents a child process.
* @alias module:workers.Child * @alias module:workers.Child
* @extends EventEmitter
* @ignore
*/
class Child extends EventEmitter {
/**
* Represents a child process.
* @constructor * @constructor
* @param {String} file * @param {String} file
*/ */
function Child(file) { constructor(file) {
if (!(this instanceof Child)) super();
return new Child(file);
EventEmitter.call(this);
bindExit(); bindExit();
children.add(this); children.add(this);
this.init(file); this.init(file);
} }
Object.setPrototypeOf(Child.prototype, EventEmitter.prototype); /**
/**
* Test whether child process support is available. * Test whether child process support is available.
* @returns {Boolean} * @returns {Boolean}
*/ */
Child.hasSupport = function hasSupport() { static hasSupport() {
return true; return true;
}; }
/** /**
* Initialize child process (node.js). * Initialize child process (node.js).
* @private * @private
* @param {String} file * @param {String} file
*/ */
Child.prototype.init = function init(file) { init(file) {
const bin = process.argv[0]; const bin = process.argv[0];
const filename = path.resolve(__dirname, file); const filename = path.resolve(__dirname, file);
const options = { stdio: 'pipe', env: process.env }; const options = { stdio: 'pipe', env: process.env };
@ -85,25 +88,26 @@ Child.prototype.init = function init(file) {
this.child.stdout.on('data', (data) => { this.child.stdout.on('data', (data) => {
this.emit('data', data); this.emit('data', data);
}); });
}; }
/** /**
* Send data to child process. * Send data to child process.
* @param {Buffer} data * @param {Buffer} data
* @returns {Boolean} * @returns {Boolean}
*/ */
Child.prototype.write = function write(data) { write(data) {
return this.child.stdin.write(data); return this.child.stdin.write(data);
}; }
/** /**
* Destroy the child process. * Destroy the child process.
*/ */
Child.prototype.destroy = function destroy() { destroy() {
this.child.kill('SIGTERM'); this.child.kill('SIGTERM');
}; }
}
/** /**
* Cleanup all child processes. * Cleanup all child processes.

View File

@ -12,15 +12,17 @@ const bio = require('bufio');
/** /**
* Framer * Framer
* @alias module:workers.Framer * @alias module:workers.Framer
*/
class Framer {
/**
* Create a framer.
* @constructor * @constructor
*/ */
function Framer() { constructor() {}
if (!(this instanceof Framer))
return new Framer();
}
Framer.prototype.packet = function packet(payload) { packet(payload) {
const size = 10 + payload.getSize(); const size = 10 + payload.getSize();
const bw = bio.write(size); const bw = bio.write(size);
@ -36,7 +38,8 @@ Framer.prototype.packet = function packet(payload) {
msg.writeUInt32LE(msg.length - 10, 5, true); msg.writeUInt32LE(msg.length - 10, 5, true);
return msg; return msg;
}; }
}
/* /*
* Expose * Expose

View File

@ -18,16 +18,20 @@ const packets = require('./packets');
const Parent = require('./parent'); const Parent = require('./parent');
/** /**
* Master
* Represents the master process. * Represents the master process.
* @alias module:workers.Master * @alias module:workers.Master
* @extends EventEmitter
*/
class Master extends EventEmitter {
/**
* Create the master process.
* @constructor * @constructor
*/ */
function Master() { constructor() {
if (!(this instanceof Master)) super();
return new Master();
EventEmitter.call(this);
this.parent = new Parent(); this.parent = new Parent();
this.framer = new Framer(); this.framer = new Framer();
@ -36,16 +40,14 @@ function Master() {
this.color = false; this.color = false;
this.init(); this.init();
} }
Object.setPrototypeOf(Master.prototype, EventEmitter.prototype); /**
/**
* Initialize master. Bind events. * Initialize master. Bind events.
* @private * @private
*/ */
Master.prototype.init = function init() { init() {
this.parent.on('data', (data) => { this.parent.on('data', (data) => {
this.parser.feed(data); this.parser.feed(data);
}); });
@ -66,82 +68,82 @@ Master.prototype.init = function init() {
this.parser.on('packet', (packet) => { this.parser.on('packet', (packet) => {
this.emit('packet', packet); this.emit('packet', packet);
}); });
}; }
/** /**
* Set environment. * Set environment.
* @param {Object} env * @param {Object} env
*/ */
Master.prototype.setEnv = function setEnv(env) { setEnv(env) {
this.color = env.BCOIN_WORKER_ISTTY === '1'; this.color = env.BCOIN_WORKER_ISTTY === '1';
this.set(env.BCOIN_WORKER_NETWORK); this.set(env.BCOIN_WORKER_NETWORK);
}; }
/** /**
* Set primary network. * Set primary network.
* @param {NetworkType|Network} network * @param {NetworkType|Network} network
*/ */
Master.prototype.set = function set(network) { set(network) {
return Network.set(network); return Network.set(network);
}; }
/** /**
* Send data to worker. * Send data to worker.
* @param {Buffer} data * @param {Buffer} data
* @returns {Boolean} * @returns {Boolean}
*/ */
Master.prototype.write = function write(data) { write(data) {
return this.parent.write(data); return this.parent.write(data);
}; }
/** /**
* Frame and send a packet. * Frame and send a packet.
* @param {Packet} packet * @param {Packet} packet
* @returns {Boolean} * @returns {Boolean}
*/ */
Master.prototype.send = function send(packet) { send(packet) {
return this.write(this.framer.packet(packet)); return this.write(this.framer.packet(packet));
}; }
/** /**
* Emit an event on the worker side. * Emit an event on the worker side.
* @param {String} event * @param {String} event
* @param {...Object} arg * @param {...Object} arg
* @returns {Boolean} * @returns {Boolean}
*/ */
Master.prototype.sendEvent = function sendEvent(...items) { sendEvent(...items) {
return this.send(new packets.EventPacket(items)); return this.send(new packets.EventPacket(items));
}; }
/** /**
* Destroy the worker. * Destroy the worker.
*/ */
Master.prototype.destroy = function destroy() { destroy() {
return this.parent.destroy(); return this.parent.destroy();
}; }
/** /**
* Write a message to stdout in the master process. * Write a message to stdout in the master process.
* @param {Object|String} obj * @param {Object|String} obj
* @param {...String} args * @param {...String} args
*/ */
Master.prototype.log = function log() { log() {
const text = format.apply(null, arguments); const text = format.apply(null, arguments);
this.send(new packets.LogPacket(text)); this.send(new packets.LogPacket(text));
}; }
/** /**
* Listen for messages from master process (only if worker). * Listen for messages from master process (only if worker).
*/ */
Master.prototype.listen = function listen() { listen() {
assert(!this.listening, 'Already listening.'); assert(!this.listening, 'Already listening.');
this.listening = true; this.listening = true;
@ -157,15 +159,15 @@ Master.prototype.listen = function listen() {
this.emit('error', e); this.emit('error', e);
} }
}); });
}; }
/** /**
* Handle packet. * Handle packet.
* @private * @private
* @param {Packet} * @param {Packet}
*/ */
Master.prototype.handlePacket = function handlePacket(packet) { handlePacket(packet) {
let result; let result;
switch (packet.cmd) { switch (packet.cmd) {
@ -185,7 +187,8 @@ Master.prototype.handlePacket = function handlePacket(packet) {
this.send(result); this.send(result);
break; break;
} }
}; }
}
/* /*
* Expose * Expose

File diff suppressed because it is too large Load Diff

View File

@ -10,29 +10,31 @@ const assert = require('assert');
const EventEmitter = require('events'); const EventEmitter = require('events');
/** /**
* Parent
* Represents the parent process. * Represents the parent process.
* @alias module:workers.Parent * @alias module:workers.Parent
* @constructor * @extends EventEmitter
* @ignore * @ignore
*/ */
function Parent() { class Parent extends EventEmitter {
if (!(this instanceof Parent)) /**
return new Parent(); * Create the parent process.
* @constructor
*/
EventEmitter.call(this); constructor() {
super();
this.init(); this.init();
} }
Object.setPrototypeOf(Parent.prototype, EventEmitter.prototype); /**
/**
* Initialize master (web workers). * Initialize master (web workers).
* @private * @private
*/ */
Parent.prototype.init = function init() { init() {
global.onerror = (event) => { global.onerror = (event) => {
this.emit('error', new Error('Worker error.')); this.emit('error', new Error('Worker error.'));
}; };
@ -50,15 +52,15 @@ Parent.prototype.init = function init() {
} }
this.emit('data', data); this.emit('data', data);
}; };
}; }
/** /**
* Send data to parent process. * Send data to parent process.
* @param {Buffer} data * @param {Buffer} data
* @returns {Boolean} * @returns {Boolean}
*/ */
Parent.prototype.write = function write(data) { write(data) {
if (global.postMessage.length === 2) { if (global.postMessage.length === 2) {
data.__proto__ = Uint8Array.prototype; data.__proto__ = Uint8Array.prototype;
global.postMessage({ data }, [data]); global.postMessage({ data }, [data]);
@ -66,15 +68,16 @@ Parent.prototype.write = function write(data) {
global.postMessage(data.toString('hex')); global.postMessage(data.toString('hex'));
} }
return true; return true;
}; }
/** /**
* Destroy the parent process. * Destroy the parent process.
*/ */
Parent.prototype.destroy = function destroy() { destroy() {
global.close(); global.close();
}; }
}
/* /*
* Expose * Expose

View File

@ -9,28 +9,30 @@
const EventEmitter = require('events'); const EventEmitter = require('events');
/** /**
* Parent
* Represents the parent process. * Represents the parent process.
* @alias module:workers.Parent * @alias module:workers.Parent
* @extends EventEmitter
*/
class Parent extends EventEmitter {
/**
* Create the parent process.
* @constructor * @constructor
*/ */
function Parent() { constructor() {
if (!(this instanceof Parent)) super();
return new Parent();
EventEmitter.call(this);
this.init(); this.init();
} }
Object.setPrototypeOf(Parent.prototype, EventEmitter.prototype); /**
/**
* Initialize master (node.js). * Initialize master (node.js).
* @private * @private
*/ */
Parent.prototype.init = function init() { init() {
process.stdin.on('data', (data) => { process.stdin.on('data', (data) => {
this.emit('data', data); this.emit('data', data);
}); });
@ -43,25 +45,26 @@ Parent.prototype.init = function init() {
process.on('uncaughtException', (err) => { process.on('uncaughtException', (err) => {
this.emit('exception', err); this.emit('exception', err);
}); });
}; }
/** /**
* Send data to parent process. * Send data to parent process.
* @param {Buffer} data * @param {Buffer} data
* @returns {Boolean} * @returns {Boolean}
*/ */
Parent.prototype.write = function write(data) { write(data) {
return process.stdout.write(data); return process.stdout.write(data);
}; }
/** /**
* Destroy the parent process. * Destroy the parent process.
*/ */
Parent.prototype.destroy = function destroy() { destroy() {
return process.exit(0); return process.exit(0);
}; }
}
/* /*
* Expose * Expose

View File

@ -1,5 +1,5 @@
/*! /*!
* workers.js - worker processes for bcoin * parser.js - worker parser for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin * https://github.com/bcoin-org/bcoin
@ -14,24 +14,25 @@ const packets = require('./packets');
/** /**
* Parser * Parser
* @alias module:workers.Parser * @alias module:workers.Parser
* @extends EventEmitter
*/
class Parser extends EventEmitter {
/**
* Create a parser.
* @constructor * @constructor
*/ */
function Parser() { constructor() {
if (!(this instanceof Parser)) super();
return new Parser();
EventEmitter.call(this);
this.waiting = 9; this.waiting = 9;
this.header = null; this.header = null;
this.pending = []; this.pending = [];
this.total = 0; this.total = 0;
} }
Object.setPrototypeOf(Parser.prototype, EventEmitter.prototype); feed(data) {
Parser.prototype.feed = function feed(data) {
this.total += data.length; this.total += data.length;
this.pending.push(data); this.pending.push(data);
@ -39,9 +40,9 @@ Parser.prototype.feed = function feed(data) {
const chunk = this.read(this.waiting); const chunk = this.read(this.waiting);
this.parse(chunk); this.parse(chunk);
} }
}; }
Parser.prototype.read = function read(size) { read(size) {
assert(this.total >= size, 'Reading too much.'); assert(this.total >= size, 'Reading too much.');
if (size === 0) if (size === 0)
@ -80,9 +81,9 @@ Parser.prototype.read = function read(size) {
this.total -= chunk.length; this.total -= chunk.length;
return chunk; return chunk;
}; }
Parser.prototype.parse = function parse(data) { parse(data) {
let header = this.header; let header = this.header;
if (!header) { if (!header) {
@ -118,16 +119,16 @@ Parser.prototype.parse = function parse(data) {
packet.id = header.id; packet.id = header.id;
this.emit('packet', packet); this.emit('packet', packet);
}; }
Parser.prototype.parseHeader = function parseHeader(data) { parseHeader(data) {
const id = data.readUInt32LE(0, true); const id = data.readUInt32LE(0, true);
const cmd = data.readUInt8(4, true); const cmd = data.readUInt8(4, true);
const size = data.readUInt32LE(5, true); const size = data.readUInt32LE(5, true);
return new Header(id, cmd, size); return new Header(id, cmd, size);
}; }
Parser.prototype.parsePacket = function parsePacket(header, data) { parsePacket(header, data) {
switch (header.cmd) { switch (header.cmd) {
case packets.types.ENV: case packets.types.ENV:
return packets.EnvPacket.fromRaw(data); return packets.EnvPacket.fromRaw(data);
@ -174,18 +175,25 @@ Parser.prototype.parsePacket = function parsePacket(header, data) {
default: default:
throw new Error('Unknown packet.'); throw new Error('Unknown packet.');
} }
}; }
}
/** /**
* Header * Header
* @constructor
* @ignore * @ignore
*/ */
function Header(id, cmd, size) { class Header {
/**
* Create a header.
* @constructor
*/
constructor(id, cmd, size) {
this.id = id; this.id = id;
this.cmd = cmd; this.cmd = cmd;
this.size = size; this.size = size;
}
} }
/* /*

View File

@ -20,23 +20,26 @@ const Framer = require('./framer');
const packets = require('./packets'); const packets = require('./packets');
/** /**
* A worker pool. * Worker Pool
* @alias module:workers.WorkerPool * @alias module:workers.WorkerPool
* @constructor * @extends EventEmitter
* @param {Object} options
* @param {Number} [options.size=num-cores] - Max pool size.
* @param {Number} [options.timeout=120000] - Execution timeout.
* @property {Number} size * @property {Number} size
* @property {Number} timeout * @property {Number} timeout
* @property {Map} children * @property {Map} children
* @property {Number} uid * @property {Number} uid
*/ */
function WorkerPool(options) { class WorkerPool extends EventEmitter {
if (!(this instanceof WorkerPool)) /**
return new WorkerPool(options); * Create a worker pool.
* @constructor
* @param {Object} options
* @param {Number} [options.size=num-cores] - Max pool size.
* @param {Number} [options.timeout=120000] - Execution timeout.
*/
EventEmitter.call(this); constructor(options) {
super();
this.enabled = false; this.enabled = false;
this.size = getCores(); this.size = getCores();
@ -47,16 +50,14 @@ function WorkerPool(options) {
this.uid = 0; this.uid = 0;
this.set(options); this.set(options);
} }
Object.setPrototypeOf(WorkerPool.prototype, EventEmitter.prototype); /**
/**
* Set worker pool options. * Set worker pool options.
* @param {Object} options * @param {Object} options
*/ */
WorkerPool.prototype.set = function set(options) { set(options) {
if (!options) if (!options)
return; return;
@ -81,33 +82,33 @@ WorkerPool.prototype.set = function set(options) {
assert(typeof options.file === 'string'); assert(typeof options.file === 'string');
this.file = options.file; this.file = options.file;
} }
}; }
/** /**
* Open worker pool. * Open worker pool.
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.open = async function open() { async open() {
; ;
}; }
/** /**
* Close worker pool. * Close worker pool.
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.close = async function close() { async close() {
this.destroy(); this.destroy();
}; }
/** /**
* Spawn a new worker. * Spawn a new worker.
* @param {Number} id - Worker ID. * @param {Number} id - Worker ID.
* @returns {Worker} * @returns {Worker}
*/ */
WorkerPool.prototype.spawn = function spawn(id) { spawn(id) {
const child = new Worker(this.file); const child = new Worker(this.file);
child.id = id; child.id = id;
@ -135,31 +136,31 @@ WorkerPool.prototype.spawn = function spawn(id) {
this.emit('spawn', child); this.emit('spawn', child);
return child; return child;
}; }
/** /**
* Allocate a new worker, will not go above `size` option * Allocate a new worker, will not go above `size` option
* and will automatically load balance the workers. * and will automatically load balance the workers.
* @returns {Worker} * @returns {Worker}
*/ */
WorkerPool.prototype.alloc = function alloc() { alloc() {
const id = this.uid++ % this.size; const id = this.uid++ % this.size;
if (!this.children.has(id)) if (!this.children.has(id))
this.children.set(id, this.spawn(id)); this.children.set(id, this.spawn(id));
return this.children.get(id); return this.children.get(id);
}; }
/** /**
* Emit an event on the worker side (all workers). * Emit an event on the worker side (all workers).
* @param {String} event * @param {String} event
* @param {...Object} arg * @param {...Object} arg
* @returns {Boolean} * @returns {Boolean}
*/ */
WorkerPool.prototype.sendEvent = function sendEvent() { sendEvent() {
let result = true; let result = true;
for (const child of this.children.values()) { for (const child of this.children.values()) {
@ -168,25 +169,25 @@ WorkerPool.prototype.sendEvent = function sendEvent() {
} }
return result; return result;
}; }
/** /**
* Destroy all workers. * Destroy all workers.
*/ */
WorkerPool.prototype.destroy = function destroy() { destroy() {
for (const child of this.children.values()) for (const child of this.children.values())
child.destroy(); child.destroy();
}; }
/** /**
* Call a method for a worker to execute. * Call a method for a worker to execute.
* @param {Packet} packet * @param {Packet} packet
* @param {Number} timeout * @param {Number} timeout
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.execute = function execute(packet, timeout) { execute(packet, timeout) {
if (!this.enabled || !Child.hasSupport()) { if (!this.enabled || !Child.hasSupport()) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setImmediate(() => { setImmediate(() => {
@ -208,9 +209,9 @@ WorkerPool.prototype.execute = function execute(packet, timeout) {
const child = this.alloc(); const child = this.alloc();
return child.execute(packet, timeout); return child.execute(packet, timeout);
}; }
/** /**
* Execute the tx check job (default timeout). * Execute the tx check job (default timeout).
* @method * @method
* @param {TX} tx * @param {TX} tx
@ -219,7 +220,7 @@ WorkerPool.prototype.execute = function execute(packet, timeout) {
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.check = async function check(tx, view, flags) { async check(tx, view, flags) {
const packet = new packets.CheckPacket(tx, view, flags); const packet = new packets.CheckPacket(tx, view, flags);
const result = await this.execute(packet, -1); const result = await this.execute(packet, -1);
@ -227,9 +228,9 @@ WorkerPool.prototype.check = async function check(tx, view, flags) {
throw result.error; throw result.error;
return null; return null;
}; }
/** /**
* Execute the tx signing job (default timeout). * Execute the tx signing job (default timeout).
* @method * @method
* @param {MTX} tx * @param {MTX} tx
@ -238,7 +239,7 @@ WorkerPool.prototype.check = async function check(tx, view, flags) {
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.sign = async function sign(tx, ring, type) { async sign(tx, ring, type) {
let rings = ring; let rings = ring;
if (!Array.isArray(rings)) if (!Array.isArray(rings))
@ -250,9 +251,9 @@ WorkerPool.prototype.sign = async function sign(tx, ring, type) {
result.inject(tx); result.inject(tx);
return result.total; return result.total;
}; }
/** /**
* Execute the tx input check job (default timeout). * Execute the tx input check job (default timeout).
* @method * @method
* @param {TX} tx * @param {TX} tx
@ -262,7 +263,7 @@ WorkerPool.prototype.sign = async function sign(tx, ring, type) {
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.checkInput = async function checkInput(tx, index, coin, flags) { async checkInput(tx, index, coin, flags) {
const packet = new packets.CheckInputPacket(tx, index, coin, flags); const packet = new packets.CheckInputPacket(tx, index, coin, flags);
const result = await this.execute(packet, -1); const result = await this.execute(packet, -1);
@ -270,9 +271,9 @@ WorkerPool.prototype.checkInput = async function checkInput(tx, index, coin, fla
throw result.error; throw result.error;
return null; return null;
}; }
/** /**
* Execute the tx input signing job (default timeout). * Execute the tx input signing job (default timeout).
* @method * @method
* @param {MTX} tx * @param {MTX} tx
@ -283,14 +284,14 @@ WorkerPool.prototype.checkInput = async function checkInput(tx, index, coin, fla
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.signInput = async function signInput(tx, index, coin, ring, type) { async signInput(tx, index, coin, ring, type) {
const packet = new packets.SignInputPacket(tx, index, coin, ring, type); const packet = new packets.SignInputPacket(tx, index, coin, ring, type);
const result = await this.execute(packet, -1); const result = await this.execute(packet, -1);
result.inject(tx); result.inject(tx);
return result.value; return result.value;
}; }
/** /**
* Execute the secp256k1 verify job (no timeout). * Execute the secp256k1 verify job (no timeout).
* @method * @method
* @param {Buffer} msg * @param {Buffer} msg
@ -299,13 +300,13 @@ WorkerPool.prototype.signInput = async function signInput(tx, index, coin, ring,
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.ecVerify = async function ecVerify(msg, sig, key) { async ecVerify(msg, sig, key) {
const packet = new packets.ECVerifyPacket(msg, sig, key); const packet = new packets.ECVerifyPacket(msg, sig, key);
const result = await this.execute(packet, -1); const result = await this.execute(packet, -1);
return result.value; return result.value;
}; }
/** /**
* Execute the secp256k1 signing job (no timeout). * Execute the secp256k1 signing job (no timeout).
* @method * @method
* @param {Buffer} msg * @param {Buffer} msg
@ -313,13 +314,13 @@ WorkerPool.prototype.ecVerify = async function ecVerify(msg, sig, key) {
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.ecSign = async function ecSign(msg, key) { async ecSign(msg, key) {
const packet = new packets.ECSignPacket(msg, key); const packet = new packets.ECSignPacket(msg, key);
const result = await this.execute(packet, -1); const result = await this.execute(packet, -1);
return result.sig; return result.sig;
}; }
/** /**
* Execute the mining job (no timeout). * Execute the mining job (no timeout).
* @method * @method
* @param {Buffer} data * @param {Buffer} data
@ -329,13 +330,13 @@ WorkerPool.prototype.ecSign = async function ecSign(msg, key) {
* @returns {Promise} - Returns {Number}. * @returns {Promise} - Returns {Number}.
*/ */
WorkerPool.prototype.mine = async function mine(data, target, min, max) { async mine(data, target, min, max) {
const packet = new packets.MinePacket(data, target, min, max); const packet = new packets.MinePacket(data, target, min, max);
const result = await this.execute(packet, -1); const result = await this.execute(packet, -1);
return result.nonce; return result.nonce;
}; }
/** /**
* Execute scrypt job (no timeout). * Execute scrypt job (no timeout).
* @method * @method
* @param {Buffer} passwd * @param {Buffer} passwd
@ -347,24 +348,28 @@ WorkerPool.prototype.mine = async function mine(data, target, min, max) {
* @returns {Promise} * @returns {Promise}
*/ */
WorkerPool.prototype.scrypt = async function scrypt(passwd, salt, N, r, p, len) { async scrypt(passwd, salt, N, r, p, len) {
const packet = new packets.ScryptPacket(passwd, salt, N, r, p, len); const packet = new packets.ScryptPacket(passwd, salt, N, r, p, len);
const result = await this.execute(packet, -1); const result = await this.execute(packet, -1);
return result.key; return result.key;
}; }
}
/** /**
* Represents a worker. * Worker
* @alias module:workers.Worker * @alias module:workers.Worker
* @extends EventEmitter
*/
class Worker extends EventEmitter {
/**
* Create a worker.
* @constructor * @constructor
* @param {String} file * @param {String} file
*/ */
function Worker(file) { constructor(file) {
if (!(this instanceof Worker)) super();
return new Worker(file);
EventEmitter.call(this);
this.id = -1; this.id = -1;
this.framer = new Framer(); this.framer = new Framer();
@ -374,16 +379,14 @@ function Worker(file) {
this.child = new Child(file); this.child = new Child(file);
this.init(); this.init();
} }
Object.setPrototypeOf(Worker.prototype, EventEmitter.prototype); /**
/**
* Initialize worker. Bind to events. * Initialize worker. Bind to events.
* @private * @private
*/ */
Worker.prototype.init = function init() { init() {
this.child.on('data', (data) => { this.child.on('data', (data) => {
this.parser.feed(data); this.parser.feed(data);
}); });
@ -405,14 +408,14 @@ Worker.prototype.init = function init() {
}); });
this.listen(); this.listen();
}; }
/** /**
* Listen for packets. * Listen for packets.
* @private * @private
*/ */
Worker.prototype.listen = function listen() { listen() {
this.on('exit', (code, signal) => { this.on('exit', (code, signal) => {
this.killJobs(); this.killJobs();
}); });
@ -435,15 +438,15 @@ Worker.prototype.listen = function listen() {
? (process.stdout.isTTY ? '1' : '0') ? (process.stdout.isTTY ? '1' : '0')
: '0' : '0'
}); });
}; }
/** /**
* Handle packet. * Handle packet.
* @private * @private
* @param {Packet} packet * @param {Packet} packet
*/ */
Worker.prototype.handlePacket = function handlePacket(packet) { handlePacket(packet) {
switch (packet.cmd) { switch (packet.cmd) {
case packets.types.EVENT: case packets.types.EVENT:
this.emit('event', packet.items); this.emit('event', packet.items);
@ -462,71 +465,71 @@ Worker.prototype.handlePacket = function handlePacket(packet) {
this.resolveJob(packet.id, packet); this.resolveJob(packet.id, packet);
break; break;
} }
}; }
/** /**
* Send data to worker. * Send data to worker.
* @param {Buffer} data * @param {Buffer} data
* @returns {Boolean} * @returns {Boolean}
*/ */
Worker.prototype.write = function write(data) { write(data) {
return this.child.write(data); return this.child.write(data);
}; }
/** /**
* Frame and send a packet. * Frame and send a packet.
* @param {Packet} packet * @param {Packet} packet
* @returns {Boolean} * @returns {Boolean}
*/ */
Worker.prototype.send = function send(packet) { send(packet) {
return this.write(this.framer.packet(packet)); return this.write(this.framer.packet(packet));
}; }
/** /**
* Send environment. * Send environment.
* @param {Object} env * @param {Object} env
* @returns {Boolean} * @returns {Boolean}
*/ */
Worker.prototype.sendEnv = function sendEnv(env) { sendEnv(env) {
return this.send(new packets.EnvPacket(env)); return this.send(new packets.EnvPacket(env));
}; }
/** /**
* Emit an event on the worker side. * Emit an event on the worker side.
* @param {String} event * @param {String} event
* @param {...Object} arg * @param {...Object} arg
* @returns {Boolean} * @returns {Boolean}
*/ */
Worker.prototype.sendEvent = function sendEvent(...items) { sendEvent(...items) {
return this.send(new packets.EventPacket(items)); return this.send(new packets.EventPacket(items));
}; }
/** /**
* Destroy the worker. * Destroy the worker.
*/ */
Worker.prototype.destroy = function destroy() { destroy() {
return this.child.destroy(); return this.child.destroy();
}; }
/** /**
* Call a method for a worker to execute. * Call a method for a worker to execute.
* @param {Packet} packet * @param {Packet} packet
* @param {Number} timeout * @param {Number} timeout
* @returns {Promise} * @returns {Promise}
*/ */
Worker.prototype.execute = function execute(packet, timeout) { execute(packet, timeout) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._execute(packet, timeout, resolve, reject); this._execute(packet, timeout, resolve, reject);
}); });
}; }
/** /**
* Call a method for a worker to execute. * Call a method for a worker to execute.
* @private * @private
* @param {Packet} packet * @param {Packet} packet
@ -536,7 +539,7 @@ Worker.prototype.execute = function execute(packet, timeout) {
* the worker method specifies. * the worker method specifies.
*/ */
Worker.prototype._execute = function _execute(packet, timeout, resolve, reject) { _execute(packet, timeout, resolve, reject) {
const job = new PendingJob(this, packet.id, resolve, reject); const job = new PendingJob(this, packet.id, resolve, reject);
assert(!this.pending.has(packet.id), 'ID overflow.'); assert(!this.pending.has(packet.id), 'ID overflow.');
@ -546,92 +549,98 @@ Worker.prototype._execute = function _execute(packet, timeout, resolve, reject)
job.start(timeout); job.start(timeout);
this.send(packet); this.send(packet);
}; }
/** /**
* Resolve a job. * Resolve a job.
* @param {Number} id * @param {Number} id
* @param {Packet} result * @param {Packet} result
*/ */
Worker.prototype.resolveJob = function resolveJob(id, result) { resolveJob(id, result) {
const job = this.pending.get(id); const job = this.pending.get(id);
if (!job) if (!job)
throw new Error(`Job ${id} is not in progress.`); throw new Error(`Job ${id} is not in progress.`);
job.resolve(result); job.resolve(result);
}; }
/** /**
* Reject a job. * Reject a job.
* @param {Number} id * @param {Number} id
* @param {Error} err * @param {Error} err
*/ */
Worker.prototype.rejectJob = function rejectJob(id, err) { rejectJob(id, err) {
const job = this.pending.get(id); const job = this.pending.get(id);
if (!job) if (!job)
throw new Error(`Job ${id} is not in progress.`); throw new Error(`Job ${id} is not in progress.`);
job.reject(err); job.reject(err);
}; }
/** /**
* Kill all jobs associated with worker. * Kill all jobs associated with worker.
*/ */
Worker.prototype.killJobs = function killJobs() { killJobs() {
for (const job of this.pending.values()) for (const job of this.pending.values())
job.destroy(); job.destroy();
}; }
}
/** /**
* Pending Job * Pending Job
* @constructor
* @ignore * @ignore
*/
class PendingJob {
/**
* Create a pending job.
* @constructor
* @param {Worker} worker * @param {Worker} worker
* @param {Number} id * @param {Number} id
* @param {Function} resolve * @param {Function} resolve
* @param {Function} reject * @param {Function} reject
*/ */
function PendingJob(worker, id, resolve, reject) { constructor(worker, id, resolve, reject) {
this.worker = worker; this.worker = worker;
this.id = id; this.id = id;
this.job = { resolve, reject }; this.job = { resolve, reject };
this.timer = null; this.timer = null;
} }
/** /**
* Start the timer. * Start the timer.
* @param {Number} timeout * @param {Number} timeout
*/ */
PendingJob.prototype.start = function start(timeout) { start(timeout) {
if (!timeout || timeout <= 0) if (!timeout || timeout <= 0)
return; return;
this.timer = setTimeout(() => { this.timer = setTimeout(() => {
this.reject(new Error('Worker timed out.')); this.reject(new Error('Worker timed out.'));
}, timeout); }, timeout);
}; }
/** /**
* Destroy the job with an error. * Destroy the job with an error.
*/ */
PendingJob.prototype.destroy = function destroy() { destroy() {
this.reject(new Error('Job was destroyed.')); this.reject(new Error('Job was destroyed.'));
}; }
/** /**
* Cleanup job state. * Cleanup job state.
* @returns {Job} * @returns {Job}
*/ */
PendingJob.prototype.cleanup = function cleanup() { cleanup() {
const job = this.job; const job = this.job;
assert(job, 'Already finished.'); assert(job, 'Already finished.');
@ -647,27 +656,28 @@ PendingJob.prototype.cleanup = function cleanup() {
this.worker.pending.delete(this.id); this.worker.pending.delete(this.id);
return job; return job;
}; }
/** /**
* Complete job with result. * Complete job with result.
* @param {Object} result * @param {Object} result
*/ */
PendingJob.prototype.resolve = function resolve(result) { resolve(result) {
const job = this.cleanup(); const job = this.cleanup();
job.resolve(result); job.resolve(result);
}; }
/** /**
* Complete job with error. * Complete job with error.
* @param {Error} err * @param {Error} err
*/ */
PendingJob.prototype.reject = function reject(err) { reject(err) {
const job = this.cleanup(); const job = this.cleanup();
job.reject(err); job.reject(err);
}; }
}
/* /*
* Helpers * Helpers