/util/fdlimiter.go

http://github.com/petar/GoHTTP · Go · 148 lines · 117 code · 16 blank · 15 comment · 17 complexity · aa2ab8ed43f28634e93a5f642e423f78 MD5 · raw file

  1. // Copyright 2009 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package util
  5. import (
  6. "errors"
  7. "sync"
  8. "time"
  9. )
  10. var ErrTimeout = errors.New("timeout")
  11. // FDLimiter helps keep track of the number of file descriptors in use.
  12. type FDLimiter struct {
  13. limit int
  14. count int
  15. lk sync.Mutex
  16. ch chan int
  17. nfych chan<- int
  18. }
  19. // Init initializes (or resets) an FDLimiter object.
  20. func (fdl *FDLimiter) Init(fdlim int) {
  21. fdl.lk.Lock()
  22. if fdlim <= 0 {
  23. panic("FDLimiter, bad limit")
  24. }
  25. fdl.limit = fdlim
  26. fdl.count = 0
  27. fdl.ch = make(chan int)
  28. fdl.lk.Unlock()
  29. }
  30. // SetNotifyChan instructs the FDLimiter to send the current
  31. // number of utilized file descriptors every time that number changes.
  32. // Calling this method with a nil argument, removes the notify channel.
  33. func (fdl *FDLimiter) SetNotifyChan(c chan<- int) {
  34. fdl.lk.Lock()
  35. fdl.nfych = c
  36. fdl.lk.Unlock()
  37. }
  38. func (fdl *FDLimiter) notify() {
  39. if fdl.nfych != nil {
  40. fdl.nfych <- fdl.count
  41. }
  42. }
  43. func (fdl *FDLimiter) LockCount() int {
  44. fdl.lk.Lock()
  45. defer fdl.lk.Unlock()
  46. return fdl.count
  47. }
  48. func (fdl *FDLimiter) Limit() int { return fdl.limit }
  49. // Lock blocks until it can allocate one fd without violating the limit.
  50. func (fdl *FDLimiter) Lock() {
  51. for {
  52. fdl.lk.Lock()
  53. if fdl.count < fdl.limit {
  54. fdl.count++
  55. fdl.notify()
  56. fdl.lk.Unlock()
  57. return
  58. }
  59. fdl.lk.Unlock()
  60. <-fdl.ch
  61. }
  62. panic("FDLimiter, unreachable")
  63. }
  64. // LockOrTimeout proceeds as Lock, except that it returns an ErrTimeout
  65. // error, if a lock cannot be obtained within ns nanoseconds.
  66. func (fdl *FDLimiter) LockOrTimeout(ns int64) error {
  67. waitsofar := int64(0)
  68. for {
  69. // Try to get an fd
  70. fdl.lk.Lock()
  71. if fdl.count < fdl.limit {
  72. fdl.count++
  73. fdl.notify()
  74. fdl.lk.Unlock()
  75. return nil
  76. }
  77. fdl.lk.Unlock()
  78. // Or, wait for an fd or timeout
  79. if waitsofar >= ns {
  80. return ErrTimeout
  81. }
  82. t0 := time.Now().UnixNano()
  83. alrm := alarmOnce(ns - waitsofar)
  84. select {
  85. case <-alrm:
  86. case <-fdl.ch:
  87. }
  88. waitsofar += time.Now().UnixNano() - t0
  89. }
  90. panic("FDLimiter, unreachable")
  91. }
  92. func (fdl *FDLimiter) LockOrChan(ch <-chan interface{}) (msg interface{}, err error) {
  93. for {
  94. fdl.lk.Lock()
  95. if fdl.count < fdl.limit {
  96. fdl.count++
  97. fdl.notify()
  98. fdl.lk.Unlock()
  99. return nil, nil
  100. }
  101. fdl.lk.Unlock()
  102. select {
  103. case msg = <-ch:
  104. return msg, ErrTimeout
  105. case <-fdl.ch:
  106. }
  107. }
  108. panic("FDLimiter, unreachable")
  109. }
  110. // Call Unlock to indicate that a file descriptor has been released.
  111. func (fdl *FDLimiter) Unlock() {
  112. fdl.lk.Lock()
  113. if fdl.count <= 0 {
  114. panic("FDLimiter")
  115. }
  116. fdl.count--
  117. fdl.notify()
  118. if fdl.count == fdl.limit-1 {
  119. fdl.ch <- 1
  120. }
  121. fdl.lk.Unlock()
  122. }
  123. // alarmOnce sends "1" to the returned chan after ns nanoseconds
  124. func alarmOnce(ns int64) <-chan int {
  125. backchan := make(chan int)
  126. go func() {
  127. time.Sleep(time.Duration(ns))
  128. backchan <- 1
  129. close(backchan)
  130. }()
  131. return backchan
  132. }