-
Notifications
You must be signed in to change notification settings - Fork 0
/
lockable.go
107 lines (93 loc) · 2.64 KB
/
lockable.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Package lockable defines Lockable type to help construct concurrent use struct.
package lockable
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"unsafe"
)
// Lockable is an object that is safe for concurrent use
// by multiple goroutines.
//
// A Lockable must not be copied after first use.
type Lockable[T any] struct {
noCopy noCopy
// L is held while invoking Do.
// It must not be changed after first use.
L sync.Locker
data T
rlock sync.Locker
checker copyChecker
}
// New returns a new Lockable with Locker l and using data as its
// initial contents. The new Lockable takes ownership of data, and the
// caller should not use data after this call.
func New[T any](l sync.Locker, data T) *Lockable[T] {
return &Lockable[T]{L: l, data: data}
}
// A rLocker represents an object that can obtains a Locker interface that implements
// the Lock and Unlock methods by calling RLock and RUnlock.
type rLocker interface {
RLocker() sync.Locker
}
// Do calls the function f while l.L is held.
//
// If readOnly is true and l.L implements 'RLocker() sync.Locker' method,
// it will call it to obtains a Locker interface that implements
// the Lock and Unlock methods by calling RLock and RUnlock.
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
func (l *Lockable[T]) Do(readOnly bool, f func(*T) error) (err error) {
l.checker.check()
lock := l.L
if readOnly {
lock.Lock()
// Check at most once whether l.L is a RLocker.
if l.rlock == nil {
if rl, ok := l.L.(rLocker); ok {
l.rlock = rl.RLocker()
} else {
l.rlock = l.L
}
}
lock.Unlock()
lock = l.rlock
}
lock.Lock()
defer func() {
if r := recover(); r != nil {
switch e := r.(type) {
case error:
err = e
case string:
err = errors.New(e)
default:
err = fmt.Errorf("%v", r)
}
}
lock.Unlock()
}()
return f(&l.data)
}
// copyChecker holds back pointer to itself to detect object copying.
type copyChecker uintptr
func (c *copyChecker) check() {
if uintptr(unsafe.Pointer(c)) != atomic.LoadUintptr((*uintptr)(c)) &&
!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
uintptr(unsafe.Pointer(c)) != atomic.LoadUintptr((*uintptr)(c)) {
panic("lockable.Lockable is copied")
}
}
// noCopy may be added to structs which must not be copied
// after the first use.
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
//
// Note that it must not be embedded, due to the Lock and Unlock methods.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}