]> 127.0.0.1 Git - part/.git/commitdiff
add v0.28.0+20230726445f3ba
authorqydysky <qydysky@foxmail.com>
Wed, 26 Jul 2023 16:04:27 +0000 (00:04 +0800)
committerqydysky <qydysky@foxmail.com>
Wed, 26 Jul 2023 16:04:27 +0000 (00:04 +0800)
.github/workflows/test.yml
sync/RWMutex.go
sync/RWMutex_test.go

index c5ec6bd14d84594f74f14bd2eacb81198a8b399e..b22155726ac0db8b894286ff4b9713253af0ef9f 100644 (file)
@@ -33,7 +33,7 @@ jobs:
         go test -count 1 -timeout 10s -v -race github.com/qydysky/part/funcCtrl
         go test -count 1 -timeout 30s -v -race github.com/qydysky/part/msgq
         go test -count 10 -race -timeout 10s -run ^Test_3$ github.com/qydysky/part/msgq
-        go test -count 1 -timeout 5s -v -race github.com/qydysky/part/sync
+        go test -count 1 -timeout 7s -v -race github.com/qydysky/part/sync
         go test -count 1 -timeout 10s -v -race github.com/qydysky/part/web
         go test -count 1 -timeout 10s -v -run "Test_Client" -race github.com/qydysky/part/websocket
         go test -count 1 -timeout 10s -v -race github.com/qydysky/part/sql
index 4943c5dfe24c8b04d326b32d86ff79bff3bf4073..f3f8b458bb1052cf515ca0fd61033d412af7abea 100644 (file)
@@ -3,117 +3,154 @@ package part
 import (
        "fmt"
        "runtime"
+       "strings"
        "sync/atomic"
        "time"
 )
 
 const (
-       lock  = -1
-       ulock = 0
-       rlock = 0
+       lock  int32 = -1
+       ulock int32 = 0
+       rlock int32 = 1
 )
 
 type RWMutex struct {
        rlc  atomic.Int32
-       want atomic.Int32
+       read atomic.Int32
 }
 
-// to[0]: wait lock timeout to[1]: run lock timeout
+func parse(i int32) string {
+       switch i {
+       case -1:
+               return "lock"
+       case 0:
+               return "ulock"
+       case 1:
+               return "rlock"
+       }
+       return "unknow"
+}
+
+// i == oldt -> i = t -> pass
 //
-// 不要在Rlock内设置变量,有DATA RACE风险
-func (m *RWMutex) RLock(to ...time.Duration) (unlockf func()) {
-       getWant := m.want.CompareAndSwap(ulock, rlock)
-       var callC atomic.Bool
-       if len(to) > 0 {
-               var calls []string
-               if len(to) > 1 {
-                       for i := 1; true; i++ {
-                               if pc, file, line, ok := runtime.Caller(i); !ok {
-                                       break
-                               } else {
-                                       calls = append(calls, fmt.Sprintf("%s\n\t%s:%d", runtime.FuncForPC(pc).Name(), file, line))
+// otherwish block until i == oldt
+func cas(i *atomic.Int32, oldt, t int32) (ok bool, loop func(to ...time.Duration) error) {
+       if i.CompareAndSwap(oldt, t) {
+               return true, func(to ...time.Duration) error { return nil }
+       } else {
+               var called atomic.Bool
+               return false, func(to ...time.Duration) error {
+                       if !called.CompareAndSwap(false, true) {
+                               panic("had called")
+                       }
+                       c := time.Now()
+                       for !i.CompareAndSwap(oldt, t) {
+                               if len(to) != 0 && time.Since(c) > to[0] {
+                                       return fmt.Errorf("timeout to set %s => %s while is %s", parse(oldt), parse(t), parse(i.Load()))
                                }
+                               runtime.Gosched()
                        }
+                       return nil
                }
-               c := time.Now()
-               for m.rlc.Load() < ulock || !getWant && !m.want.CompareAndSwap(ulock, rlock) {
-                       if time.Since(c) > to[0] {
-                               panic(fmt.Sprintf("timeout to wait lock while rlocking, rlc:%d, want:%d, getWant:%v", m.rlc.Load(), m.want.Load(), getWant))
+       }
+}
+
+// i == t -> pass
+//
+// i == oldt -> i = t -> pass
+//
+// otherwish block until i == oldt
+func lcas(i *atomic.Int32, oldt, t int32) (ok bool, loop func(to ...time.Duration) error) {
+       if i.Load() == t || i.CompareAndSwap(oldt, t) {
+               return true, func(to ...time.Duration) error { return nil }
+       } else {
+               var called atomic.Bool
+               return false, func(to ...time.Duration) error {
+                       if !called.CompareAndSwap(false, true) {
+                               panic("had called")
                        }
-                       runtime.Gosched()
-               }
-               if len(to) > 1 {
-                       time.AfterFunc(to[1], func() {
-                               if !callC.Load() {
-                                       panicS := fmt.Sprintf("timeout to run rlock %v > %v\n", time.Since(c), to[1])
-                                       for i := 0; i < len(calls); i++ {
-                                               panicS += fmt.Sprintf("call by %s\n", calls[i])
-                                       }
-                                       panic(panicS)
+                       c := time.Now()
+                       for !i.CompareAndSwap(oldt, t) {
+                               if len(to) != 0 && time.Since(c) > to[0] {
+                                       return fmt.Errorf("timeout to set %s => %s while is %s", parse(oldt), parse(t), parse(i.Load()))
                                }
-                       })
-               }
-       } else {
-               for m.rlc.Load() < ulock || !getWant && m.want.CompareAndSwap(ulock, rlock) {
-                       runtime.Gosched()
+                               runtime.Gosched()
+                       }
+                       return nil
                }
        }
-       m.rlc.Add(1)
+}
+
+// call inTimeCall() in time or panic(callTree)
+func tof(to time.Duration) (inTimeCall func() (called bool)) {
+       callTree := getCall(2)
+       return time.AfterFunc(to, func() {
+               panic("Locking timeout!\n" + callTree)
+       }).Stop
+}
+
+// to[0]: wait lock timeout to[1]: run lock timeout
+//
+// 不要在Rlock内设置变量,有DATA RACE风险
+func (m *RWMutex) RLock(to ...time.Duration) (unlockf func()) {
+       _, rlcLoop := lcas(&m.rlc, ulock, rlock)
+       if e := rlcLoop(to...); e != nil {
+               panic(e)
+       }
+       m.read.Add(1)
+       var callC atomic.Bool
+       var done func() (called bool)
+       if len(to) > 1 {
+               done = tof(to[1])
+       }
        return func() {
                if !callC.CompareAndSwap(false, true) {
-                       panic("had unrlock")
+                       panic("had unlock")
+               }
+               if done != nil {
+                       done()
                }
-               if m.rlc.Add(-1) == ulock {
-                       m.want.CompareAndSwap(rlock, ulock)
+               if m.read.Add(-1) == 0 {
+                       _, rlcLoop := cas(&m.rlc, rlock, ulock)
+                       if e := rlcLoop(to...); e != nil {
+                               panic(e)
+                       }
                }
        }
 }
 
 // to[0]: wait lock timeout to[1]: run lock timeout
 func (m *RWMutex) Lock(to ...time.Duration) (unlockf func()) {
-       getWant := m.want.CompareAndSwap(ulock, lock)
+       _, rlcLoop := cas(&m.rlc, ulock, lock)
+       if e := rlcLoop(to...); e != nil {
+               panic(e)
+       }
        var callC atomic.Bool
-       if len(to) > 0 {
-               var calls []string
-               if len(to) > 1 {
-                       for i := 1; true; i++ {
-                               if pc, file, line, ok := runtime.Caller(i); !ok {
-                                       break
-                               } else {
-                                       calls = append(calls, fmt.Sprintf("%s\n\t%s:%d", runtime.FuncForPC(pc).Name(), file, line))
-                               }
-                       }
-               }
-               c := time.Now()
-               for m.rlc.Load() != ulock || !getWant && !m.want.CompareAndSwap(ulock, lock) {
-                       if time.Since(c) > to[0] {
-                               panic(fmt.Sprintf("timeout to wait rlock while locking, rlc:%d, want:%v, getWant:%v", m.rlc.Load(), m.want.Load(), getWant))
-                       }
-                       runtime.Gosched()
-               }
-               if len(to) > 1 {
-                       time.AfterFunc(to[1], func() {
-                               if !callC.Load() {
-                                       panicS := fmt.Sprintf("timeout to run lock %v > %v\n", time.Since(c), to[1])
-                                       for i := 0; i < len(calls); i++ {
-                                               panicS += fmt.Sprintf("call by %s\n", calls[i])
-                                       }
-                                       panic(panicS)
-                               }
-                       })
-               }
-       } else {
-               for m.rlc.Load() != ulock || !getWant && m.want.CompareAndSwap(ulock, lock) {
-                       runtime.Gosched()
-               }
+       var done func() (called bool)
+       if len(to) > 1 {
+               done = tof(to[1])
        }
-       m.rlc.Add(-1)
        return func() {
                if !callC.CompareAndSwap(false, true) {
                        panic("had unlock")
                }
-               if m.rlc.Add(1) == ulock {
-                       m.want.CompareAndSwap(lock, ulock)
+               if done != nil {
+                       done()
+               }
+               _, rlcLoop := cas(&m.rlc, lock, ulock)
+               if e := rlcLoop(to...); e != nil {
+                       panic(e)
+               }
+       }
+}
+
+func getCall(i int) (calls string) {
+       for i += 1; true; i++ {
+               if pc, file, line, ok := runtime.Caller(i); !ok || strings.HasPrefix(file, runtime.GOROOT()) {
+                       break
+               } else {
+                       calls += fmt.Sprintf("call by %s\n\t%s:%d\n", runtime.FuncForPC(pc).Name(), file, line)
                }
        }
+       return
 }
index 67aaaa8991bffd87ef72f40681c7757e7d409a4c..7f45ccd5664ab5a8565b53e64807444660d24835 100644 (file)
@@ -5,12 +5,128 @@ import (
        "time"
 )
 
-func TestMain(t *testing.T) {
+func check(l *RWMutex, r, read int32) {
+       if l.rlc.Load() != r {
+               panic("rlc")
+       }
+       if l.read.Load() != read {
+               panic("read")
+       }
+}
+
+// ulock rlock rlock
+func Test1(t *testing.T) {
+       var l RWMutex
+       check(&l, ulock, 0)
+       ul := l.RLock()
+       check(&l, rlock, 1)
+       ul1 := l.RLock()
+       check(&l, rlock, 2)
+       ul()
+       check(&l, rlock, 1)
+       ul1()
+       check(&l, ulock, 0)
+}
+
+// ulock rlock lock
+func Test2(t *testing.T) {
+       var l RWMutex
+       ul := l.RLock()
+       check(&l, rlock, 1)
+       time.AfterFunc(time.Second, func() {
+               check(&l, rlock, 1)
+               ul()
+       })
+       c := time.Now()
+       ul1 := l.Lock()
+       check(&l, lock, 0)
+       if time.Since(c) < time.Second {
+               t.Fail()
+       }
+       ul1()
+       check(&l, ulock, 0)
+}
+
+// ulock lock rlock
+func Test3(t *testing.T) {
+       var l RWMutex
+       ul := l.Lock()
+       check(&l, lock, 0)
+       time.AfterFunc(time.Second, func() {
+               check(&l, lock, 0)
+               ul()
+       })
+       c := time.Now()
+       ul1 := l.RLock()
+       check(&l, rlock, 1)
+       if time.Since(c) < time.Second {
+               t.Fail()
+       }
+       ul1()
+       check(&l, ulock, 0)
+}
+
+// ulock rlock rlock
+func Panic_Test4(t *testing.T) {
+       var l RWMutex
+       check(&l, ulock, 0)
+       ul := l.RLock(time.Second, time.Second)
+       check(&l, rlock, 1)
+       ul1 := l.RLock(time.Second, time.Second)
+       check(&l, rlock, 2)
+       time.Sleep(time.Millisecond * 1500)
+       ul()
+       check(&l, rlock, 1)
+       ul1()
+       check(&l, ulock, 0)
+       time.Sleep(time.Second * 3)
+}
+
+// ulock rlock lock
+func Panic_Test5(t *testing.T) {
+       var l RWMutex
+       ul := l.RLock()
+       check(&l, rlock, 1)
+       time.AfterFunc(time.Millisecond*1500, func() {
+               check(&l, rlock, 1)
+               ul()
+       })
+       c := time.Now()
+       ul1 := l.Lock(time.Second)
+       check(&l, lock, 0)
+       if time.Since(c) < time.Second {
+               t.Fail()
+       }
+       ul1()
+       check(&l, ulock, 0)
+}
+
+// ulock lock rlock
+func Test6(t *testing.T) {
+       var l RWMutex
+       ul := l.Lock()
+       check(&l, lock, 0)
+       time.AfterFunc(time.Second, func() {
+               check(&l, lock, 0)
+               ul()
+       })
+       c := time.Now()
+       ul1 := l.RLock()
+       check(&l, rlock, 1)
+       if time.Since(c) < time.Second {
+               t.Fail()
+       }
+       ul1()
+       check(&l, ulock, 0)
 }
 
 func BenchmarkRlock(b *testing.B) {
        var lock1 RWMutex
+       var a bool
        for i := 0; i < b.N; i++ {
-               lock1.RLock(time.Second, time.Second)()
+               ul := lock1.RLock()
+               a = true
+               ul()
        }
+       println(a)
 }