16
16
17
17
package org .springframework .kafka .listener ;
18
18
19
+ import java .util .HashMap ;
20
+ import java .util .Map ;
19
21
import java .util .function .BiConsumer ;
20
22
21
23
import org .apache .kafka .clients .consumer .ConsumerRecord ;
24
+ import org .apache .kafka .common .TopicPartition ;
22
25
23
26
import org .springframework .core .log .LogAccessor ;
24
27
import org .springframework .lang .Nullable ;
35
38
*/
36
39
class FailedRecordTracker {
37
40
38
- private final ThreadLocal <FailedRecord > failures = new ThreadLocal <>(); // intentionally not static
41
+ private final ThreadLocal <Map < TopicPartition , FailedRecord > > failures = new ThreadLocal <>(); // intentionally not static
39
42
40
43
private final BiConsumer <ConsumerRecord <?, ?>, Exception > recoverer ;
41
44
@@ -50,12 +53,18 @@ class FailedRecordTracker {
50
53
51
54
Assert .notNull (backOff , "'backOff' cannot be null" );
52
55
if (recoverer == null ) {
53
- FailedRecord failedRecord = this .failures .get ();
54
- this .recoverer = (rec , thr ) -> logger .error (thr , "Backoff "
55
- + (failedRecord == null
56
- ? "none"
57
- : failedRecord .getBackOffExecution ())
58
- + " exhausted for " + rec );
56
+ this .recoverer = (rec , thr ) -> {
57
+ Map <TopicPartition , FailedRecord > map = this .failures .get ();
58
+ FailedRecord failedRecord = null ;
59
+ if (map != null ) {
60
+ failedRecord = map .get (new TopicPartition (rec .topic (), rec .partition ()));
61
+ }
62
+ logger .error (thr , "Backoff "
63
+ + (failedRecord == null
64
+ ? "none"
65
+ : failedRecord .getBackOffExecution ())
66
+ + " exhausted for " + rec );
67
+ };
59
68
}
60
69
else {
61
70
this .recoverer = recoverer ;
@@ -70,10 +79,16 @@ boolean skip(ConsumerRecord<?, ?> record, Exception exception) {
70
79
recover (record , exception );
71
80
return true ;
72
81
}
73
- FailedRecord failedRecord = this .failures .get ();
74
- if (failedRecord == null || newFailure (record , failedRecord )) {
75
- failedRecord = new FailedRecord (record .topic (), record .partition (), record .offset (), this .backOff .start ());
76
- this .failures .set (failedRecord );
82
+ Map <TopicPartition , FailedRecord > map = this .failures .get ();
83
+ if (map == null ) {
84
+ this .failures .set (new HashMap <>());
85
+ map = this .failures .get ();
86
+ }
87
+ TopicPartition topicPartition = new TopicPartition (record .topic (), record .partition ());
88
+ FailedRecord failedRecord = map .get (topicPartition );
89
+ if (failedRecord == null || failedRecord .getOffset () != record .offset ()) {
90
+ failedRecord = new FailedRecord (record .offset (), this .backOff .start ());
91
+ map .put (topicPartition , failedRecord );
77
92
}
78
93
long nextBackOff = failedRecord .getBackOffExecution ().nextBackOff ();
79
94
if (nextBackOff != BackOffExecution .STOP ) {
@@ -87,7 +102,10 @@ boolean skip(ConsumerRecord<?, ?> record, Exception exception) {
87
102
}
88
103
else {
89
104
recover (record , exception );
90
- this .failures .remove ();
105
+ map .remove (topicPartition );
106
+ if (map .isEmpty ()) {
107
+ this .failures .remove ();
108
+ }
91
109
return true ;
92
110
}
93
111
}
@@ -101,12 +119,6 @@ private void recover(ConsumerRecord<?, ?> record, Exception exception) {
101
119
}
102
120
}
103
121
104
- private boolean newFailure (ConsumerRecord <?, ?> record , FailedRecord failedRecord ) {
105
- return !failedRecord .getTopic ().equals (record .topic ())
106
- || failedRecord .getPartition () != record .partition ()
107
- || failedRecord .getOffset () != record .offset ();
108
- }
109
-
110
122
void clearThreadState () {
111
123
this .failures .remove ();
112
124
}
@@ -117,29 +129,15 @@ void clearThreadState() {
117
129
118
130
private static final class FailedRecord {
119
131
120
- private final String topic ;
121
-
122
- private final int partition ;
123
-
124
132
private final long offset ;
125
133
126
134
private final BackOffExecution backOffExecution ;
127
135
128
- FailedRecord (String topic , int partition , long offset , BackOffExecution backOffExecution ) {
129
- this .topic = topic ;
130
- this .partition = partition ;
136
+ FailedRecord (long offset , BackOffExecution backOffExecution ) {
131
137
this .offset = offset ;
132
138
this .backOffExecution = backOffExecution ;
133
139
}
134
140
135
- String getTopic () {
136
- return this .topic ;
137
- }
138
-
139
- int getPartition () {
140
- return this .partition ;
141
- }
142
-
143
141
long getOffset () {
144
142
return this .offset ;
145
143
}
0 commit comments