From bce7f97cf5d60cf37ab623760eaa44572e214f69 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 18 Jan 2013 10:23:39 +0200 Subject: Add support for post-commit/rollback callbacks New test: common/transaction/callback. --- odb/transaction.cxx | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++++ odb/transaction.hxx | 85 ++++++++++++++++++++++++ odb/transaction.ixx | 5 +- 3 files changed, 273 insertions(+), 1 deletion(-) 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 +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 (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 (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 (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 +#include +#include // std::size_t + #include #include @@ -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 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 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); } -- cgit v1.1