Skip to content

Commit 1738f84

Browse files
author
Dean Karn
authored
add corrected Mutexes in sync directory (#26)
1 parent 1309bd0 commit 1738f84

File tree

5 files changed

+213
-2
lines changed

5 files changed

+213
-2
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [5.15.0] - 2023-03-05
10+
### Added
11+
- New Mutex2 and RWMutex2 which corrects the original Mutex's design issues.
12+
- Deprecation warning for original Mutex usage.
13+
914
## [5.14.0] - 2023-02-25
1015
### Added
1116
- Added `timext.NanoTime` for fast low level monotonic time with nanosecond precision.
1217

13-
[Unreleased]: https://github.com/go-playground/pkg/compare/v5.14.0...HEAD
18+
[Unreleased]: https://github.com/go-playground/pkg/compare/v5.15.0...HEAD
19+
[5.15.0]: https://github.com/go-playground/pkg/compare/v5.14.0...v5.15.0
1420
[5.14.0]: https://github.com/go-playground/pkg/commit/v5.14.0

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pkg
22

3-
![Project status](https://img.shields.io/badge/version-5.14.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-5.15.0-green.svg)
44
[![Build Status](https://travis-ci.org/go-playground/pkg.svg?branch=master)](https://travis-ci.org/go-playground/pkg)
55
[![Coverage Status](https://coveralls.io/repos/github/go-playground/pkg/badge.svg?branch=master)](https://coveralls.io/github/go-playground/pkg?branch=master)
66
[![GoDoc](https://godoc.org/github.com/go-playground/pkg?status.svg)](https://pkg.go.dev/mod/github.com/go-playground/pkg/v5)

sync/mutex.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
)
1212

1313
// NewMutex creates a new Mutex for use.
14+
//
15+
// Deprecated: use `syncext.NewMutex2(...)` instead which corrects design issues with the current implementation.
1416
func NewMutex[T any](value T) *Mutex[T] {
1517
return &Mutex[T]{
1618
value: value,
@@ -70,6 +72,8 @@ func (m *Mutex[T]) TryLock() resultext.Result[T, struct{}] {
7072
}
7173

7274
// NewRWMutex creates a new RWMutex for use.
75+
//
76+
// Deprecated: use `syncext.NewRWMutex2(...)` instead which corrects design issues with the current implementation.
7377
func NewRWMutex[T any](value T) *RWMutex[T] {
7478
return &RWMutex[T]{
7579
value: value,

sync/mutex2.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//go:build go1.18
2+
// +build go1.18
3+
4+
package syncext
5+
6+
import (
7+
"sync"
8+
9+
resultext "github.com/go-playground/pkg/v5/values/result"
10+
)
11+
12+
// NewMutex2 creates a new Mutex for use.
13+
func NewMutex2[T any](value T) *Mutex2[T] {
14+
return &Mutex2[T]{
15+
value: value,
16+
}
17+
}
18+
19+
// Mutex2 creates a type safe mutex wrapper ensuring one cannot access the values of a locked values
20+
// without first gaining a lock.
21+
type Mutex2[T any] struct {
22+
m sync.Mutex
23+
value T
24+
}
25+
26+
// Lock locks the Mutex and returns value for use, safe for mutation if
27+
//
28+
// If the lock is already in use, the calling goroutine blocks until the mutex is available.
29+
func (m *Mutex2[T]) Lock() T {
30+
m.m.Lock()
31+
return m.value
32+
}
33+
34+
// Unlock unlocks the Mutex accepting a value to set as the new or mutated value.
35+
// It is optional to pass a new value to be set but NOT required for there reasons:
36+
// 1. If the internal value is already mutable then no need to set as changes apply as they happen.
37+
// 2. If there's a failure working with the locked value you may NOT want to set it, but still unlock.
38+
// 3. Supports locked values that are not mutable.
39+
//
40+
// It is a run-time error if the Mutex is not locked on entry to Unlock.
41+
func (m *Mutex2[T]) Unlock() {
42+
m.m.Unlock()
43+
}
44+
45+
// PerformMut safely locks and unlocks the Mutex values and performs the provided function returning its error if one
46+
// otherwise setting the returned value as the new mutex value.
47+
func (m *Mutex2[T]) PerformMut(f func(T)) {
48+
f(m.Lock())
49+
m.Unlock()
50+
}
51+
52+
// TryLock tries to lock Mutex and reports whether it succeeded.
53+
// If it does the value is returned for use in the Ok result otherwise Err with empty value.
54+
func (m *Mutex2[T]) TryLock() resultext.Result[T, struct{}] {
55+
if m.m.TryLock() {
56+
return resultext.Ok[T, struct{}](m.value)
57+
} else {
58+
return resultext.Err[T, struct{}](struct{}{})
59+
}
60+
}
61+
62+
// NewRWMutex2 creates a new RWMutex for use.
63+
func NewRWMutex2[T any](value T) *RWMutex2[T] {
64+
return &RWMutex2[T]{
65+
value: value,
66+
}
67+
}
68+
69+
// RWMutex2 creates a type safe RWMutex wrapper ensuring one cannot access the values of a locked values
70+
// without first gaining a lock.
71+
type RWMutex2[T any] struct {
72+
rw sync.RWMutex
73+
value T
74+
}
75+
76+
// Lock locks the Mutex and returns value for use, safe for mutation if
77+
//
78+
// If the lock is already in use, the calling goroutine blocks until the mutex is available.
79+
func (m *RWMutex2[T]) Lock() T {
80+
m.rw.Lock()
81+
return m.value
82+
}
83+
84+
// Unlock unlocks the Mutex accepting a value to set as the new or mutated value.
85+
// It is optional to pass a new value to be set but NOT required for there reasons:
86+
// 1. If the internal value is already mutable then no need to set as changes apply as they happen.
87+
// 2. If there's a failure working with the locked value you may NOT want to set it, but still unlock.
88+
// 3. Supports locked values that are not mutable.
89+
//
90+
// It is a run-time error if the Mutex is not locked on entry to Unlock.
91+
func (m *RWMutex2[T]) Unlock() {
92+
m.rw.Unlock()
93+
}
94+
95+
// PerformMut safely locks and unlocks the RWMutex mutable values and performs the provided function.
96+
func (m *RWMutex2[T]) PerformMut(f func(T)) {
97+
f(m.Lock())
98+
m.Unlock()
99+
}
100+
101+
// TryLock tries to lock RWMutex and returns the value in the Ok result if successful.
102+
// If it does the value is returned for use in the Ok result otherwise Err with empty value.
103+
func (m *RWMutex2[T]) TryLock() resultext.Result[T, struct{}] {
104+
if m.rw.TryLock() {
105+
return resultext.Ok[T, struct{}](m.value)
106+
} else {
107+
return resultext.Err[T, struct{}](struct{}{})
108+
}
109+
}
110+
111+
// Perform safely locks and unlocks the RWMutex read-only values and performs the provided function.
112+
func (m *RWMutex2[T]) Perform(f func(T)) {
113+
result := m.RLock()
114+
f(result)
115+
m.RUnlock()
116+
}
117+
118+
// RLock locks the RWMutex for reading and returns the value for read-only use.
119+
// It should not be used for recursive read locking; a blocked Lock call excludes new readers from acquiring the lock
120+
func (m *RWMutex2[T]) RLock() T {
121+
m.rw.RLock()
122+
return m.value
123+
}
124+
125+
// RUnlock undoes a single RLock call; it does not affect other simultaneous readers.
126+
// It is a run-time error if rw is not locked for reading on entry to RUnlock.
127+
func (m *RWMutex2[T]) RUnlock() {
128+
m.rw.RUnlock()
129+
}
130+
131+
// TryRLock tries to lock RWMutex for reading and returns the value in the Ok result if successful.
132+
// If it does the value is returned for use in the Ok result otherwise Err with empty value.
133+
func (m *RWMutex2[T]) TryRLock() resultext.Result[T, struct{}] {
134+
if m.rw.TryRLock() {
135+
return resultext.Ok[T, struct{}](m.value)
136+
} else {
137+
return resultext.Err[T, struct{}](struct{}{})
138+
}
139+
}

sync/mutex2_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//go:build go1.18
2+
// +build go1.18
3+
4+
package syncext
5+
6+
import (
7+
resultext "github.com/go-playground/pkg/v5/values/result"
8+
"testing"
9+
10+
. "github.com/go-playground/assert/v2"
11+
)
12+
13+
func TestMutex2(t *testing.T) {
14+
m := NewMutex2(make(map[string]int))
15+
m.Lock()["foo"] = 1
16+
m.Unlock()
17+
18+
m.PerformMut(func(m map[string]int) {
19+
m["boo"] = 1
20+
})
21+
myMap := m.Lock()
22+
Equal(t, 2, len(myMap))
23+
Equal(t, myMap["foo"], 1)
24+
Equal(t, myMap["boo"], 1)
25+
Equal(t, m.TryLock(), resultext.Err[map[string]int](struct{}{}))
26+
m.Unlock()
27+
28+
result := m.TryLock()
29+
Equal(t, result.IsOk(), true)
30+
m.Unlock()
31+
}
32+
33+
func TestRWMutex2(t *testing.T) {
34+
m := NewRWMutex2(make(map[string]int))
35+
m.Lock()["foo"] = 1
36+
Equal(t, m.TryLock().IsOk(), false)
37+
Equal(t, m.TryRLock().IsOk(), false)
38+
m.Unlock()
39+
40+
m.PerformMut(func(m map[string]int) {
41+
m["boo"] = 2
42+
})
43+
mp := m.Lock()
44+
Equal(t, mp["foo"], 1)
45+
Equal(t, mp["boo"], 2)
46+
m.Unlock()
47+
48+
myMap := m.RLock()
49+
Equal(t, len(myMap), 2)
50+
Equal(t, m.TryRLock().IsOk(), true)
51+
m.RUnlock()
52+
53+
m.Perform(func(m map[string]int) {
54+
Equal(t, 1, m["foo"])
55+
Equal(t, 2, m["boo"])
56+
})
57+
myMap = m.RLock()
58+
Equal(t, len(myMap), 2)
59+
Equal(t, myMap["foo"], 1)
60+
Equal(t, myMap["boo"], 2)
61+
m.RUnlock()
62+
}

0 commit comments

Comments
 (0)