loop

package
v4.24.2+incompatible Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2017 License: BSD-3-Clause Imports: 6 Imported by: 4

Documentation

Overview

Package loop of the Tideland Go Library supports the developer implementing the typical Go idiom for concurrent applications running in a loop in the background and doing a select on one or more channels. Stopping those loops or getting aware of internal errors requires extra efforts. The loop package helps to control this kind of goroutines.

Beside the simple controlled loops the also can be made recoverable. In this case a user defined recovery function gets notified if a loop ends with an error or panics. The paseed passed list of recovering information helps to check the reason and frequency.

A third way are sentinels. Those can monitor multiple loops and other sentinels. So hierarchies can be defined. In case of no handler function an error of one monitored instance will lead to a stop of all monitored instances. Otherwise the user can check the error reason inside the handler function and optionally restart the loop or sentinel.

See the example functions for more information.

Index

Examples

Constants

View Source
const (
	ErrLoopPanicked = iota + 1
	ErrHandlingFailed
	ErrRestartNonStopped
	ErrKilledBySentinel
)

Error codes of the loop package.

View Source
const (
	Running = iota
	Stopping
	Stopped
)

Status of the loop.

Variables

This section is empty.

Functions

func IsKilledBySentinelError

func IsKilledBySentinelError(err error) bool

IsKilledBySentinelError allows to check, if a loop or sentinel has been stopped due to internal reasons or after the error of another loop or sentinel.

Types

type Loop

type Loop interface {
	Observable

	// ShallStop returns a channel signalling the loop to
	// stop working.
	ShallStop() <-chan struct{}

	// IsStopping returns a channel that can be used to wait until
	// the loop is stopping or to avoid deadlocks when communicating
	// with the loop.
	IsStopping() <-chan struct{}
}

Loop manages running loops in the background as goroutines.

func Go

func Go(lf LoopFunc, dps ...interface{}) Loop

Go starts the loop function in the background. The loop can be stopped or killed. This leads to a signal out of the channel Loop.ShallStop. The loop then has to end working returning a possible error. Wait then waits until the loop ended and returns the error.

func GoRecoverable

func GoRecoverable(lf LoopFunc, rf RecoverFunc, dps ...interface{}) Loop

GoRecoverable starts the loop function in the background. The loop can be stopped or killed. This leads to a signal out of the channel Loop.ShallStop. The loop then has to end working returning a possible error. Wait then waits until the loop ended and returns the error.

If the loop panics a Recovering is created and passed with all Recoverings before to the RecoverFunc. If it returns nil the loop will be started again. Otherwise the loop will be killed with that error.

type LoopFunc

type LoopFunc func(l Loop) error

LoopFunc is managed loop function.

Example

ExampleLoopFunc shows the usage of loop.Go with one loop function and no recovery. The inner loop contains a select listening to the channel returned by ShallStop. Other channels are for the standard communication with the loop.

package main

import (
	"errors"

	"github.com/tideland/golib/loop"
)

func main() {
	printC := make(chan string)
	// Sample loop function.
	loopF := func(l loop.Loop) error {
		for {
			select {
			case <-l.ShallStop():
				// We shall stop.
				return nil
			case str := <-printC:
				if str == "panic" {
					return errors.New("panic")
				}
				println(str)
			}
		}
	}
	l := loop.Go(loopF, "simple loop demo")

	printC <- "Hello"
	printC <- "World"

	if err := l.Stop(); err != nil {
		panic(err)
	}
}
Output:

type NotificationHandlerFunc

type NotificationHandlerFunc func(s Sentinel, o Observable, rs Recoverings) (Recoverings, error)

NotificationHandlerFunc allows a sentinel to react on an observers error notification.

type Observable

type Observable interface {
	fmt.Stringer

	// Stop tells the observable to stop working and waits until it is done.
	Stop() error

	// Kill kills the observable with the passed error.
	Kill(err error)

	// Wait blocks the caller until the observable ended and returns
	// a possible error.
	Wait() error

	// Restart stops the observable and restarts it afterwards.
	Restart() error

	// Error returns information about the current status and error.
	Error() (status int, err error)
	// contains filtered or unexported methods
}

Observable is a common base interface for those objects that a sentinel can monitor.

type RecoverFunc

type RecoverFunc func(rs Recoverings) (Recoverings, error)

RecoverFunc decides if a loop shall be started again or end with an error. It is also responsible to trim the list of revocerings if needed.

Example

ExampleRecoverFunc demonstrates the usage of a recovery function when using loop.GoRecoverable. Here the frequency of the recoverings (more than five in 10 milliseconds) or the total number is checked. If the total number is not interesting the recoverings could be trimmed by e.g. rs.Trim(5). The fields Time and Reason per recovering allow even more diagnosis.

package main

import (
	"errors"
	"time"

	"github.com/tideland/golib/loop"
)

func main() {
	printC := make(chan string)
	loopF := func(l loop.Loop) error {
		for {
			select {
			case <-l.ShallStop():
				return nil
			case str := <-printC:
				println(str)
			}
		}
	}
	// Recovery function checking frequency and total number.
	recoverF := func(rs loop.Recoverings) (loop.Recoverings, error) {
		if rs.Frequency(5, 10*time.Millisecond) {
			return nil, errors.New("too high error frequency")
		}
		if rs.Len() >= 10 {
			return nil, errors.New("too many errors")
		}
		return rs, nil
	}
	loop.GoRecoverable(loopF, recoverF, "recoverable loop demo")
}
Output:

type Recovering

type Recovering struct {
	Time   time.Time
	Reason interface{}
}

Recovering stores time and reason of one of the recoverings.

type Recoverings

type Recoverings []*Recovering

Recoverings is a list of recoverings a loop already had.

func (Recoverings) First

func (rs Recoverings) First() *Recovering

First returns the first recovering.

func (Recoverings) Frequency

func (rs Recoverings) Frequency(num int, dur time.Duration) bool

Frequency checks if a given number of restarts happened during a given duration.

func (Recoverings) Last

func (rs Recoverings) Last() *Recovering

Last returns the last recovering.

func (Recoverings) Len

func (rs Recoverings) Len() int

Len returns the length of the recoverings.

func (Recoverings) Trim

func (rs Recoverings) Trim(l int) Recoverings

Trim returns the last recoverings defined by l. This way the recover func can con control the length and take care that the list not grows too much.

type Sentinel

type Sentinel interface {
	Observable

	// Observe tells the sentinel to monitor the passed observables.
	Observe(o ...Observable)

	// Forget tells the sentinel to forget the passed observables.
	Forget(o ...Observable)

	// ObservablesDo executes the passed function for each observable,
	// e.g. to react after an error.
	ObservablesDo(f func(o Observable) error) error
}

Sentinel manages a number of loops or other sentinels.

Example

ExampleSentinel demonstrates the monitoring of loops and sentinel with a handler function trying to restart the faulty observable. The nested sentinel has no handler function. An error of a monitored observable would lead to the stop of all observables.

package main

import (
	"errors"
	"time"

	"github.com/tideland/golib/loop"
)

func main() {
	loopF := func(l loop.Loop) error {
		for {
			select {
			case <-l.ShallStop():
				return nil
			}
		}
	}
	handleF := func(s loop.Sentinel, o loop.Observable, rs loop.Recoverings) (loop.Recoverings, error) {
		if rs.Frequency(5, 10*time.Millisecond) {
			return nil, errors.New("too high error frequency")
		}
		return nil, o.Restart()
	}
	loopA := loop.Go(loopF, "loop", "a")
	loopB := loop.Go(loopF, "loop", "b")
	loopC := loop.Go(loopF, "loop", "c")
	loopD := loop.Go(loopF, "loop", "d")
	sentinel := loop.GoNotifiedSentinel(handleF, "sentinel demo")

	sentinel.Observe(loopA, loopB)

	// Hierarchies are possible.
	observedSentinel := loop.GoSentinel("nested sentinel w/o handler")

	sentinel.Observe(observedSentinel)
	observedSentinel.Observe(loopC)
	observedSentinel.Observe(loopD)
}
Output:

func GoNotifiedSentinel

func GoNotifiedSentinel(nhf NotificationHandlerFunc, dps ...interface{}) Sentinel

GoNotifiedSentinel starts a new sentinel with a notification handler function. It can manage loops and other sentinels and restart them in case of errors, based on the notification handler function.

func GoSentinel

func GoSentinel(dps ...interface{}) Sentinel

GoSentinel starts a new sentinel. It can manage loops and other sentinels and will stop them in case of errors.

Jump to

Keyboard shortcuts

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