Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/sdk/nb.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ interface DBCollection {

validate(doc: object, warn?: 'warn'): object;

executeSQL<T>(query: string, params: Array<any>, options?: { query_name?: string }): Promise<sqlResult<T>>;
executeSQL<T>(query: string, params: Array<any>, options?: { query_name?: string, preferred_pool?: string }): Promise<sqlResult<T>>;
name: any;
}

Expand Down
14 changes: 11 additions & 3 deletions src/server/object_services/md_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@ class MDStore {
}, {
limit: Math.min(limit, 1000),
hint: 'deleted_unreclaimed_index',
preferred_pool: 'read_only',
});
return results;
}
Expand Down Expand Up @@ -1123,7 +1124,7 @@ class MDStore {
FROM ${this._objects.name}
WHERE (to_ts(data->>'deleted')<to_ts($1) and data ? 'deleted' and data ? 'reclaimed')
LIMIT ${query_limit};`;
const result = await this._objects.executeSQL(query, [new Date(max_delete_time).toISOString()]);
const result = await this._objects.executeSQL(query, [new Date(max_delete_time).toISOString()], {preferred_pool: 'read_only'});
return db_client.instance().uniq_ids(result.rows, '_id');
}

Expand Down Expand Up @@ -1599,6 +1600,7 @@ class MDStore {
_id: -1
},
limit: limit,
preferred_pool: 'read_only',
})

.then(chunks => ({
Expand Down Expand Up @@ -1773,21 +1775,26 @@ class MDStore {
projection: {
_id: 1,
deleted: 1
}
},
preferred_pool: 'read_only'
})
.then(objects => db_client.instance().uniq_ids(objects, '_id'));
}

has_any_blocks_for_chunk(chunk_id) {
return this._blocks.findOne({
chunk: { $eq: chunk_id, $exists: true },
}, {
preferred_pool: 'read_only'
})
.then(obj => Boolean(obj));
}

has_any_parts_for_chunk(chunk_id) {
return this._parts.findOne({
chunk: { $eq: chunk_id, $exists: true },
}, {
preferred_pool: 'read_only'
})
.then(obj => Boolean(obj));
}
Expand Down Expand Up @@ -2029,7 +2036,8 @@ class MDStore {
projection: {
_id: 1,
deleted: 1
}
},
preferred_pool: 'read_only'
})
.then(objects => db_client.instance().uniq_ids(objects, '_id'));
}
Expand Down
23 changes: 19 additions & 4 deletions src/util/postgres_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ function convert_timestamps(where_clause) {

async function _do_query(pg_client, q, transaction_counter) {
query_counter += 1;

dbg.log3("pg_client.options?.host =", pg_client.options?.host, ", retry =", pg_client.retry_with_default_pool, ", q =", q);

const tag = `T${_.padStart(transaction_counter, 8, '0')}|Q${_.padStart(query_counter.toString(), 8, '0')}`;
try {
// dbg.log0(`postgres_client: ${tag}: ${q.text}`, util.inspect(q.values, { depth: 6 }));
Expand All @@ -265,6 +268,10 @@ async function _do_query(pg_client, q, transaction_counter) {
if (err.routine === 'index_create' && err.code === '42P07') return;
dbg.error(`postgres_client: ${tag}: failed with error:`, err);
await log_query(pg_client, q, tag, 0, /*should_explain*/ false);
if (pg_client.retry_with_default_pool) {
dbg.warn("retrying with default pool. q = ", q);
return _do_query(PostgresClient.instance().get_pool('default'), q, transaction_counter);
}
Comment on lines +271 to +274
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Risk of infinite recursion if default pool also has retry flag set.

If the default pool's retry_with_default_pool is set to true, this creates infinite recursion. The code should either:

  1. Explicitly pass retry_with_default_pool: false when getting the default pool, or
  2. Add a guard to prevent retrying if already using the default pool.

Additionally, the original error details are lost before the retry. Consider logging the original error with more context.

Apply this diff to prevent infinite recursion:

-        if (pg_client.retry_with_default_pool) {
+        if (pg_client.retry_with_default_pool && pg_client !== PostgresClient.instance().get_pool('default')) {
             dbg.warn("retrying with default pool. q = ", q);
             return _do_query(PostgresClient.instance().get_pool('default'), q, transaction_counter);
         }

Or alternatively, ensure the default pool instance never has retry_with_default_pool set:

         if (pg_client.retry_with_default_pool) {
-            dbg.warn("retrying with default pool. q = ", q);
+            dbg.warn("retrying with default pool. original error = ", err.message, ", q = ", q);
-            return _do_query(PostgresClient.instance().get_pool('default'), q, transaction_counter);
+            const default_pool = PostgresClient.instance().get_pool('default');
+            // Ensure we don't retry infinitely if default pool also has retry flag
+            const safe_pool = { ...default_pool, retry_with_default_pool: false };
+            return _do_query(safe_pool, q, transaction_counter);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (pg_client.retry_with_default_pool) {
dbg.warn("retrying with default pool. q = ", q);
return _do_query(PostgresClient.instance().get_pool('default'), q, transaction_counter);
}
if (pg_client.retry_with_default_pool && pg_client !== PostgresClient.instance().get_pool('default')) {
dbg.warn("retrying with default pool. q = ", q);
return _do_query(PostgresClient.instance().get_pool('default'), q, transaction_counter);
}
🤖 Prompt for AI Agents
In src/util/postgres_client.js around lines 271 to 274, the retry branch
unconditionally calls _do_query against the default pool which can cause
infinite recursion if the default pool also has retry_with_default_pool=true and
loses the original error context; change the retry to (a) call
get_pool('default') with retry_with_default_pool explicitly set to false or (b)
add a guard that detects when the current pool is already the default and skip
retrying, and also log the original error before attempting the retry so the
original error details are preserved for debugging.

throw err;
}
}
Expand Down Expand Up @@ -629,6 +636,10 @@ class PostgresTable {
get_pool(key = this.pool_key) {
const pool = this.client.get_pool(key);
if (!pool) {
//if original get_pool was not for the default this.pool_key, try also this.pool_key
if (key && key !== this.pool_key) {
return this.get_pool();
}
throw new Error(`The postgres clients pool ${key} disconnected`);
}
return pool;
Expand Down Expand Up @@ -716,13 +727,14 @@ class PostgresTable {
* @param {Array<any>} params
* @param {{
* query_name?: string,
* preferred_pool?: string,
* }} [options = {}]
*
* @returns {Promise<import('pg').QueryResult<T>>}
*/
async executeSQL(query, params, options = {}) {
/** @type {Pool} */
const pool = this.get_pool();
const pool = this.get_pool(options.preferred_pool);
const client = await pool.connect();

const q = {
Expand Down Expand Up @@ -926,7 +938,7 @@ class PostgresTable {
query_string += ` OFFSET ${sql_query.offset}`;
}
try {
const res = await this.single_query(query_string);
const res = await this.single_query(query_string, undefined, this.get_pool(options.preferred_pool));
return res.rows.map(row => decode_json(this.schema, row.data));
} catch (err) {
dbg.error('find failed', query, options, query_string, err);
Expand All @@ -943,7 +955,7 @@ class PostgresTable {
}
query_string += ' LIMIT 1';
try {
const res = await this.single_query(query_string);
const res = await this.single_query(query_string, undefined, this.get_pool(options.preferred_pool));
if (res.rowCount === 0) return null;
return res.rows.map(row => decode_json(this.schema, row.data))[0];
} catch (err) {
Expand Down Expand Up @@ -1497,7 +1509,8 @@ class PostgresClient extends EventEmitter {
},
read_only: {
instance: null,
size: config.POSTGRES_DEFAULT_MAX_CLIENTS
size: config.POSTGRES_DEFAULT_MAX_CLIENTS,
retry_with_default_pool: true
}
};

Expand Down Expand Up @@ -1737,6 +1750,8 @@ class PostgresClient extends EventEmitter {
};
}
pool.instance.on('error', pool.error_listener);
//propagate retry_with_default_pool into instance so it will be available in _do_query()
pool.instance.retry_with_default_pool = pool.retry_with_default_pool;
}
}

Expand Down
Loading