# frozen_string_literal: true require "active_support/core_ext/object/duplicable" module ThreadsafeAttributes def self.included(klass) klass.extend(ClassMethods) end module ClassMethods def threadsafe_attribute(*attrs) main_thread = Thread.main # remember this, because it could change after forking attrs.each do |attr| define_method attr do get_threadsafe_attribute(attr, main_thread) end define_method "#{attr}=" do |value| set_threadsafe_attribute(attr, value, main_thread) end define_method "#{attr}_defined?" do threadsafe_attribute_defined?(attr, main_thread) end end end end private def get_threadsafe_attribute(name, main_thread) if threadsafe_attribute_defined_by_thread?(name, Thread.current) get_threadsafe_attribute_by_thread(name, Thread.current) elsif threadsafe_attribute_defined_by_thread?(name, main_thread) value = get_threadsafe_attribute_by_thread(name, main_thread) value = value.dup if value.duplicable? set_threadsafe_attribute_by_thread(name, value, Thread.current) value end end def set_threadsafe_attribute(name, value, main_thread) set_threadsafe_attribute_by_thread(name, value, Thread.current) unless threadsafe_attribute_defined_by_thread?(name, main_thread) set_threadsafe_attribute_by_thread(name, value, main_thread) end end def threadsafe_attribute_defined?(name, main_thread) threadsafe_attribute_defined_by_thread?(name, Thread.current) || ((Thread.current != main_thread) && threadsafe_attribute_defined_by_thread?(name, main_thread)) end def get_threadsafe_attribute_by_thread(name, thread) thread.thread_variable_get "active.resource.#{name}.#{self.object_id}" end def set_threadsafe_attribute_by_thread(name, value, thread) thread.thread_variable_set "active.resource.#{name}.#{self.object_id}.defined", true thread.thread_variable_set "active.resource.#{name}.#{self.object_id}", value end def threadsafe_attribute_defined_by_thread?(name, thread) thread.thread_variable_get "active.resource.#{name}.#{self.object_id}.defined" end end