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  }