dep-protobuf/ruby/tests/oom_test.rb
Protobuf Team Bot 43b6c37509 Handle ruby allocation failures gracefully
Prior to this change, the custom alloc function used with arenas would internally longjmp on allocation failure, which would then leak memory (as upb does not expect or permit longjmp from an alloc function, and has no way to free intermediate allocations).

PiperOrigin-RevId: 955635829
2026-07-28 21:08:09 -07:00

131 lines
4.3 KiB
Ruby

#!/usr/bin/ruby
#
# basic_test_pb.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'basic_test_pb'
require 'basic_test_proto2_pb'
require 'google/protobuf'
require 'test/unit'
return if defined?(JRUBY_VERSION) || Google::Protobuf::IMPLEMENTATION != :NATIVE
class OomTest < Test::Unit::TestCase
def is_available
Google::Protobuf::Internal.allocation_count_is_available
end
def reset_allocations
Google::Protobuf::Internal.allocation_count_reset
end
def get_allocations
Google::Protobuf::Internal.allocation_count_get
end
def fail_on_allocation(n)
Google::Protobuf::Internal.allocation_count_fail_on(n)
end
def many_allocs_scenario
msg = BasicTest::TestMessage.new
msg.optional_int32 = 123
msg.optional_int64 = 456
msg.optional_uint32 = 789
msg.optional_uint64 = 101112
msg.optional_bool = true
msg.optional_float = 1.5
msg.optional_double = 2.5
msg.optional_string = "hello"
msg.optional_bytes = "world\x00escape"
msg.optional_msg = BasicTest::TestMessage2.new(foo: 42)
100.times { |i| msg.repeated_int32 << i }
msg.repeated_int64 << 1000
msg.repeated_uint32 << 2000
msg.repeated_uint64 << 3000
msg.repeated_bool << false
msg.repeated_float << 3.5
msg.repeated_double << 4.5
msg.repeated_string << "foo"
msg.repeated_bytes << "bar"
msg.repeated_msg << BasicTest::TestMessage2.new(foo: 43)
serialized = BasicTest::TestMessage.encode(msg)
msg2 = BasicTest::TestMessage.decode(serialized)
msg3 = msg2.dup
msg4 = msg2.clone
msg5 = Google::Protobuf.deep_copy(msg2)
_ = msg5.optional_string
_ = msg5.optional_bytes
if defined?(BasicTestProto2::TestExtensions)
ext_msg = BasicTestProto2::TestExtensions.new
ext1 = Google::Protobuf::DescriptorPool.generated_pool.lookup('basic_test_proto2.optional_int32_extension')
ext2 = Google::Protobuf::DescriptorPool.generated_pool.lookup('basic_test_proto2.TestNestedExtension.test')
ext1.set(ext_msg, 42)
ext2.set(ext_msg, "hello")
ext_serialized = BasicTestProto2::TestExtensions.encode(ext_msg)
ext_msg2 = BasicTestProto2::TestExtensions.decode(ext_serialized)
ext_msg2_copy = Google::Protobuf.deep_copy(ext_msg2)
_val1 = ext1.get(ext_msg2_copy)
_val2 = ext2.get(ext_msg2_copy)
end
if defined?(BasicTestProto2::TestMessageSet)
mset_msg = BasicTestProto2::TestMessageSet.new
ext_mset1 = Google::Protobuf::DescriptorPool.generated_pool.lookup('basic_test_proto2.TestMessageSetExtension1.message_set_extension')
ext_mset2 = Google::Protobuf::DescriptorPool.generated_pool.lookup('basic_test_proto2.TestMessageSetExtension2.message_set_extension')
ext_mset1.set(mset_msg, BasicTestProto2::TestMessageSetExtension1.new(i: 123))
ext_mset2.set(mset_msg, BasicTestProto2::TestMessageSetExtension2.new(str: 'hello'))
mset_serialized = BasicTestProto2::TestMessageSet.encode(mset_msg)
mset_msg2 = BasicTestProto2::TestMessageSet.decode(mset_serialized)
mset_msg3 = Google::Protobuf.deep_copy(mset_msg2)
_val_mset1 = ext_mset1.get(mset_msg3).i
_val_mset2 = ext_mset2.get(mset_msg3).str
mset_unknown = BasicTestProto2::TestMessageSet.decode(
"\x0b\x10\x01\x1a\x03foo\x0c\x0b\x10\x02\x1a\x03bar\x0c".force_encoding("ASCII-8BIT")
)
_mset_unknown_copy = Google::Protobuf.deep_copy(mset_unknown)
end
if defined?(BasicTestProto2::TestExtensions)
empty = BasicTestProto2::TestExtensions.decode(serialized)
_empty_copy = Google::Protobuf.deep_copy(empty)
Google::Protobuf.discard_unknown(_empty_copy)
end
end
def test_oom
omit 'Requires Debug-only allocation_count API' unless is_available
# Warm up so we get a consistent set of allocations in the loop
many_allocs_scenario
reset_allocations
many_allocs_scenario
total = get_allocations
assert_operator total, :>, 0
total.times do |i|
reset_allocations
fail_on_allocation(i)
begin
many_allocs_scenario
rescue NoMemoryError
next
end
if get_allocations > i
flunk("NoMemoryError exception was expected at allocation #{i}, but completed with #{get_allocations} allocations.")
end
end
reset_allocations
end
end