Skip to content

Commit 3e6d0b2

Browse files
committed
fix: thread lifecycle context to avoid goroutine leaks on shutdown
Pass the application context from main() through wireApp to all NATS reconnect subscribers (caches and audit publisher). When the context is cancelled on shutdown, subscriber channels are closed and watcher goroutines exit cleanly. Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>
1 parent 5b6265e commit 3e6d0b2

7 files changed

Lines changed: 28 additions & 26 deletions

File tree

app/controlplane/cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func main() {
144144
ctx, cancel := context.WithCancel(context.Background())
145145
defer cancel()
146146

147-
app, cleanup, err := wireApp(&bc, credsWriter, logger, availablePlugins)
147+
app, cleanup, err := wireApp(ctx, &bc, credsWriter, logger, availablePlugins)
148148
if err != nil {
149149
panic(err)
150150
}

app/controlplane/cmd/wire.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import (
4545
"github.com/google/wire"
4646
)
4747

48-
func wireApp(*conf.Bootstrap, credentials.ReaderWriter, log.Logger, sdk.AvailablePlugins) (*app, func(), error) {
48+
func wireApp(context.Context, *conf.Bootstrap, credentials.ReaderWriter, log.Logger, sdk.AvailablePlugins) (*app, func(), error) {
4949
panic(
5050
wire.Build(
5151
wire.Bind(new(credentials.Reader), new(credentials.ReaderWriter)),
@@ -143,40 +143,40 @@ var cacheProviderSet = wire.NewSet(
143143
newPolicyEvalBundleCache,
144144
)
145145

146-
func newClaimsCache(rc *natsconn.ReloadableConnection, logger log.Logger) (cache.Cache[*jwt.MapClaims], error) {
146+
func newClaimsCache(ctx context.Context, rc *natsconn.ReloadableConnection, logger log.Logger) (cache.Cache[*jwt.MapClaims], error) {
147147
l := log.NewHelper(logger)
148148
backend := "memory"
149149
opts := []cache.Option{cache.WithTTL(10 * time.Second), cache.WithLogger(&kratosLogAdapter{h: l}), cache.WithDescription("Cache for JWT claims")}
150150
if rc != nil {
151151
backend = "nats"
152152
opts = append(opts, cache.WithNATS(rc.Conn, "chainloop-jwt-claims"))
153-
opts = append(opts, cache.WithReconnect(rc.Subscribe(context.Background())))
153+
opts = append(opts, cache.WithReconnect(rc.Subscribe(ctx)))
154154
}
155155
l.Infow("msg", "cache initialized", "bucket", "chainloop-jwt-claims", "backend", backend, "ttl", "10s")
156156
return cache.New[*jwt.MapClaims](opts...)
157157
}
158158

159-
func newMembershipsCache(rc *natsconn.ReloadableConnection, logger log.Logger) (cache.Cache[*entities.Membership], error) {
159+
func newMembershipsCache(ctx context.Context, rc *natsconn.ReloadableConnection, logger log.Logger) (cache.Cache[*entities.Membership], error) {
160160
l := log.NewHelper(logger)
161161
backend := "memory"
162162
opts := []cache.Option{cache.WithTTL(time.Second), cache.WithLogger(&kratosLogAdapter{h: l}), cache.WithDescription("Cache for org memberships")}
163163
if rc != nil {
164164
backend = "nats"
165165
opts = append(opts, cache.WithNATS(rc.Conn, "chainloop-memberships"))
166-
opts = append(opts, cache.WithReconnect(rc.Subscribe(context.Background())))
166+
opts = append(opts, cache.WithReconnect(rc.Subscribe(ctx)))
167167
}
168168
l.Infow("msg", "cache initialized", "bucket", "chainloop-memberships", "backend", backend, "ttl", "1s")
169169
return cache.New[*entities.Membership](opts...)
170170
}
171171

172-
func newPolicyEvalBundleCache(rc *natsconn.ReloadableConnection, logger log.Logger) (cache.Cache[[]byte], error) {
172+
func newPolicyEvalBundleCache(ctx context.Context, rc *natsconn.ReloadableConnection, logger log.Logger) (cache.Cache[[]byte], error) {
173173
l := log.NewHelper(logger)
174174
backend := "memory"
175175
opts := []cache.Option{cache.WithTTL(24 * time.Hour), cache.WithLogger(&kratosLogAdapter{h: l}), cache.WithDescription("Cache for policy evaluation bundles from CAS")}
176176
if rc != nil {
177177
backend = "nats"
178178
opts = append(opts, cache.WithNATS(rc.Conn, "chainloop-policy-eval-bundles"))
179-
opts = append(opts, cache.WithReconnect(rc.Subscribe(context.Background())))
179+
opts = append(opts, cache.WithReconnect(rc.Subscribe(ctx)))
180180
}
181181
l.Infow("msg", "cache initialized", "bucket", "chainloop-policy-eval-bundles", "backend", backend, "ttl", "24h")
182182
return cache.New[[]byte](opts...)

app/controlplane/cmd/wire_gen.go

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/pkg/auditor/nats.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type AuditLogPublisher struct {
4141
logger *log.Helper
4242
}
4343

44-
func NewAuditLogPublisher(rc *natsconn.ReloadableConnection, logger log.Logger) (*AuditLogPublisher, error) {
44+
func NewAuditLogPublisher(ctx context.Context, rc *natsconn.ReloadableConnection, logger log.Logger) (*AuditLogPublisher, error) {
4545
l := log.NewHelper(log.With(logger, "component", "natsAuditLogPublisher"))
4646
if rc == nil {
4747
l.Infow("msg", "NATS connection not set, audit log publisher disabled")
@@ -54,7 +54,7 @@ func NewAuditLogPublisher(rc *natsconn.ReloadableConnection, logger log.Logger)
5454
return nil, err
5555
}
5656

57-
go p.watchReconnect(rc.Subscribe(context.Background()))
57+
go p.watchReconnect(rc.Subscribe(ctx))
5858

5959
return p, nil
6060
}

app/controlplane/pkg/biz/testhelpers/database.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2024-2025 The Chainloop Authors.
2+
// Copyright 2024-2026 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -146,7 +146,7 @@ func NewTestingUseCases(t *testing.T, opts ...NewTestingUCOpt) *TestingUseCases
146146

147147
db := NewTestDatabase(t)
148148
log := log.NewStdLogger(io.Discard)
149-
testData, _, err := WireTestData(db, t, log, newArgs.credsReaderWriter, &robotaccount.Builder{}, &conf.Auth{
149+
testData, _, err := WireTestData(t.Context(), db, t, log, newArgs.credsReaderWriter, &robotaccount.Builder{}, &conf.Auth{
150150
GeneratedJwsHmacSecret: "test",
151151
CasRobotAccountPrivateKeyPath: "./testdata/test-key.ec.pem",
152152
}, &conf.Bootstrap{}, newArgs.onboardingConfiguration, newArgs.integrations, newArgs.providers)

app/controlplane/pkg/biz/testhelpers/wire.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package testhelpers
2222

2323
import (
24+
"context"
2425
"testing"
2526

2627
conf "github.com/chainloop-dev/chainloop/app/controlplane/internal/conf/controlplane/config/v1"
@@ -41,7 +42,7 @@ import (
4142
)
4243

4344
// wireTestData init testing data
44-
func WireTestData(*TestDatabase, *testing.T, log.Logger, credentials.ReaderWriter, *robotaccount.Builder, *conf.Auth, *conf.Bootstrap, []*config.OnboardingSpec, sdk.AvailablePlugins, backends.Providers) (*TestingUseCases, func(), error) {
45+
func WireTestData(context.Context, *TestDatabase, *testing.T, log.Logger, credentials.ReaderWriter, *robotaccount.Builder, *conf.Auth, *conf.Bootstrap, []*config.OnboardingSpec, sdk.AvailablePlugins, backends.Providers) (*TestingUseCases, func(), error) {
4546
panic(
4647
wire.Build(
4748
data.ProviderSet,

app/controlplane/pkg/biz/testhelpers/wire_gen.go

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)