@@ -1914,9 +1914,11 @@ class ConfirmGarbageCollectWorkflow < Temporalio::Workflow::Definition
19141914 @initialized_count = 0
19151915 @finalized_count = 0
19161916 @weak_instance = nil
1917+ @strong_instance = nil
1918+ @instance_object_id = nil
19171919
19181920 class << self
1919- attr_accessor :initialized_count , :finalized_count , :weak_instance
1921+ attr_accessor :initialized_count , :finalized_count , :weak_instance , :strong_instance , :instance_object_id
19201922
19211923 def create_finalizer
19221924 proc { @finalized_count += 1 }
@@ -1926,6 +1928,10 @@ def create_finalizer
19261928 def initialize
19271929 self . class . initialized_count += 1
19281930 self . class . weak_instance = WeakRef . new ( self )
1931+ # Uncomment this to cause test to fail
1932+ # self.class.strong_instance = self
1933+ self . class . instance_object_id = object_id
1934+
19291935 ObjectSpace . define_finalizer ( self , self . class . create_finalizer )
19301936 end
19311937
@@ -1936,11 +1942,10 @@ def execute
19361942
19371943 def test_confirm_garbage_collect
19381944 major , minor = RUBY_VERSION . split ( '.' ) . take ( 2 ) . map ( &:to_i )
1939- skip ( 'Only Ruby 3.4+ has predictable eager GC' ) if major != 3 || minor < 4
1945+ skip ( 'Only Ruby 3.4+ has somewhat predictable eager GC' ) if major != 3 || minor < 4
19401946
19411947 # This test confirms the workflow instance is reliably GC'd when workflow/worker done. To confirm the test fails
1942- # when there is still an instance, make a "strong_instance" singleton attribute and assign "self" to it in
1943- # initialize and confirm this calls flunk later.
1948+ # when there is still an instance, uncomment the strong_instance set in the initialize of the workflow.
19441949
19451950 execute_workflow ( ConfirmGarbageCollectWorkflow ) do |handle |
19461951 # Wait until it is started
@@ -1950,18 +1955,30 @@ def test_confirm_garbage_collect
19501955 assert_equal 0 , ConfirmGarbageCollectWorkflow . finalized_count
19511956 end
19521957
1953- # Perform a GC and confirm gone
1954- GC . start
1955- begin
1956- # Access instance and assert that it fails when a method is called on it as expected
1957- instance = ConfirmGarbageCollectWorkflow . weak_instance . __getobj__
1958-
1959- # Print out the path still holding it
1960- path , cat = GCUtils . find_retaining_path_to ( instance . object_id , max_depth : 12 )
1961- GCUtils . print_annotated_path ( path , root_category : cat )
1962- flunk
1963- rescue WeakRef ::RefError
1964- # Expected
1958+ # Perform a GC and confirm gone. There are cases in Ruby where dead stack slots leave the item around for a bit, so
1959+ # we check repeatedly for a bit (every 200ms for 10s). We can't use assert_eventually, because path doesn't show
1960+ # well.
1961+ start_time = Time . now
1962+ loop do
1963+ GC . start
1964+ # Break if the instance is gone
1965+ break unless ConfirmGarbageCollectWorkflow . weak_instance . weakref_alive?
1966+
1967+ # If this is last iteration, flunk w/ the path
1968+ if Time . now - start_time > 10
1969+ path , cat = GCUtils . find_retaining_path_to ( ConfirmGarbageCollectWorkflow . instance_object_id , max_depth : 12 )
1970+ msg = GCUtils . annotated_path ( path , root_category : cat )
1971+ msg += "\n Path:\n #{ path . map { |p | " Item: #{ p } " } . join ( "\n " ) } "
1972+ # Also display any Thread/Fiber backtraces that are in the path
1973+ path . grep ( Thread ) . each do |thread |
1974+ msg += "\n Thread trace: #{ thread . backtrace . join ( "\n " ) } "
1975+ end
1976+ path . grep ( Fiber ) . each do |fiber |
1977+ msg += "\n Fiber trace: #{ fiber . backtrace . join ( "\n " ) } "
1978+ end
1979+ flunk msg
1980+ end
1981+ sleep ( 0.2 )
19651982 end
19661983 end
19671984
0 commit comments