From 7aadac132d8512e7ee63970561f40ade80772726 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 29 Mar 2011 16:31:59 +0200 Subject: Support for shared cache and unlock notification --- odb/sqlite/connection-factory.cxx | 24 ++++++++++--- odb/sqlite/connection-factory.hxx | 12 ++++--- odb/sqlite/connection.cxx | 55 ++++++++++++++++++++++++++-- odb/sqlite/connection.hxx | 24 +++++++++++-- odb/sqlite/error.cxx | 11 +++++- odb/sqlite/makefile | 1 + odb/sqlite/statement.cxx | 75 ++++++++++++++++++++++++++++++++------- 7 files changed, 173 insertions(+), 29 deletions(-) diff --git a/odb/sqlite/connection-factory.cxx b/odb/sqlite/connection-factory.cxx index 4cae67f..11184bc 100644 --- a/odb/sqlite/connection-factory.cxx +++ b/odb/sqlite/connection-factory.cxx @@ -5,6 +5,7 @@ #include +#include #include using namespace std; @@ -31,13 +32,19 @@ namespace odb shared_ptr new_connection_factory:: connect () { - return shared_ptr (new (shared) connection (*db_)); + return shared_ptr ( + new (shared) connection (*db_, extra_flags_)); } void new_connection_factory:: database (database_type& db) { db_ = &db; + + // Unless explicitly disabled, enable shared cache. + // + if ((db_->flags () & SQLITE_OPEN_PRIVATECACHE) == 0) + extra_flags_ |= SQLITE_OPEN_SHAREDCACHE; } // @@ -82,7 +89,7 @@ namespace odb if(max_ == 0 || in_use_ < max_) { shared_ptr c ( - new (shared) pooled_connection (*db_, this)); + new (shared) pooled_connection (*db_, extra_flags_, this)); in_use_++; return c; } @@ -100,6 +107,11 @@ namespace odb { db_ = &db; + // Unless explicitly disabled, enable shared cache. + // + if ((db_->flags () & SQLITE_OPEN_PRIVATECACHE) == 0) + extra_flags_ |= SQLITE_OPEN_SHAREDCACHE; + if (min_ > 0) { connections_.reserve (min_); @@ -108,7 +120,7 @@ namespace odb { connections_.push_back ( shared_ptr ( - new (shared) pooled_connection (*db_, 0))); + new (shared) pooled_connection (*db_, extra_flags_, 0))); } } } @@ -143,8 +155,10 @@ namespace odb // connection_pool_factory::pooled_connection:: - pooled_connection (database_type& db, connection_pool_factory* pool) - : connection (db), pool_ (pool) + pooled_connection (database_type& db, + int extra_flags, + connection_pool_factory* pool) + : connection (db, extra_flags), pool_ (pool) { callback_.arg = this; callback_.zero_counter = &zero_counter; diff --git a/odb/sqlite/connection-factory.hxx b/odb/sqlite/connection-factory.hxx index 6a42c8f..76bdc11 100644 --- a/odb/sqlite/connection-factory.hxx +++ b/odb/sqlite/connection-factory.hxx @@ -44,10 +44,7 @@ namespace odb public connection_factory { public: - new_connection_factory () - : db_ (0) - { - } + new_connection_factory (): db_ (0), extra_flags_ (0) {} virtual details::shared_ptr connect (); @@ -61,6 +58,7 @@ namespace odb private: database_type* db_; + int extra_flags_; }; class LIBODB_SQLITE_EXPORT connection_pool_factory: @@ -87,6 +85,7 @@ namespace odb std::size_t min_connections = 0) : max_ (max_connections), min_ (min_connections), + extra_flags_ (0), in_use_ (0), waiters_ (0), db_ (0), @@ -114,7 +113,9 @@ namespace odb public: // NULL pool value indicates that the connection is not in use. // - pooled_connection (database_type&, connection_pool_factory*); + pooled_connection (database_type&, + int extra_flags, + connection_pool_factory*); private: static bool @@ -139,6 +140,7 @@ namespace odb private: const std::size_t max_; const std::size_t min_; + int extra_flags_; std::size_t in_use_; // Number of connections currently in use. std::size_t waiters_; // Number of threads waiting for a connection. diff --git a/odb/sqlite/connection.cxx b/odb/sqlite/connection.cxx index 9af5a3b..91b430a 100644 --- a/odb/sqlite/connection.cxx +++ b/odb/sqlite/connection.cxx @@ -7,14 +7,22 @@ #include #include +#include + #include #include #include #include #include +#include // deadlock + +#include // LIBODB_SQLITE_HAVE_UNLOCK_NOTIFY using namespace std; +extern "C" void +odb_sqlite_connection_unlock_callback (void**, int); + namespace odb { namespace sqlite @@ -29,10 +37,10 @@ namespace odb } connection:: - connection (database_type& db) - : db_ (db), statements_ (0) + connection (database_type& db, int extra_flags) + : db_ (db), unlock_cond_ (unlock_mutex_), statements_ (0) { - int f (db.flags ()); + int f (db.flags () | extra_flags); const string& n (db.name ()); // If we are opening a temporary database, then add the create flag. @@ -57,6 +65,41 @@ namespace odb statement_cache_.reset (new statement_cache_type (*this)); } + inline void + connection_unlock_callback (void** args, int n) + { + for (int i (0); i < n; ++i) + { + connection* c (static_cast (args[i])); + details::lock l (c->unlock_mutex_); + c->unlocked_ = true; + c->unlock_cond_.signal (); + } + } + + void connection:: + wait () + { +#ifdef LIBODB_SQLITE_HAVE_UNLOCK_NOTIFY + unlocked_ = false; + + // unlock_notify() returns SQLITE_OK or SQLITE_LOCKED (deadlock). + // + int e (sqlite3_unlock_notify (handle_, + &odb_sqlite_connection_unlock_callback, + this)); + if (e == SQLITE_LOCKED) + throw deadlock (); + + details::lock l (unlock_mutex_); + + while (!unlocked_) + unlock_cond_.wait (); +#else + translate_error (SQLITE_LOCKED, *this); +#endif + } + void connection:: clear () { @@ -75,3 +118,9 @@ namespace odb } } } + +extern "C" void +odb_sqlite_connection_unlock_callback (void** args, int n) +{ + odb::sqlite::connection_unlock_callback (args, n); +} diff --git a/odb/sqlite/connection.hxx b/odb/sqlite/connection.hxx index 5390609..3ae82e3 100644 --- a/odb/sqlite/connection.hxx +++ b/odb/sqlite/connection.hxx @@ -13,6 +13,8 @@ #include // std::auto_ptr #include +#include +#include #include #include @@ -35,7 +37,7 @@ namespace odb virtual ~connection (); - connection (database_type&); + connection (database_type&, int extra_flags = 0); database_type& database () @@ -56,6 +58,12 @@ namespace odb return *statement_cache_; } + // Wait for the locks to be released via unlock notification. Can + // be called after getting SQLITE_LOCKED_SHAREDCACHE. + // + void + wait (); + public: // Reset active and finalize uncached statements. // @@ -67,14 +75,24 @@ namespace odb connection& operator= (const connection&); private: - friend class statement; - database_type& db_; sqlite3* handle_; + // Unlock notification machinery. + // + private: + bool unlocked_; + details::mutex unlock_mutex_; + details::condition unlock_cond_; + + friend void + connection_unlock_callback (void**, int); + // Linked list of active and uncached statements currently associated // with this connection. // + private: + friend class statement; statement* statements_; std::auto_ptr statement_cache_; diff --git a/odb/sqlite/error.cxx b/odb/sqlite/error.cxx index d223b4e..f84a6c0 100644 --- a/odb/sqlite/error.cxx +++ b/odb/sqlite/error.cxx @@ -39,8 +39,17 @@ namespace odb m = "SQLite API misuse"; break; } - case SQLITE_BUSY: case SQLITE_LOCKED: + { + if (ee != SQLITE_LOCKED_SHAREDCACHE) + throw deadlock (); // The DROP TABLE special case. + + // Getting SQLITE_LOCKED_SHAREDCACHE here means we don't have + // the unlock notify support. Translate this to timeout. + // + throw timeout (); + } + case SQLITE_BUSY: case SQLITE_IOERR: { if (e != SQLITE_IOERR || ee == SQLITE_IOERR_BLOCKED) diff --git a/odb/sqlite/makefile b/odb/sqlite/makefile index 2d51d7d..feb62eb 100644 --- a/odb/sqlite/makefile +++ b/odb/sqlite/makefile @@ -73,6 +73,7 @@ $(out_base)/details/config.h: @echo '#ifndef ODB_SQLITE_DETAILS_CONFIG_H' >>$@ @echo '#define ODB_SQLITE_DETAILS_CONFIG_H' >>$@ @echo '' >>$@ + @echo '#define LIBODB_SQLITE_HAVE_UNLOCK_NOTIFY 1' >>$@ @echo '' >>$@ @echo '#endif /* ODB_SQLITE_DETAILS_CONFIG_H */' >>$@ diff --git a/odb/sqlite/statement.cxx b/odb/sqlite/statement.cxx index 0975545..f503144 100644 --- a/odb/sqlite/statement.cxx +++ b/odb/sqlite/statement.cxx @@ -30,16 +30,19 @@ namespace odb void statement:: init (const char* s, std::size_t n) { - if (int e = sqlite3_prepare_v2 ( - conn_.handle (), - s, - static_cast (n), - &stmt_, - 0)) + int e; + while ((e = sqlite3_prepare_v2 (conn_.handle (), + s, + static_cast (n), + &stmt_, + 0)) == SQLITE_LOCKED) { - translate_error (e, conn_); + conn_.wait (); } + if (e != SQLITE_OK) + translate_error (e, conn_); + active_ = false; cached_ = false; @@ -194,8 +197,20 @@ namespace odb unsigned long long r (0); + // Only the first call to sqlite3_step() can return SQLITE_LOCKED. + // int e; - for (e = sqlite3_step (stmt_); e == SQLITE_ROW; e = sqlite3_step (stmt_)) + sqlite3* h (conn_.handle ()); + while ((e = sqlite3_step (stmt_)) == SQLITE_LOCKED) + { + if (sqlite3_extended_errcode (h) != SQLITE_LOCKED_SHAREDCACHE) + break; + + sqlite3_reset (stmt_); + conn_.wait (); + } + + for (; e == SQLITE_ROW; e = sqlite3_step (stmt_)) r++; sqlite3_reset (stmt_); @@ -245,7 +260,16 @@ namespace odb { if (!done_) { - int e (sqlite3_step (stmt_)); + int e; + sqlite3* h (conn_.handle ()); + while ((e = sqlite3_step (stmt_)) == SQLITE_LOCKED) + { + if (sqlite3_extended_errcode (h) != SQLITE_LOCKED_SHAREDCACHE) + break; + + sqlite3_reset (stmt_); + conn_.wait (); + } if (e != SQLITE_ROW) { @@ -292,7 +316,16 @@ namespace odb { bind_param (data_.bind, data_.count); - int e (sqlite3_step (stmt_)); + int e; + sqlite3* h (conn_.handle ()); + while ((e = sqlite3_step (stmt_)) == SQLITE_LOCKED) + { + if (sqlite3_extended_errcode (h) != SQLITE_LOCKED_SHAREDCACHE) + break; + + sqlite3_reset (stmt_); + conn_.wait (); + } sqlite3_reset (stmt_); @@ -337,7 +370,16 @@ namespace odb bind_param (data_.bind, data_.count); bind_param (cond_.bind, cond_.count, data_.count); - int e (sqlite3_step (stmt_)); + int e; + sqlite3* h (conn_.handle ()); + while ((e = sqlite3_step (stmt_)) == SQLITE_LOCKED) + { + if (sqlite3_extended_errcode (h) != SQLITE_LOCKED_SHAREDCACHE) + break; + + sqlite3_reset (stmt_); + conn_.wait (); + } sqlite3_reset (stmt_); @@ -362,7 +404,16 @@ namespace odb { bind_param (cond_.bind, cond_.count); - int e (sqlite3_step (stmt_)); + int e; + sqlite3* h (conn_.handle ()); + while ((e = sqlite3_step (stmt_)) == SQLITE_LOCKED) + { + if (sqlite3_extended_errcode (h) != SQLITE_LOCKED_SHAREDCACHE) + break; + + sqlite3_reset (stmt_); + conn_.wait (); + } sqlite3_reset (stmt_); -- cgit v1.1