133 lines
5.0 KiB
Markdown
133 lines
5.0 KiB
Markdown
|
# goprocess - lifecycles in go
|
||
|
|
||
|
[![travisbadge](https://travis-ci.org/jbenet/goprocess.svg)](https://travis-ci.org/jbenet/goprocess)
|
||
|
|
||
|
(Based on https://github.com/jbenet/go-ctxgroup)
|
||
|
|
||
|
- Godoc: https://godoc.org/github.com/jbenet/goprocess
|
||
|
|
||
|
`goprocess` introduces a way to manage process lifecycles in go. It is
|
||
|
much like [go.net/context](https://godoc.org/code.google.com/p/go.net/context)
|
||
|
(it actually uses a Context), but it is more like a Context-WaitGroup hybrid.
|
||
|
`goprocess` is about being able to start and stop units of work, which may
|
||
|
receive `Close` signals from many clients. Think of it like a UNIX process
|
||
|
tree, but inside go.
|
||
|
|
||
|
`goprocess` seeks to minimally affect your objects, so you can use it
|
||
|
with both embedding or composition. At the heart of `goprocess` is the
|
||
|
`Process` interface:
|
||
|
|
||
|
```Go
|
||
|
// Process is the basic unit of work in goprocess. It defines a computation
|
||
|
// with a lifecycle:
|
||
|
// - running (before calling Close),
|
||
|
// - closing (after calling Close at least once),
|
||
|
// - closed (after Close returns, and all teardown has _completed_).
|
||
|
//
|
||
|
// More specifically, it fits this:
|
||
|
//
|
||
|
// p := WithTeardown(tf) // new process is created, it is now running.
|
||
|
// p.AddChild(q) // can register children **before** Closing.
|
||
|
// go p.Close() // blocks until done running teardown func.
|
||
|
// <-p.Closing() // would now return true.
|
||
|
// <-p.childrenDone() // wait on all children to be done
|
||
|
// p.teardown() // runs the user's teardown function tf.
|
||
|
// p.Close() // now returns, with error teardown returned.
|
||
|
// <-p.Closed() // would now return true.
|
||
|
//
|
||
|
// Processes can be arranged in a process "tree", where children are
|
||
|
// automatically Closed if their parents are closed. (Note, it is actually
|
||
|
// a Process DAG, children may have multiple parents). A process may also
|
||
|
// optionally wait for another to fully Close before beginning to Close.
|
||
|
// This makes it easy to ensure order of operations and proper sequential
|
||
|
// teardown of resurces. For example:
|
||
|
//
|
||
|
// p1 := goprocess.WithTeardown(func() error {
|
||
|
// fmt.Println("closing 1")
|
||
|
// })
|
||
|
// p2 := goprocess.WithTeardown(func() error {
|
||
|
// fmt.Println("closing 2")
|
||
|
// })
|
||
|
// p3 := goprocess.WithTeardown(func() error {
|
||
|
// fmt.Println("closing 3")
|
||
|
// })
|
||
|
//
|
||
|
// p1.AddChild(p2)
|
||
|
// p2.AddChild(p3)
|
||
|
//
|
||
|
//
|
||
|
// go p1.Close()
|
||
|
// go p2.Close()
|
||
|
// go p3.Close()
|
||
|
//
|
||
|
// // Output:
|
||
|
// // closing 3
|
||
|
// // closing 2
|
||
|
// // closing 1
|
||
|
//
|
||
|
// Process is modelled after the UNIX processes group idea, and heavily
|
||
|
// informed by sync.WaitGroup and go.net/context.Context.
|
||
|
//
|
||
|
// In the function documentation of this interface, `p` always refers to
|
||
|
// the self Process.
|
||
|
type Process interface {
|
||
|
|
||
|
// WaitFor makes p wait for q before exiting. Thus, p will _always_ close
|
||
|
// _after_ q. Note well: a waiting cycle is deadlock.
|
||
|
//
|
||
|
// If q is already Closed, WaitFor calls p.Close()
|
||
|
// If p is already Closing or Closed, WaitFor panics. This is the same thing
|
||
|
// as calling Add(1) _after_ calling Done() on a wait group. Calling WaitFor
|
||
|
// on an already-closed process is a programming error likely due to bad
|
||
|
// synchronization
|
||
|
WaitFor(q Process)
|
||
|
|
||
|
// AddChildNoWait registers child as a "child" of Process. As in UNIX,
|
||
|
// when parent is Closed, child is Closed -- child may Close beforehand.
|
||
|
// This is the equivalent of calling:
|
||
|
//
|
||
|
// go func(parent, child Process) {
|
||
|
// <-parent.Closing()
|
||
|
// child.Close()
|
||
|
// }(p, q)
|
||
|
//
|
||
|
// Note: the naming of functions is `AddChildNoWait` and `AddChild` (instead
|
||
|
// of `AddChild` and `AddChildWaitFor`) because:
|
||
|
// - it is the more common operation,
|
||
|
// - explicitness is helpful in the less common case (no waiting), and
|
||
|
// - usual "child" semantics imply parent Processes should wait for children.
|
||
|
AddChildNoWait(q Process)
|
||
|
|
||
|
// AddChild is the equivalent of calling:
|
||
|
// parent.AddChildNoWait(q)
|
||
|
// parent.WaitFor(q)
|
||
|
AddChild(q Process)
|
||
|
|
||
|
// Go creates a new process, adds it as a child, and spawns the ProcessFunc f
|
||
|
// in its own goroutine. It is equivalent to:
|
||
|
//
|
||
|
// GoChild(p, f)
|
||
|
//
|
||
|
// It is useful to construct simple asynchronous workers, children of p.
|
||
|
Go(f ProcessFunc) Process
|
||
|
|
||
|
// Close ends the process. Close blocks until the process has completely
|
||
|
// shut down, and any teardown has run _exactly once_. The returned error
|
||
|
// is available indefinitely: calling Close twice returns the same error.
|
||
|
// If the process has already been closed, Close returns immediately.
|
||
|
Close() error
|
||
|
|
||
|
// Closing is a signal to wait upon. The returned channel is closed
|
||
|
// _after_ Close has been called at least once, but teardown may or may
|
||
|
// not be done yet. The primary use case of Closing is for children who
|
||
|
// need to know when a parent is shutting down, and therefore also shut
|
||
|
// down.
|
||
|
Closing() <-chan struct{}
|
||
|
|
||
|
// Closed is a signal to wait upon. The returned channel is closed
|
||
|
// _after_ Close has completed; teardown has finished. The primary use case
|
||
|
// of Closed is waiting for a Process to Close without _causing_ the Close.
|
||
|
Closed() <-chan struct{}
|
||
|
}
|
||
|
```
|