@@ -1914,9 +1914,10 @@ class ConfirmGarbageCollectWorkflow < Temporalio::Workflow::Definition
19141914 @initialized_count = 0
19151915 @finalized_count = 0
19161916 @weak_instance = nil
1917+ @strong_instance = nil
19171918
19181919 class << self
1919- attr_accessor :initialized_count , :finalized_count , :weak_instance
1920+ attr_accessor :initialized_count , :finalized_count , :weak_instance , :strong_instance
19201921
19211922 def create_finalizer
19221923 proc { @finalized_count += 1 }
@@ -1926,6 +1927,9 @@ def create_finalizer
19261927 def initialize
19271928 self . class . initialized_count += 1
19281929 self . class . weak_instance = WeakRef . new ( self )
1930+ # Uncomment this to cause test to fail
1931+ # self.class.strong_instance = self
1932+
19291933 ObjectSpace . define_finalizer ( self , self . class . create_finalizer )
19301934 end
19311935
@@ -1936,11 +1940,10 @@ def execute
19361940
19371941 def test_confirm_garbage_collect
19381942 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
1943+ skip ( 'Only Ruby 3.4+ has somewhat predictable eager GC' ) if major != 3 || minor < 4
19401944
19411945 # 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.
1946+ # when there is still an instance, uncomment the strong_instance set in the initialize of the workflow.
19441947
19451948 execute_workflow ( ConfirmGarbageCollectWorkflow ) do |handle |
19461949 # Wait until it is started
@@ -1950,18 +1953,31 @@ def test_confirm_garbage_collect
19501953 assert_equal 0 , ConfirmGarbageCollectWorkflow . finalized_count
19511954 end
19521955
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
1956+ # Perform a GC and confirm gone. There are cases in Ruby where dead stack slots leave the item around for a bit, so
1957+ # we check repeatedly for a bit (every 200ms for 10s). We can't use assert_eventually, because path doesn't show
1958+ # well.
1959+ start_time = Time . now
1960+ loop do
1961+ GC . start
1962+ # Break if the instance is gone
1963+ break unless ConfirmGarbageCollectWorkflow . weak_instance . weakref_alive?
1964+
1965+ # If this is last iteration, flunk w/ the path
1966+ if Time . now - start_time > 10
1967+ instance = ConfirmGarbageCollectWorkflow . weak_instance . __getobj__
1968+ path , cat = GCUtils . find_retaining_path_to ( instance . object_id , max_depth : 12 )
1969+ msg = GCUtils . annotated_path ( path , root_category : cat )
1970+ msg += "\n Path:\n #{ path . map { |p | " Item: #{ p } " } . join ( "\n " ) } "
1971+ # Also display any Thread/Fiber backtraces that are in the path
1972+ path . grep ( Thread ) . each do |thread |
1973+ msg += "\n Thread trace: #{ thread . backtrace . join ( "\n " ) } "
1974+ end
1975+ path . grep ( Fiber ) . each do |fiber |
1976+ msg += "\n Fiber trace: #{ fiber . backtrace . join ( "\n " ) } "
1977+ end
1978+ flunk msg
1979+ end
1980+ sleep ( 0.2 )
19651981 end
19661982 end
19671983
0 commit comments