transactor.hxx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /* Transactor framework, a wrapper for safely retryable transactions.
  2. *
  3. * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transactor instead.
  4. *
  5. * Copyright (c) 2000-2019, Jeroen T. Vermeulen.
  6. *
  7. * See COPYING for copyright license. If you did not receive a file called
  8. * COPYING with this source code, please notify the distributor of this mistake,
  9. * or contact the author.
  10. */
  11. #ifndef PQXX_H_TRANSACTOR
  12. #define PQXX_H_TRANSACTOR
  13. #include "pqxx/compiler-public.hxx"
  14. #include "pqxx/compiler-internal-pre.hxx"
  15. #include "pqxx/connection_base.hxx"
  16. #include "pqxx/transaction.hxx"
  17. // Methods tested in eg. test module test01 are marked with "//[t01]".
  18. namespace pqxx
  19. {
  20. /**
  21. * @defgroup transactor Transactor framework
  22. *
  23. * Sometimes a transaction can fail for completely transient reasons, such as a
  24. * conflict with another transaction in SERIALIZABLE isolation. The right way
  25. * to handle those failures is often just to re-run the transaction from
  26. * scratch.
  27. *
  28. * For example, your REST API might be handling each HTTP request in its own
  29. * database transaction, and if this kind of transient failure happens, you
  30. * simply want to "replay" the whole request, in a fresh transaction.
  31. *
  32. * You won't necessarily want to execute the exact same SQL commands with the
  33. * exact same data. Some of your SQL statements may depend on state that can
  34. * vary between retries. So instead of dumbly replaying the SQL, you re-run
  35. * the same application code that produced those SQL commands.
  36. *
  37. * The transactor framework makes it a little easier for you to do this safely,
  38. * and avoid typical pitfalls. You encapsulate the work that you want to do
  39. * into a callable that you pass to the @c perform function.
  40. *
  41. * Here's how it works. You write your transaction code as a lambda or
  42. * function, which creates its own transaction object, does its work, and
  43. * commits at the end. You pass that callback to @c pqxx::perform, which runs
  44. * it for you.
  45. *
  46. * If there's a failure inside your callback, there will be an exception. Your
  47. * transaction object goes out of scope and gets destroyed, so that it aborts
  48. * implicitly. Seeing this, @c perform tries running your callback again. It
  49. * stops doing that when the callback succeeds, or when it has failed too many
  50. * times, or when there's an error that leaves the database in an unknown
  51. * state, such as a lost connection just while we're waiting for the database
  52. * to confirm a commit. It all depends on the type of exception.
  53. *
  54. * The callback takes no arguments. If you're using lambdas, the easy way to
  55. * pass arguments is for the lambda to "capture" them from your variables. Or,
  56. * if you're using functions, you may want to use @c std::bind.
  57. *
  58. * Once your callback succeeds, it can return a result, and @c perform will
  59. * return that result back to you.
  60. */
  61. //@{
  62. /// Simple way to execute a transaction with automatic retry.
  63. /**
  64. * Executes your transaction code as a callback. Repeats it until it completes
  65. * normally, or it throws an error other than the few libpqxx-generated
  66. * exceptions that the framework understands, or after a given number of failed
  67. * attempts, or if the transaction ends in an "in-doubt" state.
  68. *
  69. * (An in-doubt state is one where libpqxx cannot determine whether the server
  70. * finally committed a transaction or not. This can happen if the network
  71. * connection to the server is lost just while we're waiting for its reply to
  72. * a "commit" statement. The server may have completed the commit, or not, but
  73. * it can't tell you because there's no longer a connection.
  74. *
  75. * Using this still takes a bit of care. If your callback makes use of data
  76. * from the database, you'll probably have to query that data within your
  77. * callback. If the attempt to perform your callback fails, and the framework
  78. * tries again, you'll be in a new transaction and the data in the database may
  79. * have changed under your feet.
  80. *
  81. * Also be careful about changing variables or data structures from within
  82. * your callback. The run may still fail, and perhaps get run again. The
  83. * ideal way to do it (in most cases) is to return your result from your
  84. * callback, and change your program's data state only after @c perform
  85. * completes successfully.
  86. *
  87. * @param callback Transaction code that can be called with no arguments.
  88. * @param attempts Maximum number of times to attempt performing callback.
  89. * Must be greater than zero.
  90. * @return Whatever your callback returns.
  91. */
  92. template<typename TRANSACTION_CALLBACK>
  93. inline auto perform(const TRANSACTION_CALLBACK &callback, int attempts=3)
  94. -> decltype(callback())
  95. {
  96. if (attempts <= 0)
  97. throw std::invalid_argument{
  98. "Zero or negative number of attempts passed to pqxx::perform()."};
  99. for (; attempts > 0; --attempts)
  100. {
  101. try
  102. {
  103. return callback();
  104. }
  105. catch (const in_doubt_error &)
  106. {
  107. // Not sure whether transaction went through or not. The last thing in
  108. // the world that we should do now is try again!
  109. throw;
  110. }
  111. catch (const statement_completion_unknown &)
  112. {
  113. // Not sure whether our last statement succeeded. Don't risk running it
  114. // again.
  115. throw;
  116. }
  117. catch (const broken_connection &)
  118. {
  119. // Connection failed. Definitely worth retrying.
  120. if (attempts <= 1) throw;
  121. continue;
  122. }
  123. catch (const transaction_rollback &)
  124. {
  125. // Some error that may well be transient, such as serialization failure
  126. // or deadlock. Worth retrying.
  127. if (attempts <= 1) throw;
  128. continue;
  129. }
  130. }
  131. throw pqxx::internal_error{"No outcome reached on perform()."};
  132. }
  133. /// @deprecated Pre-C++11 wrapper for automatically retrying transactions.
  134. /**
  135. * Pass an object of your transactor-based class to connection_base::perform()
  136. * to execute the transaction code embedded in it.
  137. *
  138. * connection_base::perform() is actually a template, specializing itself to any
  139. * transactor type you pass to it. This means you will have to pass it a
  140. * reference of your object's ultimate static type; runtime polymorphism is
  141. * not allowed. Hence the absence of virtual methods in transactor. The
  142. * exact methods to be called at runtime *must* be resolved at compile time.
  143. *
  144. * Your transactor-derived class must define a copy constructor. This will be
  145. * used to create a "clean" copy of your transactor for every attempt that
  146. * perform() makes to run it.
  147. */
  148. template<typename TRANSACTION=transaction<read_committed>> class transactor
  149. {
  150. public:
  151. using argument_type = TRANSACTION;
  152. PQXX_DEPRECATED explicit transactor( //[t04]
  153. const std::string &TName="transactor") :
  154. m_name{TName} { }
  155. /// Overridable transaction definition; insert your database code here
  156. /** The operation will be retried if the connection to the backend is lost or
  157. * the operation fails, but not if the connection is broken in such a way as
  158. * to leave the library in doubt as to whether the operation succeeded. In
  159. * that case, an in_doubt_error will be thrown.
  160. *
  161. * Recommended practice is to allow this operator to modify only the
  162. * transactor itself, and the dedicated transaction object it is passed as an
  163. * argument. This is what makes side effects, retrying etc. controllable in
  164. * the transactor framework.
  165. * @param T Dedicated transaction context created to perform this operation.
  166. */
  167. void operator()(TRANSACTION &T); //[t04]
  168. // Overridable member functions, called by connection_base::perform() if an
  169. // attempt to run transaction fails/succeeds, respectively, or if the
  170. // connection is lost at just the wrong moment, goes into an indeterminate
  171. // state. Use these to patch up runtime state to match events, if needed, or
  172. // to report failure conditions.
  173. /// Optional overridable function to be called if transaction is aborted
  174. /** This need not imply complete failure; the transactor will automatically
  175. * retry the operation a number of times before giving up. on_abort() will be
  176. * called for each of the failed attempts.
  177. *
  178. * One parameter is passed in by the framework: an error string describing why
  179. * the transaction failed. This will also be logged to the connection's
  180. * notice processor.
  181. */
  182. void on_abort(const char[]) noexcept {} //[t13]
  183. /// Optional overridable function to be called after successful commit
  184. /** If your on_commit() throws an exception, the actual back-end transaction
  185. * will remain committed, so any changes in the database remain regardless of
  186. * how this function terminates.
  187. */
  188. void on_commit() {} //[t07]
  189. /// Overridable function to be called when "in doubt" about outcome
  190. /** This may happen if the connection to the backend is lost while attempting
  191. * to commit. In that case, the backend may have committed the transaction
  192. * but is unable to confirm this to the frontend; or the transaction may have
  193. * failed, causing it to be rolled back, but again without acknowledgement to
  194. * the client program. The best way to deal with this situation is typically
  195. * to wave red flags in the user's face and ask him to investigate.
  196. *
  197. * The robusttransaction class is intended to reduce the chances of this
  198. * error occurring, at a certain cost in performance.
  199. * @see robusttransaction
  200. */
  201. void on_doubt() noexcept {} //[t13]
  202. /// The transactor's name.
  203. std::string name() const { return m_name; } //[t13]
  204. private:
  205. std::string m_name;
  206. };
  207. template<typename TRANSACTOR>
  208. inline void connection_base::perform(
  209. const TRANSACTOR &T,
  210. int Attempts)
  211. {
  212. if (Attempts <= 0) return;
  213. bool Done = false;
  214. // Make attempts to perform T
  215. do
  216. {
  217. --Attempts;
  218. // Work on a copy of T2 so we can restore the starting situation if need be
  219. TRANSACTOR T2{T};
  220. try
  221. {
  222. typename TRANSACTOR::argument_type X{*this, T2.name()};
  223. T2(X);
  224. X.commit();
  225. Done = true;
  226. }
  227. catch (const in_doubt_error &)
  228. {
  229. // Not sure whether transaction went through or not. The last thing in
  230. // the world that we should do now is retry.
  231. T2.on_doubt();
  232. throw;
  233. }
  234. catch (const std::exception &e)
  235. {
  236. // Could be any kind of error.
  237. T2.on_abort(e.what());
  238. if (Attempts <= 0) throw;
  239. continue;
  240. }
  241. catch (...)
  242. {
  243. // Don't try to forge ahead if we don't even know what happened
  244. T2.on_abort("Unknown exception");
  245. throw;
  246. }
  247. T2.on_commit();
  248. } while (not Done);
  249. }
  250. } // namespace pqxx
  251. //@}
  252. #include "pqxx/compiler-internal-post.hxx"
  253. #endif