Recently I found myself needing a way to synchronize access to a file in a lua program. I’ve previously used flock
to ensure that multiple instances of my bash script couldn’t run at the same time.
I started by writing some bash scripts to test some assumptions main.sh
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#!/bin/bash
set -euo pipefail
# $$ - PID for this process
# $! - PID for last process started (eg. vim &; echo $!)
PID=$$
canRun=$(flock /tmp/mylock ./canRun.sh $PID)
echo "output: $canRun"
# -z means length of string is zero
if [[ -z $canRun ]]
then
echo "we can run"
else
echo "lock is held and valid"
exit 1
fi
# Do the work you came here to do
for i in $(seq 10); do
echo "$i"
sleep 1
done
# release the lock
flock /tmp/mylock rm /tmp/mylock
|
On line 9 above, flock command invokes canRun.sh
which is listed below
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#!/bin/bash
set -euo pipefail
PID=$1
lockContent=$(cat /tmp/mylock)
##
## NOTE: printing anything tells the calling program the lock is held
##
err(){
echo "-- $*" >>/dev/stderr
}
# -z means: string is null, that is, has zero length
# -n means: string is not null
if [[ -n $lockContent ]] && ps -p "$lockContent" > /dev/null
then
# found PID in file, and it is running
echo "fail"
else
# Either: empty lock file or the process is not running
# claiming lock for $PID
echo "$PID" > /tmp/mylock
fi
|
Here is the output when running a single instance:
1
2
3
4
5
6
7
8
9
10
11
12
|
$ ./main.sh
output:
we can run
1
2
3
4
5
6
7
8
9
|
If we attempt to run two instances:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
$ ./main.sh &; ./main.sh
[1] 1944233
output:
we can run
1
output: fail
lock is held and valid
$ 2
3
4
5
6
7
8
9
10
[1] + 1944233 done ./main.sh
|
If we start an instance, and kill it with ctrl+c we can start another instance without issue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
$ ./main.sh
output:
we can run
1
2
3
4
^C
$ ./main.sh
output:
we can run
1
2
3
4
^C
|
Now that we have a working example in bash, we can convert it to lua, here is a listing of main.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
#!/usr/bin/lua
local handle = io.popen("echo $PPID")
local PID = handle:read("*a")
handle:close()
handle = io.popen("flock /tmp/mylock ./canrun.lua "..PID)
local canRun = handle:read("*a")
handle:close()
if canRun == "" then
print "we can run"
else
print "lock is held and valid"
os.exit(1)
end
-- Do the work you came here to do
-------------------------------------------------------------------------------
-- Sample Code Start
-------------------------------------------------------------------------------
-- warning: clock can eventually wrap around for sufficiently large n
-- (whose value is platform dependent). Even for n == 1, clock() - t0
-- might become negative on the second that clock wraps
local clock = os.clock
local function sleep(n) -- seconds
local t0 = clock()
while clock() - t0 <= n do end
end
for i=1,20 do
print(i)
sleep(1)
end
-------------------------------------------------------------------------------
-- Sample Code End
-------------------------------------------------------------------------------
-- release the lock
os.execute('flock /tmp/mylock rm /tmp/mylock')
|
And the implementation of canrun.lua
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#!/usr/bin/lua
-------------------------------------------------------------------------------
-- http://lua-users.org/wiki/FileInputOutput
-- Read an entire file.
-- Use "a" in Lua 5.3; "*a" in Lua 5.1 and 5.2
local function readall(filename)
local fh = assert(io.open(filename, "rb"))
local contents = assert(fh:read(_VERSION <= "Lua 5.2" and "*a" or "a"))
fh:close()
return contents
end
-- Write a string to a file.
local function write(filename, contents)
local fh = assert(io.open(filename, "wb"))
fh:write(contents)
fh:flush()
fh:close()
end
-------------------------------------------------------------------------------
local PID=arg[1]
local lockPath='/tmp/mylock'
local lockContent=readall(lockPath)
--
-- NOTE: printing anything tells the calling program the lock is held
--
if lockContent ~= '' and os.execute('ps -p "'..lockContent..'" > /dev/null') == 0 then
-- found PID in file, and it is running
print("fail")
else
-- Either: empty lock file or the process is not running
-- claiming lock for $PID
write(lockPath, PID)
end
|