github.com/wfusion/gofusion@v1.1.14/routine/candy.go (about) 1 package routine 2 3 import ( 4 "context" 5 "log" 6 "reflect" 7 "sync" 8 "sync/atomic" 9 10 "github.com/pkg/errors" 11 12 "github.com/wfusion/gofusion/common/constant" 13 "github.com/wfusion/gofusion/common/utils" 14 "github.com/wfusion/gofusion/config" 15 ) 16 17 type candyOption struct { 18 args []any 19 ch chan<- any 20 wg *sync.WaitGroup 21 appName string 22 funcName string 23 } 24 25 func Args(args ...any) utils.OptionFunc[candyOption] { 26 return func(o *candyOption) { 27 o.args = append(o.args, args...) 28 } 29 } 30 31 func WaitGroup(wg *sync.WaitGroup) utils.OptionFunc[candyOption] { 32 return func(o *candyOption) { 33 o.wg = wg 34 } 35 } 36 37 func Channel(ch chan<- any) utils.OptionFunc[candyOption] { 38 return func(o *candyOption) { 39 o.ch = ch 40 } 41 } 42 43 func AppName(name string) utils.OptionFunc[candyOption] { 44 return func(o *candyOption) { 45 o.appName = name 46 } 47 } 48 49 func FuncName(name string) utils.OptionFunc[candyOption] { 50 return func(o *candyOption) { 51 o.funcName = name 52 } 53 } 54 55 func Go(task any, opts ...utils.OptionExtender) { 56 opt := utils.ApplyOptions[candyOption](opts...) 57 if opt.funcName == "" { 58 opt.funcName = utils.GetFuncName(task) 59 } 60 allocate(opt.appName, 1, &NewPoolOption{ApplyTimeout: -1}) 61 exec := func() { 62 defer func() { 63 release(opt.appName, nil, nil) 64 delRoutine(opt.appName, opt.funcName) 65 if opt.wg != nil { 66 opt.wg.Done() 67 } 68 wg.Done() 69 }() 70 71 addRoutine(opt.appName, opt.funcName) 72 wrapPromise(task, false, opts...). 73 OnFailure(func(v any) { 74 if opt.ch == nil { 75 log.Printf("[Gofusion] %s catches an error in routine.Go function: \n"+ 76 "error: %s\nfunc: %s\nfunc signature: %T", 77 config.ComponentGoroutinePool, v, utils.GetFuncName(task), task) 78 } 79 }). 80 OnComplete(func(v any) { 81 if opt.ch != nil { 82 opt.ch <- v 83 } 84 }) 85 } 86 87 wg.Add(1) 88 if forceSync(opt.appName) { 89 exec() 90 } else { 91 go exec() 92 } 93 } 94 95 func Goc(ctx context.Context, task any, opts ...utils.OptionExtender) { 96 opt := utils.ApplyOptions[candyOption](opts...) 97 if opt.funcName == "" { 98 opt.funcName = utils.GetFuncName(task) 99 } 100 allocate(opt.appName, 1, &NewPoolOption{ApplyTimeout: -1}) 101 exec := func() { 102 defer func() { 103 release(opt.appName, nil, nil) 104 delRoutine(opt.appName, opt.funcName) 105 if opt.wg != nil { 106 opt.wg.Done() 107 } 108 wg.Done() 109 }() 110 111 addRoutine(opt.appName, opt.funcName) 112 select { 113 case <-ctx.Done(): 114 case <-wrapPromise(task, false, opts...). 115 OnFailure(func(v any) { 116 if opt.ch == nil { 117 log.Printf("[Gofusion] %s catches an error in routine.Goc function: \n"+ 118 "error: %s\nfunc: %s\nfunc signature: %T", 119 config.ComponentGoroutinePool, v, utils.GetFuncName(task), task) 120 } 121 }). 122 OnComplete(func(v any) { 123 if opt.ch != nil { 124 opt.ch <- v 125 } 126 }). 127 GetChan(): 128 } 129 } 130 131 wg.Add(1) 132 if forceSync(opt.appName) { 133 exec() 134 } else { 135 go exec() 136 } 137 } 138 139 func Loop(task any, opts ...utils.OptionExtender) { 140 opt := utils.ApplyOptions[candyOption](opts...) 141 allocate(opt.appName, 1, &NewPoolOption{ApplyTimeout: -1}, ignoreRecycled()) 142 exec := func() { 143 defer func() { 144 release(opt.appName, nil, ignoreRecycled()) 145 if opt.wg != nil { 146 opt.wg.Done() 147 } 148 }() 149 wrapPromise(task, false, opts...). 150 OnFailure(func(v any) { 151 if opt.ch == nil { 152 log.Printf("[Gofusion] %s catches an error in routine.Loop function: \n"+ 153 "error: %s\nfunc: %s\nfunc signature: %T", 154 config.ComponentGoroutinePool, v, utils.GetFuncName(task), task) 155 } 156 }). 157 OnComplete(func(v any) { 158 if opt.ch != nil { 159 opt.ch <- v 160 } 161 }) 162 } 163 164 go exec() 165 } 166 167 func Loopc(ctx context.Context, task any, opts ...utils.OptionExtender) { 168 opt := utils.ApplyOptions[candyOption](opts...) 169 allocate(opt.appName, 1, &NewPoolOption{ApplyTimeout: -1}, ignoreRecycled()) 170 exec := func() { 171 defer func() { 172 release(opt.appName, nil, ignoreRecycled()) 173 if opt.wg != nil { 174 opt.wg.Done() 175 } 176 }() 177 select { 178 case <-ctx.Done(): 179 case <-wrapPromise(task, false, opts...). 180 OnFailure(func(v any) { 181 if opt.ch == nil { 182 log.Printf("[Gofusion] %s catches an error in routine.Loopc function: \n"+ 183 "error: %s\nfunc: %s\nfunc signature: %T", 184 config.ComponentGoroutinePool, v, utils.GetFuncName(task), task) 185 } 186 }). 187 OnComplete(func(v any) { 188 if opt.ch != nil { 189 opt.ch <- v 190 } 191 }). 192 GetChan(): 193 } 194 } 195 196 go exec() 197 } 198 199 func Promise(fn any, async bool, opts ...utils.OptionExtender) *Future { 200 opt := utils.ApplyOptions[candyOption](opts...) 201 async = async && !forceSync(opt.appName) 202 defer func() { 203 if !async && opt.wg != nil { 204 opt.wg.Done() 205 } 206 }() 207 return wrapPromise(fn, async, append(opts, FuncName(utils.GetFuncName(fn)))...). 208 OnFailure(func(v any) { 209 if opt.ch == nil { 210 log.Printf("[Gofusion] %s catches an error in routine.Loop function: \n"+ 211 "error: %s\nfunc: %s\nfunc signature: %T", 212 config.ComponentGoroutinePool, v, utils.GetFuncName(fn), fn) 213 } 214 }). 215 OnComplete(func(v any) { 216 if opt.ch != nil { 217 opt.ch <- v 218 } 219 }) 220 } 221 222 // wrapPromise support function: 223 // *Future 224 // func() error 225 // func() (any, error) 226 // func(Canceller) 227 // func(Canceller) error 228 // func(Canceller) (any, error) 229 // func(t1 T1, t2 T2, t3 T3, tx ...Tx) 230 // func(t1 T1, t2 T2, t3 T3, tx ...Tx) error 231 // func(t1 T1, t2 T2, t3 T3, tx ...Tx) (any, error) 232 func wrapPromise(fn any, async bool, opts ...utils.OptionExtender) *Future { 233 // check supported function 234 switch act := fn.(type) { 235 case *Future: 236 return act 237 238 case func(), 239 func() error, 240 func() (any, error), 241 func(Canceller), 242 func(Canceller) error, 243 func(Canceller) (any, error): 244 245 return start(fn, async, opts...) 246 247 default: 248 typ := reflect.TypeOf(fn) 249 if typ.Kind() != reflect.Func { 250 return WrapFuture(errors.Errorf("unsupported function type %T", fn), opts...) 251 } 252 if typ.NumOut() > 0 && (typ.NumOut() > 2 || 253 typ.Out(typ.NumOut()-1) != constant.ErrorType || 254 (typ.NumOut() == 2 && typ.Out(0) != constant.AnyType)) { 255 return WrapFuture(errors.Errorf("unsupported function signature %T", fn), opts...) 256 } 257 258 return start(fn, async, opts...) 259 } 260 } 261 262 // WrapFuture return a Future that presents the wrapped value 263 func WrapFuture(value any, opts ...utils.OptionExtender) *Future { 264 opt := utils.ApplyOptions[candyOption](opts...) 265 p := NewPromise() 266 p.AppName = opt.appName 267 if e, ok := value.(error); !ok { 268 _ = p.Resolve(value) 269 } else { 270 _ = p.Reject(e) 271 } 272 273 return p.Future 274 } 275 276 // WhenAny returns a Future. 277 // If any Future is resolved, this Future will be resolved and return result of resolved Future. 278 // Otherwise, it will be rejected with results slice returned by all Futures 279 // Legit types of act are same with Start function 280 func WhenAny(acts ...any) *Future { 281 return WhenAnyMatched(nil, acts...) 282 } 283 284 type anyPromiseResult struct { 285 result any 286 i int 287 } 288 289 // WhenAnyMatched returns a Future. 290 // If any Future is resolved and match the predicate, this Future will be resolved and return result of resolved Future. 291 // If all Futures are cancelled, this Future will be cancelled. 292 // Otherwise, it will be rejected with a NoMatchedError included results slice returned by all Futures 293 // Legit types of act are same with Start function 294 func WhenAnyMatched(predicate func(any) bool, acts ...any) *Future { 295 if predicate == nil { 296 predicate = func(v any) bool { return true } 297 } 298 299 opts := make([]utils.OptionExtender, 0, len(acts)) 300 for i, act := range acts { 301 if opt, ok := act.(utils.OptionExtender); ok { 302 opts = append(opts, opt) 303 acts = append(acts[:i], acts[i+1:]...) 304 } 305 } 306 fs := make([]*Future, len(acts)) 307 for i, act := range acts { 308 fs[i] = Promise(act, true, opts...) 309 } 310 311 nf, rs := NewPromise(), make([]any, len(fs)) 312 if len(acts) == 0 { 313 _ = nf.Resolve(nil) 314 } 315 316 chFails, chDones := make(chan anyPromiseResult), make(chan anyPromiseResult) 317 // close the channel for avoid the sender be blocked 318 closeChan := func(c chan anyPromiseResult) { 319 defer func() { _ = recover() }() 320 close(c) 321 } 322 323 go func() { 324 defer func() { 325 if e := recover(); e != nil { 326 _ = nf.Reject(newErrorWithStacks(e)) 327 } 328 closeChan(chFails) 329 closeChan(chDones) 330 }() 331 332 for i, f := range fs { 333 k := i 334 f.OnSuccess(func(v any) { 335 defer func() { _ = recover() }() 336 chDones <- anyPromiseResult{result: v, i: k} 337 }).OnFailure(func(v any) { 338 defer func() { _ = recover() }() 339 chFails <- anyPromiseResult{result: v, i: k} 340 }).OnCancel(func() { 341 defer func() { _ = recover() }() 342 chFails <- anyPromiseResult{result: ErrCancelled, i: k} 343 }) 344 } 345 }() 346 347 if len(fs) == 1 { 348 select { 349 case r := <-chFails: 350 if _, ok := r.result.(CancelledError); ok { 351 _ = nf.Cancel() 352 } else { 353 _ = nf.Reject(newNoMatchedError1(r.result)) 354 } 355 case r := <-chDones: 356 if predicate(r.result) { 357 _ = nf.Resolve(r.result) 358 } else { 359 _ = nf.Reject(newNoMatchedError1(r.result)) 360 } 361 } 362 363 closeChan(chFails) 364 closeChan(chDones) 365 } else { 366 go func() { 367 defer func() { 368 if e := recover(); e != nil { 369 _ = nf.Reject(newErrorWithStacks(e)) 370 } 371 closeChan(chFails) 372 closeChan(chDones) 373 }() 374 375 j := 0 376 for { 377 select { 378 case r := <-chFails: 379 rs[r.i] = getError(r.result) 380 case r := <-chDones: 381 if !predicate(r.result) { 382 rs[r.i] = r.result 383 } else { 384 // try to cancel other futures 385 for _, f := range fs { 386 _ = f.Cancel() 387 } 388 389 // resolve the future and return result 390 _ = nf.Resolve(r.result) 391 return 392 } 393 } 394 395 if j++; j == len(fs) { 396 m := 0 397 for _, r := range rs { 398 switch r.(type) { 399 case CancelledError: 400 default: 401 m++ 402 } 403 } 404 if m > 0 { 405 _ = nf.Reject(newNoMatchedError(rs)) 406 } else { 407 _ = nf.Cancel() 408 } 409 break 410 } 411 } 412 }() 413 } 414 return nf.Future 415 } 416 417 // WhenAll receives function slice and returns a Future. 418 // If all Futures are resolved, this Future will be resolved and return results slice. 419 // Otherwise, it will be rejected with results slice returned by all Futures 420 // Legit types of act are same with Start function 421 func WhenAll(acts ...any) (fu *Future) { 422 p := NewPromise() 423 fu = p.Future 424 425 opts := make([]utils.OptionExtender, 0, len(acts)) 426 for i, act := range acts { 427 if opt, ok := act.(utils.OptionExtender); ok { 428 opts = append(opts, opt) 429 acts = append(acts[:i], acts[i+1:]...) 430 } 431 } 432 opt := utils.ApplyOptions[candyOption](opts...) 433 p.AppName = opt.appName 434 if len(acts) == 0 { 435 _ = p.Resolve([]any{}) 436 return 437 } 438 439 fs := make([]*Future, len(acts)) 440 for i, act := range acts { 441 fs[i] = Promise(act, true, opts...) 442 } 443 fu = whenAllFuture(fs, opts...) 444 return 445 } 446 447 // WhenAll receives Futures slice and returns a Future. 448 // If all Futures are resolved, this Future will be resolved and return results slice. 449 // If any Future is cancelled, this Future will be cancelled. 450 // Otherwise, it will be rejected with results slice returned by all Futures. 451 // Legit types of act are same with Start function 452 func whenAllFuture(fs []*Future, opts ...utils.OptionExtender) *Future { 453 opt := utils.ApplyOptions[candyOption](opts...) 454 wf := NewPromise() 455 wf.AppName = opt.appName 456 rs := make([]any, len(fs)) 457 458 if len(fs) == 0 { 459 _ = wf.Resolve([]any{}) 460 } else { 461 n := int32(len(fs)) 462 cancelOthers := func(j int) { 463 for k, f1 := range fs { 464 if k != j { 465 _ = f1.Cancel() 466 } 467 } 468 } 469 470 go func() { 471 defer func() { 472 if e := recover(); e != nil { 473 _ = wf.Reject(newErrorWithStacks(e)) 474 } 475 }() 476 477 isCancelled := int32(0) 478 for i, f := range fs { 479 j := i 480 481 f.OnSuccess(func(v any) { 482 rs[j] = v 483 if atomic.AddInt32(&n, -1) == 0 { 484 _ = wf.Resolve(rs) 485 } 486 }).OnFailure(func(v any) { 487 if atomic.CompareAndSwapInt32(&isCancelled, 0, 1) { 488 // try to cancel all futures 489 cancelOthers(j) 490 491 // errs := make([]error, 0, 1) 492 // errs = append(errs, v.(error)) 493 e := newAggregateError1("error appears in WhenAll:", v) 494 _ = wf.Reject(e) 495 } 496 }).OnCancel(func() { 497 if atomic.CompareAndSwapInt32(&isCancelled, 0, 1) { 498 // try to cancel all futures 499 cancelOthers(j) 500 501 _ = wf.Cancel() 502 } 503 }) 504 } 505 }() 506 } 507 508 return wf.Future 509 } 510 511 // start a goroutines to execute task function 512 // and return a Future that presents the result. 513 // If option parameter is true, the act function will be sync called. 514 // Type of act can be any of below four types: 515 // 516 // func() (r any, err error): 517 // if err returned by act != nil or panic error, then Future will be rejected with error, 518 // otherwise be resolved with r. 519 // func(): 520 // if act panic error, then Future will be rejected, otherwise be resolved with nil. 521 // func(c promise.Canceller) (r any, err error): 522 // if err returned by act != nil or panic error, 523 // then Future will be rejected with err, otherwise be resolved with r. 524 // We can check c.IsCancelled() to decide whether we need to exit act function 525 // func(promise.Canceller): 526 // if act panic error, then Future will be rejected with error, otherwise be resolved with nil. 527 // We can check c.IsCancelled() to decide whether we need to exit act function. 528 // error: 529 // Future will be rejected with error immediately 530 // other value: 531 // Future will be resolved with value immediately 532 func start(act any, async bool, opts ...utils.OptionExtender) *Future { 533 if f, ok := act.(*Future); ok { 534 return f 535 } 536 537 opt := utils.ApplyOptions[candyOption](opts...) 538 p := NewPromise() 539 p.AppName = opt.appName 540 if action := getAct(p, act); action != nil { 541 if !async { 542 // sync call 543 r, err := action(opt) 544 if p.IsCancelled() { 545 _ = p.Cancel() 546 } else { 547 if err == nil { 548 _ = p.Resolve(r) 549 } else { 550 _ = p.Reject(err) 551 } 552 } 553 } else { 554 // async call 555 Go(func() { 556 r, err := action(opt) 557 if p.IsCancelled() { 558 _ = p.Cancel() 559 } else { 560 if err == nil { 561 _ = p.Resolve(r) 562 } else { 563 _ = p.Reject(err) 564 } 565 } 566 }, opts...) 567 } 568 } 569 570 return p.Future 571 }