Skip to content

Commit 5caf1bb

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 e7c3153 commit 5caf1bb

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
@@ -113,8 +113,9 @@ func main() {
113113
}()
114114

115115
srvs, srvctx := errgroup.WithContext(sig)
116+
srvctx, teardown := context.WithCancelCause(srvctx)
116117
srvs.Go(serveIntrospection(srvctx, &conf))
117-
srvs.Go(serveAPI(srvctx, &conf))
118+
srvs.Go(serveAPI(srvctx, &conf, teardown))
118119

119120
zlog.Info(ctx).
120121
Str("version", cmd.Version).
@@ -129,7 +130,7 @@ func main() {
129130
}
130131
}
131132

132-
func serveAPI(ctx context.Context, cfg *config.Config) func() error {
133+
func serveAPI(ctx context.Context, cfg *config.Config, teardown context.CancelCauseFunc) func() error {
133134
apicfg := &cfg.API.V1
134135
if !*apicfg.Enabled {
135136
return func() error {
@@ -148,6 +149,10 @@ func serveAPI(ctx context.Context, cfg *config.Config) func() error {
148149
return context.WithoutCancel(ctx)
149150
},
150151
}
152+
if t := time.Duration(apicfg.IdleTimeout); t != 0 {
153+
idle := newIdleMonitor(ctx, t, teardown)
154+
srv.ConnState = idle.ServerHook
155+
}
151156
srv.Handler, err = httptransport.New(ctx, cfg, srvs.Indexer, srvs.Matcher, srvs.Notifier)
152157
if err != nil {
153158
return fmt.Errorf("http transport configuration failed: %w", err)

0 commit comments

Comments
 (0)