У меня есть код с утечкой памяти в приложении 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 или я что-то не так делаю?
ОБНОВЛЕНИЕ: Хорошо, возможно, я неправильно читаюэтот.Я смотрел на использование памяти docker-контейнера после запуска GC.start
, который иногда зависал, но поскольку Ruby не всегда освобождает память, которую он не использует, я думаю, это может быть просто то, что Ruby использует эта память, а затем, даже если она не сохраняется, она все еще не освобождает память обратно в ОС.Используя гем MemoryProfiler, я вижу, что total_retained, даже после запуска его несколько раз, равно 0.
Основная проблема здесь заключалась в том, что у нас произошел сбой контейнеров, теоретически из-за использования памяти, но, возможно, это не утечка памяти, а простоне хватает памяти, чтобы позволить Ruby потреблять то, что он хочет?Есть ли настройки для GC, чтобы помочь ему решить, когда пора очищать, прежде чем 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
Итак ... это настоящая утечка, верно?