diff --git a/src/fiber.cr b/src/fiber.cr index b34a8762037d..9f331c821fee 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -162,6 +162,10 @@ class Fiber @timeout_event.try &.free @timeout_select_action = nil + # Additional cleanup (avoid stale references) + @exec_recursive = nil + @exec_recursive_clone = nil + @alive = false {% unless flag?(:interpreted) %} Crystal::Scheduler.stack_pool.release(@stack) @@ -331,4 +335,41 @@ class Fiber @current_thread.lazy_get end {% end %} + + # :nodoc: + # + # Protects `Reference#inspect` and `Reference#pretty_print` against recursion. + def exec_recursive(object_id : UInt64, method : Symbol, &) : Bool + LibC.dprintf 2, "#{Fiber.current.name}: #{self.object_id}#exec_recursive(#{object_id}, #{method})\n" + + # NOTE: can't use `Set` because of prelude require order + hash = (@exec_recursive ||= Hash({UInt64, Symbol}, Nil).new) + + key = {object_id, method} + hash.put(key, nil) do + LibC.dprintf 2, "#{Fiber.current.name}: #{self.object_id}#exec_recursive(#{object_id}, #{method}): yield\n" + yield + hash.delete(key) + LibC.dprintf 2, "#{Fiber.current.name}: #{self.object_id}#exec_recursive(#{object_id}, #{method}): done\n" + return true + end + + LibC.dprintf 2, "#{Fiber.current.name}: #{self.object_id}#exec_recursive(#{object_id}, #{method}): done\n" + false + end + + # :nodoc: + # + # Protects `Reference#clone` implementations against recursion. See + # `Reference#exec_recursive_clone` for more details. + def exec_recursive_clone(object_id : UInt64, &) : Bool + # NOTE: can't use `Set` because of prelude require order + hash = (@exec_recursive_clone ||= Hash(UInt64, Nil).new) + hash.put(object_id, nil) do + yield + hash.delete(object_id) + return true + end + false + end end diff --git a/src/reference.cr b/src/reference.cr index f70697282fa0..798831e982f1 100644 --- a/src/reference.cr +++ b/src/reference.cr @@ -1,7 +1,3 @@ -{% if flag?(:preview_mt) %} - require "crystal/thread_local_value" -{% end %} - # `Reference` is the base class of classes you define in your program. # It is set as a class' superclass when you don't specify one: # @@ -180,54 +176,8 @@ class Reference io << '>' end - # :nodoc: - module ExecRecursive - # NOTE: can't use `Set` here because of prelude require order - alias Registry = Hash({UInt64, Symbol}, Nil) - - {% if flag?(:preview_mt) %} - @@exec_recursive = Crystal::ThreadLocalValue(Registry).new - {% else %} - @@exec_recursive = Registry.new - {% end %} - - def self.hash - {% if flag?(:preview_mt) %} - @@exec_recursive.get { Registry.new } - {% else %} - @@exec_recursive - {% end %} - end - end - private def exec_recursive(method, &) - hash = ExecRecursive.hash - key = {object_id, method} - hash.put(key, nil) do - yield - hash.delete(key) - return true - end - false - end - - # :nodoc: - module ExecRecursiveClone - alias Registry = Hash(UInt64, UInt64) - - {% if flag?(:preview_mt) %} - @@exec_recursive = Crystal::ThreadLocalValue(Registry).new - {% else %} - @@exec_recursive = Registry.new - {% end %} - - def self.hash - {% if flag?(:preview_mt) %} - @@exec_recursive.get { Registry.new } - {% else %} - @@exec_recursive - {% end %} - end + Fiber.current.exec_recursive(object_id, method) { yield } end # Helper method to perform clone by also checking recursiveness. @@ -249,12 +199,6 @@ class Reference # end # ``` private def exec_recursive_clone(&) - hash = ExecRecursiveClone.hash - clone_object_id = hash[object_id]? - unless clone_object_id - clone_object_id = yield(hash).object_id - hash.delete(object_id) - end - Pointer(Void).new(clone_object_id).as(self) + Fiber.current.exec_recursive_clone(object_id) { yield } end end