From 0b9808705dfb5653307b8efe6478e7dd879875ae Mon Sep 17 00:00:00 2001 From: Rogee Date: Fri, 28 Aug 2026 19:22:22 +0800 Subject: [PATCH] HH-776: gracefully stop managed browser containers (#15) --- docs/deployment.md | 52 +++++++++++----- docs/deployment_stop_test.sh | 113 +++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 16 deletions(-) create mode 100755 docs/deployment_stop_test.sh diff --git a/docs/deployment.md b/docs/deployment.md index 8c2b5f3..cdab741 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -128,7 +128,7 @@ docker compose config --quiet docker compose up --detach --build ``` -数据库迁移只支持安全前进,不提供自动破坏性回滚。需要同时恢复旧代码和更新前数据库时,修改下面两个变量后**整块执行一次**;不要逐行或拆块执行。预检、恢复演练、动态容器回收、停服、主库恢复、提交切换和启动都位于同一个 fail-fast subshell 中。 +数据库迁移只支持安全前进,不提供自动破坏性回滚。需要同时恢复旧代码和更新前数据库时,修改下面两个变量后**整块执行一次**;不要逐行或拆块执行。预检、恢复演练、动态容器停止、停服、主库恢复、提交切换和启动都位于同一个 fail-fast subshell 中。 ```bash ( @@ -159,11 +159,22 @@ docker compose up --detach --build docker compose exec -T postgres \ dropdb --force -U creatorhub creatorhub_restore_check - docker ps --all --quiet \ - --filter 'name=^creatorhub-browser-' \ - --filter 'label=io.creatorhub.managed=true' \ - --filter 'label=io.creatorhub.runtime-id' \ - | xargs --no-run-if-empty docker rm --force + list_running_browsers() { + docker ps --quiet \ + --filter 'name=^creatorhub-browser-' \ + --filter 'label=io.creatorhub.managed=true' \ + --filter 'label=io.creatorhub.runtime-id' + } + + browser_ids="$(list_running_browsers)" || exit 1 + for browser_id in $browser_ids; do + if ! docker stop "$browser_id"; then + docker rm --force "$browser_id" + fi + done + + browser_ids="$(list_running_browsers)" || exit 1 + test -z "$browser_ids" docker compose stop creator-hub docker-gateway umask 077 @@ -205,18 +216,27 @@ docker compose up --detach --build ## 停止与数据保留 -`docker compose stop/down` 不管理网关动态创建的浏览器容器;直接执行会留下运行中的浏览器和活动会话。先严格按固定容器名前缀及两个管理标签筛选并回收,命令不带 `--volumes`,因此 Profile 卷仍会保留: +`docker compose stop/down` 不管理网关动态创建的浏览器容器;直接执行会留下运行中的浏览器和活动会话。先严格按固定容器名前缀及两个管理标签筛选并停止;只有单个容器停止失败或超时时才强制删除。命令不带 `--volumes`,因此 Profile 卷仍会保留: ```bash -docker ps --all --quiet \ - --filter 'name=^creatorhub-browser-' \ - --filter 'label=io.creatorhub.managed=true' \ - --filter 'label=io.creatorhub.runtime-id' \ - | xargs --no-run-if-empty docker rm --force -test -z "$(docker ps --all --quiet \ - --filter 'name=^creatorhub-browser-' \ - --filter 'label=io.creatorhub.managed=true' \ - --filter 'label=io.creatorhub.runtime-id')" +set -Eeuo pipefail + +list_running_browsers() { + docker ps --quiet \ + --filter 'name=^creatorhub-browser-' \ + --filter 'label=io.creatorhub.managed=true' \ + --filter 'label=io.creatorhub.runtime-id' +} + +browser_ids="$(list_running_browsers)" || exit 1 +for browser_id in $browser_ids; do + if ! docker stop "$browser_id"; then + docker rm --force "$browser_id" + fi +done + +browser_ids="$(list_running_browsers)" || exit 1 +test -z "$browser_ids" docker compose stop ``` diff --git a/docs/deployment_stop_test.sh b/docs/deployment_stop_test.sh new file mode 100755 index 0000000..0f6de2a --- /dev/null +++ b/docs/deployment_stop_test.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +cd "$(dirname "$0")/.." + +extract_block() { + awk -v wanted="$1" ' + /^```bash$/ { + in_code = 1 + strict = "" + wrapper = "" + next + } + in_code && /^\($/ { wrapper = $0 } + in_code && /^[[:space:]]*set -Eeuo pipefail$/ { strict = $0 } + /^[[:space:]]*list_running_browsers\(\) \{/ { + count++ + capture = count == wanted + if (capture) { + if (wrapper) print wrapper + print strict + } + } + capture { print } + capture && /^[[:space:]]*docker compose stop/ { + capture = 0 + if (!wrapper) exit + closing = 1 + } + closing && /^\)$/ { print; exit } + /^```$/ { in_code = 0 } + ' docs/deployment.md +} + +docker() { + printf '%s\n' "$*" >>"$calls" + case "$1" in + ps) + [[ "$*" == 'ps --quiet --filter name=^creatorhub-browser- --filter label=io.creatorhub.managed=true --filter label=io.creatorhub.runtime-id' ]] || return 64 + query_count=0 + [[ ! -e "$state/query-count" ]] || read -r query_count <"$state/query-count" + ((query_count += 1)) + printf '%s\n' "$query_count" >"$state/query-count" + [[ "$mode:$query_count" != query_failure:1 && "$mode:$query_count" != recheck_failure:2 ]] || return 42 + [[ -e "$state/stopped" || -e "$state/removed" ]] || printf '%s\n' browser-1 + ;; + stop) + case "$mode" in + stop_success) : >"$state/stopped" ;; + stop_failure) return 1 ;; + stop_timeout) return 124 ;; + esac + ;; + rm) + [[ "$*" == 'rm --force browser-1' ]] || return 64 + : >"$state/removed" + ;; + compose) + [[ "$*" == 'compose stop' || "$*" == 'compose stop creator-hub docker-gateway' ]] || return 64 + ;; + esac +} +export -f docker + +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT +query='ps --quiet --filter name=^creatorhub-browser- --filter label=io.creatorhub.managed=true --filter label=io.creatorhub.runtime-id' +scenario_count=0 + +for block_number in 1 2; do + block="$(extract_block "$block_number")" + bash -n <<<"$block" + [[ "$block" == *'set -Eeuo pipefail'* ]] + if [[ $block_number -eq 1 ]]; then + [[ "${block%%$'\n'*}" == '(' && "${block##*$'\n'}" == ')' ]] + compose='compose stop creator-hub docker-gateway' + else + [[ "${block%%$'\n'*}" == 'set -Eeuo pipefail' ]] + compose='compose stop' + fi + + for mode in stop_success stop_failure stop_timeout query_failure recheck_failure; do + state="$work/$block_number-$mode" + calls="$state/calls" + mkdir "$state" + export mode state calls + + status=0 + bash -c "$block" || status=$? + + case "$mode" in + stop_success) + [[ $status -eq 0 ]] + diff -u <(printf '%s\n' "$query" 'stop browser-1' "$query" "$compose") "$calls" + ;; + stop_failure | stop_timeout) + [[ $status -eq 0 ]] + diff -u <(printf '%s\n' "$query" 'stop browser-1' 'rm --force browser-1' "$query" "$compose") "$calls" + ;; + query_failure) + [[ $status -eq 1 ]] + diff -u <(printf '%s\n' "$query") "$calls" + ;; + recheck_failure) + [[ $status -eq 1 ]] + diff -u <(printf '%s\n' "$query" 'stop browser-1' "$query") "$calls" + ;; + esac + scenario_count=$((scenario_count + 1)) + done +done + +[[ $scenario_count -eq 10 ]]