// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: AGPL-3.0

package keepstore

import (
	"time"

	"git.arvados.org/arvados.git/sdk/go/ctxlog"
	"github.com/prometheus/client_golang/prometheus"
	. "gopkg.in/check.v1"
)

var _ = Suite(&BufferPoolSuite{})

var bufferPoolTestSize = 10

type BufferPoolSuite struct{}

func (s *BufferPoolSuite) SetUpTest(c *C) {
	bufferPoolBlockSize = bufferPoolTestSize
}

func (s *BufferPoolSuite) TearDownTest(c *C) {
	bufferPoolBlockSize = BlockSize
}

func (s *BufferPoolSuite) TestBufferPoolBufSize(c *C) {
	bufs := newBufferPool(ctxlog.TestLogger(c), 2, prometheus.NewRegistry())
	b1 := bufs.Get()
	bufs.Get()
	bufs.Put(b1)
	b3 := bufs.Get()
	c.Check(len(b3), Equals, bufferPoolTestSize)
}

func (s *BufferPoolSuite) TestBufferPoolUnderLimit(c *C) {
	bufs := newBufferPool(ctxlog.TestLogger(c), 3, prometheus.NewRegistry())
	b1 := bufs.Get()
	bufs.Get()
	testBufferPoolRace(c, bufs, b1, "Get")
}

func (s *BufferPoolSuite) TestBufferPoolAtLimit(c *C) {
	bufs := newBufferPool(ctxlog.TestLogger(c), 2, prometheus.NewRegistry())
	b1 := bufs.Get()
	bufs.Get()
	testBufferPoolRace(c, bufs, b1, "Put")
}

func testBufferPoolRace(c *C, bufs *bufferPool, unused []byte, expectWin string) {
	race := make(chan string)
	go func() {
		bufs.Get()
		time.Sleep(time.Millisecond)
		race <- "Get"
	}()
	go func() {
		time.Sleep(10 * time.Millisecond)
		bufs.Put(unused)
		race <- "Put"
	}()
	c.Check(<-race, Equals, expectWin)
	c.Check(<-race, Not(Equals), expectWin)
	close(race)
}

func (s *BufferPoolSuite) TestBufferPoolReuse(c *C) {
	bufs := newBufferPool(ctxlog.TestLogger(c), 2, prometheus.NewRegistry())
	bufs.Get()
	last := bufs.Get()
	// The buffer pool is allowed to throw away unused buffers
	// (e.g., during sync.Pool's garbage collection hook, in the
	// the current implementation). However, if unused buffers are
	// getting thrown away and reallocated more than {arbitrary
	// frequency threshold} during a busy loop, it's not acting
	// much like a buffer pool.
	allocs := 1000
	reuses := 0
	for i := 0; i < allocs; i++ {
		bufs.Put(last)
		next := bufs.Get()
		copy(last, []byte("last"))
		copy(next, []byte("next"))
		if last[0] == 'n' {
			reuses++
		}
		last = next
	}
	c.Check(reuses > allocs*95/100, Equals, true)
}