github.com/cloudwego/kitex@v0.9.0/pkg/warmup/pool_helper.go (about)

     1  /*
     2   * Copyright 2021 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package warmup
    18  
    19  import (
    20  	"context"
    21  	"net"
    22  	"sync"
    23  
    24  	"github.com/cloudwego/kitex/pkg/gofunc"
    25  	"github.com/cloudwego/kitex/pkg/klog"
    26  	"github.com/cloudwego/kitex/pkg/remote"
    27  )
    28  
    29  // PoolHelper is trivial implementation to do warm-ups for connection pools.
    30  type PoolHelper struct {
    31  	ErrorHandling
    32  }
    33  
    34  // WarmUp warms up the given connection pool.
    35  func (p *PoolHelper) WarmUp(po *PoolOption, pool remote.ConnPool, co remote.ConnOption) (err error) {
    36  	num := 1
    37  	if po.Parallel > num {
    38  		num = po.Parallel
    39  	}
    40  
    41  	ctx, cancel := context.WithCancel(context.Background())
    42  
    43  	mgr := newManager(ctx, po, p.ErrorHandling)
    44  	go mgr.watch()
    45  	var wg sync.WaitGroup
    46  	for i := 0; i < num; i++ {
    47  		wg.Add(1)
    48  		go func() {
    49  			defer wg.Done()
    50  			defer func() {
    51  				if x := recover(); x != nil {
    52  					klog.Errorf("KITEX: warmup: unexpected error: %+v", x)
    53  				}
    54  			}()
    55  			o := &worker{
    56  				jobs: mgr.jobs,
    57  				errs: mgr.errs,
    58  				pool: pool,
    59  				co:   co,
    60  				po:   po,
    61  			}
    62  			o.fire(mgr.control)
    63  		}()
    64  	}
    65  	wg.Wait()
    66  	cancel()
    67  	return mgr.report()
    68  }
    69  
    70  type manager struct {
    71  	ErrorHandling
    72  	work    context.Context
    73  	control context.Context
    74  	cancel  context.CancelFunc
    75  	errs    chan *job
    76  	jobs    chan *job
    77  	bads    []*job
    78  	errLock sync.RWMutex // for bads
    79  }
    80  
    81  func newManager(ctx context.Context, po *PoolOption, eh ErrorHandling) *manager {
    82  	control, cancel := context.WithCancel(ctx)
    83  	return &manager{
    84  		ErrorHandling: eh,
    85  		work:          ctx,
    86  		control:       control,
    87  		cancel:        cancel,
    88  		errs:          make(chan *job),
    89  		jobs:          split(po.Targets, po.ConnNum),
    90  	}
    91  }
    92  
    93  func (m *manager) report() error {
    94  	<-m.work.Done()
    95  	close(m.errs)
    96  	m.errLock.RLock()
    97  	defer m.errLock.RUnlock()
    98  	switch {
    99  	case len(m.bads) == 0 || m.ErrorHandling == IgnoreError:
   100  		return nil
   101  	case m.ErrorHandling == FailFast:
   102  		return m.bads[0].err
   103  	default:
   104  		return m.bads[0].err // TODO: assemble all errors
   105  	}
   106  }
   107  
   108  func (m *manager) watch() {
   109  	for {
   110  		select {
   111  		case <-m.work.Done():
   112  			return
   113  		case tmp := <-m.errs:
   114  			if tmp == nil {
   115  				return // closed
   116  			}
   117  			m.errLock.Lock()
   118  			m.bads = append(m.bads, tmp)
   119  			m.errLock.Unlock()
   120  
   121  			if m.ErrorHandling == FailFast {
   122  				m.cancel() // stop workers
   123  				gofunc.GoFunc(context.Background(), func() {
   124  					// clean up
   125  					discard(m.errs)
   126  					discard(m.jobs)
   127  				})
   128  				return
   129  			}
   130  		default:
   131  			continue
   132  		}
   133  	}
   134  }
   135  
   136  func discard(ch chan *job) {
   137  	for range ch {
   138  	}
   139  }
   140  
   141  func split(targets map[string][]string, dup int) (js chan *job) {
   142  	js = make(chan *job)
   143  	gofunc.GoFunc(context.Background(), func() {
   144  		for network, addresses := range targets {
   145  			for _, address := range addresses {
   146  				for i := 0; i < dup; i++ {
   147  					js <- &job{network: network, address: address}
   148  				}
   149  			}
   150  		}
   151  		close(js)
   152  	})
   153  	return
   154  }
   155  
   156  type job struct {
   157  	network string
   158  	address string
   159  	err     error
   160  }
   161  
   162  type worker struct {
   163  	jobs chan *job
   164  	errs chan *job
   165  	pool remote.ConnPool
   166  	co   remote.ConnOption
   167  	po   *PoolOption
   168  }
   169  
   170  func (w *worker) fire(ctx context.Context) {
   171  	var conns []net.Conn
   172  	defer func() {
   173  		for _, conn := range conns {
   174  			w.pool.Put(conn)
   175  		}
   176  	}()
   177  	for {
   178  		select {
   179  		case <-ctx.Done():
   180  			return
   181  		case wood := <-w.jobs:
   182  			if wood == nil {
   183  				return
   184  			}
   185  			var conn net.Conn
   186  			conn, wood.err = w.pool.Get(
   187  				ctx, wood.network, wood.address, w.co)
   188  			if wood.err == nil {
   189  				conns = append(conns, conn)
   190  			} else {
   191  				w.errs <- wood
   192  			}
   193  		}
   194  	}
   195  }