├── README.md ├── examples ├── blocking.sh └── spin.sh ├── keyway_lib.sh └── keyway_lib_tests.sh /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | dP 3 | 88 4 | 88 .dP .d8888b. dP dP dP dP dP .d8888b. dP dP 5 | 88888" 88ooood8 88 88 88 88 88 88' `88 88 88 6 | 88 `8b. 88. ... 88. .88 88.88b.88' 88. .88 88. .88 7 | dP `YP `88888P' `8888P88 8888P Y8P `88888P8 `8888P88 8 | .88 .88 9 | d8888P d8888P 10 | ``` 11 | ### Keyway 12 | A simple lock file library. 13 | 14 | ###### Features 15 | * Provides mutual exclusion for scripts that require the same resource. 16 | * Requires three additional lines of code in your script, including sourcing the library. 17 | * Scripts using Keyway can be configured to either terminate or busy-wait if a resource is blocked. 18 | * Keyway will report when an external error was caught and there are lock files in the lock directory. 19 | 20 | ###### Usage: 21 | * `acquire_lock_for "your_task_name"` 22 | * If the resource is not locked, your task will execute, otherwise it will terminate. 23 | * `acquire_spinlock_for "your_task_name"` 24 | * If the resource is locked, your task will wait until the lock has been released before acquiring its own lock and executing. 25 | 26 | ###### Return Code Explanations: 27 | 1. Your application was not able to acquire lock. 28 | 2. There was some other problem: 29 | * Keyway could not create the lock directory. 30 | * Keyway could not create or remove a lock. 31 | 3. An error was caught and there are lock files in the lock directory. 32 | 33 | ###### An example: 34 | ```bash 35 | #!/bin/bash 36 | source keyway_lib.sh 37 | 38 | # optionally override the lock file directory 39 | LOCK_DIR="alt-lock-dir" 40 | 41 | # attempt to lock the shared resource 42 | acquire_lock_for "your_task_name" 43 | 44 | # if the lock was successful, execute the task 45 | echo "executing critical section" 46 | 47 | # release the lock when the task is done 48 | release_lock_for "your_task_name" 49 | ``` 50 | -------------------------------------------------------------------------------- /examples/blocking.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load keyway 4 | source keyway_lib.sh 5 | 6 | # acquire a regular lock and release it 7 | acquire_lock_for "regular" 8 | echo "regular lock critical section" 9 | release_lock_for "regular" 10 | 11 | # acquire a regular lock and block 12 | acquire_lock_for "blocking" 13 | echo "blocking lock critical section" 14 | sleep 30 15 | release_lock_for "blocking" 16 | -------------------------------------------------------------------------------- /examples/spin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # load keyway 4 | source keyway_lib.sh 5 | 6 | # acquire a spinlock 7 | acquire_spinlock_for "blocking" 8 | 9 | # if the blocking example is running, this script will wait 10 | # to enter the critical section 11 | echo "in" 12 | 13 | # release the lock 14 | release_lock_for "blocking" 15 | -------------------------------------------------------------------------------- /keyway_lib.sh: -------------------------------------------------------------------------------- 1 | # Keyway - a simple lock file library. 2 | # https://github.com/ATNI/keyway 3 | 4 | # Practice safe bash scripting. 5 | set -o errexit ; set -o nounset 6 | 7 | # Customize the location of your lock files for this resource. 8 | LOCK_DIR="locks" 9 | # Set to true to supress log messages. 10 | SILENT=false 11 | 12 | # Check for locks and provide a warning during an abnormal exit. 13 | trap "check_for_locks" SIGTERM SIGINT ERR 14 | 15 | acquire_lock_for() { 16 | if create_lock $1; then 17 | check_execution "acquire lock" 18 | lock_log "Created $1 lock." 19 | else 20 | lock_log "Cannot run $1 -- application locked." 21 | exit 1 22 | fi 23 | } 24 | 25 | acquire_spinlock_for() { 26 | lock_log "Waiting on lock for $1." 27 | while : 28 | do 29 | if create_lock $1; then 30 | lock_log "Created $1 lock." 31 | break 32 | else 33 | sleep 1 34 | fi 35 | done 36 | } 37 | 38 | create_lock() { 39 | check_lock_dir 40 | ( set -o noclobber; echo "locked" > "$LOCK_DIR/$1".lock ) 2> /dev/null 41 | } 42 | 43 | release_lock_for() { 44 | lock_log "Releasing $1 lock." 45 | rm "$LOCK_DIR/$1".lock 46 | check_execution "release lock" 47 | } 48 | 49 | check_for_locks() { 50 | shopt -s nullglob 51 | if [[ ($LOCK_DIR/*.lock) ]]; then 52 | lock_log "Dirty exit -- lock files found in $LOCK_DIR." 53 | shopt -u nullglob && exit 3 54 | fi 55 | shopt -u nullglob 56 | } 57 | 58 | check_lock_dir() { 59 | if [ ! -d $LOCK_DIR ]; then 60 | lock_log "Creating lock directory: $LOCK_DIR" 61 | mkdir -p $LOCK_DIR 62 | check_execution "create lock directory" 63 | fi 64 | } 65 | 66 | check_execution() { 67 | if [ $? -ne 0 ]; then 68 | lock_log "Could not $1, exiting." 69 | exit 2 70 | fi 71 | } 72 | 73 | lock_log() { 74 | if [ ! "$SILENT" == true ]; then 75 | local datetime=`date +"%Y-%m-%d %H:%M:%S"` 76 | printf "$datetime - Keyway: $1\n" 77 | fi 78 | } 79 | -------------------------------------------------------------------------------- /keyway_lib_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source keyway_lib.sh 3 | 4 | # Set our own test lock directory. 5 | LOCK_DIR=test_locks 6 | 7 | # Turn on silent mode so we can see our tests. 8 | SILENT=true 9 | 10 | # print a little information 11 | echo 12 | echo "Running Keyway Tests" 13 | echo "--------------------" 14 | echo 15 | 16 | # prepare for the tests 17 | if [ -d $LOCK_DIR ]; then 18 | echo "Removing existing lock directory." 19 | rm -rf $LOCK_DIR 20 | fi 21 | 22 | # test creation of lock directory 23 | check_lock_dir 24 | if [ -d $LOCK_DIR ]; then 25 | echo "Lock Directory: Passed" 26 | else 27 | echo "Lock Directory: Failed" 28 | fi 29 | 30 | # test creation of lock file 31 | acquire_lock_for "test" 32 | if [ -f "$LOCK_DIR"/test.lock ]; then 33 | echo "Acquire Lock: Passed" 34 | else 35 | echo "Acquire Lock: Failed" 36 | fi 37 | 38 | # test lock 39 | if ! create_lock; then 40 | echo "Mutex: Passed" 41 | else 42 | echo "Mutex: Failed" 43 | fi 44 | 45 | # test removal of lock file 46 | release_lock_for "test" 47 | if [ ! -f "$LOCK_DIR"/test.lock ]; then 48 | echo "Release Lock: Passed" 49 | else 50 | echo "Release Lock: Failed" 51 | fi 52 | 53 | # clean up after the tests 54 | echo "Cleaning up the test lock directory." 55 | rm -rf $LOCK_DIR 56 | --------------------------------------------------------------------------------