Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions v4/doc.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/*

Package suture provides Erlang-like supervisor trees.

This implements Erlang-esque supervisor trees, as adapted for Go. This
Expand All @@ -11,21 +10,21 @@ trying to fall apart.

Why use Suture?

* You want to write bullet-resistant services that will remain available
despite unforeseen failure.
* You need the code to be smart enough not to consume 100% of the CPU
restarting things.
* You want to easily compose multiple such services in one program.
* You want the Erlang programmers to stop lording their supervision
trees over you.
- You want to write bullet-resistant services that will remain available
despite unforeseen failure.
- You need the code to be smart enough not to consume 100% of the CPU
restarting things.
- You want to easily compose multiple such services in one program.
- You want the Erlang programmers to stop lording their supervision
trees over you.

Suture has 100% test coverage, and is golint clean. This doesn't prove it
free of bugs, but it shows I care.

A blog post describing the design decisions is available at
http://www.jerf.org/iri/post/2930 .

Using Suture
# Using Suture

To idiomatically use Suture, create a Supervisor which is your top level
"application" supervisor. This will often occur in your program's "main"
Expand All @@ -49,6 +48,5 @@ you've defined.

See the Example for an example, using a simple service that serves out
incrementing integers.

*/
package suture
1 change: 1 addition & 0 deletions v4/errors_after_13.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build go1.13
// +build go1.13

package suture
Expand Down
1 change: 1 addition & 0 deletions v4/errors_before_13.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !go1.13
// +build !go1.13

package suture
Expand Down
5 changes: 2 additions & 3 deletions v4/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
/*
Service is the interface that describes a service to a Supervisor.

Serve Method
# Serve Method

The Serve method is called by a Supervisor to start the service.
The service should execute within the goroutine that this is
Expand Down Expand Up @@ -55,7 +55,7 @@ properly stopped in the future. Until the service is fully stopped,
both the service and the spawned goroutine trying to stop it will be
"leaked".

Stringer Interface
# Stringer Interface

When a Service is added to a Supervisor, the Supervisor will create a
string representation of that service used for logging.
Expand All @@ -64,7 +64,6 @@ If you implement the fmt.Stringer interface, that will be used.

If you do not implement the fmt.Stringer interface, a default
fmt.Sprintf("%#v") will be used.

*/
type Service interface {
Serve(ctx context.Context) error
Expand Down
104 changes: 34 additions & 70 deletions v4/supervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,10 @@ program will be to call one of the Serve methods.
Calling ServeBackground will CORRECTLY start the supervisor running in a
new goroutine. It is risky to directly run

go supervisor.Serve()
go supervisor.Serve()

because that will briefly create a race condition as it starts up, if you
try to .Add() services immediately afterward.

*/
type Supervisor struct {
Name string
Expand Down Expand Up @@ -97,29 +96,28 @@ type Supervisor struct {
}

/*

New is the full constructor function for a supervisor.

The name is a friendly human name for the supervisor, used in logging. Suture
does not care if this is unique, but it is good for your sanity if it is.

If not set, the following values are used:

* EventHook: A function is created that uses log.Print.
* FailureDecay: 30 seconds
* FailureThreshold: 5 failures
* FailureBackoff: 15 seconds
* Timeout: 10 seconds
* BackoffJitter: DefaultJitter
- EventHook: A function is created that uses log.Print.
- FailureDecay: 30 seconds
- FailureThreshold: 5 failures
- FailureBackoff: 15 seconds
- Timeout: 10 seconds
- BackoffJitter: DefaultJitter

The EventHook function will be called when errors occur. Suture will log the
following:

* When a service has failed, with a descriptive message about the
current backoff status, and whether it was immediately restarted
* When the supervisor has gone into its backoff mode, and when it
exits it
* When a service fails to stop
- When a service has failed, with a descriptive message about the
current backoff status, and whether it was immediately restarted
- When the supervisor has gone into its backoff mode, and when it
exits it
- When a service fails to stop

The failureRate, failureThreshold, and failureBackoff controls how failures
are handled, in order to avoid the supervisor failure case where the
Expand All @@ -145,63 +143,32 @@ DontPropagateTermination indicates whether this supervisor tree will
propagate a ErrTerminateTree if a child process returns it. If false,
this supervisor will itself return an error that will terminate its
parent. If true, it will merely return ErrDoNotRestart. false by default.

*/
func New(name string, spec Spec) *Supervisor {
spec.configureDefaults(name)

return &Supervisor{
name,

spec,

// services
make(map[serviceID]serviceWithName),
// cancellations
make(map[serviceID]context.CancelFunc),
// servicesShuttingDown
make(map[serviceID]serviceWithName),
// lastFail, deliberately the zero time
time.Time{},
// failures
0,
// restartQueue
make([]serviceID, 0, 1),
// serviceCounter
0,
// control
make(chan supervisorMessage),
// notifyServiceDone
make(chan serviceID),
// resumeTimer
make(chan time.Time),

// liveness
make(chan struct{}),

sync.Mutex{},
// ctx
nil,
// myCancel
nil,

// the tests can override these for testing threshold
// behavior
// getNow
time.Now,
// getAfterChan
time.After,

// m
sync.Mutex{},

// unstoppedServiceReport
nil,

// id
nextSupervisorID(),
// state
notRunning,
Name: name,

spec: spec,

services: make(map[serviceID]serviceWithName),
cancellations: make(map[serviceID]context.CancelFunc),
servicesShuttingDown: make(map[serviceID]serviceWithName),
lastFail: time.Time{}, // deliberately the zero time
restartQueue: make([]serviceID, 0, 1),
control: make(chan supervisorMessage),
notifyServiceDone: make(chan serviceID),
resumeTimer: make(chan time.Time),

liveness: make(chan struct{}),

// the tests can override these for testing threshold behavior
getNow: time.Now,
getAfterChan: time.After,

id: nextSupervisorID(),
state: notRunning,
}
}

Expand Down Expand Up @@ -254,7 +221,6 @@ supervisor being added will copy the EventHook function from the Supervisor it
is being added to. This allows factoring out providing a Supervisor
from its logging. This unconditionally overwrites the child Supervisor's
logging functions.

*/
func (s *Supervisor) Add(service Service) ServiceToken {
if s == nil {
Expand Down Expand Up @@ -315,7 +281,7 @@ func (s *Supervisor) Serve(ctx context.Context) error {
panic("Can't serve with a nil *suture.Supervisor")
}
// Take a separate cancellation function so this tree can be
// indepedently cancelled.
// independently cancelled.
ctx, myCancel := context.WithCancel(ctx)
s.ctxMutex.Lock()
s.ctx = ctx
Expand Down Expand Up @@ -756,10 +722,8 @@ func (s *Supervisor) RemoveAndWait(id ServiceToken, timeout time.Duration) error
}

/*

Services returns a []Service containing a snapshot of the services this
Supervisor is managing.

*/
func (s *Supervisor) Services() []Service {
ls := listServices{make(chan []Service)}
Expand Down