From 22e752bc4a12438c9e6c87c5f7f3a565a21d71e7 Mon Sep 17 00:00:00 2001
From: Matej Spiller Muys <matej.spiller-muys@bitstamp.net>
Date: Sun, 22 Dec 2024 21:40:10 +0100
Subject: [PATCH] Replace locking fieldmap with concurrent safe haxmap

---
 .gitignore              |   1 +
 field_map.go            | 193 ++++++++--------------------------------
 go.mod                  |  20 +++--
 go.sum                  |  55 +++++-------
 message.go              |  43 ++++-----
 repeating_group.go      |  13 +--
 repeating_group_test.go |   2 -
 7 files changed, 94 insertions(+), 233 deletions(-)

diff --git a/.gitignore b/.gitignore
index fb92ea1b5..87aece5f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 *.swp
 *.swo
 .idea
+*.iml
 vendor
 _test/test
 _test/echo_server
diff --git a/field_map.go b/field_map.go
index 4aac64b1d..7e69a0d0c 100644
--- a/field_map.go
+++ b/field_map.go
@@ -17,9 +17,10 @@ package quickfix
 
 import (
 	"bytes"
-	"sort"
-	"sync"
+	"slices"
 	"time"
+
+	"github.com/alphadose/haxmap"
 )
 
 // field stores a slice of TagValues.
@@ -40,46 +41,33 @@ func writeField(f field, buffer *bytes.Buffer) {
 }
 
 // tagOrder true if tag i should occur before tag j.
-type tagOrder func(i, j Tag) bool
-
-type tagSort struct {
-	tags    []Tag
-	compare tagOrder
-}
-
-func (t tagSort) Len() int           { return len(t.tags) }
-func (t tagSort) Swap(i, j int)      { t.tags[i], t.tags[j] = t.tags[j], t.tags[i] }
-func (t tagSort) Less(i, j int) bool { return t.compare(t.tags[i], t.tags[j]) }
+type tagOrder func(i, j Tag) int
 
 // FieldMap is a collection of fix fields that make up a fix message.
 type FieldMap struct {
-	tagLookup map[Tag]field
-	tagSort
-	rwLock *sync.RWMutex
+	tagLookup *haxmap.Map[Tag, field]
+	compare   tagOrder
 }
 
 // ascending tags.
-func normalFieldOrder(i, j Tag) bool { return i < j }
+func normalFieldOrder(i, j Tag) int { return int(i - j) }
 
 func (m *FieldMap) init() {
 	m.initWithOrdering(normalFieldOrder)
 }
 
 func (m *FieldMap) initWithOrdering(ordering tagOrder) {
-	m.rwLock = &sync.RWMutex{}
-	m.tagLookup = make(map[Tag]field)
+	m.tagLookup = haxmap.New[Tag, field]()
 	m.compare = ordering
 }
 
 // Tags returns all of the Field Tags in this FieldMap.
 func (m FieldMap) Tags() []Tag {
-	m.rwLock.RLock()
-	defer m.rwLock.RUnlock()
-
-	tags := make([]Tag, 0, len(m.tagLookup))
-	for t := range m.tagLookup {
-		tags = append(tags, t)
-	}
+	var tags []Tag
+	m.tagLookup.ForEach(func(tag Tag, _ field) bool {
+		tags = append(tags, tag)
+		return true
+	})
 
 	return tags
 }
@@ -91,33 +79,13 @@ func (m FieldMap) Get(parser Field) MessageRejectError {
 
 // Has returns true if the Tag is present in this FieldMap.
 func (m FieldMap) Has(tag Tag) bool {
-	m.rwLock.RLock()
-	defer m.rwLock.RUnlock()
-
-	_, ok := m.tagLookup[tag]
+	_, ok := m.tagLookup.Get(tag)
 	return ok
 }
 
 // GetField parses of a field with Tag tag. Returned reject may indicate the field is not present, or the field value is invalid.
 func (m FieldMap) GetField(tag Tag, parser FieldValueReader) MessageRejectError {
-	m.rwLock.RLock()
-	defer m.rwLock.RUnlock()
-
-	f, ok := m.tagLookup[tag]
-	if !ok {
-		return ConditionallyRequiredFieldMissing(tag)
-	}
-
-	if err := parser.Read(f[0].value); err != nil {
-		return IncorrectDataFormatForValue(tag)
-	}
-
-	return nil
-}
-
-// GetField parses of a field with Tag tag. Returned reject may indicate the field is not present, or the field value is invalid.
-func (m FieldMap) getFieldNoLock(tag Tag, parser FieldValueReader) MessageRejectError {
-	f, ok := m.tagLookup[tag]
+	f, ok := m.tagLookup.Get(tag)
 	if !ok {
 		return ConditionallyRequiredFieldMissing(tag)
 	}
@@ -131,20 +99,7 @@ func (m FieldMap) getFieldNoLock(tag Tag, parser FieldValueReader) MessageReject
 
 // GetBytes is a zero-copy GetField wrapper for []bytes fields.
 func (m FieldMap) GetBytes(tag Tag) ([]byte, MessageRejectError) {
-	m.rwLock.RLock()
-	defer m.rwLock.RUnlock()
-
-	f, ok := m.tagLookup[tag]
-	if !ok {
-		return nil, ConditionallyRequiredFieldMissing(tag)
-	}
-
-	return f[0].value, nil
-}
-
-// getBytesNoLock is a lock free zero-copy GetField wrapper for []bytes fields.
-func (m FieldMap) getBytesNoLock(tag Tag) ([]byte, MessageRejectError) {
-	f, ok := m.tagLookup[tag]
+	f, ok := m.tagLookup.Get(tag)
 	if !ok {
 		return nil, ConditionallyRequiredFieldMissing(tag)
 	}
@@ -176,26 +131,8 @@ func (m FieldMap) GetInt(tag Tag) (int, MessageRejectError) {
 	return int(val), err
 }
 
-// GetInt is a lock free GetField wrapper for int fields.
-func (m FieldMap) getIntNoLock(tag Tag) (int, MessageRejectError) {
-	bytes, err := m.getBytesNoLock(tag)
-	if err != nil {
-		return 0, err
-	}
-
-	var val FIXInt
-	if val.Read(bytes) != nil {
-		err = IncorrectDataFormatForValue(tag)
-	}
-
-	return int(val), err
-}
-
 // GetTime is a GetField wrapper for utc timestamp fields.
 func (m FieldMap) GetTime(tag Tag) (t time.Time, err MessageRejectError) {
-	m.rwLock.RLock()
-	defer m.rwLock.RUnlock()
-
 	bytes, err := m.GetBytes(tag)
 	if err != nil {
 		return
@@ -218,21 +155,9 @@ func (m FieldMap) GetString(tag Tag) (string, MessageRejectError) {
 	return string(val), nil
 }
 
-// GetString is a GetField wrapper for string fields.
-func (m FieldMap) getStringNoLock(tag Tag) (string, MessageRejectError) {
-	var val FIXString
-	if err := m.getFieldNoLock(tag, &val); err != nil {
-		return "", err
-	}
-	return string(val), nil
-}
-
 // GetGroup is a Get function specific to Group Fields.
 func (m FieldMap) GetGroup(parser FieldGroupReader) MessageRejectError {
-	m.rwLock.RLock()
-	defer m.rwLock.RUnlock()
-
-	f, ok := m.tagLookup[parser.Tag()]
+	f, ok := m.tagLookup.Get(parser.Tag())
 	if !ok {
 		return ConditionallyRequiredFieldMissing(parser.Tag())
 	}
@@ -277,67 +202,38 @@ func (m *FieldMap) SetString(tag Tag, value string) *FieldMap {
 
 // Remove removes a tag from field map.
 func (m *FieldMap) Remove(tag Tag) {
-	m.rwLock.Lock()
-	defer m.rwLock.Unlock()
-
-	delete(m.tagLookup, tag)
+	m.tagLookup.Del(tag)
 }
 
 // Clear purges all fields from field map.
 func (m *FieldMap) Clear() {
-	m.rwLock.Lock()
-	defer m.rwLock.Unlock()
-
-	m.tags = m.tags[0:0]
-	for k := range m.tagLookup {
-		delete(m.tagLookup, k)
-	}
-}
-
-func (m *FieldMap) clearNoLock() {
-	m.tags = m.tags[0:0]
-	for k := range m.tagLookup {
-		delete(m.tagLookup, k)
-	}
+	m.tagLookup.Clear()
 }
 
 // CopyInto overwrites the given FieldMap with this one.
 func (m *FieldMap) CopyInto(to *FieldMap) {
-	m.rwLock.RLock()
-	defer m.rwLock.RUnlock()
-
-	to.tagLookup = make(map[Tag]field)
-	for tag, f := range m.tagLookup {
+	to.tagLookup = haxmap.New[Tag, field]()
+	m.tagLookup.ForEach(func(tag Tag, f field) bool {
 		clone := make(field, 1)
 		clone[0] = f[0]
-		to.tagLookup[tag] = clone
-	}
-	to.tags = make([]Tag, len(m.tags))
-	copy(to.tags, m.tags)
+		to.tagLookup.Set(tag, clone)
+		return true
+	})
 	to.compare = m.compare
 }
 
 func (m *FieldMap) add(f field) {
-	t := fieldTag(f)
-	if _, ok := m.tagLookup[t]; !ok {
-		m.tags = append(m.tags, t)
-	}
-
-	m.tagLookup[t] = f
+	m.tagLookup.Set(fieldTag(f), f)
 }
 
 func (m *FieldMap) getOrCreate(tag Tag) field {
-	m.rwLock.Lock()
-	defer m.rwLock.Unlock()
-
-	if f, ok := m.tagLookup[tag]; ok {
+	if f, ok := m.tagLookup.Get(tag); ok {
 		f = f[:1]
 		return f
 	}
 
 	f := make(field, 1)
-	m.tagLookup[tag] = f
-	m.tags = append(m.tags, tag)
+	m.tagLookup.Set(tag, f)
 	return f
 }
 
@@ -350,39 +246,27 @@ func (m *FieldMap) Set(field FieldWriter) *FieldMap {
 
 // SetGroup is a setter specific to group fields.
 func (m *FieldMap) SetGroup(field FieldGroupWriter) *FieldMap {
-	m.rwLock.Lock()
-	defer m.rwLock.Unlock()
-
-	_, ok := m.tagLookup[field.Tag()]
-	if !ok {
-		m.tags = append(m.tags, field.Tag())
-	}
-	m.tagLookup[field.Tag()] = field.Write()
+	m.tagLookup.Set(field.Tag(), field.Write())
 	return m
 }
 
 func (m *FieldMap) sortedTags() []Tag {
-	sort.Sort(m)
-	return m.tags
+	tags := m.Tags()
+	slices.SortFunc(tags, m.compare)
+	return tags
 }
 
 func (m FieldMap) write(buffer *bytes.Buffer) {
-	m.rwLock.Lock()
-	defer m.rwLock.Unlock()
-
 	for _, tag := range m.sortedTags() {
-		if f, ok := m.tagLookup[tag]; ok {
+		if f, ok := m.tagLookup.Get(tag); ok {
 			writeField(f, buffer)
 		}
 	}
 }
 
 func (m FieldMap) total() int {
-	m.rwLock.RLock()
-	defer m.rwLock.RUnlock()
-
 	total := 0
-	for _, fields := range m.tagLookup {
+	m.tagLookup.ForEach(func(_ Tag, fields field) bool {
 		for _, tv := range fields {
 			switch tv.tag {
 			case tagCheckSum: // Tag does not contribute to total.
@@ -390,17 +274,15 @@ func (m FieldMap) total() int {
 				total += tv.total()
 			}
 		}
-	}
+		return true
+	})
 
 	return total
 }
 
 func (m FieldMap) length() int {
-	m.rwLock.RLock()
-	defer m.rwLock.RUnlock()
-
 	length := 0
-	for _, fields := range m.tagLookup {
+	m.tagLookup.ForEach(func(_ Tag, fields field) bool {
 		for _, tv := range fields {
 			switch tv.tag {
 			case tagBeginString, tagBodyLength, tagCheckSum: // Tags do not contribute to length.
@@ -408,7 +290,8 @@ func (m FieldMap) length() int {
 				length += tv.length()
 			}
 		}
-	}
+		return true
+	})
 
 	return length
 }
diff --git a/go.mod b/go.mod
index 8b36cf024..bfebe1791 100644
--- a/go.mod
+++ b/go.mod
@@ -3,13 +3,14 @@ module github.com/quickfixgo/quickfix
 go 1.21
 
 require (
+	github.com/alphadose/haxmap v1.4.1
 	github.com/mattn/go-sqlite3 v1.14.22
 	github.com/pires/go-proxyproto v0.7.0
 	github.com/pkg/errors v0.9.1
 	github.com/shopspring/decimal v1.4.0
-	github.com/stretchr/testify v1.8.4
-	go.mongodb.org/mongo-driver v1.15.0
-	golang.org/x/net v0.24.0
+	github.com/stretchr/testify v1.10.0
+	go.mongodb.org/mongo-driver v1.17.1
+	golang.org/x/net v0.33.0
 )
 
 require (
@@ -17,16 +18,17 @@ require (
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/klauspost/compress v1.15.12 // indirect
 	github.com/kr/text v0.2.0 // indirect
-	github.com/montanaflynn/stats v0.6.6 // indirect
+	github.com/montanaflynn/stats v0.7.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/stretchr/objx v0.5.0 // indirect
+	github.com/stretchr/objx v0.5.2 // indirect
 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 	github.com/xdg-go/scram v1.1.2 // indirect
 	github.com/xdg-go/stringprep v1.0.4 // indirect
-	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
-	golang.org/x/crypto v0.22.0 // indirect
-	golang.org/x/sync v0.1.0 // indirect
-	golang.org/x/text v0.14.0 // indirect
+	github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
+	golang.org/x/crypto v0.31.0 // indirect
+	golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect
+	golang.org/x/sync v0.10.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index a7e47992c..997c6d271 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,12 @@
+github.com/alphadose/haxmap v1.4.1 h1:VtD6VCxUkjNIfJk/aWdYFfOzrRddDFjmvmRmILg7x8Q=
+github.com/alphadose/haxmap v1.4.1/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
 github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
@@ -16,8 +17,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
 github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
-github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ=
-github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
+github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
+github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
 github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
 github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -26,43 +27,38 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
 github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
 github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
 github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
 github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
-github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
-github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
-go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
+go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
+go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
-golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
+golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE=
+golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
-golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -73,17 +69,14 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/message.go b/message.go
index 3e1e7202b..7705e5763 100644
--- a/message.go
+++ b/message.go
@@ -41,7 +41,7 @@ type msgParser struct {
 }
 
 // in the message header, the first 3 tags in the message header must be 8,9,35.
-func headerFieldOrdering(i, j Tag) bool {
+func headerFieldOrdering(i, j Tag) int {
 	var ordering = func(t Tag) uint32 {
 		switch t {
 		case tagBeginString:
@@ -60,12 +60,12 @@ func headerFieldOrdering(i, j Tag) bool {
 
 	switch {
 	case orderi < orderj:
-		return true
+		return -1
 	case orderi > orderj:
-		return false
+		return 1
 	}
 
-	return i < j
+	return int(i - j)
 }
 
 // Init initializes the Header instance.
@@ -85,15 +85,15 @@ func (b *Body) Init() {
 type Trailer struct{ FieldMap }
 
 // In the trailer, CheckSum (tag 10) must be last.
-func trailerFieldOrdering(i, j Tag) bool {
+func trailerFieldOrdering(i, j Tag) int {
 	switch {
 	case i == tagCheckSum:
-		return false
+		return 1
 	case j == tagCheckSum:
-		return true
+		return -1
 	}
 
-	return i < j
+	return int(i - j)
 }
 
 // Init initializes the FIX message.
@@ -181,17 +181,10 @@ func ParseMessageWithDataDictionary(
 
 // doParsing executes the message parsing process.
 func doParsing(mp *msgParser) (err error) {
-	mp.msg.Header.rwLock.Lock()
-	defer mp.msg.Header.rwLock.Unlock()
-	mp.msg.Body.rwLock.Lock()
-	defer mp.msg.Body.rwLock.Unlock()
-	mp.msg.Trailer.rwLock.Lock()
-	defer mp.msg.Trailer.rwLock.Unlock()
-
 	// Initialize for parsing.
-	mp.msg.Header.clearNoLock()
-	mp.msg.Body.clearNoLock()
-	mp.msg.Trailer.clearNoLock()
+	mp.msg.Header.Clear()
+	mp.msg.Body.Clear()
+	mp.msg.Trailer.Clear()
 
 	// Allocate expected message fields in one chunk.
 	fieldCount := bytes.Count(mp.rawBytes, []byte{'\001'})
@@ -269,7 +262,7 @@ func doParsing(mp *msgParser) (err error) {
 		}
 
 		if mp.parsedFieldBytes.tag == tagXMLDataLen {
-			xmlDataLen, _ = mp.msg.Header.getIntNoLock(tagXMLDataLen)
+			xmlDataLen, _ = mp.msg.Header.GetInt(tagXMLDataLen)
 		}
 		mp.fieldIndex++
 	}
@@ -294,7 +287,7 @@ func doParsing(mp *msgParser) (err error) {
 		}
 	}
 
-	bodyLength, err := mp.msg.Header.getIntNoLock(tagBodyLength)
+	bodyLength, err := mp.msg.Header.GetInt(tagBodyLength)
 	if err != nil {
 		err = parseError{OrigError: err.Error()}
 	} else if length != bodyLength && !xmlDataMsg {
@@ -328,7 +321,7 @@ func parseGroup(mp *msgParser, tags []Tag) {
 			// Add the field member to the group.
 			dm = append(dm, *mp.parsedFieldBytes)
 		} else if isHeaderField(mp.parsedFieldBytes.tag, mp.transportDataDictionary) {
-			// Found a header tag for some reason..
+			// Found a header tag for some reason.
 			mp.msg.Body.add(dm)
 			mp.msg.Header.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1])
 			break
@@ -375,7 +368,7 @@ func parseGroup(mp *msgParser, tags []Tag) {
 // tags slice will contain multiple tags if the tag in question is found while processing a group already.
 func isNumInGroupField(msg *Message, tags []Tag, appDataDictionary *datadictionary.DataDictionary) bool {
 	if appDataDictionary != nil {
-		msgt, err := msg.msgTypeNoLock()
+		msgt, err := msg.MsgType()
 		if err != nil {
 			return false
 		}
@@ -408,7 +401,7 @@ func isNumInGroupField(msg *Message, tags []Tag, appDataDictionary *datadictiona
 // tags slice will contain multiple tags if the tag in question is found while processing a group already.
 func getGroupFields(msg *Message, tags []Tag, appDataDictionary *datadictionary.DataDictionary) (fields []*datadictionary.FieldDef) {
 	if appDataDictionary != nil {
-		msgt, err := msg.msgTypeNoLock()
+		msgt, err := msg.MsgType()
 		if err != nil {
 			return
 		}
@@ -478,10 +471,6 @@ func (m *Message) MsgType() (string, MessageRejectError) {
 	return m.Header.GetString(tagMsgType)
 }
 
-func (m *Message) msgTypeNoLock() (string, MessageRejectError) {
-	return m.Header.getStringNoLock(tagMsgType)
-}
-
 // IsMsgTypeOf returns true if the Header contains MsgType (tag 35) field and its value is the specified one.
 func (m *Message) IsMsgTypeOf(msgType string) bool {
 	if v, err := m.MsgType(); err == nil {
diff --git a/repeating_group.go b/repeating_group.go
index 853cc7d35..b494e6e04 100644
--- a/repeating_group.go
+++ b/repeating_group.go
@@ -129,13 +129,11 @@ func (f RepeatingGroup) Write() []TagValue {
 
 	for _, group := range f.groups {
 		tags := group.sortedTags()
-		group.rwLock.RLock()
 		for _, tag := range tags {
-			if fields, ok := group.tagLookup[tag]; ok {
+			if fields, ok := group.tagLookup.Get(tag); ok {
 				tvs = append(tvs, fields...)
 			}
 		}
-		group.rwLock.RUnlock()
 	}
 
 	return tvs
@@ -159,7 +157,7 @@ func (f RepeatingGroup) groupTagOrder() tagOrder {
 		tagMap[f.Tag()] = i
 	}
 
-	return func(i, j Tag) bool {
+	return func(i, j Tag) int {
 		orderi := math.MaxInt32
 		orderj := math.MaxInt32
 
@@ -171,7 +169,7 @@ func (f RepeatingGroup) groupTagOrder() tagOrder {
 			orderj = jIndex
 		}
 
-		return orderi < orderj
+		return orderi - orderj
 	}
 }
 
@@ -215,10 +213,7 @@ func (f *RepeatingGroup) Read(tv []TagValue) ([]TagValue, error) {
 			f.groups = append(f.groups, group)
 		}
 
-		group.rwLock.Lock()
-		group.tagLookup[tvRange[0].tag] = tvRange
-		group.tags = append(group.tags, gi.Tag())
-		group.rwLock.Unlock()
+		group.tagLookup.Set(tvRange[0].tag, tvRange)
 	}
 
 	if len(f.groups) != expectedGroupSize {
diff --git a/repeating_group_test.go b/repeating_group_test.go
index abd316d78..437e41790 100644
--- a/repeating_group_test.go
+++ b/repeating_group_test.go
@@ -200,8 +200,6 @@ func TestRepeatingGroup_Read(t *testing.T) {
 			for _, expected := range test.expectedGroupTvs[g] {
 				var actual FIXString
 				require.Nil(t, group.GetField(expected.tag, &actual))
-				require.NotNil(t, group.tags)
-				require.Equal(t, len(group.tags), len(group.tagLookup))
 
 				if !bytes.Equal(expected.value, []byte(actual)) {
 					t.Errorf("%v, %v: expected %s, got %s", g, expected.tag, expected.value, actual)