From 230985e93eef9267a0858ff1e8ec4d8a7d61fe22 Mon Sep 17 00:00:00 2001 From: Rahim Kanji Date: Sun, 8 Feb 2026 00:06:58 +0500 Subject: [PATCH] Fix: PostgreSQL prepared statement purge race condition The fix ensures that when the purge checks ref_count_client under write lock, it sees the latest value from concurrent threads holding raw pointers, preventing reading stale data. --- lib/PgSQL_PreparedStatement.cpp | 34 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/PgSQL_PreparedStatement.cpp b/lib/PgSQL_PreparedStatement.cpp index 5c44e2c8f..3a4b9400a 100644 --- a/lib/PgSQL_PreparedStatement.cpp +++ b/lib/PgSQL_PreparedStatement.cpp @@ -291,24 +291,22 @@ void PgSQL_STMT_Manager::ref_count_client___purge_stmts_if_needed() noexcept { // meaning there are no other references (from client or server) to this prepared statement. // So we can safely remove this entry. if (global_stmt_info.use_count() == 1) { - - // ref_count_client and ref_count_server should both be 0 in this case - assert(global_stmt_info->ref_count_client.load(std::memory_order_relaxed) == 0); - assert(global_stmt_info->ref_count_server.load(std::memory_order_relaxed) == 0); - - // Atomic counters - num_stmt_with_ref_client_count_zero.fetch_sub(1, std::memory_order_relaxed); - num_stmt_with_ref_server_count_zero.fetch_sub(1, std::memory_order_relaxed); - - // Free ID - free_stmt_ids.push(global_stmt_info->statement_id); - - // Update totals - //stat_totals.s_total -= global_stmt_info->ref_count_server.load(std::memory_order_relaxed); - - // Safe erase from map while iterating - it = map_stmt_hash_to_info.erase(it); - remaining_removals--; + // Use memory_order_acquire to see latest refcount modifications + // Since write lock prevents NEW references, only existing raw pointers can race + if (global_stmt_info->ref_count_client.load(std::memory_order_acquire) == 0 && + global_stmt_info->ref_count_server.load(std::memory_order_acquire) == 0) { + + // Atomic counters + num_stmt_with_ref_client_count_zero.fetch_sub(1, std::memory_order_relaxed); + num_stmt_with_ref_server_count_zero.fetch_sub(1, std::memory_order_relaxed); + + // Free ID + free_stmt_ids.push(global_stmt_info->statement_id); + + // Safe erase from map while iterating + it = map_stmt_hash_to_info.erase(it); + remaining_removals--; + } } else { ++it; }