Chapter 10Lesson 03~90 minutes

Signals and Graceful Termination

Production automation is interrupted by users, CI runners, containers, and supervisors. A reliable Bash process responds deliberately, forwards termination to owned children, and cleans up without hiding the original status.

BeginnerProcesses & concurrencyHands-on lab

Learning objectives

By the end of this lesson

  • Explain common termination signals.
  • Install traps for cleanup and shutdown.
  • Forward signals to children.
  • Preserve status through cleanup.
  • Design for untrappable termination.

1. Signals notify processes asynchronously

SignalTypical meaningImportant property
SIGTERMRequest graceful terminationCommon supervisor shutdown signal
SIGINTInteractive interruptOften Ctrl+C
SIGHUPHangup/session eventSometimes used as reload convention
SIGKILLImmediate forced terminationCannot be trapped or ignored

2. trap installs handlers

cleanup() {
  printf 'cleanup before exit\n' >&2
}
trap cleanup EXIT

EXIT is a Bash pseudo-signal that runs for many normal shell exit paths.

3. TERM and INT should have explicit behavior

on_term() {
  printf 'termination requested\n' >&2
  exit 143
}

trap on_term TERM
trap on_term INT

If callers depend on a status convention, document it.

4. Wrappers may need to forward signals to children

child_pid=""

forward_term() {
  if [[ -n $child_pid ]]; then
    kill -TERM "$child_pid" 2>/dev/null || true
  fi
}

trap forward_term TERM INT

long_running_command &
child_pid=$!

wait "$child_pid"
status=$?
child_pid=""
exit "$status"
Wrapper ownership

A Bash wrapper that swallows supervisor signals can leave the actual workload running.

5. kill sends signals; it is not limited to SIGKILL

kill -TERM "$pid"
kill -HUP "$pid"

Symbolic signal names make intent much clearer than unexplained signal numbers.

6. Signal 0 can probe reachability

if kill -0 "$pid" 2>/dev/null; then
  printf 'PID appears reachable\n'
fi

This does not prove application health, and PID reuse means stale PID files require careful handling.

7. Cleanup should tolerate partially initialized state

tmpdir=""
child_pid=""

cleanup() {
  local status=$?
  [[ -n $child_pid ]] && kill -TERM "$child_pid" 2>/dev/null || true
  [[ -n $tmpdir && -d $tmpdir ]] && rm -rf -- "$tmpdir"
  return "$status"
}

trap cleanup EXIT

8. EXIT traps should preserve the incoming status

cleanup() {
  local status=$?
  rm -f -- "${tmp:-}"
  exit "$status"
}
trap cleanup EXIT

9. SIGKILL and machine failure bypass cleanup

SIGKILL cannot be trapped. Power failure, kernel failure, or abrupt runtime termination can also bypass shell cleanup.

Design for leftovers

Temporary files, locks, and recovery procedures must tolerate stale artifacts.

10. Cooperate with systemd, containers, CI, and Kubernetes

Supervisors usually send a graceful signal, wait for a grace period, then escalate. Shell wrappers should forward termination and finish promptly rather than trapping signals indefinitely.

11. Hands-on lab: graceful child shutdown

mkdir -p "$HOME/devops-academy/bash/chapter10/lesson03"
cd "$HOME/devops-academy/bash/chapter10/lesson03"

cat > graceful.sh <<'EOF'
#!/usr/bin/env bash
child_pid=""

cleanup() {
  local status=$?
  if [[ -n $child_pid ]]; then
    kill -TERM "$child_pid" 2>/dev/null || true
    wait "$child_pid" 2>/dev/null || true
  fi
  exit "$status"
}

on_term() {
  printf 'parent received termination\n' >&2
  exit 143
}

trap cleanup EXIT
trap on_term TERM INT

bash -c '
  trap "echo child received TERM >&2; exit 0" TERM
  while :; do sleep 1; done
' &
child_pid=$!

printf 'parent=%s child=%s\n' "$$" "$child_pid"
printf 'test from another shell: kill -TERM %s\n' "$$"

wait "$child_pid"
status=$?
child_pid=""
exit "$status"
EOF

chmod u+x graceful.sh

Verification checklist

12. Knowledge check

Question 1. Which common graceful shutdown signal can be trapped?

Question 2. Can SIGKILL be trapped?

Question 3. Why forward TERM to a child?

Question 4. Why design for stale artifacts?

13. Summary

Signals are part of process lifecycle design. Trap graceful termination, forward it to owned children, preserve status during cleanup, and remember that forced termination can bypass every shell handler.

14. Further reading

  • GNU Bash Reference Manual — Signals and Traps.
  • POSIX kill and signal concepts.
  • Linux signal(7) and kill(2).
  • systemd and Kubernetes process-termination documentation.
Next lesson

Timeouts and Long-Running Commands

Continue Chapter 10 by making process lifecycle and concurrency behavior more explicit.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.