You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
UDS socket reconnection via udsGracefulErrorHandling does not work on hot-shots ≥14 because protocolErrorHandler() compares err.code against an array of string codes ('ENOTCONN', 'ECONNREFUSED', …), while unix-dgram (the UDS transport) sets err.code to a negative numeric errno (e.g. -107 on Linux, -54 on Darwin). The check never matches, so the socket is never replaced after Datadog Agent UDS path/inode churn.
This is the UDS counterpart of #301 / #302. #302 correctly fixed TCP by switching to string Node.js err.code values, but the same change was applied to UDS. TCP sockets from Node emit string codes; unix-dgram does not.
Node.js version: v24.3.0 (also observed on Node 20+ in production)
OS: macOS Darwin (local repro) / Linux (production Datadog Agent UDS)
StatsD server: Datadog Agent DogStatsD over UDS (/var/run/datadog/dsd.socket)
Breakdown
In lib/statsd.js, send failures are passed to protocolErrorHandler with the raw transport error:
if((this.protocol===PROTOCOL.TCP||this.protocol===PROTOCOL.UDS)&&(callback||this.errorHandler)){protocolErrorHandler(this,this.protocol,err);// err is from unix-dgram for UDS}
Then:
}elseif(protocol===PROTOCOL.UDS&&(!UDS_ERROR_CODES.includes(err.code)||Date.now()-client.socket.createdAt<client.udsGracefulRestartRateLimit)){return;// Always returns for unix-dgram numeric codes}
UDS_ERROR_CODES comes from constants.udsErrors() (post-#302):
But unix-dgram builds errors like this (lib/unix_dgram.js):
functionerrnoException(errorno,syscall){vare=newError(syscall+' '+errorno);e.errno=e.code=errorno;// numeric, typically negative (-errno)e.syscall=syscall;returne;}
So for a typical Agent-restart / stale-inode failure you get:
Field
Value (Linux)
Value (Darwin, live repro)
err.message
send -107
send -54
typeof err.code
number
number
err.code
-107 (-ENOTCONN)
-54 (-ECONNRESET)
UDS_ERROR_CODES.includes(err.code)
false
false
Pre-v14 this worked for UDS because the list was numeric os.constants.errno.* and the check was UDS_ERROR_CODES.includes(-err.code) (negating the negative errno back to the positive constant).
Matching simulation against unpatched constants.udsErrors():
linux ENOTCONN (-107):
typeof(code)=number code=-107
includes=false wouldRecreate=false
darwin ECONNRESET (-54):
typeof(code)=number code=-54
includes=false wouldRecreate=false
string 'ECONNRESET':
typeof(code)=string code="ECONNRESET"
includes=true wouldRecreate=true
Expected Behavior
When UDS send fails with ENOTCONN / ECONNREFUSED (Linux) or EDESTADDRREQ / ECONNRESET (Darwin), and udsGracefulErrorHandling: true is enabled, hot-shots should recreate the unix-dgram socket (subject to udsGracefulRestartRateLimit). After the Datadog Agent comes back and recreates /var/run/datadog/dsd.socket, metrics should resume without restarting the application process.
Actual Behavior
protocolErrorHandler never treats the unix-dgram error as retriable. The client keeps the stale connected socket (old inode). Every subsequent flush fails with send -107 / send -111 (or Darwin equivalents). Metrics are lost until the process is restarted / the StatsD client is manually replaced.
Reproduction
'use strict';constfs=require('fs');constos=require('os');constpath=require('path');constStatsD=require('hot-shots');constunixDgram=require('unix-dgram');// --- 1) Show the matching bug in isolation (no Agent required) ---const{ udsErrors }=require('hot-shots/lib/constants');// or copy udsErrors() from lib/constants.jsconstUDS_ERROR_CODES=udsErrors();functionerrnoException(errorno,syscall){// Identical to unix-dgram's errnoExceptionconste=newError(syscall+' '+errorno);e.errno=e.code=errorno;e.syscall=syscall;returne;}constudsErr=errnoException(process.platform==='linux' ? -107 : -54,'send');console.log({udsErrors: UDS_ERROR_CODES,message: udsErr.message,code: udsErr.code,typeofCode: typeofudsErr.code,includes: UDS_ERROR_CODES.includes(udsErr.code),// false on hot-shots >= 14});// --- 2) Live stale-inode UDS failure (unix-dgram) ---constsockPath=path.join(os.tmpdir(),`hot-shots-uds-repro-${process.pid}.sock`);try{fs.unlinkSync(sockPath);}catch(_){}constserver=unixDgram.createSocket('unix_dgram');server.bind(sockPath);constclientSock=unixDgram.createSocket('unix_dgram');clientSock.connect(sockPath);setTimeout(()=>{// Simulate Datadog Agent daemonset rollout: delete old socket, recreate pathserver.close();try{fs.unlinkSync(sockPath);}catch(_){}constserver2=unixDgram.createSocket('unix_dgram');server2.bind(sockPath);clientSock.send(Buffer.from('test.metric:1|c\n'),(err)=>{console.log('after agent restart simulation:',{message: err&&err.message,code: err&&err.code,typeofCode: err&&typeoferr.code,includes: err&&UDS_ERROR_CODES.includes(err.code),// false});try{clientSock.close();}catch(_){}try{server2.close();}catch(_){}try{fs.unlinkSync(sockPath);}catch(_){}});},50);// --- 3) hot-shots client never recovers (needs a real/missing dsd.socket) ---constclient=newStatsD({protocol: 'uds',path: '/var/run/datadog/dsd.socket',// or a test socket pathudsGracefulErrorHandling: true,udsGracefulRestartRateLimit: 0,errorHandler: (err)=>{console.log('hot-shots errorHandler:',{message: err.message,// Note: hot-shots wraps the message; raw unix-dgram code is what protocolErrorHandler sees});},});// With Agent running: metrics send.// Restart Agent / replace UDS inode, then:setInterval(()=>client.increment('test.uds.recovery'),1000);// Expected: socket recreated, metrics resume// Actual: perpetual "Error sending hot-shots message: Error: send -107" (Linux)
Suggested Fix
Keep string matching for TCP (Node string err.code), but make UDS matching accept both string codes and unix-dgram numeric (negative) errnos. Minimal approach for udsErrors():
functionudsErrors(){if(process.platform==='linux'){// unix-dgram reports negative numeric errnos, not string codesreturn['ENOTCONN','ECONNREFUSED',-107,-111];}if(process.platform==='darwin'){// unix-dgram reports negative numeric errnos, not string codesreturn['EDESTADDRREQ','ECONNRESET',-39,-54];}return[];}
A more defensive variant would map via os.constants.errno + Math.abs(err.code) so both signs and platforms stay correct without hardcoding.
Alternatively, restore the pre-v14 UDS check (includes(-err.code) with numeric constants) only for UDS, while leaving TCP on string codes.
Impact
Any process using protocol: 'uds' with udsGracefulErrorHandling: true on hot-shots ≥14 will never recover from Datadog Agent UDS socket replacement (daemonset rollout, agent restart, socket inode churn). Custom metrics stop until the application is restarted. This is especially painful in Kubernetes where Agent rollouts are routine and application pods are long-lived.
Issue #301 already suspected UDS might be affected; this confirms it, and shows the #302 string-code fix is correct for TCP but insufficient for unix-dgram.
Overview
UDS socket reconnection via
udsGracefulErrorHandlingdoes not work on hot-shots ≥14 becauseprotocolErrorHandler()compareserr.codeagainst an array of string codes ('ENOTCONN','ECONNREFUSED', …), whileunix-dgram(the UDS transport) setserr.codeto a negative numeric errno (e.g.-107on Linux,-54on Darwin). The check never matches, so the socket is never replaced after Datadog Agent UDS path/inode churn.This is the UDS counterpart of #301 / #302. #302 correctly fixed TCP by switching to string Node.js
err.codevalues, but the same change was applied to UDS. TCP sockets from Node emit string codes;unix-dgramdoes not.Environment
/var/run/datadog/dsd.socket)Breakdown
In
lib/statsd.js, send failures are passed toprotocolErrorHandlerwith the raw transport error:Then:
UDS_ERROR_CODEScomes fromconstants.udsErrors()(post-#302):But
unix-dgrambuilds errors like this (lib/unix_dgram.js):So for a typical Agent-restart / stale-inode failure you get:
err.messagesend -107send -54typeof err.codenumbernumbererr.code-107(-ENOTCONN)-54(-ECONNRESET)UDS_ERROR_CODES.includes(err.code)falsefalsePre-v14 this worked for UDS because the list was numeric
os.constants.errno.*and the check wasUDS_ERROR_CODES.includes(-err.code)(negating the negative errno back to the positive constant).Sample log / live repro output
Production-style hot-shots formatted error (Linux Agent UDS):
Local live repro (Darwin, hot-shots@15.0.0 unpatched, unix-dgram@2.0.6):
Matching simulation against unpatched
constants.udsErrors():Expected Behavior
When UDS send fails with
ENOTCONN/ECONNREFUSED(Linux) orEDESTADDRREQ/ECONNRESET(Darwin), andudsGracefulErrorHandling: trueis enabled, hot-shots should recreate the unix-dgram socket (subject toudsGracefulRestartRateLimit). After the Datadog Agent comes back and recreates/var/run/datadog/dsd.socket, metrics should resume without restarting the application process.Actual Behavior
protocolErrorHandlernever treats the unix-dgram error as retriable. The client keeps the stale connected socket (old inode). Every subsequent flush fails withsend -107/send -111(or Darwin equivalents). Metrics are lost until the process is restarted / the StatsD client is manually replaced.Reproduction
Suggested Fix
Keep string matching for TCP (Node string
err.code), but make UDS matching accept both string codes and unix-dgram numeric (negative) errnos. Minimal approach forudsErrors():A more defensive variant would map via
os.constants.errno+Math.abs(err.code)so both signs and platforms stay correct without hardcoding.Alternatively, restore the pre-v14 UDS check (
includes(-err.code)with numeric constants) only for UDS, while leaving TCP on string codes.Impact
Any process using
protocol: 'uds'withudsGracefulErrorHandling: trueon hot-shots ≥14 will never recover from Datadog Agent UDS socket replacement (daemonset rollout, agent restart, socket inode churn). Custom metrics stop until the application is restarted. This is especially painful in Kubernetes where Agent rollouts are routine and application pods are long-lived.Issue #301 already suspected UDS might be affected; this confirms it, and shows the #302 string-code fix is correct for TCP but insufficient for
unix-dgram.