Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
File locking is a mutual-exclusion mechanism to ensure a file can be read/written by multiple processes in a safe way.
In this tutorial, we’ll understand the interceding update problem in a multiple-processes system. Then, we’re going to introduce two types of locks in Linux.
Along the way, we’ll learn some file-locking-related commands through examples.
The interceding update is a typical race condition problem in a concurrent system. Let’s see an example to understand the problem better.
Let’s say we have a balance.dat file storing the balance of an account, and it has an initial value of “100“. Our concurrent system has two processes to update the balance value:
Obviously, after the execution of two processes, we are expecting the file has value: 100-20+80=160.
However, an interceding update problem may occur in this situation:
As a result, we have 180 in the balance.dat file instead of the expected value 160.
File locking is a mechanism to restrict access to a file among multiple processes. It allows only one process to access the file in a specific time, thus avoiding the interceding update problem.
We all know that rm -rf / is a very dangerous command in Linux. If we execute the command as the root user, all files in the running system will be deleted. This is because Linux usually doesn’t automatically lock open files. However, Linux supports two kinds of file locks: advisory locks and mandatory locks.
We’ll introduce both lock types shortly, but this article will primarily focus on advisory locking.
Advisory locking is not an enforced locking scheme. It will work only if the participating processes are cooperating by explicitly acquiring locks. Otherwise, advisory locks will be ignored if a process is not aware of locks at all.
An example may help us to understand the cooperative locking scheme easier. Let’s review our previous balance example.
We must understand that the advisory lock was not set by the operating system or file system. Therefore, even if process A locks the file, process B is still free to read, write, or even delete the file via system calls.
If process B executes file operations without trying to acquire a lock, we say process B is not cooperating with process A.
But now, let’s have a look at how the lock will work for cooperating processes:
We’ll see how the example is implemented with the flock command in a later section.
Before we start looking at mandatory file locking, we should keep in mind that “Linux implementation of mandatory locking is unreliable“.
Unlike advisory locking, mandatory locking doesn’t require any cooperation between the participating processes. Once a mandatory lock is activated on a file, the operating system prevents other processes from reading or writing the file.
To enable mandatory file locking in Linux, two requirements must be satisfied:
In this section, let’s have a look at two ways to inspect the currently acquired locks in a running system.
The lslocks command is a member of the util-linux package and available on all Linux distributions. It can list all currently held file locks in our system.
Let’s see an example output:
$ lslocks
COMMAND PID TYPE SIZE MODE M START END PATH
lvmetad 298 POSIX 4B WRITE 0 0 0 /run/lvmetad.pid
containerd 665 FLOCK 128K WRITE 0 0 0 /var/lib/docker/...
chromium 184029 POSIX 9.4M WRITE 0 1073741824 1073742335 /home/kent/.config/chromium/Default/History
nextcloud 961 POSIX 32K READ 0 128 128 /home/kent/Nextcloud/._sync_0e131dbf228b.db-shm
dockerd 630 FLOCK 16K WRITE 0 0 0 /var/lib/docker/buildkit/snapshots.db
dropbox 369159 FLOCK 10M WRITE 0 0 0 /home/kent/.dropbox/logs/1/1-4ede-5e20dd8d.tmp
...
In the above list, we can see all the currently locked files in the system. We can also see detailed information of each lock, such as the lock type, and which process holds the lock.
/proc/locks is not a command. Instead, it is a file in the procfs virtual file system. The file holds all current file locks. The lslocks command relies on this file to generate the list, too.
To get the information of /proc/locks, we execute “cat /proc/locks“:
$ cat /proc/locks
1: FLOCK ADVISORY WRITE 369159 08:12:22417368 0 EOF
2: POSIX ADVISORY WRITE 321130 00:2e:30761 0 EOF
3: POSIX ADVISORY WRITE 184029 08:12:21760394 0 EOF
4: POSIX ADVISORY WRITE 184029 08:12:21633968 1073741824 1073742335
5: POSIX ADVISORY WRITE 184029 08:12:21760401 0 EOF
6: POSIX ADVISORY WRITE 184029 08:12:21891515 0 EOF
7: POSIX ADVISORY WRITE 184029 08:12:21633928 0 EOF
...
Let’s pick the first row to understand how the lock information is organized in the /proc/locks file system:
1: FLOCK ADVISORY WRITE 369159 08:12:22417368 0 EOF
-1- --2-- ---3--- --4-- ---5-- -------6------ -7- -8-
The flock command is also provided by the util-linux package. This utility allows us to manage advisory file locks in shell scripts or on the command line.
The basic usage syntax is:
flock FILE_TO_LOCK COMMAND
Next, let’s demonstrate our balance update example with the flock command.
In addition to a balance.dat text file containing the current balance value, we still need two processes, A and B, to update the balance in the file.
We first create a simple shell script update_balance.sh to handle balance update logic for both processes:
#!/bin/bash
file="balance.dat"
value=$(cat $file)
echo "Read current balance:$value"
#sleep 10 seconds to simulate business calculation
progress=10
while [[ $progress -lt 101 ]]; do
echo -n -e "\033[77DCalculating new balance..$progress%"
sleep 1
progress=$((10+progress))
done
echo ""
value=$((value+$1))
echo "Write new balance ($value) back to $file."
echo $value > "$file"
echo "Done."
We create a simple shell script a.sh to simulate process A:
#!/bin/bash
#-----------------------------------------
# process A: lock the file and subtract 20
# from the current balance
#-----------------------------------------
flock --verbose balance.dat ./update_balance.sh '-20'
Now let’s start process A to test:
$ ./a.sh
flock: getting lock took 0.000002 seconds
flock: executing ./update_balance.sh
Read current balance:100
Calculating new balance..100%
Write new balance (80) back to balance.dat.
Done.
Through the output, we can see the flock command first acquired a lock on the file balance.dat, then the update_balance.sh script read and updated the file.
During its run, we can check the lock information via the lslocks command:
$ lslocks | grep 'balance'
flock 825712 FLOCK 4B WRITE 0 0 0 /tmp/test/balance.dat
The output shows that the flock command is holding a WRITE lock on the entire file /tmp/test/balance.dat.
We’ve learned that the advisory locks work only if participating processes are cooperating. Let’s reset the balance to 100 and check what will happen if we acquire an advisory lock on the file for process A but start the process B in a non-cooperative way.
Now let’s create a simple shell script b_non-cooperative.sh:
#!/bin/bash
#----------------------------------------
# process B: add 80 to the current balance in a
# non-cooperative way
#----------------------------------------
./update_balance.sh '80'
We see that process B calls update_balance.sh without attempting to acquire a lock on the balance data file.
Let’s demonstrate this scenario in a GIF animation:
We see that the advisory lock acquired by process A is ignored if process B starts without cooperating with process A.
Therefore, we have 180, instead of 160, in the balance.dat.
Finally, let’s create another cooperative process B, b.sh, and see how the advisory lock works:
#!/bin/bash
#----------------------------------------
# process B: add 80 to the current balance
# in a cooperative way
#----------------------------------------
flock --verbose balance.dat ./update_balance.sh '80'
Again, we show the demonstration in a GIF animation:
In the demo, we made two processes to cooperate.
We noticed that when process B attempted to acquire a lock on the balance.dat file, it waited for process A to release the lock. Thus, the advisory locking worked, and we got the expected result, 160, in the balance data file.
In this article, we started by understanding the interceding update problem. Then we discussed different types of file locking schemes in Linux systems.
We also learned the lslocks command to check locks in a system and the flock utility to implement advisory locking.
Finally, we saw two demonstrations. One helped us to understand the relationship between advisory locking and process cooperation, while the other showed us how advisory locking works.