Browse Source

daemon: Factor out SQLite handling.

* nix/libstore/local-store.cc: Move SQLite code to...
* nix/libstore/sqlite.cc, nix/libstore/sqlite.hh: ... here.  New files.
* nix/local.mk (libstore_a_SOURCES): Add sqlite.cc.
(libstore_headers): Add sqlite.hh.

Co-authored-by: Ludovic Courtès <ludo@gnu.org>
version-0.12.0
Eelco Dolstra 6 years ago
committed by Ludovic Courtès
parent
commit
7bed5d91de
No known key found for this signature in database GPG Key ID: 90B11993D9AEBB5
  1. 174
      nix/libstore/local-store.cc
  2. 35
      nix/libstore/local-store.hh
  3. 139
      nix/libstore/sqlite.cc
  4. 83
      nix/libstore/sqlite.hh
  5. 4
      nix/local.mk

174
nix/libstore/local-store.cc

@ -40,180 +40,6 @@
namespace nix {
MakeError(SQLiteError, Error);
MakeError(SQLiteBusy, SQLiteError);
static void throwSQLiteError(sqlite3 * db, const format & f)
__attribute__ ((noreturn));
static void throwSQLiteError(sqlite3 * db, const format & f)
{
int err = sqlite3_errcode(db);
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
if (err == SQLITE_PROTOCOL)
printMsg(lvlError, "warning: SQLite database is busy (SQLITE_PROTOCOL)");
else {
static bool warned = false;
if (!warned) {
printMsg(lvlError, "warning: SQLite database is busy");
warned = true;
}
}
/* Sleep for a while since retrying the transaction right away
is likely to fail again. */
#if HAVE_NANOSLEEP
struct timespec t;
t.tv_sec = 0;
t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */
nanosleep(&t, 0);
#else
sleep(1);
#endif
throw SQLiteBusy(format("%1%: %2%") % f.str() % sqlite3_errmsg(db));
}
else
throw SQLiteError(format("%1%: %2%") % f.str() % sqlite3_errmsg(db));
}
/* Convenience function for retrying a SQLite transaction when the
database is busy. */
template<typename T>
T retrySQLite(std::function<T()> fun)
{
while (true) {
try {
return fun();
} catch (SQLiteBusy & e) {
}
}
}
SQLite::~SQLite()
{
try {
if (db && sqlite3_close(db) != SQLITE_OK)
throwSQLiteError(db, "closing database");
} catch (...) {
ignoreException();
}
}
void SQLiteStmt::create(sqlite3 * db, const string & s)
{
checkInterrupt();
assert(!stmt);
if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK)
throwSQLiteError(db, "creating statement");
this->db = db;
}
void SQLiteStmt::reset()
{
assert(stmt);
/* Note: sqlite3_reset() returns the error code for the most
recent call to sqlite3_step(). So ignore it. */
sqlite3_reset(stmt);
curArg = 1;
}
SQLiteStmt::~SQLiteStmt()
{
try {
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
throwSQLiteError(db, "finalizing statement");
} catch (...) {
ignoreException();
}
}
void SQLiteStmt::bind(const string & value)
{
if (sqlite3_bind_text(stmt, curArg++, value.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
throwSQLiteError(db, "binding argument");
}
void SQLiteStmt::bind(int value)
{
if (sqlite3_bind_int(stmt, curArg++, value) != SQLITE_OK)
throwSQLiteError(db, "binding argument");
}
void SQLiteStmt::bind64(long long value)
{
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
throwSQLiteError(db, "binding argument");
}
void SQLiteStmt::bind()
{
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
throwSQLiteError(db, "binding argument");
}
/* Helper class to ensure that prepared statements are reset when
leaving the scope that uses them. Unfinished prepared statements
prevent transactions from being aborted, and can cause locks to be
kept when they should be released. */
struct SQLiteStmtUse
{
SQLiteStmt & stmt;
SQLiteStmtUse(SQLiteStmt & stmt) : stmt(stmt)
{
stmt.reset();
}
~SQLiteStmtUse()
{
try {
stmt.reset();
} catch (...) {
ignoreException();
}
}
};
struct SQLiteTxn
{
bool active;
sqlite3 * db;
SQLiteTxn(sqlite3 * db) : active(false) {
this->db = db;
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, "starting transaction");
active = true;
}
void commit()
{
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, "committing transaction");
active = false;
}
~SQLiteTxn()
{
try {
if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, "aborting transaction");
} catch (...) {
ignoreException();
}
}
};
void checkStoreNotSymlink()
{
if (getEnv("NIX_IGNORE_SYMLINK_STORE") == "1") return;

35
nix/libstore/local-store.hh

@ -1,15 +1,12 @@
#pragma once
#include "sqlite.hh"
#include <string>
#include <unordered_set>
#include "pathlocks.hh"
#include "store-api.hh"
#include "util.hh"
#include "pathlocks.hh"
class sqlite3;
class sqlite3_stmt;
namespace nix {
@ -52,34 +49,6 @@ struct RunningSubstituter
};
/* Wrapper object to close the SQLite database automatically. */
struct SQLite
{
sqlite3 * db;
SQLite() { db = 0; }
~SQLite();
operator sqlite3 * () { return db; }
};
/* Wrapper object to create and destroy SQLite prepared statements. */
struct SQLiteStmt
{
sqlite3 * db;
sqlite3_stmt * stmt;
unsigned int curArg;
SQLiteStmt() { stmt = 0; }
void create(sqlite3 * db, const string & s);
void reset();
~SQLiteStmt();
operator sqlite3_stmt * () { return stmt; }
void bind(const string & value);
void bind(int value);
void bind64(long long value);
void bind();
};
class LocalStore : public StoreAPI
{
private:

139
nix/libstore/sqlite.cc

@ -0,0 +1,139 @@
#include "sqlite.hh"
#include "util.hh"
#include <sqlite3.h>
namespace nix {
[[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f)
{
int err = sqlite3_errcode(db);
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
if (err == SQLITE_PROTOCOL)
printMsg(lvlError, "warning: SQLite database is busy (SQLITE_PROTOCOL)");
else {
static bool warned = false;
if (!warned) {
printMsg(lvlError, "warning: SQLite database is busy");
warned = true;
}
}
/* Sleep for a while since retrying the transaction right away
is likely to fail again. */
#if HAVE_NANOSLEEP
struct timespec t;
t.tv_sec = 0;
t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */
nanosleep(&t, 0);
#else
sleep(1);
#endif
throw SQLiteBusy(format("%1%: %2%") % f.str() % sqlite3_errmsg(db));
}
else
throw SQLiteError(format("%1%: %2%") % f.str() % sqlite3_errmsg(db));
}
SQLite::~SQLite()
{
try {
if (db && sqlite3_close(db) != SQLITE_OK)
throwSQLiteError(db, "closing database");
} catch (...) {
ignoreException();
}
}
void SQLiteStmt::create(sqlite3 * db, const string & s)
{
checkInterrupt();
assert(!stmt);
if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK)
throwSQLiteError(db, "creating statement");
this->db = db;
}
void SQLiteStmt::reset()
{
assert(stmt);
/* Note: sqlite3_reset() returns the error code for the most
recent call to sqlite3_step(). So ignore it. */
sqlite3_reset(stmt);
curArg = 1;
}
SQLiteStmt::~SQLiteStmt()
{
try {
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
throwSQLiteError(db, "finalizing statement");
} catch (...) {
ignoreException();
}
}
void SQLiteStmt::bind(const string & value)
{
if (sqlite3_bind_text(stmt, curArg++, value.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
throwSQLiteError(db, "binding argument");
}
void SQLiteStmt::bind(int value)
{
if (sqlite3_bind_int(stmt, curArg++, value) != SQLITE_OK)
throwSQLiteError(db, "binding argument");
}
void SQLiteStmt::bind64(long long value)
{
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
throwSQLiteError(db, "binding argument");
}
void SQLiteStmt::bind()
{
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
throwSQLiteError(db, "binding argument");
}
SQLiteStmtUse::SQLiteStmtUse(SQLiteStmt & stmt)
: stmt(stmt)
{
stmt.reset();
}
SQLiteStmtUse::~SQLiteStmtUse()
{
try {
stmt.reset();
} catch (...) {
ignoreException();
}
}
SQLiteTxn::SQLiteTxn(sqlite3 * db)
{
this->db = db;
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, "starting transaction");
active = true;
}
void SQLiteTxn::commit()
{
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, "committing transaction");
active = false;
}
SQLiteTxn::~SQLiteTxn()
{
try {
if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, "aborting transaction");
} catch (...) {
ignoreException();
}
}
}

83
nix/libstore/sqlite.hh

@ -0,0 +1,83 @@
#pragma once
#include <functional>
#include <string>
#include "types.hh"
class sqlite3;
class sqlite3_stmt;
namespace nix {
/* RAII wrapper to close a SQLite database automatically. */
struct SQLite
{
sqlite3 * db;
SQLite() { db = 0; }
~SQLite();
operator sqlite3 * () { return db; }
};
/* RAII wrapper to create and destroy SQLite prepared statements. */
struct SQLiteStmt
{
sqlite3 * db;
sqlite3_stmt * stmt;
unsigned int curArg;
SQLiteStmt() { stmt = 0; }
void create(sqlite3 * db, const std::string & s);
void reset();
~SQLiteStmt();
operator sqlite3_stmt * () { return stmt; }
void bind(const std::string & value);
void bind(int value);
void bind64(long long value);
void bind();
};
/* Helper class to ensure that prepared statements are reset when
leaving the scope that uses them. Unfinished prepared statements
prevent transactions from being aborted, and can cause locks to be
kept when they should be released. */
struct SQLiteStmtUse
{
SQLiteStmt & stmt;
SQLiteStmtUse(SQLiteStmt & stmt);
~SQLiteStmtUse();
};
/* RAII helper that ensures transactions are aborted unless explicitly
committed. */
struct SQLiteTxn
{
bool active = false;
sqlite3 * db;
SQLiteTxn(sqlite3 * db);
void commit();
~SQLiteTxn();
};
MakeError(SQLiteError, Error);
MakeError(SQLiteBusy, SQLiteError);
[[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f);
/* Convenience function for retrying a SQLite transaction when the
database is busy. */
template<typename T>
T retrySQLite(std::function<T()> fun)
{
while (true) {
try {
return fun();
} catch (SQLiteBusy & e) {
}
}
}
}

4
nix/local.mk

@ -86,7 +86,8 @@ libstore_a_SOURCES = \
%D%/libstore/local-store.cc \
%D%/libstore/build.cc \
%D%/libstore/pathlocks.cc \
%D%/libstore/derivations.cc
%D%/libstore/derivations.cc \
%D%/libstore/sqlite.cc
libstore_headers = \
%D%/libstore/references.hh \
@ -96,6 +97,7 @@ libstore_headers = \
%D%/libstore/derivations.hh \
%D%/libstore/misc.hh \
%D%/libstore/local-store.hh \
%D%/libstore/sqlite.hh \
%D%/libstore/store-api.hh
libstore_a_CPPFLAGS = \

Loading…
Cancel
Save