From 1a1b836ded242f56852db078f121d93a41ba7c22 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 22 Mar 2022 13:36:46 +0100 Subject: [PATCH] Fix multi-threading bug not firing success callbacks (#778) `@valid_transitions` is shared across threads, when a thread consumes a transition, another thread which should execute the same transition for another model instance may ignore its success callback. This change avoid to share the same hash of transition across different model instances. Each instance will have its own hash of transitions. The hash itself is not subject to thread race conditions as the object ids are unique. Also, the transitions for an object_id are cleaned after the transition have been fired, otherwise the hash would grow infinitely. See https://github.com/aasm/aasm/issues/448#issuecomment-377922368 for an example of code to reproduce the multi-thread bug. Fixes #448 --- lib/aasm/core/event.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/aasm/core/event.rb b/lib/aasm/core/event.rb index 77747f8..316b127 100644 --- a/lib/aasm/core/event.rb +++ b/lib/aasm/core/event.rb @@ -10,7 +10,7 @@ module AASM::Core @name = name @state_machine = state_machine @transitions = [] - @valid_transitions = {} + @valid_transitions = Hash.new { |h, k| h[k] = {} } @guards = Array(options[:guard] || options[:guards] || options[:if]) @unless = Array(options[:unless]) #TODO: This could use a better name @default_display_name = name.to_s.gsub(/_/, ' ').capitalize @@ -79,8 +79,9 @@ module AASM::Core def fire_transition_callbacks(obj, *args) from_state = obj.aasm(state_machine.name).current_state - transition = @valid_transitions[from_state] - @valid_transitions[from_state].invoke_success_callbacks(obj, *args) if transition + transition = @valid_transitions[obj.object_id][from_state] + transition.invoke_success_callbacks(obj, *args) if transition + @valid_transitions.delete(obj.object_id) end def ==(event) @@ -153,7 +154,7 @@ module AASM::Core result = transition else result = to_state || Array(transition.to).first - Array(transition.to).each {|to| @valid_transitions[to] = transition } + Array(transition.to).each {|to| @valid_transitions[obj.object_id][to] = transition } transition.execute(obj, *args) end