watchdog.go 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package syncs
  4. import (
  5. "context"
  6. "sync"
  7. "time"
  8. )
  9. // Watch monitors mu for contention.
  10. // On first call, and at every tick, Watch locks and unlocks mu.
  11. // (Tick should be large to avoid adding contention to mu.)
  12. // Max is the maximum length of time Watch will wait to acquire the lock.
  13. // The time required to lock mu is sent on the returned channel.
  14. // Watch exits when ctx is done, and closes the returned channel.
  15. func Watch(ctx context.Context, mu sync.Locker, tick, max time.Duration) chan time.Duration {
  16. // Set up the return channel.
  17. c := make(chan time.Duration)
  18. var (
  19. closemu sync.Mutex
  20. closed bool
  21. )
  22. sendc := func(d time.Duration) {
  23. closemu.Lock()
  24. defer closemu.Unlock()
  25. if closed {
  26. // Drop values written after c is closed.
  27. return
  28. }
  29. select {
  30. case c <- d:
  31. case <-ctx.Done():
  32. }
  33. }
  34. closec := func() {
  35. closemu.Lock()
  36. defer closemu.Unlock()
  37. close(c)
  38. closed = true
  39. }
  40. // check locks the mutex and writes how long it took to c.
  41. // check returns ~immediately.
  42. check := func() {
  43. // Start a race between two goroutines.
  44. // One locks the mutex; the other times out.
  45. // Ensure that only one of the two gets to write its result.
  46. // Since the common case is that locking the mutex is fast,
  47. // let the timeout goroutine exit early when that happens.
  48. var sendonce sync.Once
  49. done := make(chan bool)
  50. go func() {
  51. start := time.Now()
  52. mu.Lock()
  53. mu.Unlock()
  54. elapsed := time.Since(start)
  55. if elapsed > max {
  56. elapsed = max
  57. }
  58. close(done)
  59. sendonce.Do(func() { sendc(elapsed) })
  60. }()
  61. go func() {
  62. select {
  63. case <-time.After(max):
  64. // the other goroutine may not have sent a value
  65. sendonce.Do(func() { sendc(max) })
  66. case <-done:
  67. // the other goroutine sent a value
  68. }
  69. }()
  70. }
  71. // Check once at startup.
  72. // This is mainly to make testing easier.
  73. check()
  74. // Start the watchdog goroutine.
  75. // It checks the mutex every tick, until ctx is done.
  76. go func() {
  77. ticker := time.NewTicker(tick)
  78. for {
  79. select {
  80. case <-ctx.Done():
  81. closec()
  82. ticker.Stop()
  83. return
  84. case <-ticker.C:
  85. check()
  86. }
  87. }
  88. }()
  89. return c
  90. }