github.com/stafiprotocol/go-substrate-rpc-client@v1.4.7/pkg/websocketpool/pool.go (about)

     1  package websocket_pool
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/sirupsen/logrus"
     9  	"github.com/stafiprotocol/go-substrate-rpc-client/pkg/recws"
    10  )
    11  
    12  var (
    13  	ErrClosed = errors.New("pool is closed")
    14  )
    15  
    16  type Pool interface {
    17  	Get() (*PoolConn, error)
    18  	Put(*PoolConn) error
    19  	Len() int
    20  }
    21  
    22  type WsPool struct {
    23  	mu      sync.RWMutex
    24  	exist   map[*PoolConn]bool
    25  	conns   chan *PoolConn
    26  	factory Factory
    27  }
    28  
    29  type Factory func() (*PoolConn, error)
    30  
    31  func NewWsPool(initialCap, maxCap int, factory Factory) (Pool, error) {
    32  	if initialCap < 0 || maxCap <= 0 || initialCap > maxCap {
    33  		return nil, errors.New("invalid capacity settings")
    34  	}
    35  	c := &WsPool{
    36  		conns:   make(chan *PoolConn, maxCap),
    37  		exist:   map[*PoolConn]bool{},
    38  		factory: factory,
    39  	}
    40  	c.mu.Lock()
    41  	defer c.mu.Unlock()
    42  	for i := 0; i < initialCap; i++ {
    43  		conn, err := factory()
    44  		if err != nil {
    45  			c.Close()
    46  			return nil, fmt.Errorf("factory is not able to fill the pool: %s", err)
    47  		}
    48  		c.conns <- conn
    49  		c.exist[conn] = true
    50  	}
    51  	return c, nil
    52  }
    53  
    54  func (c *WsPool) getConnsAndFactory() (chan *PoolConn, Factory) {
    55  	conns := c.conns
    56  	factory := c.factory
    57  	return conns, factory
    58  }
    59  
    60  func (c *WsPool) Get() (*PoolConn, error) {
    61  	conns, factory := c.getConnsAndFactory()
    62  	if conns == nil {
    63  		return nil, ErrClosed
    64  	}
    65  	logrus.Tracef("WsPool.Get pool len :%d", len(c.conns))
    66  
    67  	var err error
    68  	select {
    69  	case conn := <-conns:
    70  		if conn == nil || !conn.Conn.IsConnected() {
    71  			c.mu.Lock()
    72  			delete(c.exist, conn)
    73  			c.mu.Unlock()
    74  
    75  			logrus.Trace("use factory reconnect")
    76  			conn, err = factory()
    77  			if err != nil {
    78  				return nil, err
    79  			}
    80  		} else {
    81  			c.mu.Lock()
    82  			delete(c.exist, conn)
    83  			c.mu.Unlock()
    84  		}
    85  		return conn, nil
    86  	default:
    87  		conn, err := factory()
    88  		if err != nil {
    89  			return nil, err
    90  		}
    91  		return conn, nil
    92  	}
    93  }
    94  
    95  func (c *WsPool) Put(conn *PoolConn) error {
    96  	if conn == nil {
    97  		return errors.New("connection is nil. rejecting")
    98  	}
    99  	conn.mu.Lock()
   100  	defer conn.mu.Unlock()
   101  
   102  	logrus.Tracef("WsPool.Put pool len :%d", len(c.conns))
   103  	if conn.unusable {
   104  		if conn.Conn != nil {
   105  			conn.Conn.Close()
   106  		}
   107  		return nil
   108  	}
   109  
   110  	c.mu.RLock()
   111  	if c.exist[conn] {
   112  		c.mu.RUnlock()
   113  		return nil
   114  	}
   115  	c.mu.RUnlock()
   116  
   117  	if c.conns == nil {
   118  		conn.Conn.Close()
   119  		return nil
   120  	}
   121  
   122  	select {
   123  	case c.conns <- conn:
   124  		c.mu.Lock()
   125  		c.exist[conn] = true
   126  		c.mu.Unlock()
   127  		return nil
   128  	default:
   129  		conn.Conn.Close()
   130  		return nil
   131  	}
   132  }
   133  
   134  func (c *WsPool) Close() {
   135  	c.mu.Lock()
   136  	conns := c.conns
   137  	c.conns = nil
   138  	c.factory = nil
   139  	c.mu.Unlock()
   140  
   141  	if conns == nil {
   142  		return
   143  	}
   144  
   145  	close(conns)
   146  	for conn := range conns {
   147  		conn.Conn.Close()
   148  	}
   149  }
   150  
   151  func (c *WsPool) Len() int {
   152  	c.mu.RLock()
   153  	defer c.mu.RUnlock()
   154  	conns, _ := c.getConnsAndFactory()
   155  	return len(conns)
   156  }
   157  
   158  type PoolConn struct {
   159  	Conn     *recws.RecConn
   160  	mu       sync.RWMutex
   161  	unusable bool
   162  }
   163  
   164  // MarkUnusable() marks the connection not usable any more, to let the pool close it instead of returning it to pool.
   165  func (p *PoolConn) MarkUnusable() {
   166  	p.mu.Lock()
   167  	defer p.mu.Unlock()
   168  	p.unusable = true
   169  }
   170  
   171  // newConn wraps a standard net.Conn to a poolConn net.Conn.
   172  func WrapConn(conn *recws.RecConn) *PoolConn {
   173  	p := &PoolConn{}
   174  	p.Conn = conn
   175  	return p
   176  }