diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index aca0e98ce..04727b7c7 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -26,6 +26,34 @@ fi php vendor/bin/shlink-installer init ${flags} if [ "$SHLINK_RUNTIME" = 'rr' ]; then + # RoadRunner resolves `num_workers: 0` against the number of CPUs the HOST + # reports, which is unaffected by any CPU limit set on the container. On a + # many-core host that starts dozens of workers which can never run + # concurrently, at roughly 50-60MB each, so the container idles far above the + # memory it needs and is eventually killed under load. + # + # When the container has a CPU limit, derive the defaults from that instead. + # Without a limit, nothing changes. + container_cpus='' + if [ -r /sys/fs/cgroup/cpu.max ]; then # cgroup v2 + read -r cpu_quota cpu_period < /sys/fs/cgroup/cpu.max + elif [ -r /sys/fs/cgroup/cpu/cpu.cfs_quota_us ]; then # cgroup v1 + cpu_quota=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us) + cpu_period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us) + fi + if [ -n "${cpu_quota}" ] && [ "${cpu_quota}" != 'max' ] && [ "${cpu_quota}" -gt 0 ] 2>/dev/null \ + && [ -n "${cpu_period}" ] && [ "${cpu_period}" -gt 0 ] 2>/dev/null; then + # Round up, so a fractional limit still gets one worker rather than none. + container_cpus=$(( (cpu_quota + cpu_period - 1) / cpu_period )) + fi + + if [ -n "${container_cpus}" ]; then + # A floor of 2 web workers, so a single slow request cannot block the + # whole container when the limit is one CPU. + [ -z "${WEB_WORKER_NUM}" ] && export WEB_WORKER_NUM=$(( container_cpus > 2 ? container_cpus : 2 )) + [ -z "${TASK_WORKER_NUM}" ] && export TASK_WORKER_NUM="${container_cpus}" + fi + # Run with `exec` so that signals are properly handled exec ./bin/rr serve -c config/roadrunner/.rr.yml fi