Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 39 and 311]

   1  <?php
   2  
   3  namespace MongoDB\Operation;
   4  
   5  use Exception;
   6  use MongoDB\Driver\Exception\RuntimeException;
   7  use MongoDB\Driver\Session;
   8  use Throwable;
   9  use function call_user_func;
  10  use function time;
  11  
  12  /**
  13   * @internal
  14   */
  15  class WithTransaction
  16  {
  17      /** @var callable */
  18      private $callback;
  19  
  20      /** @var array */
  21      private $transactionOptions;
  22  
  23      /**
  24       * @see Session::startTransaction for supported transaction options
  25       *
  26       * @param callable $callback           A callback that will be invoked within the transaction
  27       * @param array    $transactionOptions Additional options that are passed to Session::startTransaction
  28       */
  29      public function __construct(callable $callback, array $transactionOptions = [])
  30      {
  31          $this->callback = $callback;
  32          $this->transactionOptions = $transactionOptions;
  33      }
  34  
  35      /**
  36       * Execute the operation in the given session
  37       *
  38       * This helper takes care of retrying the commit operation or the entire
  39       * transaction if an error occurs.
  40       *
  41       * If the commit fails because of an UnknownTransactionCommitResult error, the
  42       * commit is retried without re-invoking the callback.
  43       * If the commit fails because of a TransientTransactionError, the entire
  44       * transaction will be retried. In this case, the callback will be invoked
  45       * again. It is important that the logic inside the callback is idempotent.
  46       *
  47       * In case of failures, the commit or transaction are retried until 120 seconds
  48       * from the initial call have elapsed. After that, no retries will happen and
  49       * the helper will throw the last exception received from the driver.
  50       *
  51       * @see Client::startSession
  52       *
  53       * @param Session $session A session object as retrieved by Client::startSession
  54       * @return void
  55       * @throws RuntimeException for driver errors while committing the transaction
  56       * @throws Exception for any other errors, including those thrown in the callback
  57       */
  58      public function execute(Session $session)
  59      {
  60          $startTime = time();
  61  
  62          while (true) {
  63              $session->startTransaction($this->transactionOptions);
  64  
  65              try {
  66                  call_user_func($this->callback, $session);
  67              } catch (Throwable $e) {
  68                  if ($session->isInTransaction()) {
  69                      $session->abortTransaction();
  70                  }
  71  
  72                  if ($e instanceof RuntimeException &&
  73                      $e->hasErrorLabel('TransientTransactionError') &&
  74                      ! $this->isTransactionTimeLimitExceeded($startTime)
  75                  ) {
  76                      continue;
  77                  }
  78  
  79                  throw $e;
  80              }
  81  
  82              if (! $session->isInTransaction()) {
  83                  // Assume callback intentionally ended the transaction
  84                  return;
  85              }
  86  
  87              while (true) {
  88                  try {
  89                      $session->commitTransaction();
  90                  } catch (RuntimeException $e) {
  91                      if ($e->getCode() !== 50 /* MaxTimeMSExpired */ &&
  92                          $e->hasErrorLabel('UnknownTransactionCommitResult') &&
  93                          ! $this->isTransactionTimeLimitExceeded($startTime)
  94                      ) {
  95                          // Retry committing the transaction
  96                          continue;
  97                      }
  98  
  99                      if ($e->hasErrorLabel('TransientTransactionError') &&
 100                          ! $this->isTransactionTimeLimitExceeded($startTime)
 101                      ) {
 102                          // Restart the transaction, invoking the callback again
 103                          continue 2;
 104                      }
 105  
 106                      throw $e;
 107                  }
 108  
 109                  // Commit was successful
 110                  break;
 111              }
 112  
 113              // Transaction was successful
 114              break;
 115          }
 116      }
 117  
 118      /**
 119       * Returns whether the time limit for retrying transactions in the convenient transaction API has passed
 120       *
 121       * @param int $startTime The time the transaction was started
 122       * @return bool
 123       */
 124      private function isTransactionTimeLimitExceeded($startTime)
 125      {
 126          return time() - $startTime >= 120;
 127      }
 128  }