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  }