mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
[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"] ] Co-authored-by: Yusuke Endoh <mame@ruby-lang.org>
This commit is contained in:
parent
d21e4e76c4
commit
a7efcfbe93
2 changed files with 89 additions and 0 deletions
|
@ -132,4 +132,55 @@ module ObjectSpace
|
|||
return nil if output == :stdout
|
||||
ret
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# ObjectSpace.find_paths_to_unshareable_objects(obj) {|path| ... } -> nil
|
||||
# ObjectSpace.find_paths_to_unshareable_objects(obj) -> enumerator
|
||||
#
|
||||
# Finds all unshareable objects reachable from +obj+.
|
||||
#
|
||||
# When called with a block, yields an array representing the path from +obj+ to
|
||||
# each unshareable object found. The path includes all intermediate objects
|
||||
# traversed, ending with the unshareable object itself.
|
||||
#
|
||||
# If +obj+ itself is shareable, no paths are yielded.
|
||||
#
|
||||
# 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"]
|
||||
# ]
|
||||
def find_paths_to_unshareable_objects(obj)
|
||||
return to_enum(__method__, obj) if !block_given?
|
||||
|
||||
queue = [[obj, []]]
|
||||
visited = Set.new
|
||||
|
||||
while current = queue.shift
|
||||
current_obj, current_path = current
|
||||
visited.add(current_obj.object_id)
|
||||
|
||||
if !Ractor.shareable?(current_obj)
|
||||
yield current_path + [current_obj]
|
||||
|
||||
ObjectSpace.reachable_objects_from(current_obj).each do |reachable|
|
||||
if !reachable.is_a?(ObjectSpace::InternalObjectWrapper) && !visited.include?(reachable.object_id)
|
||||
queue.push([reachable, current_path + [current_obj]])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require "test/unit"
|
||||
require "objspace"
|
||||
|
||||
class TestObjSpaceRactor < Test::Unit::TestCase
|
||||
def test_tracing_does_not_crash
|
||||
|
@ -52,4 +53,41 @@ class TestObjSpaceRactor < Test::Unit::TestCase
|
|||
ractors.each(&:join)
|
||||
RUBY
|
||||
end
|
||||
|
||||
def test_find_paths_to_unshareable_objects
|
||||
# Direct shareable object
|
||||
assert_equal([], ObjectSpace.find_paths_to_unshareable_objects(1).to_a)
|
||||
|
||||
# Direct unshareable object
|
||||
assert_equal([["unfrozen"]], ObjectSpace.find_paths_to_unshareable_objects("unfrozen").to_a)
|
||||
|
||||
# Hash containing unshareable object
|
||||
obj = { a: 1, b: "frozen".freeze, c: "unfrozen" }
|
||||
paths = ObjectSpace.find_paths_to_unshareable_objects(obj).to_a
|
||||
assert_include(paths, [obj])
|
||||
assert_include(paths, [obj, "unfrozen"])
|
||||
|
||||
# Array containing unshareable object
|
||||
obj = [1, 2, "unfrozen", "frozen".freeze]
|
||||
paths = ObjectSpace.find_paths_to_unshareable_objects(obj).to_a
|
||||
assert_include(paths, [obj])
|
||||
assert_include(paths, [obj, "unfrozen"])
|
||||
|
||||
# Custom class
|
||||
klass = Class.new do
|
||||
attr_accessor :value
|
||||
end
|
||||
obj = klass.new
|
||||
obj.value = "unfrozen"
|
||||
paths = ObjectSpace.find_paths_to_unshareable_objects(obj).to_a
|
||||
assert_include(paths, [obj])
|
||||
assert_include(paths, [obj, "unfrozen"])
|
||||
|
||||
# Circular reference
|
||||
obj1 = { name: "obj1" }
|
||||
obj2 = { name: "obj2", ref: obj1 }
|
||||
obj1[:ref] = obj2
|
||||
paths = ObjectSpace.find_paths_to_unshareable_objects(obj1).to_a
|
||||
assert_include(paths, [obj1, obj2, "obj2"]) # does not circle back to obj1
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue