Skip to content

Commit 97f6ca2

Browse files
Make duplicate push builds yield to PR builds
In a GitHub double-event, Buildbot can queue the push and pull_request tarball-docker requests together, but the push build may get a worker first and complete while the corresponding PR request is still waiting in the queue. By the time the PR build finally runs, it is too late to prevent the duplicate tarball work and any downstream triggers that already happened. Flip the direction so the push side yields when it sees the matching PR request. That makes the first running duplicate decide whether it should continue, instead of depending on the PR side to arrive soon enough after the fact. The branch guard keeps this limited to disposable normal push branches: refs/* are ignored and branches covered by SAVED_PACKAGE_BRANCHES are not allowed to self-cancel.
1 parent 78d1768 commit 97f6ca2

1 file changed

Lines changed: 45 additions & 70 deletions

File tree

utils.py

Lines changed: 45 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from buildbot.plugins import steps, util, worker
1717
from buildbot.process.builder import Builder
1818
from buildbot.process.buildstep import BuildStep
19-
from buildbot.process.results import FAILURE, SUCCESS
19+
from buildbot.process.results import CANCELLED, FAILURE, SUCCESS
2020
from buildbot.process.workerforbuilder import AbstractWorkerForBuilder
2121
from buildbot.worker import AbstractWorker
2222
from constants import (
@@ -682,11 +682,11 @@ def mtrEnv(props: IProperties) -> dict:
682682

683683

684684
class CancelDuplicateBuildRequests(BuildStep):
685-
"""BuildStep to cancel duplicate buildrequests for the same commit on the same builder
686-
if the current build is for a pull request event. It checks for other pending buildrequests
687-
with the same builder and if they have a sourcestamp with the same revision as
688-
the current build, it cancels them. It only cancels normal branch builds, not other
689-
pull request refs, and avoids important branches like main, release or merge branches.
685+
"""Stop duplicate push builds when a matching pull request build exists.
686+
687+
When GitHub sends push and pull_request events for the same commit, both can
688+
queue tarball-docker requests. A normal branch push should yield to a matching
689+
pull request buildrequest on the same builder.
690690
"""
691691

692692
name = "cancel duplicate buildrequests"
@@ -717,17 +717,15 @@ def _fmt_ss(ss):
717717

718718
@staticmethod
719719
def _branch_is_cancelable(branch):
720-
"""Only cancel duplicate builds originating from normal branch pushes."""
720+
"""Only let disposable normal branch pushes yield to pull request builds."""
721721
if not branch:
722722
return False
723723

724724
branch_lc = branch.lower()
725725
return (
726726
len(branch) > 5
727727
and not branch_lc.startswith("refs/")
728-
and "release" not in branch_lc
729-
and "merge" not in branch_lc
730-
and "preview" not in branch_lc
728+
and not fnmatch_any(branch, SAVED_PACKAGE_BRANCHES)
731729
)
732730

733731
@defer.inlineCallbacks
@@ -744,8 +742,8 @@ def run(self):
744742
self.addCompleteLog("duplicate-buildrequests", "\n".join(lines) + "\n")
745743
return SUCCESS
746744

747-
if event != "pull_request":
748-
lines.append("Event is not 'pull_request'; nothing to do.")
745+
if event != "push":
746+
lines.append("Event is not 'push'; nothing to do.")
749747
self.addCompleteLog("duplicate-buildrequests", "\n".join(lines) + "\n")
750748
return SUCCESS
751749

@@ -768,15 +766,17 @@ def run(self):
768766
self.addCompleteLog("duplicate-buildrequests", "\n".join(lines) + "\n")
769767
return SUCCESS
770768

771-
current_revisions = {
772-
ss.get("revision")
769+
current_targets = {
770+
(ss.get("revision"), ss.get("repository"), ss.get("codebase", ""))
773771
for ss in current_sourcestamps
774772
if ss.get("revision") is not None
773+
and self._branch_is_cancelable(ss.get("branch"))
775774
}
776775

777-
if not current_revisions:
776+
if not current_targets:
778777
lines.append(
779-
"Current buildset has no revision in sourcestamps; nothing to do."
778+
"Current buildset has no cancelable push sourcestamp with a "
779+
"revision; nothing to do."
780780
)
781781
self.addCompleteLog("duplicate-buildrequests", "\n".join(lines) + "\n")
782782
return SUCCESS
@@ -787,20 +787,12 @@ def run(self):
787787
if ss.get("branch") is not None
788788
}
789789

790-
if not any(branch.startswith("refs/pull/") for branch in current_branches):
791-
lines.append(
792-
"Current build is a pull_request event, but no pull request ref "
793-
"was found in sourcestamps; nothing to do."
794-
)
795-
self.addCompleteLog("duplicate-buildrequests", "\n".join(lines) + "\n")
796-
return SUCCESS
797-
798790
lines.append("Current:")
799791
lines.append(f" buildid={current_buildid}")
800792
lines.append(f" buildrequestid={current_buildrequestid}")
801793
lines.append(f" builderid={current_builderid}")
802794
lines.append(f" buildsetid={current_buildsetid}")
803-
lines.append(f" revisions={sorted(current_revisions)!r}")
795+
lines.append(f" targets={sorted(current_targets)!r}")
804796
lines.append(f" branches={sorted(current_branches)!r}")
805797
current_url = self._buildrequest_url(current_buildrequestid)
806798
if current_url:
@@ -829,8 +821,6 @@ def run(self):
829821

830822
matches = []
831823
actions = []
832-
cancel_errors = []
833-
834824
for br in buildrequests:
835825
brid = br["buildrequestid"]
836826

@@ -848,16 +838,19 @@ def run(self):
848838
for other_ss in other_sourcestamps:
849839
other_revision = other_ss.get("revision")
850840
other_branch = other_ss.get("branch")
841+
other_target = (
842+
other_revision,
843+
other_ss.get("repository"),
844+
other_ss.get("codebase", ""),
845+
)
851846

852-
if other_revision not in current_revisions:
853-
continue
854-
855-
if not self._branch_is_cancelable(other_branch):
847+
if not other_branch or not other_branch.startswith("refs/pull/"):
856848
continue
857849

858-
matched_revision = other_revision
859-
matched_branch = other_branch
860-
break
850+
if other_target in current_targets:
851+
matched_revision = other_revision
852+
matched_branch = other_branch
853+
break
861854

862855
if matched_revision is None:
863856
continue
@@ -876,47 +869,24 @@ def run(self):
876869

877870
if self.dry_run:
878871
msg = (
879-
f"[DRY-RUN] would cancel buildrequest {brid} "
872+
f"[DRY-RUN] would stop current build "
873+
f"because pull request buildrequest {brid} exists "
880874
f"(claimed={br.get('claimed')}, "
881875
f"revision={matched_revision!r}, "
882876
f"branch={matched_branch!r})"
883877
)
884-
if info["url"]:
885-
msg += f" url={info['url']}"
886-
actions.append(msg)
887878
else:
888-
try:
889-
yield self.master.data.control(
890-
"cancel",
891-
{"reason": "Duplicate build for same commit on same builder"},
892-
("buildrequests", brid),
893-
)
894-
except Exception as e:
895-
msg = (
896-
f"Failed to request cancel for buildrequest {brid} "
897-
f"(claimed={br.get('claimed')}, "
898-
f"revision={matched_revision!r}, "
899-
f"branch={matched_branch!r}, "
900-
f"error={e!r})"
901-
)
902-
if info["url"]:
903-
msg += f" url={info['url']}"
904-
actions.append(msg)
905-
cancel_errors.append(msg)
906-
log.err(e, f"Failed to request cancel for buildrequest {brid}")
907-
else:
908-
msg = (
909-
f"Requested cancel for buildrequest {brid} "
910-
f"(claimed={br.get('claimed')}, "
911-
f"revision={matched_revision!r}, "
912-
f"branch={matched_branch!r})"
913-
)
914-
if info["url"]:
915-
msg += f" url={info['url']}"
916-
actions.append(msg)
879+
msg = (
880+
f"Stopping current build because pull request buildrequest {brid} "
881+
f"exists (claimed={br.get('claimed')}, "
882+
f"revision={matched_revision!r}, "
883+
f"branch={matched_branch!r})"
884+
)
885+
if info["url"]:
886+
msg += f" url={info['url']}"
887+
actions.append(msg)
917888

918-
lines.append(f"Matched duplicate buildrequests: {len(matches)}")
919-
lines.append(f"Cancel request failures: {len(cancel_errors)}")
889+
lines.append(f"Matched pull request buildrequests: {len(matches)}")
920890
lines.append("")
921891

922892
if matches:
@@ -934,9 +904,14 @@ def run(self):
934904
lines.append("Actions:")
935905
lines.extend(f" {a}" for a in actions)
936906
else:
937-
lines.append("No matching cancelable buildrequests found on this builder.")
907+
lines.append(
908+
"No matching pull request buildrequests found on this builder."
909+
)
938910

939911
self.addCompleteLog("duplicate-buildrequests", "\n".join(lines) + "\n")
912+
if matches and not self.dry_run:
913+
self.build.stopBuild("Superseded by pull request build for same commit")
914+
return CANCELLED
940915
return SUCCESS
941916

942917

0 commit comments

Comments
 (0)