cascade

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 7, 2020 License: MIT Imports: 3 Imported by: 0

README

Go Cascade

Build Status codecov Go Report Card GoDoc Release

Cascade is a system for assisting with goroutine lifecycles in a top-down fashion.

This is a tool for organizing complex Golang applications that have routines that are reliant on children that need to be exited/killed in a particular order in order to safely clean up and exit.

A Cascade can be thought of as a tree where one parent can have many children and the children must exit before the parent. Helpers for keeping track of functions/goroutines are included as well as a system for running cleanup actions in a safe order.

For the full documentation, see the godocs.

Basic Usage:

Root and Child Cascades:

Create a root Cascade:

rootCas := cascade.RootCascade()

Create a child from a root Cascade:

child1 := rootCas.ChildCascade()

Create a child from a child Cascade:

child2 := child1.ChildCascade()
Marking a function to be monitored by a Cascade

Note: A function that is marked by a Cascade must un-mark when it completes otherwise the Cascade could get stuck while trying to kill

rootCas := cascade.RootCascade()
go func markedFunc() {
	rootCas.Mark() // This function is now marked!
	// Do things, maybe block until killed?
	rootCas.UnMark() // This function is done, let the mark go!
}()
go func markedFuncWithDefer() {
	rootCas.Mark() // This function is now marked!
	defer rootCas.UnMark() // We'll un-mark when this function is done!
	// Do stuff
	return
}()
Blocking until Killed:

Block execution until a Cascade is Killed:

rootCas := cascade.RootCascade()
go func asyncFunc() {
	rootCas.Mark()
	// Do some setup
	rootCas.Hold() // Hold here until the Kill command is executed
	// Do cleanup
	rootCas.UnMark()
}

Or in a loop:

rootCas := cascade.RootCascade()
go func asyncFunc() {
	rootCas.Mark()
	defer rootCas.UnMark()
	// Do some setup
	for {
		select {
		case <-rootCas.Dying():
			return
		default:
			continue
		}
	}
}
Kill functions:

To Kill the current Cascade and any child Cascades that have been created by it:

child1.Kill()  // This will kill everything managed by child1 and it's child, child2

To Kill everything from root on if you're not on the root Cascade or if you're not sure if it's the root Cascade:

child1.KillAll()  // This will kill everything managed by this Cascade tree: rootCas, child1, and child2
Cancel functions:

To Kill the current Cascade and any child Cascades that have been created by it without performing extra actions:

child1.Cancel()  // This will kill everything managed by child1 and it's child, child2

To Cancel everything from root on if you're not on the root Cascade or if you're not sure if it's the root Cascade:

child1.CancelAll()  // This will kill everything managed by this Cascade tree: rootCas, child1, and child2
Wait until Kill is complete:

Warning: Do not add a wait within a Cascade-managed function as it could cause the exit of the Cascade to be blocked!

rootCas := cascade.RootCascade()
func sendKill() {
	rootCas.Kill()     // Send the kill command
	rootCas.Wait()     // Wait until all managed functions have exited
	rootCas.WaitDone() // Wait until all managed functions have exited AND any other actions have been executed
}
Check if a Cascade has already been Killed
var d bool = rootCas.IsDead()  // true if killed, false if alive
var a bool = rootCas.Alive()   // true if alive, false if killed
Things to do on Kill

A Cascade can run a number of actions before completing the kill command, such as cleanup tasks or logs.

Note: Cancels will bypass any queued actions!

rootCas := cascade.RootCascade()

myAction1 := func() {
	fmt.Println("Action 1!")
}

myAction2 := func() {
	fmt.Println("Action 2!")
}

myAction3 := func() {
	fmt.Println("Action 3!")
}

rootCas.DoOnKill(myAction1)
rootCas.DoOnKill(myAction2)
rootCas.DoFirstOnKill(myAction3)

rootCas.Kill()

Output:

Action 3!
Action 1!
Action 2!

Actions on a Cascade will run after all the of actions of its children have run:

rootCas := cascade.RootCascade()
child1 := rootCas.ChildCascade()

myAction1 := func() {
	fmt.Println("Action 1!")
}

myAction2 := func() {
	fmt.Println("Action 2!")
}

myAction3 := func() {
	fmt.Println("Action 3!")
}

rootCas.DoOnKill(myAction1)
child1.DoOnKill(myAction2)
rootCas.DoFirstOnKill(myAction3)

rootCas.Kill()

Output:

Action 2!
Action 3!
Action 1!
Note on Killing and Garbage Collection

You MUST kill every Cascade you create to ensure that it does not get left in memory. A parent cascade keeps a record of children that will keep the garbage collector from removing them. Killing a child will remove its reference from its parent.

Further Reading

See the godocs for a full list of all available functions!

Documentation

Overview

Package cascade is a system for tracking and controlling a series of goroutines. It allows for the creation of tracked routines, conversion of non-blocking functions into tracked goroutines, and manually tracking goroutines.

Cascade allows tying into various points of a lifecycle including "dying", "dead", and "done" states. Goroutines can be killed or cancelled at any point from any part of the program.

The system allows for a parent-child branching similar to cancelable contexts except that it ensures that routines tracked by a child Cascade fully exit before the routines tracked by the parent. This means that there is a guarantee that a parent routine will not exit before all of its children have cleanly exited and proper cleanup has been done.

You can also set actions to be run at each level at a guaranteed time and order. It is also possible to tie Cascades to Contexts or Contexts to Cascades to move between different systems.

Index

Constants

View Source
const Version string = "1.0.0"

Version is the current version of Cascade.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cascade

type Cascade struct {
	// contains filtered or unexported fields
}

Cascade is the core structure of the cascade package. It contains all of the non-public resources used to maintain all tracked routines.

func RootCascade

func RootCascade() *Cascade

RootCascade creates a new Cascade that is fully-initialized and ready to go. This Cascade also acts as the root cascade which means that it is the highest-up parent.

Note about RootCascade and Errors

When calling `KillAllWithError` or `CancelAllWithError`, the `RootCascade` Cascade is the only one that will receive the passed error.

func WithContext

func WithContext(ctx context.Context) (*Cascade, context.Context)

WithContext links a Context to a new `RootCascade`. When the provided Context is Cancelled, the Cascade will be killed.

The function also returns a child context that will be cancelled when the Cascade is killed or cancelled. (Regardless of the state of the parent Context)

func (*Cascade) Alive

func (c *Cascade) Alive() bool

Alive returns `true` if the Cascade has not been cancelled or killed.

func (*Cascade) Cancel

func (c *Cascade) Cancel()

Cancel will kill the Cascade and any children (just like the `Kill` function) but will not run any set actions.

Note: This function blocks until all children and the specified Cascade have finished exiting.

func (*Cascade) CancelAll

func (c *Cascade) CancelAll()

CancelAll will `Cancel` all Cascades in the whole tree from the `RootCascade` all the way to every child. No actions will be run.

Note: This function blocks until ALL Cascades have been cancelled and finished exiting.

func (*Cascade) CancelAllWithError

func (c *Cascade) CancelAllWithError(err error)

CancelAllWithError will `Cancel` all Cascades in the whole tree from the `RootCascade` all the way to every child. No actions will be run. The provided `error` is set ONLY on the `RootCascade`

Notes:

This function blocks until ALL Cascades have been cancelled and finished exiting.

An error will be returned if an error has already been set on the current Cascade.

func (*Cascade) CancelWithError

func (c *Cascade) CancelWithError(err error) error

CancelWithError will kill the Cascade and any children (just like the `KillWithError` function) but will not run any set actions. The provided error will be set ONLY on the current Cascade.

Notes:

This function blocks until all children and the current Cascade have finished exiting.

An error will be returned if an error has already been set on the current Cascade.

func (*Cascade) ChildCascade

func (c *Cascade) ChildCascade() *Cascade

ChildCascade creates a new Cascade which is a child of the current Cascade.

The child Cascade being killed or cancelled will not kill or cancel the parent.

func (*Cascade) Context

func (c *Cascade) Context(ctx context.Context) context.Context

Context returns a `context.Context` that will be cancelled when the Cascade that it was generated from is killed or cancelled.

If a Context is provided, it will be used as the parent for the new Context. If `nil` is passed, either the Cascade's parent Context (if it exists) or `context.Background()` will be used as the parent.

func (*Cascade) Dead

func (c *Cascade) Dead() <-chan interface{}

Dead provides a channel that will close once the Cascade is considered dead.

This can be used as a signal to indicate when all goroutines have exited. However, actions may not have run yet

func (*Cascade) DoFirstOnKill

func (c *Cascade) DoFirstOnKill(action func())

DoFirstOnKill adds a function to the list of actions that should be performed when the Cascade is killed.

Functions are added in a LIFO order and will be executed in order.

Note: These actions will NOT be run if the Cascade is cancelled instead of killed.

func (*Cascade) DoOnKill

func (c *Cascade) DoOnKill(action func())

DoOnKill adds a function to the list of actions that should be performed when the Cascade is killed.

Functions are added in a FIFO order and will be executed in order.

Note: These actions will NOT be run if the Cascade is cancelled instead of killed.

func (*Cascade) Done

func (c *Cascade) Done() <-chan interface{}

Done provides a channel that will close once the Cascade is completely done.

This can be used as a signal to indicate when all goroutines have exited and all actions have been completed.

func (*Cascade) Dying

func (c *Cascade) Dying() <-chan interface{}

Dying provides a channel that will close once the Cascade is considered dying.

This should be what goroutines use to determine when to exit.

func (*Cascade) Error

func (c *Cascade) Error() error

Error returns the error set by one of the `WithError` functions.

func (*Cascade) Go

func (c *Cascade) Go(f func(*Cascade)) *Cascade

Go wraps a function that takes a Cascade as an argument and runs it as a tracked goroutine.

The returned Cascade is a child of the current Cascade that is tracking the provided function.

The provided function MUST implement an exit condition using the provided Cascade.

Example Function:

func(c *Cascade) {
	// Do Something
	<-c.Dying() // Block until the exit condition
}

func (*Cascade) GoInLoop

func (c *Cascade) GoInLoop(f func()) *Cascade

GoInLoop wraps a function inside a loop and runs it as a tracked goroutine.

The provided function MUST not block, it will continue getting called until the Cascade is killed or cancelled.

The returned Cascade is a child of the current Cascade that is tracking the provided function.

Warning: The only way to exit the function is to kill or cancel the Cascade.

func (*Cascade) GoInLoopWithBool

func (c *Cascade) GoInLoopWithBool(f func() bool) *Cascade

GoInLoopWithBool wraps a function inside a loop and runs it as a tracked goroutine as long as the function returns `true`

The provided function MUST not block, it will continue getting called until the Cascade is killed or cancelled or the provided function returns `false`

The returned Cascade is a child of the current Cascade that is tracking the provided function.

func (*Cascade) Hold

func (c *Cascade) Hold()

Hold blocks until the Cascade is considered dying.

This should be what goroutines use to determine when to exit.

func (*Cascade) IsDead

func (c *Cascade) IsDead() bool

IsDead returns `true` if the Cascade has been cancelled or killed.

func (*Cascade) Kill

func (c *Cascade) Kill()

Kill will kill the Cascade and any children (just like the `Cancel` function) and will run any set actions.

Note: This function blocks until all children and the specified Cascade have finished exiting.

func (*Cascade) KillAll

func (c *Cascade) KillAll()

KillAll will `Kill` all Cascades in the whole tree from the `RootCascade` all the way to every child. All actions will be run.

Note: This function blocks until ALL Cascades have been killed and finished exiting.

func (*Cascade) KillAllWithError

func (c *Cascade) KillAllWithError(err error)

KillAllWithError will `Kill` all Cascades in the whole tree from the `RootCascade` all the way to every child. All actions will be run. The provided `error` is set ONLY on the `RootCascade`

Notes:

This function blocks until ALL Cascades have been killed and finished exiting.

An error will be returned if an error has already been set on the current Cascade.

func (*Cascade) KillWithError

func (c *Cascade) KillWithError(err error) error

KillWithError will kill the Cascade and any children (just like the `CancelWithError` function) and will run any set actions. The provided error will be set ONLY on the current Cascade.

Notes:

This function blocks until all children and the current Cascade have finished exiting.

An error will be returned if an error has already been set on the current Cascade.

func (*Cascade) Mark

func (c *Cascade) Mark()

Mark marks a goroutine as being tracked by a Cascade. It should be used similar to `Add` in `sync.WaitGroup` and called at the beginning of a goroutine.

A marked goroutine MUST have an exit condition set for when the Cascade is in a dying state. Either `<-Dying()` or `Hold()` should be used.

A marked goroutine MUST also `UnMark` once it has exited. This is most easily accomplished by using a `defer`.

Example of a marked goroutine:

c := RootCascade()
go func() {
	c.Mark()          // Mark the goroutine as tracked
	defer c.UnMark()  // UnMark the goroutine once it's done.
	// Do something
	Hold()            // Wait for Cascade kill or cancel.
}()
// Additional Code Not Shown

func (*Cascade) UnMark

func (c *Cascade) UnMark()

UnMark removes the mark from a goroutine being tracked by a Cascade. It should be used similar to `Done` in `sync.WaitGroup` and called whenever a goroutine that has called `Mark` exits.

See the docs for `Mark` for a usage example.

func (*Cascade) Wait

func (c *Cascade) Wait()

Wait until the Cascade is considered dead.

This can be used as a signal to indicate when all goroutines have exited. However, actions may not have run yet

func (*Cascade) WaitDone

func (c *Cascade) WaitDone()

WaitDone blocks until the Cascade is completely done.

This can be used as a signal to indicate when all goroutines have exited and all actions have been completed.

func (*Cascade) WithContext

func (c *Cascade) WithContext(ctx context.Context) (*Cascade, context.Context)

WithContext links a Context to a new child Cascade. When the provided Context is Cancelled, the child Cascade will be killed.

The function also returns a child context that will be cancelled when the Cascade is killed or cancelled. (Regardless of the state of the parent Context)

func (*Cascade) Wrap

func (c *Cascade) Wrap(f func(*Cascade))

Wrap wraps a function that takes a Cascade as an argument and turns it into a tracked function.

This is NOT a goroutine and will block until the provided function exits.

The provided function MUST implement an exit condition using the provided Cascade.

For an example of a suitable function, see the example for the `Go` function.

func (*Cascade) WrapInLoop

func (c *Cascade) WrapInLoop(f func())

WrapInLoop wraps a function inside a loop and runs it as a tracked function.

This is NOT a goroutine and will block until the provided function exits.

The provided function MUST not block, it will continue getting called until the Cascade is killed or cancelled.

Warning: The only way to exit the function is to kill or cancel the Cascade.

func (*Cascade) WrapInLoopWithBool

func (c *Cascade) WrapInLoopWithBool(f func() bool)

WrapInLoopWithBool wraps a function inside a loop and runs it as a tracked function as long as the provided function returns `true`

This is NOT a goroutine and will block until the provided function exits.

The provided function MUST not block, it will continue getting called until the Cascade is killed or cancelled or the provided function returns `false`

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL