bcoin: start switching to class syntax.

This commit is contained in:
Christopher Jeffrey 2017-11-01 16:58:39 -07:00
parent f9eba3f5a6
commit a79c2b0b1a
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
11 changed files with 4504 additions and 4396 deletions

File diff suppressed because it is too large Load Diff

View File

@ -690,7 +690,7 @@ Mempool.prototype.has = function has(hash) {
*/
Mempool.prototype.exists = function exists(hash) {
if (this.locker.hasPending(hash))
if (this.locker.pending(hash))
return true;
if (this.hasOrphan(hash))

View File

@ -93,6 +93,14 @@ exports.MAX_BLOCK_SIGOPS = 1000000 / 50;
exports.MAX_BLOCK_SIGOPS_COST = 80000;
/**
* Size of set to pick median time from.
* @const {Number}
* @default
*/
exports.MEDIAN_TIMESPAN = 11;
/**
* What bits to set in version
* for versionbits blocks.

View File

@ -9,388 +9,428 @@
const assert = require('assert');
/**
* Represents a promise-resolving event emitter.
* Async Emitter
* @alias module:utils.AsyncEmitter
* @see EventEmitter
* @constructor
*/
function AsyncEmitter() {
if (!(this instanceof AsyncEmitter))
return new AsyncEmitter();
class AsyncEmitter {
/**
* Create an async emitter.
* @constructor
*/
this._events = Object.create(null);
constructor() {
this._events = Object.create(null);
}
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
addListener(type, handler) {
return this._push(type, handler, false);
}
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
on(type, handler) {
return this.addListener(type, handler);
}
/**
* Add a listener to execute once.
* @param {String} type
* @param {Function} handler
*/
once(type, handler) {
return this._push(type, handler, true);
}
/**
* Prepend a listener.
* @param {String} type
* @param {Function} handler
*/
prependListener(type, handler) {
return this._unshift(type, handler, false);
}
/**
* Prepend a listener to execute once.
* @param {String} type
* @param {Function} handler
*/
prependOnceListener(type, handler) {
return this._unshift(type, handler, true);
}
/**
* Push a listener.
* @private
* @param {String} type
* @param {Function} handler
* @param {Boolean} once
*/
_push(type, handler, once) {
assert(typeof type === 'string', '`type` must be a string.');
if (!this._events[type])
this._events[type] = [];
this.emit('newListener', type, handler);
this._events[type].push(new Listener(handler, once));
}
/**
* Unshift a listener.
* @param {String} type
* @param {Function} handler
* @param {Boolean} once
*/
_unshift(type, handler, once) {
assert(typeof type === 'string', '`type` must be a string.');
if (!this._events[type])
this._events[type] = [];
this.emit('newListener', type, handler);
this._events[type].unshift(new Listener(handler, once));
}
/**
* Remove a listener.
* @param {String} type
* @param {Function} handler
*/
removeListener(type, handler) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners)
return;
let index = -1;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
if (listener.handler === handler) {
index = i;
break;
}
}
if (index === -1)
return;
splice(listeners, index);
if (listeners.length === 0)
delete this._events[type];
this.emit('removeListener', type, handler);
}
/**
* Set max listeners.
* @param {Number} max
*/
setMaxListeners(max) {
assert(typeof max === 'number', '`max` must be a number.');
assert(max >= 0, '`max` must be non-negative.');
assert(Number.isSafeInteger(max), '`max` must be an integer.');
}
/**
* Remove all listeners.
* @param {String?} type
*/
removeAllListeners(type) {
if (arguments.length === 0) {
this._events = Object.create(null);
return;
}
assert(typeof type === 'string', '`type` must be a string.');
delete this._events[type];
}
/**
* Get listeners array.
* @param {String} type
* @returns {Function[]}
*/
listeners(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners)
return [];
const result = [];
for (const {handler} of listeners)
result.push(handler);
return result;
}
/**
* Get listener count for an event.
* @param {String} type
*/
listenerCount(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners)
return 0;
return listeners.length;
}
/**
* Get event names.
* @returns {String[]}
*/
eventNames() {
return Object.keys(this._events);
}
/**
* Emit an event synchronously.
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
emit(type) {
try {
this._emit.apply(this, arguments);
} catch (e) {
if (type === 'error')
throw e;
this._emit('error', e);
}
}
/**
* Emit an event synchronously.
* @private
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
_emit(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners) {
if (type === 'error') {
const msg = arguments[1];
if (msg instanceof Error)
throw msg;
const err = new Error(`Uncaught, unspecified "error" event. (${msg})`);
err.context = msg;
throw err;
}
return;
}
assert(listeners.length > 0);
let args = null;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
const handler = listener.handler;
if (listener.once) {
splice(listeners, i);
if (listeners.length === 0)
delete this._events[type];
i -= 1;
}
switch (arguments.length) {
case 1:
handler();
break;
case 2:
handler(arguments[1]);
break;
case 3:
handler(arguments[1], arguments[2]);
break;
case 4:
handler(arguments[1], arguments[2], arguments[3]);
break;
default:
if (!args) {
args = new Array(arguments.length - 1);
for (let j = 1; j < arguments.length; j++)
args[j - 1] = arguments[j];
}
handler.apply(null, args);
break;
}
}
}
/**
* Emit an event. Wait for promises to resolve.
* @method
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
async emitAsync(type) {
try {
await this._emitAsync.apply(this, arguments);
} catch (e) {
if (type === 'error')
throw e;
await this._emitAsync('error', e);
}
}
/**
* Emit an event. Wait for promises to resolve.
* @private
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
async _emitAsync(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners) {
if (type === 'error') {
const msg = arguments[1];
if (msg instanceof Error)
throw msg;
const err = new Error(`Uncaught, unspecified "error" event. (${msg})`);
err.context = msg;
throw err;
}
return;
}
assert(listeners.length > 0);
let args = null;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
const handler = listener.handler;
if (listener.once) {
splice(listeners, i);
if (listeners.length === 0)
delete this._events[type];
i -= 1;
}
switch (arguments.length) {
case 1:
await handler();
break;
case 2:
await handler(arguments[1]);
break;
case 3:
await handler(arguments[1], arguments[2]);
break;
case 4:
await handler(arguments[1], arguments[2], arguments[3]);
break;
default:
if (!args) {
args = new Array(arguments.length - 1);
for (let j = 1; j < arguments.length; j++)
args[j - 1] = arguments[j];
}
await handler.apply(null, args);
break;
}
}
}
}
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
AsyncEmitter.prototype.addListener = function addListener(type, handler) {
return this._push(type, handler, false);
};
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
AsyncEmitter.prototype.on = function on(type, handler) {
return this.addListener(type, handler);
};
/**
* Add a listener to execute once.
* @param {String} type
* @param {Function} handler
*/
AsyncEmitter.prototype.once = function once(type, handler) {
return this._push(type, handler, true);
};
/**
* Prepend a listener.
* @param {String} type
* @param {Function} handler
*/
AsyncEmitter.prototype.prependListener = function prependListener(type, handler) {
return this._unshift(type, handler, false);
};
/**
* Prepend a listener to execute once.
* @param {String} type
* @param {Function} handler
*/
AsyncEmitter.prototype.prependOnceListener = function prependOnceListener(type, handler) {
return this._unshift(type, handler, true);
};
/**
* Push a listener.
* @private
* @param {String} type
* @param {Function} handler
* @param {Boolean} once
*/
AsyncEmitter.prototype._push = function _push(type, handler, once) {
assert(typeof type === 'string', '`type` must be a string.');
if (!this._events[type])
this._events[type] = [];
this._events[type].push(new Listener(handler, once));
this.emit('newListener', type, handler);
};
/**
* Unshift a listener.
* @param {String} type
* @param {Function} handler
* @param {Boolean} once
*/
AsyncEmitter.prototype._unshift = function _unshift(type, handler, once) {
assert(typeof type === 'string', '`type` must be a string.');
if (!this._events[type])
this._events[type] = [];
this._events[type].unshift(new Listener(handler, once));
this.emit('newListener', type, handler);
};
/**
* Remove a listener.
* @param {String} type
* @param {Function} handler
*/
AsyncEmitter.prototype.removeListener = function removeListener(type, handler) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners)
return;
let index = -1;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
if (listener.handler === handler) {
index = i;
break;
}
}
if (index === -1)
return;
listeners.splice(index, 1);
if (listeners.length === 0)
delete this._events[type];
this.emit('removeListener', type, handler);
};
/**
* Set max listeners.
* @param {Number} max
*/
AsyncEmitter.prototype.setMaxListeners = function setMaxListeners(max) {
assert(typeof max === 'number', '`max` must be a number.');
assert(max >= 0, '`max` must be non-negative.');
assert(Number.isSafeInteger(max), '`max` must be an integer.');
};
/**
* Remove all listeners.
* @param {String?} type
*/
AsyncEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
if (arguments.length === 0) {
this._events = Object.create(null);
return;
}
assert(typeof type === 'string', '`type` must be a string.');
delete this._events[type];
};
/**
* Get listeners array.
* @param {String} type
* @returns {Function[]}
*/
AsyncEmitter.prototype.listeners = function listeners(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners)
return [];
const result = [];
for (const {handler} of listeners)
result.push(handler);
return result;
};
/**
* Get listener count for an event.
* @param {String} type
*/
AsyncEmitter.prototype.listenerCount = function listenerCount(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners)
return 0;
return listeners.length;
};
/**
* Emit an event synchronously.
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
AsyncEmitter.prototype.emit = function emit(type) {
try {
this._emit.apply(this, arguments);
} catch (e) {
if (type === 'error')
throw e;
this._emit('error', e);
}
};
/**
* Emit an event synchronously.
* @private
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
AsyncEmitter.prototype._emit = function _emit(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners) {
if (type === 'error') {
const error = arguments[1];
if (error instanceof Error)
throw error;
const err = new Error(`Uncaught, unspecified "error" event. (${error})`);
err.context = error;
throw err;
}
return;
}
assert(listeners.length > 0);
let args = null;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
const handler = listener.handler;
if (listener.once) {
listeners.splice(i, 1);
i -= 1;
}
switch (arguments.length) {
case 1:
handler();
break;
case 2:
handler(arguments[1]);
break;
case 3:
handler(arguments[1], arguments[2]);
break;
case 4:
handler(arguments[1], arguments[2], arguments[3]);
break;
default:
if (!args) {
args = new Array(arguments.length - 1);
for (let j = 1; j < arguments.length; j++)
args[j - 1] = arguments[j];
}
handler.apply(null, args);
break;
}
}
};
/**
* Emit an event. Wait for promises to resolve.
* @method
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
AsyncEmitter.prototype.emitAsync = async function emitAsync(type) {
try {
await this._emitAsync.apply(this, arguments);
} catch (e) {
if (type === 'error')
throw e;
await this._emitAsync('error', e);
}
};
/**
* Emit an event. Wait for promises to resolve.
* @private
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
AsyncEmitter.prototype._emitAsync = async function _emitAsync(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners) {
if (type === 'error') {
const error = arguments[1];
if (error instanceof Error)
throw error;
const err = new Error(`Uncaught, unspecified "error" event. (${error})`);
err.context = error;
throw err;
}
return;
}
assert(listeners.length > 0);
let args = null;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
const handler = listener.handler;
if (listener.once) {
listeners.splice(i, 1);
i -= 1;
}
switch (arguments.length) {
case 1:
await handler();
break;
case 2:
await handler(arguments[1]);
break;
case 3:
await handler(arguments[1], arguments[2]);
break;
case 4:
await handler(arguments[1], arguments[2], arguments[3]);
break;
default:
if (!args) {
args = new Array(arguments.length - 1);
for (let j = 1; j < arguments.length; j++)
args[j - 1] = arguments[j];
}
await handler.apply(null, args);
break;
}
}
};
/**
* Event Listener
* @constructor
* @ignore
* @param {Function} handler
* @param {Boolean} once
* @property {Function} handler
* @property {Boolean} once
*/
function Listener(handler, once) {
assert(typeof handler === 'function', '`handler` must be a function.');
assert(typeof once === 'boolean', '`once` must be a function.');
this.handler = handler;
this.once = once;
class Listener {
/**
* Create an event listener.
* @constructor
* @param {Function} handler
* @param {Boolean} once
*/
constructor(handler, once) {
assert(typeof handler === 'function', '`handler` must be a function.');
assert(typeof once === 'boolean', '`once` must be a function.');
this.handler = handler;
this.once = once;
}
}
/*
* Helpers
*/
function splice(list, i) {
if (i === 0) {
list.shift();
return;
}
let k = i + 1;
while (k < list.length)
list[i++] = list[k++];
list.pop();
}
/*

View File

@ -79,7 +79,25 @@ exports.remove = function remove(items, item, compare) {
if (i === -1)
return false;
items.splice(i, 1);
splice(items, i);
return true;
};
/*
* Helpers
*/
function splice(list, i) {
if (i === 0) {
list.shift();
return;
}
let k = i + 1;
while (k < list.length)
list[i++] = list[k++];
list.pop();
}

View File

@ -11,221 +11,224 @@ const assert = require('assert');
/**
* Binary Heap
* @alias module:utils.Heap
* @constructor
* @param {Function?} compare
*/
function Heap(compare) {
if (!(this instanceof Heap))
return new Heap(compare);
class Heap {
/**
* Create a binary heap.
* @constructor
* @param {Function?} compare
*/
this.compare = comparator;
this.items = [];
constructor(compare) {
this.compare = comparator;
this.items = [];
if (compare)
this.set(compare);
if (compare)
this.set(compare);
}
/**
* Initialize and sort heap.
*/
init() {
const n = this.items.length;
if (n <= 1)
return;
for (let i = (n / 2 | 0) - 1; i >= 0; i--)
this.down(i, n);
}
/**
* Get heap size.
* @returns {Number}
*/
size() {
return this.items.length;
}
/**
* Set comparator.
* @param {Function} compare
*/
set(compare) {
assert(typeof compare === 'function',
'Comparator must be a function.');
this.compare = compare;
}
/**
* Push item onto heap.
* @param {Object} item
* @returns {Number}
*/
insert(item) {
this.items.push(item);
this.up(this.items.length - 1);
return this.items.length;
}
/**
* Pop next item off of heap.
* @param {Object} item
* @returns {Object}
*/
shift() {
if (this.items.length === 0)
return null;
const n = this.items.length - 1;
this.swap(0, n);
this.down(0, n);
return this.items.pop();
}
/**
* Remove item from heap.
* @param {Number} i
* @returns {Object}
*/
remove(i) {
if (this.items.length === 0)
return null;
const n = this.items.length - 1;
if (i < 0 || i > n)
return null;
if (n !== i) {
this.swap(i, n);
this.down(i, n);
this.up(i);
}
return this.items.pop();
}
/**
* Swap indicies.
* @private
* @param {Number} a
* @param {Number} b
*/
swap(a, b) {
const x = this.items[a];
const y = this.items[b];
this.items[a] = y;
this.items[b] = x;
}
/**
* Compare indicies.
* @private
* @param {Number} i
* @param {Number} j
* @returns {Boolean}
*/
less(i, j) {
return this.compare(this.items[i], this.items[j]) < 0;
}
/**
* Bubble item down.
* @private
* @param {Number} i
* @param {Number} n
*/
down(i, n) {
for (;;) {
const l = 2 * i + 1;
assert(l >= 0);
if (l < 0 || l >= n)
break;
let j = l;
const r = l + 1;
if (r < n && !this.less(l, r))
j = r;
if (!this.less(j, i))
break;
this.swap(i, j);
i = j;
}
}
/**
* Bubble item up.
* @private
* @param {Number} i
*/
up(i) {
for (;;) {
const j = (i - 1) / 2 | 0;
assert(j >= 0);
if (j < 0 || j === i)
break;
if (!this.less(i, j))
break;
this.swap(j, i);
i = j;
}
}
/**
* Convert heap to sorted array.
* @returns {Object[]}
*/
toArray() {
const heap = new Heap();
const result = [];
heap.compare = this.compare;
heap.items = this.items.slice();
while (heap.size() > 0)
result.push(heap.shift());
return result;
}
/**
* Instantiate heap from array and comparator.
* @param {Function} compare
* @param {Object[]} items
* @returns {Heap}
*/
static fromArray(compare, items) {
const heap = new Heap();
heap.set(compare);
heap.items = items;
heap.init();
return heap;
}
}
/**
* Initialize and sort heap.
*/
Heap.prototype.init = function init() {
const n = this.items.length;
if (n <= 1)
return;
for (let i = (n / 2 | 0) - 1; i >= 0; i--)
this.down(i, n);
};
/**
* Get heap size.
* @returns {Number}
*/
Heap.prototype.size = function size() {
return this.items.length;
};
/**
* Set comparator.
* @param {Function} compare
*/
Heap.prototype.set = function set(compare) {
assert(typeof compare === 'function',
'Comparator must be a function.');
this.compare = compare;
};
/**
* Push item onto heap.
* @param {Object} item
* @returns {Number}
*/
Heap.prototype.insert = function insert(item) {
this.items.push(item);
this.up(this.items.length - 1);
return this.items.length;
};
/**
* Pop next item off of heap.
* @param {Object} item
* @returns {Object}
*/
Heap.prototype.shift = function shift() {
if (this.items.length === 0)
return null;
const n = this.items.length - 1;
this.swap(0, n);
this.down(0, n);
return this.items.pop();
};
/**
* Remove item from heap.
* @param {Number} i
* @returns {Object}
*/
Heap.prototype.remove = function remove(i) {
if (this.items.length === 0)
return null;
const n = this.items.length - 1;
if (i < 0 || i > n)
return null;
if (n !== i) {
this.swap(i, n);
this.down(i, n);
this.up(i);
}
return this.items.pop();
};
/**
* Swap indicies.
* @private
* @param {Number} a
* @param {Number} b
*/
Heap.prototype.swap = function swap(a, b) {
const x = this.items[a];
const y = this.items[b];
this.items[a] = y;
this.items[b] = x;
};
/**
* Compare indicies.
* @private
* @param {Number} i
* @param {Number} j
* @returns {Boolean}
*/
Heap.prototype.less = function less(i, j) {
return this.compare(this.items[i], this.items[j]) < 0;
};
/**
* Bubble item down.
* @private
* @param {Number} i
* @param {Number} n
*/
Heap.prototype.down = function down(i, n) {
for (;;) {
const l = 2 * i + 1;
assert(l >= 0);
if (l < 0 || l >= n)
break;
let j = l;
const r = l + 1;
if (r < n && !this.less(l, r))
j = r;
if (!this.less(j, i))
break;
this.swap(i, j);
i = j;
}
};
/**
* Bubble item up.
* @private
* @param {Number} i
*/
Heap.prototype.up = function up(i) {
for (;;) {
const j = (i - 1) / 2 | 0;
assert(j >= 0);
if (j < 0 || j === i)
break;
if (!this.less(i, j))
break;
this.swap(j, i);
i = j;
}
};
/**
* Convert heap to sorted array.
* @returns {Object[]}
*/
Heap.prototype.toArray = function toArray() {
const heap = new Heap();
const result = [];
heap.compare = this.compare;
heap.items = this.items.slice();
while (heap.size() > 0)
result.push(heap.shift());
return result;
};
/**
* Instantiate heap from array and comparator.
* @param {Function} compare
* @param {Object[]} items
* @returns {Heap}
*/
Heap.fromArray = function fromArray(compare, items) {
const heap = new Heap();
heap.set(compare);
heap.items = items;
heap.init();
return heap;
};
/*
* Helpers
*/

View File

@ -9,263 +9,273 @@
const assert = require('assert');
/**
* A double linked list.
* Double Linked List
* @alias module:utils.List
* @constructor
* @property {ListItem|null} head
* @property {ListItem|null} tail
* @property {Number} size
*/
function List() {
if (!(this instanceof List))
return new List();
class List {
/**
* Create a list.
* @constructor
* @property {ListItem|null} head
* @property {ListItem|null} tail
* @property {Number} size
*/
this.head = null;
this.tail = null;
this.size = 0;
}
/**
* Reset the cache. Clear all items.
*/
List.prototype.reset = function reset() {
let item, next;
for (item = this.head; item; item = next) {
next = item.next;
item.prev = null;
item.next = null;
constructor() {
this.head = null;
this.tail = null;
this.size = 0;
}
assert(!item);
/**
* Reset the cache. Clear all items.
*/
this.head = null;
this.tail = null;
this.size = 0;
};
reset() {
let item, next;
/**
* Remove the first item in the list.
* @returns {ListItem}
*/
List.prototype.shift = function shift() {
const item = this.head;
if (!item)
return null;
this.remove(item);
return item;
};
/**
* Prepend an item to the linked list (sets new head).
* @param {ListItem}
* @returns {Boolean}
*/
List.prototype.unshift = function unshift(item) {
return this.insert(null, item);
};
/**
* Append an item to the linked list (sets new tail).
* @param {ListItem}
* @returns {Boolean}
*/
List.prototype.push = function push(item) {
return this.insert(this.tail, item);
};
/**
* Remove the last item in the list.
* @returns {ListItem}
*/
List.prototype.pop = function pop() {
const item = this.tail;
if (!item)
return null;
this.remove(item);
return item;
};
/**
* Insert item into the linked list.
* @private
* @param {ListItem|null} ref
* @param {ListItem} item
* @returns {Boolean}
*/
List.prototype.insert = function insert(ref, item) {
if (item.prev || item.next || item === this.head)
return false;
assert(!item.prev);
assert(!item.next);
if (ref == null) {
if (!this.head) {
this.head = item;
this.tail = item;
} else {
this.head.prev = item;
item.next = this.head;
this.head = item;
for (item = this.head; item; item = next) {
next = item.next;
item.prev = null;
item.next = null;
}
this.size++;
assert(!item);
this.head = null;
this.tail = null;
this.size = 0;
}
/**
* Remove the first item in the list.
* @returns {ListItem}
*/
shift() {
const item = this.head;
if (!item)
return null;
this.remove(item);
return item;
}
/**
* Prepend an item to the linked list (sets new head).
* @param {ListItem}
* @returns {Boolean}
*/
unshift(item) {
return this.insert(null, item);
}
/**
* Append an item to the linked list (sets new tail).
* @param {ListItem}
* @returns {Boolean}
*/
push(item) {
return this.insert(this.tail, item);
}
/**
* Remove the last item in the list.
* @returns {ListItem}
*/
pop() {
const item = this.tail;
if (!item)
return null;
this.remove(item);
return item;
}
/**
* Insert item into the linked list.
* @private
* @param {ListItem|null} ref
* @param {ListItem} item
* @returns {Boolean}
*/
insert(ref, item) {
if (item.prev || item.next || item === this.head)
return false;
assert(!item.prev);
assert(!item.next);
if (ref == null) {
if (!this.head) {
this.head = item;
this.tail = item;
} else {
this.head.prev = item;
item.next = this.head;
this.head = item;
}
this.size += 1;
return true;
}
item.next = ref.next;
item.prev = ref;
ref.next = item;
if (ref === this.tail)
this.tail = item;
this.size += 1;
return true;
}
item.next = ref.next;
item.prev = ref;
ref.next = item;
/**
* Remove item from the linked list.
* @private
* @param {ListItem}
* @returns {Boolean}
*/
if (ref === this.tail)
this.tail = item;
remove(item) {
if (!item.prev && !item.next && item !== this.head)
return false;
this.size++;
if (item.prev)
item.prev.next = item.next;
return true;
};
if (item.next)
item.next.prev = item.prev;
/**
* Remove item from the linked list.
* @private
* @param {ListItem}
* @returns {Boolean}
*/
if (item === this.head)
this.head = item.next;
List.prototype.remove = function remove(item) {
if (!item.prev && !item.next && item !== this.head)
return false;
if (item === this.tail)
this.tail = item.prev || this.head;
if (item.prev)
item.prev.next = item.next;
if (!this.head)
assert(!this.tail);
if (item.next)
item.next.prev = item.prev;
if (!this.tail)
assert(!this.head);
if (item === this.head)
this.head = item.next;
if (item === this.tail)
this.tail = item.prev || this.head;
if (!this.head)
assert(!this.tail);
if (!this.tail)
assert(!this.head);
item.prev = null;
item.next = null;
this.size--;
return true;
};
/**
* Replace an item in-place.
* @param {ListItem} ref
* @param {ListItem} item
*/
List.prototype.replace = function replace(ref, item) {
if (ref.prev)
ref.prev.next = item;
if (ref.next)
ref.next.prev = item;
item.prev = ref.prev;
item.next = ref.next;
ref.next = null;
ref.prev = null;
if (this.head === ref)
this.head = item;
if (this.tail === ref)
this.tail = item;
};
/**
* Slice the list to an array of items.
* Will remove the items sliced.
* @param {Number?} total
* @returns {ListItem[]}
*/
List.prototype.slice = function slice(total) {
const items = [];
let item, next;
if (total == null)
total = -1;
for (item = this.head; item; item = next) {
next = item.next;
item.prev = null;
item.next = null;
this.size--;
this.size -= 1;
items.push(item);
if (items.length === total)
break;
return true;
}
if (next) {
this.head = next;
next.prev = null;
} else {
this.head = null;
this.tail = null;
/**
* Replace an item in-place.
* @param {ListItem} ref
* @param {ListItem} item
*/
replace(ref, item) {
if (ref.prev)
ref.prev.next = item;
if (ref.next)
ref.next.prev = item;
item.prev = ref.prev;
item.next = ref.next;
ref.next = null;
ref.prev = null;
if (this.head === ref)
this.head = item;
if (this.tail === ref)
this.tail = item;
}
return items;
};
/**
* Slice the list to an array of items.
* Will remove the items sliced.
* @param {Number?} total
* @returns {ListItem[]}
*/
slice(total) {
if (total == null)
total = -1;
const items = [];
let next = null;
for (let item = this.head; item; item = next) {
next = item.next;
item.prev = null;
item.next = null;
this.size -= 1;
items.push(item);
if (items.length === total)
break;
}
if (next) {
this.head = next;
next.prev = null;
} else {
this.head = null;
this.tail = null;
}
return items;
}
/**
* Convert the list to an array of items.
* @returns {ListItem[]}
*/
toArray() {
const items = [];
for (let item = this.head; item; item = item.next)
items.push(item);
return items;
}
}
/**
* Convert the list to an array of items.
* @returns {ListItem[]}
*/
List.prototype.toArray = function toArray() {
const items = [];
for (let item = this.head; item; item = item.next)
items.push(item);
return items;
};
/**
* Represents an linked list item.
* List Item
* @alias module:utils.ListItem
* @constructor
* @private
* @param {String} key
* @param {Object} value
*/
function ListItem(value) {
this.next = null;
this.prev = null;
this.value = value;
class ListItem {
/**
* Create a list item.
* @constructor
* @private
* @param {String} key
* @param {Object} value
*/
constructor(value) {
this.next = null;
this.prev = null;
this.value = value;
}
}
/*

View File

@ -10,194 +10,196 @@
const assert = require('assert');
/**
* Represents a mutex lock for locking asynchronous object methods.
* Mutex Lock
* @alias module:utils.Lock
* @constructor
* @param {Boolean?} named - Whether to
* maintain a map of queued jobs by job name.
*/
function Lock(named) {
if (!(this instanceof Lock))
return Lock.create(named);
class Lock {
/**
* Create a lock.
* @constructor
* @param {Boolean?} named - Whether to
* maintain a map of queued jobs by job name.
*/
this.named = named === true;
constructor(named = false) {
this.named = named === true;
this.jobs = [];
this.busy = false;
this.destroyed = false;
this.jobs = [];
this.busy = false;
this.destroyed = false;
this.map = new Map();
this.current = null;
this.map = new Map();
this.current = null;
this.unlocker = this.unlock.bind(this);
this.unlocker = this.unlock.bind(this);
}
/**
* Create a closure scoped lock.
* @param {Boolean?} named
* @returns {Function} Lock method.
*/
static create(named) {
const lock = new Lock(named);
return function _lock(arg1, arg2) {
return lock.lock(arg1, arg2);
};
}
/**
* Test whether the lock has a pending
* job or a job in progress (by name).
* @param {String} name
* @returns {Boolean}
*/
has(name) {
assert(this.named, 'Must use named jobs.');
if (this.current === name)
return true;
return this.pending(name);
}
/**
* Test whether the lock has
* a pending job by name.
* @param {String} name
* @returns {Boolean}
*/
pending(name) {
assert(this.named, 'Must use named jobs.');
const count = this.map.get(name);
if (count == null)
return false;
return count > 0;
}
/**
* Lock the parent object and all its methods
* which use the lock. Begin to queue calls.
* @param {String?} name - Job name.
* @param {Boolean?} force - Bypass the lock.
* @returns {Promise} - Returns {Function}, must be
* called once the method finishes executing in order
* to resolve the queue.
*/
lock(arg1, arg2) {
let name, force;
if (this.named) {
name = arg1 || null;
force = arg2 || false;
} else {
name = null;
force = arg1 || false;
}
if (this.destroyed)
return Promise.reject(new Error('Lock is destroyed.'));
if (force) {
assert(this.busy);
return Promise.resolve(nop);
}
if (this.busy) {
if (name) {
const count = this.map.get(name) || 0;
this.map.set(name, count + 1);
}
return new Promise((resolve, reject) => {
this.jobs.push(new Job(resolve, reject, name));
});
}
this.busy = true;
this.current = name;
return Promise.resolve(this.unlocker);
}
/**
* The actual unlock callback.
* @private
*/
unlock() {
assert(this.destroyed || this.busy);
this.busy = false;
this.current = null;
if (this.jobs.length === 0)
return;
assert(!this.destroyed);
const job = this.jobs.shift();
if (job.name) {
let count = this.map.get(job.name);
assert(count > 0);
if (--count === 0)
this.map.delete(job.name);
else
this.map.set(job.name, count);
}
this.busy = true;
this.current = job.name;
job.resolve(this.unlocker);
}
/**
* Destroy the lock. Purge all pending calls.
*/
destroy() {
assert(!this.destroyed, 'Lock is already destroyed.');
this.destroyed = true;
const jobs = this.jobs;
this.busy = false;
this.jobs = [];
this.map.clear();
this.current = null;
for (const job of jobs)
job.reject(new Error('Lock was destroyed.'));
}
}
/**
* Create a closure scoped lock.
* @param {Boolean?} named
* @returns {Function} Lock method.
*/
Lock.create = function create(named) {
const lock = new Lock(named);
return function _lock(arg1, arg2) {
return lock.lock(arg1, arg2);
};
};
/**
* Test whether the lock has a pending
* job or a job in progress (by name).
* @param {String} name
* @returns {Boolean}
*/
Lock.prototype.has = function has(name) {
assert(this.named, 'Must use named jobs.');
if (this.current === name)
return true;
const count = this.map.get(name);
if (count == null)
return false;
return count > 0;
};
/**
* Test whether the lock has
* a pending job by name.
* @param {String} name
* @returns {Boolean}
*/
Lock.prototype.hasPending = function hasPending(name) {
assert(this.named, 'Must use named jobs.');
const count = this.map.get(name);
if (count == null)
return false;
return count > 0;
};
/**
* Lock the parent object and all its methods
* which use the lock. Begin to queue calls.
* @param {String?} name - Job name.
* @param {Boolean?} force - Bypass the lock.
* @returns {Promise} - Returns {Function}, must be
* called once the method finishes executing in order
* to resolve the queue.
*/
Lock.prototype.lock = function lock(arg1, arg2) {
let name, force;
if (this.named) {
name = arg1 || null;
force = arg2;
} else {
name = null;
force = arg1;
}
if (this.destroyed)
return Promise.reject(new Error('Lock is destroyed.'));
if (force) {
assert(this.busy);
return Promise.resolve(nop);
}
if (this.busy) {
if (name) {
let count = this.map.get(name);
if (!count)
count = 0;
this.map.set(name, count + 1);
}
return new Promise((resolve, reject) => {
this.jobs.push(new Job(resolve, reject, name));
});
}
this.busy = true;
this.current = name;
return Promise.resolve(this.unlocker);
};
/**
* The actual unlock callback.
* @private
*/
Lock.prototype.unlock = function unlock() {
assert(this.destroyed || this.busy);
this.busy = false;
this.current = null;
if (this.jobs.length === 0)
return;
assert(!this.destroyed);
const job = this.jobs.shift();
if (job.name) {
let count = this.map.get(job.name);
assert(count > 0);
if (--count === 0)
this.map.delete(job.name);
else
this.map.set(job.name, count);
}
this.busy = true;
this.current = job.name;
job.resolve(this.unlocker);
};
/**
* Destroy the lock. Purge all pending calls.
*/
Lock.prototype.destroy = function destroy() {
assert(!this.destroyed, 'Lock is already destroyed.');
this.destroyed = true;
const jobs = this.jobs;
this.busy = false;
this.jobs = [];
this.map.clear();
this.current = null;
for (const job of jobs)
job.reject(new Error('Lock was destroyed.'));
};
/**
* Lock Job
* @constructor
* @ignore
* @param {Function} resolve
* @param {Function} reject
* @param {String?} name
*/
function Job(resolve, reject, name) {
this.resolve = resolve;
this.reject = reject;
this.name = name || null;
class Job {
/**
* Create a lock job.
* @constructor
* @param {Function} resolve
* @param {Function} reject
* @param {String?} name
*/
constructor(resolve, reject, name) {
this.resolve = resolve;
this.reject = reject;
this.name = name || null;
}
}
/*

View File

@ -10,489 +10,515 @@
const assert = require('assert');
/**
* An LRU cache, used for caching {@link ChainEntry}s.
* LRU Cache
* @alias module:utils.LRU
* @constructor
* @param {Number} capacity
* @param {Function?} getSize
*/
function LRU(capacity, getSize) {
if (!(this instanceof LRU))
return new LRU(capacity, getSize);
class LRU {
/**
* Create an LRU cache.
* @constructor
* @param {Number} capacity
* @param {Function?} getSize
*/
this.map = new Map();
this.size = 0;
this.items = 0;
this.head = null;
this.tail = null;
this.pending = null;
constructor(capacity, getSize) {
this.map = new Map();
this.size = 0;
this.items = 0;
this.head = null;
this.tail = null;
this.pending = null;
assert(typeof capacity === 'number', 'Capacity must be a number.');
assert(capacity >= 0, 'Capacity cannot be negative.');
assert(!getSize || typeof getSize === 'function', 'Bad size callback.');
assert(typeof capacity === 'number', 'Capacity must be a number.');
assert(capacity >= 0, 'Capacity cannot be negative.');
assert(!getSize || typeof getSize === 'function', 'Bad size callback.');
this.capacity = capacity;
this.getSize = getSize;
this.capacity = capacity;
this.getSize = getSize;
}
/**
* Calculate size of an item.
* @private
* @param {LRUItem} item
* @returns {Number} Size.
*/
_getSize(item) {
if (this.getSize) {
const keySize = Math.floor(item.key.length * 1.375);
return 120 + keySize + this.getSize(item.value);
}
return 1;
}
/**
* Compact the LRU linked list.
* @private
*/
_compact() {
if (this.size <= this.capacity)
return;
let item = null;
let next = null;
for (item = this.head; item; item = next) {
if (this.size <= this.capacity)
break;
this.size -= this._getSize(item);
this.items -= 1;
this.map.delete(item.key);
next = item.next;
item.prev = null;
item.next = null;
}
if (!item) {
this.head = null;
this.tail = null;
return;
}
this.head = item;
item.prev = null;
}
/**
* Reset the cache. Clear all items.
*/
reset() {
let item, next;
for (item = this.head; item; item = next) {
this.map.delete(item.key);
this.items -= 1;
next = item.next;
item.prev = null;
item.next = null;
}
assert(!item);
this.size = 0;
this.head = null;
this.tail = null;
}
/**
* Add an item to the cache.
* @param {String|Number} key
* @param {Object} value
*/
set(key, value) {
if (this.capacity === 0)
return;
key = String(key);
let item = this.map.get(key);
if (item) {
this.size -= this._getSize(item);
item.value = value;
this.size += this._getSize(item);
this._removeList(item);
this._appendList(item);
this._compact();
return;
}
item = new LRUItem(key, value);
this.map.set(key, item);
this._appendList(item);
this.size += this._getSize(item);
this.items += 1;
this._compact();
}
/**
* Retrieve an item from the cache.
* @param {String|Number} key
* @returns {Object} Item.
*/
get(key) {
if (this.capacity === 0)
return null;
key = String(key);
const item = this.map.get(key);
if (!item)
return null;
this._removeList(item);
this._appendList(item);
return item.value;
}
/**
* Test whether the cache contains a key.
* @param {String|Number} key
* @returns {Boolean}
*/
has(key) {
if (this.capacity === 0)
return false;
return this.map.has(String(key));
}
/**
* Remove an item from the cache.
* @param {String|Number} key
* @returns {Boolean} Whether an item was removed.
*/
remove(key) {
if (this.capacity === 0)
return false;
key = String(key);
const item = this.map.get(key);
if (!item)
return false;
this.size -= this._getSize(item);
this.items -= 1;
this.map.delete(key);
this._removeList(item);
return true;
}
/**
* Prepend an item to the linked list (sets new head).
* @private
* @param {LRUItem}
*/
_prependList(item) {
this._insertList(null, item);
}
/**
* Append an item to the linked list (sets new tail).
* @private
* @param {LRUItem}
*/
_appendList(item) {
this._insertList(this.tail, item);
}
/**
* Insert item into the linked list.
* @private
* @param {LRUItem|null} ref
* @param {LRUItem} item
*/
_insertList(ref, item) {
assert(!item.next);
assert(!item.prev);
if (ref == null) {
if (!this.head) {
this.head = item;
this.tail = item;
} else {
this.head.prev = item;
item.next = this.head;
this.head = item;
}
return;
}
item.next = ref.next;
item.prev = ref;
ref.next = item;
if (ref === this.tail)
this.tail = item;
}
/**
* Remove item from the linked list.
* @private
* @param {LRUItem}
*/
_removeList(item) {
if (item.prev)
item.prev.next = item.next;
if (item.next)
item.next.prev = item.prev;
if (item === this.head)
this.head = item.next;
if (item === this.tail)
this.tail = item.prev || this.head;
if (!this.head)
assert(!this.tail);
if (!this.tail)
assert(!this.head);
item.prev = null;
item.next = null;
}
/**
* Collect all keys in the cache, sorted by LRU.
* @returns {String[]}
*/
keys() {
const items = [];
for (let item = this.head; item; item = item.next) {
if (item === this.head)
assert(!item.prev);
if (!item.prev)
assert(item === this.head);
if (!item.next)
assert(item === this.tail);
items.push(item.key);
}
return items;
}
/**
* Collect all values in the cache, sorted by LRU.
* @returns {String[]}
*/
values() {
const items = [];
for (let item = this.head; item; item = item.next)
items.push(item.value);
return items;
}
/**
* Convert the LRU cache to an array of items.
* @returns {Object[]}
*/
toArray() {
const items = [];
for (let item = this.head; item; item = item.next)
items.push(item);
return items;
}
/**
* Create an atomic batch for the lru
* (used for caching database writes).
* @returns {LRUBatch}
*/
batch() {
return new LRUBatch(this);
}
/**
* Start the pending batch.
*/
start() {
assert(!this.pending);
this.pending = this.batch();
}
/**
* Clear the pending batch.
*/
clear() {
assert(this.pending);
this.pending.clear();
}
/**
* Drop the pending batch.
*/
drop() {
assert(this.pending);
this.pending = null;
}
/**
* Commit the pending batch.
*/
commit() {
assert(this.pending);
this.pending.commit();
this.pending = null;
}
/**
* Push an item onto the pending batch.
* @param {String} key
* @param {Object} value
*/
push(key, value) {
assert(this.pending);
if (this.capacity === 0)
return;
this.pending.set(key, value);
}
/**
* Push a removal onto the pending batch.
* @param {String} key
*/
unpush(key) {
assert(this.pending);
if (this.capacity === 0)
return;
this.pending.remove(key);
}
}
/**
* Calculate size of an item.
* @private
* @param {LRUItem} item
* @returns {Number} Size.
*/
LRU.prototype._getSize = function _getSize(item) {
if (this.getSize) {
const keySize = Math.floor(item.key.length * 1.375);
return 120 + keySize + this.getSize(item.value);
}
return 1;
};
/**
* Compact the LRU linked list.
* @private
*/
LRU.prototype._compact = function _compact() {
if (this.size <= this.capacity)
return;
let item, next;
for (item = this.head; item; item = next) {
if (this.size <= this.capacity)
break;
this.size -= this._getSize(item);
this.items--;
this.map.delete(item.key);
next = item.next;
item.prev = null;
item.next = null;
}
if (!item) {
this.head = null;
this.tail = null;
return;
}
this.head = item;
item.prev = null;
};
/**
* Reset the cache. Clear all items.
*/
LRU.prototype.reset = function reset() {
let item, next;
for (item = this.head; item; item = next) {
this.map.delete(item.key);
this.items--;
next = item.next;
item.prev = null;
item.next = null;
}
assert(!item);
this.size = 0;
this.head = null;
this.tail = null;
};
/**
* Add an item to the cache.
* @param {String|Number} key
* @param {Object} value
*/
LRU.prototype.set = function set(key, value) {
if (this.capacity === 0)
return;
key = String(key);
let item = this.map.get(key);
if (item) {
this.size -= this._getSize(item);
item.value = value;
this.size += this._getSize(item);
this._removeList(item);
this._appendList(item);
this._compact();
return;
}
item = new LRUItem(key, value);
this.map.set(key, item);
this._appendList(item);
this.size += this._getSize(item);
this.items++;
this._compact();
};
/**
* Retrieve an item from the cache.
* @param {String|Number} key
* @returns {Object} Item.
*/
LRU.prototype.get = function get(key) {
if (this.capacity === 0)
return null;
key = String(key);
const item = this.map.get(key);
if (!item)
return null;
this._removeList(item);
this._appendList(item);
return item.value;
};
/**
* Test whether the cache contains a key.
* @param {String|Number} key
* @returns {Boolean}
*/
LRU.prototype.has = function has(key) {
if (this.capacity === 0)
return false;
return this.map.has(String(key));
};
/**
* Remove an item from the cache.
* @param {String|Number} key
* @returns {Boolean} Whether an item was removed.
*/
LRU.prototype.remove = function remove(key) {
if (this.capacity === 0)
return false;
key = String(key);
const item = this.map.get(key);
if (!item)
return false;
this.size -= this._getSize(item);
this.items--;
this.map.delete(key);
this._removeList(item);
return true;
};
/**
* Prepend an item to the linked list (sets new head).
* @private
* @param {LRUItem}
*/
LRU.prototype._prependList = function _prependList(item) {
this._insertList(null, item);
};
/**
* Append an item to the linked list (sets new tail).
* @private
* @param {LRUItem}
*/
LRU.prototype._appendList = function _appendList(item) {
this._insertList(this.tail, item);
};
/**
* Insert item into the linked list.
* @private
* @param {LRUItem|null} ref
* @param {LRUItem} item
*/
LRU.prototype._insertList = function _insertList(ref, item) {
assert(!item.next);
assert(!item.prev);
if (ref == null) {
if (!this.head) {
this.head = item;
this.tail = item;
} else {
this.head.prev = item;
item.next = this.head;
this.head = item;
}
return;
}
item.next = ref.next;
item.prev = ref;
ref.next = item;
if (ref === this.tail)
this.tail = item;
};
/**
* Remove item from the linked list.
* @private
* @param {LRUItem}
*/
LRU.prototype._removeList = function _removeList(item) {
if (item.prev)
item.prev.next = item.next;
if (item.next)
item.next.prev = item.prev;
if (item === this.head)
this.head = item.next;
if (item === this.tail)
this.tail = item.prev || this.head;
if (!this.head)
assert(!this.tail);
if (!this.tail)
assert(!this.head);
item.prev = null;
item.next = null;
};
/**
* Collect all keys in the cache, sorted by LRU.
* @returns {String[]}
*/
LRU.prototype.keys = function keys() {
const items = [];
for (let item = this.head; item; item = item.next) {
if (item === this.head)
assert(!item.prev);
if (!item.prev)
assert(item === this.head);
if (!item.next)
assert(item === this.tail);
items.push(item.key);
}
return items;
};
/**
* Collect all values in the cache, sorted by LRU.
* @returns {String[]}
*/
LRU.prototype.values = function values() {
const items = [];
for (let item = this.head; item; item = item.next)
items.push(item.value);
return items;
};
/**
* Convert the LRU cache to an array of items.
* @returns {Object[]}
*/
LRU.prototype.toArray = function toArray() {
const items = [];
for (let item = this.head; item; item = item.next)
items.push(item);
return items;
};
/**
* Create an atomic batch for the lru
* (used for caching database writes).
* @returns {LRUBatch}
*/
LRU.prototype.batch = function batch() {
return new LRUBatch(this);
};
/**
* Start the pending batch.
*/
LRU.prototype.start = function start() {
assert(!this.pending);
this.pending = this.batch();
};
/**
* Clear the pending batch.
*/
LRU.prototype.clear = function clear() {
assert(this.pending);
this.pending.clear();
};
/**
* Drop the pending batch.
*/
LRU.prototype.drop = function drop() {
assert(this.pending);
this.pending = null;
};
/**
* Commit the pending batch.
*/
LRU.prototype.commit = function commit() {
assert(this.pending);
this.pending.commit();
this.pending = null;
};
/**
* Push an item onto the pending batch.
* @param {String} key
* @param {Object} value
*/
LRU.prototype.push = function push(key, value) {
assert(this.pending);
if (this.capacity === 0)
return;
this.pending.set(key, value);
};
/**
* Push a removal onto the pending batch.
* @param {String} key
*/
LRU.prototype.unpush = function unpush(key) {
assert(this.pending);
if (this.capacity === 0)
return;
this.pending.remove(key);
};
/**
* Represents an LRU item.
* LRU Item
* @alias module:utils.LRUItem
* @constructor
* @private
* @param {String} key
* @param {Object} value
*/
function LRUItem(key, value) {
this.key = key;
this.value = value;
this.next = null;
this.prev = null;
class LRUItem {
/**
* Create an LRU item.
* @constructor
* @private
* @param {String} key
* @param {Object} value
*/
constructor(key, value) {
this.key = key;
this.value = value;
this.next = null;
this.prev = null;
}
}
/**
* LRU Batch
* @alias module:utils.LRUBatch
* @constructor
* @param {LRU} lru
*/
function LRUBatch(lru) {
this.lru = lru;
this.ops = [];
}
class LRUBatch {
/**
* Create an LRU batch.
* @constructor
* @param {LRU} lru
*/
/**
* Push an item onto the batch.
* @param {String} key
* @param {Object} value
*/
LRUBatch.prototype.set = function set(key, value) {
this.ops.push(new LRUOp(false, key, value));
};
/**
* Push a removal onto the batch.
* @param {String} key
*/
LRUBatch.prototype.remove = function remove(key) {
this.ops.push(new LRUOp(true, key, null));
};
/**
* Clear the batch.
*/
LRUBatch.prototype.clear = function clear() {
this.ops.length = 0;
};
/**
* Commit the batch.
*/
LRUBatch.prototype.commit = function commit() {
for (const op of this.ops) {
if (op.remove) {
this.lru.remove(op.key);
continue;
}
this.lru.set(op.key, op.value);
constructor(lru) {
this.lru = lru;
this.ops = [];
}
this.ops.length = 0;
};
/**
* Push an item onto the batch.
* @param {String} key
* @param {Object} value
*/
set(key, value) {
this.ops.push(new LRUOp(false, key, value));
}
/**
* Push a removal onto the batch.
* @param {String} key
*/
remove(key) {
this.ops.push(new LRUOp(true, key, null));
}
/**
* Clear the batch.
*/
clear() {
this.ops.length = 0;
}
/**
* Commit the batch.
*/
commit() {
for (const op of this.ops) {
if (op.remove) {
this.lru.remove(op.key);
continue;
}
this.lru.set(op.key, op.value);
}
this.ops.length = 0;
}
}
/**
* LRU Op
* @alias module:utils.LRUOp
* @constructor
* @private
* @param {Boolean} remove
* @param {String} key
* @param {Object} value
*/
function LRUOp(remove, key, value) {
this.remove = remove;
this.key = key;
this.value = value;
class LRUOp {
/**
* Create an LRU op.
* @constructor
* @param {Boolean} remove
* @param {String} key
* @param {Object} value
*/
constructor(remove, key, value) {
this.remove = remove;
this.key = key;
this.value = value;
}
}
/*

View File

@ -10,154 +10,162 @@
const assert = require('assert');
/**
* Represents a mutex lock for locking asynchronous object methods.
* Locks methods according to passed-in key.
* Mapped Lock
* @alias module:utils.MappedLock
* @constructor
*/
function MappedLock() {
if (!(this instanceof MappedLock))
return MappedLock.create();
class MappedLock {
/**
* Create a mapped lock.
* @constructor
*/
this.jobs = new Map();
this.busy = new Set();
this.destroyed = false;
constructor() {
this.jobs = new Map();
this.busy = new Set();
this.destroyed = false;
}
/**
* Create a closure scoped lock.
* @returns {Function} Lock method.
*/
static create() {
const lock = new MappedLock();
return function _lock(key, force) {
return lock.lock(key, force);
};
}
/**
* Test whether the lock has a pending
* job or a job in progress (by name).
* @param {String} name
* @returns {Boolean}
*/
has(name) {
return this.busy.has(name);
}
/**
* Test whether the lock has
* a pending job by name.
* @param {String} name
* @returns {Boolean}
*/
pending(name) {
return this.jobs.has(name);
}
/**
* Lock the parent object and all its methods
* which use the lock with a specified key.
* Begin to queue calls.
* @param {String|Number} key
* @param {Boolean} [force=false] - Force a call.
* @returns {Promise} - Returns {Function}, must be
* called once the method finishes executing in order
* to resolve the queue.
*/
lock(key, force = false) {
if (this.destroyed)
return Promise.reject(new Error('Lock is destroyed.'));
if (key == null)
return Promise.resolve(nop);
if (force) {
assert(this.busy.has(key));
return Promise.resolve(nop);
}
if (this.busy.has(key)) {
return new Promise((resolve, reject) => {
if (!this.jobs.has(key))
this.jobs.set(key, []);
this.jobs.get(key).push(new Job(resolve, reject));
});
}
this.busy.add(key);
return Promise.resolve(this.unlock(key));
}
/**
* Create an unlock callback.
* @private
* @param {String} key
* @returns {Function} Unlocker.
*/
unlock(key) {
const self = this;
return function unlocker() {
const jobs = self.jobs.get(key);
assert(self.destroyed || self.busy.has(key));
self.busy.delete(key);
if (!jobs)
return;
assert(!self.destroyed);
const job = jobs.shift();
assert(job);
if (jobs.length === 0)
self.jobs.delete(key);
self.busy.add(key);
job.resolve(unlocker);
};
}
/**
* Destroy the lock. Purge all pending calls.
*/
destroy() {
assert(!this.destroyed, 'Lock is already destroyed.');
const map = this.jobs;
this.destroyed = true;
this.jobs = new Map();
this.busy = new Map();
for (const jobs of map.values()) {
for (const job of jobs)
job.reject(new Error('Lock was destroyed.'));
}
}
}
/**
* Create a closure scoped lock.
* @returns {Function} Lock method.
*/
MappedLock.create = function create() {
const lock = new MappedLock();
return function _lock(key, force) {
return lock.lock(key, force);
};
};
/**
* Test whether the lock has a pending
* job or a job in progress (by name).
* @param {String} name
* @returns {Boolean}
*/
MappedLock.prototype.has = function has(name) {
return this.busy.has(name);
};
/**
* Test whether the lock has
* a pending job by name.
* @param {String} name
* @returns {Boolean}
*/
MappedLock.prototype.hasPending = function hasPending(name) {
return this.jobs.has(name);
};
/**
* Lock the parent object and all its methods
* which use the lock with a specified key.
* Begin to queue calls.
* @param {String|Number} key
* @param {Boolean?} force - Force a call.
* @returns {Promise} - Returns {Function}, must be
* called once the method finishes executing in order
* to resolve the queue.
*/
MappedLock.prototype.lock = function lock(key, force) {
if (this.destroyed)
return Promise.reject(new Error('Lock is destroyed.'));
if (key == null)
return Promise.resolve(nop);
if (force) {
assert(this.busy.has(key));
return Promise.resolve(nop);
}
if (this.busy.has(key)) {
return new Promise((resolve, reject) => {
if (!this.jobs.has(key))
this.jobs.set(key, []);
this.jobs.get(key).push(new Job(resolve, reject));
});
}
this.busy.add(key);
return Promise.resolve(this.unlock(key));
};
/**
* Create an unlock callback.
* @private
* @param {String} key
* @returns {Function} Unlocker.
*/
MappedLock.prototype.unlock = function unlock(key) {
const self = this;
return function unlocker() {
const jobs = self.jobs.get(key);
assert(self.destroyed || self.busy.has(key));
self.busy.delete(key);
if (!jobs)
return;
assert(!self.destroyed);
const job = jobs.shift();
assert(job);
if (jobs.length === 0)
self.jobs.delete(key);
self.busy.add(key);
job.resolve(unlocker);
};
};
/**
* Destroy the lock. Purge all pending calls.
*/
MappedLock.prototype.destroy = function destroy() {
assert(!this.destroyed, 'Lock is already destroyed.');
const map = this.jobs;
this.destroyed = true;
this.jobs = new Map();
this.busy = new Map();
for (const jobs of map.values()) {
for (const job of jobs)
job.reject(new Error('Lock was destroyed.'));
}
};
/**
* Lock Job
* @constructor
* @ignore
* @param {Function} resolve
* @param {Function} reject
*/
function Job(resolve, reject) {
this.resolve = resolve;
this.reject = reject;
class Job {
/**
* Create a lock job.
* @constructor
* @param {Function} resolve
* @param {Function} reject
*/
constructor(resolve, reject) {
this.resolve = resolve;
this.reject = reject;
}
}
/*

View File

@ -10,210 +10,209 @@ const assert = require('assert');
const AsyncEmitter = require('../utils/asyncemitter');
/**
* NodeClient
* Sort of a fake local client for separation of concerns.
* Node Client
* @alias module:node.NodeClient
* @constructor
*/
function NodeClient(node) {
if (!(this instanceof NodeClient))
return new NodeClient(node);
class NodeClient extends AsyncEmitter {
/**
* Create a node client.
* @constructor
*/
AsyncEmitter.call(this);
constructor(node) {
super();
this.node = node;
this.network = node.network;
this.filter = null;
this.opened = false;
this.node = node;
this.network = node.network;
this.filter = null;
this.opened = false;
this.init();
this.init();
}
/**
* Initialize the client.
*/
init() {
this.node.on('connect', (entry, block) => {
if (!this.opened)
return;
this.emit('block connect', entry, block.txs);
});
this.node.on('disconnect', (entry, block) => {
if (!this.opened)
return;
this.emit('block disconnect', entry);
});
this.node.on('tx', (tx) => {
if (!this.opened)
return;
this.emit('tx', tx);
});
this.node.on('reset', (tip) => {
if (!this.opened)
return;
this.emit('chain reset', tip);
});
}
/**
* Open the client.
* @returns {Promise}
*/
async open(options) {
assert(!this.opened, 'NodeClient is already open.');
this.opened = true;
setImmediate(() => this.emit('connect'));
}
/**
* Close the client.
* @returns {Promise}
*/
async close() {
assert(this.opened, 'NodeClient is not open.');
this.opened = false;
setImmediate(() => this.emit('disconnect'));
}
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
bind(type, handler) {
return this.on(type, handler);
}
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
hook(type, handler) {
return this.on(type, handler);
}
/**
* Get chain tip.
* @returns {Promise}
*/
async getTip() {
return this.node.chain.tip;
}
/**
* Get chain entry.
* @param {Hash} hash
* @returns {Promise}
*/
async getEntry(hash) {
const entry = await this.node.chain.getEntry(hash);
if (!entry)
return null;
if (!await this.node.chain.isMainChain(entry))
return null;
return entry;
}
/**
* Send a transaction. Do not wait for promise.
* @param {TX} tx
* @returns {Promise}
*/
async send(tx) {
this.node.relay(tx);
}
/**
* Set bloom filter.
* @param {Bloom} filter
* @returns {Promise}
*/
async setFilter(filter) {
this.filter = filter;
this.node.pool.setFilter(filter);
}
/**
* Add data to filter.
* @param {Buffer} data
* @returns {Promise}
*/
async addFilter(data) {
this.node.pool.queueFilterLoad();
}
/**
* Reset filter.
* @returns {Promise}
*/
async resetFilter() {
this.node.pool.queueFilterLoad();
}
/**
* Esimate smart fee.
* @param {Number?} blocks
* @returns {Promise}
*/
async estimateFee(blocks) {
if (!this.node.fees)
return this.network.feeRate;
return this.node.fees.estimateFee(blocks);
}
/**
* Get hash range.
* @param {Number} start
* @param {Number} end
* @returns {Promise}
*/
async getHashes(start = -1, end = -1) {
return this.node.chain.getHashes(start, end);
}
/**
* Rescan for any missed transactions.
* @param {Number|Hash} start - Start block.
* @param {Bloom} filter
* @param {Function} iter - Iterator.
* @returns {Promise}
*/
async rescan(start) {
return this.node.chain.scan(start, this.filter, (entry, txs) => {
return this.emitAsync('block rescan', entry, txs);
});
}
}
Object.setPrototypeOf(NodeClient.prototype, AsyncEmitter.prototype);
/**
* Initialize the client.
* @returns {Promise}
*/
NodeClient.prototype.init = function init() {
this.node.on('connect', (entry, block) => {
if (!this.opened)
return;
this.emit('block connect', entry, block.txs);
});
this.node.on('disconnect', (entry, block) => {
if (!this.opened)
return;
this.emit('block disconnect', entry);
});
this.node.on('tx', (tx) => {
if (!this.opened)
return;
this.emit('tx', tx);
});
this.node.on('reset', (tip) => {
if (!this.opened)
return;
this.emit('chain reset', tip);
});
};
/**
* Open the client.
* @returns {Promise}
*/
NodeClient.prototype.open = async function open(options) {
assert(!this.opened, 'NodeClient is already open.');
this.opened = true;
setImmediate(() => this.emit('connect'));
};
/**
* Close the client.
* @returns {Promise}
*/
NodeClient.prototype.close = async function close() {
assert(this.opened, 'NodeClient is not open.');
this.opened = false;
setImmediate(() => this.emit('disconnect'));
};
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
NodeClient.prototype.bind = function bind(type, handler) {
return this.on(type, handler);
};
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
NodeClient.prototype.hook = function hook(type, handler) {
return this.on(type, handler);
};
/**
* Get chain tip.
* @returns {Promise}
*/
NodeClient.prototype.getTip = async function getTip() {
return this.node.chain.tip;
};
/**
* Get chain entry.
* @param {Hash} hash
* @returns {Promise}
*/
NodeClient.prototype.getEntry = async function getEntry(hash) {
const entry = await this.node.chain.getEntry(hash);
if (!entry)
return null;
if (!await this.node.chain.isMainChain(entry))
return null;
return entry;
};
/**
* Send a transaction. Do not wait for promise.
* @param {TX} tx
* @returns {Promise}
*/
NodeClient.prototype.send = async function send(tx) {
this.node.relay(tx);
};
/**
* Set bloom filter.
* @param {Bloom} filter
* @returns {Promise}
*/
NodeClient.prototype.setFilter = async function setFilter(filter) {
this.filter = filter;
this.node.pool.setFilter(filter);
};
/**
* Add data to filter.
* @param {Buffer} data
* @returns {Promise}
*/
NodeClient.prototype.addFilter = async function addFilter(data) {
this.node.pool.queueFilterLoad();
};
/**
* Reset filter.
* @returns {Promise}
*/
NodeClient.prototype.resetFilter = async function resetFilter() {
this.node.pool.queueFilterLoad();
};
/**
* Esimate smart fee.
* @param {Number?} blocks
* @returns {Promise}
*/
NodeClient.prototype.estimateFee = async function estimateFee(blocks) {
if (!this.node.fees)
return this.network.feeRate;
return this.node.fees.estimateFee(blocks);
};
/**
* Get hash range.
* @param {Number} start
* @param {Number} end
* @returns {Promise}
*/
NodeClient.prototype.getHashes = async function getHashes(start = -1, end = -1) {
return this.node.chain.getHashes(start, end);
};
/**
* Rescan for any missed transactions.
* @param {Number|Hash} start - Start block.
* @param {Bloom} filter
* @param {Function} iter - Iterator.
* @returns {Promise}
*/
NodeClient.prototype.rescan = async function rescan(start) {
return this.node.chain.scan(start, this.filter, (entry, txs) => {
return this.emitAsync('block rescan', entry, txs);
});
};
/*
* Expose
*/