In this patch we replace every single use of SCYLLA_ASSERT() in the cql3/ directory by throwing_assert(). The problem with SCYLLA_ASSERT() is that when it fails, it crashes Scylla. This is almost always a bad idea (see #7871 discussing why), but it's even riskier in front-end code like cql3/: In front-end code, there is a risk that due to a bug in our code, a specific user request can cause Scylla to crash. A malicious user can send this query to all nodes and crash the entire cluster. When the user is not malicious, it causes a small problem (a failing request) to become a much worse crash - and worse, the user has no idea which request is causing this crash and the crash will repeat if the same request is tried again. All of this is solved by using the new throwing_assert(), which is the same as SCYLLA_ASSERT() but throws an exception (using on_internal_error()) instead of crashing. The exception will prevent the code path with the invalid assumption from continuing, but will result in only the current user request being aborted, with a clear error message reporting the internal server error due to an assertion failure. I reviewed all the changes that I did in this patch to check that (to the best of my understanding) none of the assertions in cql3/ involve the sort of serious corruption that might require crashing the Scylla node entirely. throwing_assert() also improves logging of assertion failures compared to the original SCYLLA_ASSERT() - SCYLLA_ASSERT() printed a message to stderr which in many installations is lost, whereas throwing_assert() uses Scylla's standard logger, and also includes a backtrace in the log message. Fixes #13970 (Exorcise assertions from CQL code paths) Refs #7871 (Exorcise assertions from Scylla) Signed-off-by: Nadav Har'El <nyh@scylladb.com>
95 lines
3.4 KiB
C++
95 lines
3.4 KiB
C++
/*
|
|
* Copyright (C) 2019-present ScyllaDB
|
|
*
|
|
* Modified by ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: (LicenseRef-ScyllaDB-Source-Available-1.0 and Apache-2.0)
|
|
*/
|
|
#pragma once
|
|
#include "cdc/log.hh"
|
|
#include "utils/assert.hh"
|
|
#include "service/paxos/cas_request.hh"
|
|
#include "cql3/statements/modification_statement.hh"
|
|
|
|
namespace cql3::statements {
|
|
|
|
using namespace std::chrono;
|
|
|
|
/**
|
|
* Due to some operation on lists, we can't generate the update that a given Modification statement does before
|
|
* we get the values read by the initial read of Paxos. A RowUpdate thus just store the relevant information
|
|
* (include the statement itself) to generate those updates. We'll have multiple RowUpdate for a Batch, otherwise
|
|
* we'll have only one.
|
|
*/
|
|
struct cas_row_update {
|
|
modification_statement const& statement;
|
|
std::vector<query::clustering_range> ranges;
|
|
modification_statement::json_cache_opt json_cache;
|
|
// This statement query options. Different from cas_request::query_options,
|
|
// which may stand for BATCH statement, not individual modification_statement,
|
|
// in case of BATCH
|
|
const query_options& options;
|
|
};
|
|
|
|
/**
|
|
* Processed CAS conditions and update on potentially multiple rows of the same partition.
|
|
*/
|
|
class cas_request: public service::cas_request {
|
|
private:
|
|
std::vector<cas_row_update> _updates;
|
|
schema_ptr _schema;
|
|
// A single partition key. Represented as a vector of partition ranges
|
|
// since this is the conventional format for storage_proxy.
|
|
std::vector<dht::partition_range> _key;
|
|
update_parameters::prefetch_data _rows;
|
|
|
|
public:
|
|
cas_request(schema_ptr schema_arg, std::vector<dht::partition_range> key_arg)
|
|
: _schema(schema_arg)
|
|
, _key(std::move(key_arg))
|
|
, _rows(schema_arg)
|
|
{
|
|
throwing_assert(_key.size() == 1 && query::is_single_partition(_key.front()));
|
|
}
|
|
|
|
dht::partition_range_vector key() const {
|
|
return dht::partition_range_vector(_key);
|
|
}
|
|
|
|
const update_parameters::prefetch_data& rows() const {
|
|
return _rows;
|
|
}
|
|
|
|
lw_shared_ptr<query::read_command> read_command(query_processor& qp) const;
|
|
|
|
void add_row_update(const modification_statement& stmt_arg, std::vector<query::clustering_range> ranges_arg,
|
|
modification_statement::json_cache_opt json_cache_arg, const query_options& options_arg);
|
|
|
|
virtual std::optional<mutation> apply(foreign_ptr<lw_shared_ptr<query::result>> qr,
|
|
const query::partition_slice& slice, api::timestamp_type ts, cdc::per_request_options&) override;
|
|
|
|
/// Build a result set with prefetched rows, but return only
|
|
/// the columns required by CAS.
|
|
///
|
|
/// Each cas_row_update provides a row in the result set.
|
|
/// Rows are ordered the same way as the individual statements appear
|
|
/// in case of batch statement.
|
|
seastar::shared_ptr<cql_transport::messages::result_message>
|
|
build_cas_result_set(seastar::shared_ptr<cql3::metadata> metadata,
|
|
const column_set& mask, bool is_applied) const;
|
|
|
|
private:
|
|
bool applies_to() const;
|
|
std::optional<mutation> apply_updates(api::timestamp_type t) const;
|
|
/// Find a row in prefetch_data which matches primary key identifying a given `cas_row_update`
|
|
struct old_row {
|
|
const clustering_key* ckey;
|
|
const update_parameters::prefetch_data::row* row;
|
|
};
|
|
old_row find_old_row(const cas_row_update& op) const;
|
|
};
|
|
|
|
} // end of namespace "cql3::statements"
|