Skip to content

Commit 00d56e1

Browse files
committed
Add failing test for #1411
1 parent 3f68080 commit 00d56e1

1 file changed

Lines changed: 112 additions & 0 deletions

File tree

reader_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
"testing"
1616
"time"
1717

18+
"github.com/segmentio/kafka-go/compress/gzip"
19+
"github.com/segmentio/kafka-go/compress/lz4"
20+
"github.com/segmentio/kafka-go/compress/snappy"
1821
"github.com/stretchr/testify/require"
1922
)
2023

@@ -1996,3 +1999,112 @@ func testReaderTopicRecreated(t *testing.T, ctx context.Context, r *Reader) {
19961999
_, err = r.ReadMessage(ctx)
19972000
require.ErrorIs(t, err, OffsetOutOfRange)
19982001
}
2002+
2003+
// TestReaderCompressedCompactedTopicCPUSpinning reproduces the segmentio/kafka-go
2004+
// bug where readers get stuck in an infinite CPU spinning loop when processing
2005+
// compressed batches from compacted topics.
2006+
//
2007+
// Root Cause: When decompressing message batches, the lengthRemain field is not
2008+
// updated to match the decompressed data size, causing lengthRemain to go negative.
2009+
// This breaks the compaction offset-skipping logic in batch.go:283, leading to
2010+
// an endless loop where the same compressed batch is processed repeatedly.
2011+
//
2012+
// Expected Behavior:
2013+
// - Without fix: Test times out due to CPU spinning in (*reader).read
2014+
// - With PR #1411 fix: Test passes, reading messages successfully.
2015+
func TestReaderCompressedCompactedTopicCPUSpinning(t *testing.T) {
2016+
topic := makeTopic()
2017+
createTopicWithCompaction(t, topic, 1)
2018+
defer deleteTopic(t, topic)
2019+
2020+
msgs := makeTestDuplicateSequence()
2021+
2022+
// Write compressed messages that will be compacted
2023+
writeCompressedMessagesForCompaction(t, topic, msgs)
2024+
2025+
expectedKeys := map[string]int{}
2026+
for _, msg := range msgs {
2027+
expectedKeys[string(msg.Key)] = 1
2028+
}
2029+
2030+
// Use shorter timeout to detect CPU spinning faster
2031+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
2032+
defer cancel()
2033+
2034+
// Wait for compaction to occur (may take time on slower systems)
2035+
time.Sleep(2 * time.Second)
2036+
2037+
for {
2038+
success := func() bool {
2039+
r := NewReader(ReaderConfig{
2040+
Brokers: []string{"localhost:9092"},
2041+
Topic: topic,
2042+
MinBytes: 200,
2043+
MaxBytes: 200,
2044+
MaxWait: 100 * time.Millisecond,
2045+
})
2046+
defer r.Close()
2047+
2048+
keys := map[string]int{}
2049+
2050+
// This loop should NOT hang indefinitely
2051+
// If it does, we have the CPU spinning bug
2052+
for {
2053+
m, err := r.FetchMessage(ctx)
2054+
if err != nil {
2055+
if errors.Is(err, context.DeadlineExceeded) {
2056+
t.Fatalf("Reader CPU spinning detected - stuck in (*reader).read after processing compressed compacted batches. This indicates the lengthRemain bug in message_reader.go")
2057+
}
2058+
t.Logf("can't get message from compacted log: %v", err)
2059+
return false
2060+
}
2061+
keys[string(m.Key)]++
2062+
2063+
if len(keys) == countKeys(msgs) {
2064+
t.Logf("got keys: %+v", keys)
2065+
return reflect.DeepEqual(keys, expectedKeys)
2066+
}
2067+
}
2068+
}()
2069+
if success {
2070+
return
2071+
}
2072+
select {
2073+
case <-ctx.Done():
2074+
t.Fatal("Test timed out - likely due to CPU spinning bug in compressed compacted topic processing")
2075+
default:
2076+
}
2077+
}
2078+
}
2079+
2080+
// writeCompressedMessagesForCompaction writes compressed messages with specific
2081+
// writer configuration that combines compression with compaction-friendly patterns.
2082+
// This helper creates the exact conditions needed to reproduce the lengthRemain bug.
2083+
func writeCompressedMessagesForCompaction(t *testing.T, topic string, msgs []Message) {
2084+
t.Helper()
2085+
2086+
// Test multiple compression codecs to ensure the bug affects all of them
2087+
codecs := []CompressionCodec{
2088+
&gzip.Codec{},
2089+
&snappy.Codec{},
2090+
&lz4.Codec{},
2091+
}
2092+
2093+
for _, codec := range codecs {
2094+
wr := NewWriter(WriterConfig{
2095+
Brokers: []string{"localhost:9092"},
2096+
Topic: topic,
2097+
// KEY: Enable compression - this creates compressed batches
2098+
CompressionCodec: codec,
2099+
// Small batch size ensures multiple batches and triggers compaction edge cases
2100+
BatchSize: 3,
2101+
Async: false,
2102+
// Use manual balancer for predictable behavior
2103+
Balancer: &LeastBytes{},
2104+
})
2105+
defer wr.Close()
2106+
2107+
err := wr.WriteMessages(context.Background(), msgs...)
2108+
require.NoError(t, err)
2109+
}
2110+
}

0 commit comments

Comments
 (0)