feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

21
vendor/github.com/anacrolix/stm/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Luke Champine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

124
vendor/github.com/anacrolix/stm/README.md generated vendored Normal file
View File

@@ -0,0 +1,124 @@
# stm
[![Go Reference](https://pkg.go.dev/badge/github.com/anacrolix/stm.svg)](https://pkg.go.dev/github.com/anacrolix/stm)
[![Go Report Card](https://goreportcard.com/badge/github.com/anacrolix/stm)](https://goreportcard.com/report/github.com/anacrolix/stm)
Package `stm` provides [Software Transactional Memory](https://en.wikipedia.org/wiki/Software_transactional_memory) operations for Go. This is
an alternative to the standard way of writing concurrent code (channels and
mutexes). STM makes it easy to perform arbitrarily complex operations in an
atomic fashion. One of its primary advantages over traditional locking is that
STM transactions are composable, whereas locking functions are not -- the
composition will either deadlock or release the lock between functions (making
it non-atomic).
The `stm` API tries to mimic that of Haskell's [`Control.Concurrent.STM`](https://hackage.haskell.org/package/stm-2.4.4.1/docs/Control-Concurrent-STM.html), but
this is not entirely possible due to Go's type system; we are forced to use
`interface{}` and type assertions. Furthermore, Haskell can enforce at compile
time that STM variables are not modified outside the STM monad. This is not
possible in Go, so be especially careful when using pointers in your STM code.
Unlike Haskell, data in Go is not immutable by default, which means you have
to be careful when using STM to manage pointers. If two goroutines have access
to the same pointer, it doesn't matter whether they retrieved the pointer
atomically; modifying the pointer can still cause a data race. To resolve
this, either use immutable data structures, or replace pointers with STM
variables. A more concrete example is given below.
It remains to be seen whether this style of concurrency has practical
applications in Go. If you find this package useful, please tell us about it!
## Examples
See the package examples in the Go package docs for examples of common operations.
See [example_santa_test.go](example_santa_test.go) for a more complex example.
## Pointers
Note that `Operation` now returns a value of type `interface{}`, which isn't included in the
examples throughout the documentation yet. See the type signatures for `Atomically` and `Operation`.
Be very careful when managing pointers inside transactions! (This includes
slices, maps, channels, and captured variables.) Here's why:
```go
p := stm.NewVar([]byte{1,2,3})
stm.Atomically(func(tx *stm.Tx) {
b := tx.Get(p).([]byte)
b[0] = 7
tx.Set(p, b)
})
```
This transaction looks innocent enough, but it has a hidden side effect: the
modification of b is visible outside the transaction. Instead of modifying
pointers directly, prefer to operate on immutable values as much as possible.
Following this advice, we can rewrite the transaction to perform a copy:
```go
stm.Atomically(func(tx *stm.Tx) {
b := tx.Get(p).([]byte)
c := make([]byte, len(b))
copy(c, b)
c[0] = 7
tx.Set(p, c)
})
```
This is less efficient, but it preserves atomicity.
In the same vein, it would be a mistake to do this:
```go
type foo struct {
i int
}
p := stm.NewVar(&foo{i: 2})
stm.Atomically(func(tx *stm.Tx) {
f := tx.Get(p).(*foo)
f.i = 7
tx.Set(p, f)
})
```
...because setting `f.i` is a side-effect that escapes the transaction. Here,
the correct approach is to move the `Var` inside the struct:
```go
type foo struct {
i *stm.Var
}
f := foo{i: stm.NewVar(2)}
stm.Atomically(func(tx *stm.Tx) {
i := tx.Get(f.i).(int)
i = 7
tx.Set(f.i, i)
})
```
## Benchmarks
In synthetic benchmarks, STM seems to have a 1-5x performance penalty compared
to traditional mutex- or channel-based concurrency. However, note that these
benchmarks exhibit a lot of data contention, which is where STM is weakest.
For example, in `BenchmarkIncrementSTM`, each increment transaction retries an
average of 2.5 times. Less contentious benchmarks are forthcoming.
```
BenchmarkAtomicGet-4 50000000 26.7 ns/op
BenchmarkAtomicSet-4 20000000 65.7 ns/op
BenchmarkIncrementSTM-4 500 2852492 ns/op
BenchmarkIncrementMutex-4 2000 645122 ns/op
BenchmarkIncrementChannel-4 2000 986317 ns/op
BenchmarkReadVarSTM-4 5000 268726 ns/op
BenchmarkReadVarMutex-4 10000 248479 ns/op
BenchmarkReadVarChannel-4 10000 240086 ns/op
```
## Credits
Package stm was [originally](https://github.com/lukechampine/stm/issues/3#issuecomment-549087541)
created by lukechampine.

8
vendor/github.com/anacrolix/stm/bench generated vendored Normal file
View File

@@ -0,0 +1,8 @@
benchout="benches/$(./describe)"
echo
echo writing "$benchout"
echo
go test -bench . -benchtime 3s -benchmem | tee "$benchout"
echo
echo wrote "$benchout"
echo

1
vendor/github.com/anacrolix/stm/describe generated vendored Normal file
View File

@@ -0,0 +1 @@
echo $(git rev-parse --short HEAD)$(git diff --quiet || echo -dirty)

79
vendor/github.com/anacrolix/stm/doc.go generated vendored Normal file
View File

@@ -0,0 +1,79 @@
/*
Package stm provides Software Transactional Memory operations for Go. This is
an alternative to the standard way of writing concurrent code (channels and
mutexes). STM makes it easy to perform arbitrarily complex operations in an
atomic fashion. One of its primary advantages over traditional locking is that
STM transactions are composable, whereas locking functions are not -- the
composition will either deadlock or release the lock between functions (making
it non-atomic).
To begin, create an STM object that wraps the data you want to access
concurrently.
x := stm.NewVar(3)
You can then use the Atomically method to atomically read and/or write the the
data. This code atomically decrements x:
stm.Atomically(func(tx *stm.Tx) {
cur := tx.Get(x).(int)
tx.Set(x, cur-1)
})
An important part of STM transactions is retrying. At any point during the
transaction, you can call tx.Retry(), which will abort the transaction, but
not cancel it entirely. The call to Atomically will block until another call
to Atomically finishes, at which point the transaction will be rerun.
Specifically, one of the values read by the transaction (via tx.Get) must be
updated before the transaction will be rerun. As an example, this code will
try to decrement x, but will block as long as x is zero:
stm.Atomically(func(tx *stm.Tx) {
cur := tx.Get(x).(int)
if cur == 0 {
tx.Retry()
}
tx.Set(x, cur-1)
})
Internally, tx.Retry simply calls panic(stm.Retry). Panicking with any other
value will cancel the transaction; no values will be changed. However, it is
the responsibility of the caller to catch such panics.
Multiple transactions can be composed using Select. If the first transaction
calls Retry, the next transaction will be run, and so on. If all of the
transactions call Retry, the call will block and the entire selection will be
retried. For example, this code implements the "decrement-if-nonzero"
transaction above, but for two values. It will first try to decrement x, then
y, and block if both values are zero.
func dec(v *stm.Var) {
return func(tx *stm.Tx) {
cur := tx.Get(v).(int)
if cur == 0 {
tx.Retry()
}
tx.Set(v, cur-1)
}
}
// Note that Select does not perform any work itself, but merely
// returns a transaction function.
stm.Atomically(stm.Select(dec(x), dec(y)))
An important caveat: transactions must be idempotent (they should have the
same effect every time they are invoked). This is because a transaction may be
retried several times before successfully completing, meaning its side effects
may execute more than once. This will almost certainly cause incorrect
behavior. One common way to get around this is to build up a list of impure
operations inside the transaction, and then perform them after the transaction
completes.
The stm API tries to mimic that of Haskell's Control.Concurrent.STM, but this
is not entirely possible due to Go's type system; we are forced to use
interface{} and type assertions. Furthermore, Haskell can enforce at compile
time that STM variables are not modified outside the STM monad. This is not
possible in Go, so be especially careful when using pointers in your STM code.
Remember: modifying a pointer is a side effect!
*/
package stm

177
vendor/github.com/anacrolix/stm/funcs.go generated vendored Normal file
View File

@@ -0,0 +1,177 @@
package stm
import (
"math/rand"
"reflect"
"runtime/pprof"
"sync"
"time"
)
var (
txPool = sync.Pool{New: func() interface{} {
expvars.Add("new txs", 1)
tx := &Tx{
reads: make(map[*Var]VarValue),
writes: make(map[*Var]interface{}),
watching: make(map[*Var]struct{}),
}
tx.cond.L = &tx.mu
return tx
}}
failedCommitsProfile *pprof.Profile
)
const (
profileFailedCommits = false
sleepBetweenRetries = false
)
func init() {
if profileFailedCommits {
failedCommitsProfile = pprof.NewProfile("stmFailedCommits")
}
}
func newTx() *Tx {
tx := txPool.Get().(*Tx)
tx.tries = 0
tx.completed = false
return tx
}
func WouldBlock(fn Operation) (block bool) {
tx := newTx()
tx.reset()
_, block = catchRetry(fn, tx)
if len(tx.watching) != 0 {
panic("shouldn't have installed any watchers")
}
tx.recycle()
return
}
// Atomically executes the atomic function fn.
func Atomically(op Operation) interface{} {
expvars.Add("atomically", 1)
// run the transaction
tx := newTx()
retry:
tx.tries++
tx.reset()
if sleepBetweenRetries {
shift := int64(tx.tries - 1)
const maxShift = 30
if shift > maxShift {
shift = maxShift
}
ns := int64(1) << shift
d := time.Duration(rand.Int63n(ns))
if d > 100*time.Microsecond {
tx.updateWatchers()
time.Sleep(time.Duration(ns))
}
}
tx.mu.Lock()
ret, retry := catchRetry(op, tx)
tx.mu.Unlock()
if retry {
expvars.Add("retries", 1)
// wait for one of the variables we read to change before retrying
tx.wait()
goto retry
}
// verify the read log
tx.lockAllVars()
if tx.inputsChanged() {
tx.unlock()
expvars.Add("failed commits", 1)
if profileFailedCommits {
failedCommitsProfile.Add(new(int), 0)
}
goto retry
}
// commit the write log and broadcast that variables have changed
tx.commit()
tx.mu.Lock()
tx.completed = true
tx.cond.Broadcast()
tx.mu.Unlock()
tx.unlock()
expvars.Add("commits", 1)
tx.recycle()
return ret
}
// AtomicGet is a helper function that atomically reads a value.
func AtomicGet(v *Var) interface{} {
return v.value.Load().(VarValue).Get()
}
// AtomicSet is a helper function that atomically writes a value.
func AtomicSet(v *Var, val interface{}) {
v.mu.Lock()
v.changeValue(val)
v.mu.Unlock()
}
// Compose is a helper function that composes multiple transactions into a
// single transaction.
func Compose(fns ...Operation) Operation {
return func(tx *Tx) interface{} {
for _, f := range fns {
f(tx)
}
return nil
}
}
// Select runs the supplied functions in order. Execution stops when a
// function succeeds without calling Retry. If no functions succeed, the
// entire selection will be retried.
func Select(fns ...Operation) Operation {
return func(tx *Tx) interface{} {
switch len(fns) {
case 0:
// empty Select blocks forever
tx.Retry()
panic("unreachable")
case 1:
return fns[0](tx)
default:
oldWrites := tx.writes
tx.writes = make(map[*Var]interface{}, len(oldWrites))
for k, v := range oldWrites {
tx.writes[k] = v
}
ret, retry := catchRetry(fns[0], tx)
if retry {
tx.writes = oldWrites
return Select(fns[1:]...)(tx)
} else {
return ret
}
}
}
}
type Operation func(*Tx) interface{}
func VoidOperation(f func(*Tx)) Operation {
return func(tx *Tx) interface{} {
f(tx)
return nil
}
}
func AtomicModify(v *Var, f interface{}) {
r := reflect.ValueOf(f)
Atomically(VoidOperation(func(tx *Tx) {
cur := reflect.ValueOf(tx.Get(v))
out := r.Call([]reflect.Value{cur})
if lenOut := len(out); lenOut != 1 {
panic(lenOut)
}
tx.Set(v, out[0].Interface())
}))
}

7
vendor/github.com/anacrolix/stm/metrics.go generated vendored Normal file
View File

@@ -0,0 +1,7 @@
package stm
import (
"expvar"
)
var expvars = expvar.NewMap("stm")

24
vendor/github.com/anacrolix/stm/retry.go generated vendored Normal file
View File

@@ -0,0 +1,24 @@
package stm
import (
"runtime/pprof"
)
var retries = pprof.NewProfile("stmRetries")
// retry is a sentinel value. When thrown via panic, it indicates that a
// transaction should be retried.
var retry = &struct{}{}
// catchRetry returns true if fn calls tx.Retry.
func catchRetry(fn Operation, tx *Tx) (result interface{}, gotRetry bool) {
defer func() {
if r := recover(); r == retry {
gotRetry = true
} else if r != nil {
panic(r)
}
}()
result = fn(tx)
return
}

182
vendor/github.com/anacrolix/stm/stmutil/containers.go generated vendored Normal file
View File

@@ -0,0 +1,182 @@
package stmutil
import (
"unsafe"
"github.com/benbjohnson/immutable"
"github.com/anacrolix/missinggo/v2/iter"
)
type Settish interface {
Add(interface{}) Settish
Delete(interface{}) Settish
Contains(interface{}) bool
Range(func(interface{}) bool)
iter.Iterable
Len() int
}
type mapToSet struct {
m Mappish
}
type interhash struct{}
func (interhash) Hash(x interface{}) uint32 {
return uint32(nilinterhash(unsafe.Pointer(&x), 0))
}
func (interhash) Equal(i, j interface{}) bool {
return i == j
}
func NewSet() Settish {
return mapToSet{NewMap()}
}
func NewSortedSet(lesser lessFunc) Settish {
return mapToSet{NewSortedMap(lesser)}
}
func (s mapToSet) Add(x interface{}) Settish {
s.m = s.m.Set(x, nil)
return s
}
func (s mapToSet) Delete(x interface{}) Settish {
s.m = s.m.Delete(x)
return s
}
func (s mapToSet) Len() int {
return s.m.Len()
}
func (s mapToSet) Contains(x interface{}) bool {
_, ok := s.m.Get(x)
return ok
}
func (s mapToSet) Range(f func(interface{}) bool) {
s.m.Range(func(k, _ interface{}) bool {
return f(k)
})
}
func (s mapToSet) Iter(cb iter.Callback) {
s.Range(cb)
}
type Map struct {
*immutable.Map
}
func NewMap() Mappish {
return Map{immutable.NewMap(interhash{})}
}
var _ Mappish = Map{}
func (m Map) Delete(x interface{}) Mappish {
m.Map = m.Map.Delete(x)
return m
}
func (m Map) Set(key, value interface{}) Mappish {
m.Map = m.Map.Set(key, value)
return m
}
func (sm Map) Range(f func(key, value interface{}) bool) {
iter := sm.Map.Iterator()
for !iter.Done() {
if !f(iter.Next()) {
return
}
}
}
func (sm Map) Iter(cb iter.Callback) {
sm.Range(func(key, _ interface{}) bool {
return cb(key)
})
}
type SortedMap struct {
*immutable.SortedMap
}
func (sm SortedMap) Set(key, value interface{}) Mappish {
sm.SortedMap = sm.SortedMap.Set(key, value)
return sm
}
func (sm SortedMap) Delete(key interface{}) Mappish {
sm.SortedMap = sm.SortedMap.Delete(key)
return sm
}
func (sm SortedMap) Range(f func(key, value interface{}) bool) {
iter := sm.SortedMap.Iterator()
for !iter.Done() {
if !f(iter.Next()) {
return
}
}
}
func (sm SortedMap) Iter(cb iter.Callback) {
sm.Range(func(key, _ interface{}) bool {
return cb(key)
})
}
type lessFunc func(l, r interface{}) bool
type comparer struct {
less lessFunc
}
func (me comparer) Compare(i, j interface{}) int {
if me.less(i, j) {
return -1
} else if me.less(j, i) {
return 1
} else {
return 0
}
}
func NewSortedMap(less lessFunc) Mappish {
return SortedMap{
SortedMap: immutable.NewSortedMap(comparer{less}),
}
}
type Mappish interface {
Set(key, value interface{}) Mappish
Delete(key interface{}) Mappish
Get(key interface{}) (interface{}, bool)
Range(func(_, _ interface{}) bool)
Len() int
iter.Iterable
}
func GetLeft(l, _ interface{}) interface{} {
return l
}
//go:noescape
//go:linkname nilinterhash runtime.nilinterhash
func nilinterhash(p unsafe.Pointer, h uintptr) uintptr
func interfaceHash(x interface{}) uint32 {
return uint32(nilinterhash(unsafe.Pointer(&x), 0))
}
type Lenner interface {
Len() int
}
type List = *immutable.List

39
vendor/github.com/anacrolix/stm/stmutil/context.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package stmutil
import (
"context"
"sync"
"github.com/anacrolix/stm"
)
var (
mu sync.Mutex
ctxVars = map[context.Context]*stm.Var{}
)
// Returns an STM var that contains a bool equal to `ctx.Err != nil`, and a cancel function to be
// called when the user is no longer interested in the var.
func ContextDoneVar(ctx context.Context) (*stm.Var, func()) {
mu.Lock()
defer mu.Unlock()
if v, ok := ctxVars[ctx]; ok {
return v, func() {}
}
if ctx.Err() != nil {
// TODO: What if we had read-only Vars? Then we could have a global one for this that we
// just reuse.
v := stm.NewBuiltinEqVar(true)
return v, func() {}
}
v := stm.NewVar(false)
go func() {
<-ctx.Done()
stm.AtomicSet(v, true)
mu.Lock()
delete(ctxVars, ctx)
mu.Unlock()
}()
ctxVars[ctx] = v
return v, func() {}
}

223
vendor/github.com/anacrolix/stm/tx.go generated vendored Normal file
View File

@@ -0,0 +1,223 @@
package stm
import (
"fmt"
"sort"
"sync"
"unsafe"
)
// A Tx represents an atomic transaction.
type Tx struct {
reads map[*Var]VarValue
writes map[*Var]interface{}
watching map[*Var]struct{}
locks txLocks
mu sync.Mutex
cond sync.Cond
waiting bool
completed bool
tries int
numRetryValues int
}
// Check that none of the logged values have changed since the transaction began.
func (tx *Tx) inputsChanged() bool {
for v, read := range tx.reads {
if read.Changed(v.value.Load().(VarValue)) {
return true
}
}
return false
}
// Writes the values in the transaction log to their respective Vars.
func (tx *Tx) commit() {
for v, val := range tx.writes {
v.changeValue(val)
}
}
func (tx *Tx) updateWatchers() {
for v := range tx.watching {
if _, ok := tx.reads[v]; !ok {
delete(tx.watching, v)
v.watchers.Delete(tx)
}
}
for v := range tx.reads {
if _, ok := tx.watching[v]; !ok {
v.watchers.Store(tx, nil)
tx.watching[v] = struct{}{}
}
}
}
// wait blocks until another transaction modifies any of the Vars read by tx.
func (tx *Tx) wait() {
if len(tx.reads) == 0 {
panic("not waiting on anything")
}
tx.updateWatchers()
tx.mu.Lock()
firstWait := true
for !tx.inputsChanged() {
if !firstWait {
expvars.Add("wakes for unchanged versions", 1)
}
expvars.Add("waits", 1)
tx.waiting = true
tx.cond.Broadcast()
tx.cond.Wait()
tx.waiting = false
firstWait = false
}
tx.mu.Unlock()
}
// Get returns the value of v as of the start of the transaction.
func (tx *Tx) Get(v *Var) interface{} {
// If we previously wrote to v, it will be in the write log.
if val, ok := tx.writes[v]; ok {
return val
}
// If we haven't previously read v, record its version
vv, ok := tx.reads[v]
if !ok {
vv = v.value.Load().(VarValue)
tx.reads[v] = vv
}
return vv.Get()
}
// Set sets the value of a Var for the lifetime of the transaction.
func (tx *Tx) Set(v *Var, val interface{}) {
if v == nil {
panic("nil Var")
}
tx.writes[v] = val
}
type txProfileValue struct {
*Tx
int
}
// Retry aborts the transaction and retries it when a Var changes. You can return from this method
// to satisfy return values, but it should never actually return anything as it panics internally.
func (tx *Tx) Retry() interface{} {
retries.Add(txProfileValue{tx, tx.numRetryValues}, 1)
tx.numRetryValues++
panic(retry)
panic("unreachable")
}
// Assert is a helper function that retries a transaction if the condition is
// not satisfied.
func (tx *Tx) Assert(p bool) {
if !p {
tx.Retry()
}
}
func (tx *Tx) reset() {
tx.mu.Lock()
for k := range tx.reads {
delete(tx.reads, k)
}
for k := range tx.writes {
delete(tx.writes, k)
}
tx.mu.Unlock()
tx.removeRetryProfiles()
tx.resetLocks()
}
func (tx *Tx) removeRetryProfiles() {
for tx.numRetryValues > 0 {
tx.numRetryValues--
retries.Remove(txProfileValue{tx, tx.numRetryValues})
}
}
func (tx *Tx) recycle() {
for v := range tx.watching {
delete(tx.watching, v)
v.watchers.Delete(tx)
}
tx.removeRetryProfiles()
// I don't think we can reuse Txs, because the "completed" field should/needs to be set
// indefinitely after use.
//txPool.Put(tx)
}
func (tx *Tx) lockAllVars() {
tx.resetLocks()
tx.collectAllLocks()
tx.sortLocks()
tx.lock()
}
func (tx *Tx) resetLocks() {
tx.locks.clear()
}
func (tx *Tx) collectReadLocks() {
for v := range tx.reads {
tx.locks.append(&v.mu)
}
}
func (tx *Tx) collectAllLocks() {
tx.collectReadLocks()
for v := range tx.writes {
if _, ok := tx.reads[v]; !ok {
tx.locks.append(&v.mu)
}
}
}
func (tx *Tx) sortLocks() {
sort.Sort(&tx.locks)
}
func (tx *Tx) lock() {
for _, l := range tx.locks.mus {
l.Lock()
}
}
func (tx *Tx) unlock() {
for _, l := range tx.locks.mus {
l.Unlock()
}
}
func (tx *Tx) String() string {
return fmt.Sprintf("%[1]T %[1]p", tx)
}
// Dedicated type avoids reflection in sort.Slice.
type txLocks struct {
mus []*sync.Mutex
}
func (me txLocks) Len() int {
return len(me.mus)
}
func (me txLocks) Less(i, j int) bool {
return uintptr(unsafe.Pointer(me.mus[i])) < uintptr(unsafe.Pointer(me.mus[j]))
}
func (me txLocks) Swap(i, j int) {
me.mus[i], me.mus[j] = me.mus[j], me.mus[i]
}
func (me *txLocks) clear() {
me.mus = me.mus[:0]
}
func (me *txLocks) append(mu *sync.Mutex) {
me.mus = append(me.mus, mu)
}

51
vendor/github.com/anacrolix/stm/var-value.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
package stm
type VarValue interface {
Set(interface{}) VarValue
Get() interface{}
Changed(VarValue) bool
}
type version uint64
type versionedValue struct {
value interface{}
version version
}
func (me versionedValue) Set(newValue interface{}) VarValue {
return versionedValue{
value: newValue,
version: me.version + 1,
}
}
func (me versionedValue) Get() interface{} {
return me.value
}
func (me versionedValue) Changed(other VarValue) bool {
return me.version != other.(versionedValue).version
}
type customVarValue struct {
value interface{}
changed func(interface{}, interface{}) bool
}
var _ VarValue = customVarValue{}
func (me customVarValue) Changed(other VarValue) bool {
return me.changed(me.value, other.(customVarValue).value)
}
func (me customVarValue) Set(newValue interface{}) VarValue {
return customVarValue{
value: newValue,
changed: me.changed,
}
}
func (me customVarValue) Get() interface{} {
return me.value
}

68
vendor/github.com/anacrolix/stm/var.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
package stm
import (
"sync"
"sync/atomic"
)
// Holds an STM variable.
type Var struct {
value atomic.Value
watchers sync.Map
mu sync.Mutex
}
func (v *Var) changeValue(new interface{}) {
old := v.value.Load().(VarValue)
newVarValue := old.Set(new)
v.value.Store(newVarValue)
if old.Changed(newVarValue) {
go v.wakeWatchers(newVarValue)
}
}
func (v *Var) wakeWatchers(new VarValue) {
v.watchers.Range(func(k, _ interface{}) bool {
tx := k.(*Tx)
// We have to lock here to ensure that the Tx is waiting before we signal it. Otherwise we
// could signal it before it goes to sleep and it will miss the notification.
tx.mu.Lock()
if read := tx.reads[v]; read != nil && read.Changed(new) {
tx.cond.Broadcast()
for !tx.waiting && !tx.completed {
tx.cond.Wait()
}
}
tx.mu.Unlock()
return !v.value.Load().(VarValue).Changed(new)
})
}
type varSnapshot struct {
val interface{}
version uint64
}
// Returns a new STM variable.
func NewVar(val interface{}) *Var {
v := &Var{}
v.value.Store(versionedValue{
value: val,
})
return v
}
func NewCustomVar(val interface{}, changed func(interface{}, interface{}) bool) *Var {
v := &Var{}
v.value.Store(customVarValue{
value: val,
changed: changed,
})
return v
}
func NewBuiltinEqVar(val interface{}) *Var {
return NewCustomVar(val, func(a, b interface{}) bool {
return a != b
})
}