Skip to content

Commit 6f84613

Browse files
committed
clair: add idle timeout
This will allow for Clair instances to spin down when they stop receiving requests for some period. This is particularly useful when combined with a systemd socket unit. Signed-off-by: Hank Donnay <hdonnay@redhat.com>
1 parent a949155 commit 6f84613

2 files changed

Lines changed: 61 additions & 2 deletions

File tree

cmd/clair/idle.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"net"
6+
"net/http"
7+
"sync/atomic"
8+
"time"
9+
)
10+
11+
// IdleMontior holds idle tracking machinery.
12+
type idleMonitor struct {
13+
timer *time.Timer
14+
timeout time.Duration
15+
ct atomic.Uint32
16+
}
17+
18+
// NewIdleMonitor starts a goroutine to call "f" if the duration "timeout"
19+
// passes with no active HTTP connections.
20+
//
21+
// The goroutine will also exit if the passed context is canceled.
22+
func newIdleMonitor(ctx context.Context, timeout time.Duration, f context.CancelCauseFunc) idleMonitor {
23+
timer := time.NewTimer(timeout)
24+
go func() {
25+
select {
26+
case <-ctx.Done():
27+
case <-timer.C:
28+
f(nil) // TODO(hank) Add a specific "idle timeout" condition?
29+
}
30+
}()
31+
32+
return idleMonitor{
33+
timer: timer,
34+
timeout: timeout,
35+
}
36+
}
37+
38+
// ServerHook is a function suitable for use as [http.Server.ConnState].
39+
//
40+
// This hook watches connection state changes, starting an idle timer when there
41+
// are no open connections. The timer will be stopped if new connections arrive
42+
// during the timeout period.
43+
func (m *idleMonitor) ServerHook(_ net.Conn, state http.ConnState) {
44+
switch state {
45+
case http.StateNew:
46+
if m.ct.Add(1) == 1 {
47+
m.timer.Stop()
48+
}
49+
case http.StateClosed, http.StateHijacked:
50+
if m.ct.Add(^uint32(0)) == 0 {
51+
m.timer.Reset(m.timeout)
52+
}
53+
}
54+
}

cmd/clair/main.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ func main() {
115115
}()
116116

117117
srvs, srvctx := errgroup.WithContext(sig)
118+
srvctx, teardown := context.WithCancelCause(srvctx)
118119
srvs.Go(serveIntrospection(srvctx, &conf))
119-
srvs.Go(serveAPI(srvctx, &conf))
120+
srvs.Go(serveAPI(srvctx, &conf, teardown))
120121

121122
slog.InfoContext(ctx, "ready", "version", cmd.Version)
122123
notify(msgReady,
@@ -127,7 +128,7 @@ func main() {
127128
}
128129
}
129130

130-
func serveAPI(ctx context.Context, cfg *config.Config) func() error {
131+
func serveAPI(ctx context.Context, cfg *config.Config, teardown context.CancelCauseFunc) func() error {
131132
apicfg := &cfg.API.V1
132133
if !*apicfg.Enabled {
133134
return func() error {
@@ -146,6 +147,10 @@ func serveAPI(ctx context.Context, cfg *config.Config) func() error {
146147
return context.WithoutCancel(ctx)
147148
},
148149
}
150+
if t := time.Duration(apicfg.IdleTimeout); t != 0 {
151+
idle := newIdleMonitor(ctx, t, teardown)
152+
srv.ConnState = idle.ServerHook
153+
}
149154
srv.Handler, err = httptransport.New(ctx, cfg, srvs.Indexer, srvs.Matcher, srvs.Notifier)
150155
if err != nil {
151156
return fmt.Errorf("http transport configuration failed: %w", err)

0 commit comments

Comments
 (0)