]> cat aescling's git repositories - mastodon.git/commitdiff
Avoid race condition when streaming deleted statuses (#10280)
authorThibG <thib@sitedethib.com>
Sat, 16 Mar 2019 19:18:47 +0000 (20:18 +0100)
committerEugen Rochko <eugen@zeonfederated.com>
Sat, 16 Mar 2019 19:18:47 +0000 (20:18 +0100)
* Avoid race condition when streaming deleted statuses

* Move redis lock to DistributionWorker to avoid extra Redis value

app/services/remove_status_service.rb
app/workers/distribution_worker.rb

index 98875429d12fbed7c93b66ded91deb85f6987fc2..747f209f3320b8f9b54304fddc30725bc0f5f098 100644 (file)
@@ -14,16 +14,22 @@ class RemoveStatusService < BaseService
     @stream_entry = status.stream_entry
     @options      = options
 
-    remove_from_self if status.account.local?
-    remove_from_followers
-    remove_from_lists
-    remove_from_affected
-    remove_reblogs
-    remove_from_hashtags
-    remove_from_public
-    remove_from_media if status.media_attachments.any?
-
-    @status.destroy!
+    RedisLock.acquire(lock_options) do |lock|
+      if lock.acquired?
+        remove_from_self if status.account.local?
+        remove_from_followers
+        remove_from_lists
+        remove_from_affected
+        remove_reblogs
+        remove_from_hashtags
+        remove_from_public
+        remove_from_media if status.media_attachments.any?
+
+        @status.destroy!
+      else
+        raise Mastodon::RaceConditionError
+      end
+    end
 
     # There is no reason to send out Undo activities when the
     # cause is that the original object has been removed, since
@@ -156,4 +162,8 @@ class RemoveStatusService < BaseService
     redis.publish('timeline:public:media', @payload)
     redis.publish('timeline:public:local:media', @payload) if @status.local?
   end
+
+  def lock_options
+    { redis: Redis.current, key: "distribute:#{@status.id}" }
+  end
 end
index f423d43ae58df1cccd560153b368e7f6e4c78e03..4e20ef31bfc0155054d74f86f316ca83aa688722 100644 (file)
@@ -4,7 +4,13 @@ class DistributionWorker
   include Sidekiq::Worker
 
   def perform(status_id)
-    FanOutOnWriteService.new.call(Status.find(status_id))
+    RedisLock.acquire(redis: Redis.current, key: "distribute:#{status_id}") do |lock|
+      if lock.acquired?
+        FanOutOnWriteService.new.call(Status.find(status_id))
+      else
+        raise Mastodon::RaceConditionError
+      end
+    end
   rescue ActiveRecord::RecordNotFound
     true
   end