Files
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Mario
a739cb2805 Bump github.com/twmb/franz-go from 1.18.1 to 1.20.0 (#5766)
* Bump github.com/twmb/franz-go from 1.18.1 to 1.20.0

Bumps [github.com/twmb/franz-go](https://github.com/twmb/franz-go) from 1.18.1 to 1.20.0.
- [Changelog](https://github.com/twmb/franz-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/twmb/franz-go/compare/v1.18.1...v1.20.0)

---
updated-dependencies:
- dependency-name: github.com/twmb/franz-go
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>

* Update kfake

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mario <[email protected]>
2025-10-20 13:55:55 +02:00

238 lines
5.3 KiB
Go

package kfake
import (
"net"
"strconv"
"sync"
"time"
"github.com/twmb/franz-go/pkg/kerr"
"github.com/twmb/franz-go/pkg/kmsg"
)
// Behavior:
//
// * If topic does not exist, we hang
// * Topic created while waiting is not returned in final response
// * If any partition is on a different broker, we return immediately
// * Out of range fetch causes early return
// * Raw bytes of batch counts against wait bytes
func init() { regKey(1, 4, 18) }
func (c *Cluster) handleFetch(creq *clientReq, w *watchFetch) (kmsg.Response, error) {
var (
req = creq.kreq.(*kmsg.FetchRequest)
resp = req.ResponseKind().(*kmsg.FetchResponse)
)
if err := checkReqVersion(req.Key(), req.Version); err != nil {
return nil, err
}
var (
nbytes int
returnEarly bool
needp tps[int]
)
if w == nil {
out:
for i, rt := range req.Topics {
if req.Version >= 13 {
rt.Topic = c.data.id2t[rt.TopicID]
req.Topics[i].Topic = rt.Topic
}
t, ok := c.data.tps.gett(rt.Topic)
if !ok {
continue
}
for _, rp := range rt.Partitions {
pd, ok := t[rp.Partition]
if !ok || pd.createdAt.After(creq.at) {
continue
}
if pd.leader != creq.cc.b && !pd.followers.has(creq.cc.b) {
returnEarly = true // NotLeaderForPartition
break out
}
i, ok, atEnd := pd.searchOffset(rp.FetchOffset)
if atEnd {
continue
}
if !ok {
returnEarly = true // OffsetOutOfRange
break out
}
pbytes := 0
for _, b := range pd.batches[i:] {
nbytes += b.nbytes
pbytes += b.nbytes
if pbytes >= int(rp.PartitionMaxBytes) {
returnEarly = true
break out
}
}
needp.set(rt.Topic, rp.Partition, int(rp.PartitionMaxBytes)-pbytes)
}
}
}
wait := time.Duration(req.MaxWaitMillis) * time.Millisecond
deadline := creq.at.Add(wait)
if w == nil && !returnEarly && nbytes < int(req.MinBytes) && time.Now().Before(deadline) {
w := &watchFetch{
need: int(req.MinBytes) - nbytes,
needp: needp,
deadline: deadline,
creq: creq,
}
w.cb = func() {
select {
case c.watchFetchCh <- w:
case <-c.die:
}
}
for _, rt := range req.Topics {
t, ok := c.data.tps.gett(rt.Topic)
if !ok {
continue
}
for _, rp := range rt.Partitions {
pd, ok := t[rp.Partition]
if !ok || pd.createdAt.After(creq.at) {
continue
}
pd.watch[w] = struct{}{}
w.in = append(w.in, pd)
}
}
w.t = time.AfterFunc(wait, w.cb)
return nil, nil
}
id2t := make(map[uuid]string)
tidx := make(map[string]int)
donet := func(t string, id uuid, errCode int16) *kmsg.FetchResponseTopic {
if i, ok := tidx[t]; ok {
return &resp.Topics[i]
}
id2t[id] = t
tidx[t] = len(resp.Topics)
st := kmsg.NewFetchResponseTopic()
st.Topic = t
st.TopicID = id
resp.Topics = append(resp.Topics, st)
return &resp.Topics[len(resp.Topics)-1]
}
donep := func(t string, id uuid, p int32, errCode int16) *kmsg.FetchResponseTopicPartition {
sp := kmsg.NewFetchResponseTopicPartition()
sp.Partition = p
sp.ErrorCode = errCode
st := donet(t, id, 0)
st.Partitions = append(st.Partitions, sp)
return &st.Partitions[len(st.Partitions)-1]
}
var includeBrokers bool
defer func() {
if includeBrokers {
for _, b := range c.bs {
sb := kmsg.NewFetchResponseBroker()
h, p, _ := net.SplitHostPort(b.ln.Addr().String())
p32, _ := strconv.Atoi(p)
sb.NodeID = b.node
sb.Host = h
sb.Port = int32(p32)
resp.Brokers = append(resp.Brokers, sb)
}
}
}()
var batchesAdded int
nbytes = 0
full:
for _, rt := range req.Topics {
for _, rp := range rt.Partitions {
pd, ok := c.data.tps.getp(rt.Topic, rp.Partition)
if !ok {
if req.Version >= 13 {
donep(rt.Topic, rt.TopicID, rp.Partition, kerr.UnknownTopicID.Code)
} else {
donep(rt.Topic, rt.TopicID, rp.Partition, kerr.UnknownTopicOrPartition.Code)
}
continue
}
if pd.leader != creq.cc.b && !pd.followers.has(creq.cc.b) {
p := donep(rt.Topic, rt.TopicID, rp.Partition, kerr.NotLeaderForPartition.Code)
p.CurrentLeader.LeaderID = pd.leader.node
p.CurrentLeader.LeaderEpoch = pd.epoch
includeBrokers = true
continue
}
sp := donep(rt.Topic, rt.TopicID, rp.Partition, 0)
sp.HighWatermark = pd.highWatermark
sp.LastStableOffset = pd.lastStableOffset
sp.LogStartOffset = pd.logStartOffset
i, ok, atEnd := pd.searchOffset(rp.FetchOffset)
if atEnd {
continue
}
if !ok {
sp.ErrorCode = kerr.OffsetOutOfRange.Code
continue
}
var pbytes int
for _, b := range pd.batches[i:] {
if nbytes = nbytes + b.nbytes; nbytes > int(req.MaxBytes) && batchesAdded > 0 {
break full
}
if pbytes = pbytes + b.nbytes; pbytes > int(rp.PartitionMaxBytes) && batchesAdded > 0 {
break
}
batchesAdded++
sp.RecordBatches = b.AppendTo(sp.RecordBatches)
}
}
}
return resp, nil
}
type watchFetch struct {
need int
needp tps[int]
deadline time.Time
creq *clientReq
in []*partData
cb func()
t *time.Timer
once sync.Once
cleaned bool
}
func (w *watchFetch) push(nbytes int) {
w.need -= nbytes
if w.need <= 0 {
w.once.Do(func() {
go w.cb()
})
}
}
func (w *watchFetch) deleted() {
w.once.Do(func() {
go w.cb()
})
}
func (w *watchFetch) cleanup(c *Cluster) {
w.cleaned = true
for _, in := range w.in {
delete(in.watch, w)
}
w.t.Stop()
}