From 3b4f830ff13b6f1022e638a50d5abfb1fc35821d Mon Sep 17 00:00:00 2001 From: caiyi0616 <2056014003@qq.com> Date: Fri, 4 Sep 2026 16:38:13 +0800 Subject: [PATCH] fix(repeating_group): add Reset() method to support reusable instances Implement RepeatingGroup.Reset() to clear all groups without reallocating the template or struct. This addresses issue #758, where repeated message parsing required new RepeatingGroup instances for each parse, causing unnecessary allocations in high-throughput FIX message processing scenarios. Changes: - Add RepeatingGroup.Reset() that clears groups slice (keeps capacity) - Add test_repeating_group_reset.go covering Reset() behavior and multi-cycle reuse without reallocation --- repeating_group.go | 7 +++++ repeating_group_test.go | 67 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/repeating_group.go b/repeating_group.go index 853cc7d35..b2dc39809 100644 --- a/repeating_group.go +++ b/repeating_group.go @@ -121,6 +121,13 @@ func (f *RepeatingGroup) Add() *Group { return g } +// Reset clears all groups from this RepeatingGroup, allowing the instance to be +// reused for a new message parse without reallocating the template and struct. +// This avoids the allocation overhead of calling NewRepeatingGroup for each parse. +func (f *RepeatingGroup) Reset() { + f.groups = f.groups[:0] +} + // Write returns tagValues for all Items in the repeating group ordered by // Group sequence and Group template order. func (f RepeatingGroup) Write() []TagValue { diff --git a/repeating_group_test.go b/repeating_group_test.go index abd316d78..5248acb24 100644 --- a/repeating_group_test.go +++ b/repeating_group_test.go @@ -17,6 +17,7 @@ package quickfix import ( "bytes" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -291,3 +292,69 @@ func TestRepeatingGroup_ReadComplete(t *testing.T) { } } } + +func TestRepeatingGroup_Reset(t *testing.T) { + // Issue #758: RepeatingGroup should support Reset() to avoid reallocation + template := GroupTemplate{GroupElement(1), GroupElement(2)} + rg := NewRepeatingGroup(Tag(100), template) + + // Add two groups + g1 := rg.Add() + g1.SetField(Tag(1), FIXString("hello")) + g1.SetField(Tag(2), FIXString("world")) + + g2 := rg.Add() + g2.SetField(Tag(1), FIXString("foo")) + g2.SetField(Tag(2), FIXString("bar")) + + require.Equal(t, 2, rg.Len()) + + // Reset and reuse + rg.Reset() + require.Equal(t, 0, rg.Len()) + + // Add new groups after reset + g3 := rg.Add() + g3.SetField(Tag(1), FIXString("reused")) + g3.SetField(Tag(2), FIXString("instance")) + + require.Equal(t, 1, rg.Len()) + + // Verify the new group has correct data + var v1, v2 FIXString + require.Nil(t, rg.groups[0].GetField(Tag(1), &v1)) + require.Nil(t, rg.groups[0].GetField(Tag(2), &v2)) + require.Equal(t, "reused", string(v1)) + require.Equal(t, "instance", string(v2)) + + // Tag and template should be unchanged + require.Equal(t, Tag(100), rg.Tag()) + require.Equal(t, 2, len(rg.template)) +} + +func TestRepeatingGroup_ResetMultipleCycles(t *testing.T) { + template := GroupTemplate{GroupElement(1)} + rg := NewRepeatingGroup(Tag(50), template) + + // Simulate multiple message parses without reallocation + for cycle := 0; cycle < 3; cycle++ { + numGroups := cycle + 1 + for i := 0; i < numGroups; i++ { + g := rg.Add() + g.SetField(Tag(1), FIXString(fmt.Sprintf("cycle-%d-group-%d", cycle, i))) + } + + require.Equal(t, numGroups, rg.Len()) + + // Verify data + for i := 0; i < numGroups; i++ { + var v FIXString + require.Nil(t, rg.groups[i].GetField(Tag(1), &v)) + require.Equal(t, fmt.Sprintf("cycle-%d-group-%d", cycle, i), string(v)) + } + + // Reset for next cycle + rg.Reset() + require.Equal(t, 0, rg.Len()) + } +}