idem

package module
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Jan 29, 2025 License: MIT Imports: 3 Imported by: 13

README

idem.Halter

idem.Halter supports a common pattern for halting goroutines in Go.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrAlreadyClosed = fmt.Errorf("Chan already closed")
View Source
var ErrGiveUp = fmt.Errorf("giveup channel was closed.")

ErrGiveUp is returned by IdemCloseChan.WaitTilDone if the giveup channel close was the reason that WaitTilDone returned.

View Source
var ErrNotClosed = fmt.Errorf("tree is not closed")

ErrNotClosed is returned by FirstTreeReason if it is called on a tree that is not completely closed.

Functions

This section is empty.

Types

type Halter

type Halter struct {
	// The owning goutine should call Done.Close() as its last
	// action once it has received the ReqStop() signal.
	Done *IdemCloseChan

	// Other goroutines call ReqStop.Close() in order
	// to request that the owning goroutine stop immediately.
	// The owning goroutine should select on ReqStop.Chan
	// in order to recognize shutdown requests.
	ReqStop *IdemCloseChan
	// contains filtered or unexported fields
}

Halter helps shutdown a goroutine

func NewHalter

func NewHalter() *Halter

func (*Halter) AddChild added in v0.0.5

func (h *Halter) AddChild(child *Halter)

AddChild adds child to h's children; and adds child.ReqStop to the children of h.ReqStop.

Note that if h.ReqStop is already closed, then child.ReqStop will also be ClosedWithReason with the same reason as h, if any is available.

func (*Halter) IsDone

func (h *Halter) IsDone() bool

IsDone returns true iff h.Done has been Closed().

func (*Halter) IsStopRequested

func (h *Halter) IsStopRequested() bool

IsStopRequested returns true iff h.ReqStop has been Closed().

func (*Halter) MarkDone

func (h *Halter) MarkDone()

MarkDone closes the h.Done channel if it has not already done so. Safe for multiple goroutine access.

func (*Halter) RemoveChild added in v0.0.5

func (h *Halter) RemoveChild(child *Halter)

RemoveChild removes child from the set of h.children. It does not undo any close operation that AddChild did on addition.

func (*Halter) RequestStop

func (h *Halter) RequestStop()

RequestStop closes the h.ReqStop channel if it has not already done so. Safe for multiple goroutine access.

func (*Halter) StopTreeAndWaitTilDone added in v0.0.5

func (h *Halter) StopTreeAndWaitTilDone(atMost time.Duration, why error)

StopTreeAndWaitTilDone first calls ReqStop.CloseWithReason(why) on h (which will recursively call ReqStop.CloseWithReason(why) on all non-closed children in the ReqStop parallel, tree, efficiently stopping at the first already-closed node). Then it waits up to atMost time for all tree members to Close their Done.Chan. It may return much more quickly than atMost, but will never wait longer. An atMost duration <= 0 will wait indefinitely.

type IdemCloseChan

type IdemCloseChan struct {
	Chan chan struct{}
	// contains filtered or unexported fields
}

IdemCloseChan can have Close() called on it multiple times, and it will only close Chan once.

func NewIdemCloseChan

func NewIdemCloseChan() *IdemCloseChan

NewIdemCloseChan makes a new IdemCloseChan.

func (*IdemCloseChan) AddChild

func (c *IdemCloseChan) AddChild(child *IdemCloseChan)

AddChild adds a child IdemCloseChan that will be closed when its parent is Close()-ed. If c is already closed, we close child immediately.

func (*IdemCloseChan) Close

func (c *IdemCloseChan) Close() error

Close returns ErrAlreadyClosed if it has been called before. It never closes IdemClose.Chan more than once, so it is safe to ignore the returned error value. Close() is safe for concurrent access by multiple goroutines. Close returns nil on the initial call, and ErrAlreadyClosed on any subsequent call.

When we actually close the underlying c.Chan, we also call Close on any children, while holding our mutex. This does not happen if the underlying channel is already closed, so the recursion stops at the depth of the first already-closed node in the tree.

func (*IdemCloseChan) CloseWithReason added in v0.0.7

func (c *IdemCloseChan) CloseWithReason(why error) error

CloseWithReason is a no-op if c is already closed. Otherwise, we record the why and close c. Use Reason() to get the why back. Like Close(), we recursive call CloseWithReason() on any children. Naturally decendents will only set why if they were still open when the recursive CloseWithReason gets to them, and if any child is already closed the recursion stops.

func (*IdemCloseChan) FirstTreeReason added in v0.4.0

func (c *IdemCloseChan) FirstTreeReason() (err error)

FirstTreeReason scans the whole tree root at c for the first non-nil close reason, and returns it. If the tree is not completely closed at any node, it returns ErrNotClosed.

func (*IdemCloseChan) IsClosed

func (c *IdemCloseChan) IsClosed() bool

IsClosed tells you if Chan is already closed or not.

func (*IdemCloseChan) Reason added in v0.0.7

func (c *IdemCloseChan) Reason() (why error, isClosed bool)

Reason gets the why set with CloseWithReason(). It also returns the closed state of c. If c is still open, why will be nil, as why is only set during the initial CloseWithReason if c was still open. The returned why will also be nil if c was closed without a reason, by Close() for example.

Callers should be prepared to handle a nil why no matter what the state of isClosed.

func (*IdemCloseChan) RemoveChild

func (c *IdemCloseChan) RemoveChild(child *IdemCloseChan)

func (*IdemCloseChan) WaitTilChildrenDone added in v0.5.0

func (c *IdemCloseChan) WaitTilChildrenDone(giveup <-chan struct{}) (err error)

WaitTilChildrenDone is just like WaitTilDone, but does not require (or check) if we ourselves, the root of our tree, is closed.

func (*IdemCloseChan) WaitTilDone added in v0.2.1

func (c *IdemCloseChan) WaitTilDone(giveup <-chan struct{}) (err error)

WaitTilDone does not return until either a) we and all our children have closed the Done channel; or b) the supplied giveup channel has been closed.

We recursively call WaitTilDone on all of our children, and then wait for our own Done close.

Note we cannot use a time.After channel for giveup, since that only fires once--we have possibly many children to wait for. Hence giveup must be _closed_, not just sent on, to have them all stop waiting.

There is no protection against racey mutation of our child tree once this operation has started. The user must guarantee that nobody will do AddChild or RemoveChild while we are waiting.

If the giveup channel was closed, the returned error will be ErrGiveUp. Otherwise it will be the first Reason() supplied by any CloseWithReason() calls in the tree. The first non-nil 'why' error we encounter is sticky. If err is returned nil, you know that either CloseWithReason was not used, or that there were only nil reason closes.

Jump to

Keyboard shortcuts

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