@@ -234,6 +234,7 @@ type cursorOffsetNext struct {
234
234
type cursorOffsetPreferred struct {
235
235
cursorOffsetNext
236
236
preferredReplica int32
237
+ ooor bool
237
238
}
238
239
239
240
// Moves a cursor from one source to another. This is done while handling
@@ -268,12 +269,13 @@ func (cs cursorPreferreds) String() string {
268
269
type pnext struct {
269
270
p int32
270
271
next int32
272
+ ooor bool
271
273
}
272
274
ts := make (map [string ][]pnext )
273
275
for _ , c := range cs {
274
276
t := c .from .topic
275
277
p := c .from .partition
276
- ts [t ] = append (ts [t ], pnext {p , c .preferredReplica })
278
+ ts [t ] = append (ts [t ], pnext {p , c .preferredReplica , c . ooor })
277
279
}
278
280
tsorted := make ([]string , 0 , len (ts ))
279
281
for t , ps := range ts {
@@ -303,9 +305,17 @@ func (cs cursorPreferreds) String() string {
303
305
304
306
for j , p := range ps {
305
307
if j < len (ps )- 1 {
306
- fmt .Fprintf (sb , "%d=>%d, " , p .p , p .next )
308
+ if p .ooor {
309
+ fmt .Fprintf (sb , "%d=>%d[ooor], " , p .p , p .next )
310
+ } else {
311
+ fmt .Fprintf (sb , "%d=>%d, " , p .p , p .next )
312
+ }
307
313
} else {
308
- fmt .Fprintf (sb , "%d=>%d" , p .p , p .next )
314
+ if p .ooor {
315
+ fmt .Fprintf (sb , "%d=>%d[ooor]" , p .p , p .next )
316
+ } else {
317
+ fmt .Fprintf (sb , "%d=>%d" , p .p , p .next )
318
+ }
309
319
}
310
320
}
311
321
@@ -1083,6 +1093,7 @@ func (s *source) handleReqResp(br *broker, req *fetchRequest, resp *kmsg.FetchRe
1083
1093
preferreds = append (preferreds , cursorOffsetPreferred {
1084
1094
* partOffset ,
1085
1095
preferred ,
1096
+ false ,
1086
1097
})
1087
1098
continue
1088
1099
}
@@ -1152,6 +1163,9 @@ func (s *source) handleReqResp(br *broker, req *fetchRequest, resp *kmsg.FetchRe
1152
1163
// KIP-392 (case 3) specifies that if we are consuming
1153
1164
// from a follower, then if our offset request is before
1154
1165
// the low watermark, we list offsets from the follower.
1166
+ // However, Kafka does not actually implement handling
1167
+ // ListOffsets from anything from the leader, so we
1168
+ // need to redirect ourselves back to the leader.
1155
1169
//
1156
1170
// KIP-392 (case 4) specifies that if we are consuming
1157
1171
// a follower and our request is larger than the high
@@ -1205,7 +1219,22 @@ func (s *source) handleReqResp(br *broker, req *fetchRequest, resp *kmsg.FetchRe
1205
1219
addList (- 1 , true )
1206
1220
1207
1221
case partOffset .offset < fp .LogStartOffset : // KIP-392 case 3
1208
- addList (s .nodeID , false )
1222
+ // KIP-392 specifies that we should list offsets against the follower,
1223
+ // but that actually is not supported and the Java client redirects
1224
+ // back to the leader. The leader then does *not* direct the client
1225
+ // back to the follower because the follower is not an in sync
1226
+ // replica. If we did not redirect back to the leader, we would spin
1227
+ // loop receiving offset_out_of_range from the follower for Fetch, and
1228
+ // then not_leader_or_follower from the follower for ListOffsets
1229
+ // (even though it is a follower). So, we just set the preferred replica
1230
+ // back to the follower. We go directly back to fetching with the
1231
+ // hope that the offset is available on the leader, and if not, we'll
1232
+ // just get an OOOR error again and fall into case 1 just above.
1233
+ preferreds = append (preferreds , cursorOffsetPreferred {
1234
+ * partOffset ,
1235
+ partOffset .from .leader ,
1236
+ true ,
1237
+ })
1209
1238
1210
1239
default : // partOffset.offset > fp.HighWatermark, KIP-392 case 4
1211
1240
if kip320 {
0 commit comments