gqlgen中@delete指令导致返回结果不稳定的原因排查
问题描述
Schema 定义
type Account { id: Int nickname: String } directive @delete on FIELD type Query { accounts: [Account] }
@delete 指令的Go实现
func (d directive) Delete(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { fieldContext := graphql.GetFieldContext(ctx) parentResult := fieldContext.Parent.Parent.Result.([]*Account) parentResult[0] = nil parentResult[1] = nil return next(ctx) }
查询语句
query Accounts { accounts { nickname @delete } }
异常现象
每次发送该请求返回结果都不一致:
- 有时返回部分null:
{ "data": { "accounts": [ null, { "nickname": "Jane" }, { "nickname": "Jack" } ] } }
- 有时仅第二个对象为null,有时无null,有时两个对象同时为null(这是预期结果)。
请问该现象的原因是什么?
原因分析
核心问题是GraphQL字段解析的并发特性加上对共享数据的不安全修改导致的竞态条件:
并发解析的竞态:GraphQL引擎处理列表类型字段时,会并行解析列表里每个
Account对象的nickname字段。你的@delete指令会在每个nickname解析时,都去修改同一个parentResult数组(也就是accounts查询返回的列表)。多个goroutine同时修改同一个数组元素,执行顺序完全不确定,导致最终结果随机。错误的修改逻辑:你在字段级指令里直接修改父级查询的返回结果,本身就违反了GraphQL的执行模型。父级字段的结果应该是只读的,字段指令的作用范围应该局限于当前字段的解析,而不是去篡改上层数据。
简单来说,就是多个指令实例在抢着改同一个数组,谁先执行完谁的修改生效,最终结果完全看并发调度的运气,所以每次返回都不一样。
内容的提问来源于stack exchange,提问作者Dan
相关产品推荐
相关产品推荐

