// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2019 The gVisor Authors.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// GOMAXPROCS=10 go test

// Copy/pasted from the standard library's sync/rwmutex_test.go, except for the
// addition of downgradingWriter and the renaming of num_iterations to
// numIterations to shut up Golint.

package sync

import (
	"fmt"
	"runtime"
	"sync/atomic"
	"testing"
)

func parallelReader(m *RWMutex, clocked, cunlock, cdone chan bool) {
	m.RLock()
	clocked <- true
	<-cunlock
	m.RUnlock()
	cdone <- true
}

func doTestParallelReaders(numReaders, gomaxprocs int) {
	runtime.GOMAXPROCS(gomaxprocs)
	var m RWMutex
	clocked := make(chan bool)
	cunlock := make(chan bool)
	cdone := make(chan bool)
	for i := 0; i < numReaders; i++ {
		go parallelReader(&m, clocked, cunlock, cdone)
	}
	// Wait for all parallel RLock()s to succeed.
	for i := 0; i < numReaders; i++ {
		<-clocked
	}
	for i := 0; i < numReaders; i++ {
		cunlock <- true
	}
	// Wait for the goroutines to finish.
	for i := 0; i < numReaders; i++ {
		<-cdone
	}
}

func TestParallelReaders(t *testing.T) {
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1))
	doTestParallelReaders(1, 4)
	doTestParallelReaders(3, 4)
	doTestParallelReaders(4, 2)
}

func reader(rwm *RWMutex, numIterations int, activity *int32, cdone chan bool) {
	for i := 0; i < numIterations; i++ {
		rwm.RLock()
		n := atomic.AddInt32(activity, 1)
		if n < 1 || n >= 10000 {
			panic(fmt.Sprintf("wlock(%d)\n", n))
		}
		for i := 0; i < 100; i++ {
		}
		atomic.AddInt32(activity, -1)
		rwm.RUnlock()
	}
	cdone <- true
}

func writer(rwm *RWMutex, numIterations int, activity *int32, cdone chan bool) {
	for i := 0; i < numIterations; i++ {
		rwm.Lock()
		n := atomic.AddInt32(activity, 10000)
		if n != 10000 {
			panic(fmt.Sprintf("wlock(%d)\n", n))
		}
		for i := 0; i < 100; i++ {
		}
		atomic.AddInt32(activity, -10000)
		rwm.Unlock()
	}
	cdone <- true
}

func downgradingWriter(rwm *RWMutex, numIterations int, activity *int32, cdone chan bool) {
	for i := 0; i < numIterations; i++ {
		rwm.Lock()
		n := atomic.AddInt32(activity, 10000)
		if n != 10000 {
			panic(fmt.Sprintf("wlock(%d)\n", n))
		}
		for i := 0; i < 100; i++ {
		}
		atomic.AddInt32(activity, -10000)
		rwm.DowngradeLock()
		n = atomic.AddInt32(activity, 1)
		if n < 1 || n >= 10000 {
			panic(fmt.Sprintf("wlock(%d)\n", n))
		}
		for i := 0; i < 100; i++ {
		}
		n = atomic.AddInt32(activity, -1)
		rwm.RUnlock()
	}
	cdone <- true
}

func HammerDowngradableRWMutex(gomaxprocs, numReaders, numIterations int) {
	runtime.GOMAXPROCS(gomaxprocs)
	// Number of active readers + 10000 * number of active writers.
	var activity int32
	var rwm RWMutex
	cdone := make(chan bool)
	go writer(&rwm, numIterations, &activity, cdone)
	go downgradingWriter(&rwm, numIterations, &activity, cdone)
	var i int
	for i = 0; i < numReaders/2; i++ {
		go reader(&rwm, numIterations, &activity, cdone)
	}
	go writer(&rwm, numIterations, &activity, cdone)
	go downgradingWriter(&rwm, numIterations, &activity, cdone)
	for ; i < numReaders; i++ {
		go reader(&rwm, numIterations, &activity, cdone)
	}
	// Wait for the 4 writers and all readers to finish.
	for i := 0; i < 4+numReaders; i++ {
		<-cdone
	}
}

func TestDowngradableRWMutex(t *testing.T) {
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1))
	n := 1000
	if testing.Short() {
		n = 5
	}
	HammerDowngradableRWMutex(1, 1, n)
	HammerDowngradableRWMutex(1, 3, n)
	HammerDowngradableRWMutex(1, 10, n)
	HammerDowngradableRWMutex(4, 1, n)
	HammerDowngradableRWMutex(4, 3, n)
	HammerDowngradableRWMutex(4, 10, n)
	HammerDowngradableRWMutex(10, 1, n)
	HammerDowngradableRWMutex(10, 3, n)
	HammerDowngradableRWMutex(10, 10, n)
	HammerDowngradableRWMutex(10, 5, n)
}

func TestRWDoubleTryLock(t *testing.T) {
	var rwm RWMutex
	if !rwm.TryLock() {
		t.Fatal("failed to aquire lock")
	}
	if rwm.TryLock() {
		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
	}
}

func TestRWTryLockAfterLock(t *testing.T) {
	var rwm RWMutex
	rwm.Lock()
	if rwm.TryLock() {
		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
	}
}

func TestRWTryLockUnlock(t *testing.T) {
	var rwm RWMutex
	if !rwm.TryLock() {
		t.Fatal("failed to aquire lock")
	}
	rwm.Unlock()
	if !rwm.TryLock() {
		t.Fatal("failed to aquire lock after unlock")
	}
}

func TestTryRLockAfterLock(t *testing.T) {
	var rwm RWMutex
	rwm.Lock()
	if rwm.TryRLock() {
		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
	}
}

func TestTryLockAfterRLock(t *testing.T) {
	var rwm RWMutex
	rwm.RLock()
	if rwm.TryLock() {
		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
	}
}

func TestDoubleTryRLock(t *testing.T) {
	var rwm RWMutex
	if !rwm.TryRLock() {
		t.Fatal("failed to aquire lock")
	}
	if !rwm.TryRLock() {
		t.Fatal("failed to read aquire read locked lock")
	}
}