gopkg.in/tomb.v2

1,884 views
Skip to first unread message

Gustavo Niemeyer

unread,
Jun 25, 2014, 7:19:40 PM6/25/14
to golan...@googlegroups.com
The tomb package received an API update, bumping it to v2:

http://gopkg.in/tomb.v2

It improves the v1 API (which is still available) in the following ways:

- The most common case is now even simpler

- The same API now supports tracking the termination of multiple goroutines

- Better integrates the traditional error return convention

For more details, please see the package documentation at:

http://godoc.org/gopkg.in/tomb.v2

and the introductory blog post, which was tweaked for the new API:

http://blog.labix.org/2011/10/09/death-of-goroutines-under-control


gustavo @ http://niemeyer.net

Lubos Pintes

unread,
Jun 26, 2014, 3:54:40 AM6/26/14
to golan...@googlegroups.com
You have a typo in the introductory blog post:
line, err := lr.r.ReadSlice('n')
There is missing backslash I think.

Gustavo Niemeyer

unread,
Jun 26, 2014, 9:03:49 AM6/26/14
to Lubos Pintes, golan...@googlegroups.com
On Thu, Jun 26, 2014 at 4:54 AM, Lubos Pintes <lubos....@gmail.com> wrote:
> You have a typo in the introductory blog post:
> line, err := lr.r.ReadSlice('n')
> There is missing backslash I think.

Fixed, thanks.


gustavo @ http://niemeyer.net

Nicolas Hillegeer

unread,
Jun 26, 2014, 6:40:08 PM6/26/14
to golan...@googlegroups.com, lubos....@gmail.com
Already migrated from v1 (I hope in a good way, my tomb spawns 2 goroutines, a sender and a receiver, waits for 2 values on the error chan, and returns with the first non-nil error).

Thanks, Gustavo!

Gustavo Niemeyer

unread,
Jun 26, 2014, 9:48:08 PM6/26/14
to Nicolas Hillegeer, golan...@googlegroups.com, Lubos Pintes
On Thu, Jun 26, 2014 at 7:40 PM, Nicolas Hillegeer
<nicolash...@gmail.com> wrote:
> Already migrated from v1 (I hope in a good way, my tomb spawns 2 goroutines,
> a sender and a receiver, waits for 2 values on the error chan, and returns
> with the first non-nil error).

Yeah, that's the typical case. It may just call tomb.Go for the two
goroutines, and return the error per the standard convention. To wait
for both goroutines to terminate, call tomb.Wait, and it will wait for
both and return the first non-nil error.


gustavo @ http://niemeyer.net

Nicolas Hillegeer

unread,
Jun 27, 2014, 5:15:55 AM6/27/14
to golan...@googlegroups.com, nicolash...@gmail.com, lubos....@gmail.com
I actually subconsciously skipped over that feature (recursive Go) I think.

Now I have (pseudo-code):

c.Go(c.pump)
c.Wait() // wait for finish

func (c *client) pump() error {
  errc := make(chan error)
  go sender(errc)
  go receiver(errc)

  for i := 0; i < 2; i++ {
    err = <-errc // I actually only remember the first one
  }

  return err
}

But you're saying it might be more idiomatic (for the package) to do something like:

c.Go(c.pump)
c.Wait() // wait for finish

func (c *client) pump() error {
  c.Go(c.sender)
  c.Go(c.receiver)

  return c.Wait() // wait for sub-finish (does this even work?)
}

Does tomb handle that well? Or do I have to create new tombs for the sender/receiver? Does tomb take care of recursively killing?

Nicolas Hillegeer

unread,
Jun 27, 2014, 5:49:09 AM6/27/14
to golan...@googlegroups.com, nicolash...@gmail.com, lubos....@gmail.com
After studying the tomb source, I see I had it wrong, you shouldn't call wait recursively. I should do:

c.pump
c.Wait() // wait for finish

func (c *client) pump() error {
  c.Go(c.sender)
  c.Go(c.receiver)

  return nil
}

Instead of wait()'ing twice. Sorry for spamming. And thanks for the package, it does look a lot cleaner now (I really like the idiomatic error return).


Am Freitag, 27. Juni 2014 03:48:08 UTC+2 schrieb Gustavo Niemeyer:

roger peppe

unread,
Jun 27, 2014, 6:28:15 AM6/27/14
to Nicolas Hillegeer, golang-nuts, lubos....@gmail.com
On 27 June 2014 10:49, Nicolas Hillegeer <nicolash...@gmail.com> wrote:
> After studying the tomb source, I see I had it wrong, you shouldn't call
> wait recursively. I should do:
>
> c.pump
> c.Wait() // wait for finish
>
> func (c *client) pump() error {
> c.Go(c.sender)
> c.Go(c.receiver)
>
> return nil
> }
>
> Instead of wait()'ing twice. Sorry for spamming. And thanks for the package,
> it does look a lot cleaner now (I really like the idiomatic error return).

That's not quite right. You should do:

c.Go(c.pump)
c.Wait()

because otherwise there is a risk that sender will finish before
receiver has been started and thus get a runtime panic because
the tomb is dead.

As the documentation says: "... calling the Go method a second time out
of a tracked goroutine is unsafe.

Nicolas Hillegeer

unread,
Jun 27, 2014, 6:33:00 AM6/27/14
to golan...@googlegroups.com, nicolash...@gmail.com, lubos....@gmail.com
Right, the docs do say that indeed. I was too happy to remove goroutines I thought I didn't need. Thanks!

dbra...@proletariat.com

unread,
Jan 4, 2016, 5:51:04 PM1/4/16
to golang-nuts
If I want to wait for the first error, should I just block on <-Dying()? Or select on both <-Dying() and <-Dead()? It looks like the current v2 code always closes dying before closing dead, but I wasn't sure if that was something I should rely on or not.

For example, should I do this:

  var t tomb.Tomb

  t
.Go(foo.ProcessMessages)
  t
.Go(bar.ProcessMessages)

 
<-t.Dying()

 
return t.Err()

Or this:

  var t tomb.Tomb

  t
.Go(foo.ProcessMessages)
  t
.Go(bar.ProcessMessages)

 
select {
 
case <-t.Dying():
 
case <-t.Dead():
 
}

 
return t.Err()


Gustavo Niemeyer

unread,
Jan 5, 2016, 7:10:31 AM1/5/16
to dbra...@proletariat.com, golang-nuts
Hi there,

You can rely on that behavior, and choose which channel to use depending on the desired semantics.

Dying closes as soon as the tomb has been flagged for termination, so there might still be goroutines active at that point. Dead is closed when all known goroutines willingly terminated. 

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

herfray...@gmail.com

unread,
Jan 28, 2016, 8:23:48 PM1/28/16
to golang-nuts
Hey ! Thanks a lot for your amazing package.

Right now I'm encountering a problem with it.

I have a couple of processing functions which returns their result through channels.
I have also a couple of other functions that use those results and wait them on the channels.
Basically, everywhere, I have:

select {
case res := <-resChan:
if err := otherFunction(res); err != nil {
return err
}
case <-i.t.Dying():
return nil
}

The problem is that in my code, that block is called in parallel multiple times.
So the first call is fine but the second one never get the result the the "resChan" again.

The way to solve this would be to use real "future" objects like in https://github.com/Workiva/go-datastructures instead of channels.

The problem is that in that package, the futures are blocking functions that dies when the action is done or when there is a timeout.
And I can't do a "select" on a function so I can't check for the Tomb dying state.

The real way to solve this would be to add futures directly to the Tomb package that doesn't listen on timeouts but on the dying state of Tomb.

The new block would be:

res, err := future.GetResult()
if err != nil {
return err
}

if err := otherFunction(res.(resType)); err != nil {
return err
}

That would be so powerful and amazing :)

What do you think ?

Gustavo Niemeyer

unread,
Jan 29, 2016, 2:41:03 PM1/29/16
to herfray...@gmail.com, golang-nuts

Hi there,

The problem is not really related to tomb, but to concurrency in general.

If you want a value to be read any number of times by an arbitrary number of readers, then:

1. Set the value before starting concurrency, and never change it. You don't need concurrency control for that.

2. Use sync.Once.

3. Have a trivial method v.thing() that returns the last set value, and only set that value once. Both with proper concurrency control.

4. Have a goroutine spinning over and over sending the same value, until the tomb reports death. Feels like concurrency for concurrency sake, in my view.

5. Be creative in other ways.. :)


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

herfray...@gmail.com

unread,
Feb 8, 2016, 1:29:19 PM2/8/16
to golang-nuts, herfray...@gmail.com
Thanks for your answer.

Your third point is exactly what I'm talking about.
That's what are futures.

The idea would be to provide a method to get a future object that listen on a result channel and on the tomb dying state.
It would directly reduce verbosity and not bother users that don't care.

An other thing, do you have any plans about adding net/context oriented methods ?
A simple method that takes a context, a tomb object and returns a context that is canceled when tomb dies could be useful I guess.
Or the other way around, a method that returns a tomb object that dies if the context is cancelled.

Gustavo Niemeyer

unread,
Feb 8, 2016, 9:15:29 PM2/8/16
to Fabien Herfray, golang-nuts
On Mon, Feb 8, 2016 at 4:28 PM, <herfray...@gmail.com> wrote:
Thanks for your answer.

Your third point is exactly what I'm talking about.
That's what are futures.

Sure, but I prefer to call it simply a value with a mutex. "Future" tends to come with a baggage of async development practices whose end game isn't pretty.

The idea would be to provide a method to get a future object that listen on a result channel and on the tomb dying state.
It would directly reduce verbosity and not bother users that don't care.

An other thing, do you have any plans about adding net/context oriented methods ?
A simple method that takes a context, a tomb object and returns a context that is canceled when tomb dies could be useful I guess.
Or the other way around, a method that returns a tomb object that dies if the context is cancelled.

If context goes into the standard library and becomes more widely used, might be worth some sort of integration helper. But it doesn't feel like a requirement.. they're both solving slightly orthogonal issues, and can easily be plugged together when desired.


Reply all
Reply to author
Forward
0 new messages