Let's say I have the following ruby code that uses FFI library:
class SimpleStruct < FFI::Struct
layout :value, :pointer
end
class Test1
def self.foo(s)
result = FFI::MemoryPointer.from_string(s)
result
end
def self.bar(s)
simple_struct = SimpleStruct.new
value = FFI::MemoryPointer.from_string(s)
simple_struct[:value] = value
simple_struct
end
end
class Test2
def self.testing
a = Test1.foo('test')
b = Test1.bar('test')
puts a.read_string, b[:value].read_string
end
end
The FFI wiki mentions When a MemoryPointer goes out of scope, the memory is freed up as part of the garbage collection process
. In the Test1
class above, foo
method returns a MemoryPointer and bar
method returns an FFI struct that holds MemoryPointer. The testing
method in Test2
class calls these methods and stores the returned values in variable a
and b
respectively.
My question is when the MemoryPointer created inside foo
and bar
method will be garbage collected in this case? Is it when the MemoryPointer variable goes out of scope in foo
and bar
methods or when the local variable a
or b
(that references the MemoryPointer returned from the foo
/bar
methods) goes out of scope in testing
method?
I believe FFI::Struct#[]=
takes care of this. I didn't check it in the sources, but I've added some checks into your code and it looks so.
require 'ffi'
class SimpleStruct < FFI::Struct
layout :value, :pointer
end
class Test1
def self.foo(s)
puts "foo is called"
result = FFI::MemoryPointer.from_string(s)
ObjectSpace.define_finalizer(result, proc { puts "foo result is garbage collected" })
result
end
def self.bar(s)
puts "bar is called"
simple_struct = SimpleStruct.new
ObjectSpace.define_finalizer(s, proc { puts "bar result is garbage collected" })
value = FFI::MemoryPointer.from_string(s)
ObjectSpace.define_finalizer(s, proc { puts "bar result[:value] is garbage collected" })
simple_struct[:value] = value
simple_struct
end
def self.baz(s)
puts "baz is called"
simple_struct = {}
ObjectSpace.define_finalizer(s, proc { puts "baz result is garbage collected" })
value = FFI::MemoryPointer.from_string(s)
ObjectSpace.define_finalizer(s, proc { puts "baz result[:value] is garbage collected" })
simple_struct[:value] = value
simple_struct
end
end
class Test2
def self.testing
puts "testing is started"
a = Test1.foo('foo')
b = Test1.bar('bar')
c = Test1.baz('baz')
puts a.read_string, b[:value].read_string, c[:value].read_string
puts "testing is finished"
end
end
GC.stress
Test2.testing
# testing is started
# foo is called
# bar is called
# baz is called
# foo
# bar
# baz
# testing is finished
# baz result is garbage collected
# baz result[:value] is garbage collected
# bar result is garbage collected
# bar result[:value] is garbage collected
# foo result is garbage collected
A pointer is only garbage collected when the struct is garbage-collected, as is the case with a hash.