Skip to content

Commit 4c2baec

Browse files
committed
Full Implementation
1 parent 7d63aa9 commit 4c2baec

File tree

4 files changed

+283
-3
lines changed

4 files changed

+283
-3
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
name: "tagged-release"
3+
4+
on:
5+
push:
6+
tags:
7+
- "v*"
8+
9+
jobs:
10+
tagged-release:
11+
name: "Tagged Release"
12+
runs-on: "ubuntu-latest"
13+
14+
steps:
15+
# ...
16+
- name: "Build & test"
17+
run: |
18+
echo "done!"
19+
20+
- uses: "marvinpinto/action-automatic-releases@latest"
21+
with:
22+
repo_token: "${{ secrets.GITHUB_TOKEN }}"
23+
prerelease: false
24+
files: |
25+
LICENSE
26+
LICENSE.txt
27+
*.jar

src/SimpleLockFiles.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module SimpleLockFiles
22

3-
# Write your package code here.
3+
include("core.jl")
4+
5+
export lock_path, rand_lkid
46

57
end

src/core.jl

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# ----------------------------------------------------------------------
2+
# type
3+
struct SimpleLockFile
4+
path::String
5+
end
6+
7+
SimpleLockFile() = SimpleLockFile(tempname())
8+
9+
lock_path(slf::SimpleLockFile) = slf.path
10+
11+
# ----------------------------------------------------------------------
12+
# Base
13+
import Base.isfile
14+
isfile(slf::SimpleLockFile) = isfile(lock_path(slf))
15+
16+
import Base.rm
17+
rm(slf::SimpleLockFile; kwargs...) = rm(lock_path(slf); kwargs...)
18+
19+
# ----------------------------------------------------------------------
20+
# id
21+
const _lock_path_SEP = ","
22+
23+
_validate_id(lock_id::String) =
24+
contains(lock_id, _lock_path_SEP) &&
25+
error("Separator '", _lock_path_SEP, "' found in the lock id")
26+
27+
const _LOCK_ID_DICT = ['a':'z'; 'A':'Z'; '0':'9']
28+
rand_lkid(n = 10) = join(rand(_LOCK_ID_DICT, n))
29+
30+
# ----------------------------------------------------------------------
31+
# write
32+
33+
const _LOCK_DFT_TIME_OUT = 0.0
34+
const _LOCK_DFT_WAIT_TIME = 1.0
35+
const _LOCK_DFT_VALID_TIME = 30.0
36+
37+
function _write_lock_file(lf::String;
38+
lkid::String = rand_lkid(),
39+
vtime::Float64 = _LOCK_DFT_VALID_TIME
40+
)
41+
ttag = time() + vtime
42+
write(lf, string(lkid, _lock_path_SEP, ttag))
43+
return (lkid, ttag)
44+
end
45+
46+
write_lock_file(slf::SimpleLockFile; kwargs...) =
47+
_write_lock_file(lock_path(slf); kwargs...)
48+
49+
# ----------------------------------------------------------------------
50+
# read
51+
52+
function _read_lock_file(lf::String)
53+
!isfile(lf) && return ("", -1.0)
54+
txt = read(lf, String)
55+
spt = split(txt, _lock_path_SEP)
56+
length(spt) != 2 && return ("", -1.0)
57+
lkid = spt[1]
58+
ttag = tryparse(Float64, spt[2])
59+
ttag = isnothing(ttag) ? -1.0 : ttag
60+
return (lkid, ttag)
61+
end
62+
read_lock_file(slf::SimpleLockFile) = _read_lock_file(lock_path(slf))
63+
64+
# ----------------------------------------------------------------------
65+
# has lock
66+
67+
_is_valid_ttag(ttag) = ttag > time()
68+
69+
function has_lock(lf::String, lkid::String)
70+
71+
# read
72+
curr_lid, ttag = _read_lock_file(lf)
73+
74+
# del if invalid
75+
if !_is_valid_ttag(ttag)
76+
rm(lf; force = true)
77+
return false
78+
end
79+
80+
# test
81+
return lkid == curr_lid
82+
end
83+
84+
has_lock(slf::SimpleLockFile, lkid::String) = has_lock(lock_path(slf), lkid)
85+
86+
# ----------------------------------------------------------------------
87+
# release
88+
89+
function release_lock(lf::String, lkid::String)
90+
!isfile(lf) && return false
91+
!has_lock(lf, lkid) && return false
92+
isfile(lf) && rm(lf; force = true)
93+
return true
94+
end
95+
96+
release_lock(slf::SimpleLockFile, lkid::String) = release_lock(lock_path(slf), lkid)
97+
98+
# ----------------------------------------------------------------------
99+
# acquire
100+
101+
function _acquire(lf::String, lkid::String = rand_lkid();
102+
vtime = _LOCK_DFT_VALID_TIME
103+
)
104+
if isfile(lf)
105+
curr_lid, ttag = _read_lock_file(lf)
106+
107+
# check if is taken
108+
if _is_valid_ttag(ttag)
109+
110+
if curr_lid == lkid
111+
return (curr_lid, ttag) # is mine
112+
else
113+
return ("", ttag) # is taken
114+
end
115+
else
116+
# del if invalid
117+
rm(lf; force = true)
118+
end
119+
end
120+
return _write_lock_file(lf; lkid, vtime)
121+
end
122+
123+
function acquire(lf::String, lkid::String = rand_lkid();
124+
vtime = _LOCK_DFT_VALID_TIME,
125+
wt = _LOCK_DFT_WAIT_TIME,
126+
tout = _LOCK_DFT_TIME_OUT,
127+
force = false
128+
)
129+
if tout > 0.0
130+
t0 = time()
131+
while true
132+
lkid0, ttag = _acquire(lf, lkid; vtime)
133+
!isempty(lkid0) && return (lkid, ttag)
134+
if (time() - t0) > tout
135+
if force
136+
rm(lf; force)
137+
return _acquire(lf, lkid; vtime)
138+
else
139+
return ("", ttag)
140+
end
141+
end
142+
sleep(wt)
143+
end
144+
else
145+
force && rm(lf; force)
146+
return _acquire(lf, lkid; vtime)
147+
end
148+
end
149+
150+
acquire(slf::SimpleLockFile, lkid::String = rand_lkid(); kwargs...) = acquire(lock_path(slf), lkid)
151+
152+
# ----------------------------------------------------------------------
153+
# Base.lock
154+
155+
import Base.lock
156+
"""
157+
lock(f::Function, slf::SimpleLockFile, lkid::String = rand_lkid();
158+
vtime = $(_LOCK_DFT_VALID_TIME),
159+
wt = $(_LOCK_DFT_WAIT_TIME),
160+
tout = $(_LOCK_DFT_TIME_OUT),
161+
force = false
162+
)
163+
164+
Acquire the lock, execute `f()` with the lock held, and release the lock when f returns.
165+
If the lock is already locked by a different `lkid`, wait (till `tout`) for it to become available.
166+
During waiting, it will sleep `wt` seconds before re-attemping to acquire.
167+
If `force = true` it will acquire the lock after `tout`.
168+
This method is not fully secure to race, but it must be ok for sllow applications.
169+
Returns `true` if the lock
170+
171+
"""
172+
function lock(
173+
f::Function, slf::SimpleLockFile, lkid::String = rand_lkid();
174+
vtime = _LOCK_DFT_VALID_TIME,
175+
wt = _LOCK_DFT_WAIT_TIME,
176+
tout = _LOCK_DFT_TIME_OUT,
177+
force = false
178+
)
179+
180+
lf = lock_path(slf)
181+
try
182+
acquire(lf, lkid; force, vtime, wt, tout)
183+
f()
184+
finally
185+
ok_flag = has_lock(lf, lkid)
186+
release_lock(lf, lkid)
187+
return ok_flag
188+
end
189+
190+
end

test/runtests.jl

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,67 @@
11
using SimpleLockFiles
2+
const SLF = SimpleLockFiles
23
using Test
34

4-
@testset "SimpleLockFiles.jl" begin
5-
# Write your tests here.
5+
@testset "SLF.jl" begin
6+
7+
lkfn = tempname()
8+
slf = SLF.SimpleLockFile(lkfn)
9+
10+
@test SLF.lock_path(slf) == lkfn
11+
12+
# Test write and read
13+
lid1, ttag1 = SLF.write_lock_file(slf)
14+
lid2, ttag2 = SLF.read_lock_file(slf)
15+
16+
@test lid1 == lid2
17+
@test ttag1 == ttag2
18+
19+
# Test valid period
20+
vtime = 3.0
21+
lid3, ttag3 = SLF.write_lock_file(slf; vtime)
22+
@test !isempty(lid3)
23+
@test ttag3 > time()
24+
@test isfile(slf)
25+
26+
@test SLF.has_lock(slf, lid3)
27+
28+
lid4, ttag4 = SLF.acquire(slf) # This must be taken
29+
@test isempty(lid4)
30+
@test ttag3 == ttag4
31+
32+
sleep(2 * vtime) # expire lock
33+
34+
@test !SLF.has_lock(slf, lid3)
35+
@test !isfile(slf) # has_lock must delete an invalid lock file
36+
37+
vtime = 50.0
38+
lid4, ttag4 = SLF.acquire(slf; vtime) # This must be free
39+
@test lid4 != lid3
40+
@test ttag4 > ttag3
41+
@test isfile(slf)
42+
43+
# test wait
44+
lid5, ttag5 = SLF.acquire(slf; tout = 2.0) # This must fail
45+
@test isempty(lid5)
46+
@test ttag4 == ttag5
47+
48+
# Test release
49+
@test SLF.has_lock(slf, lid4)
50+
@test SLF.release_lock(slf, lid4)
51+
@test !SLF.has_lock(slf, lid4)
52+
@test !isfile(slf)
53+
54+
# base.lock
55+
lock(slf; vtime = 3.0) do
56+
# all this time the lock is taken
57+
for it in 1:10
58+
@test !SLF.has_lock(slf, "Not a lock id")
59+
sleep(0.2)
60+
end
61+
end
62+
@test !isfile(slf) # released!
63+
64+
# clear
65+
rm(slf; force = true)
66+
667
end

0 commit comments

Comments
 (0)