385 lines
9.9 KiB
Go
385 lines
9.9 KiB
Go
package executionregistry
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDrainRejectsLateInstallAndCancelsBoundScopes(t *testing.T) {
|
|
registry := New()
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
scope, errInstall := registry.Install(pending, ScopeSpec{RequestID: "req-1", CredentialID: "cred-1", Model: "gpt", Kind: "http", StartedAt: time.Now()})
|
|
if errInstall != nil {
|
|
t.Fatal(errInstall)
|
|
}
|
|
closed := atomic.Int32{}
|
|
if errBind := scope.Bind(func() error {
|
|
closed.Add(1)
|
|
go scope.End("canceled")
|
|
return nil
|
|
}); errBind != nil {
|
|
t.Fatal(errBind)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
if errDrain := registry.Drain(ctx); errDrain != nil {
|
|
t.Fatal(errDrain)
|
|
}
|
|
if closed.Load() != 1 {
|
|
t.Fatalf("close calls = %d", closed.Load())
|
|
}
|
|
if _, errLate := registry.BeginDispatch(); !errors.Is(errLate, ErrRegistryNotAccepting) {
|
|
t.Fatalf("late dispatch error = %v", errLate)
|
|
}
|
|
}
|
|
|
|
func TestScopeEndIsExactlyOnce(t *testing.T) {
|
|
registry := New()
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
scope, errInstall := registry.Install(pending, ScopeSpec{})
|
|
if errInstall != nil {
|
|
t.Fatal(errInstall)
|
|
}
|
|
closed := atomic.Int32{}
|
|
if errBind := scope.Bind(func() error {
|
|
closed.Add(1)
|
|
return nil
|
|
}); errBind != nil {
|
|
t.Fatal(errBind)
|
|
}
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
scope.End("complete")
|
|
close(done)
|
|
}()
|
|
scope.End("duplicate")
|
|
<-done
|
|
if closed.Load() != 1 {
|
|
t.Fatalf("close calls = %d, want 1", closed.Load())
|
|
}
|
|
}
|
|
|
|
func TestDrainWaitsForPendingDispatch(t *testing.T) {
|
|
registry := New()
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
done := make(chan error, 1)
|
|
go func() { done <- registry.Drain(ctx) }()
|
|
|
|
select {
|
|
case errDrain := <-done:
|
|
t.Fatalf("Drain() returned before pending dispatch ended: %v", errDrain)
|
|
case <-time.After(20 * time.Millisecond):
|
|
}
|
|
pending.End()
|
|
if errDrain := <-done; errDrain != nil {
|
|
t.Fatalf("Drain() error = %v", errDrain)
|
|
}
|
|
}
|
|
|
|
func TestWaitPendingDoesNotDrainActiveScope(t *testing.T) {
|
|
registry := New()
|
|
activePending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
scope, errInstall := registry.Install(activePending, ScopeSpec{})
|
|
if errInstall != nil {
|
|
t.Fatal(errInstall)
|
|
}
|
|
defer scope.End("test cleanup")
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
done := make(chan error, 1)
|
|
go func() { done <- registry.WaitPending(ctx) }()
|
|
select {
|
|
case errWait := <-done:
|
|
t.Fatalf("WaitPending() returned before pending dispatch ended: %v", errWait)
|
|
case <-time.After(20 * time.Millisecond):
|
|
}
|
|
pending.End()
|
|
if errWait := <-done; errWait != nil {
|
|
t.Fatalf("WaitPending() error = %v", errWait)
|
|
}
|
|
nextPending, errNext := registry.BeginDispatch()
|
|
if errNext != nil {
|
|
t.Fatalf("WaitPending() stopped registry acceptance: %v", errNext)
|
|
}
|
|
nextPending.End()
|
|
}
|
|
|
|
func TestDrainReturnsWhenBlockingResourceCloseExceedsContext(t *testing.T) {
|
|
registry := New()
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
scope, errInstall := registry.Install(pending, ScopeSpec{})
|
|
if errInstall != nil {
|
|
t.Fatal(errInstall)
|
|
}
|
|
started := make(chan struct{})
|
|
release := make(chan struct{})
|
|
if errBind := scope.Bind(func() error {
|
|
close(started)
|
|
<-release
|
|
return nil
|
|
}); errBind != nil {
|
|
t.Fatal(errBind)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
|
defer cancel()
|
|
errDrain := registry.Drain(ctx)
|
|
if !errors.Is(errDrain, context.DeadlineExceeded) {
|
|
t.Fatalf("Drain() error = %v, want context deadline exceeded", errDrain)
|
|
}
|
|
select {
|
|
case <-started:
|
|
default:
|
|
t.Fatal("Drain() did not start closing the bound resource")
|
|
}
|
|
if state := State(registry.state.Load()); state != StateDraining {
|
|
t.Fatalf("registry state = %v, want draining", state)
|
|
}
|
|
|
|
ended := make(chan struct{})
|
|
go func() {
|
|
scope.End("canceled")
|
|
close(ended)
|
|
}()
|
|
close(release)
|
|
select {
|
|
case <-ended:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Scope.End() did not wait for resource close completion")
|
|
}
|
|
if errDrain = registry.Drain(context.Background()); errDrain != nil {
|
|
t.Fatalf("Drain() after resource close = %v", errDrain)
|
|
}
|
|
}
|
|
|
|
func TestDrainWaitsForBlockingResourceClose(t *testing.T) {
|
|
registry := New()
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
scope, errInstall := registry.Install(pending, ScopeSpec{})
|
|
if errInstall != nil {
|
|
t.Fatal(errInstall)
|
|
}
|
|
started := make(chan struct{})
|
|
release := make(chan struct{})
|
|
if errBind := scope.Bind(func() error {
|
|
close(started)
|
|
<-release
|
|
return nil
|
|
}); errBind != nil {
|
|
t.Fatal(errBind)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
done := make(chan error, 1)
|
|
go func() { done <- registry.Drain(ctx) }()
|
|
|
|
select {
|
|
case <-started:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Drain() did not close the bound resource")
|
|
}
|
|
go scope.End("canceled")
|
|
select {
|
|
case errDrain := <-done:
|
|
t.Fatalf("Drain() returned before the resource close completed: %v", errDrain)
|
|
case <-time.After(20 * time.Millisecond):
|
|
}
|
|
close(release)
|
|
if errDrain := <-done; errDrain != nil {
|
|
t.Fatalf("Drain() error = %v", errDrain)
|
|
}
|
|
}
|
|
|
|
func TestConcurrentDrainWaitsForBlockingResourceClose(t *testing.T) {
|
|
registry := New()
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
scope, errInstall := registry.Install(pending, ScopeSpec{})
|
|
if errInstall != nil {
|
|
t.Fatal(errInstall)
|
|
}
|
|
started := make(chan struct{})
|
|
release := make(chan struct{})
|
|
if errBind := scope.Bind(func() error {
|
|
close(started)
|
|
<-release
|
|
return nil
|
|
}); errBind != nil {
|
|
t.Fatal(errBind)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
firstDrain := make(chan error, 1)
|
|
go func() { firstDrain <- registry.Drain(ctx) }()
|
|
select {
|
|
case <-started:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("first Drain() did not close the bound resource")
|
|
}
|
|
ended := make(chan struct{})
|
|
go func() {
|
|
scope.End("canceled")
|
|
close(ended)
|
|
}()
|
|
select {
|
|
case <-ended:
|
|
t.Fatal("Scope.End() returned before the resource close completed")
|
|
case <-time.After(20 * time.Millisecond):
|
|
}
|
|
secondDrain := make(chan error, 1)
|
|
go func() { secondDrain <- registry.Drain(ctx) }()
|
|
select {
|
|
case errDrain := <-secondDrain:
|
|
t.Fatalf("second Drain() returned before resource close completed: %v", errDrain)
|
|
case <-time.After(20 * time.Millisecond):
|
|
}
|
|
close(release)
|
|
select {
|
|
case <-ended:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Scope.End() did not complete after the resource close")
|
|
}
|
|
if errDrain := <-firstDrain; errDrain != nil {
|
|
t.Fatalf("first Drain() error = %v", errDrain)
|
|
}
|
|
if errDrain := <-secondDrain; errDrain != nil {
|
|
t.Fatalf("second Drain() error = %v", errDrain)
|
|
}
|
|
}
|
|
|
|
func TestConcurrentCloseWaitsForBlockingResourceClose(t *testing.T) {
|
|
registry := New()
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
scope, errInstall := registry.Install(pending, ScopeSpec{})
|
|
if errInstall != nil {
|
|
t.Fatal(errInstall)
|
|
}
|
|
started := make(chan struct{})
|
|
release := make(chan struct{})
|
|
if errBind := scope.Bind(func() error {
|
|
close(started)
|
|
<-release
|
|
return nil
|
|
}); errBind != nil {
|
|
t.Fatal(errBind)
|
|
}
|
|
|
|
firstClose := make(chan error, 1)
|
|
go func() { firstClose <- registry.Close() }()
|
|
select {
|
|
case <-started:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("first Close() did not close the bound resource")
|
|
}
|
|
|
|
secondClose := make(chan error, 1)
|
|
go func() { secondClose <- registry.Close() }()
|
|
select {
|
|
case errClose := <-secondClose:
|
|
t.Fatalf("second Close() returned before resource close completed: %v", errClose)
|
|
case <-time.After(20 * time.Millisecond):
|
|
}
|
|
|
|
close(release)
|
|
if errClose := <-firstClose; errClose != nil {
|
|
t.Fatalf("first Close() error = %v", errClose)
|
|
}
|
|
if errClose := <-secondClose; errClose != nil {
|
|
t.Fatalf("second Close() error = %v", errClose)
|
|
}
|
|
}
|
|
|
|
func TestDrainRejectsLateBind(t *testing.T) {
|
|
registry := New()
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
scope, errInstall := registry.Install(pending, ScopeSpec{})
|
|
if errInstall != nil {
|
|
t.Fatal(errInstall)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
done := make(chan error, 1)
|
|
go func() { done <- registry.Drain(ctx) }()
|
|
|
|
deadline := time.After(time.Second)
|
|
for State(registry.state.Load()) == StateAccepting {
|
|
select {
|
|
case <-deadline:
|
|
t.Fatal("registry did not begin draining")
|
|
default:
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}
|
|
if errBind := scope.Bind(func() error { return nil }); !errors.Is(errBind, ErrRegistryNotAccepting) {
|
|
t.Fatalf("Bind() error = %v, want ErrRegistryNotAccepting", errBind)
|
|
}
|
|
scope.End("canceled")
|
|
if errDrain := <-done; errDrain != nil {
|
|
t.Fatalf("Drain() error = %v", errDrain)
|
|
}
|
|
}
|
|
|
|
func TestDrainRejectsLateInstall(t *testing.T) {
|
|
registry := New()
|
|
pending, errBegin := registry.BeginDispatch()
|
|
if errBegin != nil {
|
|
t.Fatal(errBegin)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
done := make(chan error, 1)
|
|
go func() { done <- registry.Drain(ctx) }()
|
|
|
|
deadline := time.After(time.Second)
|
|
for State(registry.state.Load()) == StateAccepting {
|
|
select {
|
|
case <-deadline:
|
|
t.Fatal("registry did not begin draining")
|
|
default:
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}
|
|
if _, errInstall := registry.Install(pending, ScopeSpec{}); !errors.Is(errInstall, ErrRegistryNotAccepting) {
|
|
t.Fatalf("Install() error = %v, want ErrRegistryNotAccepting", errInstall)
|
|
}
|
|
if errDrain := <-done; errDrain != nil {
|
|
t.Fatalf("Drain() error = %v", errDrain)
|
|
}
|
|
}
|