Chapter 12Lesson 04~100 minutes

Locking, Mutual Exclusion, and Duplicate-Run Protection

Cron overlap, CI retries, and manual reruns can execute the same automation concurrently. Locking defines which instance owns a critical section and what other instances should do while it is busy.

IntermediateProduction reliabilityHands-on lab

Learning objectives

By the end of this lesson

  • Explain mutual exclusion.
  • Implement a portable mkdir lock.
  • Use flock where available.
  • Choose fail, skip, or wait behavior.
  • Recognize when distributed coordination is required.

1. Duplicate execution can corrupt shared state

Cron overlap, CI retries, manual reruns, and supervisor restarts can launch the same script simultaneously. If both instances mutate the same files or remote resources, race conditions can produce inconsistent results.

Mutual exclusion
flowchart LR
  A["instance A"] --> L{"lock"}
  B["instance B"] --> L
  L -->|"winner"| R["critical section"]
  L -->|"loser"| W["wait / fail / skip"]

2. mkdir can act as a portable lock primitive

lockdir=/tmp/myjob.lock

if mkdir -- "$lockdir" 2>/dev/null; then
  printf 'lock acquired\n'
else
  printf 'another instance is running\n' >&2
  exit 75
fi

Directory creation is atomic enough on typical local filesystems to make this a useful portable pattern. It still needs stale-lock handling.

3. Release a lock through cleanup

lockdir=/tmp/myjob.lock

cleanup() {
  local status=$?
  rm -rf -- "$lockdir"
  exit "$status"
}

mkdir -- "$lockdir" 2>/dev/null || exit 75
trap cleanup EXIT
Only release what you own

Do not register lock cleanup until acquisition succeeds.

4. Lock metadata helps diagnose stale owners

printf 'pid=%s\nstarted=%s\n' \
  "$$" \
  "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
  > "$lockdir/owner"

Metadata is diagnostic, not proof that the recorded PID still represents the original process. PID reuse makes naive stale-lock deletion risky.

5. flock provides kernel-backed advisory locking on Linux

exec 9>"/var/lock/myjob.lock"

if flock -n 9; then
  printf 'lock acquired\n'
else
  printf 'job already running\n' >&2
  exit 75
fi

The lock is associated with the open file descriptor and released automatically when it closes.

Portability

flock is common on Linux through util-linux but is not a POSIX shell utility and is not universally available.

6. Decide whether callers wait, fail, or skip

PolicyBehaviorUse
Fail immediatelyReturn non-zero when lock is busyGood for CI jobs that must not overlap
SkipReturn success/NOOP when another run owns workGood for periodic maintenance
WaitBlock for lock availabilityGood when every run must eventually execute

7. Lock acquisition itself may need a timeout

if flock -w 30 9; then
  run_critical_section
else
  printf 'lock wait timed out\n' >&2
  exit 75
fi

Never let a coordination primitive accidentally create an infinite wait unless that is explicitly acceptable.

8. Keep the critical section as small as practical

prepare_inputs

acquire_lock
update_shared_state
release_lock

publish_report

Do expensive independent work outside the lock so one slow instance does not block others unnecessarily.

9. Local file locks do not coordinate multiple hosts

A lock under /tmp or a local filesystem protects only processes sharing that lock domain. Multi-host coordination may require database locks, distributed leases, orchestrator primitives, or remote state services.

Choose the lock domain

The synchronization mechanism must cover every actor that can mutate the protected resource.

10. Locks and atomic updates solve different problems

Atomic rename prevents readers from seeing partially written content. A lock prevents multiple writers from simultaneously deciding or modifying state. Robust workflows may need both.

11. Hands-on lab: duplicate-run protection

mkdir -p "$HOME/devops-academy/bash/chapter12/lesson04"
cd "$HOME/devops-academy/bash/chapter12/lesson04"

cat > single-run.sh <<'EOF'
#!/usr/bin/env bash
set -u

lockdir="./job.lock"
owned=false

cleanup() {
  local status=$?
  if [[ $owned == true ]]; then
    rm -rf -- "$lockdir"
  fi
  exit "$status"
}

trap cleanup EXIT

if mkdir -- "$lockdir" 2>/dev/null; then
  owned=true
else
  printf 'another instance is active\n' >&2
  exit 75
fi

printf 'pid=%s\n' "$$" > "$lockdir/owner"
printf 'running pid=%s\n' "$$"
sleep 5
printf 'done pid=%s\n' "$$"
EOF

chmod u+x single-run.sh
printf 'Run ./single-run.sh twice quickly to observe lock rejection.\n'

Verification checklist

12. Knowledge check

Question 1. What problem does a lock solve?

Question 2. Why is PID metadata not sufficient proof of lock ownership?

Question 3. Does a local /tmp lock coordinate multiple hosts?

Question 4. How are atomic rename and locking different?

13. Summary

Duplicate-run protection requires a clearly defined lock domain, ownership lifecycle, busy policy, and stale-state strategy. Portable mkdir locks are simple; kernel-backed flock is convenient where available. Keep the critical section narrow and remember that local locks do not solve distributed coordination.

14. Further reading

  • util-linux documentation — flock.
  • GNU Coreutils manual — mkdir and file operations.
  • POSIX filesystem atomicity concepts.
  • systemd documentation — service instance and timer overlap controls.
Next lesson

Safe Temporary State, Checkpoints, and Recovery

Continue Chapter 12 by turning error-safe scripts into production-grade, recoverable automation.

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.