mirror of https://github.com/borgbackup/borg.git
locking code: extract timeout/sleep code into reusable TimeoutTimer class
This commit is contained in:
parent
e4c519b1e9
commit
2deb520e67
|
@ -19,6 +19,58 @@ def get_id():
|
|||
return hostname, pid, tid
|
||||
|
||||
|
||||
class TimeoutTimer:
|
||||
"""
|
||||
A timer for timeout checks (can also deal with no timeout, give timeout=None [default]).
|
||||
It can also compute and optionally execute a reasonable sleep time (e.g. to avoid
|
||||
polling too often or to support thread/process rescheduling).
|
||||
"""
|
||||
def __init__(self, timeout=None, sleep=None):
|
||||
"""
|
||||
Initialize a timer.
|
||||
|
||||
:param timeout: time out interval [s] or None (no timeout)
|
||||
:param sleep: sleep interval [s] (>= 0: do sleep call, <0: don't call sleep)
|
||||
or None (autocompute: use 10% of timeout, or 1s for no timeout)
|
||||
"""
|
||||
if timeout is not None and timeout < 0:
|
||||
raise ValueError("timeout must be >= 0")
|
||||
self.timeout_interval = timeout
|
||||
if sleep is None:
|
||||
if timeout is None:
|
||||
sleep = 1.0
|
||||
else:
|
||||
sleep = timeout / 10.0
|
||||
self.sleep_interval = sleep
|
||||
self.start_time = None
|
||||
self.end_time = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: start=%r end=%r timeout=%r sleep=%r>" % (
|
||||
self.__class__.__name__, self.start_time, self.end_time,
|
||||
self.timeout_interval, self.sleep_interval)
|
||||
|
||||
def start(self):
|
||||
self.start_time = time.time()
|
||||
if self.timeout_interval is not None:
|
||||
self.end_time = self.start_time + self.timeout_interval
|
||||
return self
|
||||
|
||||
def sleep(self):
|
||||
if self.sleep_interval >= 0:
|
||||
time.sleep(self.sleep_interval)
|
||||
|
||||
def timed_out(self):
|
||||
return self.end_time is not None and time.time() >= self.end_time
|
||||
|
||||
def timed_out_or_sleep(self):
|
||||
if self.timed_out():
|
||||
return True
|
||||
else:
|
||||
self.sleep()
|
||||
return False
|
||||
|
||||
|
||||
class ExclusiveLock:
|
||||
"""An exclusive Lock based on mkdir fs operation being atomic"""
|
||||
class LockError(Error):
|
||||
|
@ -55,23 +107,12 @@ class ExclusiveLock:
|
|||
def __repr__(self):
|
||||
return "<%s: %r>" % (self.__class__.__name__, self.unique_name)
|
||||
|
||||
def _get_timing(self, timeout, sleep):
|
||||
def acquire(self, timeout=None, sleep=None):
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
start = end = time.time()
|
||||
if timeout is not None and timeout > 0:
|
||||
end += timeout
|
||||
if sleep is None:
|
||||
sleep = self.sleep
|
||||
if sleep is None:
|
||||
if timeout is None:
|
||||
sleep = 1.0
|
||||
else:
|
||||
sleep = max(0, timeout / 10.0)
|
||||
return start, sleep, end, timeout
|
||||
|
||||
def acquire(self, timeout=None, sleep=None):
|
||||
start, sleep, end, timeout = self._get_timing(timeout, sleep)
|
||||
timer = TimeoutTimer(timeout, sleep).start()
|
||||
while True:
|
||||
try:
|
||||
os.mkdir(self.path)
|
||||
|
@ -79,9 +120,8 @@ class ExclusiveLock:
|
|||
if err.errno == errno.EEXIST: # already locked
|
||||
if self.by_me():
|
||||
return self
|
||||
if timeout is not None and time.time() > end:
|
||||
if timer.timed_out_or_sleep():
|
||||
raise self.LockTimeout(self.path)
|
||||
time.sleep(sleep)
|
||||
else:
|
||||
raise self.LockFailed(self.path, str(err))
|
||||
else:
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from ..locking import get_id, ExclusiveLock, UpgradableLock, LockRoster, ADD, REMOVE, SHARED, EXCLUSIVE
|
||||
from ..locking import get_id, TimeoutTimer, ExclusiveLock , UpgradableLock, LockRoster, ADD, REMOVE, SHARED, EXCLUSIVE
|
||||
|
||||
|
||||
ID1 = "foo", 1, 1
|
||||
|
@ -15,6 +17,23 @@ def test_id():
|
|||
assert pid > 0
|
||||
|
||||
|
||||
class TestTimeoutTimer:
|
||||
def test_timeout(self):
|
||||
timeout = 0.5
|
||||
t = TimeoutTimer(timeout).start()
|
||||
assert not t.timed_out()
|
||||
time.sleep(timeout * 1.5)
|
||||
assert t.timed_out()
|
||||
|
||||
def test_notimeout_sleep(self):
|
||||
timeout, sleep = None, 0.5
|
||||
t = TimeoutTimer(timeout, sleep).start()
|
||||
assert not t.timed_out_or_sleep()
|
||||
assert time.time() >= t.start_time + 1 * sleep
|
||||
assert not t.timed_out_or_sleep()
|
||||
assert time.time() >= t.start_time + 2 * sleep
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def lockpath(tmpdir):
|
||||
return str(tmpdir.join('lock'))
|
||||
|
|
Loading…
Reference in New Issue