Skip to content

Cancel tagging when repo times out - #59

Open
sgallagher wants to merge 2 commits into
fedora-eln:mainfrom
sgallagher:tag_cancel
Open

Cancel tagging when repo times out#59
sgallagher wants to merge 2 commits into
fedora-eln:mainfrom
sgallagher:tag_cancel

Conversation

@sgallagher

@sgallagher sgallagher commented Jul 31, 2026

Copy link
Copy Markdown
Member

When Koji is very busy, sometimes the initial side-tag creation will fail because a repo cannot be regenerated within the timeout. In this situation, we were deleting the side-tag and retrying, but we didn't actually cancel all of the pending tag tasks for that side-tag buildroot. In situations where the Koji queue is extremely backlogged, this would only serve to make that backlog worse.

Summary by CodeRabbit

  • New Features

    • Added support for cancelling multiple build tasks at once, with results reported for each task.
    • Side-tag operations now provide clearer lifecycle management, including creation, tagging, cleanup, and removal.
    • Side tags can be initialized using either numeric or text-based build references.
  • Bug Fixes

    • Pending tagging tasks are cancelled when side-tag operations fail.
    • Side-tag timeouts and other failures now produce clearer errors and trigger cleanup.
    • Improved error handling exposes the underlying cause of tagging failures.

@sgallagher
sgallagher requested a review from bhoy-troy July 31, 2026 13:40
@sgallagher sgallagher self-assigned this Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 070dbb63-418d-4d37-a455-57cdd928e055

📥 Commits

Reviewing files that changed from the base of the PR and between a7dd109 and 739f8d5.

📒 Files selected for processing (3)
  • elnbuildsync/kojihelpers/builds.py
  • elnbuildsync/kojihelpers/tags.py
  • elnbuildsync/rebuildbatch.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • elnbuildsync/kojihelpers/builds.py
  • elnbuildsync/rebuildbatch.py
  • elnbuildsync/kojihelpers/tags.py

📝 Walkthrough

Walkthrough

This change replaces prepare_side_tag with a SideTag lifecycle. It adds typed errors, tracks tagging tasks, cancels failed tasks through Koji multicall, and updates RebuildBatch side-tag creation, Bodhi submission, retries, and cleanup.

Changes

Side-tag lifecycle and task cancellation

Layer / File(s) Summary
Koji task cancellation API
elnbuildsync/kojihelpers/builds.py
Adds cancel_tasks and a multicall helper that cancels task IDs and returns results keyed by task ID.
SideTag creation and failure handling
elnbuildsync/kojihelpers/tags.py
Adds SideTag, tracks tagging tasks, unwraps deferred failures, cancels tasks on failure, and raises typed errors.
SideTag workflow integration
elnbuildsync/rebuildbatch.py
Uses SideTag.create(), retries SideTagTimeoutError, submits Bodhi updates with SideTag.name, and removes side tags during cleanup.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RebuildBatch
  participant SideTag
  participant Koji
  participant cancel_tasks

  RebuildBatch->>SideTag: create side tag
  SideTag->>Koji: create tag and tag initial builds
  Koji-->>SideTag: tagging task IDs
  SideTag->>SideTag: wait for tag completion
  SideTag->>cancel_tasks: cancel tasks on failure
  cancel_tasks->>Koji: cancelTask via multicall
  Koji-->>cancel_tasks: results keyed by task ID
  SideTag-->>RebuildBatch: SideTag or typed error
  RebuildBatch->>SideTag: remove on cleanup
  SideTag->>Koji: remove side tag
Loading

Possibly related PRs

Suggested reviewers: bhoy-troy

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes cancelling tagging tasks when repository regeneration times out, which is the primary change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
elnbuildsync/kojihelpers/tags.py (1)

74-79: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Submit the cancellation batch only once.

When results contains multiple failures, this branch passes the complete task index to cancel_tasks for each failure. The same task IDs are then submitted to Koji repeatedly.

Guard the call with a flag or move it after the loop. Use one cancellation attempt per failed side-tag preparation unless repeated cancellation is an intentional retry policy.

Suggested fix
+        cancellation_requested = False
         for success, value in results:
             if success:
                 logger.info(f"Build {value} tagged into {side_tag_name}")
             else:
...
-                await kojihelpers.builds.cancel_tasks(tagging_tasks_index.keys())
+                if not cancellation_requested:
+                    cancellation_requested = True
+                    await kojihelpers.builds.cancel_tasks(tagging_tasks_index.keys())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@elnbuildsync/kojihelpers/tags.py` around lines 74 - 79, The cancel_tasks call
with tagging_tasks_index.keys() is inside a loop that processes each failure
from results, causing the same task IDs to be submitted for cancellation
multiple times. Move the await
kojihelpers.builds.cancel_tasks(tagging_tasks_index.keys()) call outside and
after the loop so the cancellation batch is submitted only once, regardless of
how many failures exist in the results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@elnbuildsync/kojihelpers/builds.py`:
- Around line 171-181: The helper _cancel_multiple_tasks_thread must be
synchronous because it is invoked through deferToThread and must execute its
multicall body immediately. Change its declaration from async def to def while
preserving the existing task cancellation and result aggregation logic; do not
add coroutine awaiting to _call_koji_once.
- Around line 154-168: The cancel_tasks function assigns results only inside the
try block when call_koji succeeds, but the except handler logs the exception and
then tries to return the unassigned results variable, causing UnboundLocalError.
Initialize results to an empty dictionary before the try block or add a return
statement in the except handler to return an empty dictionary. This preserves
the best-effort behavior where cancellation failures are logged but the function
still returns a valid result instead of propagating UnboundLocalError.

---

Nitpick comments:
In `@elnbuildsync/kojihelpers/tags.py`:
- Around line 74-79: The cancel_tasks call with tagging_tasks_index.keys() is
inside a loop that processes each failure from results, causing the same task
IDs to be submitted for cancellation multiple times. Move the await
kojihelpers.builds.cancel_tasks(tagging_tasks_index.keys()) call outside and
after the loop so the cancellation batch is submitted only once, regardless of
how many failures exist in the results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 104d3dc9-c0a8-4e23-b16f-db24923cace1

📥 Commits

Reviewing files that changed from the base of the PR and between a7dd109 and 9fa40fa.

📒 Files selected for processing (2)
  • elnbuildsync/kojihelpers/builds.py
  • elnbuildsync/kojihelpers/tags.py

Comment thread elnbuildsync/kojihelpers/builds.py
Comment thread elnbuildsync/kojihelpers/builds.py Outdated
@sgallagher
sgallagher marked this pull request as draft July 31, 2026 14:00
@sgallagher

Copy link
Copy Markdown
Member Author

This still needs some work; the "nitpick" comment (copied below so that it isn't lost) that CodeRabbit spotted is actually quite important and I overlooked it.

Actionable comments posted: 2

🧹 Nitpick comments (1)
elnbuildsync/kojihelpers/tags.py (1)

74-79: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Submit the cancellation batch only once.

When results contains multiple failures, this branch passes the complete task index to cancel_tasks for each failure. The same task IDs are then submitted to Koji repeatedly.

Guard the call with a flag or move it after the loop. Use one cancellation attempt per failed side-tag preparation unless repeated cancellation is an intentional retry policy.

Suggested fix
+        cancellation_requested = False
         for success, value in results:
             if success:
                 logger.info(f"Build {value} tagged into {side_tag_name}")
             else:
...
-                await kojihelpers.builds.cancel_tasks(tagging_tasks_index.keys())
+                if not cancellation_requested:
+                    cancellation_requested = True
+                    await kojihelpers.builds.cancel_tasks(tagging_tasks_index.keys())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@elnbuildsync/kojihelpers/tags.py` around lines 74 - 79, The cancel_tasks call
with tagging_tasks_index.keys() is inside a loop that processes each failure
from results, causing the same task IDs to be submitted for cancellation
multiple times. Move the await
kojihelpers.builds.cancel_tasks(tagging_tasks_index.keys()) call outside and
after the loop so the cancellation batch is submitted only once, regardless of
how many failures exist in the results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@elnbuildsync/kojihelpers/builds.py`:
- Around line 171-181: The helper _cancel_multiple_tasks_thread must be
synchronous because it is invoked through deferToThread and must execute its
multicall body immediately. Change its declaration from async def to def while
preserving the existing task cancellation and result aggregation logic; do not
add coroutine awaiting to _call_koji_once.
- Around line 154-168: The cancel_tasks function assigns results only inside the
try block when call_koji succeeds, but the except handler logs the exception and
then tries to return the unassigned results variable, causing UnboundLocalError.
Initialize results to an empty dictionary before the try block or add a return
statement in the except handler to return an empty dictionary. This preserves
the best-effort behavior where cancellation failures are logged but the function
still returns a valid result instead of propagating UnboundLocalError.

---

Nitpick comments:
In `@elnbuildsync/kojihelpers/tags.py`:
- Around line 74-79: The cancel_tasks call with tagging_tasks_index.keys() is
inside a loop that processes each failure from results, causing the same task
IDs to be submitted for cancellation multiple times. Move the await
kojihelpers.builds.cancel_tasks(tagging_tasks_index.keys()) call outside and
after the loop so the cancellation batch is submitted only once, regardless of
how many failures exist in the results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 104d3dc9-c0a8-4e23-b16f-db24923cace1

📥 Commits

Reviewing files that changed from the base of the PR and between a7dd109 and 9fa40fa.

📒 Files selected for processing (2)
  • elnbuildsync/kojihelpers/builds.py
  • elnbuildsync/kojihelpers/tags.py

@sgallagher
sgallagher marked this pull request as ready for review August 3, 2026 15:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
elnbuildsync/rebuildbatch.py (1)

92-136: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Fix _create_and_populate_side_tag to use SideTag.create() consistently.

kojihelpers.tags.prepare_side_tag no longer exists, so this method will raise AttributeError in both import paths. The method currently returns a SideTag as a string via the stale prepare_side_tag name, but async_init() later uses self.side_tag.name/self.side_tag.remove() as an object, while the Bodhi update path expects update_tag as a plain string. Either return SideTag consistently and pass update_tag.name into _submit_bodhi_update, or update the stale return annotation/string/object split before merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@elnbuildsync/rebuildbatch.py` around lines 92 - 136, Update
_create_and_populate_side_tag to create the tag through the available
SideTag.create() API instead of the removed prepare_side_tag symbol, preserving
timeout retry handling. Return a SideTag object consistently so async_init() can
use its name and remove() methods, and pass side_tag.name to
_submit_bodhi_update where a plain tag string is required; update the return
annotation and related documentation accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@elnbuildsync/kojihelpers/tags.py`:
- Around line 86-93: Update the _prepare() docstring to remove the stale
:params: entries for base_tag and initial_build_ids, and replace them with a
concise description of _prepare()’s actual behavior without documenting
nonexistent arguments.
- Around line 60-69: Update the comment describing tagging_tasks_index in the
relevant tag class to document that it maps task IDs to build IDs, matching the
value returned by tag_builds() and the usage in cancel_tasks(). Leave the
dictionary behavior unchanged.
- Around line 86-140: Update _prepare() to call self.remove() during
tagging-failure cleanup after cancelling the tagging tasks, ensuring the
partially created side tag is removed before raising. Update
wait_for_nvrs_in_tag() to preserve or return the DeferredTimeoutError from
failed deferreds despite DeferredList(consumeErrors=True), so _prepare() can
reach its DeferredTimeoutError handling branch and raise SideTagTimeoutError for
timeouts.

---

Outside diff comments:
In `@elnbuildsync/rebuildbatch.py`:
- Around line 92-136: Update _create_and_populate_side_tag to create the tag
through the available SideTag.create() API instead of the removed
prepare_side_tag symbol, preserving timeout retry handling. Return a SideTag
object consistently so async_init() can use its name and remove() methods, and
pass side_tag.name to _submit_bodhi_update where a plain tag string is required;
update the return annotation and related documentation accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cb32b1a4-8005-47dd-98b7-b208686243d4

📥 Commits

Reviewing files that changed from the base of the PR and between 9fa40fa and e6a27a5.

📒 Files selected for processing (3)
  • elnbuildsync/kojihelpers/builds.py
  • elnbuildsync/kojihelpers/tags.py
  • elnbuildsync/rebuildbatch.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • elnbuildsync/kojihelpers/builds.py

Comment thread elnbuildsync/kojihelpers/tags.py Outdated
Comment thread elnbuildsync/kojihelpers/tags.py
Comment thread elnbuildsync/kojihelpers/tags.py Outdated
@sgallagher
sgallagher force-pushed the tag_cancel branch 2 times, most recently from 69d3772 to 8ce52af Compare August 3, 2026 17:46
@sgallagher

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
elnbuildsync/rebuildbatch.py (1)

94-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the type contract with both call paths.

Line 94 accepts list[int], but _create_and_submit_bodhi_updates passes list[str] when promote_builds=True. The non-promotion branch returns the original list[int], although the return type declares list[str]. This can cause static type-checking failures and mislead callers.

Use list[int | str] for the input and returned references, or add overloads for the two modes.

Proposed type fix
 async def _create_and_populate_side_tag(
-    self, build_ids: list[int], promote_builds: bool = False
-) -> tuple[kojihelpers.tags.SideTag, list[str]]:
+    self, build_ids: list[int | str], promote_builds: bool = False
+) -> tuple[kojihelpers.tags.SideTag, list[int | str]]:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@elnbuildsync/rebuildbatch.py` around lines 94 - 108, Update the side-tag
creation method’s type contract and related annotations to accept and return
references as list[int | str], covering both build IDs and NVR strings across
the promote_builds paths. Ensure callers such as
_create_and_submit_bodhi_updates and the non-promotion branch remain
type-consistent without changing runtime behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@elnbuildsync/rebuildbatch.py`:
- Around line 94-108: Update the side-tag creation method’s type contract and
related annotations to accept and return references as list[int | str], covering
both build IDs and NVR strings across the promote_builds paths. Ensure callers
such as _create_and_submit_bodhi_updates and the non-promotion branch remain
type-consistent without changing runtime behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eea99e95-08f3-4a52-b1b5-992c19ceb49c

📥 Commits

Reviewing files that changed from the base of the PR and between 69d3772 and 8ce52af.

📒 Files selected for processing (3)
  • elnbuildsync/kojihelpers/builds.py
  • elnbuildsync/kojihelpers/tags.py
  • elnbuildsync/rebuildbatch.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • elnbuildsync/kojihelpers/builds.py
  • elnbuildsync/kojihelpers/tags.py

When Koji is very busy, sometimes the initial side-tag creation will
fail because a repo cannot be regenerated within the timeout. In this
situation, we were deleting the side-tag and retrying, but we didn't
actually cancel all of the pending tag tasks for that side-tag
buildroot. In situations where the Koji queue is extremely backlogged,
this would only serve to make that backlog worse.

For maintainability, this converts side-tag creation into a new Python
class.

Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
@sgallagher

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant