lockedcalls.go 1.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. package syncx
  2. import "sync"
  3. type (
  4. // LockedCalls makes sure the calls with the same key to be called sequentially.
  5. // For example, A called F, before it's done, B called F, then B's call would not blocked,
  6. // after A's call finished, B's call got executed.
  7. // The calls with the same key are independent, not sharing the returned values.
  8. // A ------->calls F with key and executes<------->returns
  9. // B ------------------>calls F with key<--------->executes<---->returns
  10. LockedCalls interface {
  11. Do(key string, fn func() (interface{}, error)) (interface{}, error)
  12. }
  13. lockedGroup struct {
  14. mu sync.Mutex
  15. m map[string]*sync.WaitGroup
  16. }
  17. )
  18. func NewLockedCalls() LockedCalls {
  19. return &lockedGroup{
  20. m: make(map[string]*sync.WaitGroup),
  21. }
  22. }
  23. func (lg *lockedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
  24. begin:
  25. lg.mu.Lock()
  26. if wg, ok := lg.m[key]; ok {
  27. lg.mu.Unlock()
  28. wg.Wait()
  29. goto begin
  30. }
  31. return lg.makeCall(key, fn)
  32. }
  33. func (lg *lockedGroup) makeCall(key string, fn func() (interface{}, error)) (interface{}, error) {
  34. var wg sync.WaitGroup
  35. wg.Add(1)
  36. lg.m[key] = &wg
  37. lg.mu.Unlock()
  38. defer func() {
  39. // delete key first, done later. can't reverse the order, because if reverse,
  40. // another Do call might wg.Wait() without get notified with wg.Done()
  41. lg.mu.Lock()
  42. delete(lg.m, key)
  43. lg.mu.Unlock()
  44. wg.Done()
  45. }()
  46. return fn()
  47. }