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