feat: 添加 WithDefaultTimeout 默认超时配置

Invoke 之前完全依赖调用方传入的 ctx 控制超时,池子被占满或 Python 侧
handler 阻塞时,未设置 deadline 的调用会永久阻塞且不报错。新增
WithDefaultTimeout 选项,仅在 ctx 未设置 deadline 时兜底生效,调用方
显式设置的超时优先级更高。

同时补充 example 中的阻塞/超时演示(demoTimeout、demoBlocking)和
pool_test.go 集成测试,覆盖池占满排队、默认超时、显式 deadline 优先级、
流式输出超时后 channel 静默关闭等场景。
This commit is contained in:
2026-07-23 10:37:24 +08:00
parent db71a904e0
commit 0f4a4ded53
7 changed files with 367 additions and 7 deletions
+49
View File
@@ -186,6 +186,53 @@ def slow_compute(n: int) -> int:
> 限制:长时间不释放 GIL 的 C 扩展(如大规模 numpy 矩阵运算)无法被中断,需等其释放 GIL 后才触发。
### 默认超时(WithDefaultTimeout
`Invoke` 本身不带任何默认超时——如果传入的 `ctx` 没有 deadline(比如直接用 `context.Background()`),且池子被占满(所有 worker 的连接都在处理别的请求),调用会**永久阻塞**,不会自动放弃。
`WithDefaultTimeout` 用于兜底这种情况:仅当调用方传入的 `ctx` **未设置** deadline 时才生效,调用方显式设置的 `context.WithTimeout` 优先级更高,不会被覆盖。
> 池子被占满时新调用是**排队阻塞等待**,不是立刻失败或被跳过——`example/main.go` 中的 `demoBlocking` 用 `workers=2, maxConns=2`(总容量 4)故意占满连接池,
> 再发起一个不设超时的调用,实测会阻塞约 1.8s(等到某个占位任务释放连接)才返回,而不是瞬间失败。
```go
pool, _ := gobridge.NewPool("worker.py",
gobridge.WithDefaultTimeout(500 * time.Millisecond),
)
// 未设置 deadline,池的默认超时自动生效,500ms 后返回 context.DeadlineExceeded
_, err := gobridge.Invoke[string](context.Background(), pool, "sleep_seconds", 2.0)
// 显式传入的 deadline 优先,不受默认超时影响
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := gobridge.Invoke[string](ctx, pool, "sleep_seconds", 1.0) // 正常返回
```
**超时是否会通过 `error` 返回,取决于调用模式:**
| 调用模式 | 超时表现 |
|---|---|
| 普通调用 / 流式输入 | `Invoke` 返回非 nil 的 `error``context.DeadlineExceeded` / `context.Canceled` |
| 流式输出 / 双向流 | `Invoke` 建立阶段失败会返回 `error`;**建立成功后**若中途超时,只会静默关闭已返回的 channel,不会有第二次 `error` |
流式模式下,`for v := range ch` 结束后无法区分"正常读完"还是"被超时打断",需要调用方自行检查传入的 `ctx.Err()`
```go
ch, err := gobridge.Invoke[chan int](ctx, pool, "slow_range_gen", 1, 10, 100)
if err != nil {
// 建立阶段失败/超时
}
for v := range ch {
fmt.Println(v)
}
if ctx.Err() != nil {
// channel 是因为超时/取消提前关闭的,不是正常 yield 完
}
```
完整可运行示例见 [example/main.go](example/main.go) 中的 `demoTimeout`(默认超时 / 显式 deadline 优先级 / 流式超时静默关闭 channel)和 `demoBlocking`(池子占满后阻塞排队)。
## Session 亲和路由
默认情况下,每次 `Invoke` 通过轮询分配 worker 进程。当多次调用需要共享同一 Python 进程的状态时,可以使用 Session 或 StickyCtx 将调用固定到同一进程。
@@ -394,6 +441,7 @@ pool, err := gobridge.NewPool("worker.py",
gobridge.WithSocketDir("/var/run/myapp"), // socket 文件目录,默认 /tmp
gobridge.WithStdout(os.Stdout), // 子进程 stdout,默认 os.Stdout
gobridge.WithStderr(os.Stderr), // 子进程 stderr,默认 os.Stderr
gobridge.WithDefaultTimeout(10*time.Second), // Invoke 默认超时,默认不启用
)
// 静默模式:丢弃子进程输出
@@ -414,6 +462,7 @@ pool, err := gobridge.NewPool("worker.py",
| `WithSocketDir(dir)` | UDS socket 文件目录 | `"/tmp"` |
| `WithStdout(w)` | 子进程标准输出 | `os.Stdout` |
| `WithStderr(w)` | 子进程标准错误 | `os.Stderr` |
| `WithDefaultTimeout(d)` | `Invoke` 默认超时,仅在传入的 `ctx` 未设置 deadline 时生效 | 不启用 |
## 使用 uv 管理 Python 环境