Skip to content

Commit

Permalink
Catacomb context support
Browse files Browse the repository at this point in the history
Adds context support to the catacomb type. In reality this just calls
the tomb context method. By exposing the context method we can then tie
a context to the lifecycle of a catacomb.

The only difference between a tomb and a catacomb in relation to a
context is that a catacomb doesn't expose the concept of a parent
context. The tomb type allows you to create a tomb from a context
(WithContext), which will be the root context for the tomb. Instead a
catacomb will currently fallback to a context.Background. We could
provide a WithContext for a catacomb, I'm unsure if that functionality
is required right now?

---

The tests were lifted and modified from the tomb test package, which
ensures that we don't deviate from the tomb package in the future.
  • Loading branch information
SimonRichardson committed Mar 30, 2023
1 parent 7f64a55 commit 03ee057
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 4 deletions.
19 changes: 15 additions & 4 deletions catacomb/catacomb.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package catacomb

import (
"context"
"fmt"
"runtime/debug"
"strings"
Expand Down Expand Up @@ -211,12 +212,22 @@ func (catacomb *Catacomb) Err() error {
return catacomb.tomb.Err()
}

// Context returns a context that is a copy of the provided parent context with
// a replaced Done channel that is closed when either the catacomb is dying or
// the parent is cancelled.
//
// If parent is nil, it defaults to the empty background context.
func (catacomb *Catacomb) Context(parent context.Context) context.Context {
return catacomb.tomb.Context(parent)
}

// Kill kills the Catacomb's internal tomb with the supplied error, or one
// derived from it.
// * if it's caused by this catacomb's ErrDying, it passes on tomb.ErrDying.
// * if it's tomb.ErrDying, or caused by another catacomb's ErrDying, it passes
// on a new error complaining about the misuse.
// * all other errors are passed on unmodified.
// - if it's caused by this catacomb's ErrDying, it passes on tomb.ErrDying.
// - if it's tomb.ErrDying, or caused by another catacomb's ErrDying, it passes
// on a new error complaining about the misuse.
// - all other errors are passed on unmodified.
//
// It's always safe to call Kill, but errors passed to Kill after the catacomb
// is dead will be ignored.
func (catacomb *Catacomb) Kill(err error) {
Expand Down
78 changes: 78 additions & 0 deletions catacomb/catacomb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package catacomb_test

import (
"context"
"sync"

"github.com/juju/errors"
Expand Down Expand Up @@ -481,3 +482,80 @@ func checkInvalid(c *gc.C, plan catacomb.Plan, match string) {
check(plan.Validate())
check(catacomb.Invoke(plan))
}

type CatacombContextSuite struct {
testing.IsolationSuite
}

var _ = gc.Suite(&CatacombContextSuite{})

func (CatacombContextSuite) TestContextNoParent(c *gc.C) {
var site catacomb.Catacomb
err := catacomb.Invoke(catacomb.Plan{
Site: &site,
Work: func() error { return nil },
})
c.Check(err, jc.ErrorIsNil)

parent2, cancel2 := context.WithCancel(context.WithValue(context.Background(), "parent", "parent2"))
child2 := site.Context(parent2)

if site.Context(parent2) != child2 {
c.Fatalf("Context returned different context for same parent")
}
if child2.Value("parent") != "parent2" {
c.Fatalf("Child context didn't inherit its parent's properties")
}
select {
case <-child2.Done():
c.Fatalf("Tomb's child context was born dead")
default:
}

cancel2()

select {
case <-child2.Done():
default:
c.Fatalf("Tomb's child context didn't die after parent was canceled")
}

parent3 := context.WithValue(context.Background(), "parent", "parent3")
child3 := site.Context(parent3)

if child3.Value("parent") != "parent3" {
c.Fatalf("Child context didn't inherit its parent's properties")
}
select {
case <-child3.Done():
c.Fatalf("Tomb's child context was born dead")
default:
}

site.Kill(nil)

if site.Context(parent3) == child3 {
c.Fatalf("Tomb is dead and shouldn't be tracking children anymore")
}
select {
case <-child3.Done():
default:
c.Fatalf("Child context didn't die after tomb's death")
}

parent4 := context.WithValue(context.Background(), "parent", "parent4")
child4 := site.Context(parent4)

select {
case <-child4.Done():
default:
c.Fatalf("Child context should be born canceled")
}

childnil := site.Context(nil)
select {
case <-childnil.Done():
default:
c.Fatalf("Child context should be born canceled")
}
}

0 comments on commit 03ee057

Please sign in to comment.