db: Implemented poor mans nested transactions

Nesting is provided by only actually performing the outermost
transaction and simulating the nested ones. This still allows us to
ensure on lower levels that we are in the context of a transaction
without having to resort to keeping explicitly track of it in the
calling code.

Signed-off-by: Christian Decker <decker.christian@gmail.com>
This commit is contained in:
Christian Decker 2017-11-01 11:40:23 +10:30
parent 62ca15d6aa
commit 77789bb705
3 changed files with 37 additions and 25 deletions

View File

@ -227,12 +227,15 @@ static void close_db(struct db *db) { sqlite3_close(db->sql); }
bool db_begin_transaction(struct db *db)
{
assert(!db->in_transaction);
/* Clear any errors from previous transactions and
* non-transactional queries */
db_clear_error(db);
db->in_transaction = db_exec(__func__, db, "BEGIN TRANSACTION;");
return db->in_transaction;
if (!db->in_transaction) {
/* Clear any errors from previous transactions and
* non-transactional queries */
db_clear_error(db);
db->in_transaction = db_exec(__func__, db, "BEGIN TRANSACTION;");
assert(db->in_transaction);
return db->in_transaction;
}
return false;
}
bool db_commit_transaction(struct db *db)

View File

@ -44,9 +44,11 @@ bool PRINTF_FMT(3, 4)
/**
* db_begin_transaction - Begin a transaction
*
* We do not support nesting multiple transactions, so make sure that
* we are not in a transaction when calling this. Returns true if we
* succeeded in starting a transaction.
* Begin a new DB transaction if we aren't already in one. Returns
* true if the call started a transaction, i.e., the caller MUST take
* care to either commit or rollback. If false, this is a nested
* transaction and the caller MUST not commit/rollback, since the
* transaction is handled at a higher level in the callstack.
*/
bool db_begin_transaction(struct db *db);

View File

@ -139,6 +139,7 @@ const struct utxo **wallet_select_coins(const tal_t *ctx, struct wallet *w,
u64 *fee_estimate, u64 *changesatoshi)
{
size_t i = 0;
bool should_commit;
struct utxo **available;
const struct utxo **utxos = tal_arr(ctx, const struct utxo *, 0);
*fee_estimate = 0;
@ -147,9 +148,8 @@ const struct utxo **wallet_select_coins(const tal_t *ctx, struct wallet *w,
u64 satoshi_in = 0, weight = (4 + (8 + 22) * 2 + 4) * 4;
tal_add_destructor2(utxos, destroy_utxos, w);
if (!db_begin_transaction(w->db)) {
fatal("Unable to begin transaction: %s", w->db->err);
}
should_commit = db_begin_transaction(w->db);
available = wallet_get_utxos(ctx, w, output_state_available);
for (i = 0; i < tal_count(available); i++) {
@ -177,10 +177,12 @@ const struct utxo **wallet_select_coins(const tal_t *ctx, struct wallet *w,
if (satoshi_in < *fee_estimate + value) {
/* Could not collect enough inputs, cleanup and bail */
utxos = tal_free(utxos);
db_rollback_transaction(w->db);
if (should_commit)
db_rollback_transaction(w->db);
} else {
/* Commit the db transaction to persist markings */
db_commit_transaction(w->db);
if (should_commit)
db_commit_transaction(w->db);
*changesatoshi = satoshi_in - value - *fee_estimate;
}
@ -274,6 +276,7 @@ bool wallet_shachain_add_hash(struct wallet *wallet,
const struct sha256 *hash)
{
sqlite3_stmt *stmt;
bool should_commit;
bool ok = true;
u32 pos = count_trailing_zeroes(index);
assert(index < SQLITE_MAX_UINT);
@ -281,7 +284,7 @@ bool wallet_shachain_add_hash(struct wallet *wallet,
return false;
}
db_begin_transaction(wallet->db);
should_commit = db_begin_transaction(wallet->db);
stmt = db_prepare(wallet->db, "UPDATE shachains SET num_valid=?, min_index=? WHERE id=?");
sqlite3_bind_int(stmt, 1, chain->chain.num_valid);
@ -298,10 +301,12 @@ bool wallet_shachain_add_hash(struct wallet *wallet,
sqlite3_bind_blob(stmt, 4, hash, sizeof(*hash), SQLITE_TRANSIENT);
ok &= db_exec_prepared(wallet->db, stmt);
if (ok)
ok &= db_commit_transaction(wallet->db);
else
db_rollback_transaction(wallet->db);
if (should_commit) {
if (ok)
ok &= db_commit_transaction(wallet->db);
else
db_rollback_transaction(wallet->db);
}
return ok;
}
@ -625,12 +630,12 @@ bool wallet_channel_config_load(struct wallet *w, const u64 id,
}
bool wallet_channel_save(struct wallet *w, struct wallet_channel *chan){
bool ok = true;
bool should_commit, ok = true;
struct peer *p = chan->peer;
tal_t *tmpctx = tal_tmpctx(w);
sqlite3_stmt *stmt;
db_begin_transaction(w->db);
should_commit = db_begin_transaction(w->db);
if (p->dbid == 0) {
/* Need to store the peer first */
@ -751,10 +756,12 @@ bool wallet_channel_save(struct wallet *w, struct wallet_channel *chan){
ok &= db_exec_prepared(w->db, stmt);
}
if (ok)
ok &= db_commit_transaction(w->db);
else
db_rollback_transaction(w->db);
if (should_commit) {
if (ok)
ok &= db_commit_transaction(w->db);
else
db_rollback_transaction(w->db);
}
tal_free(tmpctx);
return ok;
}