Skip to content

Commit 5de246b

Browse files
authored
Recover panic for controller reconcile and webhook handler (#1627)
Signed-off-by: FillZpp <FillZpp.pub@gmail.com>
1 parent ca01f67 commit 5de246b

File tree

3 files changed

+52
-2
lines changed

3 files changed

+52
-2
lines changed

pkg/controller/controller.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ type Options struct {
5252
// CacheSyncTimeout refers to the time limit set to wait for syncing caches.
5353
// Defaults to 2 minutes if not set.
5454
CacheSyncTimeout time.Duration
55+
56+
// RecoverPanic indicates whether the panic caused by reconcile should be recovered.
57+
RecoverPanic bool
5558
}
5659

5760
// Controller implements a Kubernetes API. A Controller manages a work queue fed reconcile.Requests
@@ -133,5 +136,6 @@ func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller
133136
SetFields: mgr.SetFields,
134137
Name: name,
135138
Log: options.Log.WithName("controller").WithName(name),
139+
RecoverPanic: options.RecoverPanic,
136140
}, nil
137141
}

pkg/internal/controller/controller.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ type Controller struct {
8585

8686
// Log is used to log messages to users during reconciliation, or for example when a watch is started.
8787
Log logr.Logger
88+
89+
// RecoverPanic indicates whether the panic caused by reconcile should be recovered.
90+
RecoverPanic bool
8891
}
8992

9093
// watchDescription contains all the information necessary to start a watch.
@@ -95,7 +98,17 @@ type watchDescription struct {
9598
}
9699

97100
// Reconcile implements reconcile.Reconciler.
98-
func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
101+
func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (_ reconcile.Result, err error) {
102+
if c.RecoverPanic {
103+
defer func() {
104+
if r := recover(); r != nil {
105+
for _, fn := range utilruntime.PanicHandlers {
106+
fn(r)
107+
}
108+
err = fmt.Errorf("panic: %v [recovered]", r)
109+
}
110+
}()
111+
}
99112
log := c.Log.WithValues("name", req.Name, "namespace", req.Namespace)
100113
ctx = logf.IntoContext(ctx, log)
101114
return c.Do.Reconcile(ctx, req)
@@ -295,7 +308,7 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
295308

296309
// RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the
297310
// resource to be synced.
298-
result, err := c.Do.Reconcile(ctx, req)
311+
result, err := c.Reconcile(ctx, req)
299312
switch {
300313
case err != nil:
301314
c.Queue.AddRateLimited(req)

pkg/internal/controller/controller_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,39 @@ var _ = Describe("controller", func() {
8888
Expect(err).NotTo(HaveOccurred())
8989
Expect(result).To(Equal(reconcile.Result{Requeue: true}))
9090
})
91+
92+
It("should not recover panic if RecoverPanic is false by default", func() {
93+
ctx, cancel := context.WithCancel(context.Background())
94+
defer cancel()
95+
96+
defer func() {
97+
Expect(recover()).ShouldNot(BeNil())
98+
}()
99+
ctrl.Do = reconcile.Func(func(context.Context, reconcile.Request) (reconcile.Result, error) {
100+
var res *reconcile.Result
101+
return *res, nil
102+
})
103+
_, _ = ctrl.Reconcile(ctx,
104+
reconcile.Request{NamespacedName: types.NamespacedName{Namespace: "foo", Name: "bar"}})
105+
})
106+
107+
It("should recover panic if RecoverPanic is true", func() {
108+
ctx, cancel := context.WithCancel(context.Background())
109+
defer cancel()
110+
111+
defer func() {
112+
Expect(recover()).To(BeNil())
113+
}()
114+
ctrl.RecoverPanic = true
115+
ctrl.Do = reconcile.Func(func(context.Context, reconcile.Request) (reconcile.Result, error) {
116+
var res *reconcile.Result
117+
return *res, nil
118+
})
119+
_, err := ctrl.Reconcile(ctx,
120+
reconcile.Request{NamespacedName: types.NamespacedName{Namespace: "foo", Name: "bar"}})
121+
Expect(err).To(HaveOccurred())
122+
Expect(err.Error()).To(ContainSubstring("[recovered]"))
123+
})
91124
})
92125

93126
Describe("Start", func() {

0 commit comments

Comments
 (0)