21
vendor/github.com/anacrolix/stm/LICENSE
generated
vendored
Normal file
21
vendor/github.com/anacrolix/stm/LICENSE
generated
vendored
Normal 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
124
vendor/github.com/anacrolix/stm/README.md
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
# stm
|
||||
|
||||
[](https://pkg.go.dev/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
8
vendor/github.com/anacrolix/stm/bench
generated
vendored
Normal 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
1
vendor/github.com/anacrolix/stm/describe
generated
vendored
Normal 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
79
vendor/github.com/anacrolix/stm/doc.go
generated
vendored
Normal 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
177
vendor/github.com/anacrolix/stm/funcs.go
generated
vendored
Normal 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
7
vendor/github.com/anacrolix/stm/metrics.go
generated
vendored
Normal 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
24
vendor/github.com/anacrolix/stm/retry.go
generated
vendored
Normal 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
182
vendor/github.com/anacrolix/stm/stmutil/containers.go
generated
vendored
Normal 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
39
vendor/github.com/anacrolix/stm/stmutil/context.go
generated
vendored
Normal 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
223
vendor/github.com/anacrolix/stm/tx.go
generated
vendored
Normal 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
51
vendor/github.com/anacrolix/stm/var-value.go
generated
vendored
Normal 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
68
vendor/github.com/anacrolix/stm/var.go
generated
vendored
Normal 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
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user