Improve SQLite busy handling
This commit is contained in:
		
							parent
							
								
									34b12bad59
								
							
						
					
					
						commit
						fd86dd93dd
					
				
					 3 changed files with 43 additions and 31 deletions
				
			
		|  | @ -265,7 +265,7 @@ AC_CHECK_FUNCS([setresuid setreuid lchown]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Nice to have, but not essential. | # Nice to have, but not essential. | ||||||
| AC_CHECK_FUNCS([strsignal posix_fallocate nanosleep sysconf]) | AC_CHECK_FUNCS([strsignal posix_fallocate sysconf]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # This is needed if bzip2 is a static library, and the Nix libraries | # This is needed if bzip2 is a static library, and the Nix libraries | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ | ||||||
| 
 | 
 | ||||||
| #include <sqlite3.h> | #include <sqlite3.h> | ||||||
| 
 | 
 | ||||||
|  | #include <atomic> | ||||||
|  | 
 | ||||||
| namespace nix { | namespace nix { | ||||||
| 
 | 
 | ||||||
| [[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f) | [[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f) | ||||||
|  | @ -13,27 +15,10 @@ namespace nix { | ||||||
|     if (!path) path = "(in-memory)"; |     if (!path) path = "(in-memory)"; | ||||||
| 
 | 
 | ||||||
|     if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { |     if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { | ||||||
|         if (err == SQLITE_PROTOCOL) |         throw SQLiteBusy( | ||||||
|             printError("warning: SQLite database ‘%s’ is busy (SQLITE_PROTOCOL)", path); |             err == SQLITE_PROTOCOL | ||||||
|         else { |             ? fmt("SQLite database ‘%s’ is busy (SQLITE_PROTOCOL)", path) | ||||||
|             static bool warned = false; |             : fmt("SQLite database ‘%s’ is busy", path)); | ||||||
|             if (!warned) { |  | ||||||
|                 printError("warning: SQLite database ‘%s’ is busy", path); |  | ||||||
|                 warned = true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         /* Sleep for a while since retrying the transaction right away
 |  | ||||||
|            is likely to fail again. */ |  | ||||||
|         checkInterrupt(); |  | ||||||
| #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("%s: %s (in ‘%s’)", f.str(), sqlite3_errstr(err), path); |  | ||||||
|     } |     } | ||||||
|     else |     else | ||||||
|         throw SQLiteError("%s: %s (in ‘%s’)", f.str(), sqlite3_errstr(err), path); |         throw SQLiteError("%s: %s (in ‘%s’)", f.str(), sqlite3_errstr(err), path); | ||||||
|  | @ -58,24 +43,27 @@ SQLite::~SQLite() | ||||||
| 
 | 
 | ||||||
| void SQLite::exec(const std::string & stmt) | void SQLite::exec(const std::string & stmt) | ||||||
| { | { | ||||||
|  |     retrySQLite<void>([&]() { | ||||||
|         if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK) |         if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK) | ||||||
|             throwSQLiteError(db, format("executing SQLite statement ‘%s’") % stmt); |             throwSQLiteError(db, format("executing SQLite statement ‘%s’") % stmt); | ||||||
|  |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SQLiteStmt::create(sqlite3 * db, const string & s) | void SQLiteStmt::create(sqlite3 * db, const string & sql) | ||||||
| { | { | ||||||
|     checkInterrupt(); |     checkInterrupt(); | ||||||
|     assert(!stmt); |     assert(!stmt); | ||||||
|     if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK) |     if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK) | ||||||
|         throwSQLiteError(db, "creating statement"); |         throwSQLiteError(db, fmt("creating statement ‘%s’", sql)); | ||||||
|     this->db = db; |     this->db = db; | ||||||
|  |     this->sql = sql; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| SQLiteStmt::~SQLiteStmt() | SQLiteStmt::~SQLiteStmt() | ||||||
| { | { | ||||||
|     try { |     try { | ||||||
|         if (stmt && sqlite3_finalize(stmt) != SQLITE_OK) |         if (stmt && sqlite3_finalize(stmt) != SQLITE_OK) | ||||||
|             throwSQLiteError(db, "finalizing statement"); |             throwSQLiteError(db, fmt("finalizing statement ‘%s’", sql)); | ||||||
|     } catch (...) { |     } catch (...) { | ||||||
|         ignoreException(); |         ignoreException(); | ||||||
|     } |     } | ||||||
|  | @ -132,14 +120,14 @@ void SQLiteStmt::Use::exec() | ||||||
|     int r = step(); |     int r = step(); | ||||||
|     assert(r != SQLITE_ROW); |     assert(r != SQLITE_ROW); | ||||||
|     if (r != SQLITE_DONE) |     if (r != SQLITE_DONE) | ||||||
|         throwSQLiteError(stmt.db, "executing SQLite statement"); |         throwSQLiteError(stmt.db, fmt("executing SQLite statement ‘%s’", stmt.sql)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool SQLiteStmt::Use::next() | bool SQLiteStmt::Use::next() | ||||||
| { | { | ||||||
|     int r = step(); |     int r = step(); | ||||||
|     if (r != SQLITE_DONE && r != SQLITE_ROW) |     if (r != SQLITE_DONE && r != SQLITE_ROW) | ||||||
|         throwSQLiteError(stmt.db, "executing SQLite query"); |         throwSQLiteError(stmt.db, fmt("executing SQLite query ‘%s’", stmt.sql)); | ||||||
|     return r == SQLITE_ROW; |     return r == SQLITE_ROW; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -186,4 +174,24 @@ SQLiteTxn::~SQLiteTxn() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void handleSQLiteBusy(const SQLiteBusy & e) | ||||||
|  | { | ||||||
|  |     static std::atomic<time_t> lastWarned{0}; | ||||||
|  | 
 | ||||||
|  |     time_t now = time(0); | ||||||
|  | 
 | ||||||
|  |     if (now > lastWarned + 10) { | ||||||
|  |         lastWarned = now; | ||||||
|  |         printError("warning: %s", e.what()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* Sleep for a while since retrying the transaction right away
 | ||||||
|  |        is likely to fail again. */ | ||||||
|  |     checkInterrupt(); | ||||||
|  |     struct timespec t; | ||||||
|  |     t.tv_sec = 0; | ||||||
|  |     t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */ | ||||||
|  |     nanosleep(&t, 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -30,8 +30,9 @@ struct SQLiteStmt | ||||||
| { | { | ||||||
|     sqlite3 * db = 0; |     sqlite3 * db = 0; | ||||||
|     sqlite3_stmt * stmt = 0; |     sqlite3_stmt * stmt = 0; | ||||||
|  |     std::string sql; | ||||||
|     SQLiteStmt() { } |     SQLiteStmt() { } | ||||||
|     SQLiteStmt(sqlite3 * db, const std::string & s) { create(db, s); } |     SQLiteStmt(sqlite3 * db, const std::string & sql) { create(db, sql); } | ||||||
|     void create(sqlite3 * db, const std::string & s); |     void create(sqlite3 * db, const std::string & s); | ||||||
|     ~SQLiteStmt(); |     ~SQLiteStmt(); | ||||||
|     operator sqlite3_stmt * () { return stmt; } |     operator sqlite3_stmt * () { return stmt; } | ||||||
|  | @ -94,6 +95,8 @@ MakeError(SQLiteBusy, SQLiteError); | ||||||
| 
 | 
 | ||||||
| [[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f); | [[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f); | ||||||
| 
 | 
 | ||||||
|  | void handleSQLiteBusy(const SQLiteBusy & e); | ||||||
|  | 
 | ||||||
| /* Convenience function for retrying a SQLite transaction when the
 | /* Convenience function for retrying a SQLite transaction when the
 | ||||||
|    database is busy. */ |    database is busy. */ | ||||||
| template<typename T> | template<typename T> | ||||||
|  | @ -103,6 +106,7 @@ T retrySQLite(std::function<T()> fun) | ||||||
|         try { |         try { | ||||||
|             return fun(); |             return fun(); | ||||||
|         } catch (SQLiteBusy & e) { |         } catch (SQLiteBusy & e) { | ||||||
|  |             handleSQLiteBusy(e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue