Ответ 1
Когда Ruby вызывает fork
, ОС будет делать копию всего адресного пространства родительских процессов, даже если fork только вызывается в exec
еще один небольшой процесс, например ls
. На мгновение ваша система должна иметь возможность выделять кусок памяти, по крайней мере, размер родительской поддержки Ruby, прежде чем сворачивать ее до того, что действительно требуется для дочернего процесса.
Таким образом, рельсы, как правило, довольно голодные. Тогда, если что-то использует fork
, вам нужно вдвое больше памяти.
TL; DR Используйте posix-spawn вместо fork, если вы контролируете код. В противном случае вы получите VM 1024MB или немного дополнительного пространства подкачки, чтобы занять слабину для вызова fork
Пример использования памяти Ruby с помощью fork
Возьмите произвольную виртуальную машину, у которой есть место подкачки отключено:
$ free -m
total used free shared buffers cached
Mem: 1009 571 438 0 1 35
-/+ buffers/cache: 534 475
Swap: 0 0 0
Посмотрите на столбец Mem:
и free
. Это примерно соответствует вашему размеру для нового процесса, в моем случае 438
MiB
My buffers/cached
уже был покраснел для этого теста, так что моя память free
ограничена. Возможно, вам придется принимать значения buffers/cache
, если они большие. Linux имеет возможность выталкивать устаревший кеш, когда память нужна процессу.
Использовать память
Создайте рубиновый процесс со строкой вокруг размера свободной памяти. Для процесса ruby
есть некоторые накладные расходы, поэтому он не будет точно соответствовать free
.
$ ruby -e 'mb = 380; a="z"*mb*2**20; puts "=)"'
=)
Затем сделайте строку немного больше:
$ ruby -e 'mb = 385; a="z"*mb*2**20; puts "=)"'
-e:1:in `*': failed to allocate memory (NoMemoryError)
from -e:1:in `<main>'
Добавьте fork
в рубиновый процесс, сократив mb
до его запуска.
$ ruby -e 'mb = 195; a="z"*mb*2**20; fork; puts "=)"'
=)
Немного больший процесс fork приведет к ошибке ENOMEM
:
$ ruby -e 'mb = 200; a="z"*mb*2**20; fork; puts "=)"'
-e:1:in `fork': Cannot allocate memory - fork(2) (Errno::ENOMEM)
from -e:1:in `<main>'
Запуск команды с backticks запускает этот процесс с fork
, поэтому имеет тот же результат:
$ ruby -e 'mb = 200; a="z"*mb*2**20; `ls`'
-e:1:in ``': Cannot allocate memory - ls (Errno::ENOMEM)
from -e:1:in `<main>'
Итак, вы идете, вам нужно в два раза больше памяти родительских процессов, доступных в системе, чтобы разблокировать новый процесс. MRI Ruby в значительной степени опирается на fork
для модели с несколькими процессами, это связано с дизайном Ruby, который использует глобальную блокировку интерпретатора , который позволяет выполнять только один поток за один раз за рубиновый процесс.
Я считаю, что Python намного меньше использует fork
внутренне. Когда вы используете os.fork
в Python, это происходит, хотя:
python -c 'a="c"*420*2**20;'
python -c 'import os; a="c"*200*2**20; os.fork()'
В Oracle есть подробная статья о проблеме и расскажите об использовании альтернативы posix_spawn()
. Статья посвящена Solaris, но это общая проблема POSIX Unix, поэтому она применима к Linux (если не к большинству Unices).
Существует также реализация Ruby posix-spawn
, которую вы могли бы использовать, если вы контролируете код. Этот модуль не заменяет ничего в Rails, поэтому он не поможет вам, если вы не замените вызовы на fork
самостоятельно.