github.com/kaydxh/golang@v0.0.131/pkg/pool/instance/pool.instance.go (about)

     1  /*
     2   *Copyright (c) 2022, kaydxh
     3   *
     4   *Permission is hereby granted, free of charge, to any person obtaining a copy
     5   *of this software and associated documentation files (the "Software"), to deal
     6   *in the Software without restriction, including without limitation the rights
     7   *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8   *copies of the Software, and to permit persons to whom the Software is
     9   *furnished to do so, subject to the following conditions:
    10   *
    11   *The above copyright notice and this permission notice shall be included in all
    12   *copies or substantial portions of the Software.
    13   *
    14   *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15   *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16   *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17   *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18   *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19   *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    20   *SOFTWARE.
    21   */
    22  package instance
    23  
    24  import (
    25  	"context"
    26  	"fmt"
    27  	"path/filepath"
    28  	"sync"
    29  	"time"
    30  
    31  	runtime_ "github.com/kaydxh/golang/go/runtime"
    32  	time_ "github.com/kaydxh/golang/go/time"
    33  	"github.com/sirupsen/logrus"
    34  )
    35  
    36  type GlobalInitFunc func() error
    37  type GlobalReleaseFunc func() error
    38  type LocalInitFunc func(instance interface{}) error
    39  type LocalReleaseFunc func(instance interface{}) error
    40  type NewFunc func() interface{}
    41  type DeleteFunc func(instance interface{})
    42  
    43  type LoadBalanceMode int
    44  
    45  const (
    46  	RoundRobinBalanceMode LoadBalanceMode = 0
    47  	RandomLoadBalanceMode LoadBalanceMode = 1
    48  	LeastLoadBalanceMode  LoadBalanceMode = 2
    49  )
    50  
    51  type PoolOptions struct {
    52  	name                    string
    53  	resevePoolSizePerCore   int64
    54  	capacityPoolSizePerCore int64
    55  	idleTimeout             time.Duration
    56  	// the wait time to try get instance again, 0 means wait forever,
    57  	// execute the block without any timeout
    58  	waitTimeoutOnce time.Duration
    59  
    60  	// the total wait time to try get instance, 0 means wait forever,
    61  	// execute the block without any timeout
    62  	waitTimeoutTotal time.Duration
    63  	loadBalanceMode  LoadBalanceMode
    64  
    65  	coreIDs []int64
    66  
    67  	modelPaths []string
    68  	batchSize  int64
    69  
    70  	globalInitFunc    GlobalInitFunc
    71  	globalReleaseFunc GlobalReleaseFunc
    72  	localInitFunc     LocalInitFunc
    73  	localReleaseFunc  LocalReleaseFunc
    74  	deleteFunc        DeleteFunc
    75  
    76  	enabledPrintCostTime bool
    77  }
    78  
    79  type Pool struct {
    80  	newFunc NewFunc
    81  
    82  	holders map[int64]chan *CoreInstanceHolder
    83  	opts    PoolOptions
    84  	mu      sync.RWMutex
    85  }
    86  
    87  func defaultPoolOptions() PoolOptions {
    88  	return PoolOptions{
    89  		resevePoolSizePerCore:   0,
    90  		capacityPoolSizePerCore: 1,
    91  		idleTimeout:             10 * time.Second,
    92  		waitTimeoutOnce:         10 * time.Millisecond,
    93  	}
    94  }
    95  
    96  func NewPool(newFunc NewFunc, opts ...PoolOption) (*Pool, error) {
    97  	if newFunc == nil {
    98  		return nil, fmt.Errorf("new func is nil")
    99  
   100  	}
   101  	p := &Pool{
   102  		newFunc: newFunc,
   103  		holders: make(map[int64]chan *CoreInstanceHolder),
   104  		opts:    defaultPoolOptions(),
   105  	}
   106  	p.ApplyOptions(opts...)
   107  
   108  	if p.opts.resevePoolSizePerCore < 0 {
   109  		p.opts.resevePoolSizePerCore = 0
   110  	}
   111  
   112  	if p.opts.capacityPoolSizePerCore < p.opts.resevePoolSizePerCore {
   113  		p.opts.capacityPoolSizePerCore = p.opts.resevePoolSizePerCore
   114  	}
   115  
   116  	return p, nil
   117  }
   118  
   119  func (p *Pool) init(ctx context.Context) error {
   120  
   121  	for _, id := range p.opts.coreIDs {
   122  		if id < 0 {
   123  			continue
   124  		}
   125  
   126  		p.holders[id] = make(chan *CoreInstanceHolder, p.opts.capacityPoolSizePerCore)
   127  		for i := int64(0); i < p.opts.resevePoolSizePerCore; i++ {
   128  			holder := &CoreInstanceHolder{
   129  				Name:       p.opts.name,
   130  				CoreID:     id,
   131  				ModelPaths: p.opts.modelPaths,
   132  				BatchSize:  p.opts.batchSize,
   133  			}
   134  			err := holder.Do(ctx, func() {
   135  				holder.Instance = p.newFunc()
   136  			})
   137  			if err != nil {
   138  				return err
   139  			}
   140  			if holder.Instance == nil {
   141  				return fmt.Errorf("instance the result of new func is nil")
   142  			}
   143  
   144  			var processErr error
   145  			err = holder.Do(ctx, func() {
   146  				processErr = p.opts.localInitFunc(holder.Instance)
   147  			})
   148  
   149  			if processErr != nil {
   150  				return processErr
   151  			}
   152  			if err != nil {
   153  				return err
   154  			}
   155  
   156  			p.holders[id] <- holder
   157  		}
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  func (p *Pool) GlobalInit(ctx context.Context) error {
   164  	err := p.globalInit(ctx)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	return p.init(ctx)
   170  }
   171  
   172  func (p *Pool) globalInit(ctx context.Context) error {
   173  	if p.opts.globalInitFunc == nil {
   174  		return nil
   175  	}
   176  
   177  	return p.opts.globalInitFunc()
   178  }
   179  
   180  func (p *Pool) GlobalRelease(ctx context.Context) error {
   181  	for _, ch := range p.holders {
   182  		select {
   183  		case holder := <-ch:
   184  			holder.Do(ctx, func() {
   185  				if p.opts.localReleaseFunc != nil {
   186  					p.opts.localReleaseFunc(holder.Instance)
   187  				}
   188  
   189  				if p.opts.deleteFunc != nil {
   190  					p.opts.deleteFunc(holder.Instance)
   191  				}
   192  			})
   193  
   194  		case <-ctx.Done():
   195  			return ctx.Err()
   196  
   197  		default:
   198  		}
   199  	}
   200  
   201  	return p.opts.globalReleaseFunc()
   202  }
   203  
   204  func (p *Pool) globalRelease(ctx context.Context) error {
   205  	if p.opts.globalReleaseFunc == nil {
   206  		return nil
   207  	}
   208  
   209  	return p.opts.globalReleaseFunc()
   210  }
   211  
   212  func (p *Pool) GetByCoreId(ctx context.Context, coreID int64) (*CoreInstanceHolder, error) {
   213  	if p.opts.capacityPoolSizePerCore <= 0 {
   214  		return nil, fmt.Errorf("no instance, capacity pool size per core is <= 0")
   215  	}
   216  
   217  	if p.opts.waitTimeoutOnce == 0 {
   218  		select {
   219  		case holder := <-p.holders[coreID]:
   220  			logrus.Infof("get a instance in core id: %v", coreID)
   221  			return holder, nil
   222  		case <-ctx.Done():
   223  			return nil, ContextCanceledError{Message: ctx.Err().Error()}
   224  		}
   225  	}
   226  
   227  	timer := time.NewTimer(p.opts.waitTimeoutOnce)
   228  	defer timer.Stop()
   229  
   230  	select {
   231  	case holder := <-p.holders[coreID]:
   232  		logrus.Infof("get a instance in core id: %v", coreID)
   233  		return holder, nil
   234  	case <-ctx.Done():
   235  		logrus.WithError(ctx.Err()).Errorf("get a instance in core id: %v context canceled", coreID)
   236  		return nil, ContextCanceledError{Message: ctx.Err().Error()}
   237  	case <-timer.C:
   238  		msg := fmt.Sprintf("get instance timeout: %v, try again.", p.opts.waitTimeoutOnce)
   239  		logrus.Warnf(msg)
   240  		return nil, TimeoutError{Message: msg}
   241  	}
   242  
   243  }
   244  
   245  func (p *Pool) Get(ctx context.Context) (*CoreInstanceHolder, error) {
   246  
   247  	switch p.opts.loadBalanceMode {
   248  	case RoundRobinBalanceMode:
   249  		return p.GetWithRoundRobinMode(ctx)
   250  	default:
   251  		return nil, fmt.Errorf("not support the loadBalance mode: %v", p.opts.loadBalanceMode)
   252  	}
   253  }
   254  
   255  // until get instance, unless context canceled
   256  func (p *Pool) GetWithRoundRobinMode(ctx context.Context) (*CoreInstanceHolder, error) {
   257  
   258  	remain := p.opts.waitTimeoutTotal
   259  
   260  	for {
   261  		for _, id := range p.opts.coreIDs {
   262  			tc := time_.New(true)
   263  
   264  			holder, err := p.GetByCoreId(ctx, id)
   265  			if err == nil {
   266  				return holder, nil
   267  			}
   268  
   269  			if p.opts.waitTimeoutTotal > 0 {
   270  				remain -= tc.Elapse()
   271  				if remain <= 0 {
   272  					msg := fmt.Sprintf("get instance total timeout: %v, remain: %v", p.opts.waitTimeoutTotal, remain)
   273  					logrus.Errorf(msg)
   274  					return nil, TimeoutError{Message: msg}
   275  				}
   276  			}
   277  
   278  			switch err.(type) {
   279  			case ContextCanceledError:
   280  				return nil, err
   281  			default:
   282  			}
   283  		}
   284  	}
   285  }
   286  
   287  func (p *Pool) Put(ctx context.Context, holder *CoreInstanceHolder) error {
   288  	select {
   289  	case p.holders[holder.CoreID] <- holder:
   290  		fmt.Println("put")
   291  		return nil
   292  
   293  	case <-ctx.Done():
   294  		return ctx.Err()
   295  
   296  	default:
   297  	}
   298  
   299  	// if get this line, it means put instance error, then we delete this instance
   300  	if p.opts.localReleaseFunc != nil {
   301  		holder.Do(ctx, func() {
   302  			p.opts.localReleaseFunc(holder.Instance)
   303  		})
   304  
   305  	}
   306  	if p.opts.deleteFunc != nil {
   307  		holder.Do(ctx, func() {
   308  			p.opts.deleteFunc(holder.Instance)
   309  		})
   310  	}
   311  
   312  	return nil
   313  }
   314  
   315  func (p *Pool) Invoke(
   316  	ctx context.Context,
   317  	f func(ctx context.Context, instance interface{}) (interface{}, error),
   318  ) (response interface{}, err error) {
   319  
   320  	holder := &CoreInstanceHolder{}
   321  	tc := time_.New(p.opts.enabledPrintCostTime)
   322  	summary := func() {
   323  		var coreID int64 = -1
   324  		if holder != nil {
   325  			coreID = holder.CoreID
   326  		}
   327  		tc.Tick(fmt.Sprintf("Invoke %v no coreID: %v", filepath.Base(runtime_.NameOfFunction(f)), coreID))
   328  		logrus.Infof(tc.String())
   329  	}
   330  	defer summary()
   331  
   332  	if f == nil {
   333  		return nil, nil
   334  	}
   335  
   336  	holder, err = p.Get(ctx)
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  	defer p.Put(ctx, holder)
   341  
   342  	var processErr error
   343  	err = holder.Do(ctx, func() {
   344  		response, processErr = f(ctx, holder.Instance)
   345  	})
   346  
   347  	if processErr != nil {
   348  		err = processErr
   349  	}
   350  
   351  	return response, err
   352  }