blob: 956bf75841842ec46f08fc41a775c2eeae450fb0 [file] [log] [blame]
Serge Bazanskic64ba1e2023-03-15 19:15:13 +01001package tinylb
2
3import (
4 "net"
5 "sort"
6 "sync"
7)
8
9// connectionPool maintains information about open connections to backends, and
10// allows for closing either arbitrary connections (by ID) or all connections to
11// a given backend.
12//
13// This structure exists to allow tinylb to kill all connections of a backend
14// that has just been removed from the BackendSet.
15//
16// Any time a connection is inserted into the pool, a unique ID for that
17// connection is returned.
18//
19// Backends are identified by 'target name' which is an opaque string.
20//
21// This structure is likely the performance bottleneck of the implementation, as
22// it takes a non-RW lock for every incoming connection.
23type connectionPool struct {
24 mu sync.Mutex
25 // detailsById maps connection ids to details about that connection.
26 detailsById map[int64]*connectionDetails
27 // idsByTarget maps a target name to all connection IDs that opened to it.
28 idsByTarget map[string][]int64
29
30 // cid is the connection id counter, increased any time a connection ID is
31 // allocated.
32 cid int64
33}
34
35// connectionDetails for each open connection. These are held in
36// connectionPool.details
37type connectionDetails struct {
38 // conn is the active net.Conn backing this connection.
39 conn net.Conn
40 // target is the target name to which this connection was initiated.
41 target string
42}
43
44func newConnectionPool() *connectionPool {
45 return &connectionPool{
46 detailsById: make(map[int64]*connectionDetails),
47 idsByTarget: make(map[string][]int64),
48 }
49}
50
51// Insert a connection that's handled by the given target name, and return the
52// connection ID used to remove this connection later.
53func (c *connectionPool) Insert(target string, conn net.Conn) int64 {
54 c.mu.Lock()
55 defer c.mu.Unlock()
56
57 id := c.cid
58 c.cid++
59
60 c.detailsById[id] = &connectionDetails{
61 conn: conn,
62 target: target,
63 }
64 c.idsByTarget[target] = append(c.idsByTarget[target], id)
65 return id
66}
67
68// CloseConn closes the underlying connection for the given connection ID, and
69// removes that connection ID from internal tracking.
70func (c *connectionPool) CloseConn(id int64) {
71 c.mu.Lock()
72 defer c.mu.Unlock()
73
74 cd, ok := c.detailsById[id]
75 if !ok {
76 return
77 }
78
79 ids := c.idsByTarget[cd.target]
80 // ids is technically sorted because 'id' is always monotonically increasing, so
81 // we could be smarter and do a binary search here.
82 ix := -1
83 for i, id2 := range ids {
84 if id2 == id {
85 ix = i
86 break
87 }
88 }
89 if ix == -1 {
90 panic("Programming error: connection present in detailsById but not in idsByTarget")
91 }
92 c.idsByTarget[cd.target] = append(ids[:ix], ids[ix+1:]...)
93 cd.conn.Close()
94 delete(c.detailsById, id)
95}
96
97// CloseTarget closes all connections to a given backend target name, and removes
98// them from internal tracking.
99func (c *connectionPool) CloseTarget(target string) {
100 c.mu.Lock()
101 defer c.mu.Unlock()
102
103 for _, id := range c.idsByTarget[target] {
104 c.detailsById[id].conn.Close()
105 delete(c.detailsById, id)
106 }
107 delete(c.idsByTarget, target)
108}
109
110// Targets removes all currently active backend target names.
111func (c *connectionPool) Targets() []string {
112 c.mu.Lock()
113 defer c.mu.Unlock()
114
115 res := make([]string, 0, len(c.idsByTarget))
116 for target, _ := range c.idsByTarget {
117 res = append(res, target)
118 }
119 sort.Strings(res)
120 return res
121}