123
This commit is contained in:
23
node_modules/generic-pool/lib/DefaultEvictor.js
generated
vendored
Normal file
23
node_modules/generic-pool/lib/DefaultEvictor.js
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
|
||||
class DefaultEvictor {
|
||||
evict(config, pooledResource, availableObjectsCount) {
|
||||
const idleTime = Date.now() - pooledResource.lastIdleTime;
|
||||
|
||||
if (
|
||||
config.softIdleTimeoutMillis > 0 &&
|
||||
config.softIdleTimeoutMillis < idleTime &&
|
||||
config.min < availableObjectsCount
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (config.idleTimeoutMillis < idleTime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DefaultEvictor;
|
||||
49
node_modules/generic-pool/lib/Deferred.js
generated
vendored
Normal file
49
node_modules/generic-pool/lib/Deferred.js
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This is apparently a bit like a Jquery deferred, hence the name
|
||||
*/
|
||||
|
||||
class Deferred {
|
||||
constructor(Promise) {
|
||||
this._state = Deferred.PENDING;
|
||||
this._resolve = undefined;
|
||||
this._reject = undefined;
|
||||
|
||||
this._promise = new Promise((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
get promise() {
|
||||
return this._promise;
|
||||
}
|
||||
|
||||
reject(reason) {
|
||||
if (this._state !== Deferred.PENDING) {
|
||||
return;
|
||||
}
|
||||
this._state = Deferred.REJECTED;
|
||||
this._reject(reason);
|
||||
}
|
||||
|
||||
resolve(value) {
|
||||
if (this._state !== Deferred.PENDING) {
|
||||
return;
|
||||
}
|
||||
this._state = Deferred.FULFILLED;
|
||||
this._resolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should these really live here? or be a seperate 'state' enum
|
||||
Deferred.PENDING = "PENDING";
|
||||
Deferred.FULFILLED = "FULFILLED";
|
||||
Deferred.REJECTED = "REJECTED";
|
||||
|
||||
module.exports = Deferred;
|
||||
106
node_modules/generic-pool/lib/Deque.js
generated
vendored
Normal file
106
node_modules/generic-pool/lib/Deque.js
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
"use strict";
|
||||
|
||||
const DoublyLinkedList = require("./DoublyLinkedList");
|
||||
const DequeIterator = require("./DequeIterator");
|
||||
/**
|
||||
* DoublyLinkedList backed double ended queue
|
||||
* implements just enough to keep the Pool
|
||||
*/
|
||||
class Deque {
|
||||
constructor() {
|
||||
this._list = new DoublyLinkedList();
|
||||
}
|
||||
|
||||
/**
|
||||
* removes and returns the first element from the queue
|
||||
* @return {any} [description]
|
||||
*/
|
||||
shift() {
|
||||
if (this.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const node = this._list.head;
|
||||
this._list.remove(node);
|
||||
|
||||
return node.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds one elemts to the beginning of the queue
|
||||
* @param {any} element [description]
|
||||
* @return {any} [description]
|
||||
*/
|
||||
unshift(element) {
|
||||
const node = DoublyLinkedList.createNode(element);
|
||||
|
||||
this._list.insertBeginning(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds one to the end of the queue
|
||||
* @param {any} element [description]
|
||||
* @return {any} [description]
|
||||
*/
|
||||
push(element) {
|
||||
const node = DoublyLinkedList.createNode(element);
|
||||
|
||||
this._list.insertEnd(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes and returns the last element from the queue
|
||||
*/
|
||||
pop() {
|
||||
if (this.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const node = this._list.tail;
|
||||
this._list.remove(node);
|
||||
|
||||
return node.data;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return new DequeIterator(this._list);
|
||||
}
|
||||
|
||||
iterator() {
|
||||
return new DequeIterator(this._list);
|
||||
}
|
||||
|
||||
reverseIterator() {
|
||||
return new DequeIterator(this._list, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a reference to the item at the head of the queue
|
||||
* @return {any} [description]
|
||||
*/
|
||||
get head() {
|
||||
if (this.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const node = this._list.head;
|
||||
return node.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a reference to the item at the tail of the queue
|
||||
* @return {any} [description]
|
||||
*/
|
||||
get tail() {
|
||||
if (this.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const node = this._list.tail;
|
||||
return node.data;
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._list.length;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Deque;
|
||||
20
node_modules/generic-pool/lib/DequeIterator.js
generated
vendored
Normal file
20
node_modules/generic-pool/lib/DequeIterator.js
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
"use strict";
|
||||
|
||||
const DoublyLinkedListIterator = require("./DoublyLinkedListIterator");
|
||||
/**
|
||||
* Thin wrapper around an underlying DDL iterator
|
||||
*/
|
||||
class DequeIterator extends DoublyLinkedListIterator {
|
||||
next() {
|
||||
const result = super.next();
|
||||
|
||||
// unwrap the node...
|
||||
if (result.value) {
|
||||
result.value = result.value.data;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DequeIterator;
|
||||
94
node_modules/generic-pool/lib/DoublyLinkedList.js
generated
vendored
Normal file
94
node_modules/generic-pool/lib/DoublyLinkedList.js
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A Doubly Linked List, because there aren't enough in the world...
|
||||
* this is pretty much a direct JS port of the one wikipedia
|
||||
* https://en.wikipedia.org/wiki/Doubly_linked_list
|
||||
*
|
||||
* For most usage 'insertBeginning' and 'insertEnd' should be enough
|
||||
*
|
||||
* nodes are expected to something like a POJSO like
|
||||
* {
|
||||
* prev: null,
|
||||
* next: null,
|
||||
* something: 'whatever you like'
|
||||
* }
|
||||
*/
|
||||
class DoublyLinkedList {
|
||||
constructor() {
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
this.length = 0;
|
||||
}
|
||||
|
||||
insertBeginning(node) {
|
||||
if (this.head === null) {
|
||||
this.head = node;
|
||||
this.tail = node;
|
||||
node.prev = null;
|
||||
node.next = null;
|
||||
this.length++;
|
||||
} else {
|
||||
this.insertBefore(this.head, node);
|
||||
}
|
||||
}
|
||||
|
||||
insertEnd(node) {
|
||||
if (this.tail === null) {
|
||||
this.insertBeginning(node);
|
||||
} else {
|
||||
this.insertAfter(this.tail, node);
|
||||
}
|
||||
}
|
||||
|
||||
insertAfter(node, newNode) {
|
||||
newNode.prev = node;
|
||||
newNode.next = node.next;
|
||||
if (node.next === null) {
|
||||
this.tail = newNode;
|
||||
} else {
|
||||
node.next.prev = newNode;
|
||||
}
|
||||
node.next = newNode;
|
||||
this.length++;
|
||||
}
|
||||
|
||||
insertBefore(node, newNode) {
|
||||
newNode.prev = node.prev;
|
||||
newNode.next = node;
|
||||
if (node.prev === null) {
|
||||
this.head = newNode;
|
||||
} else {
|
||||
node.prev.next = newNode;
|
||||
}
|
||||
node.prev = newNode;
|
||||
this.length++;
|
||||
}
|
||||
|
||||
remove(node) {
|
||||
if (node.prev === null) {
|
||||
this.head = node.next;
|
||||
} else {
|
||||
node.prev.next = node.next;
|
||||
}
|
||||
if (node.next === null) {
|
||||
this.tail = node.prev;
|
||||
} else {
|
||||
node.next.prev = node.prev;
|
||||
}
|
||||
node.prev = null;
|
||||
node.next = null;
|
||||
this.length--;
|
||||
}
|
||||
|
||||
// FIXME: this should not live here and has become a dumping ground...
|
||||
static createNode(data) {
|
||||
return {
|
||||
prev: null,
|
||||
next: null,
|
||||
data: data
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DoublyLinkedList;
|
||||
100
node_modules/generic-pool/lib/DoublyLinkedListIterator.js
generated
vendored
Normal file
100
node_modules/generic-pool/lib/DoublyLinkedListIterator.js
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Creates an interator for a DoublyLinkedList starting at the given node
|
||||
* It's internal cursor will remains relative to the last "iterated" node as that
|
||||
* node moves through the list until it either iterates to the end of the list,
|
||||
* or the the node it's tracking is removed from the list. Until the first 'next'
|
||||
* call it tracks the head/tail of the linked list. This means that one can create
|
||||
* an iterator on an empty list, then add nodes, and then the iterator will follow
|
||||
* those nodes. Because the DoublyLinkedList nodes don't track their owning "list" and
|
||||
* it's highly inefficient to walk the list for every iteration, the iterator won't know
|
||||
* if the node has been detached from one List and added to another list, or if the iterator
|
||||
*
|
||||
* The created object is an es6 compatible iterator
|
||||
*/
|
||||
class DoublyLinkedListIterator {
|
||||
/**
|
||||
* @param {Object} doublyLinkedList a node that is part of a doublyLinkedList
|
||||
* @param {Boolean} [reverse=false] is this a reverse iterator? default: false
|
||||
*/
|
||||
constructor(doublyLinkedList, reverse) {
|
||||
this._list = doublyLinkedList;
|
||||
// NOTE: these key names are tied to the DoublyLinkedListIterator
|
||||
this._direction = reverse === true ? "prev" : "next";
|
||||
this._startPosition = reverse === true ? "tail" : "head";
|
||||
this._started = false;
|
||||
this._cursor = null;
|
||||
this._done = false;
|
||||
}
|
||||
|
||||
_start() {
|
||||
this._cursor = this._list[this._startPosition];
|
||||
this._started = true;
|
||||
}
|
||||
|
||||
_advanceCursor() {
|
||||
if (this._started === false) {
|
||||
this._started = true;
|
||||
this._cursor = this._list[this._startPosition];
|
||||
return;
|
||||
}
|
||||
this._cursor = this._cursor[this._direction];
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._done = false;
|
||||
this._started = false;
|
||||
this._cursor = null;
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (
|
||||
this._started === false ||
|
||||
this._done === true ||
|
||||
this._isCursorDetached()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
this._list.remove(this._cursor);
|
||||
}
|
||||
|
||||
next() {
|
||||
if (this._done === true) {
|
||||
return { done: true };
|
||||
}
|
||||
|
||||
this._advanceCursor();
|
||||
|
||||
// if there is no node at the cursor or the node at the cursor is no longer part of
|
||||
// a doubly linked list then we are done/finished/kaput
|
||||
if (this._cursor === null || this._isCursorDetached()) {
|
||||
this._done = true;
|
||||
return { done: true };
|
||||
}
|
||||
|
||||
return {
|
||||
value: this._cursor,
|
||||
done: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the node detached from a list?
|
||||
* NOTE: you can trick/bypass/confuse this check by removing a node from one DoublyLinkedList
|
||||
* and adding it to another.
|
||||
* TODO: We can make this smarter by checking the direction of travel and only checking
|
||||
* the required next/prev/head/tail rather than all of them
|
||||
* @return {Boolean} [description]
|
||||
*/
|
||||
_isCursorDetached() {
|
||||
return (
|
||||
this._cursor.prev === null &&
|
||||
this._cursor.next === null &&
|
||||
this._list.tail !== this._cursor &&
|
||||
this._list.head !== this._cursor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DoublyLinkedListIterator;
|
||||
744
node_modules/generic-pool/lib/Pool.js
generated
vendored
Normal file
744
node_modules/generic-pool/lib/Pool.js
generated
vendored
Normal file
@ -0,0 +1,744 @@
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("events").EventEmitter;
|
||||
|
||||
const factoryValidator = require("./factoryValidator");
|
||||
const PoolOptions = require("./PoolOptions");
|
||||
const ResourceRequest = require("./ResourceRequest");
|
||||
const ResourceLoan = require("./ResourceLoan");
|
||||
const PooledResource = require("./PooledResource");
|
||||
const DefaultEvictor = require("./DefaultEvictor");
|
||||
const Deque = require("./Deque");
|
||||
const Deferred = require("./Deferred");
|
||||
const PriorityQueue = require("./PriorityQueue");
|
||||
const DequeIterator = require("./DequeIterator");
|
||||
|
||||
const reflector = require("./utils").reflector;
|
||||
|
||||
/**
|
||||
* TODO: move me
|
||||
*/
|
||||
const FACTORY_CREATE_ERROR = "factoryCreateError";
|
||||
const FACTORY_DESTROY_ERROR = "factoryDestroyError";
|
||||
|
||||
class Pool extends EventEmitter {
|
||||
/**
|
||||
* Generate an Object pool with a specified `factory` and `config`.
|
||||
*
|
||||
* @param {typeof DefaultEvictor} Evictor
|
||||
* @param {typeof Deque} Deque
|
||||
* @param {typeof PriorityQueue} PriorityQueue
|
||||
* @param {Object} factory
|
||||
* Factory to be used for generating and destroying the items.
|
||||
* @param {Function} factory.create
|
||||
* Should create the item to be acquired,
|
||||
* and call it's first callback argument with the generated item as it's argument.
|
||||
* @param {Function} factory.destroy
|
||||
* Should gently close any resources that the item is using.
|
||||
* Called before the items is destroyed.
|
||||
* @param {Function} factory.validate
|
||||
* Test if a resource is still valid .Should return a promise that resolves to a boolean, true if resource is still valid and false
|
||||
* If it should be removed from pool.
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(Evictor, Deque, PriorityQueue, factory, options) {
|
||||
super();
|
||||
|
||||
factoryValidator(factory);
|
||||
|
||||
this._config = new PoolOptions(options);
|
||||
|
||||
// TODO: fix up this ugly glue-ing
|
||||
this._Promise = this._config.Promise;
|
||||
|
||||
this._factory = factory;
|
||||
this._draining = false;
|
||||
this._started = false;
|
||||
/**
|
||||
* Holds waiting clients
|
||||
* @type {PriorityQueue}
|
||||
*/
|
||||
this._waitingClientsQueue = new PriorityQueue(this._config.priorityRange);
|
||||
|
||||
/**
|
||||
* Collection of promises for resource creation calls made by the pool to factory.create
|
||||
* @type {Set}
|
||||
*/
|
||||
this._factoryCreateOperations = new Set();
|
||||
|
||||
/**
|
||||
* Collection of promises for resource destruction calls made by the pool to factory.destroy
|
||||
* @type {Set}
|
||||
*/
|
||||
this._factoryDestroyOperations = new Set();
|
||||
|
||||
/**
|
||||
* A queue/stack of pooledResources awaiting acquisition
|
||||
* TODO: replace with LinkedList backed array
|
||||
* @type {Deque}
|
||||
*/
|
||||
this._availableObjects = new Deque();
|
||||
|
||||
/**
|
||||
* Collection of references for any resource that are undergoing validation before being acquired
|
||||
* @type {Set}
|
||||
*/
|
||||
this._testOnBorrowResources = new Set();
|
||||
|
||||
/**
|
||||
* Collection of references for any resource that are undergoing validation before being returned
|
||||
* @type {Set}
|
||||
*/
|
||||
this._testOnReturnResources = new Set();
|
||||
|
||||
/**
|
||||
* Collection of promises for any validations currently in process
|
||||
* @type {Set}
|
||||
*/
|
||||
this._validationOperations = new Set();
|
||||
|
||||
/**
|
||||
* All objects associated with this pool in any state (except destroyed)
|
||||
* @type {Set}
|
||||
*/
|
||||
this._allObjects = new Set();
|
||||
|
||||
/**
|
||||
* Loans keyed by the borrowed resource
|
||||
* @type {Map}
|
||||
*/
|
||||
this._resourceLoans = new Map();
|
||||
|
||||
/**
|
||||
* Infinitely looping iterator over available object
|
||||
* @type {DequeIterator}
|
||||
*/
|
||||
this._evictionIterator = this._availableObjects.iterator();
|
||||
|
||||
this._evictor = new Evictor();
|
||||
|
||||
/**
|
||||
* handle for setTimeout for next eviction run
|
||||
* @type {(number|null)}
|
||||
*/
|
||||
this._scheduledEviction = null;
|
||||
|
||||
// create initial resources (if factory.min > 0)
|
||||
if (this._config.autostart === true) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
_destroy(pooledResource) {
|
||||
// FIXME: do we need another state for "in destruction"?
|
||||
pooledResource.invalidate();
|
||||
this._allObjects.delete(pooledResource);
|
||||
// NOTE: this maybe very bad promise usage?
|
||||
const destroyPromise = this._factory.destroy(pooledResource.obj);
|
||||
const wrappedDestroyPromise = this._config.destroyTimeoutMillis
|
||||
? this._Promise.resolve(this._applyDestroyTimeout(destroyPromise))
|
||||
: this._Promise.resolve(destroyPromise);
|
||||
|
||||
this._trackOperation(
|
||||
wrappedDestroyPromise,
|
||||
this._factoryDestroyOperations
|
||||
).catch(reason => {
|
||||
this.emit(FACTORY_DESTROY_ERROR, reason);
|
||||
});
|
||||
|
||||
// TODO: maybe ensuring minimum pool size should live outside here
|
||||
this._ensureMinimum();
|
||||
}
|
||||
|
||||
_applyDestroyTimeout(promise) {
|
||||
const timeoutPromise = new this._Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error("destroy timed out"));
|
||||
}, this._config.destroyTimeoutMillis).unref();
|
||||
});
|
||||
return this._Promise.race([timeoutPromise, promise]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to move an available resource into test and then onto a waiting client
|
||||
* @return {Boolean} could we move an available resource into test
|
||||
*/
|
||||
_testOnBorrow() {
|
||||
if (this._availableObjects.length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pooledResource = this._availableObjects.shift();
|
||||
// Mark the resource as in test
|
||||
pooledResource.test();
|
||||
this._testOnBorrowResources.add(pooledResource);
|
||||
const validationPromise = this._factory.validate(pooledResource.obj);
|
||||
const wrappedValidationPromise = this._Promise.resolve(validationPromise);
|
||||
|
||||
this._trackOperation(
|
||||
wrappedValidationPromise,
|
||||
this._validationOperations
|
||||
).then(isValid => {
|
||||
this._testOnBorrowResources.delete(pooledResource);
|
||||
|
||||
if (isValid === false) {
|
||||
pooledResource.invalidate();
|
||||
this._destroy(pooledResource);
|
||||
this._dispense();
|
||||
return;
|
||||
}
|
||||
this._dispatchPooledResourceToNextWaitingClient(pooledResource);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to move an available resource to a waiting client
|
||||
* @return {Boolean} [description]
|
||||
*/
|
||||
_dispatchResource() {
|
||||
if (this._availableObjects.length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pooledResource = this._availableObjects.shift();
|
||||
this._dispatchPooledResourceToNextWaitingClient(pooledResource);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to resolve an outstanding resource request using an available resource from
|
||||
* the pool, or creating new ones
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_dispense() {
|
||||
/**
|
||||
* Local variables for ease of reading/writing
|
||||
* these don't (shouldn't) change across the execution of this fn
|
||||
*/
|
||||
const numWaitingClients = this._waitingClientsQueue.length;
|
||||
|
||||
// If there aren't any waiting requests then there is nothing to do
|
||||
// so lets short-circuit
|
||||
if (numWaitingClients < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resourceShortfall =
|
||||
numWaitingClients - this._potentiallyAllocableResourceCount;
|
||||
|
||||
const actualNumberOfResourcesToCreate = Math.min(
|
||||
this.spareResourceCapacity,
|
||||
resourceShortfall
|
||||
);
|
||||
for (let i = 0; actualNumberOfResourcesToCreate > i; i++) {
|
||||
this._createResource();
|
||||
}
|
||||
|
||||
// If we are doing test-on-borrow see how many more resources need to be moved into test
|
||||
// to help satisfy waitingClients
|
||||
if (this._config.testOnBorrow === true) {
|
||||
// how many available resources do we need to shift into test
|
||||
const desiredNumberOfResourcesToMoveIntoTest =
|
||||
numWaitingClients - this._testOnBorrowResources.size;
|
||||
const actualNumberOfResourcesToMoveIntoTest = Math.min(
|
||||
this._availableObjects.length,
|
||||
desiredNumberOfResourcesToMoveIntoTest
|
||||
);
|
||||
for (let i = 0; actualNumberOfResourcesToMoveIntoTest > i; i++) {
|
||||
this._testOnBorrow();
|
||||
}
|
||||
}
|
||||
|
||||
// if we aren't testing-on-borrow then lets try to allocate what we can
|
||||
if (this._config.testOnBorrow === false) {
|
||||
const actualNumberOfResourcesToDispatch = Math.min(
|
||||
this._availableObjects.length,
|
||||
numWaitingClients
|
||||
);
|
||||
for (let i = 0; actualNumberOfResourcesToDispatch > i; i++) {
|
||||
this._dispatchResource();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a pooledResource to the next waiting client (if any) else
|
||||
* puts the PooledResource back on the available list
|
||||
* @param {PooledResource} pooledResource [description]
|
||||
* @return {Boolean} [description]
|
||||
*/
|
||||
_dispatchPooledResourceToNextWaitingClient(pooledResource) {
|
||||
const clientResourceRequest = this._waitingClientsQueue.dequeue();
|
||||
if (
|
||||
clientResourceRequest === undefined ||
|
||||
clientResourceRequest.state !== Deferred.PENDING
|
||||
) {
|
||||
// While we were away either all the waiting clients timed out
|
||||
// or were somehow fulfilled. put our pooledResource back.
|
||||
this._addPooledResourceToAvailableObjects(pooledResource);
|
||||
// TODO: do need to trigger anything before we leave?
|
||||
return false;
|
||||
}
|
||||
const loan = new ResourceLoan(pooledResource, this._Promise);
|
||||
this._resourceLoans.set(pooledResource.obj, loan);
|
||||
pooledResource.allocate();
|
||||
clientResourceRequest.resolve(pooledResource.obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* tracks on operation using given set
|
||||
* handles adding/removing from the set and resolve/rejects the value/reason
|
||||
* @param {Promise} operation
|
||||
* @param {Set} set Set holding operations
|
||||
* @return {Promise} Promise that resolves once operation has been removed from set
|
||||
*/
|
||||
_trackOperation(operation, set) {
|
||||
set.add(operation);
|
||||
|
||||
return operation.then(
|
||||
v => {
|
||||
set.delete(operation);
|
||||
return this._Promise.resolve(v);
|
||||
},
|
||||
e => {
|
||||
set.delete(operation);
|
||||
return this._Promise.reject(e);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_createResource() {
|
||||
// An attempt to create a resource
|
||||
const factoryPromise = this._factory.create();
|
||||
const wrappedFactoryPromise = this._Promise
|
||||
.resolve(factoryPromise)
|
||||
.then(resource => {
|
||||
const pooledResource = new PooledResource(resource);
|
||||
this._allObjects.add(pooledResource);
|
||||
this._addPooledResourceToAvailableObjects(pooledResource);
|
||||
});
|
||||
|
||||
this._trackOperation(wrappedFactoryPromise, this._factoryCreateOperations)
|
||||
.then(() => {
|
||||
this._dispense();
|
||||
// Stop bluebird complaining about this side-effect only handler
|
||||
// - a promise was created in a handler but was not returned from it
|
||||
// https://goo.gl/rRqMUw
|
||||
return null;
|
||||
})
|
||||
.catch(reason => {
|
||||
this.emit(FACTORY_CREATE_ERROR, reason);
|
||||
this._dispense();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_ensureMinimum() {
|
||||
if (this._draining === true) {
|
||||
return;
|
||||
}
|
||||
const minShortfall = this._config.min - this._count;
|
||||
for (let i = 0; i < minShortfall; i++) {
|
||||
this._createResource();
|
||||
}
|
||||
}
|
||||
|
||||
_evict() {
|
||||
const testsToRun = Math.min(
|
||||
this._config.numTestsPerEvictionRun,
|
||||
this._availableObjects.length
|
||||
);
|
||||
const evictionConfig = {
|
||||
softIdleTimeoutMillis: this._config.softIdleTimeoutMillis,
|
||||
idleTimeoutMillis: this._config.idleTimeoutMillis,
|
||||
min: this._config.min
|
||||
};
|
||||
for (let testsHaveRun = 0; testsHaveRun < testsToRun; ) {
|
||||
const iterationResult = this._evictionIterator.next();
|
||||
|
||||
// Safety check incase we could get stuck in infinite loop because we
|
||||
// somehow emptied the array after checking its length.
|
||||
if (iterationResult.done === true && this._availableObjects.length < 1) {
|
||||
this._evictionIterator.reset();
|
||||
return;
|
||||
}
|
||||
// If this happens it should just mean we reached the end of the
|
||||
// list and can reset the cursor.
|
||||
if (iterationResult.done === true && this._availableObjects.length > 0) {
|
||||
this._evictionIterator.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
const resource = iterationResult.value;
|
||||
|
||||
const shouldEvict = this._evictor.evict(
|
||||
evictionConfig,
|
||||
resource,
|
||||
this._availableObjects.length
|
||||
);
|
||||
testsHaveRun++;
|
||||
|
||||
if (shouldEvict === true) {
|
||||
// take it out of the _availableObjects list
|
||||
this._evictionIterator.remove();
|
||||
this._destroy(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_scheduleEvictorRun() {
|
||||
// Start eviction if set
|
||||
if (this._config.evictionRunIntervalMillis > 0) {
|
||||
// @ts-ignore
|
||||
this._scheduledEviction = setTimeout(() => {
|
||||
this._evict();
|
||||
this._scheduleEvictorRun();
|
||||
}, this._config.evictionRunIntervalMillis).unref();
|
||||
}
|
||||
}
|
||||
|
||||
_descheduleEvictorRun() {
|
||||
if (this._scheduledEviction) {
|
||||
clearTimeout(this._scheduledEviction);
|
||||
}
|
||||
this._scheduledEviction = null;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this._draining === true) {
|
||||
return;
|
||||
}
|
||||
if (this._started === true) {
|
||||
return;
|
||||
}
|
||||
this._started = true;
|
||||
this._scheduleEvictorRun();
|
||||
this._ensureMinimum();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a new resource. The callback will be called,
|
||||
* when a new resource is available, passing the resource to the callback.
|
||||
* TODO: should we add a seperate "acquireWithPriority" function
|
||||
*
|
||||
* @param {Number} [priority=0]
|
||||
* Optional. Integer between 0 and (priorityRange - 1). Specifies the priority
|
||||
* of the caller if there are no available resources. Lower numbers mean higher
|
||||
* priority.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
acquire(priority) {
|
||||
if (this._started === false && this._config.autostart === false) {
|
||||
this.start();
|
||||
}
|
||||
|
||||
if (this._draining) {
|
||||
return this._Promise.reject(
|
||||
new Error("pool is draining and cannot accept work")
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: should we defer this check till after this event loop incase "the situation" changes in the meantime
|
||||
if (
|
||||
this.spareResourceCapacity < 1 &&
|
||||
this._availableObjects.length < 1 &&
|
||||
this._config.maxWaitingClients !== undefined &&
|
||||
this._waitingClientsQueue.length >= this._config.maxWaitingClients
|
||||
) {
|
||||
return this._Promise.reject(
|
||||
new Error("max waitingClients count exceeded")
|
||||
);
|
||||
}
|
||||
|
||||
const resourceRequest = new ResourceRequest(
|
||||
this._config.acquireTimeoutMillis,
|
||||
this._Promise
|
||||
);
|
||||
this._waitingClientsQueue.enqueue(resourceRequest, priority);
|
||||
this._dispense();
|
||||
|
||||
return resourceRequest.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* [use method, aquires a resource, passes the resource to a user supplied function and releases it]
|
||||
* @param {Function} fn [a function that accepts a resource and returns a promise that resolves/rejects once it has finished using the resource]
|
||||
* @return {Promise} [resolves once the resource is released to the pool]
|
||||
*/
|
||||
use(fn, priority) {
|
||||
return this.acquire(priority).then(resource => {
|
||||
return fn(resource).then(
|
||||
result => {
|
||||
this.release(resource);
|
||||
return result;
|
||||
},
|
||||
err => {
|
||||
this.destroy(resource);
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if resource is currently on loan from the pool
|
||||
*
|
||||
* @param {Function} resource
|
||||
* Resource for checking.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* True if resource belongs to this pool and false otherwise
|
||||
*/
|
||||
isBorrowedResource(resource) {
|
||||
return this._resourceLoans.has(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resource to the pool when it is no longer required.
|
||||
*
|
||||
* @param {Object} resource
|
||||
* The acquired object to be put back to the pool.
|
||||
*/
|
||||
release(resource) {
|
||||
// check for an outstanding loan
|
||||
const loan = this._resourceLoans.get(resource);
|
||||
|
||||
if (loan === undefined) {
|
||||
return this._Promise.reject(
|
||||
new Error("Resource not currently part of this pool")
|
||||
);
|
||||
}
|
||||
|
||||
this._resourceLoans.delete(resource);
|
||||
loan.resolve();
|
||||
const pooledResource = loan.pooledResource;
|
||||
|
||||
pooledResource.deallocate();
|
||||
this._addPooledResourceToAvailableObjects(pooledResource);
|
||||
|
||||
this._dispense();
|
||||
return this._Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the resource to be destroyed. The factory's destroy handler
|
||||
* will also be called.
|
||||
*
|
||||
* This should be called within an acquire() block as an alternative to release().
|
||||
*
|
||||
* @param {Object} resource
|
||||
* The acquired resource to be destoyed.
|
||||
*/
|
||||
destroy(resource) {
|
||||
// check for an outstanding loan
|
||||
const loan = this._resourceLoans.get(resource);
|
||||
|
||||
if (loan === undefined) {
|
||||
return this._Promise.reject(
|
||||
new Error("Resource not currently part of this pool")
|
||||
);
|
||||
}
|
||||
|
||||
this._resourceLoans.delete(resource);
|
||||
loan.resolve();
|
||||
const pooledResource = loan.pooledResource;
|
||||
|
||||
pooledResource.deallocate();
|
||||
this._destroy(pooledResource);
|
||||
|
||||
this._dispense();
|
||||
return this._Promise.resolve();
|
||||
}
|
||||
|
||||
_addPooledResourceToAvailableObjects(pooledResource) {
|
||||
pooledResource.idle();
|
||||
if (this._config.fifo === true) {
|
||||
this._availableObjects.push(pooledResource);
|
||||
} else {
|
||||
this._availableObjects.unshift(pooledResource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disallow any new acquire calls and let the request backlog dissapate.
|
||||
* The Pool will no longer attempt to maintain a "min" number of resources
|
||||
* and will only make new resources on demand.
|
||||
* Resolves once all resource requests are fulfilled and all resources are returned to pool and available...
|
||||
* Should probably be called "drain work"
|
||||
* @returns {Promise}
|
||||
*/
|
||||
drain() {
|
||||
this._draining = true;
|
||||
return this.__allResourceRequestsSettled()
|
||||
.then(() => {
|
||||
return this.__allResourcesReturned();
|
||||
})
|
||||
.then(() => {
|
||||
this._descheduleEvictorRun();
|
||||
});
|
||||
}
|
||||
|
||||
__allResourceRequestsSettled() {
|
||||
if (this._waitingClientsQueue.length > 0) {
|
||||
// wait for last waiting client to be settled
|
||||
// FIXME: what if they can "resolve" out of order....?
|
||||
return reflector(this._waitingClientsQueue.tail.promise);
|
||||
}
|
||||
return this._Promise.resolve();
|
||||
}
|
||||
|
||||
// FIXME: this is a horrific mess
|
||||
__allResourcesReturned() {
|
||||
const ps = Array.from(this._resourceLoans.values())
|
||||
.map(loan => loan.promise)
|
||||
.map(reflector);
|
||||
return this._Promise.all(ps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly destroys all available resources regardless of timeout. Intended to be
|
||||
* invoked as part of a drain. Does not prevent the creation of new
|
||||
* resources as a result of subsequent calls to acquire.
|
||||
*
|
||||
* Note that if factory.min > 0 and the pool isn't "draining", the pool will destroy all idle resources
|
||||
* in the pool, but replace them with newly created resources up to the
|
||||
* specified factory.min value. If this is not desired, set factory.min
|
||||
* to zero before calling clear()
|
||||
*
|
||||
*/
|
||||
clear() {
|
||||
const reflectedCreatePromises = Array.from(
|
||||
this._factoryCreateOperations
|
||||
).map(reflector);
|
||||
|
||||
// wait for outstanding factory.create to complete
|
||||
return this._Promise.all(reflectedCreatePromises).then(() => {
|
||||
// Destroy existing resources
|
||||
// @ts-ignore
|
||||
for (const resource of this._availableObjects) {
|
||||
this._destroy(resource);
|
||||
}
|
||||
const reflectedDestroyPromises = Array.from(
|
||||
this._factoryDestroyOperations
|
||||
).map(reflector);
|
||||
return reflector(this._Promise.all(reflectedDestroyPromises));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the pool is ready.
|
||||
* We define ready by checking if the current resource number is at least
|
||||
* the minimum number defined.
|
||||
* @returns {Promise} that resolves when the minimum number is ready.
|
||||
*/
|
||||
ready() {
|
||||
return new this._Promise(resolve => {
|
||||
const isReady = () => {
|
||||
if (this.available >= this.min) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(isReady, 100);
|
||||
}
|
||||
};
|
||||
|
||||
isReady();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* How many resources are available to allocated
|
||||
* (includes resources that have not been tested and may faul validation)
|
||||
* NOTE: internal for now as the name is awful and might not be useful to anyone
|
||||
* @return {Number} number of resources the pool has to allocate
|
||||
*/
|
||||
get _potentiallyAllocableResourceCount() {
|
||||
return (
|
||||
this._availableObjects.length +
|
||||
this._testOnBorrowResources.size +
|
||||
this._testOnReturnResources.size +
|
||||
this._factoryCreateOperations.size
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The combined count of the currently created objects and those in the
|
||||
* process of being created
|
||||
* Does NOT include resources in the process of being destroyed
|
||||
* sort of legacy...
|
||||
* @return {Number}
|
||||
*/
|
||||
get _count() {
|
||||
return this._allObjects.size + this._factoryCreateOperations.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many more resources does the pool have room for
|
||||
* @return {Number} number of resources the pool could create before hitting any limits
|
||||
*/
|
||||
get spareResourceCapacity() {
|
||||
return (
|
||||
this._config.max -
|
||||
(this._allObjects.size + this._factoryCreateOperations.size)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* see _count above
|
||||
* @return {Number} [description]
|
||||
*/
|
||||
get size() {
|
||||
return this._count;
|
||||
}
|
||||
|
||||
/**
|
||||
* number of available resources
|
||||
* @return {Number} [description]
|
||||
*/
|
||||
get available() {
|
||||
return this._availableObjects.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* number of resources that are currently acquired
|
||||
* @return {Number} [description]
|
||||
*/
|
||||
get borrowed() {
|
||||
return this._resourceLoans.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* number of waiting acquire calls
|
||||
* @return {Number} [description]
|
||||
*/
|
||||
get pending() {
|
||||
return this._waitingClientsQueue.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* maximum size of the pool
|
||||
* @return {Number} [description]
|
||||
*/
|
||||
get max() {
|
||||
return this._config.max;
|
||||
}
|
||||
|
||||
/**
|
||||
* minimum size of the pool
|
||||
* @return {Number} [description]
|
||||
*/
|
||||
get min() {
|
||||
return this._config.min;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Pool;
|
||||
34
node_modules/generic-pool/lib/PoolDefaults.js
generated
vendored
Normal file
34
node_modules/generic-pool/lib/PoolDefaults.js
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Create the default settings used by the pool
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
class PoolDefaults {
|
||||
constructor() {
|
||||
this.fifo = true;
|
||||
this.priorityRange = 1;
|
||||
|
||||
this.testOnBorrow = false;
|
||||
this.testOnReturn = false;
|
||||
|
||||
this.autostart = true;
|
||||
|
||||
this.evictionRunIntervalMillis = 0;
|
||||
this.numTestsPerEvictionRun = 3;
|
||||
this.softIdleTimeoutMillis = -1;
|
||||
this.idleTimeoutMillis = 30000;
|
||||
|
||||
// FIXME: no defaults!
|
||||
this.acquireTimeoutMillis = null;
|
||||
this.destroyTimeoutMillis = null;
|
||||
this.maxWaitingClients = null;
|
||||
|
||||
this.min = null;
|
||||
this.max = null;
|
||||
// FIXME: this seems odd?
|
||||
this.Promise = Promise;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PoolDefaults;
|
||||
109
node_modules/generic-pool/lib/PoolOptions.js
generated
vendored
Normal file
109
node_modules/generic-pool/lib/PoolOptions.js
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
"use strict";
|
||||
|
||||
const PoolDefaults = require("./PoolDefaults");
|
||||
|
||||
class PoolOptions {
|
||||
/**
|
||||
* @param {Object} opts
|
||||
* configuration for the pool
|
||||
* @param {Number} [opts.max=null]
|
||||
* Maximum number of items that can exist at the same time. Default: 1.
|
||||
* Any further acquire requests will be pushed to the waiting list.
|
||||
* @param {Number} [opts.min=null]
|
||||
* Minimum number of items in pool (including in-use). Default: 0.
|
||||
* When the pool is created, or a resource destroyed, this minimum will
|
||||
* be checked. If the pool resource count is below the minimum, a new
|
||||
* resource will be created and added to the pool.
|
||||
* @param {Number} [opts.maxWaitingClients=null]
|
||||
* maximum number of queued requests allowed after which acquire calls will be rejected
|
||||
* @param {Boolean} [opts.testOnBorrow=false]
|
||||
* should the pool validate resources before giving them to clients. Requires that
|
||||
* `factory.validate` is specified.
|
||||
* @param {Boolean} [opts.testOnReturn=false]
|
||||
* should the pool validate resources before returning them to the pool.
|
||||
* @param {Number} [opts.acquireTimeoutMillis=null]
|
||||
* Delay in milliseconds after which the an `acquire` call will fail. optional.
|
||||
* Default: undefined. Should be positive and non-zero
|
||||
* @param {Number} [opts.destroyTimeoutMillis=null]
|
||||
* Delay in milliseconds after which the an `destroy` call will fail, causing it to emit a factoryDestroyError event. optional.
|
||||
* Default: undefined. Should be positive and non-zero
|
||||
* @param {Number} [opts.priorityRange=1]
|
||||
* The range from 1 to be treated as a valid priority
|
||||
* @param {Boolean} [opts.fifo=true]
|
||||
* Sets whether the pool has LIFO (last in, first out) behaviour with respect to idle objects.
|
||||
* if false then pool has FIFO behaviour
|
||||
* @param {Boolean} [opts.autostart=true]
|
||||
* Should the pool start creating resources etc once the constructor is called
|
||||
* @param {Number} [opts.evictionRunIntervalMillis=0]
|
||||
* How often to run eviction checks. Default: 0 (does not run).
|
||||
* @param {Number} [opts.numTestsPerEvictionRun=3]
|
||||
* Number of resources to check each eviction run. Default: 3.
|
||||
* @param {Number} [opts.softIdleTimeoutMillis=-1]
|
||||
* amount of time an object may sit idle in the pool before it is eligible
|
||||
* for eviction by the idle object evictor (if any), with the extra condition
|
||||
* that at least "min idle" object instances remain in the pool. Default -1 (nothing can get evicted)
|
||||
* @param {Number} [opts.idleTimeoutMillis=30000]
|
||||
* the minimum amount of time that an object may sit idle in the pool before it is eligible for eviction
|
||||
* due to idle time. Supercedes "softIdleTimeoutMillis" Default: 30000
|
||||
* @param {typeof Promise} [opts.Promise=Promise]
|
||||
* What promise implementation should the pool use, defaults to native promises.
|
||||
*/
|
||||
constructor(opts) {
|
||||
const poolDefaults = new PoolDefaults();
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
this.fifo = typeof opts.fifo === "boolean" ? opts.fifo : poolDefaults.fifo;
|
||||
this.priorityRange = opts.priorityRange || poolDefaults.priorityRange;
|
||||
|
||||
this.testOnBorrow =
|
||||
typeof opts.testOnBorrow === "boolean"
|
||||
? opts.testOnBorrow
|
||||
: poolDefaults.testOnBorrow;
|
||||
this.testOnReturn =
|
||||
typeof opts.testOnReturn === "boolean"
|
||||
? opts.testOnReturn
|
||||
: poolDefaults.testOnReturn;
|
||||
|
||||
this.autostart =
|
||||
typeof opts.autostart === "boolean"
|
||||
? opts.autostart
|
||||
: poolDefaults.autostart;
|
||||
|
||||
if (opts.acquireTimeoutMillis) {
|
||||
// @ts-ignore
|
||||
this.acquireTimeoutMillis = parseInt(opts.acquireTimeoutMillis, 10);
|
||||
}
|
||||
|
||||
if (opts.destroyTimeoutMillis) {
|
||||
// @ts-ignore
|
||||
this.destroyTimeoutMillis = parseInt(opts.destroyTimeoutMillis, 10);
|
||||
}
|
||||
|
||||
if (opts.maxWaitingClients !== undefined) {
|
||||
// @ts-ignore
|
||||
this.maxWaitingClients = parseInt(opts.maxWaitingClients, 10);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
this.max = parseInt(opts.max, 10);
|
||||
// @ts-ignore
|
||||
this.min = parseInt(opts.min, 10);
|
||||
|
||||
this.max = Math.max(isNaN(this.max) ? 1 : this.max, 1);
|
||||
this.min = Math.min(isNaN(this.min) ? 0 : this.min, this.max);
|
||||
|
||||
this.evictionRunIntervalMillis =
|
||||
opts.evictionRunIntervalMillis || poolDefaults.evictionRunIntervalMillis;
|
||||
this.numTestsPerEvictionRun =
|
||||
opts.numTestsPerEvictionRun || poolDefaults.numTestsPerEvictionRun;
|
||||
this.softIdleTimeoutMillis =
|
||||
opts.softIdleTimeoutMillis || poolDefaults.softIdleTimeoutMillis;
|
||||
this.idleTimeoutMillis =
|
||||
opts.idleTimeoutMillis || poolDefaults.idleTimeoutMillis;
|
||||
|
||||
this.Promise = opts.Promise != null ? opts.Promise : poolDefaults.Promise;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PoolOptions;
|
||||
49
node_modules/generic-pool/lib/PooledResource.js
generated
vendored
Normal file
49
node_modules/generic-pool/lib/PooledResource.js
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
|
||||
const PooledResourceStateEnum = require("./PooledResourceStateEnum");
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @private
|
||||
*/
|
||||
class PooledResource {
|
||||
constructor(resource) {
|
||||
this.creationTime = Date.now();
|
||||
this.lastReturnTime = null;
|
||||
this.lastBorrowTime = null;
|
||||
this.lastIdleTime = null;
|
||||
this.obj = resource;
|
||||
this.state = PooledResourceStateEnum.IDLE;
|
||||
}
|
||||
|
||||
// mark the resource as "allocated"
|
||||
allocate() {
|
||||
this.lastBorrowTime = Date.now();
|
||||
this.state = PooledResourceStateEnum.ALLOCATED;
|
||||
}
|
||||
|
||||
// mark the resource as "deallocated"
|
||||
deallocate() {
|
||||
this.lastReturnTime = Date.now();
|
||||
this.state = PooledResourceStateEnum.IDLE;
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
this.state = PooledResourceStateEnum.INVALID;
|
||||
}
|
||||
|
||||
test() {
|
||||
this.state = PooledResourceStateEnum.VALIDATION;
|
||||
}
|
||||
|
||||
idle() {
|
||||
this.lastIdleTime = Date.now();
|
||||
this.state = PooledResourceStateEnum.IDLE;
|
||||
}
|
||||
|
||||
returning() {
|
||||
this.state = PooledResourceStateEnum.RETURNING;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PooledResource;
|
||||
11
node_modules/generic-pool/lib/PooledResourceStateEnum.js
generated
vendored
Normal file
11
node_modules/generic-pool/lib/PooledResourceStateEnum.js
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
const PooledResourceStateEnum = {
|
||||
ALLOCATED: "ALLOCATED", // In use
|
||||
IDLE: "IDLE", // In the queue, not in use.
|
||||
INVALID: "INVALID", // Failed validation
|
||||
RETURNING: "RETURNING", // Resource is in process of returning
|
||||
VALIDATION: "VALIDATION" // Currently being tested
|
||||
};
|
||||
|
||||
module.exports = PooledResourceStateEnum;
|
||||
69
node_modules/generic-pool/lib/PriorityQueue.js
generated
vendored
Normal file
69
node_modules/generic-pool/lib/PriorityQueue.js
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
"use strict";
|
||||
|
||||
const Queue = require("./Queue");
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @private
|
||||
*/
|
||||
class PriorityQueue {
|
||||
constructor(size) {
|
||||
this._size = Math.max(+size | 0, 1);
|
||||
/** @type {Queue[]} */
|
||||
this._slots = [];
|
||||
// initialize arrays to hold queue elements
|
||||
for (let i = 0; i < this._size; i++) {
|
||||
this._slots.push(new Queue());
|
||||
}
|
||||
}
|
||||
|
||||
get length() {
|
||||
let _length = 0;
|
||||
for (let i = 0, slots = this._slots.length; i < slots; i++) {
|
||||
_length += this._slots[i].length;
|
||||
}
|
||||
return _length;
|
||||
}
|
||||
|
||||
enqueue(obj, priority) {
|
||||
// Convert to integer with a default value of 0.
|
||||
priority = (priority && +priority | 0) || 0;
|
||||
|
||||
if (priority) {
|
||||
if (priority < 0 || priority >= this._size) {
|
||||
priority = this._size - 1;
|
||||
// put obj at the end of the line
|
||||
}
|
||||
}
|
||||
this._slots[priority].push(obj);
|
||||
}
|
||||
|
||||
dequeue() {
|
||||
for (let i = 0, sl = this._slots.length; i < sl; i += 1) {
|
||||
if (this._slots[i].length) {
|
||||
return this._slots[i].shift();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
get head() {
|
||||
for (let i = 0, sl = this._slots.length; i < sl; i += 1) {
|
||||
if (this._slots[i].length > 0) {
|
||||
return this._slots[i].head;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
get tail() {
|
||||
for (let i = this._slots.length - 1; i >= 0; i--) {
|
||||
if (this._slots[i].length > 0) {
|
||||
return this._slots[i].tail;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PriorityQueue;
|
||||
35
node_modules/generic-pool/lib/Queue.js
generated
vendored
Normal file
35
node_modules/generic-pool/lib/Queue.js
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
"use strict";
|
||||
|
||||
const DoublyLinkedList = require("./DoublyLinkedList");
|
||||
const Deque = require("./Deque");
|
||||
|
||||
/**
|
||||
* Sort of a internal queue for holding the waiting
|
||||
* resource requets for a given "priority".
|
||||
* Also handles managing timeouts rejections on items (is this the best place for this?)
|
||||
* This is the last point where we know which queue a resourceRequest is in
|
||||
*
|
||||
*/
|
||||
class Queue extends Deque {
|
||||
/**
|
||||
* Adds the obj to the end of the list for this slot
|
||||
* we completely override the parent method because we need access to the
|
||||
* node for our rejection handler
|
||||
* @param {any} resourceRequest [description]
|
||||
*/
|
||||
push(resourceRequest) {
|
||||
const node = DoublyLinkedList.createNode(resourceRequest);
|
||||
resourceRequest.promise.catch(this._createTimeoutRejectionHandler(node));
|
||||
this._list.insertEnd(node);
|
||||
}
|
||||
|
||||
_createTimeoutRejectionHandler(node) {
|
||||
return reason => {
|
||||
if (reason.name === "TimeoutError") {
|
||||
this._list.remove(node);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Queue;
|
||||
29
node_modules/generic-pool/lib/ResourceLoan.js
generated
vendored
Normal file
29
node_modules/generic-pool/lib/ResourceLoan.js
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
|
||||
const Deferred = require("./Deferred");
|
||||
|
||||
/**
|
||||
* Plan is to maybe add tracking via Error objects
|
||||
* and other fun stuff!
|
||||
*/
|
||||
|
||||
class ResourceLoan extends Deferred {
|
||||
/**
|
||||
*
|
||||
* @param {any} pooledResource the PooledResource this loan belongs to
|
||||
* @return {any} [description]
|
||||
*/
|
||||
constructor(pooledResource, Promise) {
|
||||
super(Promise);
|
||||
this._creationTimestamp = Date.now();
|
||||
this.pooledResource = pooledResource;
|
||||
}
|
||||
|
||||
reject() {
|
||||
/**
|
||||
* Loans can only be resolved at the moment
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ResourceLoan;
|
||||
76
node_modules/generic-pool/lib/ResourceRequest.js
generated
vendored
Normal file
76
node_modules/generic-pool/lib/ResourceRequest.js
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
"use strict";
|
||||
|
||||
const Deferred = require("./Deferred");
|
||||
const errors = require("./errors");
|
||||
|
||||
function fbind(fn, ctx) {
|
||||
return function bound() {
|
||||
return fn.apply(ctx, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a users request for a resource
|
||||
* Basically a promise mashed in with a timeout
|
||||
* @private
|
||||
*/
|
||||
class ResourceRequest extends Deferred {
|
||||
/**
|
||||
* [constructor description]
|
||||
* @param {Number} ttl timeout
|
||||
*/
|
||||
constructor(ttl, Promise) {
|
||||
super(Promise);
|
||||
this._creationTimestamp = Date.now();
|
||||
this._timeout = null;
|
||||
|
||||
if (ttl !== undefined) {
|
||||
this.setTimeout(ttl);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(delay) {
|
||||
if (this._state !== ResourceRequest.PENDING) {
|
||||
return;
|
||||
}
|
||||
const ttl = parseInt(delay, 10);
|
||||
|
||||
if (isNaN(ttl) || ttl <= 0) {
|
||||
throw new Error("delay must be a positive int");
|
||||
}
|
||||
|
||||
const age = Date.now() - this._creationTimestamp;
|
||||
|
||||
if (this._timeout) {
|
||||
this.removeTimeout();
|
||||
}
|
||||
|
||||
this._timeout = setTimeout(
|
||||
fbind(this._fireTimeout, this),
|
||||
Math.max(ttl - age, 0)
|
||||
);
|
||||
}
|
||||
|
||||
removeTimeout() {
|
||||
if (this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
}
|
||||
this._timeout = null;
|
||||
}
|
||||
|
||||
_fireTimeout() {
|
||||
this.reject(new errors.TimeoutError("ResourceRequest timed out"));
|
||||
}
|
||||
|
||||
reject(reason) {
|
||||
this.removeTimeout();
|
||||
super.reject(reason);
|
||||
}
|
||||
|
||||
resolve(value) {
|
||||
this.removeTimeout();
|
||||
super.resolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ResourceRequest;
|
||||
27
node_modules/generic-pool/lib/errors.js
generated
vendored
Normal file
27
node_modules/generic-pool/lib/errors.js
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
"use strict";
|
||||
|
||||
class ExtendableError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
// @ts-ignore
|
||||
this.name = this.constructor.name;
|
||||
this.message = message;
|
||||
if (typeof Error.captureStackTrace === "function") {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
} else {
|
||||
this.stack = new Error(message).stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable no-useless-constructor */
|
||||
class TimeoutError extends ExtendableError {
|
||||
constructor(m) {
|
||||
super(m);
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-useless-constructor */
|
||||
|
||||
module.exports = {
|
||||
TimeoutError: TimeoutError
|
||||
};
|
||||
16
node_modules/generic-pool/lib/factoryValidator.js
generated
vendored
Normal file
16
node_modules/generic-pool/lib/factoryValidator.js
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
module.exports = function(factory) {
|
||||
if (typeof factory.create !== "function") {
|
||||
throw new TypeError("factory.create must be a function");
|
||||
}
|
||||
|
||||
if (typeof factory.destroy !== "function") {
|
||||
throw new TypeError("factory.destroy must be a function");
|
||||
}
|
||||
|
||||
if (
|
||||
typeof factory.validate !== "undefined" &&
|
||||
typeof factory.validate !== "function"
|
||||
) {
|
||||
throw new TypeError("factory.validate must be a function");
|
||||
}
|
||||
};
|
||||
13
node_modules/generic-pool/lib/utils.js
generated
vendored
Normal file
13
node_modules/generic-pool/lib/utils.js
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
"use strict";
|
||||
|
||||
function noop() {}
|
||||
|
||||
/**
|
||||
* Reflects a promise but does not expose any
|
||||
* underlying value or rejection from that promise.
|
||||
* @param {Promise} promise [description]
|
||||
* @return {Promise} [description]
|
||||
*/
|
||||
exports.reflector = function(promise) {
|
||||
return promise.then(noop, noop);
|
||||
};
|
||||
Reference in New Issue
Block a user