Skip to content
This repository was archived by the owner on Mar 13, 2022. It is now read-only.

Commit 060cac1

Browse files
authored
Merge pull request #227 from chrisayoub/fix_watch_bug
Fix bug with Watch and 410 retries
2 parents 04feb9f + ebea7e3 commit 060cac1

File tree

2 files changed

+58
-6
lines changed

2 files changed

+58
-6
lines changed

watch/watch.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ def stream(self, func, *args, **kwargs):
151151
if 'resource_version' in kwargs:
152152
self.resource_version = kwargs['resource_version']
153153

154-
timeouts = ('timeout_seconds' in kwargs)
154+
# Do not attempt retries if user specifies a timeout.
155+
# We want to ensure we are returning within that timeout.
156+
disable_retries = ('timeout_seconds' in kwargs)
155157
retry_after_410 = False
156158
while True:
157159
resp = func(*args, **kwargs)
@@ -164,9 +166,9 @@ def stream(self, func, *args, **kwargs):
164166
if isinstance(event, dict) \
165167
and event['type'] == 'ERROR':
166168
obj = event['raw_object']
167-
# Current request expired, let's retry,
169+
# Current request expired, let's retry, (if enabled)
168170
# but only if we have not already retried.
169-
if not retry_after_410 and \
171+
if not disable_retries and not retry_after_410 and \
170172
obj['code'] == HTTP_STATUS_GONE:
171173
retry_after_410 = True
172174
break
@@ -190,5 +192,5 @@ def stream(self, func, *args, **kwargs):
190192
else:
191193
self._stop = True
192194

193-
if timeouts or self._stop:
195+
if self._stop or disable_retries:
194196
break

watch/watch_test.py

+52-2
Original file line numberDiff line numberDiff line change
@@ -287,15 +287,65 @@ def test_watch_with_error_event(self):
287287
fake_api = Mock()
288288
fake_api.get_thing = Mock(return_value=fake_resp)
289289

290+
w = Watch()
291+
# No events are generated when no initial resourceVersion is passed
292+
# No retry is attempted either, preventing an ApiException
293+
assert not list(w.stream(fake_api.get_thing))
294+
295+
fake_api.get_thing.assert_called_once_with(
296+
_preload_content=False, watch=True)
297+
fake_resp.read_chunked.assert_called_once_with(decode_content=False)
298+
fake_resp.close.assert_called_once()
299+
fake_resp.release_conn.assert_called_once()
300+
301+
def test_watch_retries_on_error_event(self):
302+
fake_resp = Mock()
303+
fake_resp.close = Mock()
304+
fake_resp.release_conn = Mock()
305+
fake_resp.read_chunked = Mock(
306+
return_value=[
307+
'{"type": "ERROR", "object": {"code": 410, '
308+
'"reason": "Gone", "message": "error message"}}\n'])
309+
310+
fake_api = Mock()
311+
fake_api.get_thing = Mock(return_value=fake_resp)
312+
290313
w = Watch()
291314
try:
292-
for _ in w.stream(fake_api.get_thing):
315+
for _ in w.stream(fake_api.get_thing, resource_version=0):
316+
self.fail(self, "Should fail with ApiException.")
317+
except client.rest.ApiException:
318+
pass
319+
320+
# Two calls should be expected during a retry
321+
fake_api.get_thing.assert_has_calls(
322+
[call(resource_version=0, _preload_content=False, watch=True)] * 2)
323+
fake_resp.read_chunked.assert_has_calls(
324+
[call(decode_content=False)] * 2)
325+
assert fake_resp.close.call_count == 2
326+
assert fake_resp.release_conn.call_count == 2
327+
328+
def test_watch_with_error_event_and_timeout_param(self):
329+
fake_resp = Mock()
330+
fake_resp.close = Mock()
331+
fake_resp.release_conn = Mock()
332+
fake_resp.read_chunked = Mock(
333+
return_value=[
334+
'{"type": "ERROR", "object": {"code": 410, '
335+
'"reason": "Gone", "message": "error message"}}\n'])
336+
337+
fake_api = Mock()
338+
fake_api.get_thing = Mock(return_value=fake_resp)
339+
340+
w = Watch()
341+
try:
342+
for _ in w.stream(fake_api.get_thing, timeout_seconds=10):
293343
self.fail(self, "Should fail with ApiException.")
294344
except client.rest.ApiException:
295345
pass
296346

297347
fake_api.get_thing.assert_called_once_with(
298-
_preload_content=False, watch=True)
348+
_preload_content=False, watch=True, timeout_seconds=10)
299349
fake_resp.read_chunked.assert_called_once_with(decode_content=False)
300350
fake_resp.close.assert_called_once()
301351
fake_resp.release_conn.assert_called_once()

0 commit comments

Comments
 (0)