Predicate functions with side-effects versus pure functions
A colleague of mine wrote some controller code similar to the following:
// New creates a new controller that updates metrics for Foo objects.
func New(mgr manager.Manager, config Config) (controller.Controller, error) {
reconciler := &reconciler{}
c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: reconciler})
if err != nil {
return nil, err
}
if err := c.Watch(
&source.Kind{Type: &foov1.Foo{}},
handler.EnqueueRequestsFromMapFunc(func(client.Object) []reconcile.Request {
return nil
}),
predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
if someCondition {
someMetric.Inc()
}
return false
},
DeleteFunc: func(e event.DeleteEvent) bool {
if someCondition {
someMetric.Dec()
}
return false
},
UpdateFunc: func(e event.UpdateEvent) bool {
if someCondition {
someMetric.Inc()
}
if someOtherCondition {
someMetric.Dec()
}
return false
},
GenericFunc: func(e event.GenericEvent) bool {
return false
},
},
); err != nil {
return nil, err
}
return c, nil
}
The intention is to perform some action (updating metrics) in the create/update/delete predicates on a watch, without enqueuing a reconciliation request. (This is convenient because the predicate function has the event type and other information, and presumably enqueueing a reconciliation request would incur more overhead than doing the work in the predicate.)
However, this raises the question: Does controller-runtime make any guarantees that exactly one predicate function will be called, and it will be called exactly once, for a given event, or is it assumed that predicates are pure functions? Are there caveats with defining and using predicates that have side-effects?
Would folks be open to updating the godoc for predicates to document any such guarantees, or the lack thereof, and any known caveats?