@@ -20,7 +20,7 @@ import type {
20
20
} from './native/types' ;
21
21
import Logger from '@matrixai/logger' ;
22
22
import { CreateDestroy , ready } from '@matrixai/async-init/dist/CreateDestroy' ;
23
- import { RWLockWriter } from '@matrixai/async-locks' ;
23
+ import { Lock , RWLockWriter } from '@matrixai/async-locks' ;
24
24
import DBIterator from './DBIterator' ;
25
25
import { rocksdbP } from './native' ;
26
26
import * as utils from './utils' ;
@@ -49,8 +49,11 @@ class DBTransaction {
49
49
protected _callbacksSuccess : Array < ( ) => any > = [ ] ;
50
50
protected _callbacksFailure : Array < ( e ?: Error ) => any > = [ ] ;
51
51
protected _callbacksFinally : Array < ( e ?: Error ) => any > = [ ] ;
52
+ protected _committing : boolean = false ;
52
53
protected _committed : boolean = false ;
54
+ protected _rollbacking : boolean = false ;
53
55
protected _rollbacked : boolean = false ;
56
+ protected commitOrRollbackLock : Lock = new Lock ( ) ;
54
57
55
58
public constructor ( {
56
59
db,
@@ -86,9 +89,12 @@ class DBTransaction {
86
89
*/
87
90
public async destroy ( ) {
88
91
this . logger . debug ( `Destroying ${ this . constructor . name } ${ this . id } ` ) ;
89
- if ( ! this . _committed && ! this . _rollbacked ) {
92
+ if ( ! this . _committing && ! this . _rollbacking ) {
90
93
throw new errors . ErrorDBTransactionNotCommittedNorRollbacked ( ) ;
91
94
}
95
+ // Wait for commit or rollback to finish
96
+ // this then allows the destruction to proceed
97
+ await this . commitOrRollbackLock . waitForUnlock ( ) ;
92
98
this . _db . transactionRefs . delete ( this ) ;
93
99
// Unlock all locked keys in reverse
94
100
const lockedKeys = [ ...this . _locks . keys ( ) ] . reverse ( ) ;
@@ -116,10 +122,30 @@ class DBTransaction {
116
122
return this . _callbacksFinally ;
117
123
}
118
124
125
+ /**
126
+ * Indicates when `this.commit` is first called
127
+ */
128
+ get committing ( ) : boolean {
129
+ return this . _committing ;
130
+ }
131
+
132
+ /**
133
+ * Indicates when the transaction is committed
134
+ */
119
135
get committed ( ) : boolean {
120
136
return this . _committed ;
121
137
}
122
138
139
+ /**
140
+ * Indicates when `this.rollback` is first called
141
+ */
142
+ get rollbacking ( ) : boolean {
143
+ return this . _rollbacking ;
144
+ }
145
+
146
+ /**
147
+ * Indicates when the transaction is rollbacked
148
+ */
123
149
get rollbacked ( ) : boolean {
124
150
return this . _rollbacked ;
125
151
}
@@ -437,75 +463,79 @@ class DBTransaction {
437
463
438
464
@ready ( new errors . ErrorDBTransactionDestroyed ( ) )
439
465
public async commit ( ) : Promise < void > {
440
- if ( this . _rollbacked ) {
466
+ if ( this . _rollbacking ) {
441
467
throw new errors . ErrorDBTransactionRollbacked ( ) ;
442
468
}
443
- if ( this . _committed ) {
469
+ if ( this . _committing ) {
444
470
return ;
445
471
}
472
+ this . _committing = true ;
446
473
this . logger . debug ( `Committing ${ this . constructor . name } ${ this . id } ` ) ;
447
- for ( const iterator of this . _iteratorRefs ) {
448
- await iterator . destroy ( ) ;
449
- }
450
- this . _committed = true ;
451
- try {
474
+ await this . commitOrRollbackLock . withF ( async ( ) => {
475
+ for ( const iterator of this . _iteratorRefs ) {
476
+ await iterator . destroy ( ) ;
477
+ }
452
478
try {
453
- // If this fails, the `DBTransaction` is still considered committed
454
- // it must be destroyed, it cannot be reused
455
- await rocksdbP . transactionCommit ( this . _transaction ) ;
456
- } catch ( e ) {
457
- if ( e . code === 'TRANSACTION_CONFLICT' ) {
458
- this . logger . debug (
459
- `Failed Committing ${ this . constructor . name } ${ this . id } due to ${ errors . ErrorDBTransactionConflict . name } ` ,
460
- ) ;
461
- throw new errors . ErrorDBTransactionConflict ( undefined , {
462
- cause : e ,
463
- } ) ;
464
- } else {
465
- this . logger . debug (
466
- `Failed Committing ${ this . constructor . name } ${ this . id } due to ${ e . message } ` ,
467
- ) ;
468
- throw e ;
479
+ try {
480
+ // If this fails, the `DBTransaction` is still considered committed
481
+ // it must be destroyed, it cannot be reused
482
+ await rocksdbP . transactionCommit ( this . _transaction ) ;
483
+ } catch ( e ) {
484
+ if ( e . code === 'TRANSACTION_CONFLICT' ) {
485
+ this . logger . debug (
486
+ `Failed Committing ${ this . constructor . name } ${ this . id } due to ${ errors . ErrorDBTransactionConflict . name } ` ,
487
+ ) ;
488
+ throw new errors . ErrorDBTransactionConflict ( undefined , {
489
+ cause : e ,
490
+ } ) ;
491
+ } else {
492
+ this . logger . debug (
493
+ `Failed Committing ${ this . constructor . name } ${ this . id } due to ${ e . message } ` ,
494
+ ) ;
495
+ throw e ;
496
+ }
497
+ }
498
+ for ( const f of this . _callbacksSuccess ) {
499
+ await f ( ) ;
500
+ }
501
+ } finally {
502
+ for ( const f of this . _callbacksFinally ) {
503
+ await f ( ) ;
469
504
}
470
505
}
471
- for ( const f of this . _callbacksSuccess ) {
472
- await f ( ) ;
473
- }
474
- } finally {
475
- for ( const f of this . _callbacksFinally ) {
476
- await f ( ) ;
477
- }
478
- }
479
- await this . destroy ( ) ;
506
+ this . _committed = true ;
507
+ } ) ;
480
508
this . logger . debug ( `Committed ${ this . constructor . name } ${ this . id } ` ) ;
481
509
}
482
510
483
511
@ready ( new errors . ErrorDBTransactionDestroyed ( ) )
484
512
public async rollback ( e ?: Error ) : Promise < void > {
485
- if ( this . _committed ) {
513
+ if ( this . _committing ) {
486
514
throw new errors . ErrorDBTransactionCommitted ( ) ;
487
515
}
488
- if ( this . _rollbacked ) {
516
+ if ( this . _rollbacking ) {
489
517
return ;
490
518
}
519
+ this . _rollbacking = true ;
491
520
this . logger . debug ( `Rollbacking ${ this . constructor . name } ${ this . id } ` ) ;
492
- for ( const iterator of this . _iteratorRefs ) {
493
- await iterator . destroy ( ) ;
494
- }
495
- this . _rollbacked = true ;
496
- try {
497
- // If this fails, the `DBTransaction` is still considered rollbacked
498
- // it must be destroyed, it cannot be reused
499
- await rocksdbP . transactionRollback ( this . _transaction ) ;
500
- for ( const f of this . _callbacksFailure ) {
501
- await f ( e ) ;
521
+ await this . commitOrRollbackLock . withF ( async ( ) => {
522
+ for ( const iterator of this . _iteratorRefs ) {
523
+ await iterator . destroy ( ) ;
502
524
}
503
- } finally {
504
- for ( const f of this . _callbacksFinally ) {
505
- await f ( e ) ;
525
+ try {
526
+ // If this fails, the `DBTransaction` is still considered rollbacked
527
+ // it must be destroyed, it cannot be reused
528
+ await rocksdbP . transactionRollback ( this . _transaction ) ;
529
+ for ( const f of this . _callbacksFailure ) {
530
+ await f ( e ) ;
531
+ }
532
+ } finally {
533
+ for ( const f of this . _callbacksFinally ) {
534
+ await f ( e ) ;
535
+ }
506
536
}
507
- }
508
- await this . destroy ( ) ;
537
+ this . _rollbacked = true ;
538
+ } ) ;
509
539
this . logger . debug ( `Rollbacked ${ this . constructor . name } ${ this . id } ` ) ;
510
540
}
511
541
0 commit comments