diff --git a/lib/distributed_mutex.rb b/lib/distributed_mutex.rb index 36d07e9f0e3..6a0e0af8cd7 100644 --- a/lib/distributed_mutex.rb +++ b/lib/distributed_mutex.rb @@ -1,6 +1,10 @@ # Cross-process locking using Redis. class DistributedMutex + def self.synchronize(key, redis=nil, &blk) + self.new(key, redis).synchronize(&blk) + end + def initialize(key, redis=nil) @key = key @redis = redis || $redis diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 957d5bf0cd5..6842d92c39b 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -3,6 +3,7 @@ require_dependency 'rate_limiter' require_dependency 'topic_creator' require_dependency 'post_jobs_enqueuer' +require_dependency 'distributed_mutex' class PostCreator @@ -54,7 +55,7 @@ class PostCreator @topic = nil @post = nil - Post.transaction do + transaction do setup_topic setup_post rollback_if_host_spam_detected @@ -66,21 +67,24 @@ class PostCreator update_user_counts create_embedded_topic - publish ensure_in_allowed_users if guardian.is_staff? @post.advance_draft_sequence @post.save_reply_relationships end - if @post + if @post && @post.errors.empty? + publish PostAlerter.post_created(@post) unless @opts[:import_mode] - handle_spam unless @opts[:import_mode] track_latest_on_category enqueue_jobs BadgeGranter.queue_badge_grant(Badge::Trigger::PostRevision, post: @post) end + if @post && (@post.errors.present? || @spam) + handle_spam unless @opts[:import_mode] + end + @post end @@ -111,6 +115,18 @@ class PostCreator protected + def transaction(&blk) + Post.transaction do + if new_topic? + blk.call + else + # we need to ensure post_number is monotonically increasing with no gaps + # so we serialize creation to avoid needing rollbacks + DistributedMutex.synchronize("topic_id_#{@opts[:topic_id]}", &blk) + end + end + end + # You can supply an `embed_url` for a post to set up the embedded relationship. # This is used by the wp-discourse plugin to associate a remote post with a # discourse post.