From: Matthias Kruk Date: Fri, 23 Apr 2021 22:01:00 +0000 (+0900) Subject: include/mutex,wmutex: Add weak mutexes that don't enforce ownership X-Git-Url: https://git.corax.cc/?a=commitdiff_plain;h=1582417b5d3cc8da595c854961b73fb54cf662a7;p=toolbox include/mutex,wmutex: Add weak mutexes that don't enforce ownership When using mutexes as a means for signalling between two processes, the lock and unlock operation will almost never be executed by the same process. However, the mutex module does not allow such behavior. This commit adds the wmutex module (the "w" is for "weak") which does not enforce ownership on mutexes. As a side note, the same could have been achieved by adding something like mutex_weak_lock() and mutex_weak_unlock() to the mutex module, but that would lead to people mixing the two. Implementing them in separate modules hopefully makes it more clear that using wmutex functions on a mutex (and vice-versa) is not acceptable. If necessary, I might even implement the two in different ways so a mutex can't be released with wmutex_unlock(). --- diff --git a/include/mutex.sh b/include/mutex.sh index a72be4a..c05652f 100644 --- a/include/mutex.sh +++ b/include/mutex.sh @@ -10,9 +10,7 @@ __init() { } mutex_trylock() { - local lock - - lock="$1" + local lock="$1" if ! ln -s "$BASHPID" "$lock" &> /dev/null; then return 1 @@ -22,11 +20,14 @@ mutex_trylock() { } mutex_lock() { - local lock - - lock="$1" + local lock="$1" while ! mutex_trylock "$lock"; do + # We can't inotifywait on symlinks. Which is + # fine because when the symlink is removed, the + # containing directory is changed. Hence, we can + # watch the containing directory instead. + if ! inotifywait -qq "${lock%/*}"; then return 1 fi @@ -36,20 +37,19 @@ mutex_lock() { } mutex_unlock() { - local lock - local owner + local lock="$1" - lock="$1" + local owner if ! owner=$(readlink "$lock" 2> /dev/null); then return 1 fi - if [ "$owner" -ne "$BASHPID" ]; then + if (( owner != BASHPID )); then return 2 fi - if ! rm -f "$lock"; then + if ! rm "$lock"; then return 3 fi diff --git a/include/wmutex.sh b/include/wmutex.sh new file mode 100644 index 0000000..3b78d61 --- /dev/null +++ b/include/wmutex.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# +# wmutex - weak mutex implementation for bash scripts +# Copyright (C) 2021 - Matthias Kruk +# + +__init() { + return 0 +} + +wmutex_trylock() { + local lock="$1" + + if ! ln -s "$BASHPID" "$lock" &> /dev/null; then + return 1 + fi + + return 0 +} + +wmutex_lock() { + local lock="$1" + + while ! wmutex_trylock "$lock"; do + if ! inotifywait -qq "${lock%/*}"; then + return 1 + fi + done + + return 0 +} + +wmutex_unlock() { + local lock="$1" + + if ! rm "$lock"; then + return 1 + fi + + return 0 +} diff --git a/test/mutex.sh b/test/mutex.sh index 5edbc16..e279f8d 100755 --- a/test/mutex.sh +++ b/test/mutex.sh @@ -89,5 +89,7 @@ teardown() { mutex_lock "$name" delete+=("$name") + [ -L "$name" ] ( ! mutex_unlock "$name" ) + [ -L "$name" ] } diff --git a/test/wmutex.sh b/test/wmutex.sh new file mode 100755 index 0000000..16a1180 --- /dev/null +++ b/test/wmutex.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bats + +. toolbox.sh +include "wmutex" + +setup() { + delete=() + + return 0 +} + + +teardown() { + if (( ${#delete[@]} > 0 )); then + echo "${delete[*]}" >> /tmp/teardown.bats + rm -f "${delete[@]}" + fi + + return 0 +} + +@test "wmutex_trylock() returns success if the lock was created" { + local name + + name="test_$RANDOM" + + wmutex_trylock "$name" + [ -L "$name" ] + + delete+=("$name") +} + +@test "wmutex_trylock() returns failure if the lock was not created" { + local name + + name="/$RANDOM/$RANDOM/$RANDOM/$RANDOM" + + ! [ -d "${name%/*}" ] + ! wmutex_trylock "$name" +} + +@test "wmutex_lock() returns success if the lock was created" { + local name + + name="test_$RANDOM" + + wmutex_lock "$name" + [ -L "$name" ] + + delete+=("$name") +} + +@test "wmutex_lock() returns failure if the lock was not created" { + local name + + # Unlikely to exist path + name="/$RANDOM/$RANDOM/RANDOM/$RANDOM" + + ! [ -d "${name%/*}" ] + ! wmutex_lock "$name" + ! [ -L "$name" ] +} + +@test "wmutex_unlock() returns success if the lock was removed" { + local name + + name="test_$RANDOM" + + wmutex_lock "$name" + wmutex_unlock "$name" + ! [ -e "$name" ] +} + +@test "wmutex_unlock() returns failure if the lock was not removed" { + local name + + # Unlikely to exist path + name="/$RANDOM/$RANDOM/$RANDOM/$RANDOM" + + ! [ -d "${name%/*}" ] + ! wmutex_unlock "$name" +} + +@test "wmutex_unlock() returns success if wmutex belongs to a different process" { + local name + + name="test_$RANDOM" + + wmutex_lock "$name" + delete+=("$name") + + [ -L "$name" ] + ( wmutex_unlock "$name" ) + ! [ -L "$name" ] +}