fix: materialize egress checks before basic tasks
This commit is contained in:
parent
b9b323af9b
commit
6f5a2faea6
@ -295,7 +295,10 @@ func (supervisor *ConfiguredSchedulerSupervisor) upstreamDueGroups(
|
|||||||
egressURLs []string,
|
egressURLs []string,
|
||||||
targets []configuredTargetProfile,
|
targets []configuredTargetProfile,
|
||||||
) ([]DueSource, error) {
|
) ([]DueSource, error) {
|
||||||
groups := []DueSource{basic}
|
// EGRESS and TARGET due reads lazily materialize references from BASIC due
|
||||||
|
// entries. Run them before BASIC so a small batch cannot consume the only
|
||||||
|
// source reference before these independent checks are established.
|
||||||
|
groups := make([]DueSource, 0, 1+len(egressURLs)+len(targets))
|
||||||
if egress, supported := supervisor.source.(EgressUpstreamTaskSource); supported {
|
if egress, supported := supervisor.source.(EgressUpstreamTaskSource); supported {
|
||||||
for _, targetURL := range egressURLs {
|
for _, targetURL := range egressURLs {
|
||||||
source, err := newEgressUpstreamDueSource(egress, upstreamID, targetURL)
|
source, err := newEgressUpstreamDueSource(egress, upstreamID, targetURL)
|
||||||
@ -314,6 +317,7 @@ func (supervisor *ConfiguredSchedulerSupervisor) upstreamDueGroups(
|
|||||||
groups = append(groups, source)
|
groups = append(groups, source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
groups = append(groups, basic)
|
||||||
return groups, nil
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -188,9 +188,50 @@ func TestConfiguredSchedulerSupervisorSchedulesBoundedEgressGroups(t *testing.T)
|
|||||||
t.Fatalf("NewConfiguredSchedulerSupervisor() = %v", err)
|
t.Fatalf("NewConfiguredSchedulerSupervisor() = %v", err)
|
||||||
}
|
}
|
||||||
result, err := supervisor.Tick(context.Background())
|
result, err := supervisor.Tick(context.Background())
|
||||||
if err != nil || result != (TickResult{Planned: 3, Offered: 3}) || len(sink.tasks) != 1 ||
|
if err != nil || result != (TickResult{Planned: 3, Offered: 3}) || !sink.offeredLevel(healthDomain.LevelEgress) ||
|
||||||
sink.tasks[0].Candidate.Level != healthDomain.LevelEgress || source.sources["provider-a"].egressCalls != 2 {
|
source.sources["provider-a"].egressCalls != 2 {
|
||||||
t.Fatalf("Tick() = (%+v, %v); source=%+v sink=%+v", result, err, source, sink.tasks)
|
t.Fatalf("Tick() = (%+v, %v); source=%+v sink=%+v", result, err, source, sink.batches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfiguredSchedulerSupervisorMaterializesEveryEgressGroupBeforeBasic(t *testing.T) {
|
||||||
|
now := time.Date(2026, 8, 2, 11, 15, 0, 0, time.UTC)
|
||||||
|
firstURL := "https://egress-one.example/identity"
|
||||||
|
secondURL := "https://egress-two.example/identity"
|
||||||
|
configuration := &config.Config{
|
||||||
|
Defaults: config.Defaults{Check: config.Check{
|
||||||
|
Interval: config.Duration(time.Minute), MaxInFlight: 1, Timeout: config.Duration(time.Second), MaxAttempts: 1,
|
||||||
|
URLs: []string{firstURL, secondURL},
|
||||||
|
}},
|
||||||
|
Upstreams: map[string]config.Upstream{"provider-a": {Enabled: true}},
|
||||||
|
}
|
||||||
|
source := &upstreamTaskSourceStub{sources: map[string]*dueSourceStub{
|
||||||
|
"provider-a": {candidates: []Candidate{{
|
||||||
|
ProxyID: "basic", State: proxyDomain.StateFetched, Level: healthDomain.LevelBasic, DueAt: now,
|
||||||
|
}}, egressCandidates: map[string][]Candidate{
|
||||||
|
firstURL: {{ProxyID: "egress-one", State: proxyDomain.StateFetched, Level: healthDomain.LevelEgress, TargetURL: firstURL, DueAt: now}},
|
||||||
|
secondURL: {{ProxyID: "egress-two", State: proxyDomain.StateFetched, Level: healthDomain.LevelEgress, TargetURL: secondURL, DueAt: now}},
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
sink := &taskSinkStub{}
|
||||||
|
supervisor, err := NewConfiguredSchedulerSupervisor(&configurationSourceStub{configuration: configuration}, source, sink,
|
||||||
|
SchedulerRunnerOptions{PollInterval: time.Second, BatchSize: 1, Now: func() time.Time { return now }})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewConfiguredSchedulerSupervisor() = %v", err)
|
||||||
|
}
|
||||||
|
for tick := 0; tick < 2; tick++ {
|
||||||
|
if result, err := supervisor.Tick(context.Background()); err != nil || result != (TickResult{Planned: 1, Offered: 1}) {
|
||||||
|
t.Fatalf("Tick(%d) = (%+v, %v)", tick, result, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item := source.sources["provider-a"]
|
||||||
|
if item.egressCalls != 2 || item.dueCalls != 0 || len(sink.batches) != 2 ||
|
||||||
|
sink.batches[0][0].Candidate.Level != healthDomain.LevelEgress || sink.batches[1][0].Candidate.Level != healthDomain.LevelEgress {
|
||||||
|
t.Fatalf("first two ticks did not establish egress groups: source=%+v batches=%+v", item, sink.batches)
|
||||||
|
}
|
||||||
|
if result, err := supervisor.Tick(context.Background()); err != nil || result != (TickResult{Planned: 1, Offered: 1}) ||
|
||||||
|
item.dueCalls != 1 || len(sink.batches) != 3 || sink.batches[2][0].Candidate.Level != healthDomain.LevelBasic {
|
||||||
|
t.Fatalf("basic Tick() = (%+v, %v), source=%+v batches=%+v", result, err, item, sink.batches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,10 +266,9 @@ func TestConfiguredSchedulerSupervisorSchedulesRoutingTargetProfiles(t *testing.
|
|||||||
t.Fatalf("NewConfiguredSchedulerSupervisor() = %v", err)
|
t.Fatalf("NewConfiguredSchedulerSupervisor() = %v", err)
|
||||||
}
|
}
|
||||||
result, err := supervisor.Tick(context.Background())
|
result, err := supervisor.Tick(context.Background())
|
||||||
if err != nil || result != (TickResult{Planned: 2, Offered: 2}) || len(sink.tasks) != 1 ||
|
if err != nil || result != (TickResult{Planned: 2, Offered: 2}) || !sink.offeredTarget(routingName, targetURL) ||
|
||||||
sink.tasks[0].Candidate.Level != healthDomain.LevelTarget || sink.tasks[0].Candidate.RoutingName != routingName ||
|
|
||||||
source.sources["provider-a"].targetCalls != 1 {
|
source.sources["provider-a"].targetCalls != 1 {
|
||||||
t.Fatalf("Tick() = (%+v, %v); source=%+v sink=%+v", result, err, source, sink.tasks)
|
t.Fatalf("Tick() = (%+v, %v); source=%+v sink=%+v", result, err, source, sink.batches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,14 +399,39 @@ func targetCandidateKey(routingName, targetURL string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type taskSinkStub struct {
|
type taskSinkStub struct {
|
||||||
tasks []PlannedTask
|
tasks []PlannedTask
|
||||||
offer int
|
batches [][]PlannedTask
|
||||||
|
offer int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sink *taskSinkStub) Offer(_ context.Context, tasks []PlannedTask) (int, error) {
|
func (sink *taskSinkStub) Offer(_ context.Context, tasks []PlannedTask) (int, error) {
|
||||||
sink.tasks = append([]PlannedTask(nil), tasks...)
|
sink.tasks = append([]PlannedTask(nil), tasks...)
|
||||||
|
sink.batches = append(sink.batches, append([]PlannedTask(nil), tasks...))
|
||||||
if sink.offer != 0 {
|
if sink.offer != 0 {
|
||||||
return sink.offer, nil
|
return sink.offer, nil
|
||||||
}
|
}
|
||||||
return len(tasks), nil
|
return len(tasks), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sink *taskSinkStub) offeredLevel(level healthDomain.Level) bool {
|
||||||
|
for _, batch := range sink.batches {
|
||||||
|
for _, task := range batch {
|
||||||
|
if task.Candidate.Level == level {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sink *taskSinkStub) offeredTarget(routingName, targetURL string) bool {
|
||||||
|
for _, batch := range sink.batches {
|
||||||
|
for _, task := range batch {
|
||||||
|
if task.Candidate.Level == healthDomain.LevelTarget && task.Candidate.RoutingName == routingName &&
|
||||||
|
task.Candidate.TargetURL == targetURL {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@ -19,6 +19,8 @@
|
|||||||
前重建并下发递增版本,Gateway 在同一长连接内原子替换视图;构建失败或流中断时才由
|
前重建并下发递增版本,Gateway 在同一长连接内原子替换视图;构建失败或流中断时才由
|
||||||
SessionSupervisor 按原有退避重连。该机制复用 `RefreshingSnapshotSource`,不降低
|
SessionSupervisor 按原有退避重连。该机制复用 `RefreshingSnapshotSource`,不降低
|
||||||
Delta 的 fail-closed 校验,也不把自动 Drain/ACK 记为已完成。
|
Delta 的 fail-closed 校验,也不把自动 Drain/ACK 记为已完成。
|
||||||
|
- Health Scheduler 现在先建立 EGRESS 与 Routing TARGET 的独立 due 引用,再投递
|
||||||
|
BASIC 任务;在单项小批次下,短 TTL Proxy 不会因 BASIC 先出队而错过首次出口/目标探测。
|
||||||
|
|
||||||
## 2026-07-30
|
## 2026-07-30
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user