From: qydysky Date: Wed, 26 Jul 2023 16:04:27 +0000 (+0800) Subject: add X-Git-Tag: v0.28.0+20230726445f3ba X-Git-Url: http://127.0.0.1:8081/?a=commitdiff_plain;h=445f3ba32ca26b2ac95aab69f9053fcfbc4c5cab;p=part%2F.git add --- diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5ec6bd..b221557 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/sync/RWMutex.go b/sync/RWMutex.go index 4943c5d..f3f8b45 100644 --- a/sync/RWMutex.go +++ b/sync/RWMutex.go @@ -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 } diff --git a/sync/RWMutex_test.go b/sync/RWMutex_test.go index 67aaaa8..7f45ccd 100644 --- a/sync/RWMutex_test.go +++ b/sync/RWMutex_test.go @@ -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) }