tarn
Advanced tools
Comparing version 1.1.5 to 2.0.0
@@ -1,4 +0,4 @@ | ||
/// <reference types="node" /> | ||
import { PendingOperation } from './PendingOperation'; | ||
import { Resource } from './Resource'; | ||
import { EventEmitter } from 'events'; | ||
export interface PoolOptions<T> { | ||
@@ -26,2 +26,3 @@ create: CallbackOrPromise<T>; | ||
protected pendingAcquires: PendingOperation<T>[]; | ||
protected pendingDestroys: PendingOperation<T>[]; | ||
protected interval: NodeJS.Timer | null; | ||
@@ -40,2 +41,4 @@ protected destroyed: boolean; | ||
protected validate: (resource: T) => boolean; | ||
protected eventId: number; | ||
protected emitter: EventEmitter; | ||
constructor(opt: PoolOptions<T>); | ||
@@ -51,2 +54,18 @@ numUsed(): number; | ||
destroy(): Promise<import("./PromiseInspection").PromiseInspection<{}> | import("./PromiseInspection").PromiseInspection<void>>; | ||
on(eventName: 'acquireRequest', handler: (eventId: number) => void): void; | ||
on(eventName: 'acquireSuccess', handler: (eventId: number, resource: T) => void): void; | ||
on(eventName: 'acquireFail', handler: (eventId: number, err: Error) => void): void; | ||
on(eventName: 'release', handler: (resource: T) => void): void; | ||
on(eventName: 'createRequest', handler: (eventId: number) => void): void; | ||
on(eventName: 'createSuccess', handler: (eventId: number, resource: T) => void): void; | ||
on(eventName: 'createFail', handler: (eventId: number, err: Error) => void): void; | ||
on(eventName: 'destroyRequest', handler: (eventId: number, resource: T) => void): void; | ||
on(eventName: 'destroySuccess', handler: (eventId: number, resource: T) => void): void; | ||
on(eventName: 'destroyFail', handler: (eventId: number, resource: T, err: Error) => void): void; | ||
on(eventName: 'startReaping', handler: () => void): void; | ||
on(eventName: 'stopReaping', handler: () => void): void; | ||
on(eventName: 'poolDestroyRequest', handler: (eventId: number) => void): void; | ||
on(eventName: 'poolDestroySuccess', handler: (eventId: number) => void): void; | ||
removeListener(event: string | symbol, listener: (...args: any[]) => void): void; | ||
removeAllListeners(event?: string | symbol | undefined): void; | ||
_tryAcquireOrCreate(): void; | ||
@@ -60,8 +79,9 @@ _hasFreeResources(): boolean; | ||
_create(): PendingOperation<T>; | ||
_destroy(resource: T): Promise<any>; | ||
_logError(err: Error): void; | ||
_destroy(resource: T): Promise<void | T>; | ||
_logDestroyerError(eventId: number, resource: T, err: Error): void; | ||
_startReaping(): void; | ||
_stopReaping(): void; | ||
_executeEventHandlers(eventName: string, ...args: any): void; | ||
} | ||
export declare type Callback<T> = (err: Error | null, resource: T) => any; | ||
export declare type CallbackOrPromise<T> = (cb: Callback<T>) => any | (() => Promise<T>); |
132
lib/Pool.js
@@ -6,5 +6,7 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
const events_1 = require("events"); | ||
class Pool { | ||
constructor(opt) { | ||
this.destroyed = false; | ||
this.emitter = new events_1.EventEmitter(); | ||
opt = opt || {}; | ||
@@ -45,2 +47,22 @@ if (!opt.create) { | ||
} | ||
const allowedKeys = { | ||
create: true, | ||
validate: true, | ||
destroy: true, | ||
log: true, | ||
min: true, | ||
max: true, | ||
acquireTimeoutMillis: true, | ||
createTimeoutMillis: true, | ||
destroyTimeoutMillis: true, | ||
idleTimeoutMillis: true, | ||
reapIntervalMillis: true, | ||
createRetryIntervalMillis: true, | ||
propagateCreateError: true | ||
}; | ||
for (let key of Object.keys(opt)) { | ||
if (!allowedKeys[key]) { | ||
throw new Error(`Tarn: unsupported option opt.${key}`); | ||
} | ||
} | ||
this.creator = opt.create; | ||
@@ -63,4 +85,6 @@ this.destroyer = opt.destroy; | ||
this.pendingAcquires = []; | ||
this.pendingDestroys = []; | ||
this.destroyed = false; | ||
this.interval = null; | ||
this.eventId = 1; | ||
} | ||
@@ -80,2 +104,4 @@ numUsed() { | ||
acquire() { | ||
const eventId = this.eventId++; | ||
this._executeEventHandlers('acquireRequest', eventId); | ||
const pendingAcquire = new PendingOperation_1.PendingOperation(this.acquireTimeoutMillis); | ||
@@ -85,3 +111,9 @@ this.pendingAcquires.push(pendingAcquire); | ||
// remove it from the pending queue. | ||
pendingAcquire.promise = pendingAcquire.promise.catch(err => { | ||
pendingAcquire.promise = pendingAcquire.promise | ||
.then(resource => { | ||
this._executeEventHandlers('acquireSuccess', eventId, resource); | ||
return resource; | ||
}) | ||
.catch(err => { | ||
this._executeEventHandlers('acquireFail', eventId, err); | ||
remove(this.pendingAcquires, pendingAcquire); | ||
@@ -94,2 +126,3 @@ return Promise.reject(err); | ||
release(resource) { | ||
this._executeEventHandlers('release', resource); | ||
for (let i = 0, l = this.used.length; i < l; ++i) { | ||
@@ -116,3 +149,3 @@ const used = this.used[i]; | ||
this.free.forEach(free => { | ||
if (utils_1.duration(timestamp, free.timestamp) > this.idleTimeoutMillis && | ||
if (utils_1.duration(timestamp, free.timestamp) >= this.idleTimeoutMillis && | ||
numDestroyed < maxDestroy) { | ||
@@ -134,2 +167,4 @@ numDestroyed++; | ||
destroy() { | ||
const eventId = this.eventId++; | ||
this._executeEventHandlers('poolDestroyRequest', eventId); | ||
this._stopReaping(); | ||
@@ -155,6 +190,23 @@ this.destroyed = true; | ||
.then(() => { | ||
// Also wait rest of the pending destroys to finish | ||
return Promise.all(this.pendingDestroys.map(pd => pd.promise)); | ||
}) | ||
.then(() => { | ||
this.free = []; | ||
this.pendingAcquires = []; | ||
})); | ||
})).then(res => { | ||
this._executeEventHandlers('poolDestroySuccess', eventId); | ||
this.emitter.removeAllListeners(); | ||
return res; | ||
}); | ||
} | ||
on(event, listener) { | ||
this.emitter.on(event, listener); | ||
} | ||
removeListener(event, listener) { | ||
this.emitter.removeListener(event, listener); | ||
} | ||
removeAllListeners(event) { | ||
this.emitter.removeAllListeners(event); | ||
} | ||
_tryAcquireOrCreate() { | ||
@@ -243,2 +295,4 @@ if (this.destroyed) { | ||
_create() { | ||
const eventId = this.eventId++; | ||
this._executeEventHandlers('createRequest', eventId); | ||
const pendingCreate = new PendingOperation_1.PendingOperation(this.createTimeoutMillis); | ||
@@ -252,2 +306,3 @@ this.pendingCreates.push(pendingCreate); | ||
pendingCreate.resolve(resource); | ||
this._executeEventHandlers('createSuccess', eventId, resource); | ||
return null; | ||
@@ -259,2 +314,3 @@ }) | ||
pendingCreate.reject(err); | ||
this._executeEventHandlers('createFail', eventId, err); | ||
return null; | ||
@@ -265,29 +321,31 @@ }); | ||
_destroy(resource) { | ||
try { | ||
// this.destroyer can be both synchronous and asynchronous. | ||
// When it's synchronous, errors are handled by the try/catch | ||
// When it's asynchronous, errors are handled by .catch() | ||
const retVal = this.destroyer(resource); | ||
if (retVal && retVal.then && retVal.catch) { | ||
const pendingDestroy = new PendingOperation_1.PendingOperation(this.destroyTimeoutMillis); | ||
retVal | ||
.then(() => { | ||
pendingDestroy.resolve(resource); | ||
}) | ||
.catch((err) => { | ||
pendingDestroy.reject(err); | ||
}); | ||
// In case of an error there's nothing we can do here but log it. | ||
return pendingDestroy.promise.catch(err => this._logError(err)); | ||
} | ||
return Promise.resolve(retVal); | ||
} | ||
catch (err) { | ||
// There's nothing we can do here but log the error. This would otherwise | ||
// leak out as an unhandled exception. | ||
this._logError(err); | ||
return Promise.resolve(); | ||
} | ||
const eventId = this.eventId++; | ||
this._executeEventHandlers('destroyRequest', eventId, resource); | ||
// this.destroyer can be both synchronous and asynchronous. | ||
// so we wrap it to promise to get all exceptions through same pipeline | ||
const pendingDestroy = new PendingOperation_1.PendingOperation(this.destroyTimeoutMillis); | ||
const retVal = Promise.resolve().then(() => this.destroyer(resource)); | ||
retVal | ||
.then(() => { | ||
pendingDestroy.resolve(resource); | ||
}) | ||
.catch((err) => { | ||
pendingDestroy.reject(err); | ||
}); | ||
this.pendingDestroys.push(pendingDestroy); | ||
// In case of an error there's nothing we can do here but log it. | ||
return pendingDestroy.promise | ||
.then(res => { | ||
this._executeEventHandlers('destroySuccess', eventId, resource); | ||
return res; | ||
}) | ||
.catch(err => this._logDestroyerError(eventId, resource, err)) | ||
.then(res => { | ||
const index = this.pendingDestroys.findIndex(pd => pd === pendingDestroy); | ||
this.pendingDestroys.splice(index, 1); | ||
return res; | ||
}); | ||
} | ||
_logError(err) { | ||
_logDestroyerError(eventId, resource, err) { | ||
this._executeEventHandlers('destroyFail', eventId, resource, err); | ||
this.log('Tarn: resource destroyer threw an exception ' + err.stack, 'warn'); | ||
@@ -297,2 +355,3 @@ } | ||
if (!this.interval) { | ||
this._executeEventHandlers('startReaping'); | ||
this.interval = setInterval(() => this.check(), this.reapIntervalMillis); | ||
@@ -303,2 +362,3 @@ } | ||
if (this.interval !== null) { | ||
this._executeEventHandlers('stopReaping'); | ||
clearInterval(this.interval); | ||
@@ -308,2 +368,16 @@ } | ||
} | ||
_executeEventHandlers(eventName, ...args) { | ||
const listeners = this.emitter.listeners(eventName); | ||
// just calling .emit() would stop running rest of the listeners if one them fails | ||
listeners.forEach(listener => { | ||
try { | ||
listener(...args); | ||
} | ||
catch (err) { | ||
// There's nothing we can do here but log the error. This would otherwise | ||
// leak out as an unhandled exception. | ||
this.log(`Tarn: event handler "${eventName}" threw an exception ${err.stack}`, 'warn'); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -310,0 +384,0 @@ exports.Pool = Pool; |
{ | ||
"name": "tarn", | ||
"version": "1.1.5", | ||
"version": "2.0.0", | ||
"description": "Simple and robust resource pool for node.js", | ||
@@ -26,3 +26,3 @@ "main": "lib/tarn.js", | ||
"engines": { | ||
"node": ">=4.0.0" | ||
"node": ">=8.0.0" | ||
}, | ||
@@ -29,0 +29,0 @@ "keywords": [ |
@@ -23,7 +23,6 @@ [![Build Status](https://travis-ci.org/Vincit/tarn.js.svg?branch=master)](https://travis-ci.org/Vincit/tarn.js) | ||
const pool = new Pool({ | ||
// function that creates a resource. You can either pass the resource | ||
// to the callback or return a promise that resolves the resource | ||
// to the callback(error, resource) or return a promise that resolves the resource | ||
// (but not both). | ||
create: (cb) => { | ||
create: cb => { | ||
cb(null, new SomeResource()); | ||
@@ -35,12 +34,16 @@ }, | ||
// another one is acquired. | ||
validate: (resource) => { | ||
validate: resource => { | ||
return true; | ||
}, | ||
// function that destroys a resource. This is always synchronous | ||
// as nothing waits for the return value. | ||
destroy: (someResource) => { | ||
// function that destroys a resource, should return promise if | ||
// destroying is asynchronous operation | ||
// (destroy does not support callback syntax like create) | ||
destroy: someResource => { | ||
someResource.cleanup(); | ||
}, | ||
// logger function, noop by default | ||
log: (message, logLevel) => console.log(`${logLevel}: ${message}`) | ||
// minimum size | ||
@@ -84,3 +87,5 @@ min: 2, | ||
// acquire can be aborted using the abort method | ||
// acquire can be aborted using the abort method. | ||
// If acquire had triggered creating new resource to the pool | ||
// creation will continue and it is not aborted. | ||
acquire.abort(); | ||
@@ -103,12 +108,12 @@ | ||
// returns the number of non-free resources | ||
pool.numUsed() | ||
pool.numUsed(); | ||
// returns the number of free resources | ||
pool.numFree() | ||
pool.numFree(); | ||
// how many acquires are waiting for a resource to be released | ||
pool.numPendingAcquires() | ||
pool.numPendingAcquires(); | ||
// how many asynchronous create calls are running | ||
pool.numPendingCreates() | ||
pool.numPendingCreates(); | ||
@@ -118,2 +123,39 @@ // waits for all resources to be returned to the pool and destroys them. | ||
await pool.destroy(); | ||
// The following examples add synchronous event handlers for example to | ||
// allow externally collect diagnostic data of pool behaviour. | ||
// If any of these hooks fail, all errors are catched and warnings are logged. | ||
// resource is acquired from pool | ||
pool.on('acquireRequest', eventId => {}); | ||
pool.on('acquireSuccess', (eventId, resource) => {}); | ||
pool.on('acquireFail', (eventId, err) => {}); | ||
// resource returned to pool | ||
pool.on('release', resource => {}); | ||
// resource was created and added to the pool | ||
pool.on('createRequest', eventId => {}); | ||
pool.on('createSuccess', (eventId, resource) => {}); | ||
pool.on('createFail', (eventId, err) => {}); | ||
// resource is destroyed and evicted from pool | ||
// resource may or may not be invalid when destroySuccess / destroyFail is called | ||
pool.on('destroyRequest', (eventId, resource) => {}); | ||
pool.on('destroySuccess', (eventId, resource) => {}); | ||
pool.on('destroyFail', (eventId, resource, err) => {}); | ||
// when internal reaping event clock is activated / deactivated | ||
pool.on('startReaping', () => {}); | ||
pool.on('stopReaping', () => {}); | ||
// pool is destroyed (after poolDestroySuccess all event handlers are also cleared) | ||
pool.on('poolDestroyRequest', eventId => {}); | ||
pool.on('poolDestroySuccess', eventId => {}); | ||
// remove single event listener | ||
pool.removeListener(eventName, listener); | ||
// remove all listeners from an event | ||
pool.removeAllListeners(eventName); | ||
``` | ||
@@ -123,4 +165,17 @@ | ||
### 1.1.5 2019-04-06 | ||
### Master | ||
### 2.0.0 2019-06-02 | ||
- Accidentally published breaking changes in 1.2.0. Unpublished it and published again with correct version number 2.0.0 #33 | ||
### 1.2.0 2019-06-02 (UNPUBLISHED) | ||
- Passing unknown options throws an error #19 #32 | ||
- Diagnostic event handlers to allow monitoring pool behaviour #14 #23 | ||
- Dropped node 6 support #25 #28 | ||
- pool.destroy() now always waits for all pending destroys to finish before resolving #29 | ||
### 1.1.5 2019-04-06 | ||
- Added changelog #22 | ||
@@ -127,0 +182,0 @@ - Handle opt.destroy() being a promise with destroyTimeout #16 |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
33808
701
179
0