Skip to content

Commit 2da7548

Browse files
committed
frontend: cache spec during route registration
Add a cachedSpecClient wrapper that implements gwutil.SpecLoader and uses sync.Once to cache the spec loaded by LoadSpecFromClient. This eliminates redundant spec loads during route registration where each distro's Routes() independently calls LoadSpecFromClient (14+ times per build request). LoadSpecFromClient now checks for the SpecLoader interface before falling back to the full dockerui load path. NewRouter wraps the client internally so the cache is scoped to registration only. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
1 parent 211244b commit 2da7548

File tree

2 files changed

+35
-0
lines changed

2 files changed

+35
-0
lines changed

frontend/router.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/moby/buildkit/util/bklog"
1919
"github.com/pkg/errors"
2020
"github.com/project-dalec/dalec"
21+
"github.com/project-dalec/dalec/internal/gwutil"
2122
"github.com/sirupsen/logrus"
2223
"golang.org/x/exp/maps"
2324
)
@@ -327,6 +328,10 @@ func topLevelKey(routePath string) string {
327328
// context. Build args are substituted with WithAllowAnyArg so that
328329
// unresolved args don't cause errors during route setup.
329330
func LoadSpecFromClient(ctx context.Context, client gwclient.Client) (*dalec.Spec, error) {
331+
if loader, ok := client.(gwutil.SpecLoader); ok {
332+
return loader.LoadSpec(ctx)
333+
}
334+
330335
dc, err := dockerui.NewClient(client)
331336
if err != nil {
332337
return nil, err

internal/frontendapi/router.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@ package frontendapi
22

33
import (
44
"context"
5+
"sync"
56

67
"github.com/containerd/plugin"
78
gwclient "github.com/moby/buildkit/frontend/gateway/client"
9+
"github.com/project-dalec/dalec"
810
"github.com/project-dalec/dalec/frontend"
911
"github.com/project-dalec/dalec/frontend/debug"
12+
"github.com/project-dalec/dalec/internal/gwutil"
1013
"github.com/project-dalec/dalec/internal/plugins"
1114
_ "github.com/project-dalec/dalec/targets/plugin"
1215
)
1316

1417
// NewRouter creates a flat Router with all routes registered eagerly.
1518
func NewRouter(ctx context.Context, client gwclient.Client) (*frontend.Router, error) {
19+
client = newCachedSpecClient(client)
1620
r := &frontend.Router{}
1721

1822
// Register debug routes.
@@ -60,3 +64,29 @@ func loadRouteProviders(ctx context.Context, client gwclient.Client, r *frontend
6064

6165
return nil
6266
}
67+
68+
// cachedSpecClient wraps a gwclient.Client and caches the spec loaded by
69+
// LoadSpecFromClient so that it is only loaded once.
70+
type cachedSpecClient struct {
71+
gwclient.Client
72+
73+
loadOnce sync.Once
74+
spec *dalec.Spec
75+
err error
76+
}
77+
78+
// Compile-time check that cachedSpecClient implements gwutil.SpecLoader.
79+
var _ gwutil.SpecLoader = (*cachedSpecClient)(nil)
80+
81+
func newCachedSpecClient(client gwclient.Client) gwclient.Client {
82+
return gwutil.WithCurrentFrontend(client, &cachedSpecClient{
83+
Client: client,
84+
})
85+
}
86+
87+
func (c *cachedSpecClient) LoadSpec(ctx context.Context) (*dalec.Spec, error) {
88+
c.loadOnce.Do(func() {
89+
c.spec, c.err = frontend.LoadSpecFromClient(ctx, c.Client)
90+
})
91+
return c.spec, c.err
92+
}

0 commit comments

Comments
 (0)