Ruby: невозможно выделить память

Я занимаюсь разработкой приложения Ruby on Rails. Я новичок в Ruby/Rails. Я использую Ruby 2.2.0 и Rails 4.2. Когда я запускаю команду, например:

rails g migration SomeMigrationName

он терпит неудачу с

Cannot allocate memory - fork(2) (Errno::ENOMEM)

Я использую Macbook Pro в середине 2014 года с OS X 10.10 на борту и Vagrant/Virtualbox для запуска виртуальной машины (Ubuntu 14.04) для разработки Rails.

Вот мой бродячий файл:

Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.network "forwarded_port", guest: 3000, host: 3000
  config.vm.synced_folder "dev", "/home/vagrant/dev"
  config.vm.synced_folder "opt", "/opt"
  config.vm.provider "virtualbox" do |vb|
    vb.memory = "512"
  end
end

Я читал, что такая ошибка возникает, когда ОЗУ находится вне пределов, но я использую ту же конфигурацию (файл Vagrant) для другой среды dev, которая запускает несколько приложений Python/Tornado, MongoDB и Redis, и все работает отлично.

Мне нужно увеличить значение vb.memory или это ошибка Ruby?

Ответ 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 самостоятельно.