@@ -97,8 +97,6 @@ @interface FSTRemoteStore () <FSTWatchStreamDelegate, FSTWriteStreamDelegate>
97
97
* is reached the targetId is removed from the map to free the memory).
98
98
*/
99
99
100
- @property (nonatomic , assign ) FSTBatchID lastBatchSeen;
101
-
102
100
@property (nonatomic , strong , readonly ) FSTOnlineStateTracker *onlineStateTracker;
103
101
104
102
@property (nonatomic , strong , nullable ) FSTWatchChangeAggregator *watchChangeAggregator;
@@ -109,11 +107,20 @@ @interface FSTRemoteStore () <FSTWatchStreamDelegate, FSTWriteStreamDelegate>
109
107
@property (nonatomic , strong , nullable ) FSTWriteStream *writeStream;
110
108
111
109
/* *
112
- * A FIFO queue of in-flight writes. This is in-flight from the point of view of the caller of
113
- * writeMutations, not from the point of view from the Datastore itself. In particular, these
114
- * requests may not have been sent to the Datastore server if the write stream is not yet running.
110
+ * A list of up to kMaxPendingWrites writes that we have fetched from the LocalStore via
111
+ * fillWritePipeline and have or will send to the write stream.
112
+ *
113
+ * Whenever writePipeline is not empty the RemoteStore will attempt to start or restart the write
114
+ * stream. When the stream is established the writes in the pipeline will be sent in order.
115
+ *
116
+ * Writes remain in writePipeline until they are acknowledged by the backend and thus will
117
+ * automatically be re-sent if the stream is interrupted / restarted before they're acknowledged.
118
+ *
119
+ * Write responses from the backend are linked to their originating request purely based on
120
+ * order, and so we can just remove writes from the front of the writePipeline as we receive
121
+ * responses.
115
122
*/
116
- @property (nonatomic , strong , readonly ) NSMutableArray <FSTMutationBatch *> *pendingWrites ;
123
+ @property (nonatomic , strong , readonly ) NSMutableArray <FSTMutationBatch *> *writePipeline ;
117
124
@end
118
125
119
126
@implementation FSTRemoteStore
@@ -126,8 +133,7 @@ - (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
126
133
_datastore = datastore;
127
134
_listenTargets = [NSMutableDictionary dictionary ];
128
135
129
- _lastBatchSeen = kFSTBatchIDUnknown ;
130
- _pendingWrites = [NSMutableArray array ];
136
+ _writePipeline = [NSMutableArray array ];
131
137
_onlineStateTracker = [[FSTOnlineStateTracker alloc ] initWithWorkerDispatchQueue: queue];
132
138
}
133
139
return self;
@@ -192,7 +198,12 @@ - (void)disableNetworkInternal {
192
198
[self .writeStream stop ];
193
199
194
200
[self cleanUpWatchStreamState ];
195
- [self cleanUpWriteStreamState ];
201
+
202
+ if (self.writePipeline .count > 0 ) {
203
+ LOG_DEBUG (" Stopping write stream with %lu pending writes" ,
204
+ (unsigned long )self.writePipeline .count );
205
+ [self .writePipeline removeAllObjects ];
206
+ }
196
207
197
208
self.writeStream = nil ;
198
209
self.watchStream = nil ;
@@ -432,7 +443,7 @@ - (nullable FSTQueryData *)queryDataForTarget:(FSTBoxedTargetID *)targetID {
432
443
* pending writes.
433
444
*/
434
445
- (BOOL )shouldStartWriteStream {
435
- return [self isNetworkEnabled ] && ![self .writeStream isStarted ] && self.pendingWrites .count > 0 ;
446
+ return [self isNetworkEnabled ] && ![self .writeStream isStarted ] && self.writePipeline .count > 0 ;
436
447
}
437
448
438
449
- (void )startWriteStream {
@@ -442,48 +453,50 @@ - (void)startWriteStream {
442
453
[self .writeStream startWithDelegate: self ];
443
454
}
444
455
445
- - (void )cleanUpWriteStreamState {
446
- self.lastBatchSeen = kFSTBatchIDUnknown ;
447
- LOG_DEBUG (" Stopping write stream with %s pending writes" , [self .pendingWrites count ]);
448
- [self .pendingWrites removeAllObjects ];
449
- }
450
-
456
+ /* *
457
+ * Attempts to fill our write pipeline with writes from the LocalStore.
458
+ *
459
+ * Called internally to bootstrap or refill the write pipeline and by SyncEngine whenever there
460
+ * are new mutations to process.
461
+ *
462
+ * Starts the write stream if necessary.
463
+ */
451
464
- (void )fillWritePipeline {
452
- if ([self isNetworkEnabled ]) {
453
- while ([self canWriteMutations ]) {
454
- FSTMutationBatch *batch = [self .localStore nextMutationBatchAfterBatchID: self .lastBatchSeen];
455
- if (!batch) {
456
- break ;
465
+ FSTBatchID lastBatchIDRetrieved =
466
+ self.writePipeline .count == 0 ? kFSTBatchIDUnknown : self.writePipeline .lastObject .batchID ;
467
+ while ([self canAddToWritePipeline ]) {
468
+ FSTMutationBatch *batch = [self .localStore nextMutationBatchAfterBatchID: lastBatchIDRetrieved];
469
+ if (!batch) {
470
+ if (self.writePipeline .count == 0 ) {
471
+ [self .writeStream markIdle ];
457
472
}
458
- [ self commitBatch: batch] ;
473
+ break ;
459
474
}
475
+ [self addBatchToWritePipeline: batch];
476
+ lastBatchIDRetrieved = batch.batchID ;
477
+ }
460
478
461
- if ([self .pendingWrites count ] == 0 ) {
462
- [self .writeStream markIdle ];
463
- }
479
+ if ([self shouldStartWriteStream ]) {
480
+ [self startWriteStream ];
464
481
}
465
482
}
466
483
467
484
/* *
468
- * Returns YES if the backend can accept additional write requests.
469
- *
470
- * When sending mutations to the write stream (e.g. in -fillWritePipeline), call this method first
471
- * to check if more mutations can be sent.
472
- *
473
- * Currently the only thing that can prevent the backend from accepting write requests is if
474
- * there are too many requests already outstanding. As writes complete the backend will be able
475
- * to accept more.
485
+ * Returns YES if we can add to the write pipeline (i.e. it is not full and the network is enabled).
476
486
*/
477
- - (BOOL )canWriteMutations {
478
- return [self isNetworkEnabled ] && self.pendingWrites .count < kMaxPendingWrites ;
487
+ - (BOOL )canAddToWritePipeline {
488
+ return [self isNetworkEnabled ] && self.writePipeline .count < kMaxPendingWrites ;
479
489
}
480
490
481
- /* * Given mutations to commit, actually commits them to the backend. */
482
- - (void )commitBatch : (FSTMutationBatch *)batch {
483
- HARD_ASSERT ([self canWriteMutations ], " commitBatch called when mutations can't be written" );
484
- self.lastBatchSeen = batch.batchID ;
491
+ /* *
492
+ * Queues additional writes to be sent to the write stream, sending them immediately if the write
493
+ * stream is established, else starting the write stream if it is not yet started.
494
+ */
495
+ - (void )addBatchToWritePipeline : (FSTMutationBatch *)batch {
496
+ HARD_ASSERT ([self canAddToWritePipeline ],
497
+ " addBatchToWritePipeline called when mutations can't be written" );
485
498
486
- [self .pendingWrites addObject: batch];
499
+ [self .writePipeline addObject: batch];
487
500
488
501
if ([self shouldStartWriteStream ]) {
489
502
[self startWriteStream ];
@@ -504,17 +517,8 @@ - (void)writeStreamDidCompleteHandshake {
504
517
// Record the stream token.
505
518
[self .localStore setLastStreamToken: self .writeStream.lastStreamToken];
506
519
507
- // Drain any pending writes.
508
- //
509
- // Note that at this point pendingWrites contains mutations that have already been accepted by
510
- // fillWritePipeline/commitBatch. If the pipeline is full, canWriteMutations will be NO, despite
511
- // the fact that we actually need to send mutations over.
512
- //
513
- // This also means that this method indirectly respects the limits imposed by canWriteMutations
514
- // since writes can't be added to the pendingWrites array when canWriteMutations is NO. If the
515
- // limits imposed by canWriteMutations actually protect us from DOSing ourselves then those limits
516
- // won't be exceeded here and we'll continue to make progress.
517
- for (FSTMutationBatch *write in self.pendingWrites ) {
520
+ // Send the write pipeline now that the stream is established.
521
+ for (FSTMutationBatch *write in self.writePipeline ) {
518
522
[self .writeStream writeMutations: write .mutations];
519
523
}
520
524
}
@@ -523,10 +527,10 @@ - (void)writeStreamDidCompleteHandshake {
523
527
- (void )writeStreamDidReceiveResponseWithVersion : (const SnapshotVersion &)commitVersion
524
528
mutationResults : (NSArray <FSTMutationResult *> *)results {
525
529
// This is a response to a write containing mutations and should be correlated to the first
526
- // pending write.
527
- NSMutableArray *pendingWrites = self.pendingWrites ;
528
- FSTMutationBatch *batch = pendingWrites [0 ];
529
- [pendingWrites removeObjectAtIndex: 0 ];
530
+ // write in our write pipeline .
531
+ NSMutableArray *writePipeline = self.writePipeline ;
532
+ FSTMutationBatch *batch = writePipeline [0 ];
533
+ [writePipeline removeObjectAtIndex: 0 ];
530
534
531
535
FSTMutationBatchResult *batchResult =
532
536
[FSTMutationBatchResult resultWithBatch: batch
@@ -549,7 +553,7 @@ - (void)writeStreamWasInterruptedWithError:(nullable NSError *)error {
549
553
550
554
// If the write stream closed due to an error, invoke the error callbacks if there are pending
551
555
// writes.
552
- if (error != nil && self.pendingWrites .count > 0 ) {
556
+ if (error != nil && self.writePipeline .count > 0 ) {
553
557
if (self.writeStream .handshakeComplete ) {
554
558
// This error affects the actual writes.
555
559
[self handleWriteError: error];
@@ -586,8 +590,8 @@ - (void)handleWriteError:(NSError *)error {
586
590
587
591
// If this was a permanent error, the request itself was the problem so it's not going to
588
592
// succeed if we resend it.
589
- FSTMutationBatch *batch = self.pendingWrites [0 ];
590
- [self .pendingWrites removeObjectAtIndex: 0 ];
593
+ FSTMutationBatch *batch = self.writePipeline [0 ];
594
+ [self .writePipeline removeObjectAtIndex: 0 ];
591
595
592
596
// In this case it's also unlikely that the server itself is melting down--this was just a
593
597
// bad request so inhibit backoff on the next restart.
0 commit comments