FIX: add mutex around un/mark as solved

To prevent any race conditions when two users toggle the state of a reply.

(cf. https://meta.discourse.org/161104)
This commit is contained in:
Régis Hanol 2020-08-17 16:33:24 +02:00
parent e16784fd07
commit 9ecac7a3e2

View File

@ -77,6 +77,8 @@ SQL
def self.accept_answer!(post, acting_user, topic: nil)
topic ||= post.topic
DistributedMutex.synchronize("discourse_solved_toggle_answer_#{topic.id}") do
accepted_id = topic.custom_fields["accepted_answer_post_id"].to_i
if accepted_id > 0
@ -157,9 +159,12 @@ SQL
DiscourseEvent.trigger(:accepted_solution, post)
end
end
def self.unaccept_answer!(post, topic: nil)
topic ||= post.topic
DistributedMutex.synchronize("discourse_solved_toggle_answer_#{topic.id}") do
post.custom_fields["is_accepted_answer"] = nil
topic.custom_fields["accepted_answer_post_id"] = nil
@ -198,12 +203,13 @@ SQL
DiscourseEvent.trigger(:unaccepted_solution, post)
end
end
end
require_dependency "application_controller"
class DiscourseSolved::AnswerController < ::ApplicationController
def accept
limit_accepts
post = Post.find(params[:id].to_i)
@ -219,7 +225,6 @@ SQL
end
def unaccept
limit_accepts
post = Post.find(params[:id].to_i)
@ -234,12 +239,11 @@ SQL
end
def limit_accepts
unless current_user.staff?
return if current_user.staff?
RateLimiter.new(nil, "accept-hr-#{current_user.id}", 20, 1.hour).performed!
RateLimiter.new(nil, "accept-min-#{current_user.id}", 4, 30.seconds).performed!
end
end
end
DiscourseSolved::Engine.routes.draw do
post "/accept" => "answer#accept"
@ -477,7 +481,6 @@ SQL
end
topic.user_id == current_user.id && !topic.closed
end
end
@ -486,9 +489,7 @@ SQL
attributes :can_accept_answer, :can_unaccept_answer, :accepted_answer
def can_accept_answer
topic = (topic_view && topic_view.topic) || object.topic
if topic
if topic = (topic_view && topic_view.topic) || object.topic
return scope.can_accept_answer?(topic, object) && object.post_number > 1 && !accepted_answer
end
@ -496,8 +497,7 @@ SQL
end
def can_unaccept_answer
topic = (topic_view && topic_view.topic) || object.topic
if topic
if topic = (topic_view && topic_view.topic) || object.topic
scope.can_accept_answer?(topic, object) && (post_custom_fields["is_accepted_answer"] == 'true')
end
end