timeout.go 1.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. package fx
  2. import (
  3. "context"
  4. "fmt"
  5. "runtime/debug"
  6. "strings"
  7. "time"
  8. )
  9. var (
  10. // ErrCanceled is the error returned when the context is canceled.
  11. ErrCanceled = context.Canceled
  12. // ErrTimeout is the error returned when the context's deadline passes.
  13. ErrTimeout = context.DeadlineExceeded
  14. )
  15. // DoOption defines the method to customize a DoWithTimeout call.
  16. type DoOption func() context.Context
  17. // DoWithTimeout runs fn with timeout control.
  18. func DoWithTimeout(fn func() error, timeout time.Duration, opts ...DoOption) error {
  19. parentCtx := context.Background()
  20. for _, opt := range opts {
  21. parentCtx = opt()
  22. }
  23. ctx, cancel := context.WithTimeout(parentCtx, timeout)
  24. defer cancel()
  25. // create channel with buffer size 1 to avoid goroutine leak
  26. done := make(chan error, 1)
  27. panicChan := make(chan any, 1)
  28. go func() {
  29. defer func() {
  30. if p := recover(); p != nil {
  31. // attach call stack to avoid missing in different goroutine
  32. panicChan <- fmt.Sprintf("%+v\n\n%s", p, strings.TrimSpace(string(debug.Stack())))
  33. }
  34. }()
  35. done <- fn()
  36. }()
  37. select {
  38. case p := <-panicChan:
  39. panic(p)
  40. case err := <-done:
  41. return err
  42. case <-ctx.Done():
  43. return ctx.Err()
  44. }
  45. }
  46. // WithContext customizes a DoWithTimeout call with given ctx.
  47. func WithContext(ctx context.Context) DoOption {
  48. return func() context.Context {
  49. return ctx
  50. }
  51. }