]> git.corax.cc Git - toolbox/commitdiff
include/mutex,wmutex: Add weak mutexes that don't enforce ownership
authorMatthias Kruk <m@m10k.eu>
Fri, 23 Apr 2021 22:01:00 +0000 (07:01 +0900)
committerMatthias Kruk <m@m10k.eu>
Fri, 23 Apr 2021 22:25:41 +0000 (07:25 +0900)
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().

include/mutex.sh
include/wmutex.sh [new file with mode: 0644]
test/mutex.sh
test/wmutex.sh [new file with mode: 0755]

index a72be4a998d61019f9b12c0cc7ed0a2571e0d4db..c05652fde402c6f84263c17a9315c23ba3b01c8c 100644 (file)
@@ -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 (file)
index 0000000..3b78d61
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+#
+# wmutex - weak mutex implementation for bash scripts
+# Copyright (C) 2021 - Matthias Kruk <m@m10k.eu>
+#
+
+__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
+}
index 5edbc16f19da8e3f307dd7a44f966e297f391a98..e279f8d9295786964039ed87d6bd2d3af392ada4 100755 (executable)
@@ -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 (executable)
index 0000000..16a1180
--- /dev/null
@@ -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" ]
+}