Skip to content

Commit 6aec426

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 fcbc469 commit 6aec426

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
@@ -19,6 +19,7 @@ import (
1919
"github.com/moby/buildkit/util/bklog"
2020
"github.com/pkg/errors"
2121
"github.com/project-dalec/dalec"
22+
"github.com/project-dalec/dalec/internal/gwutil"
2223
"github.com/sirupsen/logrus"
2324
"golang.org/x/exp/maps"
2425
)
@@ -385,6 +386,10 @@ func topLevelKey(routePath string) string {
385386
// context. Build args are substituted with WithAllowAnyArg so that
386387
// unresolved args don't cause errors during route setup.
387388
func LoadSpecFromClient(ctx context.Context, client gwclient.Client) (*dalec.Spec, error) {
389+
if loader, ok := client.(gwutil.SpecLoader); ok {
390+
return loader.LoadSpec(ctx)
391+
}
392+
388393
dc, err := dockerui.NewClient(client)
389394
if err != nil {
390395
return nil, err

internal/frontendapi/router.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@ package frontendapi
33
import (
44
"context"
55
"fmt"
6+
"sync"
67

78
"github.com/containerd/plugin"
89
gwclient "github.com/moby/buildkit/frontend/gateway/client"
10+
"github.com/project-dalec/dalec"
911
"github.com/project-dalec/dalec/frontend"
1012
"github.com/project-dalec/dalec/frontend/debug"
13+
"github.com/project-dalec/dalec/internal/gwutil"
1114
"github.com/project-dalec/dalec/internal/plugins"
1215
_ "github.com/project-dalec/dalec/targets/plugin"
1316
)
1417

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

1923
// Register debug routes.
@@ -67,3 +71,29 @@ func loadRouteProviders(ctx context.Context, client gwclient.Client, r *frontend
6771

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

0 commit comments

Comments
 (0)