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 }