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

// Futex is only available on DragonFly BSD, FreeBSD and Linux.
// The race detector emits calls to split stack functions so it breaks
// the test.

//go:build (dragonfly || freebsd || linux) && !race
// +build dragonfly freebsd linux
// +build !race

package runtime_test

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

type futexsleepTest struct {
	mtx uint32
	ns  int64
	msg string
	ch  chan *futexsleepTest
}

var futexsleepTests = []futexsleepTest{
	beforeY2038: {mtx: 0, ns: 86400 * 1e9, msg: "before the year 2038"},
	afterY2038:  {mtx: 0, ns: (1<<31 + 100) * 1e9, msg: "after the year 2038"},
}

const (
	beforeY2038 = iota
	afterY2038
)

func TestFutexsleep(t *testing.T) {
	if runtime.GOMAXPROCS(0) > 1 {
		// futexsleep doesn't handle EINTR or other signals,
		// so spurious wakeups may happen.
		t.Skip("skipping; GOMAXPROCS>1")
	}

	start := time.Now()
	var wg sync.WaitGroup
	for i := range futexsleepTests {
		tt := &futexsleepTests[i]
		tt.mtx = 0
		tt.ch = make(chan *futexsleepTest, 1)
		wg.Add(1)
		go func(tt *futexsleepTest) {
			runtime.Entersyscall()
			runtime.Futexsleep(&tt.mtx, 0, tt.ns)
			runtime.Exitsyscall()
			tt.ch <- tt
			wg.Done()
		}(tt)
	}
loop:
	for {
		select {
		case tt := <-futexsleepTests[beforeY2038].ch:
			t.Errorf("futexsleep test %q finished early after %s", tt.msg, time.Since(start))
			break loop
		case tt := <-futexsleepTests[afterY2038].ch:
			// Looks like FreeBSD 10 kernel has changed
			// the semantics of timedwait on userspace
			// mutex to make broken stuff look broken.
			switch {
			case runtime.GOOS == "freebsd" && runtime.GOARCH == "386":
				t.Log("freebsd/386 may not work correctly after the year 2038, see golang.org/issue/7194")
			default:
				t.Errorf("futexsleep test %q finished early after %s", tt.msg, time.Since(start))
				break loop
			}
		case <-time.After(time.Second):
			break loop
		}
	}
	for i := range futexsleepTests {
		tt := &futexsleepTests[i]
		atomic.StoreUint32(&tt.mtx, 1)
		runtime.Futexwakeup(&tt.mtx, 1)
	}
	wg.Wait()
}
