See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
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 10 use function call_user_func; 11 use function time; 12 13 /** 14 * @internal 15 */ 16 class WithTransaction 17 { 18 /** @var callable */ 19 private $callback; 20 21 /** @var array */ 22 private $transactionOptions; 23 24 /** 25 * @see Session::startTransaction for supported transaction options 26 * 27 * @param callable $callback A callback that will be invoked within the transaction 28 * @param array $transactionOptions Additional options that are passed to Session::startTransaction 29 */ 30 public function __construct(callable $callback, array $transactionOptions = []) 31 { 32 $this->callback = $callback; 33 $this->transactionOptions = $transactionOptions; 34 } 35 36 /** 37 * Execute the operation in the given session 38 * 39 * This helper takes care of retrying the commit operation or the entire 40 * transaction if an error occurs. 41 * 42 * If the commit fails because of an UnknownTransactionCommitResult error, the 43 * commit is retried without re-invoking the callback. 44 * If the commit fails because of a TransientTransactionError, the entire 45 * transaction will be retried. In this case, the callback will be invoked 46 * again. It is important that the logic inside the callback is idempotent. 47 * 48 * In case of failures, the commit or transaction are retried until 120 seconds 49 * from the initial call have elapsed. After that, no retries will happen and 50 * the helper will throw the last exception received from the driver. 51 * 52 * @see Client::startSession 53 * 54 * @param Session $session A session object as retrieved by Client::startSession 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): void 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 ( 73 $e instanceof RuntimeException && 74 $e->hasErrorLabel('TransientTransactionError') && 75 ! $this->isTransactionTimeLimitExceeded($startTime) 76 ) { 77 continue; 78 } 79 80 throw $e; 81 } 82 83 if (! $session->isInTransaction()) { 84 // Assume callback intentionally ended the transaction 85 return; 86 } 87 88 while (true) { 89 try { 90 $session->commitTransaction(); 91 } catch (RuntimeException $e) { 92 if ( 93 $e->getCode() !== 50 /* MaxTimeMSExpired */ && 94 $e->hasErrorLabel('UnknownTransactionCommitResult') && 95 ! $this->isTransactionTimeLimitExceeded($startTime) 96 ) { 97 // Retry committing the transaction 98 continue; 99 } 100 101 if ( 102 $e->hasErrorLabel('TransientTransactionError') && 103 ! $this->isTransactionTimeLimitExceeded($startTime) 104 ) { 105 // Restart the transaction, invoking the callback again 106 continue 2; 107 } 108 109 throw $e; 110 } 111 112 // Commit was successful 113 break; 114 } 115 116 // Transaction was successful 117 break; 118 } 119 } 120 121 /** 122 * Returns whether the time limit for retrying transactions in the convenient transaction API has passed 123 * 124 * @param int $startTime The time the transaction was started 125 */ 126 private function isTransactionTimeLimitExceeded(int $startTime): bool 127 { 128 return time() - $startTime >= 120; 129 } 130 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body