@@ -205,42 +205,13 @@ func (cs *changeStream) runCommand(ctx context.Context, replaceOptions bool) err
205
205
}
206
206
cs .cursor = cursor
207
207
208
- // can get resume token from initial aggregate command if non-empty batch
209
- // operationTime from aggregate saved in the session
210
208
cursorValue , err := rdr .LookupErr ("cursor" )
211
209
if err != nil {
212
210
return err
213
211
}
214
212
cursorDoc := cursorValue .Document ()
215
-
216
213
cs .ns = command .ParseNamespace (cursorDoc .Lookup ("ns" ).StringValue ())
217
214
218
- batchVal := cursorDoc .Lookup ("firstBatch" )
219
- if err != nil {
220
- return err
221
- }
222
-
223
- batch := batchVal .Array ()
224
- elements , err := batch .Elements ()
225
- if err != nil {
226
- return err
227
- }
228
-
229
- if len (elements ) == 0 {
230
- return nil // no resume token
231
- }
232
-
233
- firstElem , err := batch .IndexErr (0 )
234
- if err != nil {
235
- return err
236
- }
237
-
238
- tokenDoc , err := bsonx .ReadDoc (firstElem .Value ().Document ().Lookup ("_id" ).Document ())
239
- if err != nil {
240
- return err
241
- }
242
-
243
- cs .resumeToken = tokenDoc
244
215
return nil
245
216
}
246
217
@@ -398,6 +369,34 @@ func newClientChangeStream(ctx context.Context, client *Client, pipeline interfa
398
369
return cs , nil
399
370
}
400
371
372
+ func (cs * changeStream ) storeResumeToken () error {
373
+ br , err := cs .cursor .DecodeBytes ()
374
+ if err != nil {
375
+ return err
376
+ }
377
+
378
+ idVal , err := br .LookupErr ("_id" )
379
+ if err != nil {
380
+ _ = cs .Close (context .Background ())
381
+ return ErrMissingResumeToken
382
+ }
383
+
384
+ var idDoc bson.Raw
385
+ idDoc , ok := idVal .DocumentOK ()
386
+ if ! ok {
387
+ _ = cs .Close (context .Background ())
388
+ return ErrMissingResumeToken
389
+ }
390
+ tokenDoc , err := bsonx .ReadDoc (idDoc )
391
+ if err != nil {
392
+ _ = cs .Close (context .Background ())
393
+ return ErrMissingResumeToken
394
+ }
395
+
396
+ cs .resumeToken = tokenDoc
397
+ return nil
398
+ }
399
+
401
400
func (cs * changeStream ) ID () int64 {
402
401
if cs .cursor == nil {
403
402
return 0
@@ -407,38 +406,45 @@ func (cs *changeStream) ID() int64 {
407
406
}
408
407
409
408
func (cs * changeStream ) Next (ctx context.Context ) bool {
410
- if cs .cursor == nil {
411
- return false
412
- }
409
+ // execute in a loop to retry resume-able errors and advance the underlying cursor
410
+ for {
411
+ if cs .cursor == nil {
412
+ return false
413
+ }
413
414
414
- if cs .cursor .Next (ctx ) {
415
- return true
416
- }
415
+ if cs .cursor .Next (ctx ) {
416
+ err := cs .storeResumeToken ()
417
+ if err != nil {
418
+ cs .err = err
419
+ return false
420
+ }
417
421
418
- err := cs .cursor .Err ()
419
- if err == nil {
420
- return false
421
- }
422
+ return true
423
+ }
422
424
423
- switch t := err .(type ) {
424
- case command.Error :
425
- if t .Code == errorInterrupted || t .Code == errorCappedPositionLost || t .Code == errorCursorKilled {
425
+ err := cs .cursor .Err ()
426
+ if err == nil {
426
427
return false
427
428
}
428
- }
429
429
430
- killCursors := command.KillCursors {
431
- NS : cs .ns ,
432
- IDs : []int64 {cs .ID ()},
433
- }
430
+ switch t := err .(type ) {
431
+ case command.Error :
432
+ if t .Code == errorInterrupted || t .Code == errorCappedPositionLost || t .Code == errorCursorKilled {
433
+ return false
434
+ }
435
+ }
434
436
435
- _ , _ = driver .KillCursors (ctx , killCursors , cs .client .topology , cs .db .writeSelector )
436
- cs .err = cs .runCommand (ctx , true )
437
- if cs .err != nil {
438
- return false
439
- }
437
+ killCursors := command.KillCursors {
438
+ NS : cs .ns ,
439
+ IDs : []int64 {cs .ID ()},
440
+ }
440
441
441
- return true
442
+ _ , _ = driver .KillCursors (ctx , killCursors , cs .client .topology , cs .db .writeSelector )
443
+ cs .err = cs .runCommand (ctx , true )
444
+ if cs .err != nil {
445
+ return false
446
+ }
447
+ }
442
448
}
443
449
444
450
func (cs * changeStream ) Decode (out interface {}) error {
@@ -458,32 +464,11 @@ func (cs *changeStream) DecodeBytes() (bson.Raw, error) {
458
464
if cs .cursor == nil {
459
465
return nil , ErrNilCursor
460
466
}
461
-
462
- br , err := cs .cursor .DecodeBytes ()
463
- if err != nil {
464
- return nil , err
465
- }
466
-
467
- idVal , err := br .LookupErr ("_id" )
468
- if err != nil {
469
- _ = cs .Close (context .Background ())
470
- return nil , ErrMissingResumeToken
471
- }
472
-
473
- var idDoc bson.Raw
474
- idDoc , ok := idVal .DocumentOK ()
475
- if ! ok {
476
- _ = cs .Close (context .Background ())
477
- return nil , ErrMissingResumeToken
478
- }
479
- tokenDoc , err := bsonx .ReadDoc (idDoc )
480
- if err != nil {
481
- _ = cs .Close (context .Background ())
482
- return nil , ErrMissingResumeToken
467
+ if cs .err != nil {
468
+ return nil , cs .err
483
469
}
484
470
485
- cs .resumeToken = tokenDoc
486
- return br , nil
471
+ return cs .cursor .DecodeBytes ()
487
472
}
488
473
489
474
func (cs * changeStream ) Err () error {
0 commit comments