The Pages::conn_config() method is returning garbage SQL statements to SQLite, causing "syntax error" failures when attempting to configure database connections.
Root Cause:
The issue has two parts:
- Static initialization with instance-dependent values: The conn_config() method declares a static array of strings formatted with values from instance member variables (config.mmap_size_mb, config.cache_size_mb, etc.). Because the array is static, it is initialized only once - potentially before any valid config object exists or with garbage values from the first initialization. Subsequent calls reuse the incorrectly initialized static array rather than computing fresh values.
- Null-termination handling: The configure() template method passes SQL statements to sqlite3_exec() using .data() on various string-like types. When the container holds std::string_view objects (or range views), .data() does not guarantee null-termination. Since sqlite3_exec() requires a null-terminated C string, this causes SQLite to read beyond the actual SQL statement into garbage memory, resulting in syntax errors like: near "\337\227\317": syntax error.
Observed Error:
[1764274623:184675][28135:0xffff9b9ccd60], file:collection_0.wt_stable, eviction-server: [WT_VERB_EXTENSION][ERROR]: ext/page_log/palite/palite.cpp:818: void Connection::configure(const Container&) [with Container = std::ranges::ref_view<const std::__cxx11::basic_string<char> [3]>]: sqlite3_exec; return: 1 (SQL logic error); details: 1 (near "g": syntax error); args: '281473030825080', 'g<', '0x0', '0x0', '0x0'
Solution:
- Change Pages::conn_config() to return std::array<std::string, 3> by value instead of a view to a static array. This will ensure fresh SQL statements are generated with correct configuration values on each call.
- Modify the configure() template method to explicitly construct a std::string from each statement before calling sqlite3_exec(), then use .c_str() to obtain a guaranteed null-terminated C string:
Reproducer:
Run: ./test_disagg_failover_perf -cc 1 -kc 1000 -ks 10 -vs 1014 -lc -ingest_size_mb 50 -ve 1 On branch: perf-test-disagg-failover
Patch for fix:
diff --git a/ext/page_log/palite/palite.cpp b/ext/page_log/palite/palite.cpp
index 1b7f2767fb..7fbe930138 100644
--- a/ext/page_log/palite/palite.cpp
+++ b/ext/page_log/palite/palite.cpp
@@ -815,7 +815,8 @@ public:
configure(const Container &cfg_statements)
{
for (const auto &stmt : cfg_statements) {
- SQL_CALL_CHECK(db, sqlite3_exec, db, stmt.data(), nullptr, nullptr, nullptr);
+ std::string sql(stmt);
+ SQL_CALL_CHECK(db, sqlite3_exec, db, sql.c_str(), nullptr, nullptr, nullptr);
}
}
@@ -826,7 +827,8 @@ public:
statements.clear();
for (const auto &stmt : sql_statements) {
sqlite3_stmt *prepared_stmt = nullptr;
- SQL_CALL_CHECK(db, sqlite3_prepare_v2, db, stmt.data(), -1, &prepared_stmt, nullptr);
+ std::string sql(stmt);
+ SQL_CALL_CHECK(db, sqlite3_prepare_v2, db, sql.c_str(), -1, &prepared_stmt, nullptr);
statements.push_back(prepared_stmt);
}
}
@@ -1544,13 +1546,13 @@ struct Pages : public Table<Pages> {
Pages &operator=(const Pages &) = delete;
auto
- conn_config() -> std::ranges::view auto
+ conn_config()
{
const uint64_t MMAP_SIZE = config.mmap_size_mb * 1_MB;
const uint64_t PAGE_SIZE = 16_KB;
const uint64_t CACHE_PAGES = (config.cache_size_mb * 1_MB) / PAGE_SIZE;
- const static std::string config_statements[] = {
+ return std::array{
/*
* Uses memory mapping instead of read/write calls when the database is < mmap_size in
* bytes.
@@ -1568,8 +1570,6 @@ struct Pages : public Table<Pages> {
* of pages.
*/
std::format("PRAGMA cache_size = {};", CACHE_PAGES)};
-
- return std::views::all(config_statements);
}
void