Skip to content

Commit ed98b60

Browse files
committed
use thread safe map
1 parent e76fa5a commit ed98b60

File tree

4 files changed

+162
-8
lines changed

4 files changed

+162
-8
lines changed

pkg/gui/gui.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ type GuiRepoState struct {
185185
// WindowViewNameMap is a mapping of windows to the current view of that window.
186186
// Some views move between windows for example the commitFiles view and when cycling through
187187
// side windows we need to know which view to give focus to for a given window
188-
WindowViewNameMap map[string]string
188+
WindowViewNameMap *utils.ThreadSafeMap[string, string]
189189

190190
// tells us whether we've set up our views for the current repo. We'll need to
191191
// do this whenever we switch back and forth between repos to get the views

pkg/gui/window.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/jesseduffield/gocui"
77
"github.com/jesseduffield/lazygit/pkg/gui/context"
88
"github.com/jesseduffield/lazygit/pkg/gui/types"
9+
"github.com/jesseduffield/lazygit/pkg/utils"
910
"github.com/samber/lo"
1011
)
1112

@@ -15,18 +16,18 @@ import (
1516
// space. Right now most windows are 1:1 with views, except for commitFiles which
1617
// is a view that moves between windows
1718

18-
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) map[string]string {
19-
result := map[string]string{}
19+
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSafeMap[string, string] {
20+
result := utils.NewThreadSafeMap[string, string]()
2021

2122
for _, context := range contextTree.Flatten() {
22-
result[context.GetWindowName()] = context.GetViewName()
23+
result.Set(context.GetWindowName(), context.GetViewName())
2324
}
2425

2526
return result
2627
}
2728

2829
func (gui *Gui) getViewNameForWindow(window string) string {
29-
viewName, ok := gui.State.WindowViewNameMap[window]
30+
viewName, ok := gui.State.WindowViewNameMap.Get(window)
3031
if !ok {
3132
panic(fmt.Sprintf("Viewname not found for window: %s", window))
3233
}
@@ -51,7 +52,7 @@ func (gui *Gui) setWindowContext(c types.Context) {
5152
gui.resetWindowContext(c)
5253
}
5354

54-
gui.State.WindowViewNameMap[c.GetWindowName()] = c.GetViewName()
55+
gui.State.WindowViewNameMap.Set(c.GetWindowName(), c.GetViewName())
5556
}
5657

5758
func (gui *Gui) currentWindow() string {
@@ -60,11 +61,15 @@ func (gui *Gui) currentWindow() string {
6061

6162
// assumes the context's windowName has been set to the new window if necessary
6263
func (gui *Gui) resetWindowContext(c types.Context) {
63-
for windowName, viewName := range gui.State.WindowViewNameMap {
64+
for _, windowName := range gui.State.WindowViewNameMap.Keys() {
65+
viewName, ok := gui.State.WindowViewNameMap.Get(windowName)
66+
if !ok {
67+
continue
68+
}
6469
if viewName == c.GetViewName() && windowName != c.GetWindowName() {
6570
for _, context := range gui.State.Contexts.Flatten() {
6671
if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName {
67-
gui.State.WindowViewNameMap[windowName] = context.GetViewName()
72+
gui.State.WindowViewNameMap.Set(windowName, context.GetViewName())
6873
}
6974
}
7075
}

pkg/utils/thread_safe_map.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package utils
2+
3+
import "sync"
4+
5+
type ThreadSafeMap[K comparable, V any] struct {
6+
mutex sync.RWMutex
7+
8+
innerMap map[K]V
9+
}
10+
11+
func NewThreadSafeMap[K comparable, V any]() *ThreadSafeMap[K, V] {
12+
return &ThreadSafeMap[K, V]{
13+
innerMap: make(map[K]V),
14+
}
15+
}
16+
17+
func (m *ThreadSafeMap[K, V]) Get(key K) (V, bool) {
18+
m.mutex.RLock()
19+
defer m.mutex.RUnlock()
20+
21+
value, ok := m.innerMap[key]
22+
return value, ok
23+
}
24+
25+
func (m *ThreadSafeMap[K, V]) Set(key K, value V) {
26+
m.mutex.Lock()
27+
defer m.mutex.Unlock()
28+
29+
m.innerMap[key] = value
30+
}
31+
32+
func (m *ThreadSafeMap[K, V]) Delete(key K) {
33+
m.mutex.Lock()
34+
defer m.mutex.Unlock()
35+
36+
delete(m.innerMap, key)
37+
}
38+
39+
func (m *ThreadSafeMap[K, V]) Keys() []K {
40+
m.mutex.RLock()
41+
defer m.mutex.RUnlock()
42+
43+
keys := make([]K, 0, len(m.innerMap))
44+
for key := range m.innerMap {
45+
keys = append(keys, key)
46+
}
47+
48+
return keys
49+
}
50+
51+
func (m *ThreadSafeMap[K, V]) Values() []V {
52+
m.mutex.RLock()
53+
defer m.mutex.RUnlock()
54+
55+
values := make([]V, 0, len(m.innerMap))
56+
for _, value := range m.innerMap {
57+
values = append(values, value)
58+
}
59+
60+
return values
61+
}
62+
63+
func (m *ThreadSafeMap[K, V]) Len() int {
64+
m.mutex.RLock()
65+
defer m.mutex.RUnlock()
66+
67+
return len(m.innerMap)
68+
}
69+
70+
func (m *ThreadSafeMap[K, V]) Clear() {
71+
m.mutex.Lock()
72+
defer m.mutex.Unlock()
73+
74+
m.innerMap = make(map[K]V)
75+
}
76+
77+
func (m *ThreadSafeMap[K, V]) IsEmpty() bool {
78+
m.mutex.RLock()
79+
defer m.mutex.RUnlock()
80+
81+
return len(m.innerMap) == 0
82+
}
83+
84+
func (m *ThreadSafeMap[K, V]) Has(key K) bool {
85+
m.mutex.RLock()
86+
defer m.mutex.RUnlock()
87+
88+
_, ok := m.innerMap[key]
89+
return ok
90+
}

pkg/utils/thread_safe_map_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package utils
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestThreadSafeMap(t *testing.T) {
8+
m := NewThreadSafeMap[int, int]()
9+
10+
m.Set(1, 1)
11+
m.Set(2, 2)
12+
m.Set(3, 3)
13+
14+
if m.Len() != 3 {
15+
t.Errorf("Expected length to be 3, got %d", m.Len())
16+
}
17+
18+
if !m.Has(1) {
19+
t.Errorf("Expected to have key 1")
20+
}
21+
22+
if m.Has(4) {
23+
t.Errorf("Expected to not have key 4")
24+
}
25+
26+
if _, ok := m.Get(1); !ok {
27+
t.Errorf("Expected to have key 1")
28+
}
29+
30+
if _, ok := m.Get(4); ok {
31+
t.Errorf("Expected to not have key 4")
32+
}
33+
34+
m.Delete(1)
35+
36+
if m.Has(1) {
37+
t.Errorf("Expected to not have key 1")
38+
}
39+
40+
m.Clear()
41+
42+
if m.Len() != 0 {
43+
t.Errorf("Expected length to be 0, got %d", m.Len())
44+
}
45+
}
46+
47+
func TestThreadSafeMapConcurrentReadWrite(t *testing.T) {
48+
m := NewThreadSafeMap[int, int]()
49+
50+
go func() {
51+
for i := 0; i < 10000; i++ {
52+
m.Set(0, 0)
53+
}
54+
}()
55+
56+
for i := 0; i < 10000; i++ {
57+
m.Get(0)
58+
}
59+
}

0 commit comments

Comments
 (0)