fix(broker): back off absent-daemon observations under demand
- commit
- 43e0ba000ac660f9785fd07bebf8dae1d8944d0a
- author
- brice · · 2026-09-23 09:29 UTC
- parent
- d031a8e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +37 -1
M internal/usecase/broker/registry.go +3 -1
| @@ -1110,7 +1110,9 @@ func (r *Registry) apply(result probeResult) { | |||
| 1110 | 1110 | if !failed && availability == domain.RemoteAvailabilityNoDaemon { | |
| 1111 | 1111 | current.Availability = availability | |
| 1112 | 1112 | current.LastSuccess = result.at | |
| 1113 | - | current.NextDue = result.at.Add(r.jitter(r.freshForLocked(false), result.endpoint, result.attempt)) | |
| 1113 | + | // An absent daemon is stable: demand does not require a fresh SSH | |
| 1114 | + | // handshake every two seconds just to confirm it is still absent. | |
| 1115 | + | current.NextDue = result.at.Add(r.jitter(r.freshFor, result.endpoint, result.attempt)) | |
| 1114 | 1116 | current.ConsecutiveFailures = 0 | |
| 1115 | 1117 | current.LastFailure = domain.RemoteFailure{} | |
| 1116 | 1118 | current.Identity = "" | |
| @@ -1110,7 +1110,9 @@ func (r *Registry) apply(result probeResult) { | |||||
| 1110 | if !failed && availability == domain.RemoteAvailabilityNoDaemon { | 1110 | if !failed && availability == domain.RemoteAvailabilityNoDaemon { | ||
| 1111 | current.Availability = availability | 1111 | current.Availability = availability | ||
| 1112 | current.LastSuccess = result.at | 1112 | current.LastSuccess = result.at | ||
| 1113 | - | current.NextDue = result.at.Add(r.jitter(r.freshForLocked(false), result.endpoint, result.attempt)) | 1113 | + | // An absent daemon is stable: demand does not require a fresh SSH |
| 1114 | + | // handshake every two seconds just to confirm it is still absent. | |||
| 1115 | + | current.NextDue = result.at.Add(r.jitter(r.freshFor, result.endpoint, result.attempt)) | |||
| 1114 | current.ConsecutiveFailures = 0 | 1116 | current.ConsecutiveFailures = 0 | ||
| 1115 | current.LastFailure = domain.RemoteFailure{} | 1117 | current.LastFailure = domain.RemoteFailure{} | ||
| 1116 | current.Identity = "" | 1118 | current.Identity = "" | ||
A internal/usecase/broker/registry_no_daemon_backoff_test.go +34 -0
| @@ -0,0 +1,34 @@ | |||
| 1 | + | package broker | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | "time" | |
| 6 | + | ||
| 7 | + | "github.com/stretchr/testify/require" | |
| 8 | + | ||
| 9 | + | "github.com/bnema/vev/internal/domain" | |
| 10 | + | "github.com/bnema/vev/internal/ports" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | func TestRegistryNoDaemonUsesPassiveCadenceEvenWithDemand(t *testing.T) { | |
| 14 | + | start := time.Unix(100, 0) | |
| 15 | + | clock := newManualClock(start) | |
| 16 | + | probe := newTestProbe(2) | |
| 17 | + | registry := newTestRegistry(t, 1, newTestStore(), probe, clock) | |
| 18 | + | registry.jitter = identityJitter | |
| 19 | + | reg := registration(t, "example.test", 1) | |
| 20 | + | require.NoError(t, registry.setHosts(hostRecords(reg))) | |
| 21 | + | registry.SetDemand(true) | |
| 22 | + | startRegistry(t, registry) | |
| 23 | + | ||
| 24 | + | call := receiveCall(t, probe) | |
| 25 | + | call.result <- probeAnswer{snapshot: ports.BrokerDaemonObservation{Availability: domain.RemoteAvailabilityNoDaemon}} | |
| 26 | + | host := waitHost(t, registry, reg.Endpoint, func(host ports.BrokerDaemonObservation) bool { | |
| 27 | + | return !host.Checking && host.Availability == domain.RemoteAvailabilityNoDaemon | |
| 28 | + | }) | |
| 29 | + | require.Equal(t, start.Add(defaultFreshFor), host.NextDue) | |
| 30 | + | clock.Advance(defaultDemandFreshForRemote) | |
| 31 | + | requireNoProbeWithin(t, probe, probeGrace) | |
| 32 | + | clock.Advance(defaultFreshFor - defaultDemandFreshForRemote) | |
| 33 | + | receiveCall(t, probe) | |
| 34 | + | } | |
| @@ -0,0 +1,34 @@ | |||||
| 1 | + | package broker | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | "time" | |||
| 6 | + | ||||
| 7 | + | "github.com/stretchr/testify/require" | |||
| 8 | + | ||||
| 9 | + | "github.com/bnema/vev/internal/domain" | |||
| 10 | + | "github.com/bnema/vev/internal/ports" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | func TestRegistryNoDaemonUsesPassiveCadenceEvenWithDemand(t *testing.T) { | |||
| 14 | + | start := time.Unix(100, 0) | |||
| 15 | + | clock := newManualClock(start) | |||
| 16 | + | probe := newTestProbe(2) | |||
| 17 | + | registry := newTestRegistry(t, 1, newTestStore(), probe, clock) | |||
| 18 | + | registry.jitter = identityJitter | |||
| 19 | + | reg := registration(t, "example.test", 1) | |||
| 20 | + | require.NoError(t, registry.setHosts(hostRecords(reg))) | |||
| 21 | + | registry.SetDemand(true) | |||
| 22 | + | startRegistry(t, registry) | |||
| 23 | + | ||||
| 24 | + | call := receiveCall(t, probe) | |||
| 25 | + | call.result <- probeAnswer{snapshot: ports.BrokerDaemonObservation{Availability: domain.RemoteAvailabilityNoDaemon}} | |||
| 26 | + | host := waitHost(t, registry, reg.Endpoint, func(host ports.BrokerDaemonObservation) bool { | |||
| 27 | + | return !host.Checking && host.Availability == domain.RemoteAvailabilityNoDaemon | |||
| 28 | + | }) | |||
| 29 | + | require.Equal(t, start.Add(defaultFreshFor), host.NextDue) | |||
| 30 | + | clock.Advance(defaultDemandFreshForRemote) | |||
| 31 | + | requireNoProbeWithin(t, probe, probeGrace) | |||
| 32 | + | clock.Advance(defaultFreshFor - defaultDemandFreshForRemote) | |||
| 33 | + | receiveCall(t, probe) | |||
| 34 | + | } | |||