Fix a cache invalidation issue with Distributions serving publications indirectly via repository_version - #8008
Conversation
fbcdd0c to
d817c09
Compare
023e815 to
33db05a
Compare
|
Not quite ready for review yet. |
2b2fe3f to
a4a3d27
Compare
| return | ||
| DistributedPublication(distribution=self, publication=pub).save() | ||
| # Check if this publication is already the active one | ||
| already_current = DistributedPublication.objects.filter( |
There was a problem hiding this comment.
Should this whole block be in a transaction?
a4a3d27 to
ca7d995
Compare
0f77dea to
9837ef2
Compare
9b956cc to
5391c08
Compare
gerrod3
left a comment
There was a problem hiding this comment.
The new cache invalidation scenario looks correct. I don't know enough about DistributedPublications to comment on the code changes there.
| @pytest.mark.django_db | ||
| class TestCacheInvalidationOnDistributionUpdate: |
There was a problem hiding this comment.
Do the cache invalidation tests belong here? Should they be in their own file?
There was a problem hiding this comment.
It would be reasonable to separate them but they're also somewhat coupled in the sense that a significant amount of logic is basically shared between the two and the scenarios are basically the same. If you feel strongly we can do that.
| assert dp_pub1_reactivated.pk == dp_pub1_old.pk, ( | ||
| "Should be the same DP instance, reactivated" | ||
| ) | ||
| assert dp_pub1_reactivated.expires_at is None, "Should clear expires_at when reactivating" |
There was a problem hiding this comment.
I did an AI assisted review and it indicated a possible flaw where the reactivation path can lead to two active dps. Can you assert in this test that, after switching back to pub1, we only have one active dp for that distribution?
| DistributedPublication.objects.filter(distribution=self, publication=pub) | ||
| .select_for_update() | ||
| .first() | ||
| ) |
There was a problem hiding this comment.
I don't think this is helping with the race condition. If I understand correctly, the goal is ensure the invariant of only 1 active DP.
But the select for update locks a row only if it exists.
So let's say there is no active DP. It can happen the following in a scenario with 2 concurrent writers:
-
$W_1$ and$W_2$ get past the fast check and see no active DP -
$W_1$ starts transcation. Evaluates dp=None (no row to lock on) -
$W_2$ starts transcation. Evaluates dp=None (no row to lock on) -
$W_1$ creates$DP_1$ . Creation hook sees no other active DP to disable and activates itself -
$W_2$ creates$DP_2$ . Creation hook sees no other active DP to disable ($W_1$ transcation not done yet) and activates itself -
$W_1$ ends transaction.$DP_1$ commited -
$W_2$ ends transaction.$DP_2$ commited
There was a problem hiding this comment.
A sample test:
@pytest.mark.django_db(transaction=True)
def test_concurrent_set_distributed_publication_leaves_single_winner():
"""Racing ."""
def race():
barrier.wait()
dist.set_distributed_publication()
for i in range(5):
pub = pub_factory()
dist = dist_factory(pub=pub)
DistributedPublication.objects.filter(distribution=dist).delete()
barrier = threading.Barrier(2)
threads = [threading.Thread(target=race) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
dps = DistributedPublication.objects.filter(distribution=dist, publication=pub)
dps_count = dps.filter(expires_at__isnull=True).count()
assert dps_count == 1, (
f"[{i=}] exactly one DP must remain active even if both threads inserted a row"
)There was a problem hiding this comment.
This was probably not introduced with this changes, so if you just want to not handle it right now (maybe simplify this portion) and focus on the issue about case of serving repository_versions indirectly.
41b29b4 to
98a1f56
Compare
|
I've pushed a new commit based on your suggestions. Claude reasoning:
|
…ions Distributions serving via `repository_version` were missed by cache invalidation and DistributedPublication tracking when publications were created or deleted. Also fixes DistributedPublication to reactivate (clear `expires_at`) when switching back to a recently-superseded publication, instead of creating a duplicate record. closes pulp#7993 Assisted-By: Claude Opus 4.6
98a1f56 to
402694f
Compare
Make sure we flush the cache (and update DistributedPublication) for
publications which were indirectly served via
Distribution.repository_version, rather than directly.
closes #7993