From 9a0776c5d8d71e79f607439cf647c28a5d5714a0 Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 19 Sep 2025 11:59:16 +0100 Subject: [PATCH 1/8] feat: add new struct SyncOrderedMap --- v3/syncorderedmap.go | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 v3/syncorderedmap.go diff --git a/v3/syncorderedmap.go b/v3/syncorderedmap.go new file mode 100644 index 0000000..07c25d6 --- /dev/null +++ b/v3/syncorderedmap.go @@ -0,0 +1,8 @@ +package orderedmap + +import "sync" + +type SyncOrderedMap[K comparable, V any] struct { + OrderedMap[K, V] + sync.RWMutex +} From 9862fd53d3b736dfc16ef2fac19fc07196224880 Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 19 Sep 2025 12:00:28 +0100 Subject: [PATCH 2/8] feat: implement sync version for OrderMap methods --- v3/syncorderedmap.go | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/v3/syncorderedmap.go b/v3/syncorderedmap.go index 07c25d6..871f6eb 100644 --- a/v3/syncorderedmap.go +++ b/v3/syncorderedmap.go @@ -6,3 +6,58 @@ type SyncOrderedMap[K comparable, V any] struct { OrderedMap[K, V] sync.RWMutex } +func (m *SyncOrderedMap[K, V]) Get(key K) (value V, ok bool) { + m.RLock() + defer m.RUnlock() + + return m.OrderedMap.Get(key) +} + +func (m *SyncOrderedMap[K, V]) Set(key K, value V) bool { + m.Lock() + defer m.Unlock() + + return m.OrderedMap.Set(key, value) +} + +func (m *SyncOrderedMap[K, V]) ReplaceKey(originalKey, newKey K) bool { + m.Lock() + defer m.Unlock() + + return m.OrderedMap.ReplaceKey(originalKey, newKey) +} + +func (m *SyncOrderedMap[K, V]) GetOrDefault(key K, defaultValue V) V { + m.RLock() + defer m.RUnlock() + + return m.OrderedMap.GetOrDefault(key, defaultValue) +} + +func (m *SyncOrderedMap[K, V]) Len() int { + m.RLock() + defer m.RUnlock() + + return m.OrderedMap.Len() +} + +func (m *SyncOrderedMap[K, V]) Delete(key K) (didDelete bool) { + m.Lock() + defer m.Unlock() + + return m.OrderedMap.Delete(key) +} + +func (m *SyncOrderedMap[K, V]) Copy() *OrderedMap[K, V] { + m.RLock() + defer m.RUnlock() + + return m.OrderedMap.Copy() +} + +func (m *SyncOrderedMap[K, V]) Has(key K) bool { + m.RLock() + defer m.RUnlock() + + return m.OrderedMap.Has(key) +} From 1614cef249840901e8cd2c53e96568aef031c28c Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 19 Sep 2025 12:31:20 +0100 Subject: [PATCH 3/8] feat: add constructor for SyncOrderedMap --- v3/syncorderedmap.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/v3/syncorderedmap.go b/v3/syncorderedmap.go index 871f6eb..3f300b1 100644 --- a/v3/syncorderedmap.go +++ b/v3/syncorderedmap.go @@ -6,6 +6,15 @@ type SyncOrderedMap[K comparable, V any] struct { OrderedMap[K, V] sync.RWMutex } + +func NewSyncOrderedMap[K comparable, V any]() *SyncOrderedMap[K, V] { + return &SyncOrderedMap[K, V]{*NewOrderedMap[K, V](), sync.RWMutex{}} +} + +func NewSyncOrderedMapWithCapacity[K comparable, V any](capacity int) *SyncOrderedMap[K, V] { + return &SyncOrderedMap[K, V]{*NewOrderedMapWithCapacity[K, V](capacity), sync.RWMutex{}} +} + func (m *SyncOrderedMap[K, V]) Get(key K) (value V, ok bool) { m.RLock() defer m.RUnlock() From 55bb2ba263eacca84eb1928b70753bff76e97f23 Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 19 Sep 2025 12:34:04 +0100 Subject: [PATCH 4/8] test: add testing for race condition on SyncOrderedMap --- v3/syncorderedmap_test.go | 103 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 v3/syncorderedmap_test.go diff --git a/v3/syncorderedmap_test.go b/v3/syncorderedmap_test.go new file mode 100644 index 0000000..4427ddf --- /dev/null +++ b/v3/syncorderedmap_test.go @@ -0,0 +1,103 @@ +package orderedmap_test + +import ( + "fmt" + "math/rand" + "sync" + "testing" + + "github.com/elliotchance/orderedmap/v3" +) + +func TestRaceCondition(t *testing.T) { + m := orderedmap.NewSyncOrderedMap[int, int]() + wg := &sync.WaitGroup{} + + var asyncGet = func() { + wg.Add(1) + go func() { + key := rand.Intn(100) + m.Get(key) + wg.Done() + }() + } + + var asyncSet = func() { + wg.Add(1) + go func() { + key := rand.Intn(100) + value := rand.Intn(100) + m.Set(key, value) + wg.Done() + }() + } + + var asyncDelete = func() { + wg.Add(1) + go func() { + key := rand.Intn(100) + m.Delete(key) + wg.Done() + }() + } + + var asyncHas = func() { + wg.Add(1) + go func() { + key := rand.Intn(100) + m.Has(key) + wg.Done() + }() + } + + var asyncReplaceKEy = func() { + wg.Add(1) + go func() { + key := rand.Intn(100) + newKey := rand.Intn(100) + m.ReplaceKey(key, newKey) + wg.Done() + }() + } + + var asyncGetOrDefault = func() { + wg.Add(1) + go func() { + key := rand.Intn(100) + def := rand.Intn(100) + m.GetOrDefault(key, def) + wg.Done() + }() + } + + var asyncLen = func() { + wg.Add(1) + go func() { + m.Len() + wg.Done() + }() + } + + var asyncCopy = func() { + wg.Add(1) + go func() { + m.Copy() + wg.Done() + }() + } + + for i := 0; i < 10000; i++ { + asyncSet() + asyncGet() + asyncDelete() + asyncHas() + asyncLen() + asyncReplaceKEy() + asyncGetOrDefault() + asyncCopy() + } + + wg.Wait() + fmt.Println("TestRaceCondition completed") + fmt.Printf("SyncOrderedMap eventually has %v elements\n", m.Len()) +} From 2856db79b53c3180aa90f97c5e70e11f0ff437b1 Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 2 Jan 2026 12:06:10 +0000 Subject: [PATCH 5/8] feat: add async GetElement --- v3/syncorderedmap.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/v3/syncorderedmap.go b/v3/syncorderedmap.go index 3f300b1..e03df51 100644 --- a/v3/syncorderedmap.go +++ b/v3/syncorderedmap.go @@ -43,6 +43,13 @@ func (m *SyncOrderedMap[K, V]) GetOrDefault(key K, defaultValue V) V { return m.OrderedMap.GetOrDefault(key, defaultValue) } +func (m *SyncOrderedMap[K, V]) GetElement(key K) *Element[K, V] { + m.RLock() + defer m.RUnlock() + + return m.OrderedMap.GetElement(key) +} + func (m *SyncOrderedMap[K, V]) Len() int { m.RLock() defer m.RUnlock() From bf27d29f82a9ed96d9b6185561a02f7c5a7651a2 Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 2 Jan 2026 12:06:33 +0000 Subject: [PATCH 6/8] test: add test for async GetElement --- v3/syncorderedmap_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/v3/syncorderedmap_test.go b/v3/syncorderedmap_test.go index 4427ddf..fe0a04b 100644 --- a/v3/syncorderedmap_test.go +++ b/v3/syncorderedmap_test.go @@ -86,6 +86,18 @@ func TestRaceCondition(t *testing.T) { }() } + var asyncGetElement = func() { + wg.Add(1) + go func() { + key := rand.Intn(100) + e := m.GetElement(key) + if e != nil { + fmt.Println(e.Value) + } + wg.Done() + }() + } + for i := 0; i < 10000; i++ { asyncSet() asyncGet() @@ -95,6 +107,7 @@ func TestRaceCondition(t *testing.T) { asyncReplaceKEy() asyncGetOrDefault() asyncCopy() + asyncGetElement() } wg.Wait() From bc8c81ad687cf058513e3a523a9b430492f32126 Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 2 Jan 2026 12:07:41 +0000 Subject: [PATCH 7/8] test: add testing for manual usage of mutex in concurrent access context --- v3/syncorderedmap_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/v3/syncorderedmap_test.go b/v3/syncorderedmap_test.go index fe0a04b..43b1920 100644 --- a/v3/syncorderedmap_test.go +++ b/v3/syncorderedmap_test.go @@ -114,3 +114,34 @@ func TestRaceCondition(t *testing.T) { fmt.Println("TestRaceCondition completed") fmt.Printf("SyncOrderedMap eventually has %v elements\n", m.Len()) } + +func TestSyncOrderedMapMutex(t *testing.T) { + m := orderedmap.NewSyncOrderedMap[int, string]() + + for i := 0; i < 1000; i++ { + m.Set(i, fmt.Sprintf("value-%v", i)) + } + + stop := make(chan struct{}) + go func() { + i := 0 + for { + select { + case <-stop: + return + + default: + m.Set(i, fmt.Sprintf("new-value-%v", i)) + i++ + } + } + }() + + m.Lock() + for key := range m.Keys() { + m.OrderedMap.Get(key) + } + m.Unlock() + + stop <- struct{}{} +} From 74d1976b49682eaa613220f4462fce6f3a80267e Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 2 Jan 2026 12:16:55 +0000 Subject: [PATCH 8/8] test: fix test for async GetElement --- v3/syncorderedmap_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/v3/syncorderedmap_test.go b/v3/syncorderedmap_test.go index 43b1920..bed8577 100644 --- a/v3/syncorderedmap_test.go +++ b/v3/syncorderedmap_test.go @@ -92,7 +92,9 @@ func TestRaceCondition(t *testing.T) { key := rand.Intn(100) e := m.GetElement(key) if e != nil { - fmt.Println(e.Value) + m.RLock() + _ = e.Value + m.RUnlock() } wg.Done() }()