github.com/kaydxh/golang@v0.0.131/pkg/pool/taskqueue/pool.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 taskqueue
    23  
    24  import (
    25  	"context"
    26  	"fmt"
    27  	"sync"
    28  	"time"
    29  
    30  	errors_ "github.com/kaydxh/golang/go/errors"
    31  	time_ "github.com/kaydxh/golang/go/time"
    32  	queue_ "github.com/kaydxh/golang/pkg/pool/taskqueue/queue"
    33  	"github.com/sirupsen/logrus"
    34  )
    35  
    36  type PoolOptions struct {
    37  	workerBurst   uint32
    38  	fetcherBurst  uint32
    39  	fetchTimeout  time.Duration
    40  	resultExpired time.Duration
    41  	workTimeout   time.Duration
    42  
    43  	resultCallbackFunc queue_.ResultCallbackFunc
    44  }
    45  
    46  type Pool struct {
    47  	msgChan chan *queue_.Message
    48  	opts    PoolOptions
    49  	taskq   queue_.Queue
    50  
    51  	wg sync.WaitGroup
    52  }
    53  
    54  func defaultPoolOptions() PoolOptions {
    55  	return PoolOptions{
    56  		workerBurst:   1,
    57  		fetcherBurst:  1,
    58  		fetchTimeout:  10 * time.Second,
    59  		resultExpired: 30 * time.Minute,
    60  	}
    61  }
    62  
    63  func NewPool(taskq queue_.Queue, opts ...PoolOption) *Pool {
    64  	p := &Pool{
    65  		msgChan: make(chan *queue_.Message),
    66  		taskq:   taskq,
    67  		opts:    defaultPoolOptions(),
    68  	}
    69  	p.ApplyOptions(opts...)
    70  
    71  	return p
    72  }
    73  
    74  func (p *Pool) Publish(ctx context.Context, msg *queue_.Message) (string, error) {
    75  	taskId, err := p.taskq.Add(ctx, msg)
    76  	if err != nil {
    77  		logrus.WithError(err).Errorf("failed to publish msg: %v", msg)
    78  		return "", err
    79  	}
    80  	logrus.Infof("succeed in publishing msg: %v", msg)
    81  
    82  	result := &queue_.MessageResult{
    83  		Id:      msg.Id,
    84  		InnerId: msg.InnerId,
    85  		Name:    msg.Name,
    86  		Scheme:  msg.Scheme,
    87  		Status:  queue_.MessageStatus_Doing,
    88  	}
    89  
    90  	//write to queue
    91  	_, err = p.taskq.AddResult(ctx, result, p.opts.resultExpired)
    92  	if err != nil {
    93  		//only log error, this error not stop to handle msg
    94  		logrus.WithError(err).Errorf("failed to add msg result: %v", result)
    95  	}
    96  	return taskId, nil
    97  }
    98  
    99  func (p *Pool) Consume(ctx context.Context) (err error) {
   100  
   101  	var i uint32
   102  	for i = 0; i < p.opts.workerBurst; i++ {
   103  		p.wg.Add(1)
   104  		go func() {
   105  			defer p.wg.Done()
   106  
   107  			for {
   108  				select {
   109  				case msg, ok := <-p.msgChan:
   110  					if !ok {
   111  						logrus.Errorf("message channel is closed.")
   112  						return
   113  					}
   114  					err := p.Work(ctx, msg)
   115  					if err != nil {
   116  						logrus.Errorf("faild to process msg, err: %v", err)
   117  					}
   118  				case <-ctx.Done():
   119  					//  err: context canceled
   120  					return
   121  
   122  				}
   123  			}
   124  
   125  		}()
   126  	}
   127  
   128  	for i = 0; i < p.opts.fetcherBurst; i++ {
   129  		p.wg.Add(1)
   130  		go func() {
   131  			defer p.wg.Done()
   132  
   133  			for {
   134  
   135  				const backoff = time.Second
   136  				msg, err := p.taskq.FetchOne(ctx, p.opts.fetchTimeout)
   137  				if err != nil {
   138  					logrus.Errorf("faild to fetch msg, err: %v", err)
   139  					//todo backoff
   140  					time.Sleep(backoff)
   141  					continue
   142  				}
   143  				if msg == nil {
   144  					logrus.Debugf("no msg to fetch")
   145  					time.Sleep(backoff)
   146  					continue
   147  				}
   148  
   149  				select {
   150  				case p.msgChan <- msg:
   151  				case <-ctx.Done():
   152  					//  err: context canceled
   153  					return
   154  
   155  				}
   156  			}
   157  
   158  		}()
   159  
   160  	}
   161  
   162  	return nil
   163  }
   164  
   165  func (p *Pool) FetchResult(ctx context.Context, key string) (*queue_.MessageResult, error) {
   166  	result, err := p.taskq.FetchResult(ctx, key)
   167  	if err != nil {
   168  		logrus.WithError(err).Errorf("failed to fetch result of msg %v", key)
   169  		return nil, err
   170  	}
   171  
   172  	return result, nil
   173  }
   174  
   175  func (p *Pool) Work(ctx context.Context, msg *queue_.Message) error {
   176  
   177  	tasker := Get(msg.Scheme)
   178  	if tasker == nil {
   179  		return fmt.Errorf("not regiter task scheme %v", msg.Scheme)
   180  	}
   181  
   182  	var errs []error
   183  	clean := func() {
   184  		err := p.taskq.Delete(ctx, msg)
   185  		if err != nil {
   186  			// only log error
   187  			logrus.WithError(err).Errorf("failed to delete msg: %v", msg)
   188  			return
   189  		}
   190  		logrus.Infof("delete msg: %v", msg)
   191  	}
   192  	defer clean()
   193  
   194  	result := &queue_.MessageResult{
   195  		Id:      msg.Id,
   196  		InnerId: msg.InnerId,
   197  		Name:    msg.Name,
   198  		Scheme:  msg.Scheme,
   199  		Status:  queue_.MessageStatus_Doing,
   200  	}
   201  	err := time_.CallWithTimeout(ctx, p.opts.workTimeout, func(ctx context.Context) error {
   202  		result_, err_ := tasker.TaskHandler(ctx, msg)
   203  		if err_ != nil {
   204  			errs = append(errs, err_)
   205  			logrus.WithError(err_).Errorf("failed to handle task %v, err: %v", msg, err_)
   206  		} else {
   207  
   208  			if result_ != nil {
   209  				result = result_
   210  			}
   211  		}
   212  		return err_
   213  	})
   214  	result.Err = err
   215  
   216  	if err == time_.ErrTimeout || err == context.Canceled {
   217  		result.Status = queue_.MessageStatus_Fail
   218  	} else {
   219  		// success means the message is processed, but not means hander
   220  		// function return nil, just not run timeout or canceled
   221  		result.Status = queue_.MessageStatus_Success
   222  	}
   223  
   224  	//callback result
   225  	if p.opts.resultCallbackFunc != nil {
   226  		p.opts.resultCallbackFunc(ctx, result)
   227  	}
   228  
   229  	//write to queue
   230  	_, err = p.taskq.AddResult(ctx, result, p.opts.resultExpired)
   231  	if err != nil {
   232  		errs = append(errs, err)
   233  		//only log error
   234  		logrus.WithError(err).Errorf("failed to add msg result: %v", result)
   235  		return errors_.NewAggregate(errs)
   236  	}
   237  
   238  	return nil
   239  }