Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handle receivers counter race condition #4290

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 15 additions & 13 deletions src/oscar/apps/analytics/receivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,32 @@ def _update_counter(model, field_name, filter_kwargs, increment=1):
Efficiently updates a counter field by a given increment. Uses Django's
update() call to fetch and update in one query.

TODO: This has a race condition, we should use UPSERT here

:param model: The model class of the recording model
:param field_name: The name of the field to update
:param filter_kwargs: Parameters to the ORM's filter() function to get the
correct instance
"""
try:
record = model.objects.filter(**filter_kwargs)
affected = record.update(**{field_name: F(field_name) + increment})
if not affected:
filter_kwargs[field_name] = increment
record = model.objects.filter(**filter_kwargs)
affected = record.update(**{field_name: F(field_name) + increment})
if not affected:
filter_kwargs[field_name] = increment
try:
model.objects.create(**filter_kwargs)
except IntegrityError: # pragma: no cover
# get_or_create has a race condition (we should use upsert in supported)
# databases. For now just ignore these errors
logger.error("IntegrityError when updating analytics counter for %s", model)
except IntegrityError: # pragma: no cover
# can happen in rare race condition
logger.warning(
"IntegrityError when updating analytics counter for %s", model
)
record.update(**{field_name: F(field_name) + increment})


def _record_products_in_order(order):
# surely there's a way to do this without causing a query for each line?
for line in order.lines.all():
_update_counter(
ProductRecord, "num_purchases", {"product": line.product}, line.quantity
ProductRecord,
"num_purchases",
{"product_id": line.product_id},
line.quantity,
)


Expand Down