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  }