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