Unverified Commit 54b2a506 authored by Andreas Braun's avatar Andreas Braun

Merge pull request #654

parents c69a7155 b9865b8c
<?php
namespace MongoDB\Operation;
use Exception;
use MongoDB\Driver\Exception\RuntimeException;
use MongoDB\Driver\Session;
use function call_user_func;
use function time;
/**
* @internal
*/
class WithTransaction
{
/** @var callable */
private $callback;
/** @var array */
private $transactionOptions;
/**
* @see Session::startTransaction for supported transaction options
*
* @param callable $callback A callback that will be invoked within the transaction
* @param array $transactionOptions Additional options that are passed to Session::startTransaction
*/
public function __construct(callable $callback, array $transactionOptions = [])
{
$this->callback = $callback;
$this->transactionOptions = $transactionOptions;
}
/**
* Execute the operation in the given session
*
* This helper takes care of retrying the commit operation or the entire
* transaction if an error occurs.
*
* If the commit fails because of an UnknownTransactionCommitResult error, the
* commit is retried without re-invoking the callback.
* If the commit fails because of a TransientTransactionError, the entire
* transaction will be retried. In this case, the callback will be invoked
* again. It is important that the logic inside the callback is idempotent.
*
* In case of failures, the commit or transaction are retried until 120 seconds
* from the initial call have elapsed. After that, no retries will happen and
* the helper will throw the last exception received from the driver.
*
* @see Client::startSession
*
* @param Session $session A session object as retrieved by Client::startSession
* @return void
* @throws RuntimeException for driver errors while committing the transaction
* @throws Exception for any other errors, including those thrown in the callback
*/
public function execute(Session $session)
{
$startTime = time();
while (true) {
$session->startTransaction($this->transactionOptions);
try {
call_user_func($this->callback, $session);
} catch (Exception $e) {
if ($session->isInTransaction()) {
$session->abortTransaction();
}
if ($e instanceof RuntimeException &&
$e->hasErrorLabel('TransientTransactionError') &&
! $this->isTransactionTimeLimitExceeded($startTime)
) {
continue;
}
throw $e;
}
if (! $session->isInTransaction()) {
// Assume callback intentionally ended the transaction
return;
}
while (true) {
try {
$session->commitTransaction();
} catch (RuntimeException $e) {
if ($e->getCode() !== 50 /* MaxTimeMSExpired */ &&
$e->hasErrorLabel('UnknownTransactionCommitResult') &&
! $this->isTransactionTimeLimitExceeded($startTime)
) {
// Retry committing the transaction
continue;
}
if ($e->hasErrorLabel('TransientTransactionError') &&
! $this->isTransactionTimeLimitExceeded($startTime)
) {
// Restart the transaction, invoking the callback again
continue 2;
}
throw $e;
}
// Commit was successful
break;
}
// Transaction was successful
break;
}
}
/**
* Returns whether the time limit for retrying transactions in the convenient transaction API has passed
*
* @param int $startTime The time the transaction was started
* @return bool
*/
private function isTransactionTimeLimitExceeded($startTime)
{
return time() - $startTime >= 120;
}
}
......@@ -17,10 +17,13 @@
namespace MongoDB;
use Exception;
use MongoDB\BSON\Serializable;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\RuntimeException;
use MongoDB\Operation\WithTransaction;
use ReflectionClass;
use ReflectionException;
use function end;
......@@ -338,3 +341,35 @@ function create_field_path_type_map(array $typeMap, $fieldPath)
return $typeMap;
}
/**
* Execute a callback within a transaction in the given session
*
* This helper takes care of retrying the commit operation or the entire
* transaction if an error occurs.
*
* If the commit fails because of an UnknownTransactionCommitResult error, the
* commit is retried without re-invoking the callback.
* If the commit fails because of a TransientTransactionError, the entire
* transaction will be retried. In this case, the callback will be invoked
* again. It is important that the logic inside the callback is idempotent.
*
* In case of failures, the commit or transaction are retried until 120 seconds
* from the initial call have elapsed. After that, no retries will happen and
* the helper will throw the last exception received from the driver.
*
* @see Client::startSession
* @see Session::startTransaction for supported transaction options
*
* @param Session $session A session object as retrieved by Client::startSession
* @param callable $callback A callback that will be invoked within the transaction
* @param array $transactionOptions Additional options that are passed to Session::startTransaction
* @return void
* @throws RuntimeException for driver errors while committing the transaction
* @throws Exception for any other errors, including those thrown in the callback
*/
function with_transaction(Session $session, callable $callback, array $transactionOptions = [])
{
$operation = new WithTransaction($callback, $transactionOptions);
$operation->execute($session);
}
......@@ -5,6 +5,7 @@ namespace MongoDB\Tests\SpecTests;
use Exception;
use MongoDB\Driver\Exception\BulkWriteException;
use MongoDB\Driver\Exception\CommandException;
use MongoDB\Driver\Exception\ExecutionTimeoutException;
use MongoDB\Driver\Exception\RuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Tests\TestCase;
......@@ -26,9 +27,10 @@ final class ErrorExpectation
*/
private static $codeNameMap = [
'Interrupted' => 11601,
'WriteConflict' => 112,
'MaxTimeMSExpired' => 50,
'NoSuchTransaction' => 251,
'OperationNotSupportedInTransaction' => 263,
'WriteConflict' => 112,
];
/** @var integer */
......@@ -198,7 +200,7 @@ final class ErrorExpectation
* around this be comparing the error code against a map.
*
* TODO: Remove this once PHPC-1386 is resolved. */
if ($actual instanceof BulkWriteException) {
if ($actual instanceof BulkWriteException || $actual instanceof ExecutionTimeoutException) {
$test->assertArrayHasKey($this->codeName, self::$codeNameMap);
$test->assertSame(self::$codeNameMap[$this->codeName], $actual->getCode());
......@@ -207,6 +209,14 @@ final class ErrorExpectation
$test->assertInstanceOf(CommandException::class, $actual);
$result = $actual->getResultDocument();
if (isset($result->writeConcernError)) {
$test->assertObjectHasAttribute('codeName', $result->writeConcernError);
$test->assertSame($this->codeName, $result->writeConcernError->codeName);
return;
}
$test->assertObjectHasAttribute('codeName', $result);
$test->assertSame($this->codeName, $result->codeName);
}
......
......@@ -20,6 +20,7 @@ use function array_map;
use function fclose;
use function fopen;
use function MongoDB\is_last_pipeline_operator_write;
use function MongoDB\with_transaction;
use function stream_get_contents;
use function strtolower;
......@@ -127,6 +128,29 @@ final class Operation
return $o;
}
/**
* This method is exclusively used to prepare nested operations for the
* withTransaction session operation
*
* @return Operation
*/
private static function fromConvenientTransactions(stdClass $operation)
{
$o = new self($operation);
if (isset($operation->error)) {
$o->errorExpectation = ErrorExpectation::fromTransactions($operation);
}
$o->resultExpectation = ResultExpectation::fromTransactions($operation, $o->getResultAssertionType());
if (isset($operation->collectionOptions)) {
$o->collectionOptions = (array) $operation->collectionOptions;
}
return $o;
}
public static function fromCrud(stdClass $operation)
{
$o = new self($operation);
......@@ -179,14 +203,15 @@ final class Operation
*
* @param FunctionalTestCase $test Test instance
* @param Context $context Execution context
* @param bool $bubbleExceptions If true, any exception that was caught is rethrown
*/
public function assert(FunctionalTestCase $test, Context $context)
public function assert(FunctionalTestCase $test, Context $context, $bubbleExceptions = false)
{
$result = null;
$exception = null;
try {
$result = $this->execute($context);
$result = $this->execute($test, $context);
/* Eagerly iterate the results of a cursor. This both allows an
* exception to be thrown sooner and ensures that any expected
......@@ -211,6 +236,10 @@ final class Operation
if (isset($this->resultExpectation)) {
$this->resultExpectation->assert($test, $result);
}
if ($exception && $bubbleExceptions) {
throw $exception;
}
}
/**
......@@ -220,7 +249,7 @@ final class Operation
* @return mixed
* @throws LogicException if the operation is unsupported
*/
private function execute(Context $context)
private function execute(FunctionalTestCase $test, Context $context)
{
switch ($this->object) {
case self::OBJECT_CLIENT:
......@@ -248,9 +277,9 @@ final class Operation
return $this->executeForDatabase($database, $context);
case self::OBJECT_SESSION0:
return $this->executeForSession($context->session0, $context);
return $this->executeForSession($context->session0, $test, $context);
case self::OBJECT_SESSION1:
return $this->executeForSession($context->session1, $context);
return $this->executeForSession($context->session1, $test, $context);
default:
throw new LogicException('Unsupported object: ' . $this->object);
}
......@@ -480,11 +509,12 @@ final class Operation
* Executes the session operation and return its result.
*
* @param Session $session
* @param FunctionalTestCase $test
* @param Context $context Execution context
* @return mixed
* @throws LogicException if the session operation is unsupported
*/
private function executeForSession(Session $session, Context $context)
private function executeForSession(Session $session, FunctionalTestCase $test, Context $context)
{
switch ($this->name) {
case 'abortTransaction':
......@@ -495,6 +525,21 @@ final class Operation
$options = isset($this->arguments['options']) ? (array) $this->arguments['options'] : [];
return $session->startTransaction($context->prepareOptions($options));
case 'withTransaction':
/** @var self[] $callbackOperations */
$callbackOperations = array_map(function ($operation) {
return self::fromConvenientTransactions($operation);
}, $this->arguments['callback']->operations);
$callback = function () use ($callbackOperations, $test, $context) {
foreach ($callbackOperations as $operation) {
$operation->assert($test, $context, true);
}
};
$options = isset($this->arguments['options']) ? (array) $this->arguments['options'] : [];
return with_transaction($session, $callback, $context->prepareOptions($options));
default:
throw new LogicException('Unsupported session operation: ' . $this->name);
}
......
......@@ -12,6 +12,7 @@ use MongoDB\Driver\Server;
use stdClass;
use Symfony\Bridge\PhpUnit\SetUpTearDownTrait;
use function basename;
use function dirname;
use function file_get_contents;
use function get_object_vars;
use function glob;
......@@ -34,22 +35,17 @@ class TransactionsSpecTest extends FunctionalTestCase
* @var array
*/
private static $incompleteTests = [
'error-labels: add unknown commit label to MaxTimeMSExpired' => 'PHPC-1382',
'error-labels: add unknown commit label to writeConcernError MaxTimeMSExpired' => 'PHPC-1382',
'read-pref: default readPreference' => 'PHPLIB does not properly inherit readPreference for transactions',
'read-pref: primary readPreference' => 'PHPLIB does not properly inherit readPreference for transactions',
'run-command: run command with secondary read preference in client option and primary read preference in transaction options' => 'PHPLIB does not properly inherit readPreference for transactions',
'transaction-options: transaction options inherited from defaultTransactionOptions' => 'PHPC-1382',
'transaction-options: startTransaction options override defaults' => 'PHPC-1382',
'transaction-options: defaultTransactionOptions override client options' => 'PHPC-1382',
'transaction-options: transaction options inherited from client' => 'PHPLIB does not properly inherit readConcern for transactions',
'transaction-options: readConcern local in defaultTransactionOptions' => 'PHPLIB does not properly inherit readConcern for transactions',
'transaction-options: readConcern snapshot in startTransaction options' => 'PHPLIB does not properly inherit readConcern for transactions',
'transactions/read-pref: default readPreference' => 'PHPLIB does not properly inherit readPreference for transactions (PHPLIB-473)',
'transactions/read-pref: primary readPreference' => 'PHPLIB does not properly inherit readPreference for transactions (PHPLIB-473)',
'transactions/run-command: run command with secondary read preference in client option and primary read preference in transaction options' => 'PHPLIB does not properly inherit readPreference for transactions (PHPLIB-473)',
'transactions/transaction-options: transaction options inherited from client' => 'PHPLIB does not properly inherit readConcern for transactions (PHPLIB-473)',
'transactions/transaction-options: readConcern local in defaultTransactionOptions' => 'PHPLIB does not properly inherit readConcern for transactions (PHPLIB-473)',
'transactions/transaction-options: readConcern snapshot in startTransaction options' => 'PHPLIB does not properly inherit readConcern for transactions (PHPLIB-473)',
];
private static function doSetUpBeforeClass()
private function doSetUp()
{
parent::setUpBeforeClass();
parent::setUp();
static::killAllSessions();
}
......@@ -189,9 +185,9 @@ class TransactionsSpecTest extends FunctionalTestCase
{
$testArgs = [];
foreach (glob(__DIR__ . '/transactions/*.json') as $filename) {
foreach (glob(__DIR__ . '/transactions*/*.json') as $filename) {
$json = $this->decodeJson(file_get_contents($filename));
$group = basename($filename, '.json');
$group = basename(dirname($filename)) . '/' . basename($filename, '.json');
$runOn = isset($json->runOn) ? $json->runOn : null;
$data = isset($json->data) ? $json->data : [];
$databaseName = isset($json->database_name) ? $json->database_name : null;
......
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.8",
"topology": [
"sharded"
]
}
],
"database_name": "withTransaction-tests",
"collection_name": "test",
"data": [],
"tests": [
{
"description": "withTransaction succeeds if callback aborts",
"useMultipleMongoses": true,
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"insertedId": 1
}
},
{
"name": "abortTransaction",
"object": "session0"
}
]
}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"readConcern": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"abortTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "abortTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": []
}
}
},
{
"description": "withTransaction succeeds if callback aborts with no ops",
"useMultipleMongoses": true,
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "abortTransaction",
"object": "session0"
}
]
}
}
}
],
"expectations": [],
"outcome": {
"collection": {
"data": []
}
}
},
{
"description": "withTransaction still succeeds if callback aborts and runs extra op",
"useMultipleMongoses": true,
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"insertedId": 1
}
},
{
"name": "abortTransaction",
"object": "session0"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 2
}
},
"result": {
"insertedId": 2
}
}
]
}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"readConcern": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"abortTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "abortTransaction",
"database_name": "admin"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 2
}
],
"ordered": true,
"lsid": "session0",
"autocommit": null,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 2
}
]
}
}
}
]
}
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.8",
"topology": [
"sharded"
]
}
],
"database_name": "withTransaction-tests",
"collection_name": "test",
"data": [],
"tests": [
{
"description": "withTransaction succeeds if callback commits",
"useMultipleMongoses": true,
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"insertedId": 1
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 2
}
},
"result": {
"insertedId": 2
}
},
{
"name": "commitTransaction",
"object": "session0"
}
]
}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"readConcern": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 2
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1
},
{
"_id": 2
}
]
}
}
},
{
"description": "withTransaction still succeeds if callback commits and runs extra op",
"useMultipleMongoses": true,
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"insertedId": 1
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 2
}
},
"result": {
"insertedId": 2
}
},
{
"name": "commitTransaction",
"object": "session0"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 3
}
},
"result": {
"insertedId": 3
}
}
]
}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"readConcern": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 2
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 3
}
],
"ordered": true,
"lsid": "session0",
"autocommit": null,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1
},
{
"_id": 2
},
{
"_id": 3
}
]
}
}
}
]
}
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.8",
"topology": [
"sharded"
]
}
],
"database_name": "withTransaction-tests",
"collection_name": "test",
"data": [],
"tests": [
{
"description": "callback succeeds after multiple connection errors",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"insert"
],
"closeConnection": true
}
},
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
}
}
]
}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"readConcern": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"abortTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "abortTransaction",
"database_name": "admin"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"readConcern": {
"afterClusterTime": 42
},
"txnNumber": {
"$numberLong": "2"
},
"startTransaction": true,
"autocommit": false,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"abortTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "2"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "abortTransaction",
"database_name": "admin"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"readConcern": {
"afterClusterTime": 42
},
"txnNumber": {
"$numberLong": "3"
},
"startTransaction": true,
"autocommit": false,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "3"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1
}
]
}
}
},
{
"description": "callback is not retried after non-transient error (DuplicateKeyError)",
"useMultipleMongoses": true,
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"insertedId": 1
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"errorLabelsOmit": [
"TransientTransactionError",
"UnknownTransactionCommitResult"
]
}
}
]
}
},
"result": {
"errorLabelsOmit": [
"TransientTransactionError",
"UnknownTransactionCommitResult"
]
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"readConcern": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"abortTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "abortTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": []
}
}
}
]
}
This diff is collapsed.
{
"runOn": [
{
"minServerVersion": "4.1.6",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.8",
"topology": [
"sharded"
]
}
],
"database_name": "withTransaction-tests",
"collection_name": "test",
"data": [],
"tests": [
{
"description": "transaction is retried after commitTransaction TransientTransactionError (PreparedTransactionInProgress)",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 2
},
"data": {
"failCommands": [
"commitTransaction"
],
"errorCode": 267,
"closeConnection": false
}
},
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"insertedId": 1
}
}
]
}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"readConcern": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"readConcern": {
"afterClusterTime": 42
},
"txnNumber": {
"$numberLong": "2"
},
"startTransaction": true,
"autocommit": false,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "2"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"readConcern": {
"afterClusterTime": 42
},
"txnNumber": {
"$numberLong": "3"
},
"startTransaction": true,
"autocommit": false,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "3"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1
}
]
}
}
}
]
}
{
"runOn": [
{
"minServerVersion": "4.0",
"topology": [
"replicaset"
]
},
{
"minServerVersion": "4.1.8",
"topology": [
"sharded"
]
}
],
"database_name": "withTransaction-tests",
"collection_name": "test",
"data": [],
"tests": [
{
"description": "withTransaction commits after callback returns",
"useMultipleMongoses": true,
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"insertedId": 1
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 2
}
},
"result": {
"insertedId": 2
}
}
]
}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"readConcern": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 2
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1
},
{
"_id": 2
}
]
}
}
},
{
"description": "withTransaction commits after callback returns (second transaction)",
"useMultipleMongoses": true,
"operations": [
{
"name": "withTransaction",
"object": "session0",
"arguments": {
"callback": {
"operations": [
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 1
}
},
"result": {
"insertedId": 1
}
},
{
"name": "commitTransaction",
"object": "session0"
},
{
"name": "startTransaction",
"object": "session0"
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"session": "session0",
"document": {
"_id": 2
}
},
"result": {
"insertedId": 2
}
}
]
}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 1
}
],
"ordered": true,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"startTransaction": true,
"autocommit": false,
"readConcern": null,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "1"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
},
{
"command_started_event": {
"command": {
"insert": "test",
"documents": [
{
"_id": 2
}
],
"ordered": true,
"lsid": "session0",
"readConcern": {
"afterClusterTime": 42
},
"txnNumber": {
"$numberLong": "2"
},
"startTransaction": true,
"autocommit": false,
"writeConcern": null
},
"command_name": "insert",
"database_name": "withTransaction-tests"
}
},
{
"command_started_event": {
"command": {
"commitTransaction": 1,
"lsid": "session0",
"txnNumber": {
"$numberLong": "2"
},
"autocommit": false,
"readConcern": null,
"startTransaction": null,
"writeConcern": null
},
"command_name": "commitTransaction",
"database_name": "admin"
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1
},
{
"_id": 2
}
]
}
}
}
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment