{"record":{"id":"c1e89a7dc8d262de","repo":"vitessio/vitess","slug":"no-read-locks-acquired-yet","errorCode":null,"errorMessage":"no read locks acquired yet","messagePattern":"no read locks acquired yet","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/mysqlctl/query.go","lineNumber":343,"sourceCode":"\n\tconn, err := getPoolReconnect(ctx, mysqld.dbaPool)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = mysqld.executeSuperQueryListConn(ctx, conn, []string{\"FLUSH TABLES WITH READ LOCK\"})\n\tif err != nil {\n\t\tconn.Recycle()\n\t\treturn err\n\t}\n\n\tmysqld.lockConn = conn\n\treturn nil\n}\n\nfunc (mysqld *Mysqld) ReleaseGlobalReadLock(ctx context.Context) error {\n\tif mysqld.lockConn == nil {\n\t\treturn errors.New(\"no read locks acquired yet\")\n\t}\n\n\terr := mysqld.executeSuperQueryListConn(ctx, mysqld.lockConn, []string{\"UNLOCK TABLES\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmysqld.lockConn.Recycle()\n\tmysqld.lockConn = nil\n\treturn nil\n}\n\nconst (\n\tsourcePasswordStart = \"  SOURCE_PASSWORD = '\"\n\tsourcePasswordEnd   = \"',\\n\"\n\tmasterPasswordStart = \"  MASTER_PASSWORD = '\"\n\tmasterPasswordEnd   = \"',\\n\"\n\tidentifiedByStart   = \" IDENTIFIED BY '\"","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/query.go#L325-L361","documentation":"ReleaseGlobalReadLock requires that a global read lock was previously acquired: mysqld.lockConn must be non-nil. Releasing without a prior acquisition returns this error, since there is no lock connection on which to issue UNLOCK TABLES. It protects the pairing of Acquire/Release calls on the same Mysqld instance.","triggerScenarios":"Calling ReleaseGlobalReadLock without a prior successful AcquireGlobalReadLock; calling Release twice (second call finds lockConn already nil); releasing on a different Mysqld instance than the one that acquired.","commonSituations":"Cleanup/defer paths that run even when acquisition failed or never happened; error paths that release unconditionally; splitting acquire and release across components that each construct their own Mysqld.","solutions":["Only call ReleaseGlobalReadLock after a successful AcquireGlobalReadLock on the same Mysqld instance","Track lock ownership (e.g. a bool or mutex-held flag) so cleanup code skips release when it does not hold the lock","Make acquire/release pairs symmetric — acquire first, defer release, so the release only runs on success"],"exampleFix":"// before\nerr := mysqld.AcquireGlobalReadLock(ctx)\nif err != nil { return err }\n// ... later, double release ...\nmysqld.ReleaseGlobalReadLock(ctx)\nmysqld.ReleaseGlobalReadLock(ctx) // error: no read locks acquired yet\n// after\nif err := mysqld.AcquireGlobalReadLock(ctx); err == nil {\n    defer mysqld.ReleaseGlobalReadLock(ctx)\n}","handlingStrategy":"validation","validationCode":"var lockHeld bool\nif !lockHeld {\n    return nil // nothing to release\n}\nerr := mysqld.ReleaseGlobalReadLock(ctx)","typeGuard":null,"tryCatchPattern":"if err := mysqld.ReleaseGlobalReadLock(ctx); err != nil {\n    if strings.Contains(err.Error(), \"no read locks acquired yet\") {\n        return nil // nothing held; safe to ignore\n    }\n    return err\n}","preventionTips":["Only release after a confirmed successful acquire","Use defer-based release immediately after a successful acquire","Keep acquire/release within one component so ownership is unambiguous"],"tags":["go","mysqlctl","locking","global-read-lock","api-misuse"],"backgroundTag":"lock-not-held","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}