// +build darwin freebsd dragonfly netbsd openbsd linux

package reuseport

import (
	"github.com/ipfs/fs-repo-migrations/fs-repo-6-to-7/gx/ipfs/QmPXvegq26x982cQjSfbTvSzZXn7GiaMwhhVPHkeTEhrPT/sys/unix"
	"sync"
	"syscall"
	"time"

	sockaddrnet "github.com/ipfs/fs-repo-migrations/fs-repo-6-to-7/gx/ipfs/QmWBEjwiGM7mzpa2zkaen5PhxwRS4LrVRWAagBaPJio8eY/go-sockaddr/net"
)

var (
	hasReusePort bool
	didReusePort sync.Once
)

// Available returns whether or not SO_REUSEPORT is available in the OS.
// It does so by attepting to open a tcp socket, setting the option, and
// checking ENOPROTOOPT on error. After checking, the decision is cached
// for the rest of the process run.
func available() bool {
	didReusePort.Do(checkReusePort)
	return hasReusePort
}

func checkReusePort() {
	// there may be fluke reasons to fail to open a socket.
	// so we give it 5 shots. if not, give up and call it not avail.
	for i := 0; i < 5; i++ {
		// try to setup a TCP socket.
		fd, err := socket(sockaddrnet.AF_INET, sockaddrnet.SOCK_STREAM, sockaddrnet.IPPROTO_TCP)
		if err == nil {
			unix.Close(fd)
			hasReusePort = true
			return
		}

		if errno, ok := err.(syscall.Errno); ok && errno == unix.ENOPROTOOPT {
			return // :( that's all folks.
		}

		// not an errno? or not ENOPROTOOPT? retry.
		time.Sleep(20 * time.Millisecond) // wait a bit
	}
}
