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