mirror of https://github.com/docker/buildx.git
Merge pull request #2615 from tonistiigi/bake-shared-transfer-sessions
bake: use shared session for local sources for multiple targets
This commit is contained in:
commit
5a50d13641
158
build/build.go
158
build/build.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -34,6 +35,7 @@ import (
|
||||||
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
"github.com/moby/buildkit/identity"
|
"github.com/moby/buildkit/identity"
|
||||||
"github.com/moby/buildkit/session"
|
"github.com/moby/buildkit/session"
|
||||||
|
"github.com/moby/buildkit/session/filesync"
|
||||||
"github.com/moby/buildkit/solver/errdefs"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
spb "github.com/moby/buildkit/sourcepolicy/pb"
|
spb "github.com/moby/buildkit/sourcepolicy/pb"
|
||||||
|
@ -44,6 +46,8 @@ import (
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/tonistiigi/fsutil"
|
||||||
|
fstypes "github.com/tonistiigi/fsutil/types"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
@ -291,6 +295,12 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sharedSessions, err := detectSharedMounts(ctx, reqForNodes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sharedSessionsWG := map[string]*sync.WaitGroup{}
|
||||||
|
|
||||||
resp = map[string]*client.SolveResponse{}
|
resp = map[string]*client.SolveResponse{}
|
||||||
var respMu sync.Mutex
|
var respMu sync.Mutex
|
||||||
results := waitmap.New()
|
results := waitmap.New()
|
||||||
|
@ -355,7 +365,37 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var done func()
|
||||||
|
if sessions, ok := sharedSessions[node.Name]; ok {
|
||||||
|
wg, ok := sharedSessionsWG[node.Name]
|
||||||
|
if ok {
|
||||||
|
wg.Add(1)
|
||||||
|
} else {
|
||||||
|
wg = &sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
sharedSessionsWG[node.Name] = wg
|
||||||
|
for _, s := range sessions {
|
||||||
|
s := s
|
||||||
|
eg.Go(func() error {
|
||||||
|
return s.Run(baseCtx, c.Dialer())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
for _, s := range sessions {
|
||||||
|
s.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
done = wg.Done
|
||||||
|
}
|
||||||
|
|
||||||
eg2.Go(func() error {
|
eg2.Go(func() error {
|
||||||
|
if done != nil {
|
||||||
|
defer done()
|
||||||
|
}
|
||||||
|
|
||||||
pw = progress.ResetTime(pw)
|
pw = progress.ResetTime(pw)
|
||||||
|
|
||||||
if err := waitContextDeps(ctx, dp.driverIndex, results, so); err != nil {
|
if err := waitContextDeps(ctx, dp.driverIndex, results, so); err != nil {
|
||||||
|
@ -786,6 +826,124 @@ func resultKey(index int, name string) string {
|
||||||
return fmt.Sprintf("%d-%s", index, name)
|
return fmt.Sprintf("%d-%s", index, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectSharedMounts looks for same local mounts used by multiple requests to the same node
|
||||||
|
// and creates a separate session that will be used by all detected requests.
|
||||||
|
func detectSharedMounts(ctx context.Context, reqs map[string][]*reqForNode) (_ map[string][]*session.Session, err error) {
|
||||||
|
type fsTracker struct {
|
||||||
|
fs fsutil.FS
|
||||||
|
so []*client.SolveOpt
|
||||||
|
}
|
||||||
|
type fsKey struct {
|
||||||
|
name string
|
||||||
|
dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]map[fsKey]*fsTracker{}
|
||||||
|
for _, reqs := range reqs {
|
||||||
|
for _, req := range reqs {
|
||||||
|
nodeName := req.resolvedNode.Node().Name
|
||||||
|
if _, ok := m[nodeName]; !ok {
|
||||||
|
m[nodeName] = map[fsKey]*fsTracker{}
|
||||||
|
}
|
||||||
|
fsMap := m[nodeName]
|
||||||
|
for name, m := range req.so.LocalMounts {
|
||||||
|
fs, ok := m.(*fs)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := fsKey{name: name, dir: fs.dir}
|
||||||
|
if _, ok := fsMap[key]; !ok {
|
||||||
|
fsMap[key] = &fsTracker{fs: fs.FS}
|
||||||
|
}
|
||||||
|
fsMap[key].so = append(fsMap[key].so, req.so)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sharedSession struct {
|
||||||
|
*session.Session
|
||||||
|
fsMap map[string]fsutil.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionMap := map[string][]*sharedSession{}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
for _, sessions := range sessionMap {
|
||||||
|
for _, s := range sessions {
|
||||||
|
s.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for node, fsMap := range m {
|
||||||
|
for key, fs := range fsMap {
|
||||||
|
if len(fs.so) <= 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions := sessionMap[node]
|
||||||
|
|
||||||
|
// find session that doesn't have the fs name reserved
|
||||||
|
idx := slices.IndexFunc(sessions, func(s *sharedSession) bool {
|
||||||
|
_, ok := s.fsMap[key.name]
|
||||||
|
return !ok
|
||||||
|
})
|
||||||
|
|
||||||
|
var ss *sharedSession
|
||||||
|
if idx == -1 {
|
||||||
|
s, err := session.NewSession(ctx, "", fs.so[0].SharedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ss = &sharedSession{Session: s, fsMap: map[string]fsutil.FS{}}
|
||||||
|
sessions = append(sessions, ss)
|
||||||
|
sessionMap[node] = sessions
|
||||||
|
} else {
|
||||||
|
ss = sessions[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.fsMap[key.name] = fs.fs
|
||||||
|
for _, so := range fs.so {
|
||||||
|
if so.FrontendAttrs == nil {
|
||||||
|
so.FrontendAttrs = map[string]string{}
|
||||||
|
}
|
||||||
|
so.FrontendAttrs["local-sessionid:"+key.name] = ss.ID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetUIDAndGID := func(p string, st *fstypes.Stat) fsutil.MapResult {
|
||||||
|
st.Uid = 0
|
||||||
|
st.Gid = 0
|
||||||
|
return fsutil.MapResultKeep
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert back to regular sessions
|
||||||
|
sessions := map[string][]*session.Session{}
|
||||||
|
for n, ss := range sessionMap {
|
||||||
|
arr := make([]*session.Session, 0, len(ss))
|
||||||
|
for _, s := range ss {
|
||||||
|
arr = append(arr, s.Session)
|
||||||
|
|
||||||
|
src := make(filesync.StaticDirSource, len(s.fsMap))
|
||||||
|
for name, fs := range s.fsMap {
|
||||||
|
fs, err := fsutil.NewFilterFS(fs, &fsutil.FilterOpt{
|
||||||
|
Map: resetUIDAndGID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
src[name] = fs
|
||||||
|
}
|
||||||
|
s.Allow(filesync.NewFSSyncProvider(src))
|
||||||
|
}
|
||||||
|
sessions[n] = arr
|
||||||
|
}
|
||||||
|
return sessions, nil
|
||||||
|
}
|
||||||
|
|
||||||
// calculateChildTargets returns all the targets that depend on current target for reverse index
|
// calculateChildTargets returns all the targets that depend on current target for reverse index
|
||||||
func calculateChildTargets(reqs map[string][]*reqForNode, opt map[string]Options) map[string][]string {
|
func calculateChildTargets(reqs map[string][]*reqForNode, opt map[string]Options) map[string][]string {
|
||||||
out := make(map[string][]string)
|
out := make(map[string][]string)
|
||||||
|
|
Loading…
Reference in New Issue