AJMansfield
AJMansfield

Reputation: 4129

How to avoid race condition in updating model field to newest timestamp

I need to keep track of the most recent time a user was mentioned in a post, and update this field each time a new post is created based on it's post time.

My current code looks like this:

from django.db.models.signals import post_save
from django.dispatch import receiver
from messageboard.models import Post

@receiver(post_save, sender=Post)
def user_last_mentioned_updater(sender, instance, **kwargs):
    for users in instance.mentions:
        user.last_mentioned = max(user.last_mentioned, instance.timestamp)
        user.save()

However, if two posts are processed concurrently, this could potentially leave the last_mentioned field at the timestamp of the earlier post.

Unfortunately, F doesn't support the max operation, when I try it I get a TypeError: unorderable types: datetime.datetime() > F():

user.last_mentioned = max(F('last_mentioned'), instance.timestamp)

How can I avoid this race condition?

If it matters, for the moment I'm using Postgresql for the ORM, although this may be subject to change.

Upvotes: 2

Views: 749

Answers (1)

Kevin Christopher Henry
Kevin Christopher Henry

Reputation: 48952

Here's a version that should be free of race conditions and more efficient:

@receiver(post_save, sender=Post)
def user_last_mentioned_updater(sender, instance, **kwargs)
    User.objects.filter(
        id__in=[u.id for u in instance.mentions],
        last_mentioned__lt=instance.timestamp,
    ).update(last_mentioned=instance.timestamp)

That is, we select the mentioned users who's timestamps need updating, and update them, all in a single SQL statement.

Upvotes: 2

Related Questions