Skip to content

Instantly share code, notes, and snippets.

@gligneul
Last active September 25, 2023 22:49
Show Gist options
  • Save gligneul/dafa46f3e778888e108da13aa2d89d17 to your computer and use it in GitHub Desktop.
Save gligneul/dafa46f3e778888e108da13aa2d89d17 to your computer and use it in GitHub Desktop.
// Example of a simple supervisor that start multiple services in different goroutines
package main
import (
"context"
"fmt"
"time"
)
// service.go
// Interface of a service
type Service interface {
fmt.Stringer
// Start a service that should run until the context is canceled
Start(ctx context.Context) error
}
type Step interface {
fmt.Stringer
// Executes one step of computation
Step(ctx context.Context) error
}
// Implementation of the service interface for services that can be broken into steps
type StepService struct {
Step
}
func (s StepService) Start(ctx context.Context) error {
for {
select {
case <-ctx.Done():
fmt.Printf("%v: %v\n", s.String(), ctx.Err())
return nil
default:
if err := s.Step.Step(ctx); err != nil {
return err
}
time.Sleep(time.Second)
}
}
}
// hello.go
// The hello service sends a hello each second and shuts down when requested
type Hello struct{}
func (h Hello) Step(ctx context.Context) error {
fmt.Println("hello: hello")
return nil
}
func (_ Hello) String() string {
return "hello"
}
// bye.go
// The bye service waits three seconds and then exits
type Bye struct{}
func (b Bye) Start(ctx context.Context) error {
time.Sleep(3 * time.Second)
fmt.Println("bye: bye")
return fmt.Errorf("requesting shutdown")
}
func (_ Bye) String() string {
return "bye"
}
// hang.go
// The hang service does nothing and doesn't shutdown when requested
type Hang struct {
}
func (h Hang) Start(ctx context.Context) error {
for {
time.Sleep(time.Second)
}
}
func (_ Hang) String() string {
return "hang"
}
// main.go
func main() {
// declare services
services := []Service{
StepService{Hello{}},
Bye{},
Hang{},
}
// start services
ctx, cancel := context.WithCancel(context.Background())
exit := make(chan struct{})
for _, service := range services {
// avoid reference to loop variable
service := service
go func() {
if err := service.Start(ctx); err != nil {
msg := "main: service '%v' exited with error: %v\n"
fmt.Printf(msg, service.String(), err)
} else {
msg := "main: service '%v' exited successfully\n"
fmt.Printf(msg, service.String())
}
exit <- struct{}{}
}()
}
// wait for the first exit
<-exit
// send shutdown to remaining services
wait := make(chan struct{})
go func() {
cancel()
for i := 0; i < len(services)-1; i++ {
<-exit
}
wait <- struct{}{}
}()
// timeout in case one of the services hang
timeout := make(chan struct{})
go func() {
time.Sleep(3 * time.Second)
timeout <- struct{}{}
}()
select {
case <-wait:
fmt.Println("main: all services exited successfully")
case <-timeout:
fmt.Println("main: exit timout")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment