mirror of
https://github.com/servalproject/serval-dna.git
synced 2025-03-13 15:56:33 +00:00
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:
parent
e1cacdb687
commit
3040d2b665
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user