Skip to content

Commit 940518a

Browse files
author
Dean Karn
authored
make it safer (#27)
1 parent 1738f84 commit 940518a

File tree

4 files changed

+119
-75
lines changed

4 files changed

+119
-75
lines changed

CHANGELOG.md

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

77
## [Unreleased]
88

9+
## [5.15.1] - 2023-03-06
10+
### Fixed
11+
- New Mutex2 functions and guards; checked in the wrong code accidentally last commit.
12+
913
## [5.15.0] - 2023-03-05
1014
### Added
1115
- New Mutex2 and RWMutex2 which corrects the original Mutex's design issues.
@@ -15,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1519
### Added
1620
- Added `timext.NanoTime` for fast low level monotonic time with nanosecond precision.
1721

18-
[Unreleased]: https://github.com/go-playground/pkg/compare/v5.15.0...HEAD
22+
[Unreleased]: https://github.com/go-playground/pkg/compare/v5.15.1...HEAD
23+
[5.15.1]: https://github.com/go-playground/pkg/compare/v5.15.0...v5.15.1
1924
[5.15.0]: https://github.com/go-playground/pkg/compare/v5.14.0...v5.15.0
2025
[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.15.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-5.15.1-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/mutex2.go

Lines changed: 91 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,131 +9,163 @@ import (
99
resultext "github.com/go-playground/pkg/v5/values/result"
1010
)
1111

12+
// MutexGuard protects the inner contents of a Mutex2 for safety and unlocking.
13+
type MutexGuard[T any, M interface{ Unlock() }] struct {
14+
m M
15+
// T is the inner generic type of the Mutex
16+
T T
17+
}
18+
19+
// Unlock unlocks the Mutex2 value.
20+
func (g MutexGuard[T, M]) Unlock() {
21+
g.m.Unlock()
22+
}
23+
1224
// NewMutex2 creates a new Mutex for use.
13-
func NewMutex2[T any](value T) *Mutex2[T] {
14-
return &Mutex2[T]{
25+
func NewMutex2[T any, M *sync.Mutex](value T) Mutex2[T, M] {
26+
return Mutex2[T, M]{
27+
m: new(sync.Mutex),
1528
value: value,
1629
}
1730
}
1831

1932
// Mutex2 creates a type safe mutex wrapper ensuring one cannot access the values of a locked values
2033
// without first gaining a lock.
21-
type Mutex2[T any] struct {
22-
m sync.Mutex
34+
type Mutex2[T any, M *sync.Mutex] struct {
35+
m *sync.Mutex
2336
value T
2437
}
2538

2639
// Lock locks the Mutex and returns value for use, safe for mutation if
2740
//
2841
// If the lock is already in use, the calling goroutine blocks until the mutex is available.
29-
func (m *Mutex2[T]) Lock() T {
42+
func (m Mutex2[T, M]) Lock() MutexGuard[T, *sync.Mutex] {
3043
m.m.Lock()
31-
return m.value
44+
return MutexGuard[T, *sync.Mutex]{
45+
m: m.m,
46+
T: m.value,
47+
}
3248
}
3349

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-
}
50+
//// Unlock unlocks the Mutex accepting a value to set as the new or mutated value.
51+
//// It is optional to pass a new value to be set but NOT required for there reasons:
52+
//// 1. If the internal value is already mutable then no need to set as changes apply as they happen.
53+
//// 2. If there's a failure working with the locked value you may NOT want to set it, but still unlock.
54+
//// 3. Supports locked values that are not mutable.
55+
////
56+
//// It is a run-time error if the Mutex is not locked on entry to Unlock.
57+
//func (m Mutex2[T]) Unlock() {
58+
// m.m.Unlock()
59+
//}
4460

4561
// PerformMut safely locks and unlocks the Mutex values and performs the provided function returning its error if one
4662
// 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()
63+
func (m Mutex2[T, M]) PerformMut(f func(T)) {
64+
guard := m.Lock()
65+
f(guard.T)
66+
guard.Unlock()
5067
}
5168

5269
// TryLock tries to lock Mutex and reports whether it succeeded.
5370
// 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{}] {
71+
func (m Mutex2[T, M]) TryLock() resultext.Result[MutexGuard[T, *sync.Mutex], struct{}] {
5572
if m.m.TryLock() {
56-
return resultext.Ok[T, struct{}](m.value)
73+
return resultext.Ok[MutexGuard[T, *sync.Mutex], struct{}](MutexGuard[T, *sync.Mutex]{
74+
m: m.m,
75+
T: m.value,
76+
})
5777
} else {
58-
return resultext.Err[T, struct{}](struct{}{})
78+
return resultext.Err[MutexGuard[T, *sync.Mutex], struct{}](struct{}{})
5979
}
6080
}
6181

82+
// RMutexGuard protects the inner contents of a RWMutex2 for safety and unlocking.
83+
type RMutexGuard[T any] struct {
84+
rw *sync.RWMutex
85+
// T is the inner generic type of the Mutex
86+
T T
87+
}
88+
89+
// RUnlock unlocks the RWMutex2 value.
90+
func (g RMutexGuard[T]) RUnlock() {
91+
g.rw.RUnlock()
92+
}
93+
6294
// NewRWMutex2 creates a new RWMutex for use.
63-
func NewRWMutex2[T any](value T) *RWMutex2[T] {
64-
return &RWMutex2[T]{
95+
func NewRWMutex2[T any](value T) RWMutex2[T] {
96+
return RWMutex2[T]{
97+
rw: new(sync.RWMutex),
6598
value: value,
6699
}
67100
}
68101

69102
// RWMutex2 creates a type safe RWMutex wrapper ensuring one cannot access the values of a locked values
70103
// without first gaining a lock.
71104
type RWMutex2[T any] struct {
72-
rw sync.RWMutex
105+
rw *sync.RWMutex
73106
value T
74107
}
75108

76109
// Lock locks the Mutex and returns value for use, safe for mutation if
77110
//
78111
// If the lock is already in use, the calling goroutine blocks until the mutex is available.
79-
func (m *RWMutex2[T]) Lock() T {
112+
func (m RWMutex2[T]) Lock() MutexGuard[T, *sync.RWMutex] {
80113
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()
114+
return MutexGuard[T, *sync.RWMutex]{
115+
m: m.rw,
116+
T: m.value,
117+
}
93118
}
94119

95120
// 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()
121+
func (m RWMutex2[T]) PerformMut(f func(T)) {
122+
guard := m.Lock()
123+
f(guard.T)
124+
guard.Unlock()
99125
}
100126

101127
// TryLock tries to lock RWMutex and returns the value in the Ok result if successful.
102128
// 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{}] {
129+
func (m RWMutex2[T]) TryLock() resultext.Result[MutexGuard[T, *sync.RWMutex], struct{}] {
104130
if m.rw.TryLock() {
105-
return resultext.Ok[T, struct{}](m.value)
131+
return resultext.Ok[MutexGuard[T, *sync.RWMutex], struct{}](
132+
MutexGuard[T, *sync.RWMutex]{
133+
m: m.rw,
134+
T: m.value,
135+
})
106136
} else {
107-
return resultext.Err[T, struct{}](struct{}{})
137+
return resultext.Err[MutexGuard[T, *sync.RWMutex], struct{}](struct{}{})
108138
}
109139
}
110140

111141
// 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()
142+
func (m RWMutex2[T]) Perform(f func(T)) {
143+
guard := m.RLock()
144+
f(guard.T)
145+
guard.RUnlock()
116146
}
117147

118148
// RLock locks the RWMutex for reading and returns the value for read-only use.
119149
// 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 {
150+
func (m RWMutex2[T]) RLock() RMutexGuard[T] {
121151
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()
152+
return RMutexGuard[T]{
153+
rw: m.rw,
154+
T: m.value,
155+
}
129156
}
130157

131158
// TryRLock tries to lock RWMutex for reading and returns the value in the Ok result if successful.
132159
// 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{}] {
160+
func (m RWMutex2[T]) TryRLock() resultext.Result[RMutexGuard[T], struct{}] {
134161
if m.rw.TryRLock() {
135-
return resultext.Ok[T, struct{}](m.value)
162+
return resultext.Ok[RMutexGuard[T], struct{}](
163+
RMutexGuard[T]{
164+
rw: m.rw,
165+
T: m.value,
166+
},
167+
)
136168
} else {
137-
return resultext.Err[T, struct{}](struct{}{})
169+
return resultext.Err[RMutexGuard[T], struct{}](struct{}{})
138170
}
139171
}

sync/mutex2_test.go

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,65 @@ package syncext
55

66
import (
77
resultext "github.com/go-playground/pkg/v5/values/result"
8+
"sync"
89
"testing"
910

1011
. "github.com/go-playground/assert/v2"
1112
)
1213

1314
func TestMutex2(t *testing.T) {
1415
m := NewMutex2(make(map[string]int))
15-
m.Lock()["foo"] = 1
16-
m.Unlock()
16+
guard := m.Lock()
17+
guard.T["foo"] = 1
18+
guard.Unlock()
1719

1820
m.PerformMut(func(m map[string]int) {
1921
m["boo"] = 1
2022
})
21-
myMap := m.Lock()
23+
guard = m.Lock()
24+
myMap := guard.T
2225
Equal(t, 2, len(myMap))
2326
Equal(t, myMap["foo"], 1)
2427
Equal(t, myMap["boo"], 1)
25-
Equal(t, m.TryLock(), resultext.Err[map[string]int](struct{}{}))
26-
m.Unlock()
28+
Equal(t, m.TryLock(), resultext.Err[MutexGuard[map[string]int, *sync.Mutex]](struct{}{}))
29+
guard.Unlock()
2730

2831
result := m.TryLock()
2932
Equal(t, result.IsOk(), true)
30-
m.Unlock()
33+
result.Unwrap().Unlock()
3134
}
3235

3336
func TestRWMutex2(t *testing.T) {
3437
m := NewRWMutex2(make(map[string]int))
35-
m.Lock()["foo"] = 1
38+
guard := m.Lock()
39+
guard.T["foo"] = 1
3640
Equal(t, m.TryLock().IsOk(), false)
3741
Equal(t, m.TryRLock().IsOk(), false)
38-
m.Unlock()
42+
guard.Unlock()
3943

4044
m.PerformMut(func(m map[string]int) {
4145
m["boo"] = 2
4246
})
43-
mp := m.Lock()
47+
guard = m.Lock()
48+
mp := guard.T
4449
Equal(t, mp["foo"], 1)
4550
Equal(t, mp["boo"], 2)
46-
m.Unlock()
51+
guard.Unlock()
4752

48-
myMap := m.RLock()
53+
rguard := m.RLock()
54+
myMap := rguard.T
4955
Equal(t, len(myMap), 2)
5056
Equal(t, m.TryRLock().IsOk(), true)
51-
m.RUnlock()
57+
rguard.RUnlock()
5258

5359
m.Perform(func(m map[string]int) {
5460
Equal(t, 1, m["foo"])
5561
Equal(t, 2, m["boo"])
5662
})
57-
myMap = m.RLock()
63+
rguard = m.RLock()
64+
myMap = rguard.T
5865
Equal(t, len(myMap), 2)
5966
Equal(t, myMap["foo"], 1)
6067
Equal(t, myMap["boo"], 2)
61-
m.RUnlock()
68+
rguard.RUnlock()
6269
}

0 commit comments

Comments
 (0)