Regarding the original report this is a simple program which keeps the maximal allowed children running and it does not get killed by cgroups, just the fork() call fails:
---
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
I believe this is by design and I think this approach is reasonable.
A shell should not try to keep itself alive forking new processes when it hit system limits already for a few times. There are other tools available for implementing servers with worker pools which adapt to system limits which are not defined in advance.
If you know the number of workers need in advance I suggest setting TasksMax to high enough or to infinity in case you don't want to rely on cgroup fork limits.
Regarding the original report this is a simple program which keeps the maximal allowed children running and it does not get killed by cgroups, just the fork() call fails:
---
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#define MASTER_SLEEP_NS 1000000L
#define CHILD_SLEEP_S 5
void main(void)
{
pid_t pid;
struct timespec master_sleep = {0, MASTER_SLEEP_NS};
for (;;) { (&master_ sleep, NULL); CHILD_SLEEP_ S); &master_ sleep, NULL); Reproducer user.target
pid = fork();
if (pid < 0) {
perror("fork failed:");
nanosleep
}
if (pid == 0) {
sleep(
exit(0);
}
nanosleep(
/* collect exited children */
while (waitpid(-1, NULL, WNOHANG) > 0);
}
}
---
[Unit]
Description=
After=multi-
[Service] /home/user/ reproducer
ExecStart=
Type=simple
TasksMax=512
[Install] multi-user. target system/ reproducer. service; disabled; vendor preset: enabled) slice/reproduce r.service reproducer reproducer reproducer reproducer reproducer reproducer
WantedBy=
---
● reproducer.service - Reproducer
Loaded: loaded (/etc/systemd/
Active: active (running) since Mon 2017-05-22 13:16:50 UTC; 3min 3s ago
Main PID: 11778 (reproducer)
Tasks: 512 (limit: 512)
Memory: 55.4M
CPU: 1min 2.794s
CGroup: /system.
├─11778 /home/rbalint/
├─18144 /home/rbalint/
...
├─26763 /home/rbalint/
├─26764 /home/rbalint/
├─26765 /home/rbalint/
└─26766 /home/rbalint/
May 22 13:20:14 zesty-test reproducer[11778]: fork failed:: Resource temporarily unavailable
May 22 13:20:14 zesty-test reproducer[11778]: fork failed:: Resource temporarily unavailable
May 22 13:20:14 zesty-test reproducer[11778]: fork failed:: Resource temporarily unavailable
---
Bash on the other hand kills itself after a few failing forks:
● reproducer.service - Reproducer system/ reproducer. service; disabled; vendor preset: enabled) /home/rbalint/ reproducer. sh (code=exited, status=0/SUCCESS)
Loaded: loaded (/etc/systemd/
Active: failed (Result: exit-code) since Mon 2017-05-22 13:22:38 UTC; 3s ago
Process: 14281 ExecStart=
Main PID: 14287 (code=exited, status=254)
CPU: 639ms
May 22 13:22:35 zesty-test reproducer. sh[14281] : /home/rbalint/ reproducer. sh: fork: retry: Resource temporarily unavailable sh[14281] : /home/rbalint/ reproducer. sh: fork: retry: Resource temporarily unavailable sh[14281] : /home/rbalint/ reproducer. sh: fork: retry: Resource temporarily unavailable sh[14281] : /home/rbalint/ reproducer. sh: fork: retry: Resource temporarily unavailable sh[14281] : /home/rbalint/ reproducer. sh: fork: retry: Resource temporarily unavailable sh[14281] : /home/rbalint/ reproducer. sh: fork: retry: Resource temporarily unavailable sh[14281] : /home/rbalint/ reproducer. sh: fork: Interrupted system call
May 22 13:22:35 zesty-test reproducer.
May 22 13:22:35 zesty-test reproducer.
May 22 13:22:35 zesty-test reproducer.
May 22 13:22:35 zesty-test reproducer.
May 22 13:22:35 zesty-test reproducer.
May 22 13:22:38 zesty-test reproducer.
May 22 13:22:38 zesty-test systemd[1]: reproducer.service: Main process exited, code=exited, status=254/n/a
May 22 13:22:38 zesty-test systemd[1]: reproducer.service: Unit entered failed state.
http:// sources. debian. net/src/ bash/4. 4-5/jobs. c/?hl=1919# L1919
/* Create the child, handle severe errors. Retry on EAGAIN. */
while ((pid = fork ()) < 0 && errno == EAGAIN && forksleep < FORKSLEEP_MAX)
{
/* bash-4.2 */
sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
/* If we can't create any children, try to reap some dead ones. */
waitchld (-1, 0);
errno = EAGAIN; /* restore errno */ SIGTERM;
sys_error ("fork: retry");
RESET_
if (sleep (forksleep) != 0)
break;
forksleep <<= 1;
if (interrupt_state)
break;
sigprocmask (SIG_SETMASK, &set, (sigset_t *)NULL);
}
...
if (pid < 0)
{
sys_error ("fork");
/* Kill all of the processes in the current pipeline. */ _current_ pipeline ();
terminate
/* Discard the current pipeline, if any. */ pipeline ();
if (the_pipeline)
kill_current_
last_ command_ exit_value = EX_NOEXEC; to_top_ level (); /* Reset signals, etc. */
throw_
}
...
I believe this is by design and I think this approach is reasonable.
A shell should not try to keep itself alive forking new processes when it hit system limits already for a few times. There are other tools available for implementing servers with worker pools which adapt to system limits which are not defined in advance.
If you know the number of workers need in advance I suggest setting TasksMax to high enough or to infinity in case you don't want to rely on cgroup fork limits.