Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jcontext #43

Merged
merged 34 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c35567e
WIP
AudriusButkevicius Jul 30, 2020
57668be
Tweaks
AudriusButkevicius Jul 30, 2020
bbb44b0
Tweaks
AudriusButkevicius Jul 30, 2020
135abd2
Tweaks
AudriusButkevicius Jul 30, 2020
b961529
Fix test I broke by misunderstanding what it does, make stop channel …
AudriusButkevicius Jul 30, 2020
14c5a70
Fix for TestCompleteJob maybe? not sure I understand how this test works
AudriusButkevicius Jul 30, 2020
cdd5040
Push through failure error
AudriusButkevicius Jul 30, 2020
11d6e43
Save a context function for cancelling myself without anything else.
thejerf Aug 3, 2020
0701b1e
Refactor setting up the spec in the supervisor to a separate method.
thejerf Aug 3, 2020
d5aae5c
Refactor the creation of Supervisor into something more idiomatic.
thejerf Aug 3, 2020
a705ed2
Remove the liveness channel in favor of the context's Done channel.
thejerf Aug 3, 2020
9a2ae91
WIP.
thejerf Aug 10, 2020
f13bd44
More descriptive error names.
thejerf Aug 10, 2020
f205986
Checkpoint.
thejerf Aug 17, 2020
11faac3
Reset the top-level directory to be v3, and create a v4 directory.
thejerf Aug 22, 2020
26ca500
Add support for Go prior to 1.13.
thejerf Aug 22, 2020
09149dd
Tests compile now.
thejerf Aug 22, 2020
3503006
TestServiceReport passes with a single-level tree.
thejerf Aug 22, 2020
e12eab7
The original test suite passes with the context modifications.
thejerf Aug 22, 2020
7f43f06
Test some aspects of the new behavior.
thejerf Aug 24, 2020
785399c
Don't block on Add when supervisor is done
imsodin Sep 16, 2020
def39e4
Merge pull request #42 from imsodin/jcontext-addDeadlock
thejerf Sep 18, 2020
df9eaba
Test shutting down service & terminating supervisor.
thejerf Sep 18, 2020
79c80ba
Add testing for the DeprecatedService shim.
thejerf Sep 18, 2020
564ade9
Make DoNotPropagate work, and test it.
thejerf Sep 18, 2020
14d95cc
Updates to documentation and naming in response to feedback.
thejerf Oct 8, 2020
1ab1360
v4: Add EventHook instead of various logging closures
imsodin Oct 9, 2020
4c048a7
Merge pull request #44 from imsodin/v4-log
thejerf Oct 12, 2020
31d3842
Various event changes.
thejerf Oct 12, 2020
2ace103
fix regression introduced in #42
imsodin Oct 27, 2020
d3dc493
comment: Err -> ErrTimeout
imsodin Oct 27, 2020
488c628
travis: add go1.15 and timeout to tests
imsodin Oct 27, 2020
22b10da
Merge pull request #46 from imsodin/jcontext-fix
thejerf Oct 27, 2020
d4c2633
Final README updates.
thejerf Oct 27, 2020
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
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ go:
- 1.9
- 1.11
- 1.13
- 1.15
- tip
script: go test -timeout 20s ${gobuild_args} ./...
46 changes: 42 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ Suture

[![Build Status](https://travis-ci.org/thejerf/suture.png?branch=master)](https://travis-ci.org/thejerf/suture)

import "gopkg.in/thejerf/suture.v4"

Suture provides Erlang-ish supervisor trees for Go. "Supervisor trees" ->
"sutree" -> "suture" -> holds your code together when it's trying to die.

This library has hit maturity, and isn't expected to be changed
radically. This can also be imported via gopkg.in/thejerf/suture.v2 .

It is intended to deal gracefully with the real failure cases that can
occur with supervision trees (such as burning all your CPU time endlessly
restarting dead services), while also making no unnecessary demands on the
Expand All @@ -23,6 +22,25 @@ with [godoc](https://godoc.org/github.com/thejerf/suture),
including an example, usage, and everything else you might expect from a
README.md on GitHub. (DRY.)

Special Thanks
--------------

Special thanks to the [Syncthing team](https://syncthing.net/), who have
been fantastic about working with me to push fixes upstream of them.

Major Versions
--------------

v4 is a rewrite to make Suture function
with [contexts](https://golang.org/pkg/context/). If you are using suture
for the first time, I recommend it. It also changes how logging works, to
get a single function from the user that is presented with a defined set of
structs, rather than requiring a number of closures from the consumer.

[suture v3](https://godoc.org/gopkg.in/thejerf/suture.v3) is the latest
version that does not feature contexts. It is still supported and getting
backported fixes as of now.

Code Signing
------------

Expand All @@ -47,8 +65,28 @@ without explicitly depending on the Suture library.
Changelog
---------

suture uses semantic versioning.
suture uses semantic versioning and go modules.

* 4.0:
* Switched the entire API to be context based.
* Switched how logging works to take a single closure that will be
presented with a defined set of structs, rather than a set of closures
for each event.
* Consequently, "Stop" removed from the Service interface. A wrapper for
old-style code is provided.
* Services can now return errors. Errors will be included in the log
message. Two special errors control restarting behavior:
* ErrDoNotRestart indicates the service should not be restarted,
but other services should be unaffected.
* ErrTerminateTree indicates the parent service tree should be
terminated. Supervisor trees can be configured to either continue
terminating upwards, or terminate themselves but not continue
propagating the termination upwards.
* UnstoppedServiceReport calling semantics modified to allow correctly
retrieving reports from entire trees. (Prior to 4.0, a report was
only on the supervisor it was called on.)
* 3.0.4:
* Fix a problem with adding services to a stopped supervisor.
* 3.0.3:
* Implemented request in Issue #37, creating a new method StopWithReport
on supervisors that reports what services failed to stop. While a bit
Expand Down
7 changes: 5 additions & 2 deletions supervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,8 @@ Add adds a service to this supervisor.

If the supervisor is currently running, the service will be started
immediately. If the supervisor is not currently running, the service
will be started when the supervisor is.
will be started when the supervisor is. If the supervisor was already stopped,
this is a no-op returning an empty service-token.

The returned ServiceID may be passed to the Remove method of the Supervisor
to terminate the service.
Expand Down Expand Up @@ -407,7 +408,9 @@ func (s *Supervisor) Add(service Service) ServiceToken {
s.Unlock()

response := make(chan serviceID)
s.control <- addService{service, serviceName(service), response}
if !s.sendControl(addService{service, serviceName(service), response}) {
return ServiceToken{}
}
return ServiceToken{uint64(s.id)<<32 | uint64(<-response)}
}

Expand Down
23 changes: 23 additions & 0 deletions suture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,29 @@ func TestEverMultistarted(t *testing.T) {
}
}

func TestAddAfterStopping(t *testing.T) {
// t.Parallel()

s := NewSimple("main")

service := NewService("A1")
addDone := make(chan struct{})

s.ServeBackground()
s.Stop()

go func() {
s.Add(service)
close(addDone)
}()

select {
case <-time.After(5 * time.Second):
t.Fatal("Timed out waiting for Add to return")
case <-addDone:
}
}

// A test service that can be induced to fail, panic, or hang on demand.
func NewService(name string) *FailableService {
return &FailableService{name, make(chan bool), make(chan int),
Expand Down
48 changes: 48 additions & 0 deletions v4/complete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package suture

import (
"context"
"fmt"
"testing"
)

const (
JobLimit = 2
)

type IncrementorJob struct {
current int
next chan int
}

func (i *IncrementorJob) Serve(ctx context.Context) error {
for {
select {
case i.next <- i.current + 1:
i.current++
if i.current >= JobLimit {
fmt.Println("Stopping the service")
return ErrDoNotRestart
}
}
}
}

func TestCompleteJob(t *testing.T) {
supervisor := NewSimple("Supervisor")
service := &IncrementorJob{0, make(chan int)}
supervisor.Add(service)

ctx, myCancel := context.WithCancel(context.Background())
supervisor.ServeBackground(ctx)

fmt.Println("Got:", <-service.next)
fmt.Println("Got:", <-service.next)

myCancel()

// Output:
// Got: 1
// Got: 2
// Stopping the service
}
54 changes: 54 additions & 0 deletions v4/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*

Package suture provides Erlang-like supervisor trees.

This implements Erlang-esque supervisor trees, as adapted for Go. This is
intended to be an industrial-strength implementation, but it has not yet
been deployed in a hostile environment. (It's headed there, though.)

Supervisor Tree -> SuTree -> suture -> holds your code together when it's
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.

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
https://www.jerf.org/iri/post/2930 .

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"
function.

Create "Service"s, which implement the Service interface. .Add() them
to your Supervisor. Supervisors are also services, so you can create a
tree structure here, depending on the exact combination of restarts
you want to create.

As a special case, when adding Supervisors to Supervisors, the "sub"
supervisor will have the "super" supervisor's Log function copied.
This allows you to set one log function on the "top" supervisor, and
have it propagate down to all the sub-supervisors. This also allows
libraries or modules to provide Supervisors without having to commit
their users to a particular logging method.

Finally, as what is probably the last line of your main() function, call
.Serve() on your top level supervisor. This will start all the services
you've defined.

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

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

package suture

import "errors"

func isErr(err error, target error) bool {
return errors.Is(err, target)
}
7 changes: 7 additions & 0 deletions v4/errors_before_13.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// +build !go1.13

package suture

func isErr(err error, target error) bool {
return err == target
}
Loading