Skip to content

Commit 1b20e43

Browse files
committed
[Feature #21028] ObjectSpace#find_paths_to_unshareable_objects
Add a method to find paths to Ractor-unshareable objects which can be traced from an object. Example: class Container attr_reader :value def initialize(value) @value = value end end mutable_string = "hello" container = Container.new(mutable_string) pp ObjectSpace.find_paths_to_unshareable_objects(container).to_a #=> [ [#<Container:0x00007fc35843e388 @value="hello">], [#<Container:0x00007fc35843e388 @value="hello">, "hello"] ]
1 parent d21e4e7 commit 1b20e43

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

ext/objspace/lib/objspace.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,55 @@ def dump_shapes(output: :file, since: 0)
132132
return nil if output == :stdout
133133
ret
134134
end
135+
136+
# call-seq:
137+
# ObjectSpace.find_path_to_unshareable_object(obj) {|path| ... } -> nil
138+
# ObjectSpace.find_path_to_unshareable_object(obj) -> enumerator
139+
#
140+
# Finds all unshareable objects reachable from +obj+.
141+
#
142+
# When called with a block, yields an array representing the path from +obj+ to
143+
# each unshareable object found. The path includes all intermediate objects
144+
# traversed, ending with the unshareable object itself.
145+
#
146+
# If +obj+ itself is shareable, no paths are yielded.
147+
#
148+
# Example:
149+
#
150+
# class Container
151+
# attr_reader :value
152+
# def initialize(value)
153+
# @value = value
154+
# end
155+
# end
156+
#
157+
# mutable_string = "hello"
158+
# container = Container.new(mutable_string)
159+
#
160+
# pp ObjectSpace.find_path_to_unshareable_object(container).to_a
161+
# #=> [
162+
# [#<Container:0x00007fc35843e388 @value="hello">],
163+
# [#<Container:0x00007fc35843e388 @value="hello">, "hello"]
164+
# ]
165+
def find_path_to_unshareable_object(obj)
166+
return to_enum(__method__, obj) if !block_given?
167+
168+
queue = [[obj, []]]
169+
visited = Set.new
170+
171+
while current = queue.shift
172+
current_obj, current_path = current
173+
visited.add(current_obj.object_id)
174+
175+
if !Ractor.shareable?(current_obj)
176+
yield current_path + [current_obj]
177+
178+
ObjectSpace.reachable_objects_from(current_obj).each do |reachable|
179+
if !reachable.is_a?(ObjectSpace::InternalObjectWrapper) && !visited.include?(reachable.object_id)
180+
queue.push([reachable, current_path + [current_obj]])
181+
end
182+
end
183+
end
184+
end
185+
end
135186
end

test/objspace/test_ractor.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "test/unit"
2+
require "objspace"
23

34
class TestObjSpaceRactor < Test::Unit::TestCase
45
def test_tracing_does_not_crash
@@ -52,4 +53,41 @@ def fin
5253
ractors.each(&:join)
5354
RUBY
5455
end
56+
57+
def test_find_path_to_unshareable_object
58+
# Direct shareable object
59+
assert_equal([], ObjectSpace.find_path_to_unshareable_object(1).to_a)
60+
61+
# Direct unshareable object
62+
assert_equal([["unfrozen"]], ObjectSpace.find_path_to_unshareable_object("unfrozen").to_a)
63+
64+
# Hash containing unshareable object
65+
obj = { a: 1, b: "frozen".freeze, c: "unfrozen" }
66+
paths = ObjectSpace.find_path_to_unshareable_object(obj).to_a
67+
assert_include(paths, [obj])
68+
assert_include(paths, [obj, "unfrozen"])
69+
70+
# Array containing unshareable object
71+
obj = [1, 2, "unfrozen", "frozen".freeze]
72+
paths = ObjectSpace.find_path_to_unshareable_object(obj).to_a
73+
assert_include(paths, [obj])
74+
assert_include(paths, [obj, "unfrozen"])
75+
76+
# Custom class
77+
klass = Class.new do
78+
attr_accessor :value
79+
end
80+
obj = klass.new
81+
obj.value = "unfrozen"
82+
paths = ObjectSpace.find_path_to_unshareable_object(obj).to_a
83+
assert_include(paths, [obj])
84+
assert_include(paths, [obj, "unfrozen"])
85+
86+
# Circular reference
87+
obj1 = { name: "obj1" }
88+
obj2 = { name: "obj2", ref: obj1 }
89+
obj1[:ref] = obj2
90+
paths = ObjectSpace.find_path_to_unshareable_object(obj1).to_a
91+
assert_include(paths, [obj1, obj2, "obj2"]) # does not circle back to obj1
92+
end
5593
end

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy