Go Academy · Lesson

Graceful Job Shutdown

Stop jobs cleanly.

Lesson 4 of 413 steps

Graceful Job Shutdown is a free Go Academy lesson on CoddyKit. This is lesson 4 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Go Academy learning path, and your progress syncs across the web and the CoddyKit app. The Go Academy course includes 4 lessons in total.

Stopping Jobs Cleanly

A scheduler that is killed mid-job can corrupt data or leave half-finished work. Graceful shutdown means: stop accepting new runs, let in-flight jobs finish (up to a deadline), then exit.

Listening for Signals

Catch OS signals like SIGINT (Ctrl+C) and SIGTERM (orchestrator stop) so you can shut down on purpose.

sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
fmt.Println("shutting down")

signal.NotifyContext

Go 1.16+ offers a tidy helper that cancels a context when a signal arrives.

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
<-ctx.Done()

Stopping the Scheduler

With robfig/cron, c.Stop() returns a context that is done once all running jobs complete. Wait on it to drain.

ctx := c.Stop()        // stops scheduling, returns ctx
<-ctx.Done()            // wait for running jobs
fmt.Println("all jobs finished")

Propagating Cancellation to Jobs

Pass a context into each job so long-running work can abort promptly when shutdown begins.

func job(ctx context.Context) {
    select {
    case <-time.After(time.Hour):
    case <-ctx.Done():
        return // bail out early
    }
}

Bounding the Wait

Do not wait forever for stuck jobs. Use a timeout context so shutdown completes even if a job hangs.

shutCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

WaitGroups for Custom Schedulers

If you roll your own with goroutines, track them with a sync.WaitGroup and Wait() during shutdown.

var wg sync.WaitGroup
wg.Add(1)
go func() { defer wg.Done(); doWork() }()
// shutdown:
wg.Wait()

Avoid Starting New Work

Once shutdown begins, the ticker/cron must stop firing. Closing a done channel or canceling the context ensures the loop sees the signal and stops scheduling new runs.

Idempotency Helps

Design jobs to be safe to re-run. If a job is killed mid-way and retried after restart, idempotent logic avoids duplicate effects (double emails, double charges).

The Full Pattern

Putting it together: a notify-on-signal context, a cron Stop that returns a drain context, and a bounded timeout. This is the standard graceful-shutdown skeleton for scheduled services.

Runnable: Graceful Stop With Context

This self-contained program runs a periodic worker and stops it gracefully via context cancellation, waiting for the in-flight tick to finish.

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        t := time.NewTicker(5 * time.Millisecond)
        defer t.Stop()
        for {
            select {
            case <-t.C:
                fmt.Println("working")
            case <-ctx.Done():
                fmt.Println("stopping cleanly")
                return
            }
        }
    }()
    time.Sleep(17 * time.Millisecond)
    cancel()   // signal shutdown
    wg.Wait()  // wait for worker to finish
    fmt.Println("shutdown complete")
}

Quick Check

Test your understanding of graceful job shutdown.

Recap

You learned graceful job shutdown:

  • Catch SIGINT/SIGTERM, ideally via signal.NotifyContext
  • cron.Stop() returns a context that drains running jobs
  • Propagate context into jobs and bound the wait with a timeout
  • Make jobs idempotent so retries are safe
Free to start

Learn Go with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
51
Lessons
203

Frequently Asked Questions

Is the “Graceful Job Shutdown” lesson free?

Yes — any 3 lessons of the Go Academy learning path, including “Graceful Job Shutdown”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Go Academy course includes 4 lessons in total.

What will I learn in “Graceful Job Shutdown”?

Stop jobs cleanly. You practise Go Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Go Academy?

No prior experience is required. Go Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.

How long does the “Graceful Job Shutdown” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Go Academy lesson?

Yes. Every Go Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Time-Based Scheduling
  2. The robfig/cron Library
  3. Tickers for Intervals
  4. Graceful Job Shutdown
← Back to Go Academy