[about] [index] [prev] [2021-05-17 20:33:20+03:00] [17efdb3c97a8de4227c447bd8dc67cbbd7ae873b]
Topics: [bsd][djb]

Ограничения процесса по памяти и процессору

https://unix.stackexchange.com/questions/44985/limit-memory-usage-for-a-single-linux-process
http://coldattic.info/post/40/
ulimit-ом я пользовался прежде только для подкручивания кол-ва файловых
дескрипторов. Регулярно во многих задачах в них утыкался. Недавно в
goredo, в котором я много открытых файлов без надобности имел. Но вот по
памяти никогда не приходилось ограничивать. И я так и не понял как с
этим работать и почему оно не работает. Говорю маленький "ulimit -m", но
что Go, что Си программы упорно спокойно могут аллоцировать гигабайты.
Судя по stackexchange, оно или у кого-то работает, или предлагают
совершенно другие решения. Вот и я пока совершенно не знаю как оно всё
устроено и почему не работает.

Но, как всегда, на помощь пришёл софт от DJB! Помню что в daemontools
была утилита по ограничению ресурсов. "softlimit -m" отлично
срабатывает, возвращая ошибку нехватки памяти. И для Go софта
отрабатывает без проблем.

Обратил внимание на то, что можно ограничить CPU время через этот
softlimit. Проверил -- действительно это не время жизни процесса, а
именно съеденное время. Посетила идея что надо бы lighttpd обернуть в
эту штуку -- раз в несколько месяцев, но замечаю что иногда lighttpd
отжирает 100% CPU, вроде даже ни на что и не реагируя (но не помню
точно). Поставил перезапуск по cron. Как раз softlimit по процессору
вроде бы должен помочь: при штатной работе он не много ест CPU и его
наверное неделями можно было бы не перезапускать, а если он уйдёт в
бесконечный цикл, то быстро иссякнет лимит и его грохнут, автоматически
перезапустив.

А вообще тут возился с одной программой, которая жутко течёт. Сейчас
выбросил её, но вот не понравилось мне то, что при росте памяти оно
начнёт отъедать swap и, соответственно, делать систему
неработоспособной. А если OOM killer и сработает, то запросто грохнет
какой-нибудь SSH. Знаю что это вечная проблема и придумана тьмища
способов решения или, хотя бы, выбора кого надо грохнуть. Но сейчас вот
вижу что можно просто бы обернуть в softlimit недоверенный (или известно
что текучий) софт и не париться.

Где-то я вроде у DJB в его скудной документации видел совет по
оборачиванию в softlimit -- теперь дошло насколько это полезно. Unix
познаётся мною только сейчас...

А ещё в статье на coldattic.info упоминается что soft limit-ы пошлют
сигнал SIGCPU, который можно перехватить и как-то среагировать. Знал что
есть soft и hard, но даже примерно не представлял чем они на практике бы
отличались. Впрочем у меня всё равно ulimit никоим образом не
отрабатывает (или я делаю что-то не так).

[leave comment]
comment 0:
From: kmeaw
Date: 2021-05-17 20:01:04Z

> Говорю маленький "ulimit -m", но
> что Go, что Си программы упорно спокойно могут аллоцировать гигабайты.

ulimit -d 16384 -s 16384 -l 16384 -m 16384

(data, stack, mlocked & resident memory)

После этого ни mmap, ни (s)brk не выделяют память.

> А если OOM killer и сработает, то запросто грохнет
> какой-нибудь SSH.

Обычно для этого подкручивают oom_adj для sshd, чтобы его грохали в
последнюю очередь. А браузеры, в свою очередь, подкручивают в обратную
сторону oom_adj для процессов-вкладок, чтобы их OOM killer убивал
первыми.

> Знал что есть soft и hard, но даже примерно не представлял чем они на
> практике бы отличались.

Реально на процесс действует soft limit. Hard limit - административное
ограничение, выше которого soft limit поднять нельзя.

На практике администраторы обычно для почти всех ресурсов ставят soft
limit равным hard limit, а на файловые дескрипторы soft limit делают
меньше hard limit в несколько раз. Если программа знает про resource
limits, то она временно опустит soft limit до необходимого минимума или
поднимет soft limit на открытые файловые дескрипторы, если, например,
это какой-нибудь нагруженный сетевой сервис.

> Впрочем у меня всё равно ulimit никоим образом не
> отрабатывает (или я делаю что-то не так).

odin% zsh --version
zsh 5.4.2 (x86_64--netbsd)
odin% ulimit -t 5
odin% while true; do :; done
Connection to sdf-eu.org closed.
comment 1:
From: Sergey Matveev
Date: 2021-05-18 07:24:23Z

*** kmeaw [2021-05-17 22:59]:
>ulimit -d 16384 -s 16384 -l 16384 -m 16384
>После этого ни mmap, ни (s)brk не выделяют память.

Похоже я совсем не знаю как всё устроено и работает с памятью. Я вот
написал вот такое:

    int
    main(void)
    {
        int i = 0;
        while (true) {
            if (malloc(10*(1<<20)) == NULL) {
                abort();
            };
            usleep(10000);
            printf("%d\n", i++);
        };
        return 0;
    }

Сделал ulimit как у вас (только без -l, ибо у меня hard-limit сильно
меньше) и смотрю в top, дошло до:

      PID USERNAME    THR PRI NICE   SIZE    RES STATE    C   TIME    WCPU COMMAND
    38174 stargrave     1  20    0    43G    20M nanslp   0   0:00   0.35% main

Размер виртуальной памяти огромный -- понимаю ещё почему может быть, это
не напрягает. Хотя я считал что malloc то уж должен быть RES-ом, а
всякие SIZE учитывают mmap-нутые файлы например. У меня zsh 5.6.2,
FreeBSD 12.0, но, как видно, всё равно даже RES достиг больше чем 16MiB
указанных в ulimit и ничего не падает. Или ulimit влияет только и только
на сам shell, но не его детей? Буду читать про это. Ибо ваш пример с -t 5
то у меня честно отрабатывает.

>Обычно для этого подкручивают oom_adj для sshd, чтобы его грохали в
>последнюю очередь. А браузеры, в свою очередь, подкручивают в обратную
>сторону oom_adj для процессов-вкладок, чтобы их OOM killer убивал
>первыми.

Наслышан, наслышан. Сам правда ни с чем таким ни разу не встречался.

>Реально на процесс действует soft limit. Hard limit - административное
>ограничение, выше которого soft limit поднять нельзя.
>На практике администраторы обычно для почти всех ресурсов ставят soft
>limit равным hard limit

Спасибо за информацию! С этим всем пока ещё в курсе.
comment 2:
From: Sergey Matveev
Date: 2021-05-18 09:49:15Z

У коллеги под bash и Gentoo какой-то тоже ничего не вышло.
Вроде у меня всё встаёт на свои места почему не работало. man setrlimit
говорит что:

     RLIMIT_RSS      When there is memory pressure and swap is available,
                     prioritize eviction of a process' resident pages beyond
                     this amount (in bytes).  When memory is not under
                     pressure, this rlimit is effectively ignored.  Even when
                     there is memory pressure, the amount of available swap
                     space and some sysctl settings like vm.swap_enabled and
                     vm.swap_idle_enabled can affect what happens to processes
                     that have exceeded this size.

                     Processes that exceed their set RLIMIT_RSS are not
                     signalled or halted.  The limit is merely a hint to the
                     VM daemon to prefer to deactivate pages from processes
                     that have exceeded their set RLIMIT_RSS.

то есть RLIMIT_RSS никому не скажет ENOMEM и ничего не сделает.

     RLIMIT_DATA     The maximum size (in bytes) of the data segment for a
                     process; this defines how far a program may extend its
                     break with the sbrk(2) function.

влияет только честно на sbrk. Но у меня из коробки используется
jemalloc, который использует по умолчанию для больших блоков mmap, а не
sbrk, который не учитывается RLIMIT_DATA. В итоге у нас получилось
ограничить память только по RLIMIT_VMEM, который как-раз и делает (в том
числе) softlimit из daemontools.
comment 3:
From: kmeaw
Date: 2021-05-18 20:08:09Z

Проверил программу из комментария 1 на разных ОС.

Linux 4.19, 5.2.11, 5.4.31, 5.10.27: ломается сразу, напечатав "0".

NetBSD 9.1: не ломается, быстро растёт SIZE, медленно растёт RES.

Если добавить bzero на выделяемую память, то начинает быстро расти RES.

Возможно, что реализация malloc у NetBSD вызывает mmap без BSD-аналога
MAP_POPULATE, отдавая "непрогретые" странички. При таком вызове страница
будет выделена "по-настоящему" тогда, когда её потрогают.

В Linux это можно отключить настройкой vm.overcommit_ratio=0, но этого
делать не рекомендуется, потому что многие программы расчитывают на
такое поведение. В Windows такого нет, зато драйверы ядра умеют уходить
в своп.

FreeBSD 13.0-RELEASE, тоже не ломается. Без bzero растёт SIZE, поведение
аналогично NetBSD.

OpenBSD 6.9: ломается сразу, напечатав "0".

Вариант с RLIMIT_VMEM мне не очень нравится, потому что на 64-битных
платформах нет особого смысла экономить адресное пространство, а
лимитируемая программа может при нормальной работе отображать
(non-anonymous mmap) большие файлы на память, не приводя к расходу
резидентной памяти. Но, похоже, что на BSD без этого никак, так как "[t]he
limit is merely a hint". А в OpenBSD вовсе нет RLIMIT_VMEM (или аналога,
как RLIMIT_AS в Linux).

Попытался включить rctl, но настройка user:foo:memoryuse:deny=40m тоже
не ломает эту программу. user:foo:vmemoryuse:deny=40m работает, так же,
как и RLIMIT_VMEM. Но можно написать правило для monit, которое будет
следить и перезапускать прожорливую программу.

> limit влияет только и только на сам shell, но не его детей?

При execve resource limits наследуются от родителя, но могут быть
изменены дочерним процессом для себя. В Linux ещё есть prlimit, который
позволяет менять resource limits чужим процессам (если есть
capability CAP_SYS_RESOURCE).
comment 4:
From: Sergey Matveev
Date: 2021-05-18 20:38:03Z

*** kmeaw [2021-05-18 23:06]:
>Linux 4.19, 5.2.11, 5.4.31, 5.10.27: ломается сразу, напечатав "0".

У коллеги с Gentoo не знаю что за версии ядра, но было такое же
поведение как у меня -- ни разу не "сломалось", пока VMEM не ограничили.

>Если добавить bzero на выделяемую память, то начинает быстро расти RES.

Ага, это тоже проделывали на Gentoo. С этим оно действительно начинает
есть память, а не просто "резервировать" с неспешным ростом RES.

>В Linux это можно отключить настройкой vm.overcommit_ratio=0, но этого
>делать не рекомендуется, потому что многие программы расчитывают на
>такое поведение.

Во-во, именно про overcommit я и подумал и понимаю что поведение
"непрогретых" страниц связано с ним.

>Вариант с RLIMIT_VMEM мне не очень нравится

Вот и мне тоже. Я поэтому всё что касается VMEM-а и не трогал и не
пробовал. Плюс ещё можно mmap-ать файлы, что тоже не имеет смысла
ограничивать.

>Но можно написать правило для monit, которое будет
>следить и перезапускать прожорливую программу.

Ну это уже совсем костыль, так сказать. Работать то конечно будет, если
процесс не разрастётся в окне между проверками monit. Завтра попробую
ограничить в Jail-е через rctl. https://wiki.freebsd.org/JailResourceLimits
вроде бы можно RSS ограничивать.
comment 5:
From: Sergey Matveev
Date: 2021-05-19 10:02:59Z

*** Sergey Matveev [2021-05-18 23:38]:
>ограничить в Jail-е через rctl

Ограничение по RSS работает... top показывает что процесс более не
вылезает за указанный лимит. Вот только размер виртуальной памяти при
этом всё равно упорно продолжает расти. Сейчас использую цикл с
malloc-ами и arc4random заполнением этих страниц, чтобы точно там были
недедуплицируемые данные.

В общем, я не вижу как можно было бы ограничить размер памяти в FreeBSD,
кроме как только ограничением размера виртуальной.