Skip to content

Commit 543eb6e

Browse files
ryantimwilsonfacebook-github-bot
authored andcommitted
Fix crash when syncing old subjects from patchwork -> Github
Summary: We saw crashes in KPD like: ``` Traceback (most recent call last): File "kernel_patches_daemon/daemon.py", line 58, in run await self.run_once() File "kernel_patches_daemon/daemon.py", line 49, in run_once await self.github_sync_worker.sync_patches() File "kernel_patches_daemon/github_sync.py", line 287, in sync_patches await worker.checkout_and_patch(branch_name, latest_series) File "kernel_patches_daemon/branch_worker.py", line 986, in checkout_and_patch return await self.apply_push_comment(branch_name, series_to_apply) File "kernel_patches_daemon/branch_worker.py", line 911, in apply_push_comment raise NewPRWithNoChangeException(self.repo_pr_base_branch, branch_name) ``` This can happen during patchwork -> Github sync when a patch series is already merged upstream so no changes are necessary vs the target branch and thus no PR is generated. While some code accounts for this case, the "syncing old subjects" part did not and thus the exception would cause recurring crashes. Reviewed By: chantra Differential Revision: D63265309 fbshipit-source-id: 98f4fa960d4132a1a222f058c88d5102b6d51d40
1 parent be555b0 commit 543eb6e

File tree

3 files changed

+63
-15
lines changed

3 files changed

+63
-15
lines changed

kernel_patches_daemon/branch_worker.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,7 @@ async def checkout_and_patch(
979979
Patch in place and push.
980980
Returns true if whole series applied.
981981
Return None if at least one patch in series failed.
982+
Raises NewPRWithNoChangeException if series would not result in any changes.
982983
If at least one patch in series failed nothing gets pushed.
983984
"""
984985
if await self._pr_closed(branch_name, series_to_apply):

kernel_patches_daemon/github_sync.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,24 @@ def close_existing_prs_with_same_base(
168168
if pr_to_close.title in worker.prs:
169169
del worker.prs[pr_to_close.title]
170170

171+
async def checkout_and_patch_safe(
172+
self, worker, branch_name: str, series_to_apply: Series
173+
) -> Optional[PullRequest]:
174+
try:
175+
self.increment_counter("all_known_subjects")
176+
pr = await worker.checkout_and_patch(branch_name, series_to_apply)
177+
if pr is None:
178+
logging.info(
179+
f"PR associated with branch {branch_name} for series {series_to_apply.id} is closed; ignoring"
180+
)
181+
return pr
182+
except NewPRWithNoChangeException as e:
183+
self.increment_counter("empty_pr")
184+
logger.exception(
185+
f"Could not create PR for series {series_to_apply.id} merging {e.base_branch} into {e.target_branch} as PR would introduce no changes"
186+
)
187+
return None
188+
171189
async def sync_patches(self) -> None:
172190
"""
173191
One subject = one branch
@@ -239,17 +257,8 @@ async def sync_patches(self) -> None:
239257
else:
240258
logging.info(msg + "no more next, staying.")
241259
logging.info(f"Choosing branch {branch} to create/update PR.")
242-
try:
243-
self.increment_counter("all_known_subjects")
244-
pr = await worker.checkout_and_patch(pr_branch_name, series)
245-
if pr is None:
246-
logging.info(
247-
"PR associated with branch {pr_branch_name} for series {series.id} is closed; ignoring"
248-
)
249-
continue
250-
except NewPRWithNoChangeException:
251-
self.increment_counter("empty_pr")
252-
logger.exception("Could not create PR with no changes")
260+
pr = await self.checkout_and_patch_safe(worker, pr_branch_name, series)
261+
if pr is None:
253262
continue
254263

255264
logging.info(
@@ -283,8 +292,11 @@ async def sync_patches(self) -> None:
283292
pr.edit(title=subject.subject)
284293
branch_name = f"{await subject.branch}{HEAD_BASE_SEPARATOR}{worker.repo_branch}"
285294
latest_series = await subject.latest_series or series
286-
self.increment_counter("all_known_subjects")
287-
await worker.checkout_and_patch(branch_name, latest_series)
295+
pr = await self.checkout_and_patch_safe(
296+
worker, branch_name, latest_series
297+
)
298+
if pr is None:
299+
continue
288300
await worker.sync_checks(pr, latest_series)
289301

290302
await loop.run_in_executor(None, worker.expire_branches)

tests/test_github_sync.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
import unittest
1111
from dataclasses import dataclass
1212
from typing import Any, Dict, Optional
13-
from unittest.mock import MagicMock, patch
13+
from unittest.mock import AsyncMock, MagicMock, patch
1414

15+
from kernel_patches_daemon.branch_worker import NewPRWithNoChangeException
1516
from kernel_patches_daemon.config import KPDConfig
1617
from kernel_patches_daemon.github_sync import GithubSync
1718

@@ -54,7 +55,7 @@ def __init__(
5455
super().__init__(*args, **presets)
5556

5657

57-
class TestGihubSync(unittest.TestCase):
58+
class TestGihubSync(unittest.IsolatedAsyncioTestCase):
5859
def setUp(self) -> None:
5960
patcher = patch("kernel_patches_daemon.github_connector.Github")
6061
self._gh_mock = patcher.start()
@@ -128,3 +129,37 @@ def test_close_existing_prs_with_same_base(self) -> None:
128129
self.assertTrue("matching" not in worker.prs)
129130

130131
matching_pr_mock.edit.assert_called_with(state="close")
132+
133+
async def test_checkout_and_patch_safe(self) -> None:
134+
pr_branch_name = "fake_pr_branch"
135+
series = MagicMock()
136+
pr = MagicMock()
137+
branch_worker_mock = MagicMock()
138+
branch_worker_mock.checkout_and_patch = AsyncMock()
139+
140+
# PR generated
141+
branch_worker_mock.checkout_and_patch.return_value = pr
142+
self.assertEqual(
143+
await self._gh.checkout_and_patch_safe(
144+
branch_worker_mock, pr_branch_name, series
145+
),
146+
pr,
147+
)
148+
149+
# One patch in series failed to apply
150+
branch_worker_mock.checkout_and_patch.return_value = None
151+
self.assertIsNone(
152+
await self._gh.checkout_and_patch_safe(
153+
branch_worker_mock, pr_branch_name, series
154+
)
155+
)
156+
157+
# Series generates no changes vs target branch, likely already merged
158+
branch_worker_mock.checkout_and_patch.side_effect = NewPRWithNoChangeException(
159+
pr_branch_name, "target"
160+
)
161+
self.assertIsNone(
162+
await self._gh.checkout_and_patch_safe(
163+
branch_worker_mock, pr_branch_name, series
164+
)
165+
)

0 commit comments

Comments
 (0)