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

Ever since AASM supported multiple named state machines in a single class, it has accessed and stored its state machines in class variables. However accesses to the state machines are not thread-safe causing exceptions when multiple threads attempt to access the same state machine at the same time during lazy initialization. Using a Concurrent Map makes calls to state machines, and lazy initialization thread safe.
76 lines
2.1 KiB
Ruby
76 lines
2.1 KiB
Ruby
require 'concurrent'
|
|
module AASM
|
|
class StateMachineStore
|
|
@stores = Concurrent::Map.new
|
|
|
|
class << self
|
|
def stores
|
|
@stores
|
|
end
|
|
|
|
# do not overwrite existing state machines, which could have been created by
|
|
# inheritance, see AASM::ClassMethods method inherited
|
|
def register(klass, overwrite = false, state_machine = nil)
|
|
raise "Cannot register #{klass}" unless klass.is_a?(Class)
|
|
|
|
case name = template = overwrite
|
|
when FalseClass then stores[klass.to_s] ||= new
|
|
when TrueClass then stores[klass.to_s] = new
|
|
when Class then stores[klass.to_s] = stores[template.to_s].clone
|
|
when Symbol then stores[klass.to_s].register(name, state_machine)
|
|
when String then stores[klass.to_s].register(name, state_machine)
|
|
else raise "Don't know what to do with #{overwrite}"
|
|
end
|
|
end
|
|
alias_method :[]=, :register
|
|
|
|
def fetch(klass, fallback = nil)
|
|
stores[klass.to_s] || fallback && begin
|
|
match = klass.ancestors.find do |ancestor|
|
|
ancestor.include? AASM and stores[ancestor.to_s]
|
|
end
|
|
|
|
stores[match.to_s]
|
|
end
|
|
end
|
|
alias_method :[], :fetch
|
|
|
|
def unregister(klass)
|
|
stores.delete(klass.to_s)
|
|
end
|
|
end
|
|
|
|
def initialize
|
|
@machines = Concurrent::Map.new
|
|
end
|
|
|
|
def clone
|
|
StateMachineStore.new.tap do |store|
|
|
@machines.each_pair do |name, machine|
|
|
store.register(name, machine.clone)
|
|
end
|
|
end
|
|
end
|
|
|
|
def machine(name)
|
|
@machines[name.to_s]
|
|
end
|
|
alias_method :[], :machine
|
|
|
|
def machine_names
|
|
@machines.keys
|
|
end
|
|
alias_method :keys, :machine_names
|
|
|
|
def register(name, machine, force = false)
|
|
raise "Cannot use #{name.inspect} for machine name" unless name.is_a?(Symbol) or name.is_a?(String)
|
|
raise "Cannot use #{machine.inspect} as a machine" unless machine.is_a?(AASM::StateMachine)
|
|
|
|
if force
|
|
@machines[name.to_s] = machine
|
|
else
|
|
@machines[name.to_s] ||= machine
|
|
end
|
|
end
|
|
end
|
|
end
|