package compiler
import (
"context"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/require"
syncutil "github.com/projectdiscovery/utils/sync"
)
func TestAddWithContextRespectsDeadline(t *testing.T) {
pool, err := syncutil.New(syncutil.WithSize(1))
require.NoError(t, err)
pool.Add()
defer pool.Done()
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
start := time.Now()
err = pool.AddWithContext(ctx)
elapsed := time.Since(start)
require.Error(t, err, "AddWithContext should fail when pool is full and deadline expires")
require.Less(t, elapsed, 200*time.Millisecond, "AddWithContext should fail fast after deadline")
}
func TestWatchdogReleasesSlotOnDeadline(t *testing.T) {
pool, err := syncutil.New(syncutil.WithSize(1))
require.NoError(t, err)
pool.Add()
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
var slotReleased atomic.Bool
watchdogDone := make(chan struct{})
go func() {
select {
case <-ctx.Done():
if slotReleased.CompareAndSwap(false, true) {
pool.Done()
}
case <-watchdogDone:
}
}()
defer func() {
close(watchdogDone)
if slotReleased.CompareAndSwap(false, true) {
pool.Done()
}
}()
<-ctx.Done()
time.Sleep(20 * time.Millisecond)
freshCtx, freshCancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer freshCancel()
require.NoError(t, pool.AddWithContext(freshCtx),
"slot acquisition should succeed after watchdog release")
pool.Done()
}
func TestPoolExhaustionRecovery(t *testing.T) {
const poolSize = 3
pool, err := syncutil.New(syncutil.WithSize(poolSize))
require.NoError(t, err)
for i := range poolSize {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
require.NoError(t, pool.AddWithContext(ctx), "initial slot acquisition %d", i)
var released atomic.Bool
done := make(chan struct{})
go func() {
select {
case <-ctx.Done():
if released.CompareAndSwap(false, true) {
pool.Done()
}
case <-done:
}
}()
go func() {
defer func() {
close(done)
if released.CompareAndSwap(false, true) {
pool.Done()
}
}()
time.Sleep(10 * time.Second)
}()
}
time.Sleep(200 * time.Millisecond)
for i := range poolSize {
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
require.NoError(t, pool.AddWithContext(ctx),
"post-recovery slot acquisition %d/%d (pool still starved)", i+1, poolSize)
pool.Done()
}
}