登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go gojq 怎样取消仍在计算的查询

来源:17golang原创

时间:2026-10-30 23:04:13 500浏览 收藏

Go gojq 要取消仍在计算的查询,应把可取消的 context 传入 code.RunWithContext(ctx, input) 或 query.RunWithContext(ctx, input)。执行期间取消上下文,迭代器会返回对应的 context 错误。需要检查 Next 返回值是否为 error;只在外层超时后停止等待,不能让未接收上下文的查询自动结束。

官方地址:https://github.com/itchyny/gojq

区分停止等待与停止计算

设想一个提供 JSON 查询的服务:请求已超时,处理协程却仍卡在 iter.Next()。如果只用 select 让请求函数返回,查询所在协程可能继续计算。并发请求增加后,这些遗留计算仍然占用资源,外层超时数字并不代表内部任务已经停止。

还有一种容易漏掉的情况:在每次 Next 之前检查 ctx.Err(),但 Next 本身长期不返回。对于没有中间输出的复杂或无限查询,循环外的检查没有机会再次运行。因此取消信号必须传到 gojq 自身的执行环境,而不能仅停留在消费循环中。

把上下文绑定到查询执行

先 Parse 得到 Query,再 Compile 为 Code;执行阶段调用 RunWithContext,为每次任务创建自己的迭代器。来自 HTTP 请求的 r.Context() 可以作为父上下文,再用 WithTimeout 收紧单次查询预算。父请求取消也能传播到子上下文。

gojq查询执行与上下文的静态关系
静态结构图:请求分组关联父上下文和预算上下文,查询分组包含 Code、RunWithContext 与迭代器;连线表示依赖,不是时间轴。

复用同一查询的 Code 可以避免重复编译,但每次执行仍需要新的上下文和迭代器。本文不共享可变输入,也不在多个协程中并发调用同一个 Iter.Next。相关入口见Code.RunWithContext 官方文档。

在迭代器中处理取消错误

下面的 CollectLimited 接收已经编译好的 Code,为执行设置 250 毫秒预算,并限制结果数量。出错时返回 nil,明确丢弃已收集的部分结果。示例用持续归约的查询说明:即使没有中间值可取,也必须让计算本身收到取消信号。

package main

import (
    "context"
    "errors"
    "fmt"
    "time"

    "github.com/itchyny/gojq"
)

// CollectLimited 接收已编译查询;出错时丢弃部分结果。
// 上下文约束查询执行,不包含调用前的 Parse、Compile。
func CollectLimited(parent context.Context, code *gojq.Code,
    input any, limit int) ([]any, error) {
    if code == nil || limit = limit {
            return nil, fmt.Errorf("查询结果超过上限 %d", limit)
        }
        values = append(values, value)
    }
}

func main() {
    // 示例查询持续计算归约值,不产生可提前消费的中间结果。
    query, err := gojq.Parse("reduce range(0; infinite) as $k (0; . + 2)")
    if err != nil {
        panic(err)
    }
    code, err := gojq.Compile(query)
    if err != nil {
        panic(err)
    }
    _, err = CollectLimited(context.Background(), code, nil, 8)
    switch {
    case errors.Is(err, context.DeadlineExceeded):
        fmt.Println("查询预算已耗尽")
    case errors.Is(err, context.Canceled):
        fmt.Println("上游取消查询")
    case err != nil:
        panic(err)
    }
}

250 毫秒是演示预算,不是保证函数恰在这一时刻返回。错误通过 %w 包装,所以入口可以用 errors.Is 区分 DeadlineExceeded 与 Canceled。这里按首个执行错误终止,是应用选择;不要把所有 gojq 执行错误都改写成超时。

这个示例没有在本文环境执行,所述取消行为来自官方接口与实现。部署时应按实际输入、查询表达式及业务预算配置超时,不把演示时长照搬为统一标准。

说明服务层并发与资源边界

RunWithContext 是协作式取消。当前执行器在解释执行循环中检查 ctx.Done,并将 ctx.Err 作为错误值返回;它不会强制抢占任意 Go 代码。自定义函数、输入迭代器或其他扩展如果在一次调用里阻塞很久,需要自己有退出机制,不能假设解释器能够打断它。可参考执行器的上下文处理。

迭代错误、扩展调用与资源限制的关系
静态结构图:执行器关联取消错误,扩展调用关联独立退出机制,结果数量关联收集内存;说明各层职责,不是性能数据。

Parse、Compile、输入 JSON 解码与结果序列化,也不因把 ctx 传给 RunWithContext 就自动获得相同的可中断能力。生产服务应分别控制输入大小、表达式来源、执行并发和输出体积。结果条数上限只能约束已产生的值,不能限制某一个巨大结果的内存占用。

不必为了使用上下文额外为每次 Next 启动协程。先让 gojq 执行环境感知取消,再决定服务层如何调度任务。若查询仍长期不返回,应检查是否停在扩展调用、输入等待或单次耗时操作,而不是继续叠加只会停止等待的超时包装。

层次本文措施仍需关注
执行计算RunWithContext协作式检查与扩展调用
请求生命周期父 context 与单任务超时是否丢失父取消信号
结果收集条数上限单个结果大小
并发任务每次独立迭代器服务端并发预算与共享数据

处理部分结果及常见问题

流式查询可能先给出部分值,再超时。接口要明确这些值能否交付:本文选择出错即丢弃,适合需要完整结果的调用;若服务允许部分结果,则应同时返回明确的未完成状态,不能只输出已有数组而伪装成完整成功。

观察执行耗时、取消错误数量、活跃查询数、结果体积和超时后任务是否回落,有助于判断取消是否真正传到了执行层。这里提供观察项,不声称已经取得线上改善或吞吐提升。

创建了 WithTimeout,却还在用 Run,可以取消吗?不能仅靠创建上下文实现取消,必须把该上下文传入 RunWithContext。

Next 的 ok 为 true 就是正常数据吗?不是。error 也可能以迭代值出现,需先做类型判断,再处理正常结果。

只要限制结果条数就能限制计算吗?不能。有些查询长时间没有输出,条数限制无从触发;执行预算与结果限制应配合使用。

取消后要再次执行怎么办?使用新的上下文和新的迭代器启动任务。不要复用已取消的上下文,也不要把旧迭代器当成可重新开始的查询。

将请求上下文传到执行入口,读取并分类迭代错误,再明确扩展调用和部分结果的处理职责,才能让“请求不再等待”对应到可控的查询生命周期。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>