Skip to content

Commit 77c8c2d

Browse files
Ensure polling thread is resilient to errors and exceptions (#60)
* Ensure polling thread is resilient to API errors * Ensure polling thread is resilient to request exceptions * Use proper logger * Remove intermediate mocks * Prefer mocker fixture * Remove unecessary return_value with side_effect --------- Co-authored-by: Kim Gustyr <[email protected]>
1 parent d334564 commit 77c8c2d

File tree

2 files changed

+46
-1
lines changed

2 files changed

+46
-1
lines changed

flagsmith/polling_manager.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import logging
12
import threading
23
import time
34
import typing
45

6+
import requests
7+
8+
from flagsmith.exceptions import FlagsmithAPIError
9+
510
if typing.TYPE_CHECKING:
611
from flagsmith import Flagsmith
712

13+
logger = logging.getLogger(__name__)
14+
815

916
class EnvironmentDataPollingManager(threading.Thread):
1017
def __init__(
@@ -21,7 +28,10 @@ def __init__(
2128

2229
def run(self) -> None:
2330
while not self._stop_event.is_set():
24-
self.main.update_environment()
31+
try:
32+
self.main.update_environment()
33+
except (FlagsmithAPIError, requests.RequestException):
34+
logger.exception("Failed to update environment")
2535
time.sleep(self.refresh_interval_seconds)
2636

2737
def stop(self) -> None:

tests/test_polling_manager.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import time
22
from unittest import mock
33

4+
import requests
5+
6+
from flagsmith import Flagsmith
47
from flagsmith.polling_manager import EnvironmentDataPollingManager
58

69

@@ -35,3 +38,35 @@ def test_polling_manager_calls_update_environment_on_each_refresh():
3538
# for each subsequent refresh
3639
assert flagsmith.update_environment.call_count == 3
3740
polling_manager.stop()
41+
42+
43+
def test_polling_manager_is_resilient_to_api_errors(mocker, server_api_key):
44+
# Given
45+
session_mock = mocker.patch("requests.Session")
46+
session_mock.get.return_value = mock.MagicMock(status_code=500)
47+
flagsmith = Flagsmith(
48+
environment_key=server_api_key,
49+
enable_local_evaluation=True,
50+
environment_refresh_interval_seconds=0.1,
51+
)
52+
polling_manager = flagsmith.environment_data_polling_manager_thread
53+
54+
# Then
55+
assert polling_manager.is_alive()
56+
polling_manager.stop()
57+
58+
59+
def test_polling_manager_is_resilient_to_request_exceptions(mocker, server_api_key):
60+
# Given
61+
session_mock = mocker.patch("requests.Session")
62+
session_mock.get.side_effect = requests.RequestException()
63+
flagsmith = Flagsmith(
64+
environment_key=server_api_key,
65+
enable_local_evaluation=True,
66+
environment_refresh_interval_seconds=0.1,
67+
)
68+
polling_manager = flagsmith.environment_data_polling_manager_thread
69+
70+
# Then
71+
assert polling_manager.is_alive()
72+
polling_manager.stop()

0 commit comments

Comments
 (0)