Skip to content

Allow configuring worker_limit at runtime (server startup and per-request) #4269

Description

@grzn

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions