From 30943ded71e123886291ad393e55bfb6aa837df3 Mon Sep 17 00:00:00 2001 From: Chris Wendt Date: Wed, 8 Jun 2022 02:28:37 -0600 Subject: BIG change: generic Var[T], txVar, etc. --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 6330f79..22dfdda 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,11 @@ 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}) +p := stm.NewVar[[]byte]([]byte{1,2,3}) stm.Atomically(func(tx *stm.Tx) { - b := tx.Get(p).([]byte) + b := p.Get(tx) b[0] = 7 - tx.Set(p, b) + stm.p.Set(tx, b) }) ``` @@ -57,11 +57,11 @@ Following this advice, we can rewrite the transaction to perform a copy: ```go stm.Atomically(func(tx *stm.Tx) { - b := tx.Get(p).([]byte) + b := p.Get(tx) c := make([]byte, len(b)) copy(c, b) c[0] = 7 - tx.Set(p, c) + p.Set(tx, c) }) ``` @@ -73,11 +73,11 @@ In the same vein, it would be a mistake to do this: type foo struct { i int } -p := stm.NewVar(&foo{i: 2}) +p := stm.NewVar[*foo](&foo{i: 2}) stm.Atomically(func(tx *stm.Tx) { - f := tx.Get(p).(*foo) + f := p.Get(tx) f.i = 7 - tx.Set(p, f) + stm.p.Set(tx, f) }) ``` @@ -88,11 +88,11 @@ the correct approach is to move the `Var` inside the struct: type foo struct { i *stm.Var } -f := foo{i: stm.NewVar(2)} +f := foo{i: stm.NewVar[int](2)} stm.Atomically(func(tx *stm.Tx) { - i := tx.Get(f.i).(int) + i := f.i.Get(tx).(int) i = 7 - tx.Set(f.i, i) + f.i.Set(tx, i) }) ``` -- cgit v1.2.3 From ea580b2c20a5e26e68c8f1d111bbe3adfb3e54d9 Mon Sep 17 00:00:00 2001 From: Chris Wendt Date: Wed, 8 Jun 2022 03:15:50 -0600 Subject: update README --- README.md | 9 ++++----- doc.go | 10 ++++------ 2 files changed, 8 insertions(+), 11 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 22dfdda..8073b9d 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,9 @@ 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. +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! 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 @@ -31,7 +30,7 @@ applications in Go. If you find this package useful, please tell us about it! 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. +See [cmd/santa-example/main.go](cmd/santa-example/main.go) for a more complex example. ## Pointers diff --git a/doc.go b/doc.go index 748af80..c704c33 100644 --- a/doc.go +++ b/doc.go @@ -69,11 +69,9 @@ 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! +The stm API tries to mimic that of Haskell's Control.Concurrent.STM, but +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 -- cgit v1.2.3 From a7900ffa1ba5655a5fad903c6634c888d3025d86 Mon Sep 17 00:00:00 2001 From: Chris Wendt Date: Wed, 8 Jun 2022 03:18:43 -0600 Subject: eliminate some type assertions --- README.md | 2 +- external_test.go | 12 ++++++------ rate/ratelimit.go | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 8073b9d..ba770d9 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ type foo struct { } f := foo{i: stm.NewVar[int](2)} stm.Atomically(func(tx *stm.Tx) { - i := f.i.Get(tx).(int) + i := f.i.Get(tx) i = 7 f.i.Set(tx, i) }) diff --git a/external_test.go b/external_test.go index 201712d..a56aeee 100644 --- a/external_test.go +++ b/external_test.go @@ -78,14 +78,14 @@ func BenchmarkThunderingHerd(b *testing.B) { }() } go func() { - for stm.Atomically(func(tx *stm.Tx) interface{} { + for stm.Atomically(func(tx *stm.Tx) bool { if done.Get(tx) { return false } tx.Assert(tokens.Get(tx) < maxTokens) tokens.Set(tx, tokens.Get(tx)+1) return true - }).(bool) { + }) { } }() stm.Atomically(stm.VoidOperation(func(tx *stm.Tx) { @@ -118,18 +118,18 @@ func BenchmarkInvertedThunderingHerd(b *testing.B) { }() } go func() { - for stm.Atomically(func(tx *stm.Tx) interface{} { + for stm.Atomically(func(tx *stm.Tx) bool { if done.Get(tx) { return false } tx.Assert(tokens.Get(tx) < maxTokens) tokens.Set(tx, tokens.Get(tx)+1) return true - }).(bool) { + }) { } }() go func() { - for stm.Atomically(func(tx *stm.Tx) interface{} { + for stm.Atomically(func(tx *stm.Tx) bool { tx.Assert(tokens.Get(tx) > 0) tokens.Set(tx, tokens.Get(tx)-1) pending.Get(tx).Range(func(i interface{}) bool { @@ -141,7 +141,7 @@ func BenchmarkInvertedThunderingHerd(b *testing.B) { return true }) return !done.Get(tx) - }).(bool) { + }) { } }() stm.Atomically(stm.VoidOperation(func(tx *stm.Tx) { diff --git a/rate/ratelimit.go b/rate/ratelimit.go index 6645e04..f521f66 100644 --- a/rate/ratelimit.go +++ b/rate/ratelimit.go @@ -77,9 +77,9 @@ func (rl *Limiter) Allow() bool { } func (rl *Limiter) AllowN(n numTokens) bool { - return stm.Atomically(func(tx *stm.Tx) interface{} { + return stm.Atomically(func(tx *stm.Tx) bool { return rl.takeTokens(tx, n) - }).(bool) + }) } func (rl *Limiter) AllowStm(tx *stm.Tx) bool { @@ -105,7 +105,7 @@ func (rl *Limiter) Wait(ctx context.Context) error { func (rl *Limiter) WaitN(ctx context.Context, n int) error { ctxDone, cancel := stmutil.ContextDoneVar(ctx) defer cancel() - if err := stm.Atomically(func(tx *stm.Tx) interface{} { + if err := stm.Atomically(func(tx *stm.Tx) error { if ctxDone.Get(tx) { return ctx.Err() } @@ -123,7 +123,7 @@ func (rl *Limiter) WaitN(ctx context.Context, n int) error { tx.Retry() panic("unreachable") }); err != nil { - return err.(error) + return err } return nil -- cgit v1.2.3 From e6ac933d42b3b4a8ffd891b205ba48f0c1e278a4 Mon Sep 17 00:00:00 2001 From: Chris Wendt Date: Wed, 8 Jun 2022 03:27:33 -0600 Subject: replace "interface{}" with "any" --- README.md | 2 +- external_test.go | 2 +- funcs.go | 6 +++--- stm_test.go | 8 ++++---- stmutil/containers.go | 54 +++++++++++++++++++++++++-------------------------- tx.go | 4 ++-- var-value.go | 12 ++++++------ var.go | 4 ++-- 8 files changed, 46 insertions(+), 46 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index ba770d9..07e9952 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ See [cmd/santa-example/main.go](cmd/santa-example/main.go) for a more complex ex ## Pointers -Note that `Operation` now returns a value of type `interface{}`, which isn't included in the +Note that `Operation` now returns a value of type `any`, 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 diff --git a/external_test.go b/external_test.go index a56aeee..abdf544 100644 --- a/external_test.go +++ b/external_test.go @@ -132,7 +132,7 @@ func BenchmarkInvertedThunderingHerd(b *testing.B) { for stm.Atomically(func(tx *stm.Tx) bool { tx.Assert(tokens.Get(tx) > 0) tokens.Set(tx, tokens.Get(tx)-1) - pending.Get(tx).Range(func(i interface{}) bool { + pending.Get(tx).Range(func(i any) bool { ready := i.(*stm.Var[bool]) if !ready.Get(tx) { ready.Set(tx, true) diff --git a/funcs.go b/funcs.go index c3a597b..07d35ec 100644 --- a/funcs.go +++ b/funcs.go @@ -8,11 +8,11 @@ import ( ) var ( - txPool = sync.Pool{New: func() interface{} { + txPool = sync.Pool{New: func() any { expvars.Add("new txs", 1) tx := &Tx{ reads: make(map[txVar]VarValue), - writes: make(map[txVar]interface{}), + writes: make(map[txVar]any), watching: make(map[txVar]struct{}), } tx.cond.L = &tx.mu @@ -138,7 +138,7 @@ func Select[R any](fns ...Operation[R]) Operation[R] { return fns[0](tx) default: oldWrites := tx.writes - tx.writes = make(map[txVar]interface{}, len(oldWrites)) + tx.writes = make(map[txVar]any, len(oldWrites)) for k, v := range oldWrites { tx.writes[k] = v } diff --git a/stm_test.go b/stm_test.go index 8726d40..5ed1b70 100644 --- a/stm_test.go +++ b/stm_test.go @@ -170,7 +170,7 @@ func TestCompose(t *testing.T) { func TestPanic(t *testing.T) { // normal panics should escape Atomically assert.PanicsWithValue(t, "foo", func() { - Atomically(func(*Tx) interface{} { + Atomically(func(*Tx) any { panic("foo") }) }) @@ -213,7 +213,7 @@ func testPingPong(t testing.TB, n int, afterHit func(string)) { var wg sync.WaitGroup bat := func(from, to bool, noise string) { defer wg.Done() - for !Atomically(func(tx *Tx) interface{} { + for !Atomically(func(tx *Tx) any { if doneVar.Get(tx) { return true } @@ -246,7 +246,7 @@ func TestPingPong(t *testing.T) { func TestSleepingBeauty(t *testing.T) { require.Panics(t, func() { - Atomically(func(tx *Tx) interface{} { + Atomically(func(tx *Tx) any { tx.Assert(false) return nil }) @@ -262,7 +262,7 @@ func TestSleepingBeauty(t *testing.T) { // i++ // } // }() -// Atomically(func(tx *Tx) interface{} { +// Atomically(func(tx *Tx) any { // debug.PrintStack() // ret := func() { // defer Atomically(nil) diff --git a/stmutil/containers.go b/stmutil/containers.go index e0b532d..c7a4a49 100644 --- a/stmutil/containers.go +++ b/stmutil/containers.go @@ -9,10 +9,10 @@ import ( ) type Settish interface { - Add(interface{}) Settish - Delete(interface{}) Settish - Contains(interface{}) bool - Range(func(interface{}) bool) + Add(any) Settish + Delete(any) Settish + Contains(any) bool + Range(func(any) bool) iter.Iterable Len() int } @@ -23,11 +23,11 @@ type mapToSet struct { type interhash struct{} -func (interhash) Hash(x interface{}) uint32 { +func (interhash) Hash(x any) uint32 { return uint32(nilinterhash(unsafe.Pointer(&x), 0)) } -func (interhash) Equal(i, j interface{}) bool { +func (interhash) Equal(i, j any) bool { return i == j } @@ -39,12 +39,12 @@ func NewSortedSet(lesser lessFunc) Settish { return mapToSet{NewSortedMap(lesser)} } -func (s mapToSet) Add(x interface{}) Settish { +func (s mapToSet) Add(x any) Settish { s.m = s.m.Set(x, nil) return s } -func (s mapToSet) Delete(x interface{}) Settish { +func (s mapToSet) Delete(x any) Settish { s.m = s.m.Delete(x) return s } @@ -53,13 +53,13 @@ func (s mapToSet) Len() int { return s.m.Len() } -func (s mapToSet) Contains(x interface{}) bool { +func (s mapToSet) Contains(x any) bool { _, ok := s.m.Get(x) return ok } -func (s mapToSet) Range(f func(interface{}) bool) { - s.m.Range(func(k, _ interface{}) bool { +func (s mapToSet) Range(f func(any) bool) { + s.m.Range(func(k, _ any) bool { return f(k) }) } @@ -78,17 +78,17 @@ func NewMap() Mappish { var _ Mappish = Map{} -func (m Map) Delete(x interface{}) Mappish { +func (m Map) Delete(x any) Mappish { m.Map = m.Map.Delete(x) return m } -func (m Map) Set(key, value interface{}) Mappish { +func (m Map) Set(key, value any) Mappish { m.Map = m.Map.Set(key, value) return m } -func (sm Map) Range(f func(key, value interface{}) bool) { +func (sm Map) Range(f func(key, value any) bool) { iter := sm.Map.Iterator() for !iter.Done() { if !f(iter.Next()) { @@ -98,7 +98,7 @@ func (sm Map) Range(f func(key, value interface{}) bool) { } func (sm Map) Iter(cb iter.Callback) { - sm.Range(func(key, _ interface{}) bool { + sm.Range(func(key, _ any) bool { return cb(key) }) } @@ -107,17 +107,17 @@ type SortedMap struct { *immutable.SortedMap } -func (sm SortedMap) Set(key, value interface{}) Mappish { +func (sm SortedMap) Set(key, value any) Mappish { sm.SortedMap = sm.SortedMap.Set(key, value) return sm } -func (sm SortedMap) Delete(key interface{}) Mappish { +func (sm SortedMap) Delete(key any) Mappish { sm.SortedMap = sm.SortedMap.Delete(key) return sm } -func (sm SortedMap) Range(f func(key, value interface{}) bool) { +func (sm SortedMap) Range(f func(key, value any) bool) { iter := sm.SortedMap.Iterator() for !iter.Done() { if !f(iter.Next()) { @@ -127,18 +127,18 @@ func (sm SortedMap) Range(f func(key, value interface{}) bool) { } func (sm SortedMap) Iter(cb iter.Callback) { - sm.Range(func(key, _ interface{}) bool { + sm.Range(func(key, _ any) bool { return cb(key) }) } -type lessFunc func(l, r interface{}) bool +type lessFunc func(l, r any) bool type comparer struct { less lessFunc } -func (me comparer) Compare(i, j interface{}) int { +func (me comparer) Compare(i, j any) int { if me.less(i, j) { return -1 } else if me.less(j, i) { @@ -155,15 +155,15 @@ func NewSortedMap(less lessFunc) Mappish { } type Mappish interface { - Set(key, value interface{}) Mappish - Delete(key interface{}) Mappish - Get(key interface{}) (interface{}, bool) - Range(func(_, _ interface{}) bool) + Set(key, value any) Mappish + Delete(key any) Mappish + Get(key any) (any, bool) + Range(func(_, _ any) bool) Len() int iter.Iterable } -func GetLeft(l, _ interface{}) interface{} { +func GetLeft(l, _ any) any { return l } @@ -171,7 +171,7 @@ func GetLeft(l, _ interface{}) interface{} { //go:linkname nilinterhash runtime.nilinterhash func nilinterhash(p unsafe.Pointer, h uintptr) uintptr -func interfaceHash(x interface{}) uint32 { +func interfaceHash(x any) uint32 { return uint32(nilinterhash(unsafe.Pointer(&x), 0)) } diff --git a/tx.go b/tx.go index 4542e61..825ff01 100644 --- a/tx.go +++ b/tx.go @@ -11,7 +11,7 @@ import ( type txVar interface { getValue() *atomic.Value[VarValue] - changeValue(interface{}) + changeValue(any) getWatchers() *sync.Map getLock() *sync.Mutex } @@ -19,7 +19,7 @@ type txVar interface { // A Tx represents an atomic transaction. type Tx struct { reads map[txVar]VarValue - writes map[txVar]interface{} + writes map[txVar]any watching map[txVar]struct{} locks txLocks mu sync.Mutex diff --git a/var-value.go b/var-value.go index f4921f3..3518bf6 100644 --- a/var-value.go +++ b/var-value.go @@ -1,8 +1,8 @@ package stm type VarValue interface { - Set(interface{}) VarValue - Get() interface{} + Set(any) VarValue + Get() any Changed(VarValue) bool } @@ -13,14 +13,14 @@ type versionedValue[T any] struct { version version } -func (me versionedValue[T]) Set(newValue interface{}) VarValue { +func (me versionedValue[T]) Set(newValue any) VarValue { return versionedValue[T]{ value: newValue.(T), version: me.version + 1, } } -func (me versionedValue[T]) Get() interface{} { +func (me versionedValue[T]) Get() any { return me.value } @@ -39,13 +39,13 @@ func (me customVarValue[T]) Changed(other VarValue) bool { return me.changed(me.value, other.(customVarValue[T]).value) } -func (me customVarValue[T]) Set(newValue interface{}) VarValue { +func (me customVarValue[T]) Set(newValue any) VarValue { return customVarValue[T]{ value: newValue.(T), changed: me.changed, } } -func (me customVarValue[T]) Get() interface{} { +func (me customVarValue[T]) Get() any { return me.value } diff --git a/var.go b/var.go index 2354d96..ec6a81e 100644 --- a/var.go +++ b/var.go @@ -25,7 +25,7 @@ func (v *Var[T]) getLock() *sync.Mutex { return &v.mu } -func (v *Var[T]) changeValue(new interface{}) { +func (v *Var[T]) changeValue(new any) { old := v.value.Load() newVarValue := old.Set(new) v.value.Store(newVarValue) @@ -35,7 +35,7 @@ func (v *Var[T]) changeValue(new interface{}) { } func (v *Var[T]) wakeWatchers(new VarValue) { - v.watchers.Range(func(k, _ interface{}) bool { + v.watchers.Range(func(k, _ any) 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. -- cgit v1.2.3