github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/gate/server/wsp/pool.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package wsp 20 21 import ( 22 "context" 23 "sync" 24 "time" 25 26 "github.com/google/uuid" 27 "github.com/gorilla/websocket" 28 ) 29 30 // Pool handles all connections from the peer. 31 type Pool struct { 32 timeout time.Duration 33 idleTimeout time.Duration 34 id PoolID 35 36 size int 37 idle chan *Connection 38 39 done bool 40 sync.RWMutex 41 connections map[string]*Connection 42 } 43 44 // PoolID represents the identifier of the connected WebSocket client. 45 type PoolID string 46 47 // NewPool creates a new Pool 48 func NewPool(timeout, idleTimeout time.Duration, id PoolID) *Pool { 49 p := &Pool{ 50 timeout: timeout, 51 idleTimeout: idleTimeout, 52 id: id, 53 idle: make(chan *Connection), 54 connections: make(map[string]*Connection), 55 } 56 return p 57 } 58 59 // RegisterConnection creates a new Connection and adds it to the pool 60 func (p *Pool) RegisterConnection(ws *websocket.Conn) { 61 62 // Ensure we never add a connection to a pool we have garbage collected 63 if p.done { 64 return 65 } 66 67 log.Infof("Registering new connection from %s", p.id) 68 connection := NewConnection(p, ws) 69 id := uuid.NewString() 70 p.Lock() 71 p.connections[id] = connection 72 p.Unlock() 73 74 go func() { 75 connection.WritePump() 76 p.Lock() 77 delete(p.connections, id) 78 p.Unlock() 79 }() 80 81 } 82 83 func (p *Pool) Offer(connection *Connection) { 84 // The original code of root-gg/wsp was invoking goroutine, 85 // but the callder was also invoking goroutine, 86 // so it was deemed unnecessary and removed. 87 p.idle <- connection 88 } 89 90 func (p *Pool) Clean() { 91 idle := 0 92 now := time.Now() 93 for id, connection := range p.connections { 94 // We need to be sur we'll never close a BUSY or soon to be BUSY connection 95 if connection.status == Idle { 96 idle++ 97 if idle > p.size+1 { 98 if now.Sub(connection.idleSince).Seconds() > p.idleTimeout.Seconds() { 99 connection.Close() 100 delete(p.connections, id) 101 } 102 } 103 } 104 if connection.status == Closed { 105 connection.Close() 106 } 107 } 108 } 109 110 // IsEmpty clean the pool and return true if the pool is empty 111 func (p *Pool) IsEmpty() bool { 112 p.Lock() 113 defer p.Unlock() 114 115 p.Clean() 116 return len(p.connections) == 0 117 } 118 119 // Shutdown closes every connections in the pool and cleans it 120 func (p *Pool) Shutdown() { 121 p.Lock() 122 defer p.Unlock() 123 124 p.done = true 125 126 for id, connection := range p.connections { 127 connection.Close() 128 delete(p.connections, id) 129 } 130 } 131 132 // Size return the number of connection in each state in the pool 133 func (p *Pool) Size() (ps *PoolSize) { 134 p.Lock() 135 defer p.Unlock() 136 137 ps = &PoolSize{} 138 for _, connection := range p.connections { 139 if connection.status == Idle { 140 ps.Idle++ 141 } else if connection.status == Busy { 142 ps.Busy++ 143 } else if connection.status == Closed { 144 ps.Closed++ 145 } 146 } 147 148 return 149 } 150 151 func (p *Pool) SetSize(size int) { 152 p.size = size 153 } 154 155 func (p *Pool) GetIdleConnection(ctx context.Context) (conn *Connection) { 156 if p.IsEmpty() { 157 return 158 } 159 160 ctx2, cancel := context.WithTimeout(ctx, p.timeout) 161 defer cancel() 162 163 for { 164 select { 165 case <-ctx2.Done(): 166 return 167 case v := <-p.idle: 168 if v.Take() { 169 conn = v 170 return 171 } 172 } 173 continue 174 } 175 }