質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.31%
bash

bash(Bourne-again-Shell)は sh(Bourne Shell)のインプリメンテーションに様々な機能が追加されたシェルです。LinuxやMac OS XではBashはデフォルトで導入されています。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

Docker

Dockerは、Docker社が開発したオープンソースのコンテナー管理ソフトウェアの1つです

意見交換

クローズ

8回答

1382閲覧

バックグラウンド実行するとなぜ子プロセスの明示的な停止が必要なのか?

退会済みユーザー

退会済みユーザー

総合スコア0

bash

bash(Bourne-again-Shell)は sh(Bourne Shell)のインプリメンテーションに様々な機能が追加されたシェルです。LinuxやMac OS XではBashはデフォルトで導入されています。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

Docker

Dockerは、Docker社が開発したオープンソースのコンテナー管理ソフトウェアの1つです

0グッド

0クリップ

投稿2023/10/12 13:51

0

0

テーマ、知りたいこと

バックグラウンド実行すると子プロセスの明示的な停止がなぜ必要なのか?

背景、状況

dockerでsleepするだけの簡単なbashスクリプトをバックグラウンド実行(-d)してみたら停止に10秒かかった。

sleep.sh

bash

1sleep 100000

実行

bash

1# dockerでsleep.shをバックグラウンド実行 2docker run -d --rm --name bash -v $(pwd)/sleep.sh:/sleep.sh bash bash /sleep.sh 3 4# docker stopで停止させてみる -> 停止に10秒かかる 5time docker stop bash

出力

text

184454d3fd1ecbda92978df18c85900b206ac014e40844217ba9c6f60e13d17fe 2bash 3 4real 0m10.347s 5user 0m0.009s 6sys 0m0.009s

しかし、同じスクリプトをフォアグラウンド実行(-it)してCtrl-C(SIGINT)する場合は、即時終了してくれる。

bash

1$ docker run -it --rm --name bash -v $(pwd)/sleep.sh:/sleep.sh bash bash /sleep.sh 2^C$

なぜこのような違いが出るのだろうか?

調査

以下のようなスクリプトを書いてみた。

bash

1# dockerバージョン表示 2docker --version 3 4# sleep.shを作成 5cat >sleep.sh <<EOF 6sleep 100000 7EOF 8 9# dockerでsleep.shをバックグラウンド実行 10docker run -d --rm --name bash -v $(pwd)/sleep.sh:/sleep.sh bash bash /sleep.sh 11 12# docker stopで停止させてみる -> 停止に10秒かかる 13time docker stop bash 14 15sleep 1 16 17# dockerでsleep.shをバックグラウンド実行 18docker run -d --rm --name bash -v $(pwd)/sleep.sh:/sleep.sh bash bash /sleep.sh 19 20# 同じコンテナでbashを動かし、pkillで子プロセスのsleepをkill -> 即時終了する 21time docker exec bash bash -c "pkill sleep" 22 23# sleep_trap.shを作成(SIGTERMを捕捉し、子プロセスにもシグナル送る) 24cat >sleep_trap.sh <<EOF 25child=0 26kill_child() { 27 echo "killing child...\$child" 28 kill \$child 29} 30trap "kill_child;wait;exit 1" INT TERM 31sleep 100000& 32child=\$! 33wait 34exit 0 35EOF 36 37sleep 1 38 39# dockerでsleep_trap.shをバックグラウンド実行 40docker run -d --rm --name bash -v $(pwd)/sleep_trap.sh:/sleep_trap.sh bash bash /sleep_trap.sh 41 42# docker stopで停止させてみる -> 即時終了する 43time docker stop bash

実行

bash

1Docker version 24.0.6, build ed223bc 284454d3fd1ecbda92978df18c85900b206ac014e40844217ba9c6f60e13d17fe 3bash 4 5real 0m10.347s 6user 0m0.009s 7sys 0m0.009s 8c3e11ef71574f454920b798ff8d32a893e75bd6bcfd5018fc6af45fb55671525 9 10real 0m0.079s 11user 0m0.012s 12sys 0m0.006s 1354faf926c241a8abbe77f45d894ae0439d49fcd525634e338745b7b71fd6bdbd 14bash 15 16real 0m0.431s 17user 0m0.008s 18sys 0m0.008s

docker stopで即時停止できるようになったが、面倒すぎる。
なぜこんなに面倒になっているのか?
フォアグラウンドのCtrl-Cと同じくらい簡単に出来ないのだろうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

回答8

#1

melian

総合スコア21118

投稿2023/10/12 15:26

停止に10秒かかった。

docker stop | Docker Docs

Description

The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL.

この grace period のデフォルト値が 10秒になっていて、--time もしくは -t で変更できます。

Options

OptionShortDefaultDescription
--time-tSeconds to wait before killing the container

bash

1$ time docker stop -t 1 bash 2bash 3 4real 0m2.455s 5user 0m0.005s 6sys 0m0.015s 7 8## docker kill では grace period は無い 9$ time docker kill bash 10bash 11 12real 0m1.110s 13user 0m0.004s 14sys 0m0.014s

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#2

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/10/12 17:29

編集2023/10/12 17:30

#1
そうですね。
質問主旨はSIGTERM(これも設定に依る)でgracefulな終了が出来ない理由がなぜかということです。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#3

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/10/12 21:27

Ctrl-Cを押すと端末によりSIGINTがフォアグラウンドプロセス全体にブロードキャストされるそうです。

linux - How is bash able to kill children processes with CTRL+C - Stack Overflow
https://stackoverflow.com/questions/6108953/how-does-ctrl-c-terminate-a-child-process

なのでバックグラウンドプロセスでもプロセスグループ全体をkillするようにしてみました。

stop_pgid.sh

bash

1# bash /sleep.shを実行してるpidを取得 2pid=$(pgrep -f 'bash /sleep.sh') 3 4# bash /sleep.shを実行してるプロセスをpgidとして持っているpidを取得 5pids=$(ps -o pid,pgid | awk '$2~/^'$pid'$/{print $1;}') 6 7# まとめてpidをkill(普通のkillには負値のpidを指定するとpgid指定だが、busyboxなのでその機能がない) 8kill $pids

上記ファイルを用意し、以下を実施しても即時停止できました。

bash

1$ docker run -d --rm --name bash -v $(pwd)/sleep.sh:/sleep.sh bash bash /sleep.sh 21dbc558d01275440b15bb94123ed5d6dc50f1f7a168d12f951caadd18b9740e9 3$ time bash -c "cat stop_pgid.sh | docker exec -i bash bash" 4 5real 0m0.129s 6user 0m0.003s 7sys 0m0.021s 8$

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#4

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/10/15 00:51

外からpgidでkillすると、docker composeを使ったときに不便なので入口をラップしてみました。

bash

1# dockerバージョン表示 2docker --version 3 4# sleep.shを作成 5cat >sleep.sh <<EOF 6sleep 100000 7EOF 8 9# entry.shを作成 10cat >entry.sh <<EOF 11entrypoint=/sleep.sh 12kill_child() { 13 output=\$(ps -o pid,pgid,args) 14 pids=\$( 15 echo "\$output" | 16 sed '/ps -o pid,pgid,args *\$/d;/bash \\/entry.sh *\$/d' | 17 awk '\$2~/^'\$\$'\$/{print \$1;}' | 18 sed '/'\$\$'/d' 19 ) 20 kill \$pids 21} 22trap "echo trapped;kill_child;wait;exit 1" INT TERM 23bash \$entrypoint & 24wait 25echo "entry finished" 26exit 0 27EOF 28 29# dockerでentry.sh経由でsleep.shをバックグラウンド実行 30docker run -d --rm --name bash -v $(pwd)/sleep.sh:/sleep.sh -v $(pwd)/entry.sh:/entry.sh bash bash /entry.sh 31 32# ログをバックグラウンドで表示 33docker logs -f bash& 34 35# docker stopで停止させてみる -> 即時停止 36time docker stop bash 37wait 38 39# docker-compose.ymlを作成 40cat >docker-compose.yml <<EOF 41version: "3" 42services: 43 sleep: 44 image: bash 45 command: bash /entry.sh 46 volumes: 47 - ./sleep.sh:/sleep.sh 48 - ./entry.sh:/entry.sh 49EOF 50 51# 同じことをdocker composeで実施 52docker compose up -d 53echo 54docker compose logs -f& 55time docker compose down 56wait

これでdocker compose-dを付けずにupしたときにCtrl-Cで停止した場合と、似たような挙動になります(ただしフォアグラウンドだとSIGINT,SIGTERMが1回ずつ出ちゃうと思う)。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#5

hqf00342

総合スコア394

投稿2023/10/17 11:34

melianさんが書かれている通り、あなたのプログラムがSIGTERMを無視しているためにdockerが10秒待っているだけなので、以下のようにsleep.shをSIGTERM認識するようにしてみてはどうでしょうか。

bash:sleep.sh

1#!/bin/bash 2trap "exit 0" TERM 3sleep 100000 & wait $!

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#6

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/10/17 15:30

#5
#1では無視されてるという言及はありませんが、そこから順番に説明した方がいいですね。

まずはsleepを3秒にして、直後にechoを出力し、そのログを出力させてみます。10秒以内なので、SIGTERMで停止できず、3秒で終わりますが、SIGTERMがブロックされてるだけなら直後のecho出力がされずに終了し、SIGTERMが無視されているならecho出力が出てきます。

hoge2.sh

bash

1# sleep.shを作成 2cat >sleep.sh <<EOF 3sleep 3 4echo "hoge" 5EOF 6 7# dockerでsleep.shをバックグラウンド実行 8docker run -d --rm --name bash -v $(pwd)/sleep.sh:/sleep.sh bash bash /sleep.sh 9 10# docker logsでログ出力させる 11docker logs -f bash& 12 13# docker stopで停止させてみる -> 停止に3秒かかる 14time docker stop bash 15 16wait

bash

1$ bash hoge2.sh 2f546b659c7a0fd030f291bbaa1b575feaa5d44d8f7657cf3cf20e955c2468b23 3hoge 4bash 5 6real 0m3.537s 7user 0m0.031s 8sys 0m0.004s 9$

これで無視されていることが分かりました。念のためSIGINTでも試します。

bash

1$ diff hoge2.sh hoge3.sh 214c14 3< time docker stop bash 4--- 5> time docker stop -s INT bash 6$ bash hoge3.sh 7f16d6c814736ed5927b45a60143f1a487e92db7806963eb83fb0daae10d14557 8hoge 9bash 10 11real 0m3.588s 12user 0m0.013s 13sys 0m0.023s 14$

これでSIGINTでもSIGTERMでも無視されていることが分かりました。他方bashのマニュアルには以下の記述があります。

When bash is interactive, in the absence of any traps, it ignores SIGTERM (so that kill 0 does not kill an interactive shell), and SIGINT is caught and handled (so that the wait builtin is interruptible). In all cases, bash ignores SIGQUIT. If job control is in effect, bash ignores SIGTTIN, SIGTTOU, and SIGTSTP.

読んだ通りですが、無視されるかどうかにはiteractiveかどうかとtrapがあるかどうかが関係していることが分かります。実際どう起動されているのかは分かりませんが、少なくともtrapがあれば無視はされません。

以下はそれぞれhoge4.sh hoge5.shとし、trapを入れたものです。実際にシグナルを補足したタイミングを知るために、dateだけ入れています。

bash

1$ diff -up hoge2.sh hoge4.sh 2--- hoge2.sh 2023-10-17 21:30:55.356473047 +0900 3+++ hoge4.sh 2023-10-17 21:53:13.618216200 +0900 4@@ -1,5 +1,7 @@ 5 # sleep.shを作成 6 cat >sleep.sh <<EOF 7+trap "echo trapped;date" TERM 8+date 9 sleep 3 10 echo "hoge" 11 EOF 12$ bash hoge4.sh 13aab18a273ad41c1f01b25d672e1853780560abbfca0f004b72bf5e192597bdd9 14Tue Oct 17 12:55:43 UTC 2023 15trapped 16Tue Oct 17 12:55:46 UTC 2023 17hoge 18bash 19 20real 0m3.436s 21user 0m0.023s 22sys 0m0.014s 23$ diff -up hoge3.sh hoge5.sh 24--- hoge3.sh 2023-10-17 21:38:11.420603296 +0900 25+++ hoge5.sh 2023-10-17 21:58:07.229999184 +0900 26@@ -1,5 +1,7 @@ 27 # sleep.shを作成 28 cat >sleep.sh <<EOF 29+trap "echo trapped;date" INT 30+date 31 sleep 3 32 echo "hoge" 33 EOF 34$ bash hoge5.sh 35377417acce570f1b7392f427d8601d5d59a0d1ef9fd3f5fd3d0efb5dc6455476 36Tue Oct 17 12:58:15 UTC 2023 37trapped 38Tue Oct 17 12:58:18 UTC 2023 39hoge 40bash 41 42real 0m3.814s 43user 0m0.027s 44sys 0m0.009s 45$

trapを入れればシグナルを補足できるのですが、sleepをブロック実行していると、シグナルを補足できるのがsleep実行後になってしまいます。そこで最後にsleep自体をバックグラウンド実行させて、シグナルを即時補足できるようにします。

bash

1$ diff -up hoge4.sh hoge6.sh 2--- hoge4.sh 2023-10-17 21:53:13.618216200 +0900 3+++ hoge6.sh 2023-10-18 00:11:47.341217777 +0900 4@@ -2,7 +2,14 @@ 5 cat >sleep.sh <<EOF 6 trap "echo trapped;date" TERM 7 date 8-sleep 3 9+sleep 3& 10+child=\$! 11+wait $child # prob. trapped while waiting 12+echo $! 13+date 14+wait $child # continue to wait 15+echo $! 16+date 17 echo "hoge" 18 EOF 19$ bash hoge6.sh 209f932acb502809450fb7f46290b9eefaf069918996b132b1a49924ebf174254c 21Tue Oct 17 15:12:35 UTC 2023 22trapped 23Tue Oct 17 15:12:35 UTC 2023 24 25Tue Oct 17 15:12:35 UTC 2023 26 27Tue Oct 17 15:12:38 UTC 2023 28hoge 29bash 30 31real 0m3.736s 32user 0m0.029s 33sys 0m0.012s 34$ diff -up hoge5.sh hoge7.sh 35--- hoge5.sh 2023-10-17 21:58:07.229999184 +0900 36+++ hoge7.sh 2023-10-18 00:15:25.242971003 +0900 37@@ -2,7 +2,14 @@ 38 cat >sleep.sh <<EOF 39 trap "echo trapped;date" INT 40 date 41-sleep 3 42+sleep 3& 43+child=\$! 44+wait $child # prob. trapped while waiting 45+echo $! 46+date 47+wait $child # continue to wait 48+echo $! 49+date 50 echo "hoge" 51 EOF 52$ bash hoge7.sh 533ee7268d05bf7bdef33fb0c467e424efca4a0315bff5820a3d732ee4e19c34c0 54Tue Oct 17 15:16:28 UTC 2023 55trapped 56Tue Oct 17 15:16:28 UTC 2023 57 58Tue Oct 17 15:16:28 UTC 2023 59 60Tue Oct 17 15:16:31 UTC 2023 61hoge 62bash 63 64real 0m3.667s 65user 0m0.014s 66sys 0m0.022s 67$

trapを入れ、sleepをバックグラウンド実行することでSIGTERM/SIGINTが無視されず、ちゃんと即時補足されるようになりました。代わりにwaitが入り、その最中にSIGTERMを受けることで一旦waitが失敗(SIGTERMで異常終了)し、子プロセスを無視して終了することも、再度waitすることもできます。綺麗な終了のためには子プロセスにSIGTERM/SIGINTを送ってそれを再度waitするのが適切だと思います。それが出来ないのであれば、素直に10秒待ってSIGKILLを待つ方が潔い気がします。

ご指摘の方法では子プロセスのsleepは動作中のままコンテナごと消えてしまう運命なので、わざわざtrapする意味があまりありません。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#7

hqf00342

総合スコア394

投稿2023/10/18 00:44

編集2023/10/18 00:52

なるほど、シェルスクリプトの子プロセス/バックグラウンドプロセスを確実に終了したいならば、exitではなく子プロセス達をKILLしたらいかがでしょうか。

bash:sleep.sh

1#!/bin/bash 2trap 'kill $(jobs -p);exit 0' TERM 3sleep 100000 & wait $!

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#8

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/10/18 00:56

#7
えーっと、、、なので、最初の質問に書いてあるとおり

バックグラウンド実行すると子プロセスの明示的な停止がなぜ必要なのか?

が知りたいわけです。フォアグラウンド実行した場合は、端末からCtrl-C(SIGINT)をするだけで停止するわけですが、バックグラウンド実行にした途端SIGTERMで止まらなくなり、よしんば無視しないようにしたとしても、子孫には伝達されない。しかし同じプログラムのはずなのにCtrl-Cだと停止してくれるのがおかしい、というのが質問の主旨なわけです。そしてその回答が

#3

表示内容を取得できませんでした

であり、あなたの回答の子プロセスだけでなく、子孫全体にSIGTERMを送るという方法を提示しています。そして、外からの停止方法の工夫だけでは使い勝手が悪いので、
#4

表示内容を取得できませんでした

でentrypointをラップして子孫全体にSIGTERMを送る処理を内包する形にしてみました。あなたの方法との違いは子孫全体であることと、ラップした形であることです。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

最新の回答から1ヶ月経過したため この意見交換はクローズされました

意見をやりとりしたい話題がある場合は質問してみましょう!

質問する

関連した質問