流式输出/双向流的 handler(Python 生成器)如果执行过程中抛异常, Invoke[chan T] 本身的 err 只描述"调用有没有发起成功",跟这个异常 无关(永远是 nil),channel 只会静默提前关闭,调用方原本完全无法 感知。新增 WithStreamErrors(ctx) 返回一个包过的 ctx 和一个查询函数 streamErr,opt-in 之后可以查到具体错误。 错误记录挂在 WithStreamErrors 返回的 ctx 的对象图里(context.WithValue), 不是全局表——调用方不再引用 ctx/channel 时会被 GC 自然回收,不需要 任何显式清理逻辑,也不依赖 ctx.Done(),即使用 context.Background() 也能正常释放;ctx 之后被别的 context.With*(包括 StickyCtx)再包一层 也不影响查询。 同时补充完整的自动化测试覆盖 example/main.go 里演示过的所有功能: 四种调用模式 × int/struct/slice/[]byte 的组合(client_test.go)、 WithHandlers/call_go 全双工(handlers_test.go)、NewSession 隔离性 与 StickyCtx 路由(session_test.go),之前这些只能靠人肉跑 go run 看输出,现在都有真实断言。
63 lines
2.3 KiB
Go
63 lines
2.3 KiB
Go
package gobridge
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
// streamErrBox 是挂在 ctx.Value 树上的一块可变存储,key 是具体的 channel 值本身。
|
||
// 生命周期完全跟着调用方持有的 ctx/channel 走,不需要任何显式清理:
|
||
// 调用方不再引用它们时,整棵对象图(ctx → streamErrBox → errs → error)
|
||
// 会被 GC 自然回收——不依赖 ctx.Done(),context.Background() 一样能正常释放。
|
||
type streamErrBox struct {
|
||
mu sync.Mutex
|
||
errs map[any]error
|
||
}
|
||
|
||
func (b *streamErrBox) get(ch any) error {
|
||
b.mu.Lock()
|
||
defer b.mu.Unlock()
|
||
return b.errs[ch]
|
||
}
|
||
|
||
func (b *streamErrBox) set(ch any, err error) {
|
||
b.mu.Lock()
|
||
defer b.mu.Unlock()
|
||
b.errs[ch] = err
|
||
}
|
||
|
||
type streamErrBoxKey struct{}
|
||
|
||
// WithStreamErrors 返回一个包过的 ctx,以及一个用于查询流式调用错误的函数 streamErr。
|
||
// 之后用这个 ctx(或它的子 ctx,即使被别的 context.With* 再包一层也不受影响)发起的
|
||
// 流式输出/双向流调用,如果 Python handler 执行过程中抛异常,可以用 streamErr(ch)
|
||
// 查到具体错误,不需要再传一次 ctx。
|
||
//
|
||
// 不调用这个函数是默认行为:流式调用中途出错只会让 channel 静默提前关闭,
|
||
// 调用方拿不到任何错误信息。这是因为 Invoke[chan T] 在建立调用阶段就已经成功
|
||
// 返回了 (ch, nil),后续 Python 生成器内部的异常没有天然的返回值可以携带。
|
||
//
|
||
// ctx, streamErr := gobridge.WithStreamErrors(ctx)
|
||
// ch, err := gobridge.Invoke[chan int](ctx, pool, "range_gen", 1, 10)
|
||
// for v := range ch {
|
||
// fmt.Println(v)
|
||
// }
|
||
// if err := streamErr(ch); err != nil {
|
||
// // channel 是因为 Python 侧异常提前关闭的,而不是正常 yield 完
|
||
// }
|
||
func WithStreamErrors(parent context.Context) (ctx context.Context, streamErr func(ch any) error) {
|
||
box := &streamErrBox{errs: make(map[any]error)}
|
||
ctx = context.WithValue(parent, streamErrBoxKey{}, box)
|
||
return ctx, box.get
|
||
}
|
||
|
||
// recordStreamError 供 invokeStreamOut/invokeStreamBoth 在读到 TypeError 时调用。
|
||
// 如果调用方没有用 WithStreamErrors 包过 ctx,直接是个空操作。
|
||
func recordStreamError(ctx context.Context, ch any, err error) {
|
||
box, ok := ctx.Value(streamErrBoxKey{}).(*streamErrBox)
|
||
if !ok {
|
||
return
|
||
}
|
||
box.set(ch, err)
|
||
}
|