Comparing version 8.1.0 to 8.2.0
CHANGELOG | ||
========= | ||
## 8.2.0 (2020-9-30) | ||
* @dhermes Making UDS error handling and recovery more robust. Note these look to be ok in a minor release but are signficant upgrades to how UDS works. Thanks as well to @prognant for an overlapping PR. | ||
## 8.1.0 (2020-9-25) | ||
@@ -5,0 +8,0 @@ * @maleblond Support multiple values for the same tag key |
@@ -0,1 +1,4 @@ | ||
const os = require('os'), | ||
process = require('process'); | ||
exports.PROTOCOL = { | ||
@@ -7,1 +10,27 @@ TCP: 'tcp', | ||
}; | ||
/** | ||
* Determines error codes that signify a connection to a Unix Domain Socket (UDS) | ||
* has failed in a way that can be retried. This codes are OS-specific. | ||
* @returns {number[]} An array of the error codes. | ||
*/ | ||
function udsErrors() { | ||
if (process.platform === 'linux') { | ||
return [ | ||
os.constants.errno.ENOTCONN, | ||
os.constants.errno.ECONNREFUSED, | ||
]; | ||
} | ||
if (process.platform === 'darwin') { | ||
return [ | ||
os.constants.errno.EDESTADDRREQ, | ||
os.constants.errno.ECONNRESET, | ||
]; | ||
} | ||
// Unknown / not yet implemented | ||
return []; | ||
} | ||
exports.udsErrors = udsErrors; |
@@ -1,8 +0,11 @@ | ||
const util = require('util'), | ||
const process = require('process'), | ||
util = require('util'), | ||
helpers = require('./helpers'), | ||
applyStatsFns = require('./statsFunctions'); | ||
const { PROTOCOL } = require('./constants'); | ||
const constants = require('./constants'); | ||
const createTransport = require('./transport'); | ||
const PROTOCOL = constants.PROTOCOL; | ||
const UDS_ERROR_CODES = constants.udsErrors(); | ||
const UDS_DEFAULT_GRACEFUL_RESTART_LIMIT = 1000; | ||
@@ -51,2 +54,4 @@ const CACHE_DNS_TTL_DEFAULT = 60000; | ||
this.port = options.port || parseInt(process.env.DD_DOGSTATSD_PORT, 10) || 8125; | ||
this.path = options.path; | ||
this.stream = options.stream; | ||
this.prefix = options.prefix || ''; | ||
@@ -72,2 +77,4 @@ this.suffix = options.suffix || ''; | ||
this.udsGracefulErrorHandling = 'udsGracefulErrorHandling' in options ? options.udsGracefulErrorHandling : true; | ||
this.udsGracefulRestartRateLimit = options.udsGracefulRestartRateLimit || UDS_DEFAULT_GRACEFUL_RESTART_LIMIT; // only recreate once per second | ||
this.isChild = options.isChild; | ||
@@ -99,12 +106,4 @@ // If we're mocking the client, create a buffer to record the outgoing calls. | ||
if (! this.socket) { | ||
this.socket = createTransport(this, { | ||
host: this.host, | ||
cacheDns: this.cacheDns, | ||
cacheDnsTtl: this.cacheDnsTtl, | ||
path: options.path, | ||
port: this.port, | ||
protocol: this.protocol, | ||
stream: options.stream | ||
}); | ||
if (!this.socket) { | ||
trySetNewSocket(this); | ||
} | ||
@@ -125,39 +124,7 @@ | ||
// does not support options.isChild (how to re-create a socket you didn't create?) | ||
if (this.socket && !options.isChild && options.protocol === PROTOCOL.UDS && this.udsGracefulErrorHandling) { | ||
const socketCreateLimit = options.udsGracefulRestartRateLimit || UDS_DEFAULT_GRACEFUL_RESTART_LIMIT; // only recreate once per second | ||
if (this.socket && options.protocol === PROTOCOL.UDS) { | ||
const lastSocketCreateTime = Date.now(); | ||
this.socket.on('error', (err) => { | ||
const code = err.code; | ||
switch (code) { | ||
case 107: | ||
case 111: { | ||
if (Date.now() - lastSocketCreateTime >= socketCreateLimit) { | ||
// recreate the socket, but only once per 30 seconds | ||
if (this.errorHandler) { | ||
this.socket.removeListener('error', this.errorHandler); | ||
} | ||
this.socket.close(); | ||
this.socket = createTransport(this, { | ||
host: this.host, | ||
path: options.path, | ||
port: this.port, | ||
protocol: this.protocol | ||
}); | ||
if (this.errorHandler) { | ||
this.socket.on('error', this.errorHandler); | ||
} else { | ||
this.socket.on('error', error => console.error(`hot-shots UDS error: ${error}`)); | ||
} | ||
} | ||
break; | ||
} | ||
default: { | ||
break; | ||
} | ||
} | ||
}); | ||
maybeAddUDSErrorHandler(this, lastSocketCreateTime); | ||
} | ||
this.messagesInFlight = 0; | ||
@@ -361,3 +328,12 @@ this.CHECKS = { | ||
if (!this.socket) { | ||
const socketWasMissing = !this.socket; | ||
if (socketWasMissing && this.protocol === PROTOCOL.UDS) { | ||
trySetNewSocket(this); | ||
if (this.socket) { | ||
// On success, add custom UDS error handling. | ||
maybeAddUDSErrorHandler(this, Date.now()); | ||
} | ||
} | ||
if (socketWasMissing) { | ||
const error = new Error('Socket not created properly. Check previous errors for details.'); | ||
@@ -534,1 +510,83 @@ if (callback) { | ||
exports.StatsD = Client; | ||
/** | ||
* Handle an error connecting to a Unix Domain Socket (UDS). This will | ||
* attempt to create a new socket and replace and close the client's current | ||
* socket, registering a **new** `udsErrorHandler()` on the newly created socket. | ||
* If a new socket can't be created (e.g. if no UDS currently exists at | ||
* `client.path`) then this will leave the existing socket intact. | ||
* | ||
* Note that this will no-op with an early exit if the last socket create time | ||
* was too recent (within the UDS graceful restart rate limit). | ||
* @param client Client The statsd Client that may be getting a UDS error handler. | ||
* @param lastSocketCreateTime number The timestamp (in milliseconds since the | ||
* epoch) when the current socket was created. | ||
*/ | ||
function udsErrorHandler(client, lastSocketCreateTime) { | ||
// recreate the socket, but only once within `udsGracefulRestartRateLimit`. | ||
if (Date.now() - lastSocketCreateTime < client.udsGracefulRestartRateLimit) { | ||
return; | ||
} | ||
if (client.errorHandler) { | ||
client.socket.removeListener('error', client.errorHandler); | ||
} | ||
const newSocket = createTransport(client, { | ||
host: client.host, | ||
path: client.path, | ||
port: client.port, | ||
protocol: client.protocol, | ||
}); | ||
if (newSocket) { | ||
client.socket.close(); | ||
client.socket = newSocket; | ||
maybeAddUDSErrorHandler(client, Date.now()); | ||
} else { | ||
console.error('Could not replace UDS connection with new socket'); | ||
return; | ||
} | ||
if (client.errorHandler) { | ||
client.socket.on('error', client.errorHandler); | ||
} else { | ||
client.socket.on('error', (error) => console.error(`hot-shots UDS error: ${error}`)); | ||
} | ||
} | ||
/** | ||
* Add a Unix Domain Socket (UDS) error handler to the client's socket, if the | ||
* client is not a "child" client and has graceful error handling enabled for | ||
* UDS. | ||
* @param client Client The statsd Client that may be getting a UDS error handler. | ||
* @param lastSocketCreateTime number The timestamp (in milliseconds since the | ||
* epoch) when the current socket was created. | ||
*/ | ||
function maybeAddUDSErrorHandler(client, lastSocketCreateTime) { | ||
if (client.isChild || !client.udsGracefulErrorHandling) { | ||
return; | ||
} | ||
client.socket.on('error', (err) => { | ||
if (UDS_ERROR_CODES.includes(-err.code)) { | ||
udsErrorHandler(client, lastSocketCreateTime); | ||
} | ||
}); | ||
} | ||
/** | ||
* Try to replace a client's socket with a new transport. If `createTransport()` | ||
* returns `null` this will still set the client's socket to `null`. | ||
* @param client Client The statsd Client that will be getting a new socket | ||
*/ | ||
function trySetNewSocket(client) { | ||
client.socket = createTransport(client, { | ||
host: client.host, | ||
cacheDns: client.cacheDns, | ||
cacheDnsTtl: client.cacheDnsTtl, | ||
path: client.path, | ||
port: client.port, | ||
protocol: client.protocol, | ||
stream: client.stream, | ||
}); | ||
} |
{ | ||
"name": "hot-shots", | ||
"description": "Node.js client for StatsD, DogStatsD, and Telegraf", | ||
"version": "8.1.0", | ||
"version": "8.2.0", | ||
"author": "Steve Ivy", | ||
@@ -6,0 +6,0 @@ "types": "./types.d.ts", |
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
83561
1541