fix: WithDefaultTimeout 不再套用到流式输出/双向流
流式输出(Invoke[chan T])返回的 channel 由调用方通过 range 自行决定 消费多久,典型场景是流式聊天回复,可能正常持续几十秒甚至更久。之前 WithDefaultTimeout 会不分场景地把这个全局默认超时套到整个调用生命周期 上(包括流还在正常输出的过程中),导致长时间运行的正常流式响应被腰斩。 改成流式输出/双向流完全不受 WithDefaultTimeout 影响,只认调用方显式传入 的 ctx deadline;需要超时保护的话必须自己 context.WithTimeout。普通调用 和流式输入不受影响,继续吃池子的默认超时。 新增 TestStreamOutIgnoresDefaultTimeout 验证;example/main.go 的 demoTimeout 补充示例4 演示这一行为;README 同步更新。
This commit is contained in:
@@ -209,16 +209,25 @@ defer cancel()
|
||||
result, err := gobridge.Invoke[string](ctx, pool, "sleep_seconds", 1.0) // 正常返回
|
||||
```
|
||||
|
||||
> **`WithDefaultTimeout` 只对普通调用和流式输入生效,对流式输出/双向流完全不生效**(即使不设置也不会被套用)。
|
||||
> 原因:`Invoke[chan T]` 返回的 channel 由调用方通过 `range` 自行决定消费多久——典型场景是流式聊天回复,
|
||||
> 可能正常持续几十秒甚至更久,如果套一个全局默认超时,会在输出还在正常进行时把它腰斩。
|
||||
> 流式输出/双向流的调用**只认调用方显式传入的 deadline**;不想让它无限期阻塞(包括等待连接、等待数据),
|
||||
> 必须自己 `context.WithTimeout` 包一层传进去,`WithDefaultTimeout` 在这里不提供任何兜底。
|
||||
|
||||
**超时是否会通过 `error` 返回,取决于调用模式:**
|
||||
|
||||
| 调用模式 | 超时表现 |
|
||||
|---|---|
|
||||
| 普通调用 / 流式输入 | `Invoke` 返回非 nil 的 `error`(`context.DeadlineExceeded` / `context.Canceled`) |
|
||||
| 流式输出 / 双向流 | `Invoke` 建立阶段失败会返回 `error`;**建立成功后**若中途超时,只会静默关闭已返回的 channel,不会有第二次 `error` |
|
||||
| 调用模式 | `WithDefaultTimeout` 是否生效 | 超时表现 |
|
||||
|---|---|---|
|
||||
| 普通调用 / 流式输入 | 生效 | `Invoke` 返回非 nil 的 `error`(`context.DeadlineExceeded` / `context.Canceled`) |
|
||||
| 流式输出 / 双向流 | **不生效**,只认显式 deadline | `Invoke` 建立阶段失败会返回 `error`;**建立成功后**若显式 ctx 超时,只会静默关闭已返回的 channel,不会有第二次 `error` |
|
||||
|
||||
流式模式下,`for v := range ch` 结束后无法区分"正常读完"还是"被超时打断",需要调用方自行检查传入的 `ctx.Err()`:
|
||||
|
||||
```go
|
||||
// 流式输出必须自己设置超时,WithDefaultTimeout 不会兜底
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
ch, err := gobridge.Invoke[chan int](ctx, pool, "slow_range_gen", 1, 10, 100)
|
||||
if err != nil {
|
||||
// 建立阶段失败/超时
|
||||
|
||||
Reference in New Issue
Block a user