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 收紧单次查询预算。父请求取消也能传播到子上下文。

复用同一查询的 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 也可能以迭代值出现,需先做类型判断,再处理正常结果。
只要限制结果条数就能限制计算吗?不能。有些查询长时间没有输出,条数限制无从触发;执行预算与结果限制应配合使用。
取消后要再次执行怎么办?使用新的上下文和新的迭代器启动任务。不要复用已取消的上下文,也不要把旧迭代器当成可重新开始的查询。
将请求上下文传到执行入口,读取并分类迭代错误,再明确扩展调用和部分结果的处理职责,才能让“请求不再等待”对应到可控的查询生命周期。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
149 收藏
-
479 收藏
-
136 收藏
-
273 收藏
-
262 收藏
-
262 收藏
-
194 收藏
-
364 收藏
-
263 收藏
-
397 收藏
-
108 收藏
-
349 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习