aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2013-01-18 10:23:39 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2013-01-18 10:23:39 +0200
commitbce7f97cf5d60cf37ab623760eaa44572e214f69 (patch)
tree21bdf3defd5029f937553603d90bf5f44d0aa653
parent63140494efedac10cf825d484ecaba5829f1ea49 (diff)
Add support for post-commit/rollback callbacks
New test: common/transaction/callback.
-rw-r--r--odb/transaction.cxx184
-rw-r--r--odb/transaction.hxx85
-rw-r--r--odb/transaction.ixx5
3 files changed, 273 insertions, 1 deletions
diff --git a/odb/transaction.cxx b/odb/transaction.cxx
index 6f92c41..197fc63 100644
--- a/odb/transaction.cxx
+++ b/odb/transaction.cxx
@@ -7,6 +7,8 @@
#include <odb/details/tls.hxx>
+using namespace std;
+
namespace odb
{
using namespace details;
@@ -74,6 +76,15 @@ namespace odb
tls_set (current_transaction, t);
}
+ struct rollback_guard
+ {
+ rollback_guard (transaction& t): t_ (&t) {}
+ ~rollback_guard () {if (t_ != 0) t_->call (transaction::event_rollback);}
+ void release () {t_ = 0;}
+ private:
+ transaction* t_;
+ };
+
void transaction::
commit ()
{
@@ -81,6 +92,8 @@ namespace odb
throw transaction_already_finalized ();
finalized_ = true;
+ rollback_guard rg (*this);
+
impl_->connection ().transaction_tracer_ = 0;
if (tls_get (current_transaction) == this)
@@ -90,6 +103,10 @@ namespace odb
}
impl_->commit ();
+ rg.release ();
+
+ if (callback_count_ != 0)
+ call (event_commit);
}
void transaction::
@@ -99,6 +116,8 @@ namespace odb
throw transaction_already_finalized ();
finalized_ = true;
+ rollback_guard rg (*this);
+
impl_->connection ().transaction_tracer_ = 0;
if (tls_get (current_transaction) == this)
@@ -108,6 +127,171 @@ namespace odb
}
impl_->rollback ();
+ rg.release ();
+
+ if (callback_count_ != 0)
+ call (event_rollback);
+ }
+
+ void transaction::
+ call (unsigned short event)
+ {
+ size_t stack_count (callback_count_ < stack_callback_count
+ ? callback_count_ : stack_callback_count);
+ size_t dyn_count (callback_count_ - stack_count);
+
+ // We need to be careful with the situation where a callback
+ // throws and we neither call the rest of the callbacks nor
+ // reset their states. To make sure this doesn't happen, we
+ // do a first pass and reset all the states.
+ //
+ for (size_t i (0); i < stack_count; ++i)
+ {
+ callback_data& d (stack_callbacks_[i]);
+ if (d.event != 0 && d.state != 0)
+ *d.state = 0;
+ }
+
+ for (size_t i (0); i < dyn_count; ++i)
+ {
+ callback_data& d (dyn_callbacks_[i]);
+ if (d.event != 0 && d.state != 0)
+ *d.state = 0;
+ }
+
+ // Now do the actual calls.
+ //
+ for (size_t i (0); i < stack_count; ++i)
+ {
+ callback_data& d (stack_callbacks_[i]);
+ if (d.event & event)
+ d.func (event, d.key, d.data);
+ }
+
+ for (size_t i (0); i < dyn_count; ++i)
+ {
+ callback_data& d (dyn_callbacks_[i]);
+ if (d.event & event)
+ d.func (event, d.key, d.data);
+ }
+
+ // Clean things up in case this instance is going to be reused.
+ //
+ if (dyn_count != 0)
+ dyn_callbacks_.clear ();
+
+ free_callback_ = max_callback_count;
+ callback_count_ = 0;
+ }
+
+ void transaction::
+ register_ (callback_type func,
+ void* key,
+ unsigned short event,
+ unsigned long long data,
+ transaction** state)
+ {
+ callback_data* s;
+
+ // If we have a free slot, use it.
+ //
+ if (free_callback_ != max_callback_count)
+ {
+ s = (free_callback_ < stack_callback_count)
+ ? stack_callbacks_ + free_callback_
+ : &dyn_callbacks_[free_callback_ - stack_callback_count];
+
+ free_callback_ = reinterpret_cast<size_t> (s->key);
+ }
+ // If we have space in the stack, grab that.
+ //
+ else if (callback_count_ < stack_callback_count)
+ {
+ s = stack_callbacks_ + callback_count_;
+ callback_count_++;
+ }
+ // Otherwise use the dynamic storage.
+ //
+ else
+ {
+ dyn_callbacks_.push_back (callback_data ());
+ s = &dyn_callbacks_.back ();
+ callback_count_++;
+ }
+
+ s->func = func;
+ s->key = key;
+ s->event = event;
+ s->data = data;
+ s->state = state;
+ }
+
+ void transaction::
+ unregister (void* key)
+ {
+ // Note that it is ok for this function not to find the key.
+ //
+ if (callback_count_ == 0)
+ return;
+
+ size_t stack_count;
+
+ // See if this is the last slot registered. This will be a fast path
+ // if things are going to be unregistered from destructors.
+ //
+ if (callback_count_ <= stack_callback_count)
+ {
+ if (stack_callbacks_[callback_count_ - 1].key == key)
+ {
+ callback_count_--;
+ return;
+ }
+
+ stack_count = callback_count_;
+ }
+ else
+ {
+ if (dyn_callbacks_.back ().key == key)
+ {
+ dyn_callbacks_.pop_back ();
+ callback_count_--;
+ return;
+ }
+
+ stack_count = stack_callback_count;
+ }
+
+ size_t dyn_count (callback_count_ - stack_count);
+
+ // Otherwise do a linear search.
+ //
+ for (size_t i (0); i < stack_count; ++i)
+ {
+ callback_data& d (stack_callbacks_[i]);
+ if (d.key == key)
+ {
+ // Add to the free list.
+ //
+ d.event = 0;
+ d.key = reinterpret_cast<void*> (free_callback_);
+ free_callback_ = i;
+ return;
+ }
+ }
+
+ for (size_t i (0); i < dyn_count; ++i)
+ {
+ callback_data& d (dyn_callbacks_[i]);
+ if (d.key == key)
+ {
+ // Add to the free list.
+ //
+ d.event = 0;
+ d.key = reinterpret_cast<void*> (free_callback_);
+ free_callback_ = stack_callback_count + i;
+ return;
+ }
+ }
}
//
diff --git a/odb/transaction.hxx b/odb/transaction.hxx
index e75cddd..1f7b866 100644
--- a/odb/transaction.hxx
+++ b/odb/transaction.hxx
@@ -7,6 +7,9 @@
#include <odb/pre.hxx>
+#include <vector>
+#include <cstddef> // std::size_t
+
#include <odb/forward.hxx>
#include <odb/details/export.hxx>
@@ -93,6 +96,46 @@ namespace odb
tracer_type*
tracer () const;
+ // Post-commit/rollback callbacks.
+ //
+ public:
+ static const unsigned short event_commit = 0x01;
+ static const unsigned short event_rollback = 0x02;
+ static const unsigned short event_all = event_commit | event_rollback;
+
+ typedef void (*callback_type) (
+ unsigned short event, void* key, unsigned long long data);
+
+ // Register a post-commit/rollback callback. The data argument
+ // can be used to store any user data that does not exceed 8
+ // bytes and doesn't require alignment greater than unsigned
+ // long long, such as an old value that needs to be restored
+ // in case of a rollback.
+ //
+ // The state argument can be used to indicate to the caller
+ // that the callback has been unregistered because the
+ // transaction has terminated. In this case the transaction
+ // resets the passed pointer to 0.
+ //
+ // Note that the order in which the callbacks are called is
+ // unspecified.
+ //
+ void
+ register_ (callback_type,
+ void* key,
+ unsigned short event = event_all,
+ unsigned long long data = 0,
+ transaction** state = 0);
+
+ // Unregister a post-commit/rollback callback. Note that this is a
+ // potentially slow operation. You also don't need to unregister
+ // a callback that has been called or auto-reset using the state
+ // argument passed to register_(). This function does nothing if
+ // the key is not found.
+ //
+ void
+ unregister (void* key);
+
public:
transaction_impl&
implementation ();
@@ -104,8 +147,50 @@ namespace odb
transaction& operator= (const transaction&);
protected:
+ friend struct rollback_guard;
+
+ void
+ call (unsigned short event);
+
+ protected:
bool finalized_;
details::unique_ptr<transaction_impl> impl_;
+
+ // Callbacks.
+ //
+ struct callback_data
+ {
+ unsigned short event;
+ callback_type func;
+ void* key;
+ unsigned long long data;
+ transaction** state;
+ };
+
+ // Slots for the first 20 callback are pre-allocated on the stack.
+ // For the rest they are allocated dynamically as needed.
+ //
+ // Note, if you change stack_callback_count, make sure you also
+ // update the common/transaction/callback test accordingly.
+ //
+ static const std::size_t stack_callback_count = 20;
+ static const std::size_t max_callback_count = ~(std::size_t (0));
+
+ callback_data stack_callbacks_[stack_callback_count];
+ std::vector<callback_data> dyn_callbacks_;
+
+ // When a callback is unregistered, the free slot from the stack is
+ // added to the linked list of free slots which is organized by
+ // re-using the key data member to store the slot's index (we cannot
+ // store a pointer because std::vector may move slots on expansion).
+ // The value equal to max_callback_count indicates no free slots are
+ // available.
+ //
+ std::size_t free_callback_;
+
+ // Total number of used slots, both registered and in the free list.
+ //
+ std::size_t callback_count_;
};
class LIBODB_EXPORT transaction_impl
diff --git a/odb/transaction.ixx b/odb/transaction.ixx
index 6c6c3c1..6d5553d 100644
--- a/odb/transaction.ixx
+++ b/odb/transaction.ixx
@@ -8,7 +8,10 @@ namespace odb
{
inline transaction::
transaction (transaction_impl* impl, bool make_current)
- : finalized_ (true), impl_ (0)
+ : finalized_ (true),
+ impl_ (0),
+ free_callback_ (max_callback_count),
+ callback_count_ (0)
{
reset (impl, make_current);
}