Add Rhizome DB retries on SQL statement preparation

It turns out that if the DB is locked, sqlite_prepare_v2() call can return
SQLITE_BUSY.  The retry logic (implemented for issue #2) only provided for
sqlite_step() to return SQLITE_BUSY.  It was a fairly straightforward matter to
extend the retry logic to cover statement preparation in an equally general
fashion.

The problem was observed while diagnosing failures in the rhizomeprotocol
DirectPush test case: the "servald rhizome list" command was failing due to a
locked database.  See issue #9.
This commit is contained in:
Andrew Bettison 2012-10-04 14:30:20 +09:30
parent e1cacdb687
commit 3040d2b665
4 changed files with 50 additions and 45 deletions

View File

@ -243,8 +243,8 @@ __RHIZOME_INLINE int sqlite_code_busy(int code)
return code == SQLITE_BUSY || code == SQLITE_LOCKED;
}
sqlite3_stmt *_sqlite_prepare(struct __sourceloc, const char *sqlformat, ...);
sqlite3_stmt *_sqlite_prepare_loglevel(struct __sourceloc, int log_level, strbuf stmt);
sqlite3_stmt *_sqlite_prepare(struct __sourceloc where, sqlite_retry_state *retry, const char *sqlformat, ...);
sqlite3_stmt *_sqlite_prepare_loglevel(struct __sourceloc where, int log_level, sqlite_retry_state *retry, strbuf stmt);
int _sqlite_retry(struct __sourceloc where, sqlite_retry_state *retry, const char *action);
void _sqlite_retry_done(struct __sourceloc where, sqlite_retry_state *retry, const char *action);
int _sqlite_step_retry(struct __sourceloc where, int log_level, sqlite_retry_state *retry, sqlite3_stmt *statement);
@ -255,8 +255,8 @@ int _sqlite_exec_int64(struct __sourceloc, long long *result, const char *sqlfor
int _sqlite_exec_int64_retry(struct __sourceloc, sqlite_retry_state *retry, long long *result, const char *sqlformat,...);
int _sqlite_exec_strbuf(struct __sourceloc, strbuf sb, const char *sqlformat,...);
#define sqlite_prepare(fmt,...) _sqlite_prepare(__HERE__, (fmt), ##__VA_ARGS__)
#define sqlite_prepare_loglevel(ll,sb) _sqlite_prepare_loglevel(__HERE__, (ll), (sb))
#define sqlite_prepare(rs,fmt,...) _sqlite_prepare(__HERE__, (rs), (fmt), ##__VA_ARGS__)
#define sqlite_prepare_loglevel(ll,rs,sb) _sqlite_prepare_loglevel(__HERE__, (ll), (rs), (sb))
#define sqlite_retry(rs,action) _sqlite_retry(__HERE__, (rs), (action))
#define sqlite_retry_done(rs,action) _sqlite_retry_done(__HERE__, (rs), (action))
#define sqlite_step(stmt) _sqlite_step_retry(__HERE__, LOG_LEVEL_ERROR, NULL, (stmt))

View File

@ -270,14 +270,14 @@ void _sqlite_retry_done(struct __sourceloc where, sqlite_retry_state *retry, con
Returns -1 if an error occurs (logged as an error), otherwise zero with the prepared
statement in *statement.
*/
sqlite3_stmt *_sqlite_prepare(struct __sourceloc where, const char *sqlformat, ...)
sqlite3_stmt *_sqlite_prepare(struct __sourceloc where, sqlite_retry_state *retry, const char *sqlformat, ...)
{
strbuf sql = strbuf_alloca(8192);
strbuf_va_printf(sql, sqlformat);
return _sqlite_prepare_loglevel(where, LOG_LEVEL_ERROR, sql);
return _sqlite_prepare_loglevel(where, LOG_LEVEL_ERROR, retry, sql);
}
sqlite3_stmt *_sqlite_prepare_loglevel(struct __sourceloc where, int log_level, strbuf stmt)
sqlite3_stmt *_sqlite_prepare_loglevel(struct __sourceloc where, int log_level, sqlite_retry_state *retry, strbuf stmt)
{
sqlite3_stmt *statement = NULL;
if (strbuf_overrun(stmt)) {
@ -286,16 +286,23 @@ sqlite3_stmt *_sqlite_prepare_loglevel(struct __sourceloc where, int log_level,
}
if (!rhizome_db && rhizome_opendb() == -1)
return NULL;
switch (sqlite3_prepare_v2(rhizome_db, strbuf_str(stmt), -1, &statement, NULL)) {
case SQLITE_OK:
case SQLITE_DONE:
break;
default:
logMessage(log_level, where, "query invalid, %s: %s", sqlite3_errmsg(rhizome_db), strbuf_str(stmt));
sqlite3_finalize(statement);
return NULL;
while (1) {
switch (sqlite3_prepare_v2(rhizome_db, strbuf_str(stmt), -1, &statement, NULL)) {
case SQLITE_OK:
case SQLITE_DONE:
return statement;
case SQLITE_BUSY:
case SQLITE_LOCKED:
if (retry && _sqlite_retry(where, retry, strbuf_str(stmt))) {
break; // back to sqlite3_prepare_v2()
}
// fall through...
default:
logMessage(log_level, where, "query invalid, %s: %s", sqlite3_errmsg(rhizome_db), strbuf_str(stmt));
sqlite3_finalize(statement);
return NULL;
}
}
return statement;
}
int _sqlite_step_retry(struct __sourceloc where, int log_level, sqlite_retry_state *retry, sqlite3_stmt *statement)
@ -350,7 +357,7 @@ static int _sqlite_vexec_void(struct __sourceloc where, int log_level, sqlite_re
{
strbuf stmt = strbuf_alloca(8192);
strbuf_vsprintf(stmt, sqlformat, ap);
return _sqlite_exec_void_prepared(where, log_level, retry, sqlite_prepare_loglevel(log_level, stmt));
return _sqlite_exec_void_prepared(where, log_level, retry, _sqlite_prepare_loglevel(where, log_level, retry, stmt));
}
/* Convenience wrapper for executing an SQL command that returns no value.
@ -399,7 +406,7 @@ static int _sqlite_vexec_int64(struct __sourceloc where, sqlite_retry_state *ret
{
strbuf stmt = strbuf_alloca(8192);
strbuf_vsprintf(stmt, sqlformat, ap);
sqlite3_stmt *statement = _sqlite_prepare_loglevel(where, LOG_LEVEL_ERROR, stmt);
sqlite3_stmt *statement = _sqlite_prepare_loglevel(where, LOG_LEVEL_ERROR, retry, stmt);
if (!statement)
return -1;
int ret = 0;
@ -465,13 +472,13 @@ int _sqlite_exec_strbuf(struct __sourceloc where, strbuf sb, const char *sqlform
{
strbuf stmt = strbuf_alloca(8192);
strbuf_va_printf(stmt, sqlformat);
sqlite3_stmt *statement = _sqlite_prepare_loglevel(where, LOG_LEVEL_ERROR, stmt);
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = _sqlite_prepare_loglevel(where, LOG_LEVEL_ERROR, &retry, stmt);
if (!statement)
return -1;
int ret = 0;
int rowcount = 0;
int stepcode;
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
while ((stepcode = _sqlite_step_retry(where, LOG_LEVEL_ERROR, &retry, statement)) == SQLITE_ROW) {
int columncount = sqlite3_column_count(statement);
if (columncount != 1) {
@ -515,11 +522,10 @@ int rhizome_make_space(int group_priority, long long bytes)
return 0;
/* Okay, not enough space, so free up some. */
sqlite3_stmt *statement = sqlite_prepare("select id,length from files where highestpriority < %d order by descending length", group_priority);
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "select id,length from files where highestpriority < %d order by descending length", group_priority);
if (!statement)
return -1;
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
while (bytes > (rhizome_space - 65536 - rhizome_database_used_bytes())
&& sqlite_step_retry(&retry, statement) == SQLITE_ROW
) {
@ -562,10 +568,10 @@ int rhizome_drop_stored_file(const char *id,int maximum_priority)
{
if (!rhizome_str_is_file_hash(id))
return WHYF("invalid file hash id=%s", alloca_toprint(-1, id, strlen(id)));
sqlite3_stmt *statement = sqlite_prepare("select id from manifests where filehash='%s'", id);
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "select id from manifests where filehash='%s'", id);
if (!statement)
return WHYF("Could not drop stored file id=%s", id);
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
int can_drop = 1;
while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) {
/* Find manifests for this file */
@ -667,7 +673,7 @@ int rhizome_store_bundle(rhizome_manifest *m)
return -1;
sqlite3_stmt *stmt;
if ((stmt = sqlite_prepare("INSERT OR REPLACE INTO MANIFESTS(id,manifest,version,inserttime,bar,filesize,filehash) VALUES(?,?,?,?,?,?,?);")) == NULL)
if ((stmt = sqlite_prepare(&retry, "INSERT OR REPLACE INTO MANIFESTS(id,manifest,version,inserttime,bar,filesize,filehash) VALUES(?,?,?,?,?,?,?);")) == NULL)
goto rollback;
if (!( sqlite_code_ok(sqlite3_bind_text(stmt, 1, manifestid, -1, SQLITE_TRANSIENT))
&& sqlite_code_ok(sqlite3_bind_blob(stmt, 2, m->manifestdata, m->manifest_bytes, SQLITE_TRANSIENT))
@ -687,7 +693,7 @@ int rhizome_store_bundle(rhizome_manifest *m)
// we might need to leave the old file around for a bit
// clean out unreferenced files first
if ((stmt = sqlite_prepare("DELETE FROM FILES WHERE inserttime < ? AND NOT EXISTS( SELECT 1 FROM MANIFESTS WHERE MANIFESTS.filehash = FILES.id);")) == NULL)
if ((stmt = sqlite_prepare(&retry, "DELETE FROM FILES WHERE inserttime < ? AND NOT EXISTS( SELECT 1 FROM MANIFESTS WHERE MANIFESTS.filehash = FILES.id);")) == NULL)
goto rollback;
if (!sqlite_code_ok(sqlite3_bind_int64(stmt, 1, (long long)(gettime_ms() - 60000)))) {
WHYF("query failed, %s: %s", sqlite3_errmsg(rhizome_db), sqlite3_sql(stmt));
@ -703,7 +709,7 @@ int rhizome_store_bundle(rhizome_manifest *m)
if (closed<1) closed=0;
int ciphered=rhizome_manifest_get_ll(m,"cipheredgroup");
if (ciphered<1) ciphered=0;
if ((stmt = sqlite_prepare("INSERT OR REPLACE INTO GROUPLIST(id,closed,ciphered,priority) VALUES (?,?,?,?);")) == NULL)
if ((stmt = sqlite_prepare(&retry, "INSERT OR REPLACE INTO GROUPLIST(id,closed,ciphered,priority) VALUES (?,?,?,?);")) == NULL)
goto rollback;
if (!( sqlite_code_ok(sqlite3_bind_text(stmt, 1, manifestid, -1, SQLITE_TRANSIENT))
&& sqlite_code_ok(sqlite3_bind_int(stmt, 2, closed))
@ -720,7 +726,7 @@ int rhizome_store_bundle(rhizome_manifest *m)
}
if (m->group_count > 0) {
if ((stmt = sqlite_prepare("INSERT OR REPLACE INTO GROUPMEMBERSHIPS(manifestid,groupid) VALUES(?, ?);")) == NULL)
if ((stmt = sqlite_prepare(&retry, "INSERT OR REPLACE INTO GROUPMEMBERSHIPS(manifestid,groupid) VALUES(?, ?);")) == NULL)
goto rollback;
int i;
for (i=0;i<m->group_count;i++){
@ -758,7 +764,8 @@ int rhizome_list_manifests(const char *service, const char *sender_sid, const ch
strbuf_sprintf(b, " OFFSET %u", offset);
if (strbuf_overrun(b))
RETURN(WHYF("SQL command too long: ", strbuf_str(b)));
sqlite3_stmt *statement = sqlite_prepare("%s", strbuf_str(b));
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "%s", strbuf_str(b));
if (!statement)
return -1;
int ret = 0;
@ -775,7 +782,6 @@ int rhizome_list_manifests(const char *service, const char *sender_sid, const ch
cli_puts("sender"); cli_delim(":");
cli_puts("recipient"); cli_delim(":");
cli_puts("name"); cli_delim("\n"); // should be last, because name may contain ':'
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) {
++rows;
if (!( sqlite3_column_count(statement) == 4
@ -928,7 +934,8 @@ int rhizome_store_file(rhizome_manifest *m,const unsigned char *key)
int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset);
*/
sqlite3_stmt *statement = sqlite_prepare("INSERT OR REPLACE INTO FILES(id,data,length,highestpriority,datavalid,inserttime) VALUES('%s',?,%lld,%d,0,%lld);",
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "INSERT OR REPLACE INTO FILES(id,data,length,highestpriority,datavalid,inserttime) VALUES('%s',?,%lld,%d,0,%lld);",
hash, (long long)m->fileLength, priority, (long long)gettime_ms()
);
if (!statement)
@ -940,7 +947,6 @@ int rhizome_store_file(rhizome_manifest *m,const unsigned char *key)
goto insert_row_fail;
}
/* Do actual insert, and abort if it fails */
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
if (_sqlite_exec_void_prepared(__HERE__, LOG_LEVEL_ERROR, &retry, statement) == -1) {
insert_row_fail:
WHYF("Failed to insert row for fileid=%s", hash);
@ -1111,7 +1117,8 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
if (debug & DEBUG_RHIZOME)
DEBUGF("sql query: %s", sqlcmd);
int ret = 0;
sqlite3_stmt *statement = sqlite_prepare("%s", strbuf_str(b));
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "%s", strbuf_str(b));
if (!statement)
return -1;
int field = 1;
@ -1126,7 +1133,6 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
if (checkVersionP)
sqlite3_bind_int64(statement, field++, m->version);
size_t rows = 0;
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) {
++rows;
if (debug & DEBUG_RHIZOME) DEBUGF("Row %d", rows);
@ -1255,13 +1261,13 @@ int rhizome_retrieve_manifest(const char *manifestid, rhizome_manifest **mp)
unsigned char manifest_id[RHIZOME_MANIFEST_ID_BYTES];
if (fromhexstr(manifest_id, manifestid, RHIZOME_MANIFEST_ID_BYTES) == -1)
return WHY("Invalid manifest ID");
sqlite3_stmt *statement = sqlite_prepare("SELECT id, manifest, version, inserttime FROM manifests WHERE id = ?");
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "SELECT id, manifest, version, inserttime FROM manifests WHERE id = ?");
if (!statement)
return -1;
char manifestIdUpper[RHIZOME_MANIFEST_ID_STRLEN + 1];
tohex(manifestIdUpper, manifest_id, RHIZOME_MANIFEST_ID_BYTES);
sqlite3_bind_text(statement, 1, manifestIdUpper, -1, SQLITE_STATIC);
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
int ret = 0;
rhizome_manifest *m = NULL;
while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) {
@ -1355,7 +1361,8 @@ int rhizome_retrieve_file(const char *fileid, const char *filepath, const unsign
WHY("Failed to update file priority");
return 0;
}
sqlite3_stmt *statement = sqlite_prepare("SELECT id, rowid, length FROM files WHERE id = ? AND datavalid != 0");
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "SELECT id, rowid, length FROM files WHERE id = ? AND datavalid != 0");
if (!statement)
return -1;
int ret = 0;
@ -1364,7 +1371,6 @@ int rhizome_retrieve_file(const char *fileid, const char *filepath, const unsign
fileIdUpper[RHIZOME_FILEHASH_STRLEN] = '\0';
str_toupper_inplace(fileIdUpper);
sqlite3_bind_text(statement, 1, fileIdUpper, -1, SQLITE_STATIC);
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
int stepcode = sqlite_step_retry(&retry, statement);
if (stepcode != SQLITE_ROW) {
ret = 0; // no files found

View File

@ -428,10 +428,9 @@ rhizome_manifest *rhizome_direct_get_manifest(unsigned char *bid_prefix,int pref
alloca_tohex(low,RHIZOME_MANIFEST_ID_BYTES),
alloca_tohex(high,RHIZOME_MANIFEST_ID_BYTES));
sqlite3_stmt *statement=sqlite_prepare(query);
sqlite3_blob *blob=NULL;
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, query);
sqlite3_blob *blob=NULL;
if (sqlite_step_retry(&retry, statement) == SQLITE_ROW)
{
int ret;
@ -790,7 +789,7 @@ int rhizome_direct_get_bars(const unsigned char bid_low[RHIZOME_MANIFEST_ID_BYTE
alloca_tohex(bid_max,RHIZOME_MANIFEST_ID_BYTES),
bars_requested);
sqlite3_stmt *statement=sqlite_prepare(query);
sqlite3_stmt *statement=sqlite_prepare(&retry, query);
sqlite3_blob *blob=NULL;
int bars_written=0;

View File

@ -401,12 +401,12 @@ int rhizome_server_sql_query_fill_buffer(rhizome_http_request *r, char *table, c
return WHY("Not enough space to fit any records");
}
sqlite3_stmt *statement = sqlite_prepare("%s LIMIT %lld,%d", r->source, r->source_index, record_count);
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "%s LIMIT %lld,%d", r->source, r->source_index, record_count);
if (!statement)
return -1;
if (debug & DEBUG_RHIZOME_TX)
DEBUG(sqlite3_sql(statement));
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
while( r->buffer_length + r->source_record_size < r->buffer_size
&& sqlite_step_retry(&retry, statement) == SQLITE_ROW
) {