lib/finite_machine/threadable.rb
# frozen_string_literal: true
require_relative "two_phase_lock"
module FiniteMachine
# A mixin to allow instance methods to be synchronized
module Threadable
module InstanceMethods
# Exclusive lock
#
# @return [nil]
#
# @api public
def sync_exclusive(&block)
TwoPhaseLock.synchronize(:EX, &block)
end
# Shared lock
#
# @return [nil]
#
# @api public
def sync_shared(&block)
TwoPhaseLock.synchronize(:SH, &block)
end
end
# Module hook
#
# @return [nil]
#
# @api private
def self.included(base)
base.extend ClassMethods
base.module_eval do
include InstanceMethods
end
end
private_class_method :included
module ClassMethods
include InstanceMethods
# Defines threadsafe attributes for a class
#
# @example
# attr_threadable :errors, :events
#
# @example
# attr_threadable :errors, default: []
#
# @return [nil]
#
# @api public
def attr_threadsafe(*attrs)
opts = attrs.last.is_a?(::Hash) ? attrs.pop : {}
default = opts.fetch(:default, nil)
attrs.flatten.each do |attr|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{attr}(*args)
value = args.shift
if value
self.#{attr} = value
elsif instance_variables.include?(:@#{attr})
sync_shared { @#{attr} }
elsif #{!default.nil?}
sync_shared { instance_variable_set(:@#{attr}, #{default}) }
end
end
alias_method '#{attr}?', '#{attr}'
def #{attr}=(value)
sync_exclusive { @#{attr} = value }
end
RUBY_EVAL
end
end
end
end # Threadable
end # FiniteMachine