1
0
Fork 0
mirror of https://github.com/aasm/aasm synced 2023-03-27 23:22:41 -04:00

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
This commit is contained in:
Guewen Baconnier 2022-03-22 13:36:46 +01:00 committed by GitHub
parent f8cf86510e
commit 1a1b836ded
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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