Fix MySQLRecordManager DataSource connection churn#6570
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors MySQLRecordManager to reuse a single DataSource instance across operations, shares pending initializations across concurrent operations, and ensures query runners are properly released. It also introduces a close method to clean up connections, which is integrated into the indexing and vector store deletion workflows. The review feedback highlights three key improvement opportunities: moving table name sanitization inside the try block in listKeys to prevent query runner leaks, clearing state variables synchronously in close() to avoid concurrent race conditions, and querying the database time directly within update() using the active query runner to prevent connection pool starvation.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const tableName = this.sanitizeTableName(this.tableName) | ||
|
|
||
| try { |
There was a problem hiding this comment.
| const dataSource = this.dataSourcePromise ? await this.dataSourcePromise.catch(() => undefined) : this.dataSource | ||
| this.dataSourcePromise = undefined | ||
| this.dataSource = undefined | ||
|
|
||
| if (dataSource?.isInitialized) { | ||
| await dataSource.destroy() | ||
| } |
There was a problem hiding this comment.
If close() is called concurrently, multiple calls could attempt to await the same dataSourcePromise and subsequently call destroy() on the same DataSource instance, which can lead to errors. Clearing the state variables synchronously before awaiting the promise prevents this race condition.
const dataSourcePromise = this.dataSourcePromise
const dataSource = this.dataSource
this.dataSourcePromise = undefined
this.dataSource = undefined
const ds = dataSourcePromise ? await dataSourcePromise.catch(() => undefined) : dataSource
if (ds?.isInitialized) {
await ds.destroy()
}|
|
||
| const updatedAt = await this.getTime() | ||
| const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {} | ||
| const updatedAt = await this.getTime() |
There was a problem hiding this comment.
Calling this.getTime() inside update() checks out a second connection and creates another query runner from the pool, which is inefficient and can lead to pool starvation under load. Since we already have an active queryRunner, we can query the database time directly using it.
| const updatedAt = await this.getTime() | |
| const timeRes = await queryRunner.manager.query(`SELECT UNIX_TIMESTAMP(NOW()) AS epoch`) | |
| const updatedAt = Number.parseFloat(timeRes[0].epoch) |
|
Solid fix! The mock DataSource and QueryRunner tests are thorough — verifying initialize/destroy counts and connection lifecycle. This addresses the per-operation pool instantiation issue from #6567. |
Fixes #6567.
MySQLRecordManager was creating and destroying a TypeORM DataSource for every record-manager operation. Because DataSource owns the connection pool, that caused repeated pool creation during indexing and cleanup flows.
This PR changes MySQLRecordManager to:
Verification:
Note:
pnpm --dir packages/observe exec jest src/features/executions/components/ExecutionTreeSidebar.test.tsx --runInBand --detectOpenHandles