github.com/matrixorigin/matrixone@v0.7.0/pkg/common/morpc/client.go (about) 1 // Copyright 2021 - 2022 Matrix Origin 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 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package morpc 16 17 import ( 18 "context" 19 "fmt" 20 "sync" 21 "sync/atomic" 22 "time" 23 24 "github.com/matrixorigin/matrixone/pkg/common/moerr" 25 "github.com/matrixorigin/matrixone/pkg/common/stopper" 26 "github.com/matrixorigin/matrixone/pkg/logutil" 27 "go.uber.org/zap" 28 ) 29 30 // WithClientMaxBackendPerHost maximum number of connections per host 31 func WithClientMaxBackendPerHost(maxBackendsPerHost int) ClientOption { 32 return func(c *client) { 33 c.options.maxBackendsPerHost = maxBackendsPerHost 34 } 35 } 36 37 // WithClientLogger set client logger 38 func WithClientLogger(logger *zap.Logger) ClientOption { 39 return func(c *client) { 40 c.logger = logger 41 } 42 } 43 44 // WithClientInitBackends set the number of connections for the initialized backends. 45 func WithClientInitBackends(backends []string, counts []int) ClientOption { 46 return func(c *client) { 47 if len(backends) != len(counts) { 48 panic("backend and count mismatch") 49 } 50 51 c.options.initBackends = backends 52 c.options.initBackendCounts = counts 53 } 54 } 55 56 // WithClientCreateTaskChanSize set the buffer size of the chan that creates the Backend Task. 57 func WithClientCreateTaskChanSize(size int) ClientOption { 58 return func(c *client) { 59 c.createC = make(chan string, size) 60 } 61 } 62 63 // WithClientMaxBackendMaxIdleDuration set the maximum idle duration of the backend connection. 64 // Backend connection that exceed this time will be automatically closed. 0 means no idle time 65 // limit. 66 func WithClientMaxBackendMaxIdleDuration(value time.Duration) ClientOption { 67 return func(c *client) { 68 c.options.maxIdleDuration = value 69 } 70 } 71 72 func WithClientTag(tag string) ClientOption { 73 return func(c *client) { 74 c.tag = tag 75 } 76 } 77 78 type client struct { 79 tag string 80 logger *zap.Logger 81 stopper *stopper.Stopper 82 factory BackendFactory 83 createC chan string 84 gcInactiveC chan string 85 86 mu struct { 87 sync.Mutex 88 closed bool 89 backends map[string][]Backend 90 ops map[string]*op 91 } 92 93 options struct { 94 maxBackendsPerHost int 95 maxIdleDuration time.Duration 96 initBackends []string 97 initBackendCounts []int 98 } 99 } 100 101 // NewClient create rpc client with options 102 func NewClient(factory BackendFactory, options ...ClientOption) (RPCClient, error) { 103 c := &client{ 104 factory: factory, 105 gcInactiveC: make(chan string), 106 } 107 c.mu.backends = make(map[string][]Backend) 108 c.mu.ops = make(map[string]*op) 109 110 for _, opt := range options { 111 opt(c) 112 } 113 c.adjust() 114 c.stopper = stopper.NewStopper(c.tag, stopper.WithLogger(c.logger)) 115 116 if err := c.maybeInitBackends(); err != nil { 117 c.Close() 118 return nil, err 119 } 120 121 if err := c.stopper.RunTask(c.createTask); err != nil { 122 return nil, err 123 } 124 if c.options.maxIdleDuration > 0 { 125 if err := c.stopper.RunTask(c.gcIdleTask); err != nil { 126 return nil, err 127 } 128 } 129 if err := c.stopper.RunTask(c.gcInactiveTask); err != nil { 130 return nil, err 131 } 132 return c, nil 133 } 134 135 func (c *client) adjust() { 136 c.tag = fmt.Sprintf("rpc-client[%s]", c.tag) 137 c.logger = logutil.Adjust(c.logger).Named(c.tag) 138 if c.createC == nil { 139 c.createC = make(chan string, 16) 140 } 141 if c.options.maxBackendsPerHost == 0 { 142 c.options.maxBackendsPerHost = 1 143 } 144 if len(c.options.initBackendCounts) > 0 { 145 for _, cnt := range c.options.initBackendCounts { 146 if cnt > c.options.maxBackendsPerHost { 147 c.options.maxBackendsPerHost = cnt 148 } 149 } 150 } 151 } 152 153 func (c *client) maybeInitBackends() error { 154 c.mu.Lock() 155 defer c.mu.Unlock() 156 if len(c.options.initBackends) > 0 { 157 for idx, backend := range c.options.initBackends { 158 for i := 0; i < c.options.initBackendCounts[idx]; i++ { 159 _, err := c.createBackendLocked(backend) 160 if err != nil { 161 return err 162 } 163 } 164 } 165 } 166 return nil 167 } 168 169 func (c *client) Send(ctx context.Context, backend string, request Message) (*Future, error) { 170 b, err := c.getBackend(backend, false) 171 if err != nil { 172 return nil, err 173 } 174 175 f, err := b.Send(ctx, request) 176 if err != nil { 177 return nil, err 178 } 179 return f, nil 180 } 181 182 func (c *client) NewStream(backend string, lock bool) (Stream, error) { 183 b, err := c.getBackend(backend, lock) 184 if err != nil { 185 return nil, err 186 } 187 188 return b.NewStream(lock) 189 } 190 191 func (c *client) Ping(ctx context.Context, backend string) error { 192 b, err := c.getBackend(backend, false) 193 if err != nil { 194 return err 195 } 196 197 f, err := b.SendInternal(ctx, &flagOnlyMessage{flag: flagPing}) 198 if err != nil { 199 return err 200 } 201 defer f.Close() 202 _, err = f.Get() 203 return err 204 } 205 206 func (c *client) Close() error { 207 c.mu.Lock() 208 if c.mu.closed { 209 c.mu.Unlock() 210 return nil 211 } 212 c.mu.closed = true 213 214 for _, backends := range c.mu.backends { 215 for _, b := range backends { 216 b.Close() 217 } 218 } 219 c.mu.Unlock() 220 221 c.stopper.Stop() 222 close(c.createC) 223 return nil 224 } 225 226 func (c *client) getBackend(backend string, lock bool) (Backend, error) { 227 c.mu.Lock() 228 b, err := c.getBackendLocked(backend, lock) 229 if err != nil { 230 c.mu.Unlock() 231 return nil, err 232 } 233 if b != nil { 234 c.mu.Unlock() 235 return b, nil 236 } 237 c.mu.Unlock() 238 239 return c.createBackend(backend, lock) 240 } 241 242 func (c *client) getBackendLocked(backend string, lock bool) (Backend, error) { 243 if c.mu.closed { 244 return nil, moerr.NewClientClosedNoCtx() 245 } 246 247 lockedCnt := 0 248 inactiveCnt := 0 249 if backends, ok := c.mu.backends[backend]; ok { 250 n := uint64(len(backends)) 251 var b Backend 252 for i := uint64(0); i < n; i++ { 253 seq := c.mu.ops[backend].next() 254 b = backends[seq%n] 255 if !b.Locked() && b.LastActiveTime() != (time.Time{}) { 256 break 257 } 258 259 if b.Locked() { 260 lockedCnt++ 261 } 262 if b.LastActiveTime() == (time.Time{}) { 263 inactiveCnt++ 264 } 265 b = nil 266 } 267 268 // all backend inactived, trigger gc inactive. 269 if b == nil && n > 0 { 270 c.triggerGCInactive(backend) 271 c.logger.Debug("no available backends", 272 zap.String("backend", backend), 273 zap.Int("locked", lockedCnt), 274 zap.Int("inactive", inactiveCnt), 275 zap.Int("max", c.options.maxBackendsPerHost)) 276 if !c.canCreateLocked(backend) { 277 return nil, moerr.NewNoAvailableBackendNoCtx() 278 } 279 } 280 281 if lock && b != nil { 282 b.Lock() 283 } 284 c.maybeCreateLocked(backend) 285 return b, nil 286 } 287 return nil, nil 288 } 289 290 func (c *client) maybeCreateLocked(backend string) bool { 291 if len(c.mu.backends[backend]) == 0 { 292 return c.tryCreate(backend) 293 } 294 295 if !c.canCreateLocked(backend) { 296 return false 297 } 298 299 for _, b := range c.mu.backends[backend] { 300 if b.Busy() || b.Locked() { 301 return c.tryCreate(backend) 302 } 303 } 304 return false 305 } 306 307 func (c *client) tryCreate(backend string) bool { 308 select { 309 case c.createC <- backend: 310 return true 311 default: 312 return false 313 } 314 } 315 316 func (c *client) gcIdleTask(ctx context.Context) { 317 c.logger.Info("gc idle backends task started") 318 defer c.logger.Error("gc idle backends task stopped") 319 320 ticker := time.NewTicker(c.options.maxIdleDuration) 321 defer ticker.Stop() 322 323 for { 324 select { 325 case <-ctx.Done(): 326 return 327 case <-ticker.C: 328 c.closeIdleBackends() 329 } 330 } 331 } 332 333 func (c *client) triggerGCInactive(remote string) { 334 select { 335 case c.gcInactiveC <- remote: 336 c.logger.Debug("try to remove all inactived backends", 337 zap.String("remote", remote)) 338 default: 339 } 340 } 341 342 func (c *client) gcInactiveTask(ctx context.Context) { 343 c.logger.Info("gc inactive backends task started") 344 defer c.logger.Error("gc inactive backends task stopped") 345 346 for { 347 select { 348 case <-ctx.Done(): 349 return 350 case remote := <-c.gcInactiveC: 351 c.doRemoveInactive(remote) 352 } 353 } 354 } 355 356 func (c *client) doRemoveInactive(remote string) { 357 c.mu.Lock() 358 defer c.mu.Unlock() 359 backends, ok := c.mu.backends[remote] 360 if !ok { 361 return 362 } 363 364 newBackends := backends[:0] 365 for _, backend := range backends { 366 if backend.LastActiveTime() == (time.Time{}) { 367 backend.Close() 368 continue 369 } 370 newBackends = append(newBackends, backend) 371 } 372 c.mu.backends[remote] = newBackends 373 } 374 375 func (c *client) closeIdleBackends() { 376 var idleBackends []Backend 377 c.mu.Lock() 378 for k, backends := range c.mu.backends { 379 var newBackends []Backend 380 for _, b := range backends { 381 if !b.Locked() && 382 time.Since(b.LastActiveTime()) > c.options.maxIdleDuration { 383 idleBackends = append(idleBackends, b) 384 // panic(2) 385 continue 386 } 387 newBackends = append(newBackends, b) 388 } 389 c.mu.backends[k] = newBackends 390 } 391 c.mu.Unlock() 392 393 for _, b := range idleBackends { 394 b.Close() 395 } 396 } 397 398 func (c *client) createTask(ctx context.Context) { 399 for { 400 select { 401 case <-ctx.Done(): 402 return 403 case backend, ok := <-c.createC: 404 if ok { 405 c.mu.Lock() 406 if _, err := c.createBackendLocked(backend); err != nil { 407 c.logger.Error("create backend failed", 408 zap.String("backend", backend), 409 zap.Error(err)) 410 } 411 c.mu.Unlock() 412 } 413 } 414 } 415 } 416 417 func (c *client) createBackend(backend string, lock bool) (Backend, error) { 418 c.mu.Lock() 419 defer c.mu.Unlock() 420 421 b, err := c.getBackendLocked(backend, lock) 422 if err != nil { 423 return nil, err 424 } 425 if b != nil { 426 return b, nil 427 } 428 429 b, err = c.createBackendLocked(backend) 430 if err != nil { 431 return nil, err 432 } 433 if lock { 434 b.Lock() 435 } 436 return b, nil 437 } 438 439 func (c *client) createBackendLocked(backend string) (Backend, error) { 440 if !c.canCreateLocked(backend) { 441 return nil, moerr.NewNoAvailableBackendNoCtx() 442 } 443 444 b, err := c.doCreate(backend) 445 if err != nil { 446 return nil, err 447 } 448 c.mu.backends[backend] = append(c.mu.backends[backend], b) 449 if _, ok := c.mu.ops[backend]; !ok { 450 c.mu.ops[backend] = &op{} 451 } 452 return b, nil 453 } 454 455 func (c *client) doCreate(backend string) (Backend, error) { 456 b, err := c.factory.Create(backend) 457 if err != nil { 458 c.logger.Error("create backend failed", 459 zap.String("backend", backend), 460 zap.Error(err)) 461 return nil, err 462 } 463 return b, nil 464 } 465 466 func (c *client) canCreateLocked(backend string) bool { 467 return len(c.mu.backends[backend]) < c.options.maxBackendsPerHost 468 } 469 470 type op struct { 471 seq uint64 472 } 473 474 func (o *op) next() uint64 { 475 return atomic.AddUint64(&o.seq, 1) 476 }