Skip to content

Commit 3312932

Browse files
committed
Correctly re-throw exception when callback attempts have been exceeded.
1 parent bb58fcb commit 3312932

File tree

2 files changed

+43
-12
lines changed

2 files changed

+43
-12
lines changed

src/Concerns/ManagesTransactions.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use MongoDB\Client;
77
use MongoDB\Driver\Exception\RuntimeException;
88
use MongoDB\Driver\Session;
9+
use Throwable;
910
use function MongoDB\with_transaction;
1011

1112
/**
@@ -83,8 +84,9 @@ public function transaction(Closure $callback, $attempts = 1, array $options = [
8384
{
8485
$attemptsLeft = $attempts;
8586
$callbackResult = null;
87+
$throwable = null;
8688

87-
$callbackFunction = function (Session $session) use ($callback, &$attemptsLeft, &$callbackResult) {
89+
$callbackFunction = function (Session $session) use ($callback, &$attemptsLeft, &$callbackResult, &$throwable) {
8890
$attemptsLeft--;
8991

9092
if ($attemptsLeft < 0) {
@@ -93,11 +95,22 @@ public function transaction(Closure $callback, $attempts = 1, array $options = [
9395
return;
9496
}
9597

96-
$callbackResult = $callback($this);
98+
// Catch, store and re-throw any exception thrown during execution
99+
// of the callable. The last exception is re-thrown if the transaction
100+
// was aborted because the number of callback attempts has been exceeded.
101+
try {
102+
$callbackResult = $callback($this);
103+
} catch (Throwable $throwable) {
104+
throw $throwable;
105+
}
97106
};
98107

99108
with_transaction($this->getSessionOrCreate(), $callbackFunction, $options);
100109

110+
if ($attemptsLeft < 0 && $throwable) {
111+
throw $throwable;
112+
}
113+
101114
return $callbackResult;
102115
}
103116
}

tests/TransactionTest.php

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use Jenssegers\Mongodb\Connection;
55
use Jenssegers\Mongodb\Eloquent\Model;
66
use MongoDB\BSON\ObjectId;
7+
use MongoDB\Driver\Exception\BulkWriteException;
78
use MongoDB\Driver\Server;
89

910
class TransactionTest extends TestCase
@@ -317,6 +318,8 @@ public function testTransaction(): void
317318
{
318319
User::create(['name' => 'klinson', 'age' => 20, 'title' => 'admin']);
319320

321+
// The $connection parameter may be unused, but is implicitly used to
322+
// test that the closure is executed with the connection as an argument.
320323
DB::transaction(function (Connection $connection): void {
321324
User::create(['name' => 'alcaeus', 'age' => 38, 'title' => 'admin']);
322325
User::where(['name' => 'klinson'])->update(['age' => 21]);
@@ -336,14 +339,18 @@ public function testTransactionRepeatsOnTransientFailure(): void
336339
$timesRun = 0;
337340

338341
DB::transaction(function () use (&$timesRun): void {
339-
User::where(['name' => 'klinson'])->update(['age' => 21]);
342+
$timesRun++;
343+
344+
// Run a query to start the transaction on the server
345+
User::create(['name' => 'alcaeus', 'age' => 38, 'title' => 'admin']);
340346

341-
// Update user during transaction to simulate a simultaneous update
342-
if ($timesRun == 0) {
347+
// Update user outside of the session
348+
if ($timesRun == 1) {
343349
DB::getCollection('users')->updateOne(['name' => 'klinson'], ['$set' => ['age' => 22]]);
344350
}
345351

346-
$timesRun++;
352+
// This update will create a write conflict, aborting the transaction
353+
User::where(['name' => 'klinson'])->update(['age' => 21]);
347354
}, 2);
348355

349356
$this->assertSame(2, $timesRun);
@@ -356,14 +363,25 @@ public function testTransactionRespectsRepetitionLimit(): void
356363

357364
$timesRun = 0;
358365

359-
DB::transaction(function () use (&$timesRun): void {
360-
User::where(['name' => 'klinson'])->update(['age' => 21]);
366+
try {
367+
DB::transaction(function () use (&$timesRun): void {
368+
$timesRun++;
361369

362-
// Update user during transaction to simulate a simultaneous update
363-
DB::getCollection('users')->updateOne(['name' => 'klinson'], ['$inc' => ['age' => 2]]);
370+
// Run a query to start the transaction on the server
371+
User::create(['name' => 'alcaeus', 'age' => 38, 'title' => 'admin']);
364372

365-
$timesRun++;
366-
}, 2);
373+
// Update user outside of the session
374+
DB::getCollection('users')->updateOne(['name' => 'klinson'], ['$inc' => ['age' => 2]]);
375+
376+
// This update will create a write conflict, aborting the transaction
377+
User::where(['name' => 'klinson'])->update(['age' => 21]);
378+
}, 2);
379+
380+
$this->fail('Expected exception during transaction');
381+
} catch (BulkWriteException $e) {
382+
$this->assertInstanceOf(BulkWriteException::class, $e);
383+
$this->assertStringContainsString('WriteConflict', $e->getMessage());
384+
}
367385

368386
$this->assertSame(2, $timesRun);
369387

0 commit comments

Comments
 (0)