Skip to content

Commit 857dda1

Browse files
committed
add FillNarInfoCache to make HEAD requests in parallel
1 parent 518a872 commit 857dda1

File tree

10 files changed

+154
-32
lines changed

10 files changed

+154
-32
lines changed

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,17 @@ require (
4545
gopkg.in/yaml.v3 v3.0.1
4646
)
4747

48-
require github.com/joho/godotenv v1.5.1
48+
require (
49+
github.com/joho/godotenv v1.5.1
50+
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
51+
)
4952

5053
require (
5154
github.com/bahlo/generic-list-go v0.2.0 // indirect
5255
github.com/buger/jsonparser v1.1.1 // indirect
5356
github.com/kr/pretty v0.1.0 // indirect
5457
github.com/mailru/easyjson v0.7.7 // indirect
58+
golang.org/x/net v0.8.0 // indirect
5559
)
5660

5761
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
231231
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
232232
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
233233
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
234+
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
235+
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
234236
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
235237
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
236238
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

internal/devpkg/package.go

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package devpkg
55

66
import (
7+
"context"
78
"crypto/md5"
89
"encoding/hex"
910
"fmt"
@@ -27,6 +28,7 @@ import (
2728
"go.jetpack.io/devbox/internal/nix"
2829
"go.jetpack.io/devbox/internal/vercheck"
2930
"go.jetpack.io/devbox/plugins"
31+
"golang.org/x/sync/errgroup"
3032
)
3133

3234
// Package represents a "package" added to the devbox.json config.
@@ -182,6 +184,7 @@ func (p *Package) IsInstallable() bool {
182184
// Installable for this package. Installable is a nix concept defined here:
183185
// https://nixos.org/manual/nix/stable/command-ref/new-cli/nix.html#installables
184186
func (p *Package) Installable() (string, error) {
187+
185188
inCache, err := p.IsInBinaryCache()
186189
if err != nil {
187190
return "", err
@@ -435,7 +438,23 @@ func (p *Package) LegacyToVersioned() string {
435438
return p.Raw + "@latest"
436439
}
437440

438-
func (p *Package) EnsureNixpkgsPrefetched(w io.Writer) error {
441+
// ensureNixpkgsPrefetched will prefetch flake for the nixpkgs registry for the package.
442+
// This is an internal method, and should not be called directly.
443+
func EnsureNixpkgsPrefetched(ctx context.Context, w io.Writer, pkgs []*Package) error {
444+
if err := FillNarInfoCache(ctx, pkgs...); err != nil {
445+
return err
446+
}
447+
for _, input := range pkgs {
448+
if err := input.ensureNixpkgsPrefetched(w); err != nil {
449+
return err
450+
}
451+
}
452+
return nil
453+
}
454+
455+
// ensureNixpkgsPrefetched should be called via the public EnsureNixpkgsPrefetched.
456+
// See function comment there.
457+
func (p *Package) ensureNixpkgsPrefetched(w io.Writer) error {
439458

440459
inCache, err := p.IsInBinaryCache()
441460
if err != nil {
@@ -476,7 +495,7 @@ func (p *Package) HashFromNixPkgsURL() string {
476495
// It is used as FromStore in builtins.fetchClosure.
477496
const BinaryCache = "https://cache.nixos.org"
478497

479-
func (p *Package) IsInBinaryCache() (bool, error) {
498+
func (p *Package) isEligibleForBinaryCache() (bool, error) {
480499
if !featureflag.RemoveNixpkgs.Enabled() {
481500
return false, nil
482501
}
@@ -485,40 +504,84 @@ func (p *Package) IsInBinaryCache() (bool, error) {
485504
return false, nil
486505
}
487506

488-
entry, err := p.lockfile.Resolve(p.Raw)
507+
sysInfo, err := p.sysInfoIfExists()
489508
if err != nil {
490509
return false, err
510+
} else if sysInfo == nil {
511+
return false, nil
491512
}
492513

493-
userSystem, err := nix.System()
514+
version, err := nix.Version()
494515
if err != nil {
495516
return false, err
496517
}
497518

519+
// enable for nix >= 2.17
520+
return vercheck.SemverCompare(version, "2.17.0") >= 0, nil
521+
}
522+
523+
// sysInfoIfExists returns the system info for the user's system. If the sysInfo
524+
// is missing, then nil is returned
525+
func (p *Package) sysInfoIfExists() (*lock.SystemInfo, error) {
526+
527+
entry, err := p.lockfile.Resolve(p.Raw)
528+
if err != nil {
529+
return nil, err
530+
}
531+
532+
userSystem, err := nix.System()
533+
if err != nil {
534+
return nil, err
535+
}
536+
498537
if entry.Systems == nil {
499-
return false, nil
538+
return nil, nil
500539
}
501540

502541
// Check if the user's system's info is present in the lockfile
503542
sysInfo, ok := entry.Systems[userSystem]
504543
if !ok {
505-
return false, nil
544+
return nil, nil
506545
}
546+
return sysInfo, nil
547+
}
507548

508-
version, err := nix.Version()
509-
if err != nil {
510-
return false, err
511-
}
549+
// IsInBinaryCache returns true if the package is in the binary cache.
550+
// Callers should call FillNarInfoCache before calling this function.
551+
func (p *Package) IsInBinaryCache() (bool, error) {
512552

513-
// enable for nix >= 2.17
514-
if vercheck.SemverCompare(version, "2.17.0") < 0 {
553+
if eligible, err := p.isEligibleForBinaryCache(); err != nil {
554+
return false, err
555+
} else if !eligible {
515556
return false, nil
516557
}
517558

518559
// Check if the narinfo is present in the binary cache
519-
if exists, ok := isNarInfoInCache[p.Raw]; ok {
520-
fmt.Printf("narInfo cache hit: %v\n", exists)
521-
return exists, nil
560+
exists, ok := isNarInfoInCache[p.Raw]
561+
if !ok {
562+
return false, errors.Errorf("narInfo cache miss: %v. call XYZ before invoking IsInBinaryCache", p.Raw)
563+
}
564+
fmt.Printf("narInfo cache hit: %v\n", exists)
565+
return exists, nil
566+
}
567+
568+
// fillNarInfoCache fills the cache value for the narinfo of this package,
569+
// if it is eligible for the binary cache.
570+
func (p *Package) fillNarInfoCache() error {
571+
if eligible, err := p.isEligibleForBinaryCache(); err != nil {
572+
return err
573+
} else if !eligible {
574+
return nil
575+
}
576+
577+
sysInfo, err := p.sysInfoIfExists()
578+
if err != nil {
579+
return err
580+
} else if sysInfo == nil {
581+
return errors.New(
582+
"sysInfo is nil, but should not be because" +
583+
" the package is eligible for binary cache",
584+
)
522585
}
523586

524587
httpClient := &http.Client{
@@ -530,14 +593,14 @@ func (p *Package) IsInBinaryCache() (bool, error) {
530593
if err != nil {
531594
if os.IsTimeout(err) {
532595
isNarInfoInCache[p.Raw] = false // set this to avoid re-tries
533-
return false, nil
596+
return nil
534597
}
535-
return false, err
598+
return err
536599
}
537600
fmt.Printf("res status code %d\n", res.StatusCode)
538601

539602
isNarInfoInCache[p.Raw] = res.StatusCode == 200
540-
return isNarInfoInCache[p.Raw], nil
603+
return nil
541604
}
542605

543606
// InputAddressedPath is the input-addressed path in /nix/store
@@ -628,3 +691,24 @@ func newStorePathParts(path string) *storePathParts {
628691
func (p *storePathParts) Equal(other *storePathParts) bool {
629692
return p.hash == other.hash && p.name == other.name && p.version == other.version
630693
}
694+
695+
// FillNarInfoCache checks the remote binary cache for the narinfo of each
696+
// package in the list, and caches the result.
697+
// Callers of IsInBinaryCache must call this function first.
698+
func FillNarInfoCache(ctx context.Context, packages ...*Package) error {
699+
g, ctx := errgroup.WithContext(ctx)
700+
701+
for _, p := range packages {
702+
// If the package's NarInfo status is already known, skip it
703+
if _, ok := isNarInfoInCache[p.Raw]; ok {
704+
continue
705+
}
706+
g.Go(func() error {
707+
return p.fillNarInfoCache()
708+
})
709+
}
710+
if err := g.Wait(); err != nil {
711+
return err
712+
}
713+
return nil
714+
}

internal/impl/devbox.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ func (d *Devbox) StartProcessManager(
706706
processComposePath, err := utilityLookPath("process-compose")
707707
if err != nil {
708708
fmt.Fprintln(d.writer, "Installing process-compose. This may take a minute but will only happen once.")
709-
if err = d.addDevboxUtilityPackage("github:F1bonacc1/process-compose/v0.43.1"); err != nil {
709+
if err = d.addDevboxUtilityPackage(ctx, "github:F1bonacc1/process-compose/v0.43.1"); err != nil {
710710
return err
711711
}
712712

internal/impl/packages.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ func (d *Devbox) Add(ctx context.Context, platforms, excludePlatforms []string,
4242
// replace it.
4343
pkgs := devpkg.PackageFromStrings(lo.Uniq(pkgsNames), d.lockfile)
4444

45+
// Fill in narinfo cache for all packages, even if the package-names are bogus
46+
// (we'll just not use the result later)
47+
if err := devpkg.FillNarInfoCache(ctx, pkgs...); err != nil {
48+
return err
49+
}
50+
4551
// addedPackageNames keeps track of the possibly transformed (versioned)
4652
// names of added packages (even if they are already in config). We use this
4753
// to know the exact name to mark as allowed insecure later on.
@@ -295,10 +301,8 @@ func (d *Devbox) addPackagesToProfile(ctx context.Context, mode installMode) err
295301
// If packages are in profile but nixpkgs has been purged, the experience
296302
// will be poor when we try to run print-dev-env. So we ensure nixpkgs is
297303
// prefetched for all relevant packages (those not in binary cache).
298-
for _, input := range pkgs {
299-
if err := input.EnsureNixpkgsPrefetched(d.writer); err != nil {
300-
return err
301-
}
304+
if err := devpkg.EnsureNixpkgsPrefetched(ctx, d.writer, pkgs); err != nil {
305+
return err
302306
}
303307

304308
var msg string
@@ -321,7 +325,7 @@ func (d *Devbox) addPackagesToProfile(ctx context.Context, mode installMode) err
321325

322326
stepMsg := fmt.Sprintf("[%d/%d] %s", stepNum, total, pkg)
323327

324-
if err := nixprofile.ProfileInstall(&nixprofile.ProfileInstallArgs{
328+
if err := nixprofile.ProfileInstall(ctx, &nixprofile.ProfileInstallArgs{
325329
CustomStepMessage: stepMsg,
326330
Lockfile: d.lockfile,
327331
Package: pkg.Raw,
@@ -343,7 +347,12 @@ func (d *Devbox) removePackagesFromProfile(ctx context.Context, pkgs []string) e
343347
return err
344348
}
345349

346-
for _, input := range devpkg.PackageFromStrings(pkgs, d.lockfile) {
350+
packages := devpkg.PackageFromStrings(pkgs, d.lockfile)
351+
if err := devpkg.FillNarInfoCache(ctx, packages...); err != nil {
352+
return err
353+
}
354+
355+
for _, input := range packages {
347356
index, err := nixprofile.ProfileListIndex(&nixprofile.ProfileListIndexArgs{
348357
Lockfile: d.lockfile,
349358
Writer: d.writer,
@@ -414,6 +423,9 @@ func (d *Devbox) pendingPackagesForInstallation(ctx context.Context) ([]*devpkg.
414423
if err != nil {
415424
return nil, err
416425
}
426+
if err := devpkg.FillNarInfoCache(ctx, packages...); err != nil {
427+
return nil, err
428+
}
417429
for _, pkg := range packages {
418430
_, err := nixprofile.ProfileListIndex(&nixprofile.ProfileListIndexArgs{
419431
List: list,

internal/impl/update.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func (d *Devbox) Update(ctx context.Context, pkgs ...string) error {
4949
}
5050
}
5151

52+
if devpkg.FillNarInfoCache(ctx, pendingPackagesToUpdate...); err != nil {
53+
return err
54+
}
55+
5256
for _, pkg := range pendingPackagesToUpdate {
5357
if _, _, isVersioned := searcher.ParseVersionedPackage(pkg.Raw); !isVersioned {
5458
if err = d.attemptToUpgradeFlake(pkg); err != nil {

internal/impl/util.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package impl
55

66
import (
7+
"context"
78
"io/fs"
89
"os"
910
"path/filepath"
@@ -18,13 +19,13 @@ import (
1819
// It's used to install applications devbox might need, like process-compose
1920
// This is an alternative to a global install which would modify a user's
2021
// environment.
21-
func (d *Devbox) addDevboxUtilityPackage(pkg string) error {
22+
func (d *Devbox) addDevboxUtilityPackage(ctx context.Context, pkg string) error {
2223
profilePath, err := utilityNixProfilePath()
2324
if err != nil {
2425
return err
2526
}
2627

27-
return nixprofile.ProfileInstall(&nixprofile.ProfileInstallArgs{
28+
return nixprofile.ProfileInstall(ctx, &nixprofile.ProfileInstallArgs{
2829
Lockfile: d.lockfile,
2930
Package: pkg,
3031
ProfilePath: profilePath,

internal/nix/nixprofile/profile.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package nixprofile
55

66
import (
77
"bufio"
8+
"context"
89
"encoding/json"
910
"fmt"
1011
"io"
@@ -250,9 +251,15 @@ type ProfileInstallArgs struct {
250251
}
251252

252253
// ProfileInstall calls nix profile install with default profile
253-
func ProfileInstall(args *ProfileInstallArgs) error {
254+
func ProfileInstall(ctx context.Context, args *ProfileInstallArgs) error {
254255
input := devpkg.PackageFromString(args.Package, args.Lockfile)
255256

257+
// Fill in the narinfo cache for the input package. It's okay to call this for a single package
258+
// because installing is a slow operation anyway.
259+
if err := devpkg.FillNarInfoCache(ctx, input); err != nil {
260+
return err
261+
}
262+
256263
inCache, err := input.IsInBinaryCache()
257264
if err != nil {
258265
return err

internal/shellgen/flake_input.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,18 @@ func (f *flakeInput) BuildInputs() ([]string, error) {
7777
// i.e. have a commit hash and always resolve to the same package/version.
7878
// Note: inputs returned by this function include plugin packages. (php only for now)
7979
// It's not entirely clear we always want to add plugin packages to the top level
80-
func flakeInputs(ctx context.Context, packages []*devpkg.Package) []*flakeInput {
80+
func flakeInputs(ctx context.Context, packages []*devpkg.Package) ([]*flakeInput, error) {
8181
defer trace.StartRegion(ctx, "flakeInputs").End()
8282

8383
// Use the verbose name flakeInputs to distinguish from `inputs`
8484
// which refer to `nix.Input` in most of the codebase.
8585
flakeInputs := map[string]*flakeInput{}
8686

87+
// Fill the NarInfo Cache so we can check IsInBinaryCache() for each package, below.
88+
if err := devpkg.FillNarInfoCache(ctx, packages...); err != nil {
89+
return nil, err
90+
}
91+
8792
packages = lo.Filter(packages, func(item *devpkg.Package, _ int) bool {
8893
// Include packages (like local or remote flakes) that cannot be
8994
// fetched from a Binary Cache Store.
@@ -115,5 +120,5 @@ func flakeInputs(ctx context.Context, packages []*devpkg.Package) []*flakeInput
115120
}
116121
}
117122

118-
return goutil.PickByKeysSorted(flakeInputs, order)
123+
return goutil.PickByKeysSorted(flakeInputs, order), nil
119124
}

internal/shellgen/flake_plan.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ func newFlakePlan(ctx context.Context, devbox devboxer) (*flakePlan, error) {
3838
if err != nil {
3939
return nil, err
4040
}
41-
flakeInputs := flakeInputs(ctx, packages)
41+
flakeInputs, err := flakeInputs(ctx, packages)
42+
if err != nil {
43+
return nil, err
44+
}
4245
nixpkgsInfo := getNixpkgsInfo(devbox.Config().NixPkgsCommitHash())
4346

4447
// This is an optimization. Try to reuse the nixpkgs info from the flake

0 commit comments

Comments
 (0)