diff options
| author | Gab Virebent <gabriel1@virebent.art> | 2026-08-24 17:34:45 +0200 |
|---|---|---|
| committer | Gab Virebent <gabriel1@virebent.art> | 2026-08-24 17:34:45 +0200 |
| commit | e9fbbe3373eb66a345f5e3829e2563b94dc92051 (patch) | |
| tree | 950d75fda88574afb46b4aeab36e96f3c089a3bb /internal/transporthealth | |
| parent | fb83c4d70616ec23d8a5397409a5d31c70b70d66 (diff) | |
| download | n2usenet-main.tar.gz n2usenet-main.tar.xz n2usenet-main.zip | |
Diffstat (limited to 'internal/transporthealth')
| -rw-r--r-- | internal/transporthealth/monitor.go | 83 | ||||
| -rw-r--r-- | internal/transporthealth/monitor_test.go | 30 |
2 files changed, 113 insertions, 0 deletions
diff --git a/internal/transporthealth/monitor.go b/internal/transporthealth/monitor.go new file mode 100644 index 0000000..48369ee --- /dev/null +++ b/internal/transporthealth/monitor.go @@ -0,0 +1,83 @@ +package transporthealth + +import ( + "context" + "log" + "sync" + "time" +) + +type Checker interface { + Check(context.Context) error +} + +type Monitor struct { + checker Checker + interval time.Duration + timeout time.Duration + + mu sync.RWMutex + ready bool + checkedAt time.Time +} + +func New(checker Checker, interval, timeout time.Duration) *Monitor { + if interval < time.Minute { + interval = 15 * time.Minute + } + if timeout <= 0 { + timeout = 90 * time.Second + } + return &Monitor{checker: checker, interval: interval, timeout: timeout} +} + +func (m *Monitor) Run(ctx context.Context) { + m.Probe(ctx) + ticker := time.NewTicker(m.interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + m.Probe(ctx) + } + } +} + +func (m *Monitor) Probe(ctx context.Context) { + probeCtx, cancel := context.WithTimeout(ctx, m.timeout) + defer cancel() + if err := m.checker.Check(probeCtx); err != nil { + m.MarkFailure(err) + log.Printf("transport probe failed: %v", err) + return + } + m.MarkSuccess() + log.Printf("transport probe succeeded") +} + +func (m *Monitor) Ready() bool { + m.mu.RLock() + ready := m.ready + checkedAt := m.checkedAt + m.mu.RUnlock() + if !ready || checkedAt.IsZero() { + return false + } + return time.Since(checkedAt) <= 2*m.interval+m.timeout +} + +func (m *Monitor) MarkSuccess() { + m.mu.Lock() + m.ready = true + m.checkedAt = time.Now() + m.mu.Unlock() +} + +func (m *Monitor) MarkFailure(error) { + m.mu.Lock() + m.ready = false + m.checkedAt = time.Now() + m.mu.Unlock() +} diff --git a/internal/transporthealth/monitor_test.go b/internal/transporthealth/monitor_test.go new file mode 100644 index 0000000..c5eb98d --- /dev/null +++ b/internal/transporthealth/monitor_test.go @@ -0,0 +1,30 @@ +package transporthealth + +import ( + "context" + "errors" + "testing" + "time" +) + +type checkerFunc func(context.Context) error + +func (f checkerFunc) Check(ctx context.Context) error { + return f(ctx) +} + +func TestProbeTracksReadiness(t *testing.T) { + var checkErr error + monitor := New(checkerFunc(func(context.Context) error { return checkErr }), time.Minute, time.Second) + + monitor.Probe(context.Background()) + if !monitor.Ready() { + t.Fatal("successful probe did not mark transport ready") + } + + checkErr = errors.New("transport unavailable") + monitor.Probe(context.Background()) + if monitor.Ready() { + t.Fatal("failed probe left transport ready") + } +} |
