Skip to content

Commit ce7dedc

Browse files
committed
lockable_resources: use abstract Retry class in API
1 parent 7fe6c25 commit ce7dedc

File tree

2 files changed

+63
-29
lines changed

2 files changed

+63
-29
lines changed

jenkinsapi/lockable_resources.py

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
import time
32
from typing import (
43
TYPE_CHECKING,
54
Dict,
@@ -15,6 +14,7 @@
1514
from jenkinsapi.custom_exceptions import JenkinsAPIException
1615
from jenkinsapi.jenkinsbase import JenkinsBase
1716
from jenkinsapi.utils.requester import Requester
17+
from jenkinsapi.utils.retry import Retry, SimpleRetry
1818

1919
if TYPE_CHECKING:
2020
from jenkinsapi.jenkins import Jenkins
@@ -226,67 +226,59 @@ def try_reserve(
226226
def wait_reserve(
227227
self,
228228
selector: "ResourceSelector",
229-
sleep_period: float = DEFAULT_WAIT_SLEEP_PERIOD,
230-
timeout: float = DEFAULT_WAIT_TIMEOUT_PERIOD,
229+
retry: Optional[Retry] = None,
231230
) -> str:
232231
"""
233232
Reserve a resource that matches the given condition
234233
235234
:return: the name of the reserved resource
236235
:raise TimeoutError: if no resource could be reserved
237236
"""
238-
start_time = time.time()
237+
if retry is None:
238+
retry = SimpleRetry(
239+
sleep_period=self.DEFAULT_WAIT_SLEEP_PERIOD,
240+
timeout=self.DEFAULT_WAIT_TIMEOUT_PERIOD,
241+
)
242+
retry.begin()
239243
while True:
240244
result = self.try_reserve(selector)
241245
if result is not None:
242246
return result
243-
if time.time() - start_time > timeout:
244-
raise TimeoutError("Timed out waiting for resource")
245-
logger.info(
246-
"No free resources matching %r, sleep %.3f seconds",
247-
selector,
248-
sleep_period,
249-
)
247+
retry.check()
248+
logger.info("No free resources matching %r, retry", selector)
250249
self.poll()
251-
time.sleep(sleep_period)
252250

253251
def reservation_by_label(
254252
self,
255253
label: str,
256-
sleep_period: float = DEFAULT_WAIT_SLEEP_PERIOD,
257-
timeout: float = DEFAULT_WAIT_TIMEOUT_PERIOD,
254+
retry: Optional[Retry] = None,
258255
) -> "LockedResourceReservation":
259256
return LockedResourceReservation(
260257
self,
261258
ResourceLabelSelector(label),
262-
sleep_period=sleep_period,
263-
timeout=timeout,
259+
retry=retry,
264260
)
265261

266262
def reservation_by_name(
267263
self,
268264
name: str,
269-
sleep_period: float = DEFAULT_WAIT_SLEEP_PERIOD,
270-
timeout: float = DEFAULT_WAIT_TIMEOUT_PERIOD,
265+
retry: Optional[Retry] = None,
271266
) -> "LockedResourceReservation":
272267
return LockedResourceReservation(
273268
self,
274269
ResourceNameSelector(name),
275-
sleep_period=sleep_period,
276-
timeout=timeout,
270+
retry=retry,
277271
)
278272

279273
def reservation_by_name_list(
280274
self,
281275
name_list: List[str],
282-
sleep_period: float = DEFAULT_WAIT_SLEEP_PERIOD,
283-
timeout: float = DEFAULT_WAIT_TIMEOUT_PERIOD,
276+
retry: Optional[Retry] = None,
284277
) -> "LockedResourceReservation":
285278
return LockedResourceReservation(
286279
self,
287280
ResourceNameListSelector(name_list),
288-
sleep_period=sleep_period,
289-
timeout=timeout,
281+
retry=retry,
290282
)
291283

292284

@@ -363,13 +355,11 @@ def __init__(
363355
self,
364356
api: LockableResources,
365357
selector: ResourceSelector,
366-
sleep_period: float = LockableResources.DEFAULT_WAIT_SLEEP_PERIOD,
367-
timeout: float = LockableResources.DEFAULT_WAIT_TIMEOUT_PERIOD,
358+
retry: Optional[Retry] = None,
368359
):
369360
self.api = api
370361
self.selector = selector
371-
self.sleep_period = sleep_period
372-
self.timeout = timeout
362+
self.retry = retry
373363

374364
@property
375365
def locked_resource_name(self) -> str:
@@ -387,7 +377,7 @@ def __enter__(self) -> "LockedResourceReservation":
387377
if self._locked_resource_name is not None:
388378
raise RuntimeError("Lock already acquired")
389379
self._locked_resource_name = self.api.wait_reserve(
390-
self.selector, sleep_period=self.sleep_period, timeout=self.timeout
380+
self.selector, retry=self.retry
391381
)
392382
self.locked = True
393383
return self

jenkinsapi_tests/systests/test_lockable_resources.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from contextlib import ExitStack
2+
from unittest import mock
13
import pytest
24

35
from jenkinsapi.jenkins import Jenkins
@@ -7,6 +9,7 @@
79
ResourceLockedError,
810
)
911
from jenkinsapi.utils.jenkins_launcher import JenkinsLancher
12+
from jenkinsapi.utils.retry import SimpleRetry
1013

1114
GROOVY_SCRIPT_INIT_TEST_RESOURCES = """
1215
import org.jenkins.plugins.lockableresources.*
@@ -158,3 +161,44 @@ def test_reservation_by_label(
158161
assert locked_resource.is_free() is False
159162
assert test_lock_label in locked_resource.data["labelsAsList"]
160163
assert locked_resource.is_free() is True
164+
165+
166+
def test_custom_retry(
167+
lockable_resources: LockableResources,
168+
test_lock_name: str,
169+
):
170+
with ExitStack() as exit_stack:
171+
exit_stack.enter_context(
172+
mock.patch(
173+
"time.monotonic",
174+
side_effect=range(1000, 10000),
175+
)
176+
)
177+
mock_time_sleep = exit_stack.enter_context(
178+
mock.patch("time.sleep"),
179+
)
180+
mock_try_reserve = exit_stack.enter_context(
181+
mock.patch.object(
182+
lockable_resources,
183+
"try_reserve",
184+
return_value=None,
185+
)
186+
)
187+
mock_poll = exit_stack.enter_context(
188+
mock.patch.object(
189+
lockable_resources,
190+
"poll",
191+
)
192+
)
193+
exit_stack.enter_context(pytest.raises(TimeoutError))
194+
with lockable_resources.reservation_by_name(
195+
test_lock_name,
196+
retry=SimpleRetry(
197+
sleep_period=1,
198+
timeout=5.5,
199+
),
200+
):
201+
pass
202+
assert mock_time_sleep.call_count == 5
203+
assert mock_try_reserve.call_count == 6
204+
assert mock_poll.call_count == 5

0 commit comments

Comments
 (0)