From d3cb67329fb957a8cb21fa2f9ba7ad2f67c34cc4 Mon Sep 17 00:00:00 2001 From: lanceadd <1196661499@qq.com> Date: Wed, 25 Feb 2026 17:19:12 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(container):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=B8=A6=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E7=9A=84=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E6=93=8D=E4=BD=9C=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 KVMap 中新增 GetOrSetFuncWithError 和 GetOrSetFuncLockWithError 方法 - 在 KVMap 中新增 SetIfNotExistFuncWithError 和 SetIfNotExistFuncLockWithError 方法 - 在 ListKVMap 中新增 GetOrSetFuncWithError 和 GetOrSetFuncLockWithError 方法 - 在 ListKVMap 中新增 SetIfNotExistFuncWithError 和 SetIfNotExistFuncLockWithError 方法 - 修改 Instance 函数返回值处理逻辑,使用 GetOrSetFuncLockWithError 替代原有实现 - 所有新方法支持回调函数返回错误并进行相应处理 --- container/gmap/gmap_hash_k_v_map.go | 87 ++++++++++++++++++++++++++ container/gmap/gmap_list_k_v_map.go | 94 +++++++++++++++++++++++++++-- database/gdb/gdb.go | 11 +--- 3 files changed, 178 insertions(+), 14 deletions(-) diff --git a/container/gmap/gmap_hash_k_v_map.go b/container/gmap/gmap_hash_k_v_map.go index c8f6372e09e..5dae5173327 100644 --- a/container/gmap/gmap_hash_k_v_map.go +++ b/container/gmap/gmap_hash_k_v_map.go @@ -333,6 +333,54 @@ func (m *KVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var { return gvar.New(m.GetOrSetFuncLock(key, f)) } +// GetOrSetFuncWithError returns the value by key, +// or sets value with returned value of callback function `f` if it does not exist +// and then returns this value. +// +// Note that, it does not add the value to the map if the returned value of `f` is nil +// or if `f` returns a non-nil error. +func (m *KVMap[K, V]) GetOrSetFuncWithError(key K, f func() (V, error)) (V, error) { + if v, ok := m.Search(key); ok { + return v, nil + } + value, err := f() + if err != nil { + var zero V + return zero, err + } + v, _ := m.doSetWithLockCheck(key, value) + return v, nil +} + +// GetOrSetFuncLockWithError returns the value by key, +// or sets value with returned value of callback function `f` if it does not exist +// and then returns this value. +// +// GetOrSetFuncLockWithError differs with GetOrSetFuncWithError function is that it executes function `f` +// with mutex.Lock of the hash map. +// +// Note that, it does not add the value to the map if the returned value of `f` is nil +// or if `f` returns a non-nil error. +func (m *KVMap[K, V]) GetOrSetFuncLockWithError(key K, f func() (V, error)) (V, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]V) + } + if v, ok := m.data[key]; ok { + return v, nil + } + value, err := f() + if err != nil { + var zero V + return zero, err + } + if !m.isNil(value) { + m.data[key] = value + } + return value, nil +} + // SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true. // It returns false if `key` exists, and `value` would be ignored. func (m *KVMap[K, V]) SetIfNotExist(key K, value V) bool { @@ -375,6 +423,45 @@ func (m *KVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool { return false } +// SetIfNotExistFuncWithError sets value with return value of callback function `f`, and then returns true. +// It returns false if `key` exists, and `value` would be ignored. +// It returns (false, error) if `f` returns a non-nil error, and `value` would not be stored. +func (m *KVMap[K, V]) SetIfNotExistFuncWithError(key K, f func() (V, error)) (bool, error) { + if m.Contains(key) { + return false, nil + } + value, err := f() + if err != nil { + return false, err + } + return m.SetIfNotExist(key, value), nil +} + +// SetIfNotExistFuncLockWithError sets value with return value of callback function `f`, and then returns true. +// It returns false if `key` exists, and `value` would be ignored. +// It returns (false, error) if `f` returns a non-nil error, and `value` would not be stored. +// +// SetIfNotExistFuncLockWithError differs with SetIfNotExistFuncWithError function is that +// it executes function `f` with mutex.Lock of the hash map. +func (m *KVMap[K, V]) SetIfNotExistFuncLockWithError(key K, f func() (V, error)) (bool, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]V) + } + if _, ok := m.data[key]; ok { + return false, nil + } + value, err := f() + if err != nil { + return false, err + } + if !m.isNil(value) { + m.data[key] = value + } + return true, nil +} + // Remove deletes value from map by given `key`, and return this deleted value. func (m *KVMap[K, V]) Remove(key K) (value V) { m.mu.Lock() diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index 61d99f0b909..d64c93270d6 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -400,6 +400,54 @@ func (m *ListKVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var { return gvar.New(m.GetOrSetFuncLock(key, f)) } +// GetOrSetFuncWithError returns the value by key, +// or sets value with returned value of callback function `f` if it does not exist +// and then returns this value. +// +// Note that, it does not add the value to the map if the returned value of `f` is nil +// or if `f` returns a non-nil error. +func (m *ListKVMap[K, V]) GetOrSetFuncWithError(key K, f func() (V, error)) (V, error) { + if v, ok := m.Search(key); ok { + return v, nil + } + value, err := f() + if err != nil { + var zero V + return zero, err + } + return m.doSetWithLockCheck(key, value), nil +} + +// GetOrSetFuncLockWithError returns the value by key, +// or sets value with returned value of callback function `f` if it does not exist +// and then returns this value. +// +// GetOrSetFuncLockWithError differs with GetOrSetFuncWithError function is that it executes function `f` +// with mutex.Lock of the map. +// +// Note that, it does not add the value to the map if the returned value of `f` is nil +// or if `f` returns a non-nil error. +func (m *ListKVMap[K, V]) GetOrSetFuncLockWithError(key K, f func() (V, error)) (V, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + } + if e, ok := m.data[key]; ok { + return e.Value.value, nil + } + value, err := f() + if err != nil { + var zero V + return zero, err + } + if !m.isNil(value) { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) + } + return value, nil +} + // SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true. // It returns false if `key` exists, and `value` would be ignored. // @@ -426,6 +474,20 @@ func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool { // // Note that, it does not add the value to the map if the returned value of `f` is nil. func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool { + if m.Contains(key) { + return false + } + return m.SetIfNotExist(key, f()) +} + +// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. +// It returns false if `key` exists, and `value` would be ignored. +// +// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that +// it executes function `f` with mutex.Lock of the map. +// +// Note that, it does not add the value to the map if the returned value of `f` is nil. +func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool { m.mu.Lock() defer m.mu.Unlock() @@ -443,14 +505,31 @@ func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool { return true } -// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. +// SetIfNotExistFuncWithError sets value with return value of callback function `f`, and then returns true. // It returns false if `key` exists, and `value` would be ignored. +// It returns (false, error) if `f` returns a non-nil error, and `value` would not be stored. // -// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that +// Note that, it does not add the value to the map if the returned value of `f` is nil. +func (m *ListKVMap[K, V]) SetIfNotExistFuncWithError(key K, f func() (V, error)) (bool, error) { + if m.Contains(key) { + return false, nil + } + value, err := f() + if err != nil { + return false, err + } + return m.SetIfNotExist(key, value), nil +} + +// SetIfNotExistFuncLockWithError sets value with return value of callback function `f`, and then returns true. +// It returns false if `key` exists, and `value` would be ignored. +// It returns (false, error) if `f` returns a non-nil error, and `value` would not be stored. +// +// SetIfNotExistFuncLockWithError differs with SetIfNotExistFuncWithError function is that // it executes function `f` with mutex.Lock of the map. // // Note that, it does not add the value to the map if the returned value of `f` is nil. -func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool { +func (m *ListKVMap[K, V]) SetIfNotExistFuncLockWithError(key K, f func() (V, error)) (bool, error) { m.mu.Lock() defer m.mu.Unlock() @@ -459,13 +538,16 @@ func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool { m.list = glist.NewT[*gListKVMapNode[K, V]]() } if _, ok := m.data[key]; ok { - return false + return false, nil + } + value, err := f() + if err != nil { + return false, err } - value := f() if !m.isNil(value) { m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) } - return true + return true, nil } // Remove deletes value from map by given `key`, and return this deleted value. diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index cc99d8e5a3d..b9e226a0961 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -990,19 +990,14 @@ func newDBByConfigNode(node *ConfigNode, group string) (db DB, err error) { // Instance returns an instance for DB operations. // The parameter `name` specifies the configuration group name, // which is DefaultGroupName in default. -func Instance(name ...string) (db DB, err error) { +func Instance(name ...string) (DB, error) { group := configs.group if len(name) > 0 && name[0] != "" { group = name[0] } - v := instances.GetOrSetFuncLock(group, func() DB { - db, err = NewByGroup(group) - return db + return instances.GetOrSetFuncLockWithError(group, func() (DB, error) { + return NewByGroup(group) }) - if v != nil { - return v, nil - } - return nil, err } // getConfigNodeByGroup calculates and returns a configuration node of given group. It From 4c0a2255e5bcbf0b406ed6cd4560d053be94c53d Mon Sep 17 00:00:00 2001 From: Lance Add <1196661499@qq.com> Date: Thu, 26 Feb 2026 10:27:29 +0800 Subject: [PATCH 2/4] Update container/gmap/gmap_hash_k_v_map.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- container/gmap/gmap_hash_k_v_map.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container/gmap/gmap_hash_k_v_map.go b/container/gmap/gmap_hash_k_v_map.go index 5dae5173327..72b34e6d5d5 100644 --- a/container/gmap/gmap_hash_k_v_map.go +++ b/container/gmap/gmap_hash_k_v_map.go @@ -440,6 +440,7 @@ func (m *KVMap[K, V]) SetIfNotExistFuncWithError(key K, f func() (V, error)) (bo // SetIfNotExistFuncLockWithError sets value with return value of callback function `f`, and then returns true. // It returns false if `key` exists, and `value` would be ignored. // It returns (false, error) if `f` returns a non-nil error, and `value` would not be stored. +// Note that, it does not add the value to the map if the returned value of `f` is nil. // // SetIfNotExistFuncLockWithError differs with SetIfNotExistFuncWithError function is that // it executes function `f` with mutex.Lock of the hash map. From a44a1ee99a93769c03274a7713e8bf491dd5d63e Mon Sep 17 00:00:00 2001 From: Lance Add <1196661499@qq.com> Date: Thu, 26 Feb 2026 10:29:00 +0800 Subject: [PATCH 3/4] Update container/gmap/gmap_hash_k_v_map.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- container/gmap/gmap_hash_k_v_map.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/container/gmap/gmap_hash_k_v_map.go b/container/gmap/gmap_hash_k_v_map.go index 72b34e6d5d5..8ac17d3a9dd 100644 --- a/container/gmap/gmap_hash_k_v_map.go +++ b/container/gmap/gmap_hash_k_v_map.go @@ -434,6 +434,9 @@ func (m *KVMap[K, V]) SetIfNotExistFuncWithError(key K, f func() (V, error)) (bo if err != nil { return false, err } + if m.isNil(value) { + return true, nil + } return m.SetIfNotExist(key, value), nil } From 91690cf0cc4188d2ccf829352a335388677c82a3 Mon Sep 17 00:00:00 2001 From: lanceadd <1196661499@qq.com> Date: Fri, 27 Feb 2026 10:43:30 +0800 Subject: [PATCH 4/4] =?UTF-8?q?test(gmap):=20=E6=B7=BB=E5=8A=A0KVMap?= =?UTF-8?q?=E5=92=8CListKVMap=E7=9A=84=E5=B8=A6=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gmap_z_unit_k_v_map_with_error_test.go | 629 ++++++++++++++++ ...map_z_unit_list_k_v_map_with_error_test.go | 699 ++++++++++++++++++ 2 files changed, 1328 insertions(+) create mode 100644 container/gmap/gmap_z_unit_k_v_map_with_error_test.go create mode 100644 container/gmap/gmap_z_unit_list_k_v_map_with_error_test.go diff --git a/container/gmap/gmap_z_unit_k_v_map_with_error_test.go b/container/gmap/gmap_z_unit_k_v_map_with_error_test.go new file mode 100644 index 00000000000..4ad69c9d4c7 --- /dev/null +++ b/container/gmap/gmap_z_unit_k_v_map_with_error_test.go @@ -0,0 +1,629 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gmap_test + +import ( + "errors" + "sync" + "testing" + "time" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/container/gtype" + "github.com/gogf/gf/v2/test/gtest" +) + +// -------------------------------------------------------------------------- +// GetOrSetFuncWithError +// -------------------------------------------------------------------------- + +func Test_KVMap_GetOrSetFuncWithError(t *testing.T) { + type MyVal struct { + Valid bool + Data string + } + // Case: key not exist, f returns valid value → value stored and returned. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, string]() + v, err := m.GetOrSetFuncWithError("k1", func() (string, error) { + return "val1", nil + }) + t.AssertNil(err) + t.Assert(v, "val1") + t.Assert(m.Get("k1"), "val1") + t.Assert(m.Size(), 1) + }) + + // Case: key already exists → existing value returned, f is NOT called. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, string]() + m.Set("k1", "existing") + called := false + v, err := m.GetOrSetFuncWithError("k1", func() (string, error) { + called = true + return "new", nil + }) + t.AssertNil(err) + t.Assert(v, "existing") + t.Assert(called, false) + t.Assert(m.Get("k1"), "existing") + }) + + // Case: f returns an error → zero value returned, error propagated, key NOT stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, string]() + testErr := errors.New("load error") + v, err := m.GetOrSetFuncWithError("k1", func() (string, error) { + return "", testErr + }) + t.AssertNE(err, nil) + t.Assert(err, testErr) + t.Assert(v, "") + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: f returns nil pointer → nil not stored in map, nil returned. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, *string]() + v, err := m.GetOrSetFuncWithError("k1", func() (*string, error) { + return nil, nil + }) + t.AssertNil(err) + t.AssertNil(v) + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: custom NilChecker — f returns a struct treated as "nil" by checker → not stored. + gtest.C(t, func(t *gtest.T) { + + checker := func(v *MyVal) bool { return !v.Valid } + m := gmap.NewKVMapWithChecker[string, *MyVal](checker) + + // "nil" per checker: valid=false + v, err := m.GetOrSetFuncWithError("k1", func() (*MyVal, error) { + return &MyVal{Valid: false, Data: "ignored"}, nil + }) + t.AssertNil(err) + t.Assert(v.Valid, false) + t.Assert(m.Contains("k1"), false) + + // valid value: valid=true → stored + v, err = m.GetOrSetFuncWithError("k2", func() (*MyVal, error) { + return &MyVal{Valid: true, Data: "hello"}, nil + }) + t.AssertNil(err) + t.Assert(v.Valid, true) + t.Assert(v.Data, "hello") + t.Assert(m.Contains("k2"), true) + t.Assert(m.Get("k2").Data, "hello") + }) + + // Case: after f returns error, key is absent and a subsequent call can succeed. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + attempts := gtype.NewInt32() + + _, err := m.GetOrSetFuncWithError("k1", func() (int, error) { + attempts.Add(1) + return 0, errors.New("temporary error") + }) + t.AssertNE(err, nil) + t.Assert(m.Contains("k1"), false) + + v, err := m.GetOrSetFuncWithError("k1", func() (int, error) { + attempts.Add(1) + return 42, nil + }) + t.AssertNil(err) + t.Assert(v, 42) + t.Assert(m.Get("k1"), 42) + t.Assert(attempts.Val(), 2) + }) + + // Case: safe mode (concurrent-safe=true) — basic functionality is correct. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + v, err := m.GetOrSetFuncWithError("k1", func() (int, error) { + return 99, nil + }) + t.AssertNil(err) + t.Assert(v, 99) + t.Assert(m.Get("k1"), 99) + }) +} + +// -------------------------------------------------------------------------- +// GetOrSetFuncLockWithError +// -------------------------------------------------------------------------- + +func Test_KVMap_GetOrSetFuncLockWithError(t *testing.T) { + type MyVal struct { + Valid bool + Data string + } + // Case: key not exist, f returns valid value → value stored and returned. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, string]() + v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) { + return "val1", nil + }) + t.AssertNil(err) + t.Assert(v, "val1") + t.Assert(m.Get("k1"), "val1") + t.Assert(m.Size(), 1) + }) + + // Case: key already exists → existing value returned, f is NOT called. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, string]() + m.Set("k1", "existing") + called := false + v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) { + called = true + return "new", nil + }) + t.AssertNil(err) + t.Assert(v, "existing") + t.Assert(called, false) + t.Assert(m.Get("k1"), "existing") + }) + + // Case: f returns an error → zero value returned, error propagated, key NOT stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, string]() + testErr := errors.New("lock load error") + v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) { + return "", testErr + }) + t.AssertNE(err, nil) + t.Assert(err, testErr) + t.Assert(v, "") + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: f returns nil pointer → nil not stored in map, nil returned. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, *string]() + v, err := m.GetOrSetFuncLockWithError("k1", func() (*string, error) { + return nil, nil + }) + t.AssertNil(err) + t.AssertNil(v) + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: custom NilChecker — f returns a struct treated as "nil" by checker → not stored. + gtest.C(t, func(t *gtest.T) { + checker := func(v *MyVal) bool { return !v.Valid } + m := gmap.NewKVMapWithChecker[string, *MyVal](checker) + + // "nil" per checker → not stored + v, err := m.GetOrSetFuncLockWithError("k1", func() (*MyVal, error) { + return &MyVal{Valid: false, Data: "ignored"}, nil + }) + t.AssertNil(err) + t.Assert(v.Valid, false) + t.Assert(m.Contains("k1"), false) + + // valid value → stored + v, err = m.GetOrSetFuncLockWithError("k2", func() (*MyVal, error) { + return &MyVal{Valid: true, Data: "world"}, nil + }) + t.AssertNil(err) + t.Assert(v.Data, "world") + t.Assert(m.Contains("k2"), true) + t.Assert(m.Get("k2").Data, "world") + }) + + // Case: after f returns error, key is absent and a subsequent call can succeed. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + + _, err := m.GetOrSetFuncLockWithError("k1", func() (int, error) { + return 0, errors.New("temporary") + }) + t.AssertNE(err, nil) + t.Assert(m.Contains("k1"), false) + + v, err := m.GetOrSetFuncLockWithError("k1", func() (int, error) { + return 77, nil + }) + t.AssertNil(err) + t.Assert(v, 77) + t.Assert(m.Get("k1"), 77) + }) +} + +// Test_KVMap_GetOrSetFuncLockWithError_Race verifies that f is called exactly once +// under high concurrency because GetOrSetFuncLockWithError holds the mutex while calling f. +// This differs from GetOrSetFuncWithError, which calls f outside the lock and may invoke +// f multiple times when multiple goroutines all see the key as absent simultaneously. +func Test_KVMap_GetOrSetFuncLockWithError_Race(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + key := "shared" + callCount := gtype.NewInt32() + goroutines := 100 + + var wg sync.WaitGroup + wg.Add(goroutines) + + for range goroutines { + go func() { + defer wg.Done() + v, err := m.GetOrSetFuncLockWithError(key, func() (int, error) { + callCount.Add(1) + time.Sleep(time.Microsecond) + return 999, nil + }) + t.AssertNil(err) + t.Assert(v, 999) + }() + } + wg.Wait() + + // f must be called exactly once because it executes inside the write lock. + t.Assert(callCount.Val(), 1) + t.Assert(m.Get(key), 999) + t.Assert(m.Size(), 1) + }) +} + +// Test_KVMap_GetOrSetFuncLockWithError_Race_ErrorCase verifies that when f returns an error +// concurrently, the key is never stored and a later successful call stores the value. +func Test_KVMap_GetOrSetFuncLockWithError_Race_ErrorCase(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + key := "retry" + attempts := gtype.NewInt32() + + // All goroutines call f, which errors; key must never be stored. + goroutines := 20 + var wg sync.WaitGroup + wg.Add(goroutines) + for range goroutines { + go func() { + defer wg.Done() + _, err := m.GetOrSetFuncLockWithError(key, func() (int, error) { + attempts.Add(1) + return 0, errors.New("transient") + }) + t.AssertNE(err, nil) + }() + } + wg.Wait() + + t.Assert(m.Contains(key), false) + + // A subsequent call with a successful f stores the value. + v, err := m.GetOrSetFuncLockWithError(key, func() (int, error) { + return 55, nil + }) + t.AssertNil(err) + t.Assert(v, 55) + t.Assert(m.Get(key), 55) + }) +} + +// -------------------------------------------------------------------------- +// SetIfNotExistFuncWithError +// -------------------------------------------------------------------------- + +func Test_KVMap_SetIfNotExistFuncWithError(t *testing.T) { + type MyVal struct { + Valid bool + Data string + } + // Case: key not exist, f returns valid value → true, nil, value stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) { + return 100, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Get("k1"), 100) + t.Assert(m.Size(), 1) + }) + + // Case: key already exists → f NOT called, returns (false, nil), original value unchanged. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + m.Set("k1", 42) + called := false + ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) { + called = true + return 999, nil + }) + t.AssertNil(err) + t.Assert(ok, false) + t.Assert(called, false) + t.Assert(m.Get("k1"), 42) + }) + + // Case: f returns an error → (false, error), key NOT stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + testErr := errors.New("set error") + ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) { + return 0, testErr + }) + t.Assert(err, testErr) + t.Assert(ok, false) + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: f returns nil pointer → returns (true, nil) but value NOT stored in map. + // This is the special nil-value behavior: the operation reports "intent to set" + // but skips storage when the value is nil. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, *int]() + ok, err := m.SetIfNotExistFuncWithError("k1", func() (*int, error) { + return nil, nil + }) + t.AssertNil(err) + t.Assert(ok, true) // returns true + t.Assert(m.Contains("k1"), false) // but NOT stored + t.Assert(m.Size(), 0) + + // A subsequent call can still attempt to set (key was never stored). + n := 7 + ok, err = m.SetIfNotExistFuncWithError("k1", func() (*int, error) { + return &n, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k1"), true) + t.Assert(*m.Get("k1"), 7) + }) + + // Case: custom NilChecker — struct treated as "nil" by checker → not stored, returns true. + gtest.C(t, func(t *gtest.T) { + checker := func(v *MyVal) bool { return !v.Valid } + m := gmap.NewKVMapWithChecker[string, *MyVal](checker) + + // "nil" per checker → not stored, but returns true + ok, err := m.SetIfNotExistFuncWithError("k1", func() (*MyVal, error) { + return &MyVal{Valid: false, Data: "irrelevant"}, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k1"), false) + + // valid value → stored + ok, err = m.SetIfNotExistFuncWithError("k2", func() (*MyVal, error) { + return &MyVal{Valid: true, Data: "stored"}, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k2"), true) + t.Assert(m.Get("k2").Data, "stored") + }) + + // Case: safe mode (concurrent-safe=true) — basic functionality is correct. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) { + return 55, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Get("k1"), 55) + }) +} + +// -------------------------------------------------------------------------- +// SetIfNotExistFuncLockWithError +// -------------------------------------------------------------------------- + +func Test_KVMap_SetIfNotExistFuncLockWithError(t *testing.T) { + type MyVal struct { + Valid bool + Data string + } + // Case: key not exist, f returns valid value → true, nil, value stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) { + return 200, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Get("k1"), 200) + t.Assert(m.Size(), 1) + }) + + // Case: key already exists → f NOT called, returns (false, nil), original value unchanged. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + m.Set("k1", 99) + called := false + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) { + called = true + return 999, nil + }) + t.AssertNil(err) + t.Assert(ok, false) + t.Assert(called, false) + t.Assert(m.Get("k1"), 99) + }) + + // Case: f returns an error → (false, error), key NOT stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + testErr := errors.New("lock set error") + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) { + return 0, testErr + }) + t.Assert(err, testErr) + t.Assert(ok, false) + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: f returns nil pointer → returns (true, nil) but value NOT stored in map. + // Special behavior: the method signals "key was absent and no error" via true, + // but skips the actual insertion because the value is nil. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, *int]() + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (*int, error) { + return nil, nil + }) + t.AssertNil(err) + t.Assert(ok, true) // true: key was absent and f had no error + t.Assert(m.Contains("k1"), false) // but nil value was NOT stored + t.Assert(m.Size(), 0) + + // A subsequent call can still store a real value (key remains absent). + n := 9 + ok, err = m.SetIfNotExistFuncLockWithError("k1", func() (*int, error) { + return &n, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k1"), true) + t.Assert(*m.Get("k1"), 9) + }) + + // Case: custom NilChecker — struct treated as "nil" by checker → not stored, returns true. + gtest.C(t, func(t *gtest.T) { + + checker := func(v *MyVal) bool { return !v.Valid } + m := gmap.NewKVMapWithChecker[string, *MyVal](checker) + + // "nil" per checker → not stored, but returns true + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (*MyVal, error) { + return &MyVal{Valid: false, Data: "irrelevant"}, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k1"), false) + + // valid value → stored + ok, err = m.SetIfNotExistFuncLockWithError("k2", func() (*MyVal, error) { + return &MyVal{Valid: true, Data: "hello"}, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k2"), true) + t.Assert(m.Get("k2").Data, "hello") + }) +} + +// Test_KVMap_SetIfNotExistFuncLockWithError_Race verifies that f is called exactly once +// and only one goroutine succeeds under high concurrency, because +// SetIfNotExistFuncLockWithError holds the mutex for the entire check-and-set operation. +func Test_KVMap_SetIfNotExistFuncLockWithError_Race(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + key := "race_key" + callCount := gtype.NewInt32() + successCount := gtype.NewInt32() + goroutines := 100 + + var wg sync.WaitGroup + wg.Add(goroutines) + + for range goroutines { + go func() { + defer wg.Done() + ok, err := m.SetIfNotExistFuncLockWithError(key, func() (int, error) { + callCount.Add(1) + time.Sleep(time.Microsecond) + return 42, nil + }) + t.AssertNil(err) + if ok { + successCount.Add(1) + } + }() + } + wg.Wait() + + // f must be called exactly once (lock held during f execution). + t.Assert(callCount.Val(), 1) + // Exactly one goroutine reports success. + t.Assert(successCount.Val(), 1) + t.Assert(m.Get(key), 42) + t.Assert(m.Size(), 1) + }) +} + +// Test_KVMap_SetIfNotExistFuncLockWithError_Race_MultipleKeys verifies correctness when +// multiple goroutines compete over different keys simultaneously. +func Test_KVMap_SetIfNotExistFuncLockWithError_Race_MultipleKeys(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + keys := []string{"alpha", "beta", "gamma", "delta"} + callCounts := make([]*gtype.Int32, len(keys)) + successCounts := make([]*gtype.Int32, len(keys)) + for i := range callCounts { + callCounts[i] = gtype.NewInt32() + successCounts[i] = gtype.NewInt32() + } + goroutines := 30 + + var wg sync.WaitGroup + for i, key := range keys { + keyIdx := i + for range goroutines { + wg.Add(1) + go func(idx int, k string) { + defer wg.Done() + ok, err := m.SetIfNotExistFuncLockWithError(k, func() (int, error) { + callCounts[idx].Add(1) + time.Sleep(time.Microsecond) + return (idx + 1) * 10, nil + }) + t.AssertNil(err) + if ok { + successCounts[idx].Add(1) + } + }(keyIdx, key) + } + } + wg.Wait() + + for i, key := range keys { + // f called exactly once per key + t.Assert(callCounts[i].Val(), 1) + // exactly one goroutine succeeded per key + t.Assert(successCounts[i].Val(), 1) + t.Assert(m.Get(key), (i+1)*10) + } + t.Assert(m.Size(), len(keys)) + }) +} + +// Test_KVMap_SetIfNotExistFuncLockWithError_ErrorRetry verifies that after f returns an error +// the key remains absent and a subsequent successful call stores the value correctly. +func Test_KVMap_SetIfNotExistFuncLockWithError_ErrorRetry(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + key := "retry" + + ok, err := m.SetIfNotExistFuncLockWithError(key, func() (int, error) { + return 0, errors.New("transient error") + }) + t.AssertNE(err, nil) + t.Assert(ok, false) + t.Assert(m.Contains(key), false) + + // After the error the key is still absent; a new call succeeds. + ok, err = m.SetIfNotExistFuncLockWithError(key, func() (int, error) { + return 123, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Get(key), 123) + }) +} diff --git a/container/gmap/gmap_z_unit_list_k_v_map_with_error_test.go b/container/gmap/gmap_z_unit_list_k_v_map_with_error_test.go new file mode 100644 index 00000000000..5950c707587 --- /dev/null +++ b/container/gmap/gmap_z_unit_list_k_v_map_with_error_test.go @@ -0,0 +1,699 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gmap_test + +import ( + "errors" + "sync" + "testing" + "time" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/container/gtype" + "github.com/gogf/gf/v2/test/gtest" +) + +// -------------------------------------------------------------------------- +// GetOrSetFuncWithError +// -------------------------------------------------------------------------- + +func Test_ListKVMap_GetOrSetFuncWithError(t *testing.T) { + type MyVal struct { + Valid bool + Data string + } + // Case: key not exist, f returns valid value → value stored and returned. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + v, err := m.GetOrSetFuncWithError("k1", func() (string, error) { + return "val1", nil + }) + t.AssertNil(err) + t.Assert(v, "val1") + t.Assert(m.Get("k1"), "val1") + t.Assert(m.Size(), 1) + }) + + // Case: key already exists → existing value returned, f is NOT called. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + m.Set("k1", "existing") + called := false + v, err := m.GetOrSetFuncWithError("k1", func() (string, error) { + called = true + return "new", nil + }) + t.AssertNil(err) + t.Assert(v, "existing") + t.Assert(called, false) + t.Assert(m.Get("k1"), "existing") + }) + + // Case: f returns an error → zero value returned, error propagated, key NOT stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + testErr := errors.New("load error") + v, err := m.GetOrSetFuncWithError("k1", func() (string, error) { + return "", testErr + }) + t.AssertNE(err, nil) + t.Assert(err, testErr) + t.Assert(v, "") + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: f returns nil pointer → nil not stored in map, nil returned. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, *string]() + v, err := m.GetOrSetFuncWithError("k1", func() (*string, error) { + return nil, nil + }) + t.AssertNil(err) + t.AssertNil(v) + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: custom NilChecker — struct treated as "nil" by checker → not stored. + gtest.C(t, func(t *gtest.T) { + checker := func(v *MyVal) bool { return !v.Valid } + m := gmap.NewListKVMapWithChecker[string, *MyVal](checker) + + // "nil" per checker: valid=false + v, err := m.GetOrSetFuncWithError("k1", func() (*MyVal, error) { + return &MyVal{Valid: false, Data: "ignored"}, nil + }) + t.AssertNil(err) + t.Assert(v.Valid, false) + t.Assert(m.Contains("k1"), false) + + // valid value → stored + v, err = m.GetOrSetFuncWithError("k2", func() (*MyVal, error) { + return &MyVal{Valid: true, Data: "hello"}, nil + }) + t.AssertNil(err) + t.Assert(v.Valid, true) + t.Assert(v.Data, "hello") + t.Assert(m.Contains("k2"), true) + t.Assert(m.Get("k2").Data, "hello") + }) + + // Case: insertion order is maintained after GetOrSetFuncWithError adds entries. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + m.Set("a", 1) + m.Set("b", 2) + + _, err := m.GetOrSetFuncWithError("c", func() (int, error) { + return 3, nil + }) + t.AssertNil(err) + + keys := m.Keys() + t.Assert(len(keys), 3) + t.Assert(keys[0], "a") + t.Assert(keys[1], "b") + t.Assert(keys[2], "c") + }) + + // Case: after f returns error, key is absent and a subsequent call can succeed. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + attempts := gtype.NewInt32() + + _, err := m.GetOrSetFuncWithError("k1", func() (int, error) { + attempts.Add(1) + return 0, errors.New("temporary error") + }) + t.AssertNE(err, nil) + t.Assert(m.Contains("k1"), false) + + v, err := m.GetOrSetFuncWithError("k1", func() (int, error) { + attempts.Add(1) + return 42, nil + }) + t.AssertNil(err) + t.Assert(v, 42) + t.Assert(m.Get("k1"), 42) + t.Assert(attempts.Val(), 2) + }) + + // Case: safe mode (concurrent-safe=true) — basic functionality is correct. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + v, err := m.GetOrSetFuncWithError("k1", func() (int, error) { + return 99, nil + }) + t.AssertNil(err) + t.Assert(v, 99) + t.Assert(m.Get("k1"), 99) + }) +} + +// -------------------------------------------------------------------------- +// GetOrSetFuncLockWithError +// -------------------------------------------------------------------------- + +func Test_ListKVMap_GetOrSetFuncLockWithError(t *testing.T) { + type MyVal struct { + Valid bool + Data string + } + // Case: key not exist, f returns valid value → value stored and returned. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) { + return "val1", nil + }) + t.AssertNil(err) + t.Assert(v, "val1") + t.Assert(m.Get("k1"), "val1") + t.Assert(m.Size(), 1) + }) + + // Case: key already exists → existing value returned, f is NOT called. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + m.Set("k1", "existing") + called := false + v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) { + called = true + return "new", nil + }) + t.AssertNil(err) + t.Assert(v, "existing") + t.Assert(called, false) + t.Assert(m.Get("k1"), "existing") + }) + + // Case: f returns an error → zero value returned, error propagated, key NOT stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + testErr := errors.New("lock load error") + v, err := m.GetOrSetFuncLockWithError("k1", func() (string, error) { + return "", testErr + }) + t.AssertNE(err, nil) + t.Assert(err, testErr) + t.Assert(v, "") + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: f returns nil pointer → nil not stored in map, nil returned. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, *string]() + v, err := m.GetOrSetFuncLockWithError("k1", func() (*string, error) { + return nil, nil + }) + t.AssertNil(err) + t.AssertNil(v) + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: custom NilChecker — struct treated as "nil" by checker → not stored. + gtest.C(t, func(t *gtest.T) { + checker := func(v *MyVal) bool { return !v.Valid } + m := gmap.NewListKVMapWithChecker[string, *MyVal](checker) + + // "nil" per checker → not stored + v, err := m.GetOrSetFuncLockWithError("k1", func() (*MyVal, error) { + return &MyVal{Valid: false, Data: "ignored"}, nil + }) + t.AssertNil(err) + t.Assert(v.Valid, false) + t.Assert(m.Contains("k1"), false) + + // valid value → stored + v, err = m.GetOrSetFuncLockWithError("k2", func() (*MyVal, error) { + return &MyVal{Valid: true, Data: "world"}, nil + }) + t.AssertNil(err) + t.Assert(v.Data, "world") + t.Assert(m.Contains("k2"), true) + t.Assert(m.Get("k2").Data, "world") + }) + + // Case: insertion order is maintained after GetOrSetFuncLockWithError adds entries. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + m.Set("x", 10) + m.Set("y", 20) + + _, err := m.GetOrSetFuncLockWithError("z", func() (int, error) { + return 30, nil + }) + t.AssertNil(err) + + keys := m.Keys() + t.Assert(len(keys), 3) + t.Assert(keys[0], "x") + t.Assert(keys[1], "y") + t.Assert(keys[2], "z") + }) + + // Case: after f returns error, key is absent and a subsequent call can succeed. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + + _, err := m.GetOrSetFuncLockWithError("k1", func() (int, error) { + return 0, errors.New("temporary") + }) + t.AssertNE(err, nil) + t.Assert(m.Contains("k1"), false) + + v, err := m.GetOrSetFuncLockWithError("k1", func() (int, error) { + return 77, nil + }) + t.AssertNil(err) + t.Assert(v, 77) + t.Assert(m.Get("k1"), 77) + }) +} + +// Test_ListKVMap_GetOrSetFuncLockWithError_Race verifies that f is called exactly once +// under high concurrency because GetOrSetFuncLockWithError holds the mutex while calling f. +// This differs from GetOrSetFuncWithError, which calls f outside the lock and may invoke +// f multiple times when multiple goroutines all see the key as absent simultaneously. +func Test_ListKVMap_GetOrSetFuncLockWithError_Race(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + key := "shared" + callCount := gtype.NewInt32() + goroutines := 100 + + var wg sync.WaitGroup + wg.Add(goroutines) + + for range goroutines { + go func() { + defer wg.Done() + v, err := m.GetOrSetFuncLockWithError(key, func() (int, error) { + callCount.Add(1) + time.Sleep(time.Microsecond) + return 999, nil + }) + t.AssertNil(err) + t.Assert(v, 999) + }() + } + wg.Wait() + + // f must be called exactly once because it executes inside the write lock. + t.Assert(callCount.Val(), 1) + t.Assert(m.Get(key), 999) + t.Assert(m.Size(), 1) + }) +} + +// Test_ListKVMap_GetOrSetFuncLockWithError_Race_ErrorCase verifies that when f returns +// an error concurrently, the key is never stored and a later successful call works. +func Test_ListKVMap_GetOrSetFuncLockWithError_Race_ErrorCase(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + key := "retry" + attempts := gtype.NewInt32() + + goroutines := 20 + var wg sync.WaitGroup + wg.Add(goroutines) + for range goroutines { + go func() { + defer wg.Done() + _, err := m.GetOrSetFuncLockWithError(key, func() (int, error) { + attempts.Add(1) + return 0, errors.New("transient") + }) + t.AssertNE(err, nil) + }() + } + wg.Wait() + + t.Assert(m.Contains(key), false) + + v, err := m.GetOrSetFuncLockWithError(key, func() (int, error) { + return 55, nil + }) + t.AssertNil(err) + t.Assert(v, 55) + t.Assert(m.Get(key), 55) + }) +} + +// -------------------------------------------------------------------------- +// SetIfNotExistFuncWithError +// -------------------------------------------------------------------------- + +func Test_ListKVMap_SetIfNotExistFuncWithError(t *testing.T) { + type MyVal struct { + Valid bool + Data string + } + // Case: key not exist, f returns valid value → true, nil, value stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) { + return 100, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Get("k1"), 100) + t.Assert(m.Size(), 1) + }) + + // Case: key already exists → f NOT called, returns (false, nil), original value unchanged. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + m.Set("k1", 42) + called := false + ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) { + called = true + return 999, nil + }) + t.AssertNil(err) + t.Assert(ok, false) + t.Assert(called, false) + t.Assert(m.Get("k1"), 42) + }) + + // Case: f returns an error → (false, error), key NOT stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + testErr := errors.New("set error") + ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) { + return 0, testErr + }) + t.Assert(err, testErr) + t.Assert(ok, false) + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: f returns nil pointer → returns (true, nil) but value NOT stored in map. + // This is the special nil-value behavior: the operation reports "intent to set" + // but skips storage when the value is nil. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, *int]() + ok, err := m.SetIfNotExistFuncWithError("k1", func() (*int, error) { + return nil, nil + }) + t.AssertNil(err) + t.Assert(ok, true) // returns true + t.Assert(m.Contains("k1"), false) // but NOT stored + t.Assert(m.Size(), 0) + + // A subsequent call can still set the value (key was never stored). + n := 7 + ok, err = m.SetIfNotExistFuncWithError("k1", func() (*int, error) { + return &n, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k1"), true) + t.Assert(*m.Get("k1"), 7) + }) + + // Case: custom NilChecker — struct treated as "nil" by checker → not stored, returns true. + gtest.C(t, func(t *gtest.T) { + checker := func(v *MyVal) bool { return !v.Valid } + m := gmap.NewListKVMapWithChecker[string, *MyVal](checker) + + // "nil" per checker → not stored, but returns true + ok, err := m.SetIfNotExistFuncWithError("k1", func() (*MyVal, error) { + return &MyVal{Valid: false, Data: "irrelevant"}, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k1"), false) + + // valid value → stored + ok, err = m.SetIfNotExistFuncWithError("k2", func() (*MyVal, error) { + return &MyVal{Valid: true, Data: "stored"}, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k2"), true) + t.Assert(m.Get("k2").Data, "stored") + }) + + // Case: insertion order is maintained after SetIfNotExistFuncWithError adds entries. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + m.Set("a", 1) + m.Set("b", 2) + + ok, err := m.SetIfNotExistFuncWithError("c", func() (int, error) { + return 3, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + + keys := m.Keys() + t.Assert(len(keys), 3) + t.Assert(keys[0], "a") + t.Assert(keys[1], "b") + t.Assert(keys[2], "c") + }) + + // Case: safe mode (concurrent-safe=true) — basic functionality is correct. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + ok, err := m.SetIfNotExistFuncWithError("k1", func() (int, error) { + return 55, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Get("k1"), 55) + }) +} + +// -------------------------------------------------------------------------- +// SetIfNotExistFuncLockWithError +// -------------------------------------------------------------------------- + +func Test_ListKVMap_SetIfNotExistFuncLockWithError(t *testing.T) { + type MyVal struct { + Valid bool + Data string + } + // Case: key not exist, f returns valid value → true, nil, value stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) { + return 200, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Get("k1"), 200) + t.Assert(m.Size(), 1) + }) + + // Case: key already exists → f NOT called, returns (false, nil), original value unchanged. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + m.Set("k1", 99) + called := false + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) { + called = true + return 999, nil + }) + t.AssertNil(err) + t.Assert(ok, false) + t.Assert(called, false) + t.Assert(m.Get("k1"), 99) + }) + + // Case: f returns an error → (false, error), key NOT stored. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + testErr := errors.New("lock set error") + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (int, error) { + return 0, testErr + }) + t.Assert(err, testErr) + t.Assert(ok, false) + t.Assert(m.Contains("k1"), false) + t.Assert(m.Size(), 0) + }) + + // Case: f returns nil pointer → returns (true, nil) but value NOT stored in map. + // Special behavior: the method signals "key was absent and no error" via true, + // but skips the actual insertion because the value is nil. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, *int]() + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (*int, error) { + return nil, nil + }) + t.AssertNil(err) + t.Assert(ok, true) // true: key was absent and f had no error + t.Assert(m.Contains("k1"), false) // but nil value was NOT stored + t.Assert(m.Size(), 0) + + // A subsequent call can still store a real value (key remains absent). + n := 9 + ok, err = m.SetIfNotExistFuncLockWithError("k1", func() (*int, error) { + return &n, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k1"), true) + t.Assert(*m.Get("k1"), 9) + }) + + // Case: custom NilChecker — struct treated as "nil" by checker → not stored, returns true. + gtest.C(t, func(t *gtest.T) { + checker := func(v *MyVal) bool { return !v.Valid } + m := gmap.NewListKVMapWithChecker[string, *MyVal](checker) + + // "nil" per checker → not stored, but returns true + ok, err := m.SetIfNotExistFuncLockWithError("k1", func() (*MyVal, error) { + return &MyVal{Valid: false, Data: "irrelevant"}, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k1"), false) + + // valid value → stored + ok, err = m.SetIfNotExistFuncLockWithError("k2", func() (*MyVal, error) { + return &MyVal{Valid: true, Data: "hello"}, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Contains("k2"), true) + t.Assert(m.Get("k2").Data, "hello") + }) + + // Case: insertion order is maintained after SetIfNotExistFuncLockWithError adds entries. + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + m.Set("a", 1) + m.Set("b", 2) + + ok, err := m.SetIfNotExistFuncLockWithError("c", func() (int, error) { + return 3, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + + keys := m.Keys() + t.Assert(len(keys), 3) + t.Assert(keys[0], "a") + t.Assert(keys[1], "b") + t.Assert(keys[2], "c") + }) +} + +// Test_ListKVMap_SetIfNotExistFuncLockWithError_Race verifies that f is called exactly once +// and only one goroutine succeeds under high concurrency, because +// SetIfNotExistFuncLockWithError holds the mutex for the entire check-and-set operation. +func Test_ListKVMap_SetIfNotExistFuncLockWithError_Race(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + key := "race_key" + callCount := gtype.NewInt32() + successCount := gtype.NewInt32() + goroutines := 100 + + var wg sync.WaitGroup + wg.Add(goroutines) + + for range goroutines { + go func() { + defer wg.Done() + ok, err := m.SetIfNotExistFuncLockWithError(key, func() (int, error) { + callCount.Add(1) + time.Sleep(time.Microsecond) + return 42, nil + }) + t.AssertNil(err) + if ok { + successCount.Add(1) + } + }() + } + wg.Wait() + + // f must be called exactly once (lock held during f execution). + t.Assert(callCount.Val(), 1) + // Exactly one goroutine reports success. + t.Assert(successCount.Val(), 1) + t.Assert(m.Get(key), 42) + t.Assert(m.Size(), 1) + }) +} + +// Test_ListKVMap_SetIfNotExistFuncLockWithError_Race_MultipleKeys verifies correctness when +// multiple goroutines compete over different keys simultaneously. +func Test_ListKVMap_SetIfNotExistFuncLockWithError_Race_MultipleKeys(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + keys := []string{"alpha", "beta", "gamma", "delta"} + callCounts := make([]*gtype.Int32, len(keys)) + successCounts := make([]*gtype.Int32, len(keys)) + for i := range callCounts { + callCounts[i] = gtype.NewInt32() + successCounts[i] = gtype.NewInt32() + } + goroutines := 30 + + var wg sync.WaitGroup + for i, key := range keys { + keyIdx := i + for range goroutines { + wg.Add(1) + go func(idx int, k string) { + defer wg.Done() + ok, err := m.SetIfNotExistFuncLockWithError(k, func() (int, error) { + callCounts[idx].Add(1) + time.Sleep(time.Microsecond) + return (idx + 1) * 10, nil + }) + t.AssertNil(err) + if ok { + successCounts[idx].Add(1) + } + }(keyIdx, key) + } + } + wg.Wait() + + for i, key := range keys { + // f called exactly once per key + t.Assert(callCounts[i].Val(), 1) + // exactly one goroutine succeeded per key + t.Assert(successCounts[i].Val(), 1) + t.Assert(m.Get(key), (i+1)*10) + } + t.Assert(m.Size(), len(keys)) + }) +} + +// Test_ListKVMap_SetIfNotExistFuncLockWithError_ErrorRetry verifies that after f returns an error +// the key remains absent and a subsequent successful call stores the value correctly. +func Test_ListKVMap_SetIfNotExistFuncLockWithError_ErrorRetry(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + key := "retry" + + ok, err := m.SetIfNotExistFuncLockWithError(key, func() (int, error) { + return 0, errors.New("transient error") + }) + t.AssertNE(err, nil) + t.Assert(ok, false) + t.Assert(m.Contains(key), false) + + // After the error the key is still absent; a new call succeeds. + ok, err = m.SetIfNotExistFuncLockWithError(key, func() (int, error) { + return 123, nil + }) + t.AssertNil(err) + t.Assert(ok, true) + t.Assert(m.Get(key), 123) + }) +}