diff --git a/router/core/executor.go b/router/core/executor.go index ae72771f96..f3f59c304c 100644 --- a/router/core/executor.go +++ b/router/core/executor.go @@ -51,6 +51,19 @@ type Executor struct { TrackUsageInfo bool } +// Close releases schema and planner references held by the executor so a replaced +// graph mux can be garbage-collected after config reload. +func (e *Executor) Close() { + if e == nil { + return + } + e.ClientSchema = nil + e.RouterSchema = nil + e.PlanConfig = plan.Configuration{} + e.RenameTypeNames = nil + e.Resolver = nil +} + type ExecutorBuildOptions struct { EngineConfig *nodev1.EngineConfiguration Subgraphs []*nodev1.Subgraph diff --git a/router/core/executor_test.go b/router/core/executor_test.go new file mode 100644 index 0000000000..a157b09a49 --- /dev/null +++ b/router/core/executor_test.go @@ -0,0 +1,37 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/wundergraph/graphql-go-tools/v2/pkg/ast" + "github.com/wundergraph/graphql-go-tools/v2/pkg/engine/plan" +) + +func TestExecutorCloseReleasesSchemaReferences(t *testing.T) { + t.Parallel() + + executor := &Executor{ + ClientSchema: &ast.Document{}, + RouterSchema: &ast.Document{}, + PlanConfig: plan.Configuration{DataSources: []plan.DataSource{nil}}, + RenameTypeNames: nil, + } + + executor.Close() + + require.Nil(t, executor.ClientSchema) + require.Nil(t, executor.RouterSchema) + require.Empty(t, executor.PlanConfig.DataSources) + require.Nil(t, executor.RenameTypeNames) + require.Nil(t, executor.Resolver) +} + +func TestExecutorCloseNilSafe(t *testing.T) { + t.Parallel() + + var executor *Executor + require.NotPanics(t, func() { + executor.Close() + }) +} diff --git a/router/core/graph_server.go b/router/core/graph_server.go index af206997be..0ee79d7ac1 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -681,6 +681,8 @@ type graphMux struct { mux *chi.Mux reused atomic.Bool + executor *Executor + planCache *ristretto.Cache[uint64, *planWithMetaData] planFallbackCache *slowplancache.Cache[*planWithMetaData] persistedOperationCache *ristretto.Cache[uint64, NormalizationCacheEntry] @@ -971,6 +973,11 @@ func (s *graphMux) Shutdown(ctx context.Context) error { s.validationCache.Close() s.operationHashCache.Close() + if s.executor != nil { + s.executor.Close() + s.executor = nil + } + var err error if s.accessLogsFileLogger != nil { @@ -1497,6 +1504,7 @@ func (s *graphServer) buildGraphMux( if err != nil { return nil, fmt.Errorf("failed to build plan configuration: %w", err) } + gm.executor = executor s.pubSubProviders = providers if pubSubStartupErr := s.startupPubSubProviders(s.graphServerCtx); pubSubStartupErr != nil {