@@ -255,7 +255,17 @@ func (it *messageIterator) receive(maxToPull int32) ([]*Message, error) {
255
255
// do a receipt mod-ack when streaming.
256
256
maxExt := time .Now ().Add (it .po .maxExtension )
257
257
ackIDs := map [string ]* AckResult {}
258
+ it .eoMu .RLock ()
259
+ exactlyOnceDelivery := it .enableExactlyOnceDelivery
260
+ it .eoMu .RUnlock ()
258
261
it .mu .Lock ()
262
+
263
+ // pendingMessages maps ackID -> message, and is used
264
+ // only when exactly once delivery is enabled.
265
+ // At first, all messages are pending, and they
266
+ // are removed if the modack call fails. All other
267
+ // messages are returned to the client for processing.
268
+ pendingMessages := make (map [string ]* ipubsub.Message )
259
269
for _ , m := range msgs {
260
270
ackID := msgAckID (m )
261
271
addRecv (m .ID , ackID , now )
@@ -264,22 +274,52 @@ func (it *messageIterator) receive(maxToPull int32) ([]*Message, error) {
264
274
// possible if there are retries.
265
275
if _ , ok := it .pendingNacks [ackID ]; ! ok {
266
276
// Don't use the message's AckResult here since these are only for receipt modacks.
267
- // ModAckResults are transparent to the user anyway so these can automatically succeed.
277
+ // modack results are transparent to the user so these can automatically succeed unless
278
+ // exactly once is enabled.
268
279
// We can't use an empty AckResult here either since SetAckResult will try to
269
280
// close the channel without checking if it exists.
270
- ackIDs [ackID ] = newSuccessAckResult ()
281
+ if ! exactlyOnceDelivery {
282
+ ackIDs [ackID ] = newSuccessAckResult ()
283
+ } else {
284
+ ackIDs [ackID ] = ipubsub .NewAckResult ()
285
+ pendingMessages [ackID ] = m
286
+ }
271
287
}
272
288
}
273
289
deadline := it .ackDeadline ()
274
290
it .mu .Unlock ()
275
- go func () {
276
- if len (ackIDs ) > 0 {
277
- // Don't check the return value of this since modacks are fire and forget,
278
- // meaning errors should not be propagated to the client.
279
- it .sendModAck (ackIDs , deadline )
291
+
292
+ if len (ackIDs ) > 0 {
293
+ // When exactly once delivery is not enabled, modacks are fire and forget.
294
+ if ! exactlyOnceDelivery {
295
+ go func () {
296
+ it .sendModAck (ackIDs , deadline , false )
297
+ }()
298
+ return msgs , nil
280
299
}
281
- }()
282
- return msgs , nil
300
+
301
+ // If exactly once is enabled, we should wait until modack responses are successes
302
+ // before attempting to process messages.
303
+ it .sendModAck (ackIDs , deadline , false )
304
+ for ackID , ar := range ackIDs {
305
+ ctx := context .Background ()
306
+ _ , err := ar .Get (ctx )
307
+ if err != nil {
308
+ delete (pendingMessages , ackID )
309
+ it .mu .Lock ()
310
+ // Remove the message from lease management if modack fails here.
311
+ delete (it .keepAliveDeadlines , ackID )
312
+ it .mu .Unlock ()
313
+ }
314
+ }
315
+ // Only return for processing messages that were successfully modack'ed.
316
+ v := make ([]* ipubsub.Message , 0 , len (pendingMessages ))
317
+ for _ , m := range pendingMessages {
318
+ v = append (v , m )
319
+ }
320
+ return v , nil
321
+ }
322
+ return nil , nil
283
323
}
284
324
285
325
// Get messages using the Pull RPC.
@@ -399,10 +439,10 @@ func (it *messageIterator) sender() {
399
439
}
400
440
if sendNacks {
401
441
// Nack indicated by modifying the deadline to zero.
402
- it .sendModAck (nacks , 0 )
442
+ it .sendModAck (nacks , 0 , false )
403
443
}
404
444
if sendModAcks {
405
- it .sendModAck (modAcks , dl )
445
+ it .sendModAck (modAcks , dl , true )
406
446
}
407
447
if sendPing {
408
448
it .pingStream ()
@@ -479,7 +519,7 @@ func (it *messageIterator) sendAck(m map[string]*AckResult) {
479
519
// percentile in order to capture the highest amount of time necessary without
480
520
// considering 1% outliers. If the ModAck RPC fails and exactly once delivery is
481
521
// enabled, we retry it in a separate goroutine for a short duration.
482
- func (it * messageIterator ) sendModAck (m map [string ]* AckResult , deadline time.Duration ) {
522
+ func (it * messageIterator ) sendModAck (m map [string ]* AckResult , deadline time.Duration , logOnInvalid bool ) {
483
523
deadlineSec := int32 (deadline / time .Second )
484
524
ackIDs := make ([]string , 0 , len (m ))
485
525
for k := range m {
@@ -517,7 +557,7 @@ func (it *messageIterator) sendModAck(m map[string]*AckResult, deadline time.Dur
517
557
if len (toRetry ) > 0 {
518
558
// Retry modacks/nacks in a separate goroutine.
519
559
go func () {
520
- it .retryModAcks (toRetry , deadlineSec )
560
+ it .retryModAcks (toRetry , deadlineSec , logOnInvalid )
521
561
}()
522
562
}
523
563
}
@@ -563,29 +603,29 @@ func (it *messageIterator) retryAcks(m map[string]*AckResult) {
563
603
// in it.sendModAck(), with a max of 2500 ackIDs. Modacks are retried up to 3 times
564
604
// since after that, the message will have expired. Nacks are retried up until the default
565
605
// deadline of 10 minutes.
566
- func (it * messageIterator ) retryModAcks (m map [string ]* AckResult , deadlineSec int32 ) {
606
+ func (it * messageIterator ) retryModAcks (m map [string ]* AckResult , deadlineSec int32 , logOnInvalid bool ) {
567
607
bo := newExactlyOnceBackoff ()
568
608
retryCount := 0
569
609
ctx , cancel := context .WithTimeout (context .Background (), exactlyOnceDeliveryRetryDeadline )
570
610
defer cancel ()
571
611
for {
572
- // If context is done, complete all remaining Nacks with DeadlineExceeded
573
- // ModAcks are not exposed to the user so these don't need to be modified.
612
+ // If context is done, complete all AckResults with errors.
574
613
if ctx .Err () != nil {
575
- if deadlineSec == 0 {
576
- for _ , r := range m {
577
- ipubsub .SetAckResult (r , AcknowledgeStatusOther , ctx .Err ())
578
- }
614
+ for _ , r := range m {
615
+ ipubsub .SetAckResult (r , AcknowledgeStatusOther , ctx .Err ())
579
616
}
580
617
return
581
618
}
582
619
// Only retry modack requests up to 3 times.
583
620
if deadlineSec != 0 && retryCount > 3 {
584
621
ackIDs := make ([]string , 0 , len (m ))
585
- for k := range m {
622
+ for k , ar := range m {
586
623
ackIDs = append (ackIDs , k )
624
+ ipubsub .SetAckResult (ar , AcknowledgeStatusOther , errors .New ("modack retry failed" ))
625
+ }
626
+ if logOnInvalid {
627
+ log .Printf ("automatic lease modack retry failed for following IDs: %v" , ackIDs )
587
628
}
588
- log .Printf ("automatic lease modack retry failed for following IDs: %v" , ackIDs )
589
629
return
590
630
}
591
631
// Don't need to split map since this is the retry function and
@@ -723,7 +763,7 @@ func extractMetadata(err error) (*status.Status, map[string]string) {
723
763
return nil , nil
724
764
}
725
765
726
- // processResults processes AckResults by referring to errorStatus and errorsMap .
766
+ // processResults processes AckResults by referring to errorStatus and errorsByAckID .
727
767
// The errors returned by the server in `errorStatus` or in `errorsByAckID`
728
768
// are used to complete the AckResults in `ackResMap` (with a success
729
769
// or error) or to return requests for further retries.
0 commit comments