diff --git a/service/paxos/paxos_state.cc b/service/paxos/paxos_state.cc index a051230cf3..2da4136e56 100644 --- a/service/paxos/paxos_state.cc +++ b/service/paxos/paxos_state.cc @@ -438,9 +438,10 @@ static future do_execute_cql_with_timeout(sstring req, const auto cache_key = qp.compute_id(req, "", cql3::internal_dialect()); auto ps_ptr = qp.get_prepared(cache_key); + shared_ptr prepared_msg; if (!ps_ptr) { - const auto msg_ptr = co_await qp.prepare(req, qs, cql3::internal_dialect()); - ps_ptr = msg_ptr->get_prepared(); + prepared_msg = co_await qp.prepare(req, qs, cql3::internal_dialect()); + ps_ptr = prepared_msg->get_prepared(); if (!ps_ptr) { on_internal_error(paxos_state::logger, "prepared statement is null"); } @@ -449,8 +450,8 @@ static future do_execute_cql_with_timeout(sstring req, -1, service::node_local_only::yes); const auto st = ps_ptr->statement; - const auto msg_ptr = co_await st->execute(qp, qs, qo, std::nullopt); - co_return cql3::untyped_result_set(msg_ptr); + const auto result_ptr = co_await st->execute(qp, qs, qo, std::nullopt); + co_return cql3::untyped_result_set(result_ptr); } template diff --git a/table_helper.cc b/table_helper.cc index 8f5a55096e..b96e97c977 100644 --- a/table_helper.cc +++ b/table_helper.cc @@ -135,7 +135,23 @@ future<> table_helper::cache_table_info(cql3::query_processor& qp, service::migr } future<> table_helper::insert(cql3::query_processor& qp, service::migration_manager& mm, service::query_state& qs, noncopyable_function opt_maker) { - co_await cache_table_info(qp, mm, qs); + // _prepared_stmt is a checked_weak_ptr into the prepared statements + // cache and can be invalidated by a concurrent purge (e.g. on a schema + // change). cache_table_info() (re-)prepares and assigns _prepared_stmt, + // but the pin protecting the entry is dropped when try_prepare() + // returns. In release the chain of ready-future co_awaits back to here + // resumes synchronously, but debug builds preempt on every co_await + // even for ready futures, opening a window for a purge to drop the + // entry and leave _prepared_stmt null. Loop until a synchronous + // post-resume check finds _prepared_stmt valid; nothing can run between + // that check and the dereference below. _insert_stmt is a strong + // shared_ptr and is not affected by cache invalidation. + while (true) { + co_await cache_table_info(qp, mm, qs); + if (_prepared_stmt) { + break; + } + } auto opts = opt_maker(); opts.prepare(_prepared_stmt->bound_names); co_await _insert_stmt->execute(qp, qs, opts, std::nullopt);