У меня есть код с утечкой памяти в приложении Sinatra на Ruby 2.4.4, и я могу воспроизвести его в irb, хотя он не совсем стабилен, и мне интересно, есть ли у других такая же проблема. Это происходит при интерполяции большой строки внутри литерала регулярного выражения:
class Leak
STR = "RANDOM|STUFF|HERE|UNTIL|YOU|GET|TIRED|OF|TYPING|AND|ARE|SATISFIED|THAT|IT|WILL|LEAK|ENOUGH|MEMORY|TO|NOTICE"*100
def test
100.times { /#{STR}/i }
end
end
t = Leak.new
t.test # If I run this a few times, it will start leaking about 5MB each time
Теперь, если я запускаю GC.start
после этого, он обычно очищает последние 5 МБ (или столько, сколько он использовал), а затем t.test
будет использовать только несколько КБ, затем почти МБ, затем пару МБ, а затем обратно 5 МБ каждый раз, и снова GC.start
соберет только последние 5.
Альтернативный способ получить тот же результат без утечки памяти — заменить /#{STR}/i
на RegExp.new(STR, true)
. Кажется, это хорошо работает для меня.
Является ли это законной утечкой памяти в Ruby или я делаю что-то не так?
ОБНОВЛЕНИЕ: Хорошо, может быть, я неправильно понял это. Я смотрел на использование памяти док-контейнера после запуска GC.start
, который иногда падал, но поскольку Ruby не всегда освобождает память, которую он не использует, я думаю, что это может быть просто то, что Ruby использует эту память, а затем, даже если она не сохраняется, она все равно не освобождает память обратно в ОС. Используя гем MemoryProfiler, я вижу, что total_retained, даже после его запуска несколько раз, равен 0.
Основная проблема здесь заключалась в том, что у нас были сбои контейнеров, теоретически из-за использования памяти, но, возможно, это не утечка памяти, а просто нехватка памяти, чтобы позволить Ruby потреблять то, что он хочет? Существуют ли настройки для сборщика мусора, которые помогут ему решить, когда пора очиститься до того, как Ruby исчерпает память и выйдет из строя?
ОБНОВЛЕНИЕ 2: это по-прежнему не имеет смысла, потому что, почему Ruby продолжает выделять все больше и больше памяти только из-за многократного запуска одного и того же процесса (почему он не использует ранее выделенную память) ? Насколько я понимаю, GC предназначен для запуска хотя бы один раз, прежде чем выделять больше памяти из ОС, так почему же Ruby просто выделяет все больше и больше памяти, когда я запускаю это несколько раз?
ОБНОВЛЕНИЕ 3: в моем изолированном тесте Ruby, кажется, приближается к пределу, при котором он перестает выделять дополнительную память, независимо от того, сколько раз я запускаю тест (обычно это около 120 МБ), но в моем производстве код, я еще не достиг такого предела (он превышает 500 МБ без замедления - возможно, потому, что по классу разбросано больше случаев такого использования памяти). Может быть ограничение на объем памяти, который он будет использовать, но, похоже, он во много раз выше, чем можно было бы ожидать для запуска этого кода (который на самом деле использует только дюжину или около того МБ для одного запуска)
Обновление 4: я сузил тестовый пример до того, что действительно дает утечки! Чтение многобайтового символа из файла было ключом к воспроизведению реальной проблемы:
str = "String that doesn't fit into a single RVALUE, with a multibyte char:" + 160.chr(Encoding::UTF_8)
File.write('weirdstring.txt', str)
class Leak
PATTERN = File.read("weirdstring.txt").freeze
def test
10000.times { /#{PATTERN}/i }
end
end
t = Leak.new
loop do
print "Running... "
t.test
# If this doesn't work on your system, just comment these lines out and watch the memory usage of the process with top or something
mem = %x[echo 0 $(awk '/Private/ {print "+", $2}' /proc/`pidof ruby`/smaps) | bc].chomp.to_i
puts "process memory: #{mem}"
end
Итак... это настоящая утечка, верно?