// file : common/threads/driver.cxx // copyright : Copyright (c) 2009-2015 Code Synthesis Tools CC // license : GNU GPL v2; see accompanying LICENSE file // Test operations in a multi-threaded environment. // #include #include // std::auto_ptr #include // std::size_t #include #include #include #include #include #include #include // DATABASE_* #include #if defined(DATABASE_SQLITE) || defined(DATABASE_COMMON) # include #endif #include "test.hxx" #include "test-odb.hxx" using namespace std; using namespace odb::core; namespace details = odb::details; const unsigned long thread_count = 24; const unsigned long iteration_count = 30; const unsigned long sub_iteration_count = 40; struct task { task (database& db, unsigned long n) : db_ (db), n_ (n) { } void* execute () { try { for (unsigned long i (0); i < iteration_count; ++i) { unsigned long id ((n_ * iteration_count + i) * 3); object o1 (id, "first object"); object o2 (id + 1, "second object"); object o3 (id + 2, "third object"); // The following transactions may lead to deadlocks. // while (true) { try { transaction t (db_.begin ()); db_.persist (o1); db_.persist (o2); db_.persist (o3); t.commit (); break; } catch (const deadlock&) {} } while (true) { try { #if !defined(DATABASE_SQLITE) && !defined(DATABASE_COMMON) transaction t (db_.begin ()); #else // SQLite has a peculiar table locking mode (shared cache) // which can lead to any of the transactions in this test // deadlocking even though they shouldn't from the user's // perspective. One way to work around this problem is to // start a "write" transaction as such right away. // transaction t; if (db_.id () != odb::id_sqlite) t.reset (db_.begin ()); else { t.reset ( static_cast (db_).begin_immediate ()); } #endif auto_ptr o (db_.load (id)); assert (o->str_ == "first object"); o->str_ = "another value"; db_.update (*o); t.commit (); break; } catch (const deadlock&) {} } for (unsigned long j (0); j < sub_iteration_count; ++j) { typedef odb::query query; typedef odb::prepared_query prep_query; typedef odb::result result; while (true) { try { transaction t (db_.begin ()); prep_query pq (db_.lookup_query ("object-query")); if (!pq) { pq = db_.prepare_query ( "object-query", query::str == "another value"); db_.cache_query (pq); } result r (pq.execute (false)); bool found (false); for (result::iterator i (r.begin ()); i != r.end (); ++i) { if (i->id_ == id) { found = true; break; } } assert (found); t.commit (); break; } catch (const deadlock&) {} } } while (true) { try { transaction t (db_.begin ()); db_.erase (id); t.commit (); break; } catch (const deadlock&) {} } } } catch (const odb::exception& e) { cerr << e.what () << endl; return reinterpret_cast (1); } return 0; } static void* execute (void* arg) { return static_cast (arg)->execute (); } database& db_; unsigned long n_; }; bool test (int argc, char* argv[], size_t max_connections) { auto_ptr db (create_database (argc, argv, true, max_connections)); vector > threads; vector > tasks; for (unsigned long i (0); i < thread_count; ++i) { details::shared_ptr t (new (details::shared) task (*db, i)); tasks.push_back (t); threads.push_back ( details::shared_ptr ( new (details::shared) details::thread (&task::execute, t.get ()))); } bool r (true); for (unsigned long i (0); i < thread_count; ++i) if (threads[i]->join () != 0) r = false; { typedef odb::result result; transaction t (db->begin ()); result r (db->query ()); for (result::iterator i (r.begin ()); i != r.end (); ++i) db->erase (i->id_); t.commit (); } return r; } int main (int argc, char* argv[]) { try { if (!(test (argc, argv, 0) && test (argc, argv, thread_count - 1) && test (argc, argv, thread_count / 2) && test (argc, argv, thread_count / 4))) return 1; } catch (const odb::exception& e) { cerr << e.what () << endl; return 1; } }