Skip to content

Instantly share code, notes, and snippets.

@dllen
Forked from karlrwjohnson/lockfile_demo.sh
Created April 3, 2024 06:24
Show Gist options
  • Save dllen/f6c3470cb8ab713959a1297ef6caa216 to your computer and use it in GitHub Desktop.
Save dllen/f6c3470cb8ab713959a1297ef6caa216 to your computer and use it in GitHub Desktop.

Revisions

  1. @karlrwjohnson karlrwjohnson revised this gist Apr 3, 2018. No changes.
  2. @karlrwjohnson karlrwjohnson created this gist Apr 3, 2018.
    87 changes: 87 additions & 0 deletions lockfile_demo.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,87 @@
    #!/bin/bash

    # Lockfiles are a feature in Linux which allows multiple processes to use a file as a mutex.

    # This script contains example code based on this blog post, but with a bugfix: https://dmorgan.info/posts/linux-lock-files/

    # Wrapper function for easy reuse.
    # I've heavily annotated it, but without the comments or arguments it's only 13 lines long.
    # Parameters (you can hard-code them instead if you want)
    # - LOCK_FILE: Which file to use for the lock.
    # Needs to be a writable location and unique to the "thing" that needs to be locked on.
    # The file doesn't need to exist yet.
    # - LOCK_TIMEOUT: (Optional) How many SECONDS to wait before aborting
    # ...remaining args: Command to run
    function lock_cmd {
    LOCK_FILE="$1"; shift
    LOCK_TIMEOUT="$1"; shift;

    (
    # Remove the lockfile automatically when the function finishes
    trap "rm -f $LOCK_FILE" 0

    # "flock", or "file lock", takes a file descriptor ID as a parameter instead of a filename.
    # That means you either have to open a file yourself with a specific handle (Here, I've arbitrarily used 200)
    # or let the library handle it for you and somehow get the ID of the opened file.
    flock -x -w $LOCK_TIMEOUT 200
    RETVAL=$?

    # "flock" returns 1 if the timeout has occurred.
    if [ $RETVAL -ne 0 ]; then
    echo -e "Failed to aquire lock on $LOCK_FILE within $LOCK_TIMEOUT seconds. Is a similar script hung?"
    exit $RETVAL
    fi

    # "flock" can run a command automatically with the "-c" flag, but if the command fails then it returns the command's exit code.
    # That makes it impossible between the command failing and hitting the timeout.
    echo -e "Running command: $@"
    $@
    ) 200>"$LOCK_FILE"
    }

    # Example usage:
    # The motivation for this demo was that "docker network create" contains a race condition
    # which allows multiple networks to be created with the same name if run twice simultaneously
    # (https://github.com/moby/moby/issues/20648)

    netname=my_network
    lock_file=/tmp/docker-network-create-lock
    lock_timeout=5

    function assert_one {
    docker network ls --filter 'name=foo' --format '{{.ID}}'
    if [[ $(docker network ls --filter "name=$1" --format '{{.ID}}' | wc -l) -eq 1 ]]; then
    echo -e "\033[1;32m:) Pass\033[0m"
    else
    echo -e "\033[1;34m:( Fail\033[0m"
    exit 1
    fi
    }
    function cleanup {
    docker network ls --filter "name=$1" --format '{{.ID}}' | xargs --no-run-if-empty -n1 docker network rm
    }

    cleanup $netname

    echo -e "Testing with timeout $lock_timeout"
    lock_cmd $lock_file $lock_timeout docker network create $netname &
    first_pid=$!
    lock_cmd $lock_file $lock_timeout docker network create $netname &
    second_pid=$!
    tail --pid $first_pid -f /dev/null # Wait for processes to die
    tail --pid $second_pid -f /dev/null

    echo -e "There should be only one network:"
    assert_one $netname
    cleanup $netname

    # The `flock` command has an optional timeout parameter. If we decrease it to zero, we see what happens when a command takes too long
    echo -e "Testing with timeout 0"
    lock_cmd $lock_file 0 docker network create $netname &
    first_pid=$!
    lock_cmd $lock_file 0 docker network create $netname &
    second_pid=$!
    tail --pid $first_pid -f /dev/null
    tail --pid $second_pid -f /dev/null
    assert_one $netname
    cleanup $netname