Skip to content

Commit

Permalink
add worker_limit option for server code generation (#3376)
Browse files Browse the repository at this point in the history
* add property in exec

* add semaphore for goroutine limitation in template

* generation test with worker_limit option

* add comment

* update init-template

* misc
  • Loading branch information
OldBigBuddha authored Nov 18, 2024
1 parent 288848a commit 89c4e84
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 2 deletions.
4 changes: 4 additions & 0 deletions api/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func TestGenerate(t *testing.T) {
name: "federation2",
workDir: filepath.Join(wd, "testdata", "federation2"),
},
{
name: "worker_limit",
workDir: filepath.Join(wd, "testdata", "workerlimit"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
70 changes: 70 additions & 0 deletions api/testdata/workerlimit/gqlgen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
schema:
- graph/*.graphqls

# Where should the generated server code go?
exec:
filename: graph/generated.go
package: graph
worker_limit: 1

# Uncomment to enable federation
# federation:
# filename: graph/federation.go
# package: graph

# Where should any generated models go?
model:
filename: graph/model/models_gen.go
package: model

# Where should the resolver implementations go?
resolver:
layout: follow-schema
dir: graph
package: graph

# Optional: turn on use `gqlgen:"fieldName"` tags in your models
# struct_tag: json

# Optional: turn on to use []Thing instead of []*Thing
# omit_slice_element_pointers: false

# Optional: turn off to make struct-type struct fields not use pointers
# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing }
# struct_fields_always_pointers: true

# Optional: turn off to make resolvers return values instead of pointers for structs
# resolvers_always_return_pointers: true

# Optional: turn on to return pointers instead of values in unmarshalInput
# return_pointers_in_unmarshalinput: false

# Optional: wrap nullable input fields with Omittable
# nullable_input_omittable: true

# Optional: set to speed up generation time by not performing a final validation pass.
# skip_validation: true

# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.
autobind:
- "github.com/99designs/gqlgen/api/testdata/default/graph/model"

# This section declares type mapping between the GraphQL and go type systems
#
# The first line in each type will be used as defaults for resolver arguments and
# modelgen, the others will be allowed when binding to fields. Configure them to
# your liking
models:
ID:
model:
- github.com/99designs/gqlgen/graphql.ID
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
Int:
model:
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
1 change: 1 addition & 0 deletions api/testdata/workerlimit/graph/model/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package model
28 changes: 28 additions & 0 deletions api/testdata/workerlimit/graph/schema.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# GraphQL schema example
#
# https://gqlgen.com/getting-started/

type Todo {
id: ID!
text: String!
done: Boolean!
user: User!
}

type User {
id: ID!
name: String!
}

type Query {
todos: [Todo!]!
}

input NewTodo {
text: String!
userId: String!
}

type Mutation {
createTodo(input: NewTodo!): Todo!
}
6 changes: 6 additions & 0 deletions codegen/config/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ type ExecConfig struct {
// Only for follow-schema layout:
FilenameTemplate string `yaml:"filename_template,omitempty"` // String template with {name} as placeholder for base name.
DirName string `yaml:"dir"`

// Maximum number of goroutines in concurrency to use when running multiple child resolvers
// Suppressing the number of goroutines generated can reduce memory consumption per request,
// but processing time may increase due to the reduced number of concurrences
// Default: 0 (unlimited)
WorkerLimit uint `yaml:"worker_limit"`
}

type ExecLayout string
Expand Down
1 change: 1 addition & 0 deletions codegen/generated!.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
{{ reserveImport "bytes" }}
{{ reserveImport "embed" }}

{{ reserveImport "golang.org/x/sync/semaphore"}}
{{ reserveImport "github.com/vektah/gqlparser/v2" "gqlparser" }}
{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql" }}
Expand Down
22 changes: 20 additions & 2 deletions codegen/type.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
ret := make(graphql.Array, len(v))
{{- if not $type.IsScalar }}
var wg sync.WaitGroup
{{- if gt $.Config.Exec.WorkerLimit 0 }}
sm := semaphore.NewWeighted({{ $.Config.Exec.WorkerLimit }})
{{- end }}
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
Expand All @@ -124,14 +127,29 @@
}()
{{- end }}
if !isLen1 {
defer wg.Done()
{{- if gt $.Config.Exec.WorkerLimit 0 }}
defer func(){
sm.Release(1)
wg.Done()
}()
{{- else }}
defer wg.Done()
{{- end }}
}
ret[i] = ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
{{- if gt $.Config.Exec.WorkerLimit 0 }}
if err := sm.Acquire(ctx, 1); err != nil {
ec.Error(ctx, ctx.Err())
} else {
go f(i)
}
{{- else }}
go f(i)
{{- end }}
}
{{ else }}
ret[i] = ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, v[i])
Expand Down
2 changes: 2 additions & 0 deletions init-templates/gqlgen.yml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ schema:
exec:
filename: graph/generated.go
package: graph
# Optional: Maximum number of goroutines in concurrency to use per child resolvers(default: unlimited)
# worker_limit: 1000

# Uncomment to enable federation
# federation:
Expand Down

0 comments on commit 89c4e84

Please sign in to comment.