Skip to content

Commit a460156

Browse files
committed
fix checking for running sync version in coroutine in case when event loop is not set
1 parent 2ac0b99 commit a460156

File tree

3 files changed

+139
-14
lines changed

3 files changed

+139
-14
lines changed

backoff/_decorator.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,19 @@ def decorate(target):
6262
else:
6363
# Verify that sync version is not being run from coroutine
6464
# (that would lead to event loop hiccups).
65-
if asyncio.Task.current_task() is not None:
66-
raise TypeError(
67-
"backoff.on_predicate applied to a regular function "
68-
"inside coroutine, this will lead to event loop "
69-
"hiccups. "
70-
"Use backoff.on_predicate on coroutines in "
71-
"asynchronous code.")
65+
try:
66+
asyncio.get_event_loop()
67+
except RuntimeError:
68+
# Event loop not set for this thread.
69+
pass
70+
else:
71+
if asyncio.Task.current_task() is not None:
72+
raise TypeError(
73+
"backoff.on_predicate applied to a regular "
74+
"function inside coroutine, this will lead "
75+
"to event loop hiccups. "
76+
"Use backoff.on_predicate on coroutines in "
77+
"asynchronous code.")
7278

7379
if retry is None:
7480
retry = _sync.retry_predicate
@@ -138,13 +144,19 @@ def decorate(target):
138144
else:
139145
# Verify that sync version is not being run from coroutine
140146
# (that would lead to event loop hiccups).
141-
if asyncio.Task.current_task() is not None:
142-
raise TypeError(
143-
"backoff.on_exception applied to a regular function "
144-
"inside coroutine, this will lead to event loop "
145-
"hiccups. "
146-
"Use backoff.on_exception on coroutines in "
147-
"asynchronous code.")
147+
try:
148+
asyncio.get_event_loop()
149+
except RuntimeError:
150+
# Event loop not set for this thread.
151+
pass
152+
else:
153+
if asyncio.Task.current_task() is not None:
154+
raise TypeError(
155+
"backoff.on_exception applied to a regular "
156+
"function inside coroutine, this will lead "
157+
"to event loop hiccups. "
158+
"Use backoff.on_exception on coroutines in "
159+
"asynchronous code.")
148160

149161
if retry is None:
150162
retry = _sync.retry_exception

tests/python34/test_backoff_async.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,3 +573,52 @@ def regular_func():
573573
pass
574574

575575
assert "applied to a regular function" in str(excinfo.value)
576+
577+
578+
def test_on_predicate_on_regular_function_without_event_loop(monkeypatch):
579+
monkeypatch.setattr('time.sleep', lambda x: None)
580+
581+
# Set default event loop to None.
582+
loop = asyncio.get_event_loop()
583+
asyncio.set_event_loop(None)
584+
585+
try:
586+
@backoff.on_predicate(backoff.expo)
587+
def return_true(log, n):
588+
val = (len(log) == n - 1)
589+
log.append(val)
590+
return val
591+
592+
log = []
593+
ret = return_true(log, 3)
594+
assert ret is True
595+
assert 3 == len(log)
596+
597+
finally:
598+
# Restore event loop.
599+
asyncio.set_event_loop(loop)
600+
601+
602+
def test_on_exception_on_regular_function_without_event_loop(monkeypatch):
603+
monkeypatch.setattr('time.sleep', lambda x: None)
604+
605+
# Set default event loop to None.
606+
loop = asyncio.get_event_loop()
607+
asyncio.set_event_loop(None)
608+
609+
try:
610+
@backoff.on_exception(backoff.expo, KeyError)
611+
def keyerror_then_true(log, n):
612+
if len(log) == n:
613+
return True
614+
e = KeyError()
615+
log.append(e)
616+
raise e
617+
618+
log = []
619+
assert keyerror_then_true(log, 3) is True
620+
assert 3 == len(log)
621+
622+
finally:
623+
# Restore event loop.
624+
asyncio.set_event_loop(loop)

tests/test_backoff.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import backoff
44
import pytest
55
import random
6+
import threading
67

78
from tests.common import _log_hdlrs, _save_target
89

@@ -465,3 +466,66 @@ def exceptor():
465466

466467
with pytest.raises(ValueError):
467468
exceptor()
469+
470+
471+
def test_on_predicate_in_thread(monkeypatch):
472+
monkeypatch.setattr('time.sleep', lambda x: None)
473+
474+
result = []
475+
476+
def check():
477+
try:
478+
@backoff.on_predicate(backoff.expo)
479+
def return_true(log, n):
480+
val = (len(log) == n - 1)
481+
log.append(val)
482+
return val
483+
484+
log = []
485+
ret = return_true(log, 3)
486+
assert ret is True
487+
assert 3 == len(log)
488+
489+
except Exception as ex:
490+
result.append(ex)
491+
else:
492+
result.append('success')
493+
494+
t = threading.Thread(target=check)
495+
t.start()
496+
t.join()
497+
498+
assert len(result) == 1
499+
assert result[0] == 'success'
500+
501+
502+
def test_on_exception_in_thread(monkeypatch):
503+
monkeypatch.setattr('time.sleep', lambda x: None)
504+
505+
result = []
506+
507+
def check():
508+
try:
509+
@backoff.on_exception(backoff.expo, KeyError)
510+
def keyerror_then_true(log, n):
511+
if len(log) == n:
512+
return True
513+
e = KeyError()
514+
log.append(e)
515+
raise e
516+
517+
log = []
518+
assert keyerror_then_true(log, 3) is True
519+
assert 3 == len(log)
520+
521+
except Exception as ex:
522+
result.append(ex)
523+
else:
524+
result.append('success')
525+
526+
t = threading.Thread(target=check)
527+
t.start()
528+
t.join()
529+
530+
assert len(result) == 1
531+
assert result[0] == 'success'

0 commit comments

Comments
 (0)