Skip to content

Commit 25d8efa

Browse files
committed
branch_worker: introduce can_do_sync() check
Github has a limit on the number of tokens used by an app, which is 5k/1h for a free-tier org [1]. In BPF CI due to number of pending PRs, we regularly get very close to hitting the limit (remaining < 500) and have relevant alerts set up. Introduce can_do_sync() method to BranchWorker that will check if there is enough remaining tokens to run through the sync_patches. *Enough* is defined by MIN_REMAINING_GITHUB_TOKENS, which is currently a constant for simplicity. In case the number of remaining tokens is too low, the sync_patches will simply be skipped for the worker. Effectively KPD will wait until github will refresh the limit without crashing. [1] https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#primary-rate-limit-for-authenticated-users Signed-off-by: Ihor Solodrai <[email protected]>
1 parent a3588e1 commit 25d8efa

File tree

3 files changed

+30
-7
lines changed

3 files changed

+30
-7
lines changed

kernel_patches_daemon/branch_worker.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,10 @@
3939

4040
from kernel_patches_daemon.config import (
4141
EmailConfig,
42-
SERIES_ID_SEPARATOR,
4342
SERIES_TARGET_SEPARATOR,
4443
)
4544
from kernel_patches_daemon.github_connector import GithubConnector
46-
from kernel_patches_daemon.github_logs import GithubFailedJobLog, GithubLogExtractor
45+
from kernel_patches_daemon.github_logs import GithubLogExtractor
4746
from kernel_patches_daemon.patchwork import Patchwork, Series, Subject
4847
from kernel_patches_daemon.stats import HistogramMetricTimer
4948
from kernel_patches_daemon.status import (
@@ -94,6 +93,12 @@
9493
MERGE_CONFLICT_LABEL = "merge-conflict"
9594
UPSTREAM_REMOTE_NAME = "upstream"
9695

96+
# We get 5k tokens per hour. When this value is checked, we should be
97+
# able to do at least one more sync loop. Depending on number of PRs,
98+
# one iteration can use more or less tokens, but generally BPF CI uses
99+
# 4k in 10-15 iterations, so 1k should be enough almost always.
100+
MIN_REMAINING_GITHUB_TOKENS = 1000
101+
97102
# fmt: off
98103
EMAIL_TEMPLATE_BASE: Final[str] = """\
99104
Dear patch submitter,
@@ -649,6 +654,12 @@ def update_e2e_test_branch_and_update_pr(
649654

650655
self._update_e2e_pr(title, base_branch, branch_name, pushed)
651656

657+
def can_do_sync(self) -> bool:
658+
github_ratelimit = self.git.get_rate_limit()
659+
if github_ratelimit.core.remaining < MIN_REMAINING_GITHUB_TOKENS:
660+
return False
661+
return True
662+
652663
def do_sync(self) -> None:
653664
# fetch most recent upstream
654665
if UPSTREAM_REMOTE_NAME in [x.name for x in self.repo_local.remotes]:

kernel_patches_daemon/github_sync.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
from kernel_patches_daemon.config import (
2626
BranchConfig,
2727
KPDConfig,
28-
SERIES_TARGET_SEPARATOR,
2928
)
3029
from kernel_patches_daemon.github_logs import (
3130
BpfGithubLogExtractor,
@@ -280,13 +279,22 @@ async def sync_patches(self) -> None:
280279
as separate commit
281280
"""
282281

282+
sync_workers = [
283+
(branch, worker)
284+
for (branch, worker) in self.workers.items()
285+
if worker.can_do_sync()
286+
]
287+
if not sync_workers:
288+
logger.warn("No branch workers that can_do_sync(), skipping sync_patches()")
289+
return
290+
283291
# sync mirror and fetch current states of PRs
284292
loop = asyncio.get_event_loop()
285293

286294
self.drop_counters()
287295
sync_start = time.time()
288296

289-
for branch, worker in self.workers.items():
297+
for branch, worker in sync_workers:
290298
logging.info(f"Refreshing repo info for {branch}.")
291299
await loop.run_in_executor(None, worker.fetch_repo_branch)
292300
await loop.run_in_executor(None, worker.get_pulls)
@@ -298,7 +306,7 @@ async def sync_patches(self) -> None:
298306
mirror_done = time.time()
299307

300308
with HistogramMetricTimer(patchwork_fetch_duration):
301-
for branch, worker in self.workers.items():
309+
for branch, worker in sync_workers:
302310
await loop.run_in_executor(
303311
None, worker.update_e2e_test_branch_and_update_pr, branch
304312
)
@@ -313,7 +321,7 @@ async def sync_patches(self) -> None:
313321

314322
# sync old subjects
315323
subject_names = {x.subject for x in self.subjects}
316-
for worker in self.workers.values():
324+
for _, worker in sync_workers:
317325
for subject_name, pr in worker.prs.items():
318326
if subject_name in subject_names:
319327
continue
@@ -359,7 +367,7 @@ async def sync_patches(self) -> None:
359367
self.set_counter("mirror_duration", mirror_done - sync_start)
360368
self.set_counter("pw_fetch_duration", pw_done - mirror_done)
361369
self.set_counter("patch_and_update_duration", patches_done - pw_done)
362-
for worker in self.workers.values():
370+
for _, worker in sync_workers:
363371
for pr in worker.prs.values():
364372
if worker._is_relevant_pr(pr):
365373
self.increment_counter("prs_total")

tests/test_github_sync.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ def setUp(self) -> None:
8585
self.addCleanup(patcher.stop)
8686

8787
self._gh = GithubSyncMock()
88+
for worker in self._gh.workers.values():
89+
rate_limit = MagicMock()
90+
rate_limit.core.remaining = 5000
91+
worker.git.get_rate_limit = MagicMock(return_value=rate_limit)
8892

8993
def test_init_with_base_directory(self) -> None:
9094
@dataclass

0 commit comments

Comments
 (0)