1// +build !windows 2 3package statsd 4 5import ( 6 "net" 7 "sync" 8 "time" 9) 10 11/* 12UDSTimeout holds the default timeout for UDS socket writes, as they can get 13blocking when the receiving buffer is full. 14*/ 15const defaultUDSTimeout = 1 * time.Millisecond 16 17// udsWriter is an internal class wrapping around management of UDS connection 18type udsWriter struct { 19 // Address to send metrics to, needed to allow reconnection on error 20 addr net.Addr 21 // Established connection object, or nil if not connected yet 22 conn net.Conn 23 // write timeout 24 writeTimeout time.Duration 25 sync.RWMutex // used to lock conn / writer can replace it 26} 27 28// newUDSWriter returns a pointer to a new udsWriter given a socket file path as addr. 29func newUDSWriter(addr string) (*udsWriter, error) { 30 udsAddr, err := net.ResolveUnixAddr("unixgram", addr) 31 if err != nil { 32 return nil, err 33 } 34 // Defer connection to first Write 35 writer := &udsWriter{addr: udsAddr, conn: nil, writeTimeout: defaultUDSTimeout} 36 return writer, nil 37} 38 39// SetWriteTimeout allows the user to set a custom write timeout 40func (w *udsWriter) SetWriteTimeout(d time.Duration) error { 41 w.writeTimeout = d 42 return nil 43} 44 45// Write data to the UDS connection with write timeout and minimal error handling: 46// create the connection if nil, and destroy it if the statsd server has disconnected 47func (w *udsWriter) Write(data []byte) (int, error) { 48 conn, err := w.ensureConnection() 49 if err != nil { 50 return 0, err 51 } 52 53 conn.SetWriteDeadline(time.Now().Add(w.writeTimeout)) 54 n, e := conn.Write(data) 55 56 if err, isNetworkErr := e.(net.Error); err != nil && (!isNetworkErr || !err.Temporary()) { 57 // Statsd server disconnected, retry connecting at next packet 58 w.unsetConnection() 59 return 0, e 60 } 61 return n, e 62} 63 64func (w *udsWriter) Close() error { 65 if w.conn != nil { 66 return w.conn.Close() 67 } 68 return nil 69} 70 71func (w *udsWriter) ensureConnection() (net.Conn, error) { 72 // Check if we've already got a socket we can use 73 w.RLock() 74 currentConn := w.conn 75 w.RUnlock() 76 77 if currentConn != nil { 78 return currentConn, nil 79 } 80 81 // Looks like we might need to connect - try again with write locking. 82 w.Lock() 83 defer w.Unlock() 84 if w.conn != nil { 85 return w.conn, nil 86 } 87 88 newConn, err := net.Dial(w.addr.Network(), w.addr.String()) 89 if err != nil { 90 return nil, err 91 } 92 w.conn = newConn 93 return newConn, nil 94} 95 96func (w *udsWriter) unsetConnection() { 97 w.Lock() 98 defer w.Unlock() 99 w.conn = nil 100} 101