| package tinylb | 
 |  | 
 | import ( | 
 | 	"net" | 
 | 	"sort" | 
 | 	"sync" | 
 | ) | 
 |  | 
 | // connectionPool maintains information about open connections to backends, and | 
 | // allows for closing either arbitrary connections (by ID) or all connections to | 
 | // a given backend. | 
 | // | 
 | // This structure exists to allow tinylb to kill all connections of a backend | 
 | // that has just been removed from the BackendSet. | 
 | // | 
 | // Any time a connection is inserted into the pool, a unique ID for that | 
 | // connection is returned. | 
 | // | 
 | // Backends are identified by 'target name' which is an opaque string. | 
 | // | 
 | // This structure is likely the performance bottleneck of the implementation, as | 
 | // it takes a non-RW lock for every incoming connection. | 
 | type connectionPool struct { | 
 | 	mu sync.Mutex | 
 | 	// detailsById maps connection ids to details about that connection. | 
 | 	detailsById map[int64]*connectionDetails | 
 | 	// idsByTarget maps a target name to all connection IDs that opened to it. | 
 | 	idsByTarget map[string][]int64 | 
 |  | 
 | 	// cid is the connection id counter, increased any time a connection ID is | 
 | 	// allocated. | 
 | 	cid int64 | 
 | } | 
 |  | 
 | // connectionDetails for each open connection. These are held in | 
 | // connectionPool.details | 
 | type connectionDetails struct { | 
 | 	// conn is the active net.Conn backing this connection. | 
 | 	conn net.Conn | 
 | 	// target is the target name to which this connection was initiated. | 
 | 	target string | 
 | } | 
 |  | 
 | func newConnectionPool() *connectionPool { | 
 | 	return &connectionPool{ | 
 | 		detailsById: make(map[int64]*connectionDetails), | 
 | 		idsByTarget: make(map[string][]int64), | 
 | 	} | 
 | } | 
 |  | 
 | // Insert a connection that's handled by the given target name, and return the | 
 | // connection ID used to remove this connection later. | 
 | func (c *connectionPool) Insert(target string, conn net.Conn) int64 { | 
 | 	c.mu.Lock() | 
 | 	defer c.mu.Unlock() | 
 |  | 
 | 	id := c.cid | 
 | 	c.cid++ | 
 |  | 
 | 	c.detailsById[id] = &connectionDetails{ | 
 | 		conn:   conn, | 
 | 		target: target, | 
 | 	} | 
 | 	c.idsByTarget[target] = append(c.idsByTarget[target], id) | 
 | 	return id | 
 | } | 
 |  | 
 | // CloseConn closes the underlying connection for the given connection ID, and | 
 | // removes that connection ID from internal tracking. | 
 | func (c *connectionPool) CloseConn(id int64) { | 
 | 	c.mu.Lock() | 
 | 	defer c.mu.Unlock() | 
 |  | 
 | 	cd, ok := c.detailsById[id] | 
 | 	if !ok { | 
 | 		return | 
 | 	} | 
 |  | 
 | 	ids := c.idsByTarget[cd.target] | 
 | 	// ids is technically sorted because 'id' is always monotonically increasing, so | 
 | 	// we could be smarter and do a binary search here. | 
 | 	ix := -1 | 
 | 	for i, id2 := range ids { | 
 | 		if id2 == id { | 
 | 			ix = i | 
 | 			break | 
 | 		} | 
 | 	} | 
 | 	if ix == -1 { | 
 | 		panic("Programming error: connection present in detailsById but not in idsByTarget") | 
 | 	} | 
 | 	c.idsByTarget[cd.target] = append(ids[:ix], ids[ix+1:]...) | 
 | 	cd.conn.Close() | 
 | 	delete(c.detailsById, id) | 
 | } | 
 |  | 
 | // CloseTarget closes all connections to a given backend target name, and removes | 
 | // them from internal tracking. | 
 | func (c *connectionPool) CloseTarget(target string) { | 
 | 	c.mu.Lock() | 
 | 	defer c.mu.Unlock() | 
 |  | 
 | 	for _, id := range c.idsByTarget[target] { | 
 | 		c.detailsById[id].conn.Close() | 
 | 		delete(c.detailsById, id) | 
 | 	} | 
 | 	delete(c.idsByTarget, target) | 
 | } | 
 |  | 
 | // Targets removes all currently active backend target names. | 
 | func (c *connectionPool) Targets() []string { | 
 | 	c.mu.Lock() | 
 | 	defer c.mu.Unlock() | 
 |  | 
 | 	res := make([]string, 0, len(c.idsByTarget)) | 
 | 	for target, _ := range c.idsByTarget { | 
 | 		res = append(res, target) | 
 | 	} | 
 | 	sort.Strings(res) | 
 | 	return res | 
 | } |