From 6be194f680450cc1f7bf960f916a5ff9906c3e05 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 21 Feb 2011 15:11:05 +0200 Subject: Add recoverable, connection_lost, and timeout exceptions The deadlock exception now inherits from recoverable. New manual section: 3.5, "Error Handling and Recovery". --- NEWS | 6 ++ doc/manual.xhtml | 212 ++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 144 insertions(+), 74 deletions(-) diff --git a/NEWS b/NEWS index e33b7f7..e3fb895 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,12 @@ Version 1.2.0 Note that you can still use the odb namespace when qualifying individual names, for example, odb::database. + * New exceptions: odb::recoverbale, odb::connection_lost, and odb::timeout. + The odb::recoverbale exception is a common base class for all recoverable + ODB exceptions. The other two exceptions plus odb::deadlock now inherit + from this base. Refer to Section 3.5, "Error Handling and Recovery" for + details. + * Support for connection validation (ping) in MySQL connection_pool_factory. This transparently deals with the MySQL server closing connections after a certain period of inactivity. diff --git a/doc/manual.xhtml b/doc/manual.xhtml index f9652e9..3458b28 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -304,12 +304,13 @@ for consistency. 3.2Object Pointers 3.3Database 3.4Transactions - 3.5Making Objects Persistent - 3.6Loading Persistent Objects - 3.7Updating Persistent Objects - 3.8Deleting Persistent Objects - 3.9Executing Native SQL Statements - 3.10ODB Exceptions + 3.5Error Handling and Recovery + 3.6Making Objects Persistent + 3.7Loading Persistent Objects + 3.8Updating Persistent Objects + 3.9Deleting Persistent Objects + 3.10Executing Native SQL Statements + 3.11ODB Exceptions @@ -1203,7 +1204,7 @@ main (int argc, char* argv[])

The final bit of code in our example is the catch block that handles the database exceptions. We do this by catching - the base ODB exception (Section 3.10, "ODB + the base ODB exception (Section 3.11, "ODB Exceptions") and printing the diagnostics.

Let's now compile (Section 2.3, "Compiling and @@ -1712,7 +1713,7 @@ class person can make it private. It is also possible to have an object type without the default constructor. However, in this case, the database operations can only load the persistent state into an existing instance - (Section 3.6, "Loading Persistent Objects", + (Section 3.7, "Loading Persistent Objects", Section 4.4, "Query Result").

The object id type should be default-constructible.

@@ -2006,44 +2007,6 @@ namespace odb We can check whether there is a transaction in effect in this thread using the has_current() static function.

-

If two or more transactions access or modify more than one object - and are executed concurrently by different applications or by - different threads within the same application, then it is possible - that these transactions will try to access objects in an incompatible - order and deadlock. The canonical example of a deadlock are - two transactions in which the first has modified object1 - and is waiting for the second transaction to commit its changes to - object2 so that it can also update object2. - At the same time the second transaction has modified object2 - and is waiting for the first transaction to commit its changes to - object1 because it also needs to modify object1. - As a result, none of the two transactions can be completed.

- -

The database system detects such situations and automatically - aborts the waiting operation in one of the deadlocked transactions. - In ODB this translates to the odb::deadlock exception - being thrown from one of the database functions. You would normally - handle a deadlock by restarting the transaction, for example:

- -
-for (;;)
-{
-  try
-  {
-    transaction t (db.begin ());
-
-    ...
-
-    t.commit ();
-    break;
-  }
-  catch (const odb::deadlock&)
-  {
-    continue;
-  }
-}
-  
-

Note that in the above discussion of atomicity, consistency, isolation, and durability, all of those guarantees only apply to the object's state in the database as opposed to the object's @@ -2122,7 +2085,85 @@ update_age (database& db, person& p) -

3.5 Making Objects Persistent

+

3.5 Error Handling and Recovery

+ +

ODB uses C++ exceptions to report database operation errors. Most + ODB exceptions signify hard errors or errors that cannot + be corrected without some intervention from the application. + For example, if we try to load an object with an unknown object + id, the odb::object_not_persistent exception is + thrown. Our application may be able to correct this error, for + instance, by obtaining a valid object id and trying again. + The hard errors and corresponding ODB exceptions that can be + thrown by each database function are described in the remainder + of this chapter with Section 3.11, "ODB Exceptions" + providing a quick reference for all the ODB exceptions.

+ +

The second group of ODB exceptions signify soft or + recoverable errors. Such errors are temporary + failures which normally can be corrected by simply re-executing + the transaction. ODB defines three such exceptions: + odb::connection_lost, odb::timeout, + and odb::deadlock. All recoverable ODB exceptions + are derived from the common odb::recoverable base + exception which can be used to handle all the recoverable + conditions with a single catch block.

+ +

The odb::connection_lost exception is thrown if + a connection to the database is lost in the middle of + a transaction. In this situation the transaction is aborted but + it can be re-tried without any changes. Similarly, the + odb::timeout exception is thrown if one of the + database operations or the whole transaction has timed out. + Again, in this case the transaction is aborted but can be + re-tried as is.

+ +

If two or more transactions access or modify more than one object + and are executed concurrently by different applications or by + different threads within the same application, then it is possible + that these transactions will try to access objects in an incompatible + order and deadlock. The canonical example of a deadlock are + two transactions in which the first has modified object1 + and is waiting for the second transaction to commit its changes to + object2 so that it can also update object2. + At the same time the second transaction has modified object2 + and is waiting for the first transaction to commit its changes to + object1 because it also needs to modify object1. + As a result, none of the two transactions can be completed.

+ +

The database system detects such situations and automatically + aborts the waiting operation in one of the deadlocked transactions. + In ODB this translates to the odb::deadlock + recoverable exception being thrown from one of the database functions.

+ +

The following code fragment shows how to handle the recoverable + exceptions by restarting the affected transaction:

+ +
+const unsigned short max_retries = 5;
+
+for (unsigned short retry_count (0); ; retry_count++)
+{
+  try
+  {
+    transaction t (db.begin ());
+
+    ...
+
+    t.commit ();
+    break;
+  }
+  catch (const odb::recoverable& e)
+  {
+    if (retry_count > max_retries)
+      throw retry_limit_exceeded (e.what ());
+    else
+      continue;
+  }
+}
+  
+ +

3.6 Making Objects Persistent

A newly created instance of a persistent class is transient. We use the database::persist() function template @@ -2208,7 +2249,7 @@ cerr << "Jane's id: " << jane_id << endl; threads in your application and to other applications as soon as possible.

-

3.6 Loading Persistent Objects

+

3.7 Loading Persistent Objects

Once an object is made persistent, and you know its object id, it can be loaded by the application using the database::load() @@ -2273,7 +2314,7 @@ t.commit (); identifier can be significantly faster than executing a query.

-

3.7 Updating Persistent Objects

+

3.8 Updating Persistent Objects

If a persistent object has been modified, we can store the updated state in the database using the database::update() @@ -2355,7 +2396,7 @@ transfer (database& db, t.commit (); -

3.8 Deleting Persistent Objects

+

3.9 Deleting Persistent Objects

To delete a persistent object's state from the database we use the database::erase() function template. If the application @@ -2410,7 +2451,7 @@ db.erase<person> (joe_id); t.commit (); -

3.9 Executing Native SQL Statements

+

3.10 Executing Native SQL Statements

In some situations we may need to execute native SQL statements instead of using the object-oriented database API described above. @@ -2448,7 +2489,7 @@ db.execute ("CREATE TABLE test (n INT PRIMARY KEY)"); t.commit (); -

3.10 ODB Exceptions

+

3.11 ODB Exceptions

In the previous sections we have already mentioned some of the exceptions that can be thrown by the database functions. In this @@ -2483,7 +2524,7 @@ namespace odb

 namespace odb
 {
-  struct null_pointer: odb::exception
+  struct null_pointer: exception
   {
     virtual const char*
     what () const throw ();
@@ -2491,19 +2532,19 @@ namespace odb
 
   // Transaction exceptions.
   //
-  struct already_in_transaction: odb::exception
+  struct already_in_transaction: exception
   {
     virtual const char*
     what () const throw ();
   };
 
-  struct not_in_transaction: odb::exception
+  struct not_in_transaction: exception
   {
     virtual const char*
     what () const throw ();
   };
 
-  struct transaction_already_finalized: odb::exception
+  struct transaction_already_finalized: exception
   {
     virtual const char*
     what () const throw ();
@@ -2511,19 +2552,19 @@ namespace odb
 
   // Session exceptions.
   //
-  struct already_in_session: odb::exception
+  struct already_in_session: exception
   {
     virtual const char*
     what () const throw ();
   };
 
-  struct not_in_session: odb::exception
+  struct not_in_session: exception
   {
     virtual const char*
     what () const throw ();
   };
 
-  struct const_object: odb::exception
+  struct const_object: exception
   {
     virtual const char*
     what () const throw ();
@@ -2531,31 +2572,47 @@ namespace odb
 
   // Database operations exceptions.
   //
-  struct deadlock: odb::exception
+  struct recoverable: exception
+  {
+  };
+
+  struct connection_lost: recoverable
+  {
+    virtual const char*
+    what () const throw ();
+  };
+
+  struct timeout: recoverable
+  {
+    virtual const char*
+    what () const throw ();
+  };
+
+  struct deadlock: recoverable
   {
     virtual const char*
     what () const throw ();
   };
 
-  struct object_not_persistent: odb::exception
+  struct object_not_persistent: exception
   {
     virtual const char*
     what () const throw ();
   };
 
-  struct object_already_persistent: odb::exception
+  struct object_already_persistent: exception
   {
     virtual const char*
     what () const throw ();
   };
 
-  struct result_not_cached: odb::exception
+  struct result_not_cached: exception
   {
     virtual const char*
     what () const throw ();
   };
 
-  struct database_exception: odb::exception
+  struct database_exception: exception
   {
   };
 }
@@ -2578,21 +2635,28 @@ namespace odb
      odb::session class and are discussed
      in Chapter 8, "Session".

-

The deadlock exception is thrown when a transaction - deadlock is detected by the database system. It can be thrown by any - database function. See Section 3.4, "Transactions" +

The recoverable exception serves as a common base + for all the recoverable exceptions, which are: connection_lost, + timeout, and deadlock. The + connection_lost exception is thrown when a connection + to the database is lost. Similarly, the timeout exception + is thrown if one of the database operations or the whole transaction + has timed out. The deadlock exception is thrown when a + transaction deadlock is detected by the database system. These + exceptions can be thrown by any database function. See + Section 3.5, "Error Handling and Recovery" for details.

The object_already_persistent exception is thrown by the persist() database function. See - Section 3.5, "Making Objects Persistent" + Section 3.6, "Making Objects Persistent" for details.

The object_not_persistent exception is thrown by the load() and update() database functions. Refer to - Section 3.6, "Loading Persistent Objects" and - Section 3.7, "Updating Persistent Objects" for + Section 3.7, "Loading Persistent Objects" and + Section 3.8, "Updating Persistent Objects" for more information.

The result_not_cached exception is thrown by @@ -4942,9 +5006,9 @@ namespace odb

A session is an object cache. Every time an object is made persistent by calling the database::persist() function - (Section 3.5, "Making Objects Persistent"), loaded + (Section 3.6, "Making Objects Persistent"), loaded by calling the database::load() or database::find() - function (Section 3.6, "Loading Persistent Objects"), + function (Section 3.7, "Loading Persistent Objects"), or loaded by iterating over a query result (Section 4.4, "Query Result"), the pointer to the persistent object, in the form of the canonical object pointer (Section 3.2, "Object @@ -4952,7 +5016,7 @@ namespace odb session is in effect, any subsequent calls to load the same object will return the cached instance. When an object's state is deleted from the database with the database::erase() function - (Section 3.8, "Deleting Persistent Objects"), the + (Section 3.9, "Deleting Persistent Objects"), the cached object pointer is removed from the session. For example:

@@ -5721,7 +5785,7 @@ private:
      choice.

For additional information on the automatic identifier assignment, - refer to Section 3.5, "Making Objects Persistent".

+ refer to Section 3.6, "Making Objects Persistent".

9.3.3 type

-- cgit v1.1