perf(reconciler): batch workloads per tick, drop redundant image inspect
Load every workload once per tick into a map instead of a per-container GetWorkloadByID (N+1) in the upsert loop plus a second ListWorkloads in the plugin pass: one query per tick, zero GetWorkloadByID. The ListWorkloads error path returns before the missing-sweep so a failed load can't flip live container rows to 'missing'. image.Reconcile is now a no-op: the generic upsert+markMissing pass already syncs every labeled container's state from the single ListAllForReconciler (docker ps -a) snapshot earlier in the same tick, so the former per-container IsContainerRunning loop was N redundant Docker calls/tick. (Its no-op body sits in image.go, which landed with the preceding commit; the tests are here.) compose/static reconcile do non-redundant work and are intentionally untouched. Reviewed: go APPROVE.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -8,6 +9,20 @@ import (
|
||||
"github.com/alexei/tinyforge/internal/workload/plugin"
|
||||
)
|
||||
|
||||
// TestReconcileIsNoOp locks Fix B: image.Reconcile must do nothing and touch
|
||||
// neither the Store nor Docker (the generic reconciler pass syncs state). We
|
||||
// pass a zero-value plugin.Deps whose Store and Docker are nil — the old
|
||||
// implementation called deps.Store.ListContainersByWorkload then
|
||||
// deps.Docker.IsContainerRunning, both of which would nil-panic. Returning nil
|
||||
// without panicking proves it dereferences neither.
|
||||
func TestReconcileIsNoOp(t *testing.T) {
|
||||
src := &source{}
|
||||
w := plugin.Workload{ID: "wl-1", Name: "app", SourceKind: "image"}
|
||||
if err := src.Reconcile(context.Background(), plugin.Deps{}, w); err != nil {
|
||||
t.Fatalf("Reconcile should be a no-op returning nil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildContainerName(t *testing.T) {
|
||||
ts := time.Unix(1700000000, 0)
|
||||
name := buildContainerName("My App", "abcd1234-5678-1234-abcd-deadbeef0000", "v1.2.3", ts)
|
||||
@@ -56,10 +71,10 @@ func TestFaceEnabled(t *testing.T) {
|
||||
|
||||
func TestFqdnFor(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
face plugin.PublicFace
|
||||
defDom string
|
||||
want string
|
||||
name string
|
||||
face plugin.PublicFace
|
||||
defDom string
|
||||
want string
|
||||
}{
|
||||
{"subdomain + face domain", plugin.PublicFace{Subdomain: "api", Domain: "example.com"}, "default.io", "api.example.com"},
|
||||
{"subdomain inherits default", plugin.PublicFace{Subdomain: "api"}, "default.io", "api.default.io"},
|
||||
@@ -78,8 +93,8 @@ func TestFqdnFor(t *testing.T) {
|
||||
func TestPrimaryFace(t *testing.T) {
|
||||
t.Run("returns first enabled", func(t *testing.T) {
|
||||
faces := []plugin.PublicFace{
|
||||
{}, // disabled
|
||||
{Subdomain: "api"}, // first enabled
|
||||
{}, // disabled
|
||||
{Subdomain: "api"}, // first enabled
|
||||
{Domain: "second.example.com"},
|
||||
}
|
||||
got := primaryFace(faces)
|
||||
|
||||
Reference in New Issue
Block a user