fix(docker): taosd 被 OOM 杀掉时让容器退出,让 --restart 拉起来 (#34179)#35368
Conversation
…ta#34179) The container entrypoint started taosd / taosadapter / taoskeeper / taos-explorer as background jobs and then called `wait`, which only returns when *all* children have exited. When taosd is OOM-killed the remaining processes (most importantly taosadapter) stay alive, `wait` keeps blocking, and the container looks healthy to Docker even though the database is gone -- so `--restart unless-stopped` never fires and the service is effectively dead. Capture the PID of each background TDengine process as it's started and replace the final `wait` with a 2s-interval watchdog: if any one PID disappears, pkill the rest and `exit 1`, which lets Docker's restart policy bring the container back. Empty-PIDS edge case (cluster non-first node with every sidecar disabled) idles waiting for the existing SIGINT/SIGTERM trap. Mock-verified end-to-end: simulated OOM of one mock service triggers watchdog detection within ~1s, kills siblings, and exits 1. No behaviour change when all services stay healthy.
There was a problem hiding this comment.
Code Review
This pull request introduces a watchdog mechanism in the Docker entrypoint script (entrypoint.sh) to track the PIDs of background processes (taosd, taosadapter, taoskeeper, and taos-explorer) and shut down the container if any of them exits unexpectedly. The review comments identify critical improvement opportunities to prevent infinite hangs or unnecessary delays during startup. Specifically, they suggest checking process survival (using kill -0) within the initialization wait loops for taosd, taosadapter, and taoskeeper so that the script fails fast if a process crashes immediately.
| taosd & | ||
| PIDS="$PIDS $!" | ||
| while true; do | ||
| es=$(taos -h $FIRST_EP_HOST -P $FIRST_EP_PORT --check | grep "^[0-9]*:") |
There was a problem hiding this comment.
在 taosd 启动后的等待循环中,如果 taosd 因为配置错误或端口冲突等原因在启动时立即崩溃,taos --check 将永远无法成功,导致此 while true 循环变成死循环,容器将无限期挂起而无法触发 Docker 的 --restart 策略。
建议在循环中保存 taosd 的 PID,并在每次循环时检查其是否仍在运行。如果已退出,则应立即报错并退出容器,实现快速失败(Fail-Fast)。
| taosd & | |
| PIDS="$PIDS $!" | |
| while true; do | |
| es=$(taos -h $FIRST_EP_HOST -P $FIRST_EP_PORT --check | grep "^[0-9]*:") | |
| taosd & | |
| TAOSD_PID=$! | |
| PIDS="$PIDS $TAOSD_PID" | |
| while true; do | |
| if ! kill -0 "$TAOSD_PID" 2>/dev/null; then | |
| echo "taosd died during startup" | |
| exit 1 | |
| fi | |
| es=$(taos -h $FIRST_EP_HOST -P $FIRST_EP_PORT --check | grep "^[0-9]*:") |
| if [ "$TAOS_FIRST_EP" = "" ]; then | ||
| echo "run TDengine with single node." | ||
| taosd & | ||
| PIDS="$PIDS $!" | ||
| fi | ||
| while true; do | ||
| es=$(taos -h $FIRST_EP_HOST -P $FIRST_EP_PORT --check | grep "^[0-9]*:") |
There was a problem hiding this comment.
同样地,在单节点或非首节点初始化的 else 分支中,如果 taosd 启动失败,此处的 while true 循环也会陷入死循环。
建议在此处也保存 taosd 的 PID,并在循环中进行健康检查,以避免容器在启动失败时无限挂起。
| if [ "$TAOS_FIRST_EP" = "" ]; then | |
| echo "run TDengine with single node." | |
| taosd & | |
| PIDS="$PIDS $!" | |
| fi | |
| while true; do | |
| es=$(taos -h $FIRST_EP_HOST -P $FIRST_EP_PORT --check | grep "^[0-9]*:") | |
| TAOSD_PID="" | |
| if [ "$TAOS_FIRST_EP" = "" ]; then | |
| echo "run TDengine with single node." | |
| taosd & | |
| TAOSD_PID=$! | |
| PIDS="$PIDS $TAOSD_PID" | |
| fi | |
| while true; do | |
| if [ -n "$TAOSD_PID" ] && ! kill -0 "$TAOSD_PID" 2>/dev/null; then | |
| echo "taosd died during startup" | |
| exit 1 | |
| fi | |
| es=$(taos -h $FIRST_EP_HOST -P $FIRST_EP_PORT --check | grep "^[0-9]*:") |
| taosadapter & | ||
| PIDS="$PIDS $!" | ||
| # wait for 6041 port ready | ||
| for _ in $(seq 1 20); do | ||
| curl -sf http://localhost:6041/metrics && break | ||
| sleep 0.5 | ||
| done |
There was a problem hiding this comment.
如果 taosadapter 在启动时立即崩溃(例如由于端口冲突或配置错误),curl 探测会持续失败,导致脚本在 seq 1 20 循环中等待整整 10 秒(20 * 0.5s)才会继续。
建议在等待循环中加入进程存活检查(kill -0)。如果进程已经退出,应立即中断等待并报错退出,实现快速失败(Fail-Fast)。
| taosadapter & | |
| PIDS="$PIDS $!" | |
| # wait for 6041 port ready | |
| for _ in $(seq 1 20); do | |
| curl -sf http://localhost:6041/metrics && break | |
| sleep 0.5 | |
| done | |
| taosadapter & | |
| ADAPTER_PID=$! | |
| PIDS="$PIDS $ADAPTER_PID" | |
| # wait for 6041 port ready | |
| for _ in $(seq 1 20); do | |
| if ! kill -0 "$ADAPTER_PID" 2>/dev/null; then | |
| echo "taosadapter died during startup" | |
| exit 1 | |
| fi | |
| curl -sf http://localhost:6041/metrics && break | |
| sleep 0.5 | |
| done |
| taoskeeper & | ||
| PIDS="$PIDS $!" | ||
| # wait for 6043 port ready | ||
| for _ in $(seq 1 20); do | ||
| curl -sf http://localhost:6043/metrics && break | ||
| sleep 0.5 | ||
| done |
There was a problem hiding this comment.
同样地,对于 taoskeeper,如果启动即崩溃,等待循环会白白消耗 10 秒。建议加入进程存活检查以实现快速失败。
| taoskeeper & | |
| PIDS="$PIDS $!" | |
| # wait for 6043 port ready | |
| for _ in $(seq 1 20); do | |
| curl -sf http://localhost:6043/metrics && break | |
| sleep 0.5 | |
| done | |
| taoskeeper & | |
| KEEPER_PID=$! | |
| PIDS="$PIDS $KEEPER_PID" | |
| # wait for 6043 port ready | |
| for _ in $(seq 1 20); do | |
| if ! kill -0 "$KEEPER_PID" 2>/dev/null; then | |
| echo "taoskeeper died during startup" | |
| exit 1 | |
| fi | |
| curl -sf http://localhost:6043/metrics && break | |
| sleep 0.5 | |
| done |
…data#34179) Tighten the watchdog so any path out of entrypoint.sh -- watchdog detecting a dead child, SIGTERM from docker stop, set -e failure during initdb/create-snode, etc -- runs the same cleanup_children helper. Without this, a set -e failure during early setup would leave the background services orphaned for tini to reap, instead of going through the normal pkill -P $$ path. Verified end-to-end on lvm1 against tdengine/tdengine:latest with the new entrypoint mounted into the container: A happy path (10s idle) Running=true ExitCode=0 B SIGKILL taosd Running=false ExitCode=1 C SIGKILL taosadapter (symmetric) Running=false ExitCode=1 D docker stop (SIGTERM) Running=false ExitCode=0 E --restart + SIGKILL taosd RestartCount=1, restarted
…#34179) Addresses the gemini-code-assist review on PR taosdata#35368. Before this commit, the startup-time loops blindly waited for taos --check (or a /metrics curl) to succeed; if the just-spawned taosd / taosadapter / taoskeeper / taos-explorer crashed immediately (bad config, port in use, etc.) the script would either loop forever ("taos --check" never succeeding against a dead server) or burn the full 10s curl timeout in silence. Now: - Both taosd startup --check loops check kill -0 $TAOSD_PID on each iteration and exit 1 the moment the process disappears, so a dead taosd no longer turns into a forever-hanging container. - The three sidecar metric-wait loops are hoisted into a wait_for_metric_or_die helper that checks kill -0 inside the wait loop and exits 1 on early death (covers taosadapter, taoskeeper, taos-explorer; the gemini review missed taos-explorer — added too). - The taos --check pipeline is hardened with || true and an explicit empty-result guard so a transient connection error during polling doesn't trip set -e before the kill -0 check gets a chance to run. - The EXIT trap now re-exits with the captured rc instead of letting cleanup_children's last command (a best-effort pkill) overwrite the status code. E2E verified on lvm1 against tdengine/tdengine:latest: A happy path (10s idle) Running=true ExitCode=0 B SIGKILL taosd Running=false ExitCode=1 C SIGKILL taosadapter (symmetric) Running=false ExitCode=1 D docker stop (SIGTERM) Running=false ExitCode=0 E --restart + SIGKILL taosd RestartCount=1, restarted F SIGKILL taosd during startup loop Running=false ExitCode!=0 F2 same as F with --restart unless-stopped RestartCount=1, restarted
|
Thanks for the review — all four fail-fast suggestions are valid and adopted in commit What landed:
End-to-end matrix on
|
|
|
issue #34179:
tsdb:3.3.8.4容器里 taosd 被系统 OOM 杀掉之后,taosadapter 还在跑,容器对 Docker 仍然显示 healthy,--restart unless-stopped永远不会触发,服务实际已挂死。原因
packaging/docker/bin/entrypoint.sh末尾是wait,这条命令只在所有后台子进程都退出后才返回。taosd 被杀以后,taosadapter / taoskeeper / taos-explorer 任何一个还活着,wait就继续阻塞,PID 1 不退出,Docker 不重启。改动
&启动的关键进程(taosd、taosadapter、taoskeeper、taos-explorer)把$!追加到PIDSwait换成 watchdog:每 2s 检查一次PIDS里每个 PID,任意一个死了就pkill -P $$把兄弟进程清掉、exit 1--restart策略就会把它拉起来which xxx检查从命令链改成if守卫,跳过未安装时不会留空 PID 进PIDSPIDS为空,进 idle 循环让 SIGINT/SIGTERM trap 仍然能停容器用 polling 而不是
wait -n是为了不依赖 bash 4.3+;2s 心跳的 CPU 开销可以忽略。端到端验证
在 lvm1 上用
tdengine/tdengine:latest(server 3.3.6.13)跑三个 case:pkill -9 taosdRunning=true ExitCode=0,pgrep taosadapter返回 PID 416pkill -9 taosdRunning=false ExitCode=1;log 末尾child process 44 exited; shutting container down so Docker can restart it--restart unless-stoppedRestartCount+1RestartCount=1,State.StartedAt已经被刷新复现命令(lvm1 / Ubuntu 22.04 / Docker 28.2.2):
行为变化
--restart重新拉起,原wait实现里是"容器假活"——这才是 issue 报告的真问题。pkill -P $$清理子进程后 exit 0。Refs #34179