1
1
import Logger , { ILogger } from 'js-logger' ;
2
2
3
- import { SyncPriorityStatus , SyncStatus , SyncStatusOptions } from '../../../db/crud/SyncStatus.js' ;
3
+ import { SyncStatus , SyncStatusOptions } from '../../../db/crud/SyncStatus.js' ;
4
4
import { AbortOperation } from '../../../utils/AbortOperation.js' ;
5
5
import { BaseListener , BaseObserver , Disposable } from '../../../utils/BaseObserver.js' ;
6
- import { throttleLeadingTrailing } from '../../../utils/throttle .js' ;
6
+ import { onAbortPromise , throttleLeadingTrailing } from '../../../utils/async .js' ;
7
7
import { BucketChecksum , BucketDescription , BucketStorageAdapter , Checkpoint } from '../bucket/BucketStorageAdapter.js' ;
8
8
import { CrudEntry } from '../bucket/CrudEntry.js' ;
9
9
import { SyncDataBucket } from '../bucket/SyncDataBucket.js' ;
@@ -161,6 +161,7 @@ export abstract class AbstractStreamingSyncImplementation
161
161
protected abortController : AbortController | null ;
162
162
protected crudUpdateListener ?: ( ) => void ;
163
163
protected streamingSyncPromise ?: Promise < void > ;
164
+ private pendingCrudUpload ?: Promise < void > ;
164
165
165
166
syncStatus : SyncStatus ;
166
167
triggerCrudUpload : ( ) => void ;
@@ -181,10 +182,16 @@ export abstract class AbstractStreamingSyncImplementation
181
182
this . abortController = null ;
182
183
183
184
this . triggerCrudUpload = throttleLeadingTrailing ( ( ) => {
184
- if ( ! this . syncStatus . connected || this . syncStatus . dataFlowStatus . uploading ) {
185
+ if ( ! this . syncStatus . connected || this . pendingCrudUpload != null ) {
185
186
return ;
186
187
}
187
- this . _uploadAllCrud ( ) ;
188
+
189
+ this . pendingCrudUpload = new Promise ( ( resolve ) => {
190
+ this . _uploadAllCrud ( ) . finally ( ( ) => {
191
+ this . pendingCrudUpload = undefined ;
192
+ resolve ( ) ;
193
+ } ) ;
194
+ } ) ;
188
195
} , this . options . crudUploadThrottleMs ! ) ;
189
196
}
190
197
@@ -582,30 +589,12 @@ The next upload iteration will be delayed.`);
582
589
await this . options . adapter . removeBuckets ( [ ...bucketsToDelete ] ) ;
583
590
await this . options . adapter . setTargetCheckpoint ( targetCheckpoint ) ;
584
591
} else if ( isStreamingSyncCheckpointComplete ( line ) ) {
585
- this . logger . debug ( 'Checkpoint complete' , targetCheckpoint ) ;
586
- const result = await this . options . adapter . syncLocalDatabase ( targetCheckpoint ! ) ;
587
- if ( ! result . checkpointValid ) {
588
- // This means checksums failed. Start again with a new checkpoint.
589
- // TODO: better back-off
590
- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
592
+ const result = await this . applyCheckpoint ( targetCheckpoint ! , signal ) ;
593
+ if ( result . endIteration ) {
591
594
return { retry : true } ;
592
- } else if ( ! result . ready ) {
593
- // Checksums valid, but need more data for a consistent checkpoint.
594
- // Continue waiting.
595
- // landing here the whole time
596
- } else {
595
+ } else if ( result . applied ) {
597
596
appliedCheckpoint = targetCheckpoint ;
598
- this . logger . debug ( 'validated checkpoint' , appliedCheckpoint ) ;
599
- this . updateSyncStatus ( {
600
- connected : true ,
601
- lastSyncedAt : new Date ( ) ,
602
- dataFlow : {
603
- downloading : false ,
604
- downloadError : undefined
605
- }
606
- } ) ;
607
597
}
608
-
609
598
validatedCheckpoint = targetCheckpoint ;
610
599
} else if ( isStreamingSyncCheckpointPartiallyComplete ( line ) ) {
611
600
const priority = line . partial_checkpoint_complete . priority ;
@@ -617,7 +606,8 @@ The next upload iteration will be delayed.`);
617
606
await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
618
607
return { retry : true } ;
619
608
} else if ( ! result . ready ) {
620
- // Need more data for a consistent partial sync within a priority - continue waiting.
609
+ // If we have pending uploads, we can't complete new checkpoints outside of priority 0.
610
+ // We'll resolve this for a complete checkpoint.
621
611
} else {
622
612
// We'll keep on downloading, but can report that this priority is synced now.
623
613
this . logger . debug ( 'partial checkpoint validation succeeded' ) ;
@@ -707,26 +697,13 @@ The next upload iteration will be delayed.`);
707
697
}
708
698
} ) ;
709
699
} else if ( validatedCheckpoint === targetCheckpoint ) {
710
- const result = await this . options . adapter . syncLocalDatabase ( targetCheckpoint ! ) ;
711
- if ( ! result . checkpointValid ) {
712
- // This means checksums failed. Start again with a new checkpoint.
713
- // TODO: better back-off
714
- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
700
+ const result = await this . applyCheckpoint ( targetCheckpoint ! , signal ) ;
701
+ if ( result . endIteration ) {
702
+ // TODO: Why is this one retry: false? That's the only change from when we receive
703
+ // the line above?
715
704
return { retry : false } ;
716
- } else if ( ! result . ready ) {
717
- // Checksums valid, but need more data for a consistent checkpoint.
718
- // Continue waiting.
719
- } else {
705
+ } else if ( result . applied ) {
720
706
appliedCheckpoint = targetCheckpoint ;
721
- this . updateSyncStatus ( {
722
- connected : true ,
723
- lastSyncedAt : new Date ( ) ,
724
- priorityStatusEntries : [ ] ,
725
- dataFlow : {
726
- downloading : false ,
727
- downloadError : undefined
728
- }
729
- } ) ;
730
707
}
731
708
}
732
709
}
@@ -738,6 +715,48 @@ The next upload iteration will be delayed.`);
738
715
} ) ;
739
716
}
740
717
718
+ private async applyCheckpoint ( checkpoint : Checkpoint , abort : AbortSignal ) {
719
+ let result = await this . options . adapter . syncLocalDatabase ( checkpoint ) ;
720
+ if ( ! result . checkpointValid ) {
721
+ this . logger . debug ( 'Checksum mismatch in checkpoint, will reconnect' ) ;
722
+ // This means checksums failed. Start again with a new checkpoint.
723
+ // TODO: better back-off
724
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
725
+ return { applied : false , endIteration : true } ;
726
+ } else if ( ! result . ready ) {
727
+ // We have pending entries in the local upload queue or are waiting to confirm a write
728
+ // checkpoint. See if that is happening right now.
729
+ const pending = this . pendingCrudUpload ;
730
+ if ( pending != null ) {
731
+ await Promise . race ( [ pending , onAbortPromise ( abort ) ] ) ;
732
+ }
733
+
734
+ if ( abort . aborted || pending == null ) {
735
+ return { applied : false , endIteration : true } ;
736
+ }
737
+
738
+ // Try again now that uploads have completed.
739
+ result = await this . options . adapter . syncLocalDatabase ( checkpoint ) ;
740
+ }
741
+
742
+ if ( result . checkpointValid && result . ready ) {
743
+ this . logger . debug ( 'validated checkpoint' , checkpoint ) ;
744
+ this . updateSyncStatus ( {
745
+ connected : true ,
746
+ lastSyncedAt : new Date ( ) ,
747
+ dataFlow : {
748
+ downloading : false ,
749
+ downloadError : undefined
750
+ }
751
+ } ) ;
752
+
753
+ return { applied : true , endIteration : false } ;
754
+ } else {
755
+ this . logger . debug ( 'Could not apply checkpoint even after waiting for uploads. Waiting for next sync complete line.' ) ;
756
+ return { applied : false , endIteration : false } ;
757
+ }
758
+ }
759
+
741
760
protected updateSyncStatus ( options : SyncStatusOptions ) {
742
761
const updatedStatus = new SyncStatus ( {
743
762
connected : options . connected ?? this . syncStatus . connected ,
0 commit comments