Skip to content

Commit a0a7339

Browse files
author
Dean Karn
authored
Fix mutex (#14)
This is a breaking change to the Mutex interface, the previous was not usable in the manner intended.
1 parent b4e7879 commit a0a7339

File tree

4 files changed

+82
-43
lines changed

4 files changed

+82
-43
lines changed

.github/workflows/go.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@ jobs:
88
test:
99
strategy:
1010
matrix:
11-
go-version: [1.18.x,1.17.x]
11+
go-version: [1.19.x,1.18.x,1.17.x]
1212
os: [ubuntu-latest, macos-latest, windows-latest]
1313
runs-on: ${{ matrix.os }}
1414
steps:
1515
- name: Install Go
16-
uses: actions/setup-go@v2
16+
uses: actions/setup-go@v3
1717
with:
1818
go-version: ${{ matrix.go-version }}
1919

2020
- name: Checkout code
21-
uses: actions/checkout@v2
21+
uses: actions/checkout@v3
2222

2323
- name: Restore Cache
24-
uses: actions/cache@v2
24+
uses: actions/cache@v3
2525
with:
2626
path: ~/go/pkg/mod
2727
key: ${{ runner.os }}-v1-go-${{ hashFiles('**/go.sum') }}
2828
restore-keys: |
2929
${{ runner.os }}-v1-go-
3030
3131
- name: Checkout code
32-
uses: actions/checkout@v2
32+
uses: actions/checkout@v3
3333

3434
- name: Test
3535
run: go test ./...
@@ -38,8 +38,8 @@ jobs:
3838
name: lint
3939
runs-on: ubuntu-latest
4040
steps:
41-
- uses: actions/checkout@v2
41+
- uses: actions/checkout@v3
4242
- name: golangci-lint
43-
uses: golangci/golangci-lint-action@v2
43+
uses: golangci/golangci-lint-action@v3
4444
with:
45-
version: v1.45.2
45+
version: v1.50.1

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.6.1-green.svg)
3+
![Project status](https://img.shields.io/badge/version-5.6.2-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: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package syncext
44

55
import (
6+
optionext "github.com/go-playground/pkg/v5/values/option"
67
"sync"
78

89
resultext "github.com/go-playground/pkg/v5/values/result"
@@ -22,27 +23,41 @@ type Mutex[T any] struct {
2223
value T
2324
}
2425

25-
// PerformMut safely locks and unlocks the Mutex values and performs the provided function.
26+
// Lock locks the Mutex and returns value for use, safe for mutation if
2627
//
27-
// Too bad Go doesn't support PerformMut[R any](func(T) R) R syntax :(
28-
func (m *Mutex[T]) PerformMut(f func(T)) {
29-
m.Lock()
30-
defer m.Unlock()
31-
f(m.value)
32-
}
33-
34-
// Lock locks the Mutex and returns value for mutable use.
3528
// If the lock is already in use, the calling goroutine blocks until the mutex is available.
3629
func (m *Mutex[T]) Lock() T {
3730
m.m.Lock()
3831
return m.value
3932
}
4033

41-
// Unlock unlocks the Mutex. It is a run-time error if the Mutex is not locked on entry to Unlock.
42-
func (m *Mutex[T]) Unlock() {
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 *Mutex[T]) Unlock(value optionext.Option[T]) {
42+
if value.IsSome() {
43+
m.value = value.Unwrap()
44+
}
4345
m.m.Unlock()
4446
}
4547

48+
// PerformMut safely locks and unlocks the Mutex values and performs the provided function returning its error if one
49+
// otherwise setting the returned value as the new mutex value.
50+
func (m *Mutex[T]) PerformMut(f func(T) (T, error)) error {
51+
value := m.Lock()
52+
result, err := f(value)
53+
if err != nil {
54+
m.Unlock(optionext.None[T]())
55+
return err
56+
}
57+
m.Unlock(optionext.Some(result))
58+
return nil
59+
}
60+
4661
// TryLock tries to lock Mutex and reports whether it succeeded.
4762
// If it does the value is returned for use in the Ok result otherwise Err with empty value.
4863
func (m *Mutex[T]) TryLock() resultext.Result[T, struct{}] {
@@ -67,26 +82,40 @@ type RWMutex[T any] struct {
6782
value T
6883
}
6984

70-
// PerformMut safely locks and unlocks the RWMutex mutable values and performs the provided function.
85+
// Lock locks the Mutex and returns value for use, safe for mutation if
7186
//
72-
// Too bad Go doesn't support PerformMut[R any](func(T) R) R syntax :(
73-
func (m *RWMutex[T]) PerformMut(f func(T)) {
74-
m.Lock()
75-
defer m.Unlock()
76-
f(m.value)
77-
}
78-
79-
// Lock locks mutex and returns values for mutable use.
87+
// If the lock is already in use, the calling goroutine blocks until the mutex is available.
8088
func (m *RWMutex[T]) Lock() T {
8189
m.rw.Lock()
8290
return m.value
8391
}
8492

85-
// Unlock unlocks mutable lock for values.
86-
func (m *RWMutex[T]) Unlock() {
93+
// Unlock unlocks the Mutex accepting a value to set as the new or mutated value.
94+
// It is optional to pass a new value to be set but NOT required for there reasons:
95+
// 1. If the internal value is already mutable then no need to set as changes apply as they happen.
96+
// 2. If there's a failure working with the locked value you may NOT want to set it, but still unlock.
97+
// 3. Supports locked values that are not mutable.
98+
//
99+
// It is a run-time error if the Mutex is not locked on entry to Unlock.
100+
func (m *RWMutex[T]) Unlock(value optionext.Option[T]) {
101+
if value.IsSome() {
102+
m.value = value.Unwrap()
103+
}
87104
m.rw.Unlock()
88105
}
89106

107+
// PerformMut safely locks and unlocks the RWMutex mutable values and performs the provided function.
108+
func (m *RWMutex[T]) PerformMut(f func(T) (T, error)) error {
109+
value := m.Lock()
110+
result, err := f(value)
111+
if err != nil {
112+
m.Unlock(optionext.None[T]())
113+
return err
114+
}
115+
m.Unlock(optionext.Some(result))
116+
return nil
117+
}
118+
90119
// TryLock tries to lock RWMutex and returns the value in the Ok result if successful.
91120
// If it does the value is returned for use in the Ok result otherwise Err with empty value.
92121
func (m *RWMutex[T]) TryLock() resultext.Result[T, struct{}] {
@@ -98,12 +127,15 @@ func (m *RWMutex[T]) TryLock() resultext.Result[T, struct{}] {
98127
}
99128

100129
// Perform safely locks and unlocks the RWMutex read-only values and performs the provided function.
101-
//
102-
// Too bad Go doesn't support Perform[R any](func(T) R) R syntax :(
103-
func (m *RWMutex[T]) Perform(f func(T)) {
104-
m.RLock()
105-
defer m.RUnlock()
106-
f(m.value)
130+
func (m *RWMutex[T]) Perform(f func(T) error) error {
131+
result := m.RLock()
132+
err := f(result)
133+
if err != nil {
134+
m.RUnlock()
135+
return err
136+
}
137+
m.RUnlock()
138+
return nil
107139
}
108140

109141
// RLock locks the RWMutex for reading and returns the value for read-only use.

sync/mutex_test.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package syncext
44

55
import (
6+
optionext "github.com/go-playground/pkg/v5/values/option"
67
"testing"
78

89
. "github.com/go-playground/assert/v2"
@@ -11,35 +12,41 @@ import (
1112
func TestMutex(t *testing.T) {
1213
m := NewMutex(make(map[string]int))
1314
m.Lock()["foo"] = 1
14-
m.Unlock()
15+
m.Unlock(optionext.None[map[string]int]())
1516

16-
m.PerformMut(func(m map[string]int) {
17+
err := m.PerformMut(func(m map[string]int) (map[string]int, error) {
1718
m["boo"] = 1
19+
return m, nil
1820
})
21+
Equal(t, err, nil)
1922

2023
myMap := m.Lock()
2124
Equal(t, 2, len(myMap))
22-
m.Unlock()
25+
m.Unlock(optionext.None[map[string]int]())
2326
}
2427

2528
func TestRWMutex(t *testing.T) {
2629
m := NewRWMutex(make(map[string]int))
2730
m.Lock()["foo"] = 1
2831
Equal(t, m.TryLock().IsOk(), false)
2932
Equal(t, m.TryRLock().IsOk(), false)
30-
m.Unlock()
33+
m.Unlock(optionext.None[map[string]int]())
3134

32-
m.PerformMut(func(m map[string]int) {
35+
err := m.PerformMut(func(m map[string]int) (map[string]int, error) {
3336
m["boo"] = 2
37+
return m, nil
3438
})
39+
Equal(t, err, nil)
3540

3641
myMap := m.RLock()
3742
Equal(t, len(myMap), 2)
3843
Equal(t, m.TryRLock().IsOk(), true)
3944
m.RUnlock()
4045

41-
m.Perform(func(m map[string]int) {
46+
err = m.Perform(func(m map[string]int) error {
4247
Equal(t, 1, m["foo"])
4348
Equal(t, 2, m["boo"])
49+
return nil
4450
})
51+
Equal(t, err, nil)
4552
}

0 commit comments

Comments
 (0)