Skip to content

Abstract queue storage behind an interface, fix add-during-purge race#10

Merged
SamJUK merged 1 commit into
masterfrom
feat/queue-storage-abstraction
Jul 13, 2026
Merged

Abstract queue storage behind an interface, fix add-during-purge race#10
SamJUK merged 1 commit into
masterfrom
feat/queue-storage-abstraction

Conversation

@SamJUK

@SamJUK SamJUK commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replace direct ResourceConnection usage in Entries with a QueueStorageInterface + Database driver, so a batch of pending tags can be atomically claimed and drained instead of read-then-wiped.
  • Fixes the race where a tag added while a purge request is in flight got silently dropped by the trailing delete().
  • Adds batch_id + composite (batch_id, tag) primary key, tag is now NOT NULL, and regenerates the previously-missing db_schema_whitelist.json.
  • Entries::get() is removed from the public surface — it read the whole live table, which isn't meaningful once "pending" isn't a flat readable set.

Test plan

  • vendor/bin/phpunit Test/Unit passes (verified against a real Magento install's classes)
  • vendor/bin/phpstan analyse clean at the repo's configured level
  • setup:upgrade + setup:di:compile + dev/tests/integration in CI

This was referenced Jul 10, 2026
@SamJUK
SamJUK force-pushed the feat/queue-storage-abstraction branch 2 times, most recently from 9d4411a to 43348f3 Compare July 10, 2026 21:32
@SamJUK
SamJUK requested a review from Copilot July 12, 2026 20:57

Copilot AI 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.

Pull request overview

This PR refactors the cache-debounce queue to be storage-backed behind a QueueStorageInterface, introducing an atomic “claim + drain” batch model to prevent tags being dropped when they’re added during an in-flight purge.

Changes:

  • Introduces QueueStorageInterface and a Database implementation that batches pending tags via batch_id and supports add(), claim(), tags(), clear().
  • Updates Entries to use the new storage abstraction (and removes the old Entries::get() surface) so a purge drains a claimed batch rather than read-then-wipe the whole table.
  • Updates declarative schema to add batch_id, created_at, composite PK (batch_id, tag), and adds db_schema_whitelist.json; adds/updates unit + integration tests.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Test/Unit/Model/Storage/DatabaseTest.php Adds unit coverage for Database storage add/claim/tags/clear behavior.
Test/Unit/Model/EntriesTest.php Updates unit tests for Entries to use storage abstraction and batch flow.
Test/Integration/Model/Storage/DatabaseTest.php Adds integration coverage for claim batching and add-during-claim regression.
Test/Integration/Model/EntriesTest.php Updates integration tests to validate behavior via storage batches (no more Entries::get()).
Model/Storage/QueueStorageInterface.php Defines the queue storage contract for batch-based claiming and clearing.
Model/Storage/Database.php Implements queue storage in Magento DB using batch_id updates and batch-scoped reads/deletes.
Model/Entries.php Switches queue operations to storage + batch claim, and removes live-table get().
etc/di.xml Wires QueueStorageInterface to the Database implementation via preference.
etc/db_schema.xml Adds batch_id, created_at, and composite primary key to support idempotency and batching.
etc/db_schema_whitelist.json Adds the whitelist file required for declarative schema.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Model/Entries.php
Comment on lines 64 to 68
if (!$success) {
$this->logger->error(
"[CacheDebounce] Purge request failed — leaving tags queued for retry: " . json_encode($tags)
"[CacheDebounce] Purge request failed — leaving batch $batchId queued for retry: "
. json_encode($tags)
);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Claude: Confirmed, real bug. claim() only matches batch_id = '', so on sendPurgeRequest() failure this leaves the batch stuck under its random batch_id forever — the "queued for retry" log message is wrong. Needs a release-back-to-unclaimed path (either re-add tags with batch_id='' or a dedicated release(batchId) storage method) on both the failure branch and any exception.

Comment thread etc/db_schema.xml Outdated
Comment on lines +7 to +10
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="batch_id"/>
<column name="tag"/>
</constraint>

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Claude: Confirmed, real risk but low urgency. Previous schema had tag nullable=true with no uniqueness constraint, so any existing install with dupes/nulls will fail setup:upgrade against the new composite PK. Cheap fix since this table is disposable/derived data: add a data patch that truncates it before the schema change lands, rather than trying to dedupe in place.

@SamJUK

SamJUK commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

Claude: Overall — solid abstraction, one real bug blocks merge. Entries::flush() doesn't release a claimed batch back to unclaimed on purge failure/exception, so a failed purge is stuck forever despite the log claiming a retry (see inline comment on Model/Entries.php). Fix that before merging. The PK migration risk on etc/db_schema.xml is real but lower urgency — cheap to cover with a data patch that truncates the table first, since it's disposable queue data.

@SamJUK
SamJUK force-pushed the feat/queue-storage-abstraction branch 4 times, most recently from 7c1f22a to b932ccf Compare July 13, 2026 00:05
@sonarqubecloud

Copy link
Copy Markdown

@SamJUK
SamJUK force-pushed the feat/queue-storage-abstraction branch from b932ccf to ad5a3df Compare July 13, 2026 08:34
…e race

Model/Entries previously did get() -> sendPurgeRequest() -> delete(whole
table), so any tag added while a purge request was in flight got wiped
by the trailing delete without ever being purged. Queue storage is now
behind QueueStorageInterface (add/claim/tags/clear/release), with claim()
atomically carving off only the currently-pending tags into a batch, so
anything added afterward survives into the next batch instead of being
lost.

A failed or throwing purge now releases the claimed batch back to
pending instead of leaving it stranded under a batch id no future
claim() can ever match.

Schema changes (batch_id, created_at columns, db_schema_whitelist.json)
are additive only this release — no primary key yet. Existing installs
may have NULL/duplicate tag rows from the old unconstrained table, and
setup:upgrade applies declarative schema before data patches run, so a
constraint added in this same release would fail against that dirty
data before the patch ever got a chance to clean it up. A data patch
clears the table as a one-time cleanup; the batch_id+tag primary key
itself is deferred to a follow-up release. In the meantime, add() dedupes
against already-pending tags and filters null/empty ones at the
application level, since the constraint that would normally enforce
that isn't there yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@SamJUK
SamJUK force-pushed the feat/queue-storage-abstraction branch from ad5a3df to 8d9da1f Compare July 13, 2026 08:59
@SamJUK
SamJUK merged commit 15a922e into master Jul 13, 2026
7 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.

2 participants