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  }