Commit 4579a2a9 authored by Jelte Jansen's avatar Jelte Jansen
Browse files

[326] cpp version for exclusive locking at creation time

parent 08b5add9
......@@ -26,6 +26,8 @@
#include <dns/rrset.h>
#include <dns/rrsetlist.h>
#define SQLITE_SCHEMA_VERSION 1
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
......@@ -77,6 +79,8 @@ const char* const SCHEMA_LIST[] = {
NULL
};
const char* const q_version_str = "SELECT version FROM schema_version";
const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1";
const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
......@@ -254,7 +258,7 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
}
break;
}
sqlite3_reset(query);
sqlite3_clear_bindings(query);
......@@ -295,7 +299,7 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
//
sqlite3_reset(dbparameters->q_count_);
sqlite3_clear_bindings(dbparameters->q_count_);
rc = sqlite3_bind_int(dbparameters->q_count_, 1, zone_id);
if (rc != SQLITE_OK) {
isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
......@@ -653,42 +657,90 @@ prepare(sqlite3* const db, const char* const statement) {
return (prepared);
}
void
checkAndSetupSchema(Sqlite3Initializer* initializer) {
sqlite3* const db = initializer->params_.db_;
// small function to sleep for 0.1 seconds, needed when waiting for
// exclusive database locks (which should only occur on startup, and only
// when the database has not been created yet)
void do_sleep() {
struct timespec req;
req.tv_sec = 0;
req.tv_nsec = 100000000;
nanosleep(&req, NULL);
}
// returns the schema version if the schema version table exists
// returns -1 if it does not
int check_schema_version(sqlite3* db) {
sqlite3_stmt* prepared = NULL;
if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
&prepared, NULL) == SQLITE_OK &&
sqlite3_step(prepared) == SQLITE_ROW) {
initializer->params_.version_ = sqlite3_column_int(prepared, 0);
sqlite3_finalize(prepared);
} else {
logger.info(DATASRC_SQLITE_SETUP);
if (prepared != NULL) {
sqlite3_finalize(prepared);
// At this point in time, the database might be exclusively locked, in
// which case even prepare() will return BUSY, so we may need to try a
// few times
for (size_t i = 0; i < 50; ++i) {
int rc = sqlite3_prepare_v2(db, q_version_str, -1, &prepared, NULL);
if (rc == SQLITE_ERROR) {
// this is the error that is returned when the table does not
// exist
return -1;
} else if (rc == SQLITE_OK) {
break;
} else if (rc != SQLITE_BUSY || i == 50) {
isc_throw(Sqlite3Error, "Unable to prepare version query: "
<< rc << " " << sqlite3_errmsg(db));
}
// We need to create the database. However, there is a potential
// race condition with another process that wants to do the same
// So we acquire an exclusive lock, and then check whether the
// tables have been created again. If not, we create them.
sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL);
if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
&prepared, NULL) == SQLITE_OK &&
sqlite3_step(prepared) == SQLITE_ROW) {
initializer->params_.version_ = sqlite3_column_int(prepared, 0);
sqlite3_finalize(prepared);
} else {
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
isc_throw(Sqlite3Error,
"Failed to set up schema " << SCHEMA_LIST[i]);
}
do_sleep();
}
if (sqlite3_step(prepared) != SQLITE_ROW) {
isc_throw(Sqlite3Error,
"Unable to query version: " << sqlite3_errmsg(db));
}
int version = sqlite3_column_int(prepared, 0);
sqlite3_finalize(prepared);
return version;
}
// return db version
int create_database(sqlite3* db) {
// try to get an exclusive lock. Once that is obtained, do the version
// check *again*, just in case this process was racing another
//
// try for 5 secs (50*0.1)
int rc;
logger.info(DATASRC_SQLITE_SETUP);
for (size_t i = 0; i < 50; ++i) {
rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL,
NULL);
if (rc == SQLITE_OK) {
break;
} else if (rc != SQLITE_BUSY || i == 50) {
isc_throw(Sqlite3Error, "Unable to acquire exclusive lock "
"for database creation: " << sqlite3_errmsg(db));
}
do_sleep();
}
int schema_version = check_schema_version(db);
if (schema_version == -1) {
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
isc_throw(Sqlite3Error,
"Failed to set up schema " << SCHEMA_LIST[i]);
}
}
sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
return SQLITE_SCHEMA_VERSION;
} else {
return schema_version;
}
}
void
checkAndSetupSchema(Sqlite3Initializer* initializer) {
sqlite3* const db = initializer->params_.db_;
int schema_version = check_schema_version(db);
if (schema_version != SQLITE_SCHEMA_VERSION) {
schema_version = create_database(db);
}
initializer->params_.version_ = schema_version;
initializer->params_.q_zone_ = prepare(db, q_zone_str);
initializer->params_.q_record_ = prepare(db, q_record_str);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment