2019-06-19 12:50:48 +00:00
|
|
|
package statsd
|
|
|
|
|
|
|
|
import (
|
2020-07-23 20:08:49 +00:00
|
|
|
"net"
|
|
|
|
"sync"
|
2019-06-19 12:50:48 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
UDSTimeout holds the default timeout for UDS socket writes, as they can get
|
|
|
|
blocking when the receiving buffer is full.
|
|
|
|
*/
|
|
|
|
const defaultUDSTimeout = 1 * time.Millisecond
|
2020-07-23 20:08:49 +00:00
|
|
|
|
|
|
|
// udsWriter is an internal class wrapping around management of UDS connection
|
|
|
|
type udsWriter struct {
|
|
|
|
// Address to send metrics to, needed to allow reconnection on error
|
|
|
|
addr net.Addr
|
|
|
|
// Established connection object, or nil if not connected yet
|
|
|
|
conn net.Conn
|
|
|
|
// write timeout
|
|
|
|
writeTimeout time.Duration
|
|
|
|
sync.RWMutex // used to lock conn / writer can replace it
|
|
|
|
}
|
|
|
|
|
|
|
|
// newUDSWriter returns a pointer to a new udsWriter given a socket file path as addr.
|
|
|
|
func newUDSWriter(addr string) (*udsWriter, error) {
|
|
|
|
udsAddr, err := net.ResolveUnixAddr("unixgram", addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Defer connection to first Write
|
|
|
|
writer := &udsWriter{addr: udsAddr, conn: nil, writeTimeout: defaultUDSTimeout}
|
|
|
|
return writer, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetWriteTimeout allows the user to set a custom write timeout
|
|
|
|
func (w *udsWriter) SetWriteTimeout(d time.Duration) error {
|
|
|
|
w.writeTimeout = d
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write data to the UDS connection with write timeout and minimal error handling:
|
|
|
|
// create the connection if nil, and destroy it if the statsd server has disconnected
|
|
|
|
func (w *udsWriter) Write(data []byte) (int, error) {
|
|
|
|
conn, err := w.ensureConnection()
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
conn.SetWriteDeadline(time.Now().Add(w.writeTimeout))
|
|
|
|
n, e := conn.Write(data)
|
|
|
|
|
|
|
|
if err, isNetworkErr := e.(net.Error); !isNetworkErr || !err.Temporary() {
|
|
|
|
// Statsd server disconnected, retry connecting at next packet
|
|
|
|
w.unsetConnection()
|
|
|
|
return 0, e
|
|
|
|
}
|
|
|
|
return n, e
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *udsWriter) Close() error {
|
|
|
|
if w.conn != nil {
|
|
|
|
return w.conn.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *udsWriter) ensureConnection() (net.Conn, error) {
|
|
|
|
// Check if we've already got a socket we can use
|
|
|
|
w.RLock()
|
|
|
|
currentConn := w.conn
|
|
|
|
w.RUnlock()
|
|
|
|
|
|
|
|
if currentConn != nil {
|
|
|
|
return currentConn, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Looks like we might need to connect - try again with write locking.
|
|
|
|
w.Lock()
|
|
|
|
defer w.Unlock()
|
|
|
|
if w.conn != nil {
|
|
|
|
return w.conn, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
newConn, err := net.Dial(w.addr.Network(), w.addr.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
w.conn = newConn
|
|
|
|
return newConn, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *udsWriter) unsetConnection() {
|
|
|
|
w.Lock()
|
|
|
|
defer w.Unlock()
|
|
|
|
w.conn = nil
|
|
|
|
}
|