| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- package core
- import (
- "sync"
- "sync/atomic"
- "testing"
- "time"
- )
- // TestAccessLock_ReadReadParallel 验证多个读者真正并行 (不互相阻塞)。
- // 原理: 10 个 goroutine 同时 RLock, 每个 sleep 200ms, 总耗时应 ~200ms 而非 ~2000ms。
- func TestAccessLock_ReadReadParallel(t *testing.T) {
- var l accessLock
- const readers = 10
- const hold = 200 * time.Millisecond
- start := time.Now()
- var wg sync.WaitGroup
- wg.Add(readers)
- for i := 0; i < readers; i++ {
- go func() {
- defer wg.Done()
- l.RLock()
- time.Sleep(hold)
- l.RUnlock()
- }()
- }
- wg.Wait()
- elapsed := time.Since(start)
- if elapsed >= hold*2 { // 给 2x 余量, 超过说明有串行化
- t.Errorf("读读应该并行, 但耗时 %v (期望 < %v)", elapsed, hold*2)
- }
- t.Logf("10 个读者并行耗时: %v", elapsed)
- }
- // TestAccessLock_WriteBlocksRead 验证写锁阻塞读。
- func TestAccessLock_WriteBlocksRead(t *testing.T) {
- var l accessLock
- const hold = 200 * time.Millisecond
- const waitTolerance = hold / 2
- // goroutine A: 拿写锁, sleep 200ms
- doneA := make(chan struct{})
- go func() {
- l.Lock()
- time.Sleep(hold)
- l.Unlock()
- close(doneA)
- }()
- // 等 goroutine A 真正拿到锁
- time.Sleep(20 * time.Millisecond)
- // goroutine B: 尝试 RLock, 应该被阻塞, 直到 A 释放
- startB := time.Now()
- l.RLock()
- elapsedB := time.Since(startB)
- l.RUnlock()
- if elapsedB < hold-waitTolerance {
- t.Errorf("写锁应该阻塞读, 但 B 只等了 %v (期望 >= %v)", elapsedB, hold-waitTolerance)
- }
- t.Logf("读等写释放耗时: %v", elapsedB)
- <-doneA
- }
- // TestAccessLock_ReentrantWrite 验证同 goroutine 写锁重入不卡死。
- func TestAccessLock_ReentrantWrite(t *testing.T) {
- var l accessLock
- const depth = 10
- // 同 goroutine 连续 Lock 10 次, 再 Unlock 10 次
- for i := 0; i < depth; i++ {
- l.Lock()
- }
- for i := 0; i < depth; i++ {
- l.Unlock()
- }
- t.Logf("写锁 %d 层重入正常", depth)
- }
- // TestAccessLock_ReentrantRLockUnderWrite 验证同 goroutine 持有写锁时再 RLock 不卡死。
- // 这是 TsDB2 特有的关键路径: BeginTransaction (Lock) → InsertRow → scanWithPKID (RLock)
- func TestAccessLock_ReentrantRLockUnderWrite(t *testing.T) {
- var l accessLock
- l.Lock()
- l.RLock() // 同 goroutine, 已持写锁, 应该跳过
- l.RUnlock()
- l.Unlock()
- t.Logf("同 goroutine 写锁下 RLock 正常")
- }
- // TestAccessLock_WriteWaitsForReaders 验证写锁等待现有读者离开。
- func TestAccessLock_WriteWaitsForReaders(t *testing.T) {
- var l accessLock
- const hold = 200 * time.Millisecond
- const tolerance = hold / 2
- // goroutine A: RLock, sleep 200ms
- doneA := make(chan struct{})
- go func() {
- l.RLock()
- time.Sleep(hold)
- l.RUnlock()
- close(doneA)
- }()
- // 等 A 拿到 RLock
- time.Sleep(20 * time.Millisecond)
- // goroutine B: 尝试 Lock, 应该等 A 的 RUnlock
- startB := time.Now()
- l.Lock()
- elapsedB := time.Since(startB)
- l.Unlock()
- if elapsedB < hold-tolerance {
- t.Errorf("写锁应该等待读者, 但 B 只等了 %v (期望 >= %v)", elapsedB, hold-tolerance)
- }
- t.Logf("写锁等待读者耗时: %v", elapsedB)
- <-doneA
- }
- // TestAccessLock_DeadlockStress 长时间压力测试, 确保没有死锁。
- // 10 个读者 goroutine + 2 个写者 goroutine, 混合跑 3 秒。
- func TestAccessLock_DeadlockStress(t *testing.T) {
- var l accessLock
- var reads, writes int64
- stop := make(chan struct{})
- var wg sync.WaitGroup
- // 读者: RLock → 少量工作 → RUnlock
- for i := 0; i < 10; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for {
- select {
- case <-stop:
- return
- default:
- l.RLock()
- atomic.AddInt64(&reads, 1)
- l.RUnlock()
- }
- }
- }()
- }
- // 写者: Lock → 少量工作 → Unlock
- for i := 0; i < 2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for {
- select {
- case <-stop:
- return
- default:
- l.Lock()
- atomic.AddInt64(&writes, 1)
- l.Unlock()
- }
- }
- }()
- }
- // 跑 3 秒后停止
- time.Sleep(3 * time.Second)
- close(stop)
- done := make(chan struct{})
- go func() { wg.Wait(); close(done) }()
- select {
- case <-done:
- r := atomic.LoadInt64(&reads)
- w := atomic.LoadInt64(&writes)
- t.Logf("3 秒压力测试通过, reads=%d writes=%d", r, w)
- case <-time.After(2 * time.Second):
- t.Fatal("压力测试超时 —— 疑似死锁!")
- }
- }
|