This is an automated email from the ASF dual-hosted git repository.
Alanxtl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go-samples.git
The following commit(s) were added to refs/heads/main by this push:
new a435cdef fix(graceful_shutdown): use WithShutdown API in sample (#1133)
a435cdef is described below
commit a435cdef3d89d72afcf2e7dd0bab93e7ae5f8c0d
Author: XiaoFei <[email protected]>
AuthorDate: Sat Aug 22 22:05:23 2026 +0800
fix(graceful_shutdown): use WithShutdown API in sample (#1133)
* fix(graceful_shutdown): use WithShutdown API in sample
* fix:ci
* test: add graceful shutdown integration assertions
* fix:ci
* test: align graceful shutdown rejection assertion with reject phase
* test(graceful_shutdown): move assertions into sample code
* test(graceful_shutdown): validate shutdown behavior in sample code
* fix(graceful_shutdown): reject new RPCs after shutdown begins
* style(graceful_shutdown): format imports
* fix(graceful_shutdown): verify framework reject path
* fix:ci
* test:restore github-actions.yml
* fix(graceful_shutdown): simplify shutdown config and clean test hooks
* fix(graceful_shutdown): stabilize framework reject test
* fix:CI
* fix:CI
---
graceful_shutdown/README.md | 19 ++++--
graceful_shutdown/README_CN.md | 17 +++--
graceful_shutdown/go-server/cmd/main.go | 65 ++++++++++++------
integrate_test.sh | 114 +++++++++++++++++++++++++-------
4 files changed, 159 insertions(+), 56 deletions(-)
diff --git a/graceful_shutdown/README.md b/graceful_shutdown/README.md
index c58026d6..06775778 100644
--- a/graceful_shutdown/README.md
+++ b/graceful_shutdown/README.md
@@ -8,7 +8,7 @@ It is useful for verifying these behaviors:
- active notice for long connections on Triple
- passive closing behavior on the consumer side
- waiting for in-flight provider requests during shutdown
-- the effect of `timeout`, `step-timeout`, `consumer-update-wait`, and
`offline-window`
+- the effect of `timeout`, `step-timeout`, `notify-timeout`,
`consumer-update-wait`, and `offline-window`
This sample does **not** include a registry. That means you can test
protocol-level active notice and request draining, but you cannot directly
observe registry unregister propagation in this sample alone.
@@ -58,11 +58,16 @@ If you omit the protocol prefix and only pass
`127.0.0.1:20000`, the direct refe
- `-port=20000`
- `-timeout=60s`
- `-step-timeout=3s`
+- `-notify-timeout=5s`
- `-consumer-update-wait=3s`
- `-offline-window=3s`
- `-delay=0s`
+- `-ignore-context-cancel=false`
+- `-reject-request=false`
`-delay` adds artificial processing delay to every request so you can verify
in-flight request draining.
+`-ignore-context-cancel` is used by the automated integration scenario to keep
the in-flight request running after shutdown begins.
+`-reject-request` is used by the automated integration scenario to start the
framework reject path without relying on the short natural shutdown window.
## Client Flags
@@ -179,17 +184,19 @@ This sample is wired into the root integration test flow:
./integrate_test.sh graceful_shutdown
```
-The script starts the Triple server, runs the client in the background, waits
until at least one request succeeds, and then sends an interrupt signal to
trigger graceful shutdown.
+The script validates two behaviors separately. First, it starts the Triple
server with the built-in integration flags, starts an in-flight Go client
request, waits until that request enters the provider, and then sends an
interrupt signal to trigger graceful shutdown. Second, it starts a fresh server
with framework request rejection enabled and runs a separate short-connection
probe to verify new requests are rejected by the framework.
-Before the client exits, it must observe:
+The integration asserts that:
-- at least one successful request
-- at least one failed request during shutdown
+- the in-flight first request completes successfully after shutdown starts
+- a separate request is rejected by the framework provider filter without
entering the `Greet` handler
+- the server exits within the configured shutdown timeout
-If those expectations are not met, the client panics so CI fails immediately.
+If the expected success and failure counts are not met, or if the reject probe
reaches the `Greet` handler, CI fails immediately.
## Practical Notes
- Triple is the intended protocol for manual verification in this sample.
- This sample is intentionally Triple-only so it focuses on the active notice
path implemented in the current graceful shutdown flow.
+- The server configures graceful shutdown through `dubbo.WithShutdown(...)`,
which is the public instance-level configuration path used by the current API.
- Because this sample has no registry, the "unregister from registry" phase is
only part of the core implementation flow, not something you can fully observe
here.
diff --git a/graceful_shutdown/README_CN.md b/graceful_shutdown/README_CN.md
index b84c902f..e2efd47a 100644
--- a/graceful_shutdown/README_CN.md
+++ b/graceful_shutdown/README_CN.md
@@ -9,7 +9,7 @@
- 长连接消费者的主动通知
- 消费端在停机期间的被动关闭表现
- Provider 停机时对进行中请求的等待与排空
-- `timeout`、`step-timeout`、`consumer-update-wait` 和 `offline-window` 等参数的影响
+- `timeout`、`step-timeout`、`notify-timeout`、`consumer-update-wait` 和
`offline-window` 等参数的影响
该示例**不包含注册中心**。因此你可以验证协议层的主动通知和请求排空行为,但不能直接观察“从注册中心摘除并传播”的完整链路。
@@ -62,11 +62,16 @@ go run ./graceful_shutdown/go-client/cmd
-addr=tri://127.0.0.1:20000 -concurrenc
- `-port=20000`
- `-timeout=60s`
- `-step-timeout=3s`
+- `-notify-timeout=5s`
- `-consumer-update-wait=3s`
- `-offline-window=3s`
- `-delay=0s`
+- `-ignore-context-cancel=false`
+- `-reject-request=false`
其中 `-delay` 会给每次请求增加固定处理延迟,用于观察停机时的在途请求排空效果。
+`-ignore-context-cancel` 用于自动化集成测试场景,使在途请求在停机开始后仍继续执行。
+`-reject-request` 用于自动化集成测试场景,在不依赖自然停机短暂窗口的情况下进入框架拒绝路径。
## 客户端参数
@@ -197,14 +202,16 @@ go run ./graceful_shutdown/go-client/cmd
-addr=tri://127.0.0.1:20000 -short=true
./integrate_test.sh graceful_shutdown
```
-脚本会启动 Triple 服务端,后台运行客户端,在观察到至少一次成功请求后向服务端发送中断信号,并要求客户端在退出前同时观察到:
+脚本会分两段验证行为。第一段使用内置集成测试参数启动 Triple 服务端,先启动一个在途 Go client 请求,等待该请求进入 Provider
后由脚本发送中断信号触发优雅停机;第二段会重新启动一个启用框架请求拒绝的服务端,再启动一个独立的短连接探针,验证新请求会被框架拒绝。
-- 至少一次成功请求
-- 至少一次停机期间的失败请求
+- 停机开始后,第一个进行中的请求仍能成功完成
+- 独立探针请求会被框架 provider filter 拒绝,且不会进入 `Greet` handler
+- 服务端会在配置的停机超时时间内退出
-如果这些条件没有满足,客户端会直接 `panic`,从而使 CI 失败。
+如果预期的成功和失败次数不满足,或拒绝探针进入了 `Greet` handler,CI 会立即失败。
## 补充说明
- 该示例以 Triple 协议为主,用于聚焦当前优雅停机流程中的主动通知路径。
+- 服务端通过 `dubbo.WithShutdown(...)` 配置优雅停机,这是当前 API 使用的实例级公开配置方式。
- 因为没有注册中心,这里只能覆盖协议层停机行为,不能完整覆盖注册中心摘除传播。
diff --git a/graceful_shutdown/go-server/cmd/main.go
b/graceful_shutdown/go-server/cmd/main.go
index 8867f507..334ac308 100644
--- a/graceful_shutdown/go-server/cmd/main.go
+++ b/graceful_shutdown/go-server/cmd/main.go
@@ -19,16 +19,18 @@ package main
import (
"context"
+ "errors"
"flag"
"fmt"
+ "net/http"
"time"
)
import (
+ "dubbo.apache.org/dubbo-go/v3"
"dubbo.apache.org/dubbo-go/v3/graceful_shutdown"
_ "dubbo.apache.org/dubbo-go/v3/imports"
"dubbo.apache.org/dubbo-go/v3/protocol"
- "dubbo.apache.org/dubbo-go/v3/server"
"github.com/dubbogo/gost/log/logger"
)
@@ -38,7 +40,8 @@ import (
)
type GreetProvider struct {
- fixedDelay time.Duration
+ fixedDelay time.Duration
+ ignoreContextCancel bool
}
func (p *GreetProvider) Greet(ctx context.Context, req *greet.GreetRequest)
(*greet.GreetResponse, error) {
@@ -46,14 +49,18 @@ func (p *GreetProvider) Greet(ctx context.Context, req
*greet.GreetRequest) (*gr
logger.Infof("Handling greet request, name=%s delay=%s", req.Name,
p.fixedDelay)
if p.fixedDelay > 0 {
- timer := time.NewTimer(p.fixedDelay)
- defer timer.Stop()
-
- select {
- case <-timer.C:
- case <-ctx.Done():
- logger.Warnf("Greet request canceled before completion,
name=%s err=%v", req.Name, ctx.Err())
- return nil, ctx.Err()
+ if p.ignoreContextCancel {
+ time.Sleep(p.fixedDelay)
+ } else {
+ timer := time.NewTimer(p.fixedDelay)
+ defer timer.Stop()
+
+ select {
+ case <-timer.C:
+ case <-ctx.Done():
+ logger.Warnf("Greet request canceled before
completion, name=%s err=%v", req.Name, ctx.Err())
+ return nil, ctx.Err()
+ }
}
}
@@ -68,40 +75,56 @@ func main() {
port := flag.Int("port", 20000, "triple listen port")
timeout := flag.Duration("timeout", 60*time.Second, "overall graceful
shutdown timeout budget")
stepTimeout := flag.Duration("step-timeout", 3*time.Second, "timeout
for waiting provider and consumer in-flight requests")
+ notifyTimeout := flag.Duration("notify-timeout", 5*time.Second,
"timeout for notifying consumers during graceful shutdown")
consumerUpdateWait := flag.Duration("consumer-update-wait",
3*time.Second, "time to wait for consumers to observe instance changes")
offlineWindow := flag.Duration("offline-window", 3*time.Second, "time
window for observing late requests after offline")
requestDelay := flag.Duration("delay", 0, "artificial delay added to
each greet request")
+ ignoreContextCancel := flag.Bool("ignore-context-cancel", false,
"continue the artificial delay even if the request context is canceled")
+ rejectRequest := flag.Bool("reject-request", false, "start with
framework request rejection enabled for integration validation")
flag.Parse()
- graceful_shutdown.Init(
+ shutdownOpts := []graceful_shutdown.Option{
graceful_shutdown.WithTimeout(*timeout),
graceful_shutdown.WithStepTimeout(*stepTimeout),
+ graceful_shutdown.WithNotifyTimeout(*notifyTimeout),
graceful_shutdown.WithConsumerUpdateWaitTime(*consumerUpdateWait),
graceful_shutdown.WithOfflineRequestWindowTimeout(*offlineWindow),
- )
- logger.Infof("Graceful shutdown initialized, timeout=%s step-timeout=%s
consumer-update-wait=%s offline-window=%s request-delay=%s",
- timeout.String(), stepTimeout.String(),
consumerUpdateWait.String(), offlineWindow.String(), requestDelay.String())
+ }
+ if *rejectRequest {
+ shutdownOpts = append(shutdownOpts,
graceful_shutdown.WithRejectRequest())
+ }
- srv, err := server.NewServer(
- server.WithServerProtocol(
+ ins, err := dubbo.NewInstance(
+ dubbo.WithShutdown(shutdownOpts...),
+ dubbo.WithProtocol(
protocol.WithProtocol("tri"),
protocol.WithPort(*port),
protocol.WithID("tri"),
),
)
if err != nil {
- logger.Fatalf("failed to create server: %v", err)
+ panic(fmt.Sprintf("failed to create dubbo instance: %v", err))
+ }
+ logger.Infof("Graceful shutdown configured, timeout=%s step-timeout=%s
notify-timeout=%s consumer-update-wait=%s offline-window=%s request-delay=%s
ignore-context-cancel=%v reject-request=%v",
+ timeout.String(), stepTimeout.String(), notifyTimeout.String(),
consumerUpdateWait.String(), offlineWindow.String(), requestDelay.String(),
*ignoreContextCancel, *rejectRequest)
+
+ srv, err := ins.NewServer()
+ if err != nil {
+ panic(fmt.Sprintf("failed to create server: %v", err))
}
logger.Infof("Exposing Triple on port %d", *port)
- provider := &GreetProvider{fixedDelay: *requestDelay}
+ provider := &GreetProvider{
+ fixedDelay: *requestDelay,
+ ignoreContextCancel: *ignoreContextCancel,
+ }
if err := greet.RegisterGreetServiceHandler(srv, provider); err != nil {
- logger.Fatalf("failed to register greet service handler: %v",
err)
+ panic(fmt.Sprintf("failed to register greet service handler:
%v", err))
}
logger.Info("Triple server started, press Ctrl+C to trigger graceful
shutdown")
- if err := srv.Serve(); err != nil {
- logger.Fatalf("failed to serve: %v", err)
+ if err := srv.Serve(); err != nil && !errors.Is(err,
http.ErrServerClosed) {
+ panic(fmt.Sprintf("failed to serve: %v", err))
}
}
diff --git a/integrate_test.sh b/integrate_test.sh
index 6d999f7a..77387618 100755
--- a/integrate_test.sh
+++ b/integrate_test.sh
@@ -635,9 +635,10 @@ start_java_server_if_present() {
}
run_graceful_shutdown_sample() {
- local client_log="/tmp/.${PROJECT_NAME}.go-client.log"
+ local inflight_client_log="/tmp/.${PROJECT_NAME}.go-client.inflight.log"
+ local reject_client_log="/tmp/.${PROJECT_NAME}.go-client.reject.log"
+ local inflight_client_pid=""
local server_pid=""
- local client_pid=""
local server_bin="/tmp/.${PROJECT_NAME}.go-server.bin"
local client_bin="/tmp/.${PROJECT_NAME}.go-client.bin"
@@ -660,7 +661,13 @@ run_graceful_shutdown_sample() {
echo "Starting graceful_shutdown Go server..."
(
cd "$P_DIR"
- exec "$server_bin" -timeout=15s -step-timeout=2s -consumer-update-wait=0s
-delay=2s
+ exec "$server_bin" \
+ -timeout=25s \
+ -step-timeout=20s \
+ -consumer-update-wait=0s \
+ -offline-window=0s \
+ -delay=3s \
+ -ignore-context-cancel=true
) >"$GO_SERVER_LOG" 2>&1 &
server_pid="$!"
echo "$server_pid" >"$PID_FILE"
@@ -677,35 +684,42 @@ run_graceful_shutdown_sample() {
return 1
fi
- echo "Running graceful_shutdown Go client..."
+ echo "Running graceful_shutdown in-flight request validation..."
(
cd "$P_DIR"
exec "$client_bin" \
-addr=tri://127.0.0.1:20000 \
- -concurrency=2 \
- -interval=200ms \
- -short \
- -request-timeout=6s \
- -max-requests=12 \
+ -short=true \
+ -max-requests=1 \
-min-successes=1 \
- -min-failures=1 \
- -name-prefix=integration
- ) >"$client_log" 2>&1 &
- client_pid="$!"
-
- if ! wait_for_log_pattern "$client_log" "succeeded" 30; then
- echo "graceful_shutdown client did not observe a successful request before
shutdown"
- cat "$client_log" || true
+ -request-timeout=10s \
+ -name-prefix=integration-inflight
+ ) >"$inflight_client_log" 2>&1 &
+ inflight_client_pid="$!"
+
+ if ! wait_for_log_pattern "$GO_SERVER_LOG" "Handling greet request,
name=integration-inflight-1" 30; then
+ echo "graceful_shutdown in-flight request did not enter the provider"
+ kill_if_running "$inflight_client_pid"
+ wait "$inflight_client_pid" 2>/dev/null || true
+ cat "$inflight_client_log" || true
cat "$GO_SERVER_LOG" || true
return 1
fi
- echo "Triggering graceful shutdown..."
+ echo "Triggering graceful_shutdown after in-flight request entered
provider..."
kill -INT "$server_pid" 2>/dev/null || true
- if ! wait "$client_pid"; then
- echo "graceful_shutdown client exited with failure"
- cat "$client_log" || true
+ if ! wait "$inflight_client_pid"; then
+ echo "graceful_shutdown in-flight request validation failed"
+ cat "$inflight_client_log" || true
+ cat "$GO_SERVER_LOG" || true
+ return 1
+ fi
+ inflight_client_pid=""
+
+ if ! wait_for_log_pattern "$GO_SERVER_LOG" "Greet request finished,
name=integration-inflight-1" 30; then
+ echo "graceful_shutdown in-flight request did not finish in the provider"
+ cat "$inflight_client_log" || true
cat "$GO_SERVER_LOG" || true
return 1
fi
@@ -717,13 +731,65 @@ run_graceful_shutdown_sample() {
fi
wait "$server_pid" 2>/dev/null || true
+ server_pid=""
+ rm -f "$PID_FILE"
+
+ echo "Starting graceful_shutdown reject-stage validation server..."
+ : >"$GO_SERVER_LOG"
+ (
+ cd "$P_DIR"
+ exec "$server_bin" \
+ -timeout=25s \
+ -step-timeout=20s \
+ -consumer-update-wait=0s \
+ -offline-window=0s \
+ -reject-request=true
+ ) >"$GO_SERVER_LOG" 2>&1 &
+ server_pid="$!"
+ echo "$server_pid" >"$PID_FILE"
+
+ if ! wait_for_tcp_port "127.0.0.1" "20000" 30; then
+ echo "graceful_shutdown reject-stage server did not become ready on
127.0.0.1:20000"
+ cat "$GO_SERVER_LOG" || true
+ return 1
+ fi
+
+ echo "Running graceful_shutdown reject-stage probe..."
+ (
+ cd "$P_DIR"
+ exec "$client_bin" \
+ -addr=tri://127.0.0.1:20000 \
+ -short=true \
+ -max-requests=1 \
+ -min-failures=1 \
+ -request-timeout=5s \
+ -name-prefix=integration-reject-probe
+ ) >"$reject_client_log" 2>&1 || {
+ echo "graceful_shutdown reject-stage probe failed"
+ cat "$reject_client_log" || true
+ cat "$GO_SERVER_LOG" || true
+ return 1
+ }
+
+ if grep -q "Handling greet request, name=integration-reject-probe"
"$GO_SERVER_LOG"; then
+ echo "graceful_shutdown reject-stage probe reached the Greet handler"
+ cat "$reject_client_log" || true
+ cat "$GO_SERVER_LOG" || true
+ return 1
+ fi
- if ! grep -q "failed" "$client_log"; then
- echo "graceful_shutdown client did not observe request failures during
shutdown"
- cat "$client_log" || true
+ if ! wait_for_log_pattern "$GO_SERVER_LOG" "application is closing, new
request will be rejected" 30; then
+ echo "graceful_shutdown reject-stage probe was not rejected by the
framework provider filter"
+ cat "$reject_client_log" || true
+ cat "$GO_SERVER_LOG" || true
return 1
fi
+ kill_if_running "$server_pid"
+ wait "$server_pid" 2>/dev/null || true
+ server_pid=""
+ rm -f "$PID_FILE"
+
echo "graceful_shutdown integration completed"
}