From f573927d48a7ceb155cf681e7cbad1e5176b2c0b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 6 Mar 2012 11:34:57 +0200 Subject: Detect situations where session is required but not used Throw session_required. --- NEWS | 5 +++++ doc/manual.xhtml | 56 +++++++++++++++++++++++++++++++++++++++++++++++ odb/relational/source.hxx | 18 +++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/NEWS b/NEWS index 4363f64..06879eb 100644 --- a/NEWS +++ b/NEWS @@ -38,6 +38,11 @@ Version 1.9.0 namespace. For more information, refer to Section 12.5.1, "pointer" and Section 12.5.2, "table" in the ODB manual. + * New exception, odb::session_required, is thrown when ODB detects that + correctly loading a bidirectional object relationship requires a session + but one is not used. For more information, refer to Section 6.2, + "Bidirectional Relationships" in the ODB manual. + Version 1.8.0 * Support for the Microsoft SQL Server database. The provided connection diff --git a/doc/manual.xhtml b/doc/manual.xhtml index a36481d..6257cf7 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -3648,6 +3648,12 @@ namespace odb what () const throw (); }; + struct session_required: exception + { + virtual const char* + what () const throw (); + }; + // Database operations exceptions. // struct recoverable: exception @@ -3729,6 +3735,12 @@ namespace odb not_in_session) are thrown by the odb::session class and are discussed in Chapter 10, "Session".

+

The session_required exception is thrown when ODB detects + that correctly loading a bidirectional object relationship requires a + session but one is not used. See Section 6.2, + "Bidirectional Relationships" for more information on this + exception.

+

The recoverable exception serves as a common base for all the recoverable exceptions, which are: connection_lost, timeout, and deadlock. The @@ -5143,6 +5155,50 @@ private: }; +

At the beginning of this chapter we examined how to use a session + to make sure a single object is shared among all other objects pointing + to it. With bidirectional relationships involving weak pointers the + use of a session becomes even more crucial. Consider the following + transaction that tries to load the position object + from the above example without using a session:

+ +
+transaction t (db.begin ())
+shared_ptr<position> p (db.load<position> (1));
+...
+t.commit ();
+  
+ +

When we load the position object, the employee + object, which it points to, is also loaded. While employee + is initially stored as shared_ptr, it is then assigned to + the employee_ member which is weak_ptr. Once + the assignment is complete, the shared pointer goes out of scope + and the only pointer that points to the newly loaded + employee object is the employee_ weak + pointer. And that means the employee object is deleted + immediately after being loaded. To help avoid such pathological + situations ODB detects cases where a newly loaded object will + immediately be deleted and throws the odb::session_required + exception.

+ +

As the exception name suggests, the easiest way to resolve this + problem is to use a session:

+ +
+session s;
+transaction t (db.begin ())
+shared_ptr<position> p (db.load<position> (1));
+...
+t.commit ();
+  
+ +

In our example, the session will maintain a shared pointer to the + loaded employee object preventing its immediate + deletion. Another way to resolve this problem is to avoid + immediate loading of the pointed-to objects using lazy weak + pointers. Lazy pointers are discussed in Section 6.3, + "Lazy Pointers" later in this chapter.

Above, to model a bidirectional relationship in persistent classes, we used two pointers, one in each object. While this is a natural diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx index fb13ade..a8edcf3 100644 --- a/odb/relational/source.hxx +++ b/odb/relational/source.hxx @@ -1195,6 +1195,7 @@ namespace relational if (lazy_pointer (pt)) os << member << " = ptr_traits::pointer_type (*db, id);"; else + { os << "// If a compiler error points to the line below, then" << endl << "// it most likely means that a pointer used in a member" << endl << "// cannot be initialized from an object pointer." << endl @@ -1202,6 +1203,23 @@ namespace relational << member << " = ptr_traits::pointer_type (" << endl << "db->load< obj_traits::object_type > (id));"; + // If we are loading into an eager weak pointer, make sure there + // is someone else holding a strong pointer to it (normally a + // session). Otherwise, the object will be loaded and immediately + // deleted. Besides not making much sense, this also breaks the + // delayed loading machinery which expects the object to be around + // at least until the top-level load() returns. + // + if (weak_pointer (pt)) + { + os << endl + << "if (pointer_traits< ptr_traits::strong_pointer_type >" << + "::null_ptr (" << endl + << "ptr_traits::lock (" << member << ")))" << endl + << "throw session_required ();"; + } + } + // @@ Composite value currently cannot be NULL. // if (!composite (mi.t)) -- cgit v1.1