tomb.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. // Copyright (c) 2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
  2. //
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are met:
  7. //
  8. // * Redistributions of source code must retain the above copyright notice,
  9. // this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above copyright notice,
  11. // this list of conditions and the following disclaimer in the documentation
  12. // and/or other materials provided with the distribution.
  13. // * Neither the name of the copyright holder nor the names of its
  14. // contributors may be used to endorse or promote products derived from
  15. // this software without specific prior written permission.
  16. //
  17. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  18. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  20. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  21. // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  22. // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  23. // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  24. // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  25. // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  26. // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. // The tomb package offers a conventional API for clean goroutine termination.
  29. //
  30. // A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
  31. // and the reason for its death.
  32. //
  33. // The zero value of a Tomb assumes that a goroutine is about to be
  34. // created or already alive. Once Kill or Killf is called with an
  35. // argument that informs the reason for death, the goroutine is in
  36. // a dying state and is expected to terminate soon. Right before the
  37. // goroutine function or method returns, Done must be called to inform
  38. // that the goroutine is indeed dead and about to stop running.
  39. //
  40. // A Tomb exposes Dying and Dead channels. These channels are closed
  41. // when the Tomb state changes in the respective way. They enable
  42. // explicit blocking until the state changes, and also to selectively
  43. // unblock select statements accordingly.
  44. //
  45. // When the tomb state changes to dying and there's still logic going
  46. // on within the goroutine, nested functions and methods may choose to
  47. // return ErrDying as their error value, as this error won't alter the
  48. // tomb state if provided to the Kill method. This is a convenient way to
  49. // follow standard Go practices in the context of a dying tomb.
  50. //
  51. // For background and a detailed example, see the following blog post:
  52. //
  53. // http://blog.labix.org/2011/10/09/death-of-goroutines-under-control
  54. //
  55. // For a more complex code snippet demonstrating the use of multiple
  56. // goroutines with a single Tomb, see:
  57. //
  58. // http://play.golang.org/p/Xh7qWsDPZP
  59. //
  60. package tomb
  61. import (
  62. "errors"
  63. "fmt"
  64. "sync"
  65. )
  66. // A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
  67. // and the reason for its death.
  68. //
  69. // See the package documentation for details.
  70. type Tomb struct {
  71. m sync.Mutex
  72. dying chan struct{}
  73. dead chan struct{}
  74. reason error
  75. }
  76. var (
  77. ErrStillAlive = errors.New("tomb: still alive")
  78. ErrDying = errors.New("tomb: dying")
  79. )
  80. func (t *Tomb) init() {
  81. t.m.Lock()
  82. if t.dead == nil {
  83. t.dead = make(chan struct{})
  84. t.dying = make(chan struct{})
  85. t.reason = ErrStillAlive
  86. }
  87. t.m.Unlock()
  88. }
  89. // Dead returns the channel that can be used to wait
  90. // until t.Done has been called.
  91. func (t *Tomb) Dead() <-chan struct{} {
  92. t.init()
  93. return t.dead
  94. }
  95. // Dying returns the channel that can be used to wait
  96. // until t.Kill or t.Done has been called.
  97. func (t *Tomb) Dying() <-chan struct{} {
  98. t.init()
  99. return t.dying
  100. }
  101. // Wait blocks until the goroutine is in a dead state and returns the
  102. // reason for its death.
  103. func (t *Tomb) Wait() error {
  104. t.init()
  105. <-t.dead
  106. t.m.Lock()
  107. reason := t.reason
  108. t.m.Unlock()
  109. return reason
  110. }
  111. // Done flags the goroutine as dead, and should be called a single time
  112. // right before the goroutine function or method returns.
  113. // If the goroutine was not already in a dying state before Done is
  114. // called, it will be flagged as dying and dead at once with no
  115. // error.
  116. func (t *Tomb) Done() {
  117. t.Kill(nil)
  118. close(t.dead)
  119. }
  120. // Kill flags the goroutine as dying for the given reason.
  121. // Kill may be called multiple times, but only the first
  122. // non-nil error is recorded as the reason for termination.
  123. //
  124. // If reason is ErrDying, the previous reason isn't replaced
  125. // even if it is nil. It's a runtime error to call Kill with
  126. // ErrDying if t is not in a dying state.
  127. func (t *Tomb) Kill(reason error) {
  128. t.init()
  129. t.m.Lock()
  130. defer t.m.Unlock()
  131. if reason == ErrDying {
  132. if t.reason == ErrStillAlive {
  133. panic("tomb: Kill with ErrDying while still alive")
  134. }
  135. return
  136. }
  137. if t.reason == nil || t.reason == ErrStillAlive {
  138. t.reason = reason
  139. }
  140. // If the receive on t.dying succeeds, then
  141. // it can only be because we have already closed it.
  142. // If it blocks, then we know that it needs to be closed.
  143. select {
  144. case <-t.dying:
  145. default:
  146. close(t.dying)
  147. }
  148. }
  149. // Killf works like Kill, but builds the reason providing the received
  150. // arguments to fmt.Errorf. The generated error is also returned.
  151. func (t *Tomb) Killf(f string, a ...interface{}) error {
  152. err := fmt.Errorf(f, a...)
  153. t.Kill(err)
  154. return err
  155. }
  156. // Err returns the reason for the goroutine death provided via Kill
  157. // or Killf, or ErrStillAlive when the goroutine is still alive.
  158. func (t *Tomb) Err() (reason error) {
  159. t.init()
  160. t.m.Lock()
  161. reason = t.reason
  162. t.m.Unlock()
  163. return
  164. }