{"record":{"id":"8d3d6b2b0a3d04ac","repo":"vitessio/vitess","slug":"executor-is-closed","errorCode":null,"errorMessage":"executor is closed","messagePattern":"executor is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/schemamanager/tablet_executor.go","lineNumber":142,"sourceCode":"\t\t}\n\t\ttabletInfo, err := exec.ts.GetTablet(ctx, shardInfo.PrimaryAlias)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to get primary tablet info, keyspace: %s, shard: %s, error: %v\", keyspace, shardName, err)\n\t\t}\n\t\texec.tablets = append(exec.tablets, tabletInfo.Tablet)\n\t}\n\n\tif len(exec.tablets) == 0 {\n\t\treturn fmt.Errorf(\"keyspace: %s does not contain any primary tablets\", keyspace)\n\t}\n\texec.isClosed = false\n\treturn nil\n}\n\n// Validate validates a list of sql statements.\nfunc (exec *TabletExecutor) Validate(ctx context.Context, sqls []string) error {\n\tif exec.isClosed {\n\t\treturn errors.New(\"executor is closed\")\n\t}\n\tif err := exec.parseDDLs(sqls); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (exec *TabletExecutor) parseDDLs(sqls []string) error {\n\tfor _, sql := range sqls {\n\t\tstmt, err := exec.parser.Parse(sql)\n\t\tif err != nil {\n\t\t\treturn vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, \"failed to parse sql: %s, got error: %v\", sql, err)\n\t\t}\n\t\tswitch stmt.(type) {\n\t\tcase sqlparser.DDLStatement:\n\t\tcase sqlparser.DBDDLStatement:\n\t\tcase *sqlparser.RevertMigration:","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/schemamanager/tablet_executor.go#L124-L160","documentation":"TabletExecutor.Validate checks a list of SQL statements by parsing DDLs, but refuses to operate once the executor has been closed. isClosed is set after Close(), so any subsequent Validate call returns this error.","triggerScenarios":"Calling Validate (or executing) on a TabletExecutor after Close() was called on it.","commonSituations":"Reusing an executor across schema-manager runs or retry logic without creating a new instance; lifecycle bugs where a deferred Close() runs before a later Validate.","solutions":["Create a new TabletExecutor instance for validation after the previous one was closed","Reorder lifecycle code so Validate happens before Close","Check executor.isClosed (or track closure) before reusing the executor"],"exampleFix":"// before\nexec.Close()\nerr := exec.Validate(ctx, sqls) // executor is closed\n// after\nerr := exec.Validate(ctx, sqls)\nexec.Close()","handlingStrategy":"try-catch","validationCode":"if exec.IsClosed() { exec = schemamanager.NewTabletExecutor(...) }","typeGuard":null,"tryCatchPattern":"if err != nil {\n\tif err.Error() == \"executor is closed\" {\n\t\t// recreate executor and retry once\n\t}\n}","preventionTips":["Use one TabletExecutor per lifecycle phase","Call Close only after all Validate/Execute calls","Avoid storing executors in long-lived state across runs"],"tags":["schemamanager","executor","lifecycle"],"backgroundTag":"closed-resource-usage","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}