Benjamin Schieder

SHARED AND EXCLUSIVE LOCKS

2015 January 20

Here’s a few snippets to get and release shared and exclusive filesystem locks. I don’t remember where I first found these, but enjoy them anyway:

stalelock_exclusive () { 
  # Check if exclusive locks are stale, ie the process they point to no
  # longer exists
  # returns 0 if lock exists and has a running process
  # which is really wrong, should be 1 so we can do stalelock_exclusive  || return 1
  if [ -n "${1}" ] ; then
    [ -e "${1}.lock" ] || return 1
    read pid < <( readlink -f "${1}.lock" )
    [ -z "${pid}" ] && return 1
    [ -e "/proc/${pid}" ] && return 0
    rm -f "${1}.lock"
    return 1
  fi
}

stalelock_shared () { 
  # Check if shared locks are stale, ie the process they point to no
  # longer exists
  # returns 0 if lock exists and has a running process
  # which is really wrong, should be 1 so we can do stalelock_shared  || return 1
  retval=1
  if [ -n "${1}" ] ; then
    for sharedlock in ${1}.shared* ; do
      [ -f "${sharedlock}" ] || continue
      read pid < <( ${sharedlock} )
      if [ -e "/proc/${pid}" ] ; then
        retval=0
      else
        rm -f ${sharedlock}
      fi
    done
  fi
  return ${retval}
}

getlock_exclusive() { 
  # Try to get an exclusive lock on a file.
  # Returns 0 on success, 1 on fail.
  if [ -n "${1}" ] ; then
    stalelock_exclusive "${1}"
    stalelock_shared "${1}"

    # Check if lock exists. If not our own PID, return false.
    if [ -e "${1}.lock" ] ; then
      read pid < <( readlink -f "${1}.lock" )
      if [ ${pid} -ne ${$} ] ; then
        return 1
      fi
    else
      # Create lock if it doesn't exist.
      ln -s "${$}" "${1}.lock" 2>/dev/null
    fi

    # Check again if lock is our own to prevent race conditions.
    read pid < <( readlink -f "${1}.lock" )
    if [ ${pid} -eq ${$} ] ; then
      for x in ${1}.shared* ; do
        # as long as a shared lock exists, exclusive ones aren't granted, but
        # reserved. This prevents new shared locks from being granted.
        [ -e "${x}" ] || continue
        return 1
      done
      return 0
    else
      return 1
    fi
  fi
}

getlock_shared() { 
  if [ -n "${1}" ] ; then
    stalelock_exclusive "${1}"
    stalelock_shared "${1}"
    [ -e "${1}.lock" ] && return 1 # no shared locks with an exclusive one in place
    ln -s "${$}" "${1}.shared_${$}"
    return 0
  fi
}

releaselock_shared () { 
  if [ -n "${1}" ] ; then 
    [ -e "${1}.shared_${$}" ] || return 0 # no lock, so it's released, no?
    rm -f "${1}.shared_${$}"
    return 0 
  fi
} 

releaselock_exclusive() { 
  if [ -n "${1}" ] ; then
    [ -e "${1}.lock" ] || return 0 # no lock, so it's released, no?
    read pid < <( readlink -f "${1}.lock" )
    if [ ${pid} -eq ${$} ] ; then
      # our lock. release it
      rm -f "${1}.lock"
      return 0
    else
      # uh-oh, not our lock...
      # Script shouldn't call release if getting the lock failed in the
      # first place.
      echo "Tried to release lock not our own!" >&2
      echo "LockPID: ${pid} Our pid: ${$} File: ${1}" >&2
      exit 1 # Bailing out.
    fi
  fi
}

while ! getlock_shared "/tmp/foo" ; do
  sleep 1
done

# do stuff

releaselock_shared "/tmp/foo"

while ! getlock_exclusive "/tmp/foo" ; do
  sleep 1
done

# do stuff

releaselock_shared "/tmp/foo"

EOF

Category: blog

Tags: bash locks