package mapmutex_test import ( "math/rand" "strconv" "strings" "sync" "testing" "time" "github.com/ethereum/go-ethereum/common" . "github.com/stretchr/testify/assert" "github.com/synapsecns/sanguine/core/mapmutex" ) // ExampleMapMutex provides an example implementation of a map mutex. func ExampleStringMapMutex() { mapMutex := mapmutex.NewStringMapMutex() lock1 := mapMutex.Lock("lock1") lock2 := mapMutex.Lock("lock2") lock1.Unlock() lock2.Unlock() } func ExampleStringerMapMutex() { vitalik := common.HexToAddress("0xab5801a7d398351b8be11c439e05c5b3259aec9b") tether := common.HexToAddress("0xdac17f958d2ee523a2206206994597c13d831ec7") mapMutex := mapmutex.NewStringerMapMutex() // second variable will be true vitalikLock, _ := mapMutex.TryLock(vitalik) tetherLock := mapMutex.Lock(tether) vitalikLock.Unlock() tetherLock.Unlock() } func ExampleIntMapMutex() { mapMutex := mapmutex.NewIntMapMutex() // second variable will be true lock0, _ := mapMutex.TryLock(0) lock1 := mapMutex.Lock(1) lock0.Unlock() lock1.Unlock() } func (s MapMutexSuite) TestExampleMapMutex() { NotPanics(s.T(), ExampleStringMapMutex) NotPanics(s.T(), ExampleStringerMapMutex) NotPanics(s.T(), ExampleStringMapMutex) } func (s MapMutexSuite) TestKeys() { s.T().Run("StringMapMutexKeys", func(t *testing.T) { mapMutex := mapmutex.NewStringMapMutex() mapMutex.Lock("lock1") Equal(t, "lock1", mapMutex.Keys()[0]) Equal(t, 1, len(mapMutex.Keys())) }) s.T().Run("StringerMapMutexKeys", func(t *testing.T) { mapMutex := mapmutex.NewStringerMapMutex() vitalik := common.HexToAddress("0xab5801a7d398351b8be11c439e05c5b3259aec9b") mapMutex.Lock(vitalik) Equal(t, vitalik.String(), mapMutex.Keys()[0]) Equal(t, 1, len(mapMutex.Keys())) }) s.T().Run("IntMapMutexKeys", func(t *testing.T) { mapMutex := mapmutex.NewIntMapMutex() mapMutex.Lock(1) Equal(t, 1, mapMutex.Keys()[0]) Equal(t, 1, len(mapMutex.Keys())) }) } func (s MapMutexSuite) TestMapMutex() { //nolint:gosec r := rand.New(rand.NewSource(42)) m := mapmutex.NewTestMapMutex(s.T()) _ = m keyCount := 20 iCount := 10000 out := make(chan string, iCount*2) // run a bunch of concurrent requests for various keys, // the idea is to have a lot of lock contention var wg sync.WaitGroup wg.Add(iCount) for i := 0; i < iCount; i++ { go func(rn int) { defer wg.Done() key := strconv.Itoa(rn) // you can prove the test works by commenting the locking out and seeing it fail l := m.Lock(key) _, obtained := m.TryLock(key) False(s.T(), obtained) defer l.Unlock() out <- key + " A" time.Sleep(time.Microsecond) // make 'em wait a mo' out <- key + " B" }(r.Intn(keyCount)) } wg.Wait() close(out) // verify the map is empty now if l := len(m.GetMa()); l != 0 { s.T().Errorf("unexpected map length at test end: %v", l) } // confirm that the output always produced the correct sequence outLists := make([][]string, keyCount) for so := range out { sParts := strings.Fields(so) kn, err := strconv.Atoi(sParts[0]) if err != nil { s.T().Fatal(err) } outLists[kn] = append(outLists[kn], sParts[1]) } for kn := 0; kn < keyCount; kn++ { l := outLists[kn] // list of output for this particular key for i := 0; i < len(l); i += 2 { if l[i] != "A" || l[i+1] != "B" { s.T().Errorf("For key=%v and i=%v got unexpected values %v and %v", kn, i, l[i], l[i+1]) break } } } if s.T().Failed() { s.T().Logf("Failed, outLists: %#v", outLists) } } func BenchmarkMapMutex(b *testing.B) { m := mapmutex.NewTestMapMutex(b) b.ResetTimer() for i := 0; i < b.N; i++ { // run uncontended lock/unlock - should be quite fast m.Lock(i).Unlock() } }