Cluster discovery maintenance
2026-09-22: MySQL topology result ownership and fixture isolation
Failure and cause
TestMySQLReplicationAndGroupDiscovery intermittently reported no available primary,
an unknown/incorrect cluster type, or a corrupted destination such as
1rimary.internal instead of primary.internal. Repeating the original test
100 times reproduced failures in both replication modes. This was not solely a
timeout or a fixture scheduling problem: there were two independent defects.
- Production result ownership: pinned go-mysql v1.13.0's
mysql.Resultset.GetStringusesutils.ByteSliceToString, which borrows the result bytes.mysql.Result.Closereturns that resultset to a global pool.probeMySQLretained hostnames (and potentially already-lowercase roles) inClusterInfo.Nodesafter closing the result. Subsequent queries or connections could overwrite the bytes while discovery still used those nodes. - In-process fixture schema reuse: the client and fixture share that global
resultset pool.
Resultset.Resetretains field pointers. The dependency'sBuildSimpleTextResultsetinitializes fields only when their entries are nil, so it can reuse the preceding client result's names, types and raw field data for an unrelated fixture response. That can produce invalid response schemas even after retained topology strings are fixed. A 500-repeat intermediate check with only the ownership fix still reproduced discovery failures.
These conclusions come from the pinned dependency source and local reproductions, not from a live MySQL server.
Changes and resulting behavior
mysqlTopologyNodeclones host and role strings before a topology node escapes the result lifetime. Both group-replication and Source_Host/Master_Host discovery use it; role normalization remains unchanged.- The test fixture now creates independent MySQL field metadata and text-encoded
rows using
mysqlTopologyResult, avoiding the dependency's pooled simple-text builder. It still serves the actual MySQL wire protocol overnet.Pipe. - The original selected-primary and topology-type assertions remain. Additional assertions require the selected port/role and exactly the expected primary and replica/secondary node roles.
TestMySQLTopologyOwnsResultStringsdeterministically overwrites borrowed result buffers after closing the result and verifies the retained node is unchanged. It covers uppercase and already-lowercase roles, without assuming which objectsync.Poolwill return.
Scope and compatibility
Only internal/cluster is changed. There are no dependency upgrades, schema/data
migrations, retry additions, timeout increases, weakened assertions, or changes
to SQL commands, unknown-topology fallback, permissions or primary-selection rules.
The safe-sql-updates review confirms discovery remains read-only: no transaction,
lock or database write was added. The production change adds small owned string
allocations per discovered MySQL node and prevents reuse-induced routing corruption.
Validation
Using GOTOOLCHAIN=go1.25.0:
go test ./internal/cluster -run 'TestMySQL(ReplicationAndGroupDiscovery|TopologyOwnsResultStrings)' -count=1000 -timeout=120s— passed.go test ./internal/cluster -count=20 -timeout=120s— passed.go vet ./internal/cluster— passed.git diff --check -- internal/cluster— passed.- After the concurrent Vite build finished,
go test ./... -count=1 -timeout=120s— all packages passed, including API and cluster (47.1 seconds wall time). go vet ./...— passed.
These are isolated protocol fixtures, not live-server validation. Full-worktree
validation ran after the concurrent Vite build finished, avoiding compilation of
Go embedded assets while web/dist was being replaced. Shared rationale/testing documents
remain parent-owned; this note provides the scoped change rationale.