123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- /* Transactor framework, a wrapper for safely retryable transactions.
- *
- * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transactor instead.
- *
- * Copyright (c) 2000-2019, Jeroen T. Vermeulen.
- *
- * See COPYING for copyright license. If you did not receive a file called
- * COPYING with this source code, please notify the distributor of this mistake,
- * or contact the author.
- */
- #ifndef PQXX_H_TRANSACTOR
- #define PQXX_H_TRANSACTOR
- #include "pqxx/compiler-public.hxx"
- #include "pqxx/compiler-internal-pre.hxx"
- #include "pqxx/connection_base.hxx"
- #include "pqxx/transaction.hxx"
- // Methods tested in eg. test module test01 are marked with "//[t01]".
- namespace pqxx
- {
- /**
- * @defgroup transactor Transactor framework
- *
- * Sometimes a transaction can fail for completely transient reasons, such as a
- * conflict with another transaction in SERIALIZABLE isolation. The right way
- * to handle those failures is often just to re-run the transaction from
- * scratch.
- *
- * For example, your REST API might be handling each HTTP request in its own
- * database transaction, and if this kind of transient failure happens, you
- * simply want to "replay" the whole request, in a fresh transaction.
- *
- * You won't necessarily want to execute the exact same SQL commands with the
- * exact same data. Some of your SQL statements may depend on state that can
- * vary between retries. So instead of dumbly replaying the SQL, you re-run
- * the same application code that produced those SQL commands.
- *
- * The transactor framework makes it a little easier for you to do this safely,
- * and avoid typical pitfalls. You encapsulate the work that you want to do
- * into a callable that you pass to the @c perform function.
- *
- * Here's how it works. You write your transaction code as a lambda or
- * function, which creates its own transaction object, does its work, and
- * commits at the end. You pass that callback to @c pqxx::perform, which runs
- * it for you.
- *
- * If there's a failure inside your callback, there will be an exception. Your
- * transaction object goes out of scope and gets destroyed, so that it aborts
- * implicitly. Seeing this, @c perform tries running your callback again. It
- * stops doing that when the callback succeeds, or when it has failed too many
- * times, or when there's an error that leaves the database in an unknown
- * state, such as a lost connection just while we're waiting for the database
- * to confirm a commit. It all depends on the type of exception.
- *
- * The callback takes no arguments. If you're using lambdas, the easy way to
- * pass arguments is for the lambda to "capture" them from your variables. Or,
- * if you're using functions, you may want to use @c std::bind.
- *
- * Once your callback succeeds, it can return a result, and @c perform will
- * return that result back to you.
- */
- //@{
- /// Simple way to execute a transaction with automatic retry.
- /**
- * Executes your transaction code as a callback. Repeats it until it completes
- * normally, or it throws an error other than the few libpqxx-generated
- * exceptions that the framework understands, or after a given number of failed
- * attempts, or if the transaction ends in an "in-doubt" state.
- *
- * (An in-doubt state is one where libpqxx cannot determine whether the server
- * finally committed a transaction or not. This can happen if the network
- * connection to the server is lost just while we're waiting for its reply to
- * a "commit" statement. The server may have completed the commit, or not, but
- * it can't tell you because there's no longer a connection.
- *
- * Using this still takes a bit of care. If your callback makes use of data
- * from the database, you'll probably have to query that data within your
- * callback. If the attempt to perform your callback fails, and the framework
- * tries again, you'll be in a new transaction and the data in the database may
- * have changed under your feet.
- *
- * Also be careful about changing variables or data structures from within
- * your callback. The run may still fail, and perhaps get run again. The
- * ideal way to do it (in most cases) is to return your result from your
- * callback, and change your program's data state only after @c perform
- * completes successfully.
- *
- * @param callback Transaction code that can be called with no arguments.
- * @param attempts Maximum number of times to attempt performing callback.
- * Must be greater than zero.
- * @return Whatever your callback returns.
- */
- template<typename TRANSACTION_CALLBACK>
- inline auto perform(const TRANSACTION_CALLBACK &callback, int attempts=3)
- -> decltype(callback())
- {
- if (attempts <= 0)
- throw std::invalid_argument{
- "Zero or negative number of attempts passed to pqxx::perform()."};
- for (; attempts > 0; --attempts)
- {
- try
- {
- return callback();
- }
- catch (const in_doubt_error &)
- {
- // Not sure whether transaction went through or not. The last thing in
- // the world that we should do now is try again!
- throw;
- }
- catch (const statement_completion_unknown &)
- {
- // Not sure whether our last statement succeeded. Don't risk running it
- // again.
- throw;
- }
- catch (const broken_connection &)
- {
- // Connection failed. Definitely worth retrying.
- if (attempts <= 1) throw;
- continue;
- }
- catch (const transaction_rollback &)
- {
- // Some error that may well be transient, such as serialization failure
- // or deadlock. Worth retrying.
- if (attempts <= 1) throw;
- continue;
- }
- }
- throw pqxx::internal_error{"No outcome reached on perform()."};
- }
- /// @deprecated Pre-C++11 wrapper for automatically retrying transactions.
- /**
- * Pass an object of your transactor-based class to connection_base::perform()
- * to execute the transaction code embedded in it.
- *
- * connection_base::perform() is actually a template, specializing itself to any
- * transactor type you pass to it. This means you will have to pass it a
- * reference of your object's ultimate static type; runtime polymorphism is
- * not allowed. Hence the absence of virtual methods in transactor. The
- * exact methods to be called at runtime *must* be resolved at compile time.
- *
- * Your transactor-derived class must define a copy constructor. This will be
- * used to create a "clean" copy of your transactor for every attempt that
- * perform() makes to run it.
- */
- template<typename TRANSACTION=transaction<read_committed>> class transactor
- {
- public:
- using argument_type = TRANSACTION;
- PQXX_DEPRECATED explicit transactor( //[t04]
- const std::string &TName="transactor") :
- m_name{TName} { }
- /// Overridable transaction definition; insert your database code here
- /** The operation will be retried if the connection to the backend is lost or
- * the operation fails, but not if the connection is broken in such a way as
- * to leave the library in doubt as to whether the operation succeeded. In
- * that case, an in_doubt_error will be thrown.
- *
- * Recommended practice is to allow this operator to modify only the
- * transactor itself, and the dedicated transaction object it is passed as an
- * argument. This is what makes side effects, retrying etc. controllable in
- * the transactor framework.
- * @param T Dedicated transaction context created to perform this operation.
- */
- void operator()(TRANSACTION &T); //[t04]
- // Overridable member functions, called by connection_base::perform() if an
- // attempt to run transaction fails/succeeds, respectively, or if the
- // connection is lost at just the wrong moment, goes into an indeterminate
- // state. Use these to patch up runtime state to match events, if needed, or
- // to report failure conditions.
- /// Optional overridable function to be called if transaction is aborted
- /** This need not imply complete failure; the transactor will automatically
- * retry the operation a number of times before giving up. on_abort() will be
- * called for each of the failed attempts.
- *
- * One parameter is passed in by the framework: an error string describing why
- * the transaction failed. This will also be logged to the connection's
- * notice processor.
- */
- void on_abort(const char[]) noexcept {} //[t13]
- /// Optional overridable function to be called after successful commit
- /** If your on_commit() throws an exception, the actual back-end transaction
- * will remain committed, so any changes in the database remain regardless of
- * how this function terminates.
- */
- void on_commit() {} //[t07]
- /// Overridable function to be called when "in doubt" about outcome
- /** This may happen if the connection to the backend is lost while attempting
- * to commit. In that case, the backend may have committed the transaction
- * but is unable to confirm this to the frontend; or the transaction may have
- * failed, causing it to be rolled back, but again without acknowledgement to
- * the client program. The best way to deal with this situation is typically
- * to wave red flags in the user's face and ask him to investigate.
- *
- * The robusttransaction class is intended to reduce the chances of this
- * error occurring, at a certain cost in performance.
- * @see robusttransaction
- */
- void on_doubt() noexcept {} //[t13]
- /// The transactor's name.
- std::string name() const { return m_name; } //[t13]
- private:
- std::string m_name;
- };
- template<typename TRANSACTOR>
- inline void connection_base::perform(
- const TRANSACTOR &T,
- int Attempts)
- {
- if (Attempts <= 0) return;
- bool Done = false;
- // Make attempts to perform T
- do
- {
- --Attempts;
- // Work on a copy of T2 so we can restore the starting situation if need be
- TRANSACTOR T2{T};
- try
- {
- typename TRANSACTOR::argument_type X{*this, T2.name()};
- T2(X);
- X.commit();
- Done = true;
- }
- catch (const in_doubt_error &)
- {
- // Not sure whether transaction went through or not. The last thing in
- // the world that we should do now is retry.
- T2.on_doubt();
- throw;
- }
- catch (const std::exception &e)
- {
- // Could be any kind of error.
- T2.on_abort(e.what());
- if (Attempts <= 0) throw;
- continue;
- }
- catch (...)
- {
- // Don't try to forge ahead if we don't even know what happened
- T2.on_abort("Unknown exception");
- throw;
- }
- T2.on_commit();
- } while (not Done);
- }
- } // namespace pqxx
- //@}
- #include "pqxx/compiler-internal-post.hxx"
- #endif
|