Take the reconcile snapshot lazily, right before the first mutation

Reconcilers deep copied the object they had just fetched on every single
reconcile, purely so a merge patch could be computed on the rare pass
that actually changes something. The copy is a full recursive walk and
allocation of the object, and the overwhelming majority of reconciles
throw it away untouched.

Introduce lazyCopy, which takes the snapshot on the first call to
Mutate and hands back the live object. Because Mutate is the only way
to reach the object, the snapshot cannot be taken after the mutation it
is supposed to be diffed against, which is the way this optimization is
usually gotten wrong.

Apply it to the four Reconcile entry points, and move the two
EphemeralRunnerSet status copies inside the branch that patches, so they
are only paid for when the status really changed.

While here, drop the short circuit in the EphemeralRunner finalizer
block: `addedFinalizers || AddFinalizer(...)` skipped adding the actions
finalizer whenever the first finalizer was added.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Nikola Jokic
2026-09-11 11:32:03 +02:00
co-authored by Copilot App
parent f7dd0d2d01
commit a7ce96b76c
6 changed files with 179 additions and 37 deletions
@@ -0,0 +1,63 @@
package actionsgithubcom
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func TestLazyCopyDoesNotCopyUntilMutated(t *testing.T) {
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod"}}
lazy := newLazyCopy(pod)
assert.False(t, lazy.Modified())
assert.Panics(t, func() { lazy.MergeFrom() })
}
func TestLazyCopySnapshotsBeforeTheFirstMutation(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Annotations: map[string]string{"key": "old"},
},
}
lazy := newLazyCopy(pod)
lazy.Mutate().Annotations["key"] = "new"
require.True(t, lazy.Modified())
data, err := lazy.MergeFrom().Data(pod)
require.NoError(t, err)
assert.JSONEq(t, `{"metadata":{"annotations":{"key":"new"}}}`, string(data))
}
func TestLazyCopySnapshotsOnlyOnce(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Annotations: map[string]string{"first": "old", "second": "old"},
},
}
lazy := newLazyCopy(pod)
lazy.Mutate().Annotations["first"] = "new"
// The second mutation must diff against the state before the first one,
// otherwise the earlier change is dropped from the patch.
lazy.Mutate().Annotations["second"] = "new"
data, err := lazy.MergeFrom().Data(pod)
require.NoError(t, err)
assert.JSONEq(t, `{"metadata":{"annotations":{"first":"new","second":"new"}}}`, string(data))
}
func TestLazyCopyMergeFromIsAMergePatch(t *testing.T) {
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod"}}
lazy := newLazyCopy(pod)
lazy.Mutate().Labels = map[string]string{"key": "value"}
assert.Equal(t, client.MergeFrom(pod).Type(), lazy.MergeFrom().Type())
}