emsLogCallSlot.go 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. package jobs
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "strings"
  7. "time"
  8. "fwh-toolbox-server/cache"
  9. "go.uber.org/zap"
  10. )
  11. const (
  12. emsLogCallSlotTTL = 2 * time.Minute
  13. emsLogCallSlotRetry = time.Second
  14. EmsLogCallTimeout = 30 * time.Minute
  15. )
  16. var ErrEmsLogCallTimeout = errors.New("EMS 日志单文件召唤超过 30 分钟")
  17. var ErrEmsLogCallSlotLost = errors.New("EMS 日志召唤名额已丢失")
  18. // EmsLogCallSlot serializes EMS calls for one SN across both download flows and instances.
  19. type EmsLogCallSlot struct {
  20. lock *cache.RedisLock
  21. ctx context.Context
  22. cancel context.CancelFunc
  23. done chan struct{}
  24. }
  25. func TryAcquireEmsLogCallSlot(ctx context.Context, sn string) (*EmsLogCallSlot, bool, error) {
  26. keySN := strings.ToUpper(strings.TrimSpace(sn))
  27. if keySN == "" {
  28. return nil, false, fmt.Errorf("EMS log SN is empty")
  29. }
  30. lock, ok, err := cache.TryLock(ctx, "ems-log:call:"+keySN, emsLogCallSlotTTL)
  31. if err != nil || !ok {
  32. return nil, ok, err
  33. }
  34. leaseCtx, cancel := context.WithCancel(ctx)
  35. slot := &EmsLogCallSlot{lock: lock, ctx: leaseCtx, cancel: cancel, done: make(chan struct{})}
  36. go slot.renew()
  37. return slot, true, nil
  38. }
  39. func AcquireEmsLogCallSlot(ctx context.Context, sn string) (*EmsLogCallSlot, error) {
  40. for {
  41. slot, ok, err := TryAcquireEmsLogCallSlot(ctx, sn)
  42. if err != nil || ok {
  43. return slot, err
  44. }
  45. select {
  46. case <-ctx.Done():
  47. return nil, ctx.Err()
  48. case <-time.After(emsLogCallSlotRetry):
  49. }
  50. }
  51. }
  52. func (s *EmsLogCallSlot) Context() context.Context { return s.ctx }
  53. func (s *EmsLogCallSlot) Release() {
  54. s.cancel()
  55. <-s.done
  56. s.lock.SafeRelease(context.Background())
  57. }
  58. func (s *EmsLogCallSlot) renew() {
  59. defer close(s.done)
  60. ticker := time.NewTicker(emsLogCallSlotTTL / 3)
  61. defer ticker.Stop()
  62. for {
  63. select {
  64. case <-s.ctx.Done():
  65. return
  66. case <-ticker.C:
  67. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  68. err := s.lock.Extend(ctx, emsLogCallSlotTTL)
  69. cancel()
  70. if err != nil {
  71. zap.L().Error("EMS 日志召唤名额续期失败", zap.Error(err))
  72. s.cancel()
  73. return
  74. }
  75. }
  76. }
  77. }