FIX: avoid race condition when setting user status (#19817)

We caught it in logs, race condition led to this error:

    ActiveRecord::RecordNotUnique 
    (PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "user_statuses_pkey"
    DETAIL:  Key (user_id)=(15) already exists.)


The reason the problem happened was that we were checking if a user has status and if not inserting status:

    if user_status
      ...
    else
      self.user_status = UserStatus.create!(status)
    end

The problem is that it's possible that another request will insert status just after we check if status exists and just before our request call `UserStatus.create!(status)`. Using `upsert` fixes the problem because under the hood `upsert` generates the only SQL request that uses "INSERT ... ON CONFLICT DO UPDATE". So we do everything in one SQL query, and that query takes care of resolving possible conflicts.
This commit is contained in:
Andrei Prigorshnev 2023-02-06 18:56:28 +04:00 committed by GitHub
parent 156e04515f
commit 84e13e9b1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 14 additions and 7 deletions

View File

@ -1790,14 +1790,17 @@ class User < ActiveRecord::Base
end
def set_status!(description, emoji, ends_at = nil)
status = { description: description, emoji: emoji, set_at: Time.zone.now, ends_at: ends_at }
if user_status
user_status.update!(status)
else
self.user_status = UserStatus.create!(status)
end
status = {
description: description,
emoji: emoji,
set_at: Time.zone.now,
ends_at: ends_at,
user_id: id,
}
validate_status!(status)
UserStatus.upsert(status)
reload_user_status
publish_user_status(user_status)
end
@ -2175,6 +2178,10 @@ class User < ActiveRecord::Base
)
SQL
end
def validate_status!(status)
UserStatus.new(status).validate!
end
end
# == Schema Information