|
@@ -1,22 +1,25 @@
|
|
|
+import random
|
|
|
import time
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
-from ..locking import get_id, TimeoutTimer, ExclusiveLock, Lock, LockRoster, \
|
|
|
- ADD, REMOVE, SHARED, EXCLUSIVE, LockTimeout
|
|
|
-
|
|
|
+from ..platform import get_process_id, process_alive
|
|
|
+from ..locking import TimeoutTimer, ExclusiveLock, Lock, LockRoster, \
|
|
|
+ ADD, REMOVE, SHARED, EXCLUSIVE, LockTimeout, NotLocked, NotMyLock
|
|
|
|
|
|
ID1 = "foo", 1, 1
|
|
|
ID2 = "bar", 2, 2
|
|
|
|
|
|
|
|
|
-def test_id():
|
|
|
- hostname, pid, tid = get_id()
|
|
|
- assert isinstance(hostname, str)
|
|
|
- assert isinstance(pid, int)
|
|
|
- assert isinstance(tid, int)
|
|
|
- assert len(hostname) > 0
|
|
|
- assert pid > 0
|
|
|
+@pytest.fixture()
|
|
|
+def free_pid():
|
|
|
+ """Return a free PID not used by any process (naturally this is racy)"""
|
|
|
+ host, pid, tid = get_process_id()
|
|
|
+ while True:
|
|
|
+ # PIDs are often restricted to a small range. On Linux the range >32k is by default not used.
|
|
|
+ pid = random.randint(33000, 65000)
|
|
|
+ if not process_alive(host, pid, tid):
|
|
|
+ return pid
|
|
|
|
|
|
|
|
|
class TestTimeoutTimer:
|
|
@@ -57,6 +60,22 @@ class TestExclusiveLock:
|
|
|
with pytest.raises(LockTimeout):
|
|
|
ExclusiveLock(lockpath, id=ID2, timeout=0.1).acquire()
|
|
|
|
|
|
+ def test_kill_stale(self, lockpath, free_pid):
|
|
|
+ host, pid, tid = our_id = get_process_id()
|
|
|
+ dead_id = host, free_pid, tid
|
|
|
+ cant_know_if_dead_id = 'foo.bar.example.net', 1, 2
|
|
|
+
|
|
|
+ dead_lock = ExclusiveLock(lockpath, id=dead_id).acquire()
|
|
|
+ with ExclusiveLock(lockpath, id=our_id, kill_stale_locks=True):
|
|
|
+ with pytest.raises(NotMyLock):
|
|
|
+ dead_lock.release()
|
|
|
+ with pytest.raises(NotLocked):
|
|
|
+ dead_lock.release()
|
|
|
+
|
|
|
+ with ExclusiveLock(lockpath, id=cant_know_if_dead_id):
|
|
|
+ with pytest.raises(LockTimeout):
|
|
|
+ ExclusiveLock(lockpath, id=our_id, kill_stale_locks=True, timeout=0.1).acquire()
|
|
|
+
|
|
|
|
|
|
class TestLock:
|
|
|
def test_shared(self, lockpath):
|
|
@@ -117,6 +136,25 @@ class TestLock:
|
|
|
with pytest.raises(LockTimeout):
|
|
|
Lock(lockpath, exclusive=True, id=ID2, timeout=0.1).acquire()
|
|
|
|
|
|
+ def test_kill_stale(self, lockpath, free_pid):
|
|
|
+ host, pid, tid = our_id = get_process_id()
|
|
|
+ dead_id = host, free_pid, tid
|
|
|
+ cant_know_if_dead_id = 'foo.bar.example.net', 1, 2
|
|
|
+
|
|
|
+ dead_lock = Lock(lockpath, id=dead_id, exclusive=True).acquire()
|
|
|
+ roster = dead_lock._roster
|
|
|
+ with Lock(lockpath, id=our_id, kill_stale_locks=True):
|
|
|
+ assert roster.get(EXCLUSIVE) == set()
|
|
|
+ assert roster.get(SHARED) == {our_id}
|
|
|
+ assert roster.get(EXCLUSIVE) == set()
|
|
|
+ assert roster.get(SHARED) == set()
|
|
|
+ with pytest.raises(KeyError):
|
|
|
+ dead_lock.release()
|
|
|
+
|
|
|
+ with Lock(lockpath, id=cant_know_if_dead_id, exclusive=True):
|
|
|
+ with pytest.raises(LockTimeout):
|
|
|
+ Lock(lockpath, id=our_id, kill_stale_locks=True, timeout=0.1).acquire()
|
|
|
+
|
|
|
|
|
|
@pytest.fixture()
|
|
|
def rosterpath(tmpdir):
|
|
@@ -144,3 +182,28 @@ class TestLockRoster:
|
|
|
roster2 = LockRoster(rosterpath, id=ID2)
|
|
|
roster2.modify(SHARED, REMOVE)
|
|
|
assert roster2.get(SHARED) == set()
|
|
|
+
|
|
|
+ def test_kill_stale(self, rosterpath, free_pid):
|
|
|
+ host, pid, tid = our_id = get_process_id()
|
|
|
+ dead_id = host, free_pid, tid
|
|
|
+
|
|
|
+ roster1 = LockRoster(rosterpath, id=dead_id)
|
|
|
+ assert roster1.get(SHARED) == set()
|
|
|
+ roster1.modify(SHARED, ADD)
|
|
|
+ assert roster1.get(SHARED) == {dead_id}
|
|
|
+
|
|
|
+ cant_know_if_dead_id = 'foo.bar.example.net', 1, 2
|
|
|
+ roster1 = LockRoster(rosterpath, id=cant_know_if_dead_id)
|
|
|
+ assert roster1.get(SHARED) == {dead_id}
|
|
|
+ roster1.modify(SHARED, ADD)
|
|
|
+ assert roster1.get(SHARED) == {dead_id, cant_know_if_dead_id}
|
|
|
+
|
|
|
+ killer_roster = LockRoster(rosterpath, kill_stale_locks=True)
|
|
|
+ # Did kill the dead processes lock (which was alive ... I guess?!)
|
|
|
+ assert killer_roster.get(SHARED) == {cant_know_if_dead_id}
|
|
|
+ killer_roster.modify(SHARED, ADD)
|
|
|
+ assert killer_roster.get(SHARED) == {our_id, cant_know_if_dead_id}
|
|
|
+
|
|
|
+ other_killer_roster = LockRoster(rosterpath, kill_stale_locks=True)
|
|
|
+ # Did not kill us, since we're alive
|
|
|
+ assert other_killer_roster.get(SHARED) == {our_id, cant_know_if_dead_id}
|