@@ -415,6 +415,39 @@ public function testWriteAdmissionRetriesBeforeEnteringBody(): void
415415 $ this ->assertSame (1 , (int ) $ this ->connection ->selectOne ('SELECT value FROM state WHERE id=1 ' )->value );
416416 }
417417
418+ public function testWriteAdmissionCanRecoverAfterThreeProvenRootRollbacks (): void
419+ {
420+ $ writer = $ this ->writer ();
421+ $ writer ->beginTransaction ();
422+ $ writer ->exec ('UPDATE state SET value=1 WHERE id=2 ' );
423+ $ rollbacks = 0 ;
424+ $ this ->events ->listen (TransactionRolledBack::class, function () use ($ writer , &$ rollbacks ): void {
425+ $ rollbacks ++;
426+ $ this ->assertSame (0 , $ this ->connection ->transactionLevel ());
427+ $ this ->assertFalse ($ this ->connection ->getPdo ()->inTransaction ());
428+ if ($ rollbacks === 3 ) {
429+ $ writer ->commit ();
430+ }
431+ });
432+ $ calls = 0 ;
433+ try {
434+ SqliteTransaction::run (function () use (&$ calls ): void {
435+ $ calls ++;
436+ $ this ->connection ->update ('UPDATE state SET value=value+1 WHERE id=1 ' );
437+ }, admission: function (): void {
438+ $ this ->connection ->update ('UPDATE state SET id=id WHERE id=1 ' );
439+ });
440+ } finally {
441+ if ($ writer ->inTransaction ()) {
442+ $ writer ->rollBack ();
443+ }
444+ }
445+ $ this ->assertSame (3 , $ rollbacks );
446+ $ this ->assertSame (1 , $ calls );
447+ $ this ->assertSame (1 , (int ) $ this ->connection ->selectOne ('SELECT value FROM state WHERE id=1 ' )->value );
448+ $ this ->assertSame (1 , (int ) $ this ->connection ->selectOne ('SELECT value FROM state WHERE id=2 ' )->value );
449+ }
450+
418451 public function testExhaustedAdmissionNeverEntersBody (): void
419452 {
420453 $ writer = $ this ->writer ();
@@ -425,7 +458,7 @@ public function testExhaustedAdmissionNeverEntersBody(): void
425458 try {
426459 SqliteTransaction::run (static function () use (&$ calls ): void {
427460 $ calls ++;
428- }, admission: function () use (&$ admissions ): void {
461+ }, 3 , admission: function () use (&$ admissions ): void {
429462 $ admissions ++;
430463 $ this ->connection ->update ('UPDATE state SET id=id WHERE id=1 ' );
431464 });
@@ -441,6 +474,33 @@ public function testExhaustedAdmissionNeverEntersBody(): void
441474 $ this ->assertFalse ($ this ->connection ->getPdo ()->inTransaction ());
442475 }
443476
477+ public function testDefaultAdmissionRemainsBoundedWithoutExecutingTheBody (): void
478+ {
479+ $ writer = $ this ->writer ();
480+ $ writer ->beginTransaction ();
481+ $ writer ->exec ('UPDATE state SET value=1 WHERE id=2 ' );
482+ $ admissions = 0 ;
483+ $ calls = 0 ;
484+ try {
485+ SqliteTransaction::run (static function () use (&$ calls ): void {
486+ $ calls ++;
487+ }, admission: function () use (&$ admissions ): void {
488+ $ admissions ++;
489+ $ this ->connection ->update ('UPDATE state SET id=id WHERE id=1 ' );
490+ });
491+ $ this ->fail ('Persistent contention must keep its original exception. ' );
492+ } catch (PDOException $ exception ) {
493+ $ this ->assertSame (5 , $ exception ->errorInfo [1 ] & 255 );
494+ } finally {
495+ $ writer ->rollBack ();
496+ }
497+ $ this ->assertGreaterThan (3 , $ admissions );
498+ $ this ->assertLessThanOrEqual (64 , $ admissions );
499+ $ this ->assertSame (0 , $ calls );
500+ $ this ->assertSame (0 , $ this ->connection ->transactionLevel ());
501+ $ this ->assertFalse ($ this ->connection ->getPdo ()->inTransaction ());
502+ }
503+
444504 public function testAdmittedBodyDoesNotRetryItsOwnContention (): void
445505 {
446506 $ writer = $ this ->writer ();
0 commit comments