feat: 添加 WithDefaultTimeout 默认超时配置
Invoke 之前完全依赖调用方传入的 ctx 控制超时,池子被占满或 Python 侧 handler 阻塞时,未设置 deadline 的调用会永久阻塞且不报错。新增 WithDefaultTimeout 选项,仅在 ctx 未设置 deadline 时兜底生效,调用方 显式设置的超时优先级更高。 同时补充 example 中的阻塞/超时演示(demoTimeout、demoBlocking)和 pool_test.go 集成测试,覆盖池占满排队、默认超时、显式 deadline 优先级、 流式输出超时后 channel 静默关闭等场景。
This commit is contained in:
@@ -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 环境
|
||||
|
||||
|
||||
Reference in New Issue
Block a user