Skip to content

Commit 8c84738

Browse files
committed
gracefully shutdown reconcilers and catalogd FBC
Signed-off-by: Joe Lanford <[email protected]>
1 parent 9b08aea commit 8c84738

File tree

7 files changed

+92
-9
lines changed

7 files changed

+92
-9
lines changed

.golangci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ linters-settings:
6666
alias: ctrl
6767
- pkg: github.com/blang/semver/v4
6868
alias: bsemver
69+
- pkg: "^github.com/operator-framework/operator-controller/internal/util/([^/]+)$"
70+
alias: "${1}util"
6971

7072
output:
7173
formats:

catalogd/config/base/manager/manager.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ spec:
8585
imagePullPolicy: IfNotPresent
8686
terminationMessagePolicy: FallbackToLogsOnError
8787
serviceAccountName: controller-manager
88-
terminationGracePeriodSeconds: 10
88+
terminationGracePeriodSeconds: 60
8989
volumes:
9090
- name: cache
9191
emptyDir: {}

catalogd/internal/controllers/core/clustercatalog_controller.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
catalogdv1 "github.com/operator-framework/operator-controller/catalogd/api/v1"
4141
"github.com/operator-framework/operator-controller/catalogd/internal/source"
4242
"github.com/operator-framework/operator-controller/catalogd/internal/storage"
43+
contextutil "github.com/operator-framework/operator-controller/internal/util/context"
4344
)
4445

4546
const (
@@ -91,8 +92,11 @@ func (r *ClusterCatalogReconciler) Reconcile(ctx context.Context, req ctrl.Reque
9192
return ctrl.Result{}, client.IgnoreNotFound(err)
9293
}
9394

95+
delayedCtx, delayedCancel := contextutil.WithDelay(ctx, time.Minute)
96+
defer delayedCancel()
97+
9498
reconciledCatsrc := existingCatsrc.DeepCopy()
95-
res, reconcileErr := r.reconcile(ctx, reconciledCatsrc)
99+
res, reconcileErr := r.reconcile(delayedCtx, reconciledCatsrc)
96100

97101
// If we encounter an error, we should delete the stored catalog metadata
98102
// which represents the state of a successfully unpacked catalog. Deleting
@@ -118,15 +122,15 @@ func (r *ClusterCatalogReconciler) Reconcile(ctx context.Context, req ctrl.Reque
118122
finalizers := reconciledCatsrc.Finalizers
119123

120124
if updateStatus {
121-
if err := r.Client.Status().Update(ctx, reconciledCatsrc); err != nil {
125+
if err := r.Client.Status().Update(delayedCtx, reconciledCatsrc); err != nil {
122126
reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating status: %v", err))
123127
}
124128
}
125129

126130
reconciledCatsrc.Finalizers = finalizers
127131

128132
if updateFinalizers {
129-
if err := r.Client.Update(ctx, reconciledCatsrc); err != nil {
133+
if err := r.Client.Update(delayedCtx, reconciledCatsrc); err != nil {
130134
reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating finalizers: %v", err))
131135
}
132136
}

catalogd/internal/serverutil/serverutil.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func AddCatalogServerToManager(mgr ctrl.Manager, cfg CatalogServerConfig, tlsFil
4141
listener = tls.NewListener(listener, config)
4242
}
4343

44-
shutdownTimeout := 30 * time.Second
44+
shutdownTimeout := 60 * time.Second
4545

4646
l := mgr.GetLogger().WithName("catalogd-http-server")
4747
handler := catalogdmetrics.AddMetricsToHandler(cfg.LocalStorage.StorageServerHandler())

config/base/manager/manager.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ spec:
8585
memory: 64Mi
8686
terminationMessagePolicy: FallbackToLogsOnError
8787
serviceAccountName: operator-controller-controller-manager
88-
terminationGracePeriodSeconds: 10
88+
terminationGracePeriodSeconds: 60
8989
volumes:
9090
- name: cache
9191
emptyDir: {}

internal/controllers/clusterextension_controller.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import (
5656
"github.com/operator-framework/operator-controller/internal/labels"
5757
"github.com/operator-framework/operator-controller/internal/resolve"
5858
rukpaksource "github.com/operator-framework/operator-controller/internal/rukpak/source"
59+
contextutil "github.com/operator-framework/operator-controller/internal/util/context"
5960
)
6061

6162
const (
@@ -110,8 +111,11 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
110111
return ctrl.Result{}, client.IgnoreNotFound(err)
111112
}
112113

114+
delayedCtx, delayedCancel := contextutil.WithDelay(ctx, time.Minute)
115+
defer delayedCancel()
116+
113117
reconciledExt := existingExt.DeepCopy()
114-
res, reconcileErr := r.reconcile(ctx, reconciledExt)
118+
res, reconcileErr := r.reconcile(delayedCtx, reconciledExt)
115119

116120
// Do checks before any Update()s, as Update() may modify the resource structure!
117121
updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status)
@@ -129,14 +133,14 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
129133
// reconciledExt before updating the object.
130134
finalizers := reconciledExt.Finalizers
131135
if updateStatus {
132-
if err := r.Client.Status().Update(ctx, reconciledExt); err != nil {
136+
if err := r.Client.Status().Update(delayedCtx, reconciledExt); err != nil {
133137
reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating status: %v", err))
134138
}
135139
}
136140
reconciledExt.Finalizers = finalizers
137141

138142
if updateFinalizers {
139-
if err := r.Client.Update(ctx, reconciledExt); err != nil {
143+
if err := r.Client.Update(delayedCtx, reconciledExt); err != nil {
140144
reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating finalizers: %v", err))
141145
}
142146
}

internal/util/context/context.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package context
2+
3+
import (
4+
"context"
5+
"time"
6+
)
7+
8+
type delayContext struct {
9+
valuesContext context.Context
10+
cancellationContext context.Context
11+
}
12+
13+
func (d *delayContext) Deadline() (time.Time, bool) {
14+
return d.cancellationContext.Deadline()
15+
}
16+
17+
func (d *delayContext) Done() <-chan struct{} {
18+
return d.cancellationContext.Done()
19+
}
20+
21+
func (d *delayContext) Err() error {
22+
return d.cancellationContext.Err()
23+
}
24+
25+
func (d *delayContext) Value(key interface{}) interface{} {
26+
return d.valuesContext.Value(key)
27+
}
28+
29+
func WithDelay(ctx context.Context, delay time.Duration) (context.Context, context.CancelFunc) {
30+
delayedCtx, delayedCancel := context.WithCancel(context.Background())
31+
done := make(chan struct{})
32+
go func() {
33+
select {
34+
case <-ctx.Done():
35+
// If there was no delay, cancel the delayed context immediately
36+
// after the parent context is canceled, then return
37+
if delay <= 0 {
38+
delayedCancel()
39+
return
40+
}
41+
42+
// Otherwise, start a timer for our delayed cancellation
43+
timer := time.NewTimer(delay)
44+
select {
45+
case <-timer.C:
46+
// if the timer expires first, cancel the delayed context.
47+
// timer expiration here means that the function using this
48+
// context was still unable to complete even after the delay
49+
delayedCancel()
50+
case <-done:
51+
// if done is closed first, cleanup the timer to avoid a
52+
// resource leak. This case means that the function using the
53+
// context was able to complete within the allotted delay
54+
// duration.
55+
timer.Stop()
56+
}
57+
case <-done:
58+
// In this case, the original parent context was never cancelled
59+
// so we never even started our delayed context cancellation timer.
60+
// Just return to avoid leaking the goroutine.
61+
}
62+
}()
63+
64+
cancelFunc := func() {
65+
close(done) // signal the goroutine to exit
66+
delayedCancel() // cancel the graceful context immediately without waiting for the timer to expire.
67+
}
68+
69+
return &delayContext{
70+
valuesContext: ctx,
71+
cancellationContext: delayedCtx,
72+
}, cancelFunc
73+
}

0 commit comments

Comments
 (0)