github.com/godaddy-x/freego@v1.0.156/rpcx/pool/pool.go (about) 1 // Copyright 2019 shimingyah. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // ee the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package pool 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "google.golang.org/grpc" 22 "log" 23 "math" 24 "sync" 25 "sync/atomic" 26 "time" 27 ) 28 29 type ConnConfig struct { 30 Opts []grpc.DialOption 31 Timeout int 32 Address string 33 } 34 35 // ErrClosed is the error resulting if the pool is closed via pool.Close(). 36 var ErrClosed = errors.New("pool is closed") 37 38 // Pool interface describes a pool implementation. 39 // An ideal pool is threadsafe and easy to use. 40 type Pool interface { 41 // Get returns a new connection from the pool. Closing the connections puts 42 // it back to the Pool. Closing it when the pool is destroyed or full will 43 // be counted as an error. we guarantee the conn.Value() isn't nil when conn isn't nil. 44 Get() (Conn, error) 45 46 // Close closes the pool and all its connections. After Close() the pool is 47 // no longer usable. You can't make concurrent calls Close and Get method. 48 // It will be cause panic. 49 Close() error 50 51 // Status returns the current status of the pool. 52 Status() string 53 } 54 55 type pool struct { 56 // atomic, used to get connection random 57 index uint32 58 59 // atomic, the current physical connection of pool 60 current int32 61 62 // atomic, the using logic connection of pool 63 // logic connection = physical connection * MaxConcurrentStreams 64 ref int32 65 66 // pool options 67 opt Options 68 69 // all of created physical connections 70 conns []*conn 71 72 // the server address is to create connection. 73 address string 74 75 // closed set true when Close is called. 76 closed int32 77 78 // control the atomic var current's concurrent read write. 79 sync.RWMutex 80 81 // connection config 82 connConfig ConnConfig 83 } 84 85 // New return a connection pool. 86 func New(address string, option Options) (Pool, error) { 87 if address == "" { 88 return nil, errors.New("invalid address settings") 89 } 90 if option.Dial == nil { 91 return nil, errors.New("invalid dial settings") 92 } 93 if option.MaxIdle <= 0 || option.MaxActive <= 0 || option.MaxIdle > option.MaxActive { 94 return nil, errors.New("invalid maximum settings") 95 } 96 if option.MaxConcurrentStreams <= 0 { 97 return nil, errors.New("invalid maximun settings") 98 } 99 100 p := &pool{ 101 index: 0, 102 current: int32(option.MaxIdle), 103 ref: 0, 104 opt: option, 105 conns: make([]*conn, option.MaxActive), 106 address: address, 107 closed: 0, 108 } 109 110 for i := 0; i < p.opt.MaxIdle; i++ { 111 c, err := p.opt.Dial(address) 112 if err != nil { 113 p.Close() 114 return nil, fmt.Errorf("dial is not able to fill the pool: %s", err) 115 } 116 p.conns[i] = p.wrapConn(c, false) 117 } 118 log.Printf("new pool success: %v\n", p.Status()) 119 120 return p, nil 121 } 122 123 func (p *pool) NewClientConn() (*grpc.ClientConn, error) { 124 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(p.connConfig.Timeout)*time.Second) 125 defer cancel() 126 conn, err := grpc.DialContext(ctx, p.connConfig.Address, p.connConfig.Opts...) 127 if err != nil { 128 return nil, err 129 } 130 return conn, nil 131 } 132 133 // New return a connection pool. 134 func NewPool(option Options, config ConnConfig) (Pool, error) { 135 if config.Address == "" { 136 return nil, errors.New("invalid address settings") 137 } 138 if config.Timeout == 0 { 139 return nil, errors.New("invalid timeout settings") 140 } 141 if config.Opts == nil || len(config.Opts) == 0 { 142 return nil, errors.New("invalid opts settings") 143 } 144 if option.MaxIdle <= 0 || option.MaxActive <= 0 || option.MaxIdle > option.MaxActive { 145 return nil, errors.New("invalid maximum settings") 146 } 147 if option.MaxConcurrentStreams <= 0 { 148 return nil, errors.New("invalid maximun settings") 149 } 150 151 p := &pool{ 152 index: 0, 153 current: int32(option.MaxIdle), 154 ref: 0, 155 opt: option, 156 conns: make([]*conn, option.MaxActive), 157 address: config.Address, 158 closed: 0, 159 connConfig: config, 160 } 161 162 for i := 0; i < p.opt.MaxIdle; i++ { 163 c, err := p.NewClientConn() 164 if err != nil { 165 p.Close() 166 return nil, fmt.Errorf("dial is not able to fill the pool: %s", err) 167 } 168 p.conns[i] = p.wrapConn(c, false) 169 } 170 log.Printf("new grpc client pool success: %v\n", p.Status()) 171 172 return p, nil 173 } 174 175 func (p *pool) incrRef() int32 { 176 newRef := atomic.AddInt32(&p.ref, 1) 177 if newRef == math.MaxInt32 { 178 panic(fmt.Sprintf("overflow ref: %d", newRef)) 179 } 180 return newRef 181 } 182 183 func (p *pool) decrRef() { 184 newRef := atomic.AddInt32(&p.ref, -1) 185 if newRef < 0 && atomic.LoadInt32(&p.closed) == 0 { 186 panic(fmt.Sprintf("negative ref: %d", newRef)) 187 } 188 if newRef == 0 && atomic.LoadInt32(&p.current) > int32(p.opt.MaxIdle) { 189 p.Lock() 190 if atomic.LoadInt32(&p.ref) == 0 { 191 log.Printf("shrink pool: %d ---> %d, decrement: %d, maxActive: %d\n", 192 p.current, p.opt.MaxIdle, p.current-int32(p.opt.MaxIdle), p.opt.MaxActive) 193 atomic.StoreInt32(&p.current, int32(p.opt.MaxIdle)) 194 p.deleteFrom(p.opt.MaxIdle) 195 } 196 p.Unlock() 197 } 198 } 199 200 func (p *pool) reset(index int) { 201 conn := p.conns[index] 202 if conn == nil { 203 return 204 } 205 conn.reset() 206 p.conns[index] = nil 207 } 208 209 func (p *pool) deleteFrom(begin int) { 210 for i := begin; i < p.opt.MaxActive; i++ { 211 p.reset(i) 212 } 213 } 214 215 // Get see Pool interface. 216 func (p *pool) Get() (Conn, error) { 217 // the first selected from the created connections 218 nextRef := p.incrRef() 219 p.RLock() 220 current := atomic.LoadInt32(&p.current) 221 p.RUnlock() 222 if current == 0 { 223 return nil, ErrClosed 224 } 225 if nextRef <= current*int32(p.opt.MaxConcurrentStreams) { 226 next := atomic.AddUint32(&p.index, 1) % uint32(current) 227 return p.conns[next], nil 228 } 229 230 // the number connection of pool is reach to max active 231 if current == int32(p.opt.MaxActive) { 232 // the second if reuse is true, select from pool's connections 233 if p.opt.Reuse { 234 next := atomic.AddUint32(&p.index, 1) % uint32(current) 235 return p.conns[next], nil 236 } 237 // the third create one-time connection 238 //c, err := p.opt.Dial(p.address) 239 c, err := p.NewClientConn() 240 return p.wrapConn(c, true), err 241 } 242 243 // the fourth create new connections given back to pool 244 p.Lock() 245 current = atomic.LoadInt32(&p.current) 246 if current < int32(p.opt.MaxActive) && nextRef > current*int32(p.opt.MaxConcurrentStreams) { 247 // 2 times the incremental or the remain incremental 248 increment := current 249 if current+increment > int32(p.opt.MaxActive) { 250 increment = int32(p.opt.MaxActive) - current 251 } 252 var i int32 253 var err error 254 for i = 0; i < increment; i++ { 255 //c, er := p.opt.Dial(p.address) 256 c, er := p.NewClientConn() 257 if er != nil { 258 err = er 259 break 260 } 261 p.reset(int(current + i)) 262 p.conns[current+i] = p.wrapConn(c, false) 263 } 264 current += i 265 log.Printf("grow pool: %d ---> %d, increment: %d, maxActive: %d\n", 266 p.current, current, increment, p.opt.MaxActive) 267 atomic.StoreInt32(&p.current, current) 268 if err != nil { 269 p.Unlock() 270 return nil, err 271 } 272 } 273 p.Unlock() 274 next := atomic.AddUint32(&p.index, 1) % uint32(current) 275 return p.conns[next], nil 276 } 277 278 // Close see Pool interface. 279 func (p *pool) Close() error { 280 atomic.StoreInt32(&p.closed, 1) 281 atomic.StoreUint32(&p.index, 0) 282 atomic.StoreInt32(&p.current, 0) 283 atomic.StoreInt32(&p.ref, 0) 284 p.deleteFrom(0) 285 log.Printf("close pool success: %v\n", p.Status()) 286 return nil 287 } 288 289 // Status see Pool interface. 290 func (p *pool) Status() string { 291 return fmt.Sprintf("address:%s, index:%d, current:%d, ref:%d. option:%v", 292 p.address, p.index, p.current, p.ref, p.opt) 293 }