github.com/xmidt-org/webpa-common@v1.11.9/xlistener/listener.go (about) 1 package xlistener 2 3 import ( 4 "crypto/tls" 5 "net" 6 "strconv" 7 "sync" 8 "syscall" 9 10 "github.com/go-kit/kit/log" 11 "github.com/go-kit/kit/log/level" 12 "github.com/go-kit/kit/metrics/discard" 13 "github.com/xmidt-org/webpa-common/logging" 14 "github.com/xmidt-org/webpa-common/xmetrics" 15 ) 16 17 var ( 18 // netListen is the factory function for creating a net.Listener. Defaults to net.Listen. Only tests would change this variable. 19 netListen = net.Listen 20 21 // tlsListen is the factory function for creating a tls.Listener. Defaults to tls.Listen. Only tests would change this variable. 22 tlsListen = tls.Listen 23 ) 24 25 // Options defines the available options for configuring a listener 26 type Options struct { 27 // Logger is the go-kit logger to use for output. If unset, logging.DefaultLogger() is used. 28 Logger log.Logger 29 30 // MaxConnections is the maximum number of active connections the listener will permit. If this 31 // value is not positive, there is no limit to the number of connections. 32 MaxConnections int 33 34 // Rejected is is incremented each time the listener rejects a connection. If unset, a go-kit discard Counter is used. 35 Rejected xmetrics.Adder 36 37 // Active is updated to reflect the current number of active connections. If unset, a go-kit discard Gauge is used. 38 Active xmetrics.Adder 39 40 // Network is the network to listen on. This value is only used if Next is unset. Defaults to "tcp" if unset. 41 Network string 42 43 // Address is the address to listen on. This value is only used if Next is unset. Defaults to ":http" if unset. 44 Address string 45 46 // Next is the net.Listener to decorate. If this field is set, Network and Address are ignored. 47 Next net.Listener 48 49 Config *tls.Config 50 } 51 52 // New constructs a new net.Listener using a set of options. 53 // 54 // If Next is set, that listener is decorated with connection limiting and other options specfied in Options. 55 // Otherwise, a new net.Listener is created, and that new listener is decorated. Note that in the case 56 // where this function creates a new net.Listener, that listener will be occupying a port and should be cleaned 57 // up via Close() if higher level errors occur. 58 func New(o Options) (net.Listener, error) { 59 if o.Logger == nil { 60 o.Logger = logging.DefaultLogger() 61 } 62 63 var semaphore chan struct{} 64 if o.MaxConnections > 0 { 65 semaphore = make(chan struct{}, o.MaxConnections) 66 } 67 68 if o.Rejected == nil { 69 o.Rejected = discard.NewCounter() 70 } 71 72 if o.Active == nil { 73 o.Active = discard.NewGauge() 74 } 75 76 next := o.Next 77 if next == nil { 78 if len(o.Network) == 0 { 79 o.Network = "tcp" 80 } 81 82 if len(o.Address) == 0 { 83 o.Address = ":http" 84 } 85 86 var err error 87 if o.Config != nil { 88 next, err = tlsListen(o.Network, o.Address, o.Config) 89 } else { 90 next, err = netListen(o.Network, o.Address) 91 } 92 if err != nil { 93 return nil, err 94 } 95 } 96 97 return &listener{ 98 Listener: next, 99 logger: log.With(o.Logger, "listenNetwork", next.Addr().Network(), "listenAddress", next.Addr().String()), 100 semaphore: semaphore, 101 rejected: xmetrics.NewIncrementer(o.Rejected), 102 active: o.Active, 103 }, nil 104 } 105 106 // listener decorates a net.Listener with metrics and optional maximum connection enforcement 107 type listener struct { 108 net.Listener 109 logger log.Logger 110 semaphore chan struct{} 111 rejected xmetrics.Incrementer 112 active xmetrics.Adder 113 } 114 115 // acquire attempts to obtain a semaphore resource. If the semaphore has not been set (i.e. no maximum connections), 116 // this method immediately returns true. Otherwise, the semaphore must be immediately acquired or this method returns false. 117 // In all cases, the active connections gauge is updated if appropriate. 118 func (l *listener) acquire() bool { 119 if l.semaphore == nil { 120 l.active.Add(1.0) 121 return true 122 } 123 124 select { 125 case l.semaphore <- struct{}{}: 126 l.active.Add(1.0) 127 return true 128 default: 129 return false 130 } 131 } 132 133 // release returns a semaphore resource to the pool, if set. This method also decrements the active connection gauge. 134 func (l *listener) release() { 135 l.active.Add(-1.0) 136 if l.semaphore != nil { 137 <-l.semaphore 138 } 139 } 140 141 // Accept invokes the delegate net.Listener's Accept method, then attempts to acquire the semaphore. 142 // If the semaphore was set and could not be acquired, the accepted connection is immediately closed. 143 func (l *listener) Accept() (net.Conn, error) { 144 for { 145 c, err := l.Listener.Accept() 146 if err != nil { 147 sysValue := "" 148 if errno, ok := err.(syscall.Errno); ok { 149 sysValue = "0x" + strconv.FormatInt(int64(errno), 16) 150 } 151 152 l.logger.Log(level.Key(), level.ErrorValue(), logging.MessageKey(), "failed to accept connection", logging.ErrorKey(), err, "sysValue", sysValue) 153 if err == syscall.ENFILE { 154 l.logger.Log(level.Key(), level.ErrorValue(), logging.MessageKey(), "ENFILE received. translating to EMFILE") 155 return nil, syscall.EMFILE 156 } 157 158 return nil, err 159 } 160 161 if !l.acquire() { 162 l.logger.Log(level.Key(), level.ErrorValue(), logging.MessageKey(), "rejected connection", "remoteAddress", c.RemoteAddr().String()) 163 l.rejected.Inc() 164 c.Close() 165 continue 166 } 167 168 l.logger.Log(level.Key(), level.DebugValue(), logging.MessageKey(), "accepted connection", "remoteAddress", c.RemoteAddr().String()) 169 return &conn{Conn: c, release: l.release}, nil 170 } 171 } 172 173 // conn is a decorated net.Conn that supplies feedback to a listener when the connection is closed. 174 type conn struct { 175 net.Conn 176 releaseOnce sync.Once 177 release func() 178 } 179 180 // Close closes the decorated connection and invokes release on the listener that created it. The release 181 // operation is idempotent. 182 func (c *conn) Close() error { 183 err := c.Conn.Close() 184 c.releaseOnce.Do(c.release) 185 return err 186 }