Skip to content

Commit d5fffaa

Browse files
yuuki3655kyuridenamida
authored andcommitted
Makes gen command retry with exponential backoff (kyuridenamida#174)
Previously, gen command retries with fixed delay of 1.5 seconds indefinitely. It could hurt the service especially when they are experiencing overloaded traffic. This patch changes the retry strategy with exponential backoff starting from 1.5 seconds to 60 seconds at maximum. The gen commands aborts by EnvironmentInitializationError exception after 10 attempts.
1 parent 3c1e33e commit d5fffaa

File tree

2 files changed

+50
-6
lines changed

2 files changed

+50
-6
lines changed

atcodertools/tools/envgen.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66
import traceback
77
from multiprocessing import Pool, cpu_count
8-
from time import sleep
8+
import time
99
from typing import Tuple
1010

1111
from colorama import Fore
@@ -34,6 +34,10 @@ class BannedFileDetectedError(Exception):
3434
pass
3535

3636

37+
class EnvironmentInitializationError(Exception):
38+
pass
39+
40+
3741
def output_splitter():
3842
# for readability
3943
print("=================================================", file=sys.stderr)
@@ -165,16 +169,23 @@ def func(argv: Tuple[AtCoderClient, Problem, Config]):
165169

166170
def prepare_contest(atcoder_client: AtCoderClient,
167171
contest_id: str,
168-
config: Config):
169-
retry_duration = 1.5
172+
config: Config,
173+
retry_delay_secs: float = 1.5,
174+
retry_max_delay_secs: float = 60,
175+
retry_max_tries: int = 10):
176+
attempt_count = 1
170177
while True:
171178
problem_list = atcoder_client.download_problem_list(
172179
Contest(contest_id=contest_id))
173180
if problem_list:
174181
break
175-
sleep(retry_duration)
182+
if 0 < retry_max_tries < attempt_count:
183+
raise EnvironmentInitializationError
176184
logger.warning(
177-
"Failed to fetch. Will retry in {} seconds".format(retry_duration))
185+
"Failed to fetch. Will retry in {} seconds. (Attempt {})".format(retry_delay_secs, attempt_count))
186+
time.sleep(retry_delay_secs)
187+
retry_delay_secs = min(retry_delay_secs * 2, retry_max_delay_secs)
188+
attempt_count += 1
178189

179190
tasks = [(atcoder_client,
180191
problem,

tests/test_envgen.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
import shutil
33
import tempfile
44
import unittest
5+
from unittest import mock
56
from os.path import relpath
67
from logging import getLogger
78

89
from atcodertools.client.atcoder import AtCoderClient
910
from atcodertools.codegen.code_style_config import CodeStyleConfig
1011
from atcodertools.config.config import Config
1112
from atcodertools.config.etc_config import EtcConfig
12-
from atcodertools.tools.envgen import prepare_contest, main
13+
from atcodertools.tools.envgen import prepare_contest, main, EnvironmentInitializationError
1314

1415
logger = getLogger(__name__)
1516

@@ -76,6 +77,38 @@ def test_backup(self):
7677
)
7778
self.assertDirectoriesEqual(answer_data_dir_path, self.temp_dir)
7879

80+
@mock.patch('time.sleep')
81+
def test_prepare_contest_aborts_after_max_retry_attempts(self, mock_sleep):
82+
mock_client = mock.Mock(spec=AtCoderClient)
83+
mock_client.download_problem_list.return_value = []
84+
self.assertRaises(
85+
EnvironmentInitializationError,
86+
prepare_contest,
87+
mock_client,
88+
"agc029",
89+
Config(
90+
code_style_config=CodeStyleConfig(
91+
workspace_dir=self.temp_dir,
92+
template_file=TEMPLATE_PATH,
93+
lang="cpp",
94+
),
95+
etc_config=EtcConfig(
96+
in_example_format="input_{}.txt",
97+
out_example_format="output_{}.txt"
98+
))
99+
)
100+
self.assertEqual(mock_sleep.call_count, 10)
101+
mock_sleep.assert_has_calls([mock.call(1.5),
102+
mock.call(3.0),
103+
mock.call(6.0),
104+
mock.call(12.0),
105+
mock.call(24.0),
106+
mock.call(48.0),
107+
mock.call(60.0),
108+
mock.call(60.0),
109+
mock.call(60.0),
110+
mock.call(60.0)])
111+
79112
def assertDirectoriesEqual(self, expected_dir_path, dir_path):
80113
files1 = get_all_rel_file_paths(expected_dir_path)
81114
files2 = get_all_rel_file_paths(dir_path)

0 commit comments

Comments
 (0)