Comparing version 2.4.0 to 2.5.0
@@ -502,2 +502,3 @@ /* | ||
this.ch_doReleaseLeakCheck = true; | ||
this.ch_started = mod_utils.currentMillis(); | ||
@@ -568,2 +569,7 @@ FSM.call(this, 'waiting'); | ||
CueBallClaimHandle.prototype.timeout = function () { | ||
mod_assert.ok(this.isInState('waiting')); | ||
this.emit('timeout'); | ||
}; | ||
CueBallClaimHandle.prototype.fail = function (err) { | ||
@@ -608,10 +614,15 @@ this.emit('error', err); | ||
if (isFinite(this.ch_claimTimeout)) { | ||
S.timeout(this.ch_claimTimeout, function () { | ||
self.ch_lastError = new mod_errors.ClaimTimeoutError( | ||
self.ch_pool); | ||
S.gotoState('failed'); | ||
}); | ||
function onTimeout() { | ||
self.ch_lastError = new mod_errors.ClaimTimeoutError( | ||
self.ch_pool); | ||
self.ch_pool._incrCounter('claim-timeout'); | ||
S.gotoState('failed'); | ||
} | ||
if (Number.isFinite(this.ch_claimTimeout)) { | ||
S.timeout(this.ch_claimTimeout, onTimeout); | ||
} | ||
S.on(this, 'timeout', onTimeout); | ||
S.on(this, 'error', function (err) { | ||
@@ -618,0 +629,0 @@ self.ch_lastError = err; |
@@ -25,2 +25,3 @@ /* | ||
const mod_codel = require('./codel'); | ||
const mod_monitor = require('./pool-monitor'); | ||
@@ -141,2 +142,3 @@ | ||
mod_assert.optionalNumber(options.defaultPort, 'options.defaultPort'); | ||
mod_utils.assertClaimDelay(options.targetClaimDelay); | ||
@@ -183,2 +185,9 @@ mod_assert.object(options.recovery, 'options.recovery'); | ||
this.p_codel = null; | ||
if (Number.isFinite(options.targetClaimDelay)) { | ||
this.p_codel = | ||
new mod_codel.ControlledDelay(options.targetClaimDelay); | ||
} | ||
this.p_lastError = undefined; | ||
@@ -655,8 +664,22 @@ | ||
var hdl = self.p_waiters.shift(); | ||
if (hdl.isInState('waiting')) { | ||
hdl.try(fsm); | ||
return; | ||
var drop = self.p_codel !== null && | ||
self.p_codel.overloaded(hdl.ch_started); | ||
if (!hdl.isInState('waiting')) { | ||
continue; | ||
} | ||
if (drop) { | ||
hdl.timeout(); | ||
continue; | ||
} | ||
hdl.try(fsm); | ||
return; | ||
} | ||
if (self.p_codel !== null) { | ||
self.p_codel.empty(); | ||
} | ||
/* Otherwise, onto the idle queue we go! */ | ||
@@ -736,2 +759,22 @@ var node = self.p_idleq.push(fsm); | ||
CueBallConnectionPool.prototype.getStats = function () { | ||
var self = this; | ||
// Get a snapshot of current counter values. | ||
var counters = {}; | ||
Object.keys(this.p_counters).forEach(function (k) { | ||
counters[k] = self.p_counters[k]; | ||
}); | ||
var stats = { | ||
'counters': counters, | ||
'totalConnections': Object.keys(self.p_connections).length, | ||
'idleConnections': self.p_idleq.length, | ||
'pendingConnections': self.p_initq.length, | ||
'waiterCount': self.p_waiters.length | ||
}; | ||
return (stats); | ||
}; | ||
CueBallConnectionPool.prototype.claim = function (options, cb) { | ||
@@ -741,2 +784,3 @@ var self = this; | ||
var handle; | ||
var timeout; | ||
@@ -749,8 +793,18 @@ if (typeof (options) === 'function' && cb === undefined) { | ||
mod_assert.optionalNumber(options.timeout, 'options.timeout'); | ||
var timeout = options.timeout; | ||
if (timeout === undefined || timeout === null) | ||
timeout = Infinity; | ||
mod_assert.optionalBool(options.errorOnEmpty, 'options.errorOnEmpty'); | ||
var errOnEmpty = options.errorOnEmpty; | ||
if (this.p_codel !== null) { | ||
if (typeof (options.timeout) === 'number') { | ||
throw (new Error('options.timeout not allowed when ' + | ||
'targetDelay has been set')); | ||
} | ||
timeout = this.p_codel.getMaxIdle(); | ||
} else if (typeof (options.timeout) === 'number') { | ||
timeout = options.timeout; | ||
} else { | ||
timeout = Infinity; | ||
} | ||
this._incrCounter('claim'); | ||
@@ -757,0 +811,0 @@ |
@@ -262,2 +262,6 @@ /* | ||
if (this.r_isBootstrap) { | ||
/* | ||
* If we're a bootstrap resolver, then we want to look up | ||
* the DNS service itself, and try all possible resolvers. | ||
*/ | ||
this.r_service = '_dns._udp'; | ||
@@ -267,2 +271,6 @@ this.r_defport = 53; | ||
this.r_refCount = 0; | ||
/* | ||
* We might have lots of downstream resolvers, so we do this to | ||
* stop node warning about "leaked" event handlers. | ||
*/ | ||
this.setMaxListeners(500); | ||
@@ -294,2 +302,6 @@ } | ||
/* | ||
* These are used to stash our actual current retry timeout etc. | ||
* for SRV versus all other operations | ||
*/ | ||
this.r_srvRetry = { | ||
@@ -313,2 +325,11 @@ max: dnsSrvRecov.retries, | ||
/* | ||
* nextService, nextV6 and nextV4 are used to stash Date objects | ||
* representing the time at which we should next try to resolve SRV, | ||
* AAAA and A records for our target name, respectively. | ||
* | ||
* Normally these will end up reflecting the TTL expiry of the last | ||
* records we saw, but in error cases they can represent our next | ||
* attempt time instead. | ||
*/ | ||
this.r_nextService = new Date(); | ||
@@ -318,8 +339,39 @@ this.r_nextV6 = new Date(); | ||
/* | ||
* Keep track of the last SRV TTL we saw -- we'll use this in the case | ||
* where we already know we have SRV records but all our resolvers are | ||
* down, as our retry timeout. | ||
*/ | ||
this.r_lastSrvTtl = 60; | ||
this.r_lastError = undefined; | ||
/* | ||
* List of "srv" objects (our prototypes for the backends we'll | ||
* eventually emit). We use these as a common interface between the SRV | ||
* and AAAA/A resolution steps. If SRV fails, we generate a single "srv" | ||
* object with just the root name on it. If SRV succeeds, this is the | ||
* full set of backends we got from the results. The SRV lookup may not | ||
* necessarily have additional records giving us the IP addresses for | ||
* each backend, and we might need to do further AAAA/A lookups to | ||
* determine these after our SRV. | ||
*/ | ||
this.r_srvs = []; | ||
/* | ||
* Used by A/AAAA lookups: remaining "srv" objects we haven't | ||
* finished resolving yet. | ||
*/ | ||
this.r_srvRem = []; | ||
/* Used by A/AAAA: current srv object */ | ||
this.r_srv = undefined; | ||
/* Index of backends we've emitted */ | ||
this.r_backends = {}; | ||
/* | ||
* Our bootstrap, if any -- an upstream Resolver instance that's | ||
* emitting updates to the set of DNS servers we should be using. | ||
*/ | ||
this.r_bootstrap = undefined; | ||
@@ -476,2 +528,3 @@ this.r_bootstrapRes = {}; | ||
self.r_nextService = d; | ||
self.r_lastSrvTtl = ttl; | ||
@@ -517,15 +570,38 @@ var oldLookup = {}; | ||
/* | ||
* Don't bother retrying SRV lookups for at least 60 | ||
* minutes -- there probably aren't any available. | ||
*/ | ||
var d = new Date(); | ||
d.setTime(d.getTime() + 1000*60*60); | ||
if (err.code === 'NOTIMP') { | ||
/* | ||
* Don't bother retrying SRV lookups for at | ||
* least 60 minutes -- our nameserver said it | ||
* doesn't support SRV at all and this isn't | ||
* likely to change quickly. | ||
*/ | ||
self.r_log.trace('nameserver returned NOTIMP ' + | ||
'to SRV lookup on %s, will not retry SRV ' + | ||
'for 60 min', self.r_service); | ||
d.setTime(d.getTime() + 1000*60*60); | ||
} else { | ||
/* | ||
* TODO: rework the client code so this can | ||
* respect a timeout in a SOA record in | ||
* NXDOMAIN responses (binder doesn't send | ||
* these) | ||
*/ | ||
d.setTime(d.getTime() + 1000*60); | ||
} | ||
self.r_nextService = d; | ||
self.r_log.trace('no SRV records found for service ' + | ||
'%s, treating as a plain name for next 60min', | ||
self.r_service); | ||
'%s, trying as a plain name', self.r_service); | ||
S.gotoState('aaaa'); | ||
} else if (err.code === 'REFUSED') { | ||
/* | ||
* Usually sent by an authoritative nameserver to a | ||
* recursive query about a name that is not in its | ||
* area of authority. Retrying this is pointless. | ||
*/ | ||
self.r_srvRetry.count = 0; | ||
S.gotoState('srv_error'); | ||
} else { | ||
@@ -553,3 +629,3 @@ S.gotoState('srv_error'); | ||
'repeated error during SRV resolution for service %s, ' + | ||
'will retry in 5min', self.r_service); | ||
'will retry in %d sec', self.r_service, self.r_lastSrvTtl); | ||
@@ -562,11 +638,20 @@ self.r_srvs = [ { | ||
/* | ||
* Retry in 5 mins, but proceed on through -- just in case | ||
* our resolvers are giving us some error on SRV lookups | ||
* (e.g. because they don't implement the record type). | ||
* Retry in one TTL. Don't proceed to try A/AAAA lookups, to | ||
* avoid flapping back and forth between SRV and A/AAAA mode | ||
* on a service. | ||
*/ | ||
var d = new Date(); | ||
d.setTime(d.getTime() + 1000*60*5); | ||
d.setTime(d.getTime() + 1000*self.r_lastSrvTtl); | ||
self.r_nextService = d; | ||
S.gotoState('aaaa'); | ||
/* | ||
* Make sure the next time we wake up is for SRV still, not | ||
* A or AAAA. | ||
*/ | ||
if (self.r_nextV6 && self.r_nextV6.getTime() < d.getTime()) | ||
self.r_nextV6 = d; | ||
if (self.r_nextV4 && self.r_nextV4.getTime() < d.getTime()) | ||
self.r_nextV4 = d; | ||
S.gotoState('sleep'); | ||
} | ||
@@ -667,4 +752,3 @@ }; | ||
S.on(req, 'error', function (err) { | ||
if (NoRecordsError.isInstance(err) || | ||
err.code === 'NOTIMP') { | ||
if (NoRecordsError.isInstance(err) || err.code === 'NOTIMP') { | ||
/* | ||
@@ -686,2 +770,10 @@ * If we got NoRecordsError (NODATA), we probably have | ||
return; | ||
} else if (err.code === 'REFUSED') { | ||
/* | ||
* Usually sent by an authoritative nameserver to a | ||
* recursive query about a name that is not in its | ||
* area of authority. Retrying this is pointless. | ||
*/ | ||
self.r_retry.count = 0; | ||
} | ||
@@ -801,2 +893,9 @@ self.r_lastError = err; | ||
self.r_retry.count = 0; | ||
} else if (err.code === 'REFUSED') { | ||
/* | ||
* Usually sent by an authoritative nameserver to a | ||
* recursive query about a name that is not in its | ||
* area of authority. Retrying this is pointless. | ||
*/ | ||
self.r_retry.count = 0; | ||
} | ||
@@ -827,2 +926,7 @@ self.r_lastError = err; | ||
var d = new Date(); | ||
/* | ||
* TODO: rework the client code so this can respect a timeout in | ||
* a SOA record in NXDOMAIN responses (not urgent, binder | ||
* doesn't send these) | ||
*/ | ||
d.setTime(d.getTime() + 1000*60); | ||
@@ -829,0 +933,0 @@ if (self.r_nextV4 === undefined || d <= self.r_nextV4) |
@@ -14,2 +14,4 @@ /* | ||
assertRecoverySet: assertRecoverySet, | ||
assertClaimDelay: assertClaimDelay, | ||
currentMillis: currentMillis, | ||
stackTracesEnabled: stackTracesEnabled, | ||
@@ -158,2 +160,20 @@ maybeCaptureStackTrace: maybeCaptureStackTrace | ||
function assertClaimDelay(delay) { | ||
mod_assert.optionalFinite(delay, 'options.targetClaimDelay'); | ||
if (Number.isFinite(delay)) { | ||
mod_assert.ok(delay > 0, 'options.targetClaimDelay > 0'); | ||
mod_assert.equal(delay, Math.floor(delay), | ||
'options.targetClaimDelay'); | ||
} | ||
} | ||
/* Get monotonic time in milliseconds */ | ||
function currentMillis() { | ||
var time = process.hrtime(); | ||
var secs2ms = time[0] * 1000; | ||
var ns2ms = time[1] / 1000000; | ||
return (secs2ms + ns2ms); | ||
} | ||
/* A Fisher-Yates shuffle. */ | ||
@@ -160,0 +180,0 @@ function shuffle(array) { |
{ | ||
"name": "cueball", | ||
"version": "2.4.0", | ||
"version": "2.5.0", | ||
"description": "manage a pool of connections to a multi-node service where nodes are listed in DNS", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
187923
19
5097