github.com/opentelekomcloud/gophertelekomcloud@v0.9.3/openstack/obs/pool.go (about) 1 package obs 2 3 import ( 4 "errors" 5 "fmt" 6 "runtime" 7 "sync" 8 "sync/atomic" 9 "time" 10 ) 11 12 // Future defines interface with function: Get 13 type Future interface { 14 Get() interface{} 15 } 16 17 // FutureResult for task result 18 type FutureResult struct { 19 result interface{} 20 resultChan chan interface{} 21 lock sync.Mutex 22 } 23 24 type panicResult struct { 25 presult interface{} 26 } 27 28 func (f *FutureResult) checkPanic() interface{} { 29 if r, ok := f.result.(panicResult); ok { 30 panic(r.presult) 31 } 32 return f.result 33 } 34 35 // Get gets the task result 36 func (f *FutureResult) Get() interface{} { 37 if f.resultChan == nil { 38 return f.checkPanic() 39 } 40 f.lock.Lock() 41 defer f.lock.Unlock() 42 if f.resultChan == nil { 43 return f.checkPanic() 44 } 45 46 f.result = <-f.resultChan 47 close(f.resultChan) 48 f.resultChan = nil 49 return f.checkPanic() 50 } 51 52 // Task defines interface with function: Run 53 type Task interface { 54 Run() interface{} 55 } 56 57 type funcWrapper struct { 58 f func() interface{} 59 } 60 61 func (fw *funcWrapper) Run() interface{} { 62 if fw.f != nil { 63 return fw.f() 64 } 65 return nil 66 } 67 68 type taskWrapper struct { 69 t Task 70 f *FutureResult 71 } 72 73 func (tw *taskWrapper) Run() interface{} { 74 if tw.t != nil { 75 return tw.t.Run() 76 } 77 return nil 78 } 79 80 type signalTask struct { 81 id string 82 } 83 84 func (signalTask) Run() interface{} { 85 return nil 86 } 87 88 type worker struct { 89 name string 90 taskQueue chan Task 91 wg *sync.WaitGroup 92 pool *RoutinePool 93 } 94 95 func runTask(t Task) { 96 if tw, ok := t.(*taskWrapper); ok { 97 defer func() { 98 if r := recover(); r != nil { 99 tw.f.resultChan <- panicResult{ 100 presult: r, 101 } 102 } 103 }() 104 ret := t.Run() 105 tw.f.resultChan <- ret 106 } else { 107 t.Run() 108 } 109 } 110 111 func (*worker) runTask(t Task) { 112 runTask(t) 113 } 114 115 func (w *worker) start() { 116 go func() { 117 defer func() { 118 if w.wg != nil { 119 w.wg.Done() 120 } 121 }() 122 for { 123 task, ok := <-w.taskQueue 124 if !ok { 125 break 126 } 127 w.pool.AddCurrentWorkingCnt(1) 128 w.runTask(task) 129 w.pool.AddCurrentWorkingCnt(-1) 130 if w.pool.autoTuneWorker(w) { 131 break 132 } 133 } 134 }() 135 } 136 137 func (w *worker) release() { 138 w.taskQueue = nil 139 w.wg = nil 140 w.pool = nil 141 } 142 143 // Pool defines coroutine pool interface 144 type Pool interface { 145 ShutDown() 146 Submit(t Task) (Future, error) 147 SubmitFunc(f func() interface{}) (Future, error) 148 Execute(t Task) 149 ExecuteFunc(f func() interface{}) 150 GetMaxWorkerCnt() int64 151 AddMaxWorkerCnt(value int64) int64 152 GetCurrentWorkingCnt() int64 153 AddCurrentWorkingCnt(value int64) int64 154 GetWorkerCnt() int64 155 AddWorkerCnt(value int64) int64 156 EnableAutoTune() 157 } 158 159 type basicPool struct { 160 maxWorkerCnt int64 161 workerCnt int64 162 currentWorkingCnt int64 163 isShutDown int32 // nolint: structcheck 164 } 165 166 // ErrTaskInvalid will be returned if the task is nil 167 var ErrTaskInvalid = errors.New("Task is nil") 168 169 func (pool *basicPool) GetCurrentWorkingCnt() int64 { 170 return atomic.LoadInt64(&pool.currentWorkingCnt) 171 } 172 173 func (pool *basicPool) AddCurrentWorkingCnt(value int64) int64 { 174 return atomic.AddInt64(&pool.currentWorkingCnt, value) 175 } 176 177 func (pool *basicPool) GetWorkerCnt() int64 { 178 return atomic.LoadInt64(&pool.workerCnt) 179 } 180 181 func (pool *basicPool) AddWorkerCnt(value int64) int64 { 182 return atomic.AddInt64(&pool.workerCnt, value) 183 } 184 185 func (pool *basicPool) GetMaxWorkerCnt() int64 { 186 return atomic.LoadInt64(&pool.maxWorkerCnt) 187 } 188 189 func (pool *basicPool) AddMaxWorkerCnt(value int64) int64 { 190 return atomic.AddInt64(&pool.maxWorkerCnt, value) 191 } 192 193 func (pool *basicPool) CompareAndSwapCurrentWorkingCnt(oldValue, newValue int64) bool { 194 return atomic.CompareAndSwapInt64(&pool.currentWorkingCnt, oldValue, newValue) 195 } 196 197 func (pool *basicPool) EnableAutoTune() { 198 199 } 200 201 // RoutinePool defines the coroutine pool struct 202 type RoutinePool struct { 203 basicPool 204 taskQueue chan Task 205 dispatchQueue chan Task 206 workers map[string]*worker 207 cacheCnt int 208 wg *sync.WaitGroup 209 lock *sync.Mutex 210 shutDownWg *sync.WaitGroup 211 autoTune int32 212 } 213 214 // ErrSubmitTimeout will be returned if submit task timeout when calling SubmitWithTimeout function 215 var ErrSubmitTimeout = errors.New("Submit task timeout") 216 217 // ErrPoolShutDown will be returned if RoutinePool is shutdown 218 var ErrPoolShutDown = errors.New("RoutinePool is shutdown") 219 220 // ErrTaskReject will be returned if submit task is rejected 221 var ErrTaskReject = errors.New("Submit task is rejected") 222 223 var closeQueue = signalTask{id: "closeQueue"} 224 225 // NewRoutinePool creates a RoutinePool instance 226 func NewRoutinePool(maxWorkerCnt, cacheCnt int) Pool { 227 if maxWorkerCnt <= 0 { 228 maxWorkerCnt = runtime.NumCPU() 229 } 230 231 pool := &RoutinePool{ 232 cacheCnt: cacheCnt, 233 wg: new(sync.WaitGroup), 234 lock: new(sync.Mutex), 235 shutDownWg: new(sync.WaitGroup), 236 autoTune: 0, 237 } 238 pool.isShutDown = 0 239 pool.maxWorkerCnt += int64(maxWorkerCnt) 240 if pool.cacheCnt <= 0 { 241 pool.taskQueue = make(chan Task) 242 } else { 243 pool.taskQueue = make(chan Task, pool.cacheCnt) 244 } 245 pool.workers = make(map[string]*worker, pool.maxWorkerCnt) 246 // dispatchQueue must not have length 247 pool.dispatchQueue = make(chan Task) 248 pool.dispatcher() 249 250 return pool 251 } 252 253 // EnableAutoTune sets the autoTune enabled 254 func (pool *RoutinePool) EnableAutoTune() { 255 atomic.StoreInt32(&pool.autoTune, 1) 256 } 257 258 func (pool *RoutinePool) checkStatus(t Task) error { 259 if t == nil { 260 return ErrTaskInvalid 261 } 262 263 if atomic.LoadInt32(&pool.isShutDown) == 1 { 264 return ErrPoolShutDown 265 } 266 return nil 267 } 268 269 func (pool *RoutinePool) dispatcher() { 270 pool.shutDownWg.Add(1) 271 go func() { 272 for { 273 task, ok := <-pool.dispatchQueue 274 if !ok { 275 break 276 } 277 278 if task == closeQueue { 279 close(pool.taskQueue) 280 pool.shutDownWg.Done() 281 continue 282 } 283 284 if pool.GetWorkerCnt() < pool.GetMaxWorkerCnt() { 285 pool.addWorker() 286 } 287 288 pool.taskQueue <- task 289 } 290 }() 291 } 292 293 // AddMaxWorkerCnt sets the maxWorkerCnt field's value and returns it 294 func (pool *RoutinePool) AddMaxWorkerCnt(value int64) int64 { 295 if atomic.LoadInt32(&pool.autoTune) == 1 { 296 return pool.basicPool.AddMaxWorkerCnt(value) 297 } 298 return pool.GetMaxWorkerCnt() 299 } 300 301 func (pool *RoutinePool) addWorker() { 302 if atomic.LoadInt32(&pool.autoTune) == 1 { 303 pool.lock.Lock() 304 defer pool.lock.Unlock() 305 } 306 w := &worker{} 307 w.name = fmt.Sprintf("woker-%d", len(pool.workers)) 308 w.taskQueue = pool.taskQueue 309 w.wg = pool.wg 310 pool.AddWorkerCnt(1) 311 w.pool = pool 312 pool.workers[w.name] = w 313 pool.wg.Add(1) 314 w.start() 315 } 316 317 func (pool *RoutinePool) autoTuneWorker(w *worker) bool { 318 if atomic.LoadInt32(&pool.autoTune) == 0 { 319 return false 320 } 321 322 if w == nil { 323 return false 324 } 325 326 workerCnt := pool.GetWorkerCnt() 327 maxWorkerCnt := pool.GetMaxWorkerCnt() 328 if workerCnt > maxWorkerCnt && atomic.CompareAndSwapInt64(&pool.workerCnt, workerCnt, workerCnt-1) { 329 pool.lock.Lock() 330 defer pool.lock.Unlock() 331 delete(pool.workers, w.name) 332 w.wg.Done() 333 w.release() 334 return true 335 } 336 337 return false 338 } 339 340 // ExecuteFunc creates a funcWrapper instance with the specified function and calls the Execute function 341 func (pool *RoutinePool) ExecuteFunc(f func() interface{}) { 342 fw := &funcWrapper{ 343 f: f, 344 } 345 pool.Execute(fw) 346 } 347 348 // Execute pushes the specified task to the dispatchQueue 349 func (pool *RoutinePool) Execute(t Task) { 350 if t != nil { 351 pool.dispatchQueue <- t 352 } 353 } 354 355 // SubmitFunc creates a funcWrapper instance with the specified function and calls the Submit function 356 func (pool *RoutinePool) SubmitFunc(f func() interface{}) (Future, error) { 357 fw := &funcWrapper{ 358 f: f, 359 } 360 return pool.Submit(fw) 361 } 362 363 // Submit pushes the specified task to the dispatchQueue, and returns the FutureResult and error info 364 func (pool *RoutinePool) Submit(t Task) (Future, error) { 365 if err := pool.checkStatus(t); err != nil { 366 return nil, err 367 } 368 f := &FutureResult{} 369 f.resultChan = make(chan interface{}, 1) 370 tw := &taskWrapper{ 371 t: t, 372 f: f, 373 } 374 pool.dispatchQueue <- tw 375 return f, nil 376 } 377 378 // SubmitWithTimeout pushes the specified task to the dispatchQueue, and returns the FutureResult and error info. 379 // Also takes a timeout value, will return ErrSubmitTimeout if it does't complete within that time. 380 func (pool *RoutinePool) SubmitWithTimeout(t Task, timeout int64) (Future, error) { 381 if timeout <= 0 { 382 return pool.Submit(t) 383 } 384 if err := pool.checkStatus(t); err != nil { 385 return nil, err 386 } 387 timeoutChan := make(chan bool, 1) 388 go func() { 389 time.Sleep(time.Duration(time.Millisecond * time.Duration(timeout))) 390 timeoutChan <- true 391 close(timeoutChan) 392 }() 393 394 f := &FutureResult{} 395 f.resultChan = make(chan interface{}, 1) 396 tw := &taskWrapper{ 397 t: t, 398 f: f, 399 } 400 select { 401 case pool.dispatchQueue <- tw: 402 return f, nil 403 case _, ok := <-timeoutChan: 404 if ok { 405 return nil, ErrSubmitTimeout 406 } 407 return nil, ErrSubmitTimeout 408 } 409 } 410 411 func (pool *RoutinePool) beforeCloseDispatchQueue() { 412 if !atomic.CompareAndSwapInt32(&pool.isShutDown, 0, 1) { 413 return 414 } 415 pool.dispatchQueue <- closeQueue 416 pool.wg.Wait() 417 } 418 419 func (pool *RoutinePool) doCloseDispatchQueue() { 420 close(pool.dispatchQueue) 421 pool.shutDownWg.Wait() 422 } 423 424 // ShutDown closes the RoutinePool instance 425 func (pool *RoutinePool) ShutDown() { 426 pool.beforeCloseDispatchQueue() 427 pool.doCloseDispatchQueue() 428 for _, w := range pool.workers { 429 w.release() 430 } 431 pool.workers = nil 432 pool.taskQueue = nil 433 pool.dispatchQueue = nil 434 } 435 436 // NoChanPool defines the coroutine pool struct 437 type NoChanPool struct { 438 basicPool 439 wg *sync.WaitGroup 440 tokens chan interface{} 441 } 442 443 // NewNochanPool creates a new NoChanPool instance 444 func NewNochanPool(maxWorkerCnt int) Pool { 445 if maxWorkerCnt <= 0 { 446 maxWorkerCnt = runtime.NumCPU() 447 } 448 449 pool := &NoChanPool{ 450 wg: new(sync.WaitGroup), 451 tokens: make(chan interface{}, maxWorkerCnt), 452 } 453 pool.isShutDown = 0 454 pool.AddMaxWorkerCnt(int64(maxWorkerCnt)) 455 456 for i := 0; i < maxWorkerCnt; i++ { 457 pool.tokens <- struct{}{} 458 } 459 460 return pool 461 } 462 463 func (pool *NoChanPool) acquire() { 464 <-pool.tokens 465 } 466 467 func (pool *NoChanPool) release() { 468 pool.tokens <- 1 469 } 470 471 func (pool *NoChanPool) execute(t Task) { 472 pool.wg.Add(1) 473 go func() { 474 pool.acquire() 475 defer func() { 476 pool.release() 477 pool.wg.Done() 478 }() 479 runTask(t) 480 }() 481 } 482 483 // ShutDown closes the NoChanPool instance 484 func (pool *NoChanPool) ShutDown() { 485 if !atomic.CompareAndSwapInt32(&pool.isShutDown, 0, 1) { 486 return 487 } 488 pool.wg.Wait() 489 } 490 491 // Execute executes the specified task 492 func (pool *NoChanPool) Execute(t Task) { 493 if t != nil { 494 pool.execute(t) 495 } 496 } 497 498 // ExecuteFunc creates a funcWrapper instance with the specified function and calls the Execute function 499 func (pool *NoChanPool) ExecuteFunc(f func() interface{}) { 500 fw := &funcWrapper{ 501 f: f, 502 } 503 pool.Execute(fw) 504 } 505 506 // Submit executes the specified task, and returns the FutureResult and error info 507 func (pool *NoChanPool) Submit(t Task) (Future, error) { 508 if t == nil { 509 return nil, ErrTaskInvalid 510 } 511 512 f := &FutureResult{} 513 f.resultChan = make(chan interface{}, 1) 514 tw := &taskWrapper{ 515 t: t, 516 f: f, 517 } 518 519 pool.execute(tw) 520 return f, nil 521 } 522 523 // SubmitFunc creates a funcWrapper instance with the specified function and calls the Submit function 524 func (pool *NoChanPool) SubmitFunc(f func() interface{}) (Future, error) { 525 fw := &funcWrapper{ 526 f: f, 527 } 528 return pool.Submit(fw) 529 }