Skip to content

Test: Fix inventory template assignment lag - #2352

Merged
swadeley merged 2 commits into
RedHatInsights:masterfrom
swadeley:swadeley/fix_inventory_template_assignment_lag
Oct 6, 2026
Merged

swadeley merged 2 commits into
RedHatInsights:masterfrom
swadeley:swadeley/fix_inventory_template_assignment_lag

Conversation

@swadeley

@swadeley swadeley commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Moved Candlepin template resolution before the upload DB transaction in patchman-engine.

Previously Candlepin GET /consumers/{owner_id} ran while the upload transaction held a row lock. Slow or failing Candlepin meant:
longer system_inventory locks
the single-threaded Kafka listener blocked with an open tx more lag on platform.inventory.events → systems late or missing in Patch more chance of landing in Patch with empty template_uuid under load

Secure Coding Practices Checklist GitHub Link

Secure Coding Checklist

  • Input Validation
  • Output Encoding
  • Authentication and Password Management
  • Session Management
  • Access Control
  • Cryptographic Practices
  • Error Handling and Logging
  • Data Protection
  • Communication Security
  • System Configuration
  • Database Security
  • File Management
  • Memory Management
  • General Coding Practices

Summary by Sourcery

Resolve Candlepin templates before updating locked inventory records to reduce upload lag and preserve deletion safety.

Bug Fixes:

  • Prevent Candlepin template-resolution delays from holding system inventory locks and causing inventory processing lag or empty template assignments.
  • Recheck recently deleted systems after template resolution to avoid persisting uploads deleted during the external request.

Enhancements:

  • Centralize workspace validation and extraction for upload processing.

@swadeley
swadeley requested a review from a team as a code owner October 2, 2026 13:59
@sourcery-ai

sourcery-ai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Template IDs are now resolved through Candlepin before inventory persistence acquires its row lock, reducing Kafka/upload lag while a second deletion check prevents stale systems from being written after the external lookup.

File-Level Changes

Change Details Files
Move Candlepin template resolution outside the inventory update’s row-locking path.
  • Resolve the template before calling the inventory update routine.
  • Pass the resolved template ID into persistence instead of performing the HTTP lookup during the locked update.
  • Retain the database transaction while ensuring Candlepin retries do not hold the system_inventory lock.
listener/upload.go
Guard template resolution and persistence against systems deleted during the external lookup.
  • Centralize the recently-deleted-system check in a reusable helper.
  • Check deletion status both before and after Candlepin resolution.
  • Drop uploads for systems deleted within the configured threshold.
listener/upload.go
Update unit-test callers for the revised persistence API.
  • Supply the new template ID argument to updateSystemPlatform test calls.
  • Preserve existing tests by passing nil where no template is required.
listener/upload_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="listener/upload.go" line_range="813" />
<code_context>
+	// Resolve template via Candlepin before opening the DB transaction so HTTP
+	// retries do not hold SELECT ... FOR UPDATE on system_inventory or stall
+	// this Kafka consumer while a row lock is held.
+	templateID := hostTemplate(database.DB, accountID, host)
+
 	tx := database.DB.WithContext(base.Context).Begin()
</code_context>
<issue_to_address>
**issue (performance):** Recently deleted systems still execute Candlepin resolution, including its retry delay, before `processUpload` checks `DeletedSystem` and discards the upload. A slow or unavailable Candlepin therefore continues to block the single-threaded Kafka listener for uploads that should be rejected without external work.

**Triggers:** When an upload arrives for a system deleted within the last hour and Candlepin is slow or unavailable.

**Suggested fix:** Perform a preliminary deleted-system check before calling `hostTemplate`, then retain the transactional check before writing to avoid the deletion race.

```suggestion
	var preliminaryDeleted models.DeletedSystem
	if err := database.DB.Find(&preliminaryDeleted, "inventory_id = ?", host.ID).Error; err != nil &&
		!errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, base.WrapFatalDBError(err, "checking deleted systems")
	}
	if preliminaryDeleted.InventoryID != uuid.Nil && preliminaryDeleted.WhenDeleted.After(time.Now().Add(-deletionThreshold)) {
		utils.LogInfo("inventoryID", host.ID, "Received recently deleted system")
		return nil, nil
	}

	// Resolve template via Candlepin before opening the DB transaction so HTTP
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and if template resolution is wrong or becomes stale before the transaction commits, the resulting TemplateID is persisted on the system record and may drive later behavior. Reverting stops the new timing, but does not remove assignments already written; those are bounded and can be corrected by a subsequent upload or recomputation.

Blocking findings: listener/upload.go:813


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread listener/upload.go Outdated
@codecov-commenter

codecov-commenter commented Oct 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 55.00000% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.95%. Comparing base (050898c) to head (bbc7651).

Files with missing lines Patch % Lines
listener/upload.go 55.00% 12 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2352      +/-   ##
==========================================
- Coverage   58.95%   58.95%   -0.01%     
==========================================
  Files         150      150              
  Lines        9612     9626      +14     
==========================================
+ Hits         5667     5675       +8     
- Misses       3349     3352       +3     
- Partials      596      599       +3     
Flag Coverage Δ
unittests 58.95% <55.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread listener/upload.go
Comment thread listener/upload.go Outdated
// Resolve template via Candlepin before opening the DB transaction so HTTP
// retries do not hold SELECT ... FOR UPDATE on system_inventory or stall
// this Kafka consumer while a row lock is held.
templateID := hostTemplate(database.DB, accountID, host)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this should go after

tx := database.DB.WithContext(base.Context).Begin()
defer tx.Rollback()

and reuse opened tx so it's properly closed with the potential context close.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @MichaelMraka

You mean:
tx := database.DB.WithContext(base.Context).Begin() defer tx.Rollback() templateID := hostTemplate(tx, accountID, host)

That conflicts with the PR goal: keep Candlepin HTTP (with retries) outside an open transaction. Putting it after Begin() still avoids SELECT … FOR UPDATE (that happens later in updateSystemPlatform), but holds a DB connection for the whole Candlepin wait.

Is this compromise that keeps the lock fix and addresses context: call before Begin(), but with context acceptable?:

templateID := hostTemplate(database.DB.WithContext(base.Context), accountID, host)
tx := database.DB.WithContext(base.Context).Begin()

Let me know, option 1 or 2, and thank you for the review.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

open db transaction is not a problem. it's SELECT … FOR UPDATE which creates a lock.
so we just need run hostTemplate() before the lock is created.
Creating new session with database.DB.WithContext(base.Context) "costs" more than keeping open transaction (without lock) a bit longer.
I prefer 1.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK, thank you @MichaelMraka

@swadeley
swadeley requested a review from MichaelMraka October 5, 2026 14:59
@swadeley
swadeley force-pushed the swadeley/fix_inventory_template_assignment_lag branch from f50cc77 to 89e1cb6 Compare October 6, 2026 10:14
Resolve Candlepin template assignment in the upload transaction before
SELECT ... FOR UPDATE so HTTP retries do not hold a system_inventory
row lock. Skip recently deleted systems before Candlepin and re-check
afterward so dropped uploads do not wait on HTTP retries.

Co-authored-by: Cursor <cursoragent@cursor.com>
@swadeley
swadeley force-pushed the swadeley/fix_inventory_template_assignment_lag branch from 89e1cb6 to f7a786a Compare October 6, 2026 10:16
@swadeley

swadeley commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Rebased

@swadeley

swadeley commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@sourcery-ai sourcery-ai 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.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="listener/upload.go" line_range="823-826" />
<code_context>
+	// updateSystemPlatform so HTTP retries do not hold a row lock.
+	templateID := hostTemplate(tx, accountID, host)
+
+	if skip, err := skipRecentlyDeleted(tx, host.ID); err != nil || skip {
+		return nil, err
 	}
-	sys, err := updateSystemPlatform(tx, accountID, host, yumUpdates, &updatesReq)
+	sys, err := updateSystemPlatform(tx, accountID, host, yumUpdates, &updatesReq, templateID)
 	if err != nil {
 		return nil, errors.Wrap(err, "saving system into the database")
</code_context>
<issue_to_address>
**Deleted systems are revived**

When a delete commits after the tombstone check but before the upload upsert, `updateSystemPlatform` upserts the upload's `culled_timestamp` over the delete's culled state, leaving the deleted system active and potentially clearing its tombstone.

Make the upload upsert preserve a delete committed after the tombstone check instead of overwriting its culled state.
</issue_to_address>

### Comment 2
<location path="listener/upload.go" line_range="821" />
<code_context>
-		return nil, nil
+	// Resolve template via Candlepin before SELECT ... FOR UPDATE in
+	// updateSystemPlatform so HTTP retries do not hold a row lock.
+	templateID := hostTemplate(tx, accountID, host)
+
+	if skip, err := skipRecentlyDeleted(tx, host.ID); err != nil || skip {
</code_context>
<issue_to_address>
**Slow Candlepin ties up DB connections**

When candlepin is slow, retrying or stalled during template resolution, `processUpload` has already begun `tx` when `hostTemplate` waits on Candlepin, keeping the transaction and pooled connection open until the call returns; slow requests consume connections and delay uploads and other database work.

Resolve the template before `tx.Begin()` so the Candlepin call does not hold an open transaction or pooled connection.
</issue_to_address>

### Comment 3
<location path="listener/upload.go" line_range="821" />
<code_context>
-		return nil, nil
+	// Resolve template via Candlepin before SELECT ... FOR UPDATE in
+	// updateSystemPlatform so HTTP retries do not hold a row lock.
+	templateID := hostTemplate(tx, accountID, host)
+
+	if skip, err := skipRecentlyDeleted(tx, host.ID); err != nil || skip {
</code_context>
<issue_to_address>
**Invalid uploads block event processing**

When an upload with a template repository has missing or invalid workspace data and Candlepin is slow or retrying, `processUpload` calls `hostTemplate` before `updateSystemPlatform` validates `host.Groups`, so Candlepin retries delay the workspace error and hold up later events on that listener.

Validate the workspace group before calling `hostTemplate`, so invalid uploads are rejected without waiting on Candlepin.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 3 findings to address first, and if template resolution is wrong or stale, the upload can persist an incorrect template association on the inventory record until a later upload or repair recomputes it. Reverting stops future assignments but does not undo associations already written.

Blocking findings: listener/upload.go:826, listener/upload.go:821, listener/upload.go:821


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread listener/upload.go
Comment thread listener/upload.go
Comment thread listener/upload.go
Reject uploads with missing or invalid host groups before calling
Candlepin so the Kafka listener does not wait on HTTP retries for
events that will fail workspace validation anyway.

Co-authored-by: Cursor <cursoragent@cursor.com>
@swadeley
swadeley merged commit 3c8b4a3 into RedHatInsights:master Oct 6, 2026
8 checks passed
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.

4 participants