Skip to content

Commit 6485f15

Browse files
authored
Minimal handle success retry order (#29)
* minimim change * remove more logs * Guard broker abandon by producer identity * fix test * try to fix broken test
1 parent edf2ea9 commit 6485f15

5 files changed

Lines changed: 303 additions & 61 deletions

File tree

async_producer.go

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"math"
88
"sync"
9+
"sync/atomic"
910
"time"
1011

1112
"github.com/eapache/go-resiliency/breaker"
@@ -93,6 +94,9 @@ type asyncProducer struct {
9394
input, successes, retries chan *ProducerMessage
9495
inFlight sync.WaitGroup
9596

97+
done chan struct{}
98+
closed atomic.Bool
99+
96100
brokers map[*Broker]*brokerProducer
97101
brokerRefs map[*brokerProducer]int
98102
brokerLock sync.Mutex
@@ -303,6 +307,7 @@ func newAsyncProducer(client Client) (AsyncProducer, error) {
303307
input: make(chan *ProducerMessage),
304308
successes: make(chan *ProducerMessage),
305309
retries: make(chan *ProducerMessage),
310+
done: make(chan struct{}),
306311
brokers: make(map[*Broker]*brokerProducer),
307312
brokerRefs: make(map[*brokerProducer]int),
308313
txnmgr: txnmgr,
@@ -781,12 +786,16 @@ func (p *asyncProducer) newPartitionProducer(topic string, partition int32) chan
781786
}
782787

783788
func (pp *partitionProducer) backoff(retries int) {
789+
pp.parent.retryBackoff(retries)
790+
}
791+
792+
func (p *asyncProducer) retryBackoff(retries int) {
784793
var backoff time.Duration
785-
if pp.parent.conf.Producer.Retry.BackoffFunc != nil {
786-
maxRetries := pp.parent.conf.Producer.Retry.Max
787-
backoff = pp.parent.conf.Producer.Retry.BackoffFunc(retries, maxRetries)
794+
if p.conf.Producer.Retry.BackoffFunc != nil {
795+
maxRetries := p.conf.Producer.Retry.Max
796+
backoff = p.conf.Producer.Retry.BackoffFunc(retries, maxRetries)
788797
} else {
789-
backoff = pp.parent.conf.Producer.Retry.Backoff
798+
backoff = p.conf.Producer.Retry.Backoff
790799
}
791800
if backoff > 0 {
792801
time.Sleep(backoff)
@@ -821,12 +830,19 @@ func (pp *partitionProducer) dispatch() {
821830
}()
822831

823832
for msg := range pp.input {
824-
if pp.brokerProducer != nil && pp.brokerProducer.abandoned != nil {
833+
if pp.brokerProducer != nil {
825834
select {
826835
case <-pp.brokerProducer.abandoned:
827836
// a message on the abandoned channel means that our current broker selection is out of date
828-
pp.parent.unrefBrokerProducer(pp.leader, pp.brokerProducer)
837+
abandonedLeader := pp.leader
838+
pp.parent.unrefBrokerProducer(abandonedLeader, pp.brokerProducer)
829839
pp.brokerProducer = nil
840+
if leader, _ := pp.parent.client.Leader(pp.topic, pp.partition); leader != nil && leader.ID() != abandonedLeader.ID() {
841+
pp.leader = leader
842+
pp.brokerProducer = pp.parent.getBrokerProducer(pp.leader)
843+
pp.parent.inFlight.Add(1) // we're generating a syn message; track it so we don't shut down while it's still inflight
844+
pp.brokerProducer.input <- &ProducerMessage{Topic: pp.topic, Partition: pp.partition, flags: syn}
845+
}
830846
time.Sleep(pp.parent.conf.Producer.Retry.Backoff)
831847
default:
832848
// producer connection is still open.
@@ -956,6 +972,7 @@ func (p *asyncProducer) newBrokerProducer(broker *Broker) *brokerProducer {
956972
input: input,
957973
output: bridge,
958974
responses: responses,
975+
abandoned: make(chan struct{}),
959976
accumulatingBatch: newProduceSet(p),
960977
currentRetries: make(map[string]map[int32]error),
961978
}
@@ -1046,10 +1063,6 @@ func (p *asyncProducer) newBrokerProducer(broker *Broker) *brokerProducer {
10461063
}
10471064
})
10481065

1049-
if p.conf.Producer.Retry.Max <= 0 {
1050-
bp.abandoned = make(chan struct{})
1051-
}
1052-
10531066
return bp
10541067
}
10551068

@@ -1092,10 +1105,16 @@ func (bp *brokerProducer) run() {
10921105
timerChan = bp.timer.C
10931106
}
10941107

1108+
var unmuteCh <-chan struct{}
10951109
if bp.flushingBatch != nil {
10961110
output = bp.output
10971111
} else {
10981112
output = nil
1113+
if bp.accumulatingBatch.readyToFlush() {
1114+
if ch, blocked := bp.parent.muter.awaitUnmuteChan(bp.accumulatingBatch); blocked {
1115+
unmuteCh = ch
1116+
}
1117+
}
10991118
}
11001119

11011120
select {
@@ -1138,7 +1157,6 @@ func (bp *brokerProducer) run() {
11381157
}
11391158

11401159
if bp.accumulatingBatch.wouldOverflow(msg) {
1141-
DebugLogger.Printf("producer/broker/%d maximum request accumulated, waiting for space\n", bp.broker.ID())
11421160
if err := bp.waitForSpace(msg, false); err != nil {
11431161
bp.parent.retryMessage(msg, err)
11441162
continue
@@ -1169,6 +1187,7 @@ func (bp *brokerProducer) run() {
11691187
if ok {
11701188
bp.handleResponse(response)
11711189
}
1190+
case <-unmuteCh:
11721191
}
11731192
}
11741193
}
@@ -1350,32 +1369,28 @@ func (bp *brokerProducer) handleSuccess(sent *produceSet, response *ProduceRespo
13501369
case ErrInvalidMessage, ErrUnknownTopicOrPartition, ErrLeaderNotAvailable, ErrNotLeaderForPartition,
13511370
ErrRequestTimedOut, ErrNotEnoughReplicas, ErrNotEnoughReplicasAfterAppend, ErrKafkaStorageError:
13521371
if bp.parent.conf.Producer.Retry.Max <= 0 {
1353-
bp.parent.abandonBrokerConnection(bp.broker)
1372+
bp.parent.abandonBrokerConnection(bp)
13541373
bp.parent.returnErrors(pSet.msgs, block.Err)
13551374
} else {
13561375
retryTopics = append(retryTopics, topic)
1357-
if bp.parent.conf.Producer.Idempotent {
1358-
if keepMuted[topic] == nil {
1359-
keepMuted[topic] = make(map[int32]struct{})
1360-
}
1361-
keepMuted[topic][partition] = struct{}{}
1376+
if keepMuted[topic] == nil {
1377+
keepMuted[topic] = make(map[int32]struct{})
13621378
}
1379+
keepMuted[topic][partition] = struct{}{}
13631380
}
13641381
// Other non-retriable errors
13651382
default:
13661383
if bp.parent.conf.Producer.Retry.Max <= 0 {
1367-
bp.parent.abandonBrokerConnection(bp.broker)
1384+
bp.parent.abandonBrokerConnection(bp)
13681385
}
13691386
bp.parent.returnErrors(pSet.msgs, block.Err)
13701387
}
13711388
})
13721389

13731390
if len(retryTopics) > 0 {
1374-
if bp.parent.conf.Producer.Idempotent {
1375-
err := bp.parent.client.RefreshMetadata(retryTopics...)
1376-
if err != nil {
1377-
Logger.Printf("Failed refreshing metadata because of %v\n", err)
1378-
}
1391+
err := bp.parent.client.RefreshMetadata(retryTopics...)
1392+
if err != nil {
1393+
Logger.Printf("Failed refreshing metadata because of %v\n", err)
13791394
}
13801395

13811396
sent.eachPartition(func(topic string, partition int32, pSet *partitionSet) {
@@ -1390,17 +1405,25 @@ func (bp *brokerProducer) handleSuccess(sent *produceSet, response *ProduceRespo
13901405
ErrRequestTimedOut, ErrNotEnoughReplicas, ErrNotEnoughReplicasAfterAppend, ErrKafkaStorageError:
13911406
Logger.Printf("producer/broker/%d state change to [retrying] on %s/%d because %v\n",
13921407
bp.broker.ID(), topic, partition, block.Err)
1393-
if bp.currentRetries[topic] == nil {
1394-
bp.currentRetries[topic] = make(map[int32]error)
1408+
leaderChanged := false
1409+
if !bp.parent.conf.Producer.Idempotent {
1410+
leader, leaderErr := bp.parent.client.Leader(topic, partition)
1411+
leaderChanged = leaderErr == nil && leader != nil && leader.ID() != bp.broker.ID()
13951412
}
1396-
bp.currentRetries[topic][partition] = block.Err
1413+
if bp.parent.conf.Producer.Idempotent || leaderChanged {
1414+
if bp.currentRetries[topic] == nil {
1415+
bp.currentRetries[topic] = make(map[int32]error)
1416+
}
1417+
bp.currentRetries[topic][partition] = block.Err
1418+
}
1419+
if leaderChanged {
1420+
bp.parent.abandonBrokerConnection(bp)
1421+
}
1422+
go bp.parent.retryBatch(topic, partition, pSet, block.Err, true)
13971423
if bp.parent.conf.Producer.Idempotent {
1398-
go bp.parent.retryBatch(topic, partition, pSet, block.Err, true)
1399-
} else {
1400-
bp.parent.retryMessages(pSet.msgs, block.Err)
1424+
// dropping the following messages has the side effect of incrementing their retry count
1425+
bp.parent.retryMessages(bp.accumulatingBatch.dropPartition(topic, partition), block.Err)
14011426
}
1402-
// dropping the following messages has the side effect of incrementing their retry count
1403-
bp.parent.retryMessages(bp.accumulatingBatch.dropPartition(topic, partition), block.Err)
14041427
}
14051428
})
14061429
}
@@ -1422,6 +1445,7 @@ func (p *asyncProducer) retryBatch(topic string, partition int32, pSet *partitio
14221445
produceSet.msgs[topic][partition] = pSet
14231446
produceSet.bufferBytes += pSet.bufferBytes
14241447
produceSet.bufferCount += len(pSet.msgs)
1448+
retryAttempt := 0
14251449
for _, msg := range pSet.msgs {
14261450
if msg.retries >= p.conf.Producer.Retry.Max {
14271451
p.returnErrors(pSet.msgs, retryErr)
@@ -1431,6 +1455,10 @@ func (p *asyncProducer) retryBatch(topic string, partition int32, pSet *partitio
14311455
return
14321456
}
14331457
msg.retries++
1458+
retryAttempt = msg.retries
1459+
}
1460+
if !p.conf.Producer.Idempotent {
1461+
p.retryBackoff(retryAttempt)
14341462
}
14351463

14361464
// it's expected that a metadata refresh has been requested prior to calling retryBatch
@@ -1454,8 +1482,23 @@ func (p *asyncProducer) retryBatch(topic string, partition int32, pSet *partitio
14541482
}
14551483
}
14561484
bp := p.getBrokerProducer(leader)
1457-
bp.output <- produceSet
1458-
p.unrefBrokerProducer(leader, bp)
1485+
defer p.unrefBrokerProducer(leader, bp)
1486+
1487+
select {
1488+
case bp.output <- produceSet:
1489+
return
1490+
default:
1491+
}
1492+
1493+
select {
1494+
case bp.output <- produceSet:
1495+
return
1496+
case <-p.done:
1497+
for _, msg := range pSet.msgs {
1498+
p.returnError(msg, ErrShuttingDown)
1499+
}
1500+
p.muter.unmute(produceSet)
1501+
}
14591502
}
14601503

14611504
func (bp *brokerProducer) handleError(sent *produceSet, err error) {
@@ -1467,7 +1510,7 @@ func (bp *brokerProducer) handleError(sent *produceSet, err error) {
14671510
bp.parent.muter.unmute(sent)
14681511
} else {
14691512
Logger.Printf("producer/broker/%d state change to [closing] because %s\n", bp.broker.ID(), err)
1470-
bp.parent.abandonBrokerConnection(bp.broker)
1513+
bp.parent.abandonBrokerConnection(bp)
14711514
_ = bp.broker.Close()
14721515
bp.closing = err
14731516
var retryTopics []string
@@ -1479,7 +1522,7 @@ func (bp *brokerProducer) handleError(sent *produceSet, err error) {
14791522
retryTopicSeen[topic] = struct{}{}
14801523
retryTopics = append(retryTopics, topic)
14811524
})
1482-
if bp.parent.conf.Producer.Idempotent && len(retryTopics) > 0 {
1525+
if len(retryTopics) > 0 {
14831526
refreshErr := bp.parent.client.RefreshMetadata(retryTopics...)
14841527
if refreshErr != nil {
14851528
Logger.Printf("Failed refreshing metadata because of %v\n", refreshErr)
@@ -1492,15 +1535,11 @@ func (bp *brokerProducer) handleError(sent *produceSet, err error) {
14921535
bp.currentRetries[topic] = make(map[int32]error)
14931536
}
14941537
bp.currentRetries[topic][partition] = err
1495-
if bp.parent.conf.Producer.Idempotent {
1496-
if keepMuted[topic] == nil {
1497-
keepMuted[topic] = make(map[int32]struct{})
1498-
}
1499-
keepMuted[topic][partition] = struct{}{}
1500-
go bp.parent.retryBatch(topic, partition, pSet, err, true)
1501-
} else {
1502-
bp.parent.retryMessages(pSet.msgs, err)
1538+
if keepMuted[topic] == nil {
1539+
keepMuted[topic] = make(map[int32]struct{})
15031540
}
1541+
keepMuted[topic][partition] = struct{}{}
1542+
go bp.parent.retryBatch(topic, partition, pSet, err, true)
15041543
})
15051544
bp.accumulatingBatch.eachPartition(func(topic string, partition int32, pSet *partitionSet) {
15061545
bp.parent.retryMessages(pSet.msgs, err)
@@ -1583,6 +1622,9 @@ func (p *asyncProducer) retryHandler() {
15831622
// utility functions
15841623

15851624
func (p *asyncProducer) shutdown() {
1625+
if p.closed.CompareAndSwap(false, true) {
1626+
close(p.done)
1627+
}
15861628
p.inFlight.Add(1)
15871629
p.input <- &ProducerMessage{flags: shutdown}
15881630

@@ -1716,14 +1758,15 @@ func (p *asyncProducer) unrefBrokerProducer(broker *Broker, bp *brokerProducer)
17161758
}
17171759
}
17181760

1719-
func (p *asyncProducer) abandonBrokerConnection(broker *Broker) {
1761+
func (p *asyncProducer) abandonBrokerConnection(bp *brokerProducer) {
17201762
p.brokerLock.Lock()
17211763
defer p.brokerLock.Unlock()
17221764

1723-
bc, ok := p.brokers[broker]
1724-
if ok && bc.abandoned != nil {
1725-
close(bc.abandoned)
1765+
bc, ok := p.brokers[bp.broker]
1766+
if !ok || bc != bp {
1767+
return
17261768
}
17271769

1728-
delete(p.brokers, broker)
1770+
close(bc.abandoned)
1771+
delete(p.brokers, bp.broker)
17291772
}

0 commit comments

Comments
 (0)