What happened?
exec.worker_limit can only be configured at codegen time via gqlgen.yml. gqlgen bakes the value into every generated graphql.MarshalSliceConcurrently(...) call as a compile-time integer literal (see codegen/type.gotpl), so the slice-marshalling concurrency limit is frozen at generation time. Changing it requires editing gqlgen.yml and regenerating the whole server.
What did you expect?
The ability to configure the worker limit at runtime:
- Server-wide at startup — e.g.
srv.SetWorkerLimit(n), so the limit can be set from configuration/env without regenerating.
- Per request — e.g. from an extension / middleware, so it can be tuned by request shape, tenant, query complexity, headers, etc.
The runtime function graphql.MarshalSliceConcurrently already accepts workerLimit int64 as an argument (0 = unlimited), so today only the hard-coded codegen literal stands in the way.
Proposal
Resolve the limit at runtime with the following precedence:
per-request override > server-wide default > codegen worker_limit (fallback).
- Add
WorkerLimit *int64 to graphql.OperationContext, plus EffectiveWorkerLimit(codegenDefault int64) int64 and SetWorkerLimit(limit int64) helpers (*int64 so "unset" is distinguishable from an explicit 0 = unlimited).
- Add
SetWorkerLimit(int64) to executor.Executor and handler.Server; copy it into each OperationContext in CreateOperationContext.
- Change codegen to emit
ec.EffectiveWorkerLimit({{ .Config.Exec.WorkerLimit }}) instead of the raw literal, keeping the YAML value as the default.
Fully backward compatible: with nothing set at runtime, EffectiveWorkerLimit returns the codegen default, so behaviour is unchanged. MarshalSliceConcurrently's signature is untouched.
Usage:
// server-wide at startup
srv.SetWorkerLimit(1000)
// per request, e.g. based on headers / complexity
srv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
graphql.GetOperationContext(ctx).SetWorkerLimit(4)
return next(ctx)
})
I have a PR ready implementing this.
versions
gqlgen: master
go: go1.26.5
What happened?
exec.worker_limitcan only be configured at codegen time viagqlgen.yml. gqlgen bakes the value into every generatedgraphql.MarshalSliceConcurrently(...)call as a compile-time integer literal (seecodegen/type.gotpl), so the slice-marshalling concurrency limit is frozen at generation time. Changing it requires editinggqlgen.ymland regenerating the whole server.What did you expect?
The ability to configure the worker limit at runtime:
srv.SetWorkerLimit(n), so the limit can be set from configuration/env without regenerating.The runtime function
graphql.MarshalSliceConcurrentlyalready acceptsworkerLimit int64as an argument (0= unlimited), so today only the hard-coded codegen literal stands in the way.Proposal
Resolve the limit at runtime with the following precedence:
per-request override > server-wide default > codegen
worker_limit(fallback).WorkerLimit *int64tographql.OperationContext, plusEffectiveWorkerLimit(codegenDefault int64) int64andSetWorkerLimit(limit int64)helpers (*int64so "unset" is distinguishable from an explicit0= unlimited).SetWorkerLimit(int64)toexecutor.Executorandhandler.Server; copy it into eachOperationContextinCreateOperationContext.ec.EffectiveWorkerLimit({{ .Config.Exec.WorkerLimit }})instead of the raw literal, keeping the YAML value as the default.Fully backward compatible: with nothing set at runtime,
EffectiveWorkerLimitreturns the codegen default, so behaviour is unchanged.MarshalSliceConcurrently's signature is untouched.Usage:
I have a PR ready implementing this.
versions
gqlgen:mastergo:go1.26.5