Skip to content

Instantly share code, notes, and snippets.

@bzon
Last active October 5, 2021 18:29
Show Gist options
  • Save bzon/4fcdd3e9c78bca1feddfe9b05ddc2d57 to your computer and use it in GitHub Desktop.
Save bzon/4fcdd3e9c78bca1feddfe9b05ddc2d57 to your computer and use it in GitHub Desktop.
Golang gracefull shutdown with Context

Version 1.

package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	errs := make(chan error, 2)
	workerCtx, ctxDone := context.WithCancel(context.Background())
	go func() {
		errs <- worker1(workerCtx)
	}()
	go func() {
		errs <- worker2(workerCtx)
	}()

	// Terminate on CTRL+C
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	<-c
	ctxDone()

	// Wait for all the go routine to return before terminating completely.
	for i := 0; i < cap(errs); i++ {
		<-errs
	}

}

func worker1(ctx context.Context) error {
	for {
		select {
		// simulate a work that can't be interrupted unless it's done.
		default:
			fmt.Println("worker 1 working")
			time.Sleep(3 * time.Second)
			fmt.Println("worker 1 done working")
		case <-ctx.Done():
			fmt.Println("worker 1 received a shutdown")
			time.Sleep(1 * time.Second)
			fmt.Println("gracefully terminated worker 1")
			return ctx.Err()
		}
	}
}

func worker2(ctx context.Context) error {
	for {
		select {
		// simulate a work that can't be interrupted unless it's done.
		default:
			fmt.Println("worker 2 working")
			time.Sleep(10 * time.Second)
			fmt.Println("worker 2 done working")
		case <-ctx.Done():
			fmt.Println("worker 2 received a shutdown")
			time.Sleep(5 * time.Second)
			fmt.Println("gracefully terminated worker 2")
			return ctx.Err()
		}
	}
}

Version 2 using package [oklog/run].

package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/oklog/run"
)

func main() {
	g := run.Group{}
	workerCtx, ctxDone := context.WithCancel(context.Background())
	g.Add(func() error {
		return worker1(workerCtx)
	}, func(error) {
		ctxDone()
	})
	g.Add(func() error {
		return worker2(workerCtx)
	}, func(error) {
		ctxDone()
	})
	// // Terminate on CTRL+C
	cancelInterrupt := make(chan struct{})
	g.Add(func() error {
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		select {
		case sig := <-c:
			return fmt.Errorf("received signal %s", sig)
		case <-cancelInterrupt:
			return nil
		}
	}, func(error) {
		close(cancelInterrupt)
	})
	fmt.Println(g.Run())
}

func worker1(ctx context.Context) error {
	for {
		select {
		// simulate a work that can't be interrupted unless it's done.
		default:
			fmt.Println("worker 1 working")
			time.Sleep(3 * time.Second)
			fmt.Println("worker 1 done working")
		case <-ctx.Done():
			fmt.Println("worker 1 received a shutdown")
			time.Sleep(1 * time.Second)
			fmt.Println("gracefully terminated worker 1")
			return ctx.Err()
		}
	}
}

func worker2(ctx context.Context) error {
	for {
		select {
		// simulate a work that can't be interrupted unless it's done.
		default:
			fmt.Println("worker 2 working")
			time.Sleep(10 * time.Second)
			fmt.Println("worker 2 done working")
		case <-ctx.Done():
			fmt.Println("worker 2 received a shutdown")
			time.Sleep(5 * time.Second)
			fmt.Println("gracefully terminated worker 2")
			return ctx.Err()
		}
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment