Skip to content

Commit cdacb62

Browse files
committed
add FillNarInfoCache to make HEAD requests in parallel
1 parent 510fb62 commit cdacb62

File tree

10 files changed

+156
-32
lines changed

10 files changed

+156
-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: 104 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
@@ -431,7 +434,23 @@ func (p *Package) LegacyToVersioned() string {
431434
return p.Raw + "@latest"
432435
}
433436

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

436455
inCache, err := p.IsInBinaryCache()
437456
if err != nil {
@@ -472,7 +491,7 @@ func (p *Package) HashFromNixPkgsURL() string {
472491
// It is used as FromStore in builtins.fetchClosure.
473492
const BinaryCache = "https://cache.nixos.org"
474493

475-
func (p *Package) IsInBinaryCache() (bool, error) {
494+
func (p *Package) isEligibleForBinaryCache() (bool, error) {
476495
if !featureflag.RemoveNixpkgs.Enabled() {
477496
return false, nil
478497
}
@@ -481,35 +500,81 @@ func (p *Package) IsInBinaryCache() (bool, error) {
481500
return false, nil
482501
}
483502

484-
entry, err := p.lockfile.Resolve(p.Raw)
503+
sysInfo, err := p.sysInfoIfExists()
485504
if err != nil {
486505
return false, err
506+
} else if sysInfo == nil {
507+
return false, nil
487508
}
488509

510+
version, err := nix.Version()
511+
if err != nil {
512+
return false, err
513+
}
514+
515+
// enable for nix >= 2.17
516+
return vercheck.SemverCompare(version, "2.17.0") >= 0, nil
517+
}
518+
519+
// sysInfoIfExists returns the system info for the user's system. If the sysInfo
520+
// is missing, then nil is returned
521+
func (p *Package) sysInfoIfExists() (*lock.SystemInfo, error) {
522+
523+
entry, err := p.lockfile.Resolve(p.Raw)
524+
if err != nil {
525+
return nil, err
526+
}
527+
528+
userSystem := nix.System()
529+
489530
if entry.Systems == nil {
490-
return false, nil
531+
return nil, nil
491532
}
492533

493534
// Check if the user's system's info is present in the lockfile
494-
sysInfo, ok := entry.Systems[nix.System()]
535+
sysInfo, ok := entry.Systems[userSystem]
495536
if !ok {
496-
return false, nil
537+
return nil, nil
497538
}
539+
return sysInfo, nil
540+
}
498541

499-
version, err := nix.Version()
500-
if err != nil {
501-
return false, err
502-
}
542+
// IsInBinaryCache returns true if the package is in the binary cache.
543+
// Callers should call FillNarInfoCache before calling this function.
544+
func (p *Package) IsInBinaryCache() (bool, error) {
503545

504-
// enable for nix >= 2.17
505-
if vercheck.SemverCompare(version, "2.17.0") < 0 {
546+
if eligible, err := p.isEligibleForBinaryCache(); err != nil {
547+
return false, err
548+
} else if !eligible {
506549
return false, nil
507550
}
508551

509552
// Check if the narinfo is present in the binary cache
510-
if exists, ok := isNarInfoInCache[p.Raw]; ok {
511-
fmt.Printf("narInfo cache hit: %v\n", exists)
512-
return exists, nil
553+
exists, ok := isNarInfoInCache[p.Raw]
554+
if !ok {
555+
return false, errors.Errorf("narInfo cache miss: %v. call XYZ before invoking IsInBinaryCache", p.Raw)
556+
}
557+
fmt.Printf("narInfo cache hit: %v\n", exists)
558+
return exists, nil
559+
}
560+
561+
// fillNarInfoCache fills the cache value for the narinfo of this package,
562+
// if it is eligible for the binary cache.
563+
func (p *Package) fillNarInfoCache() error {
564+
if eligible, err := p.isEligibleForBinaryCache(); err != nil {
565+
return err
566+
} else if !eligible {
567+
return nil
568+
}
569+
570+
sysInfo, err := p.sysInfoIfExists()
571+
if err != nil {
572+
return err
573+
} else if sysInfo == nil {
574+
return errors.New(
575+
"sysInfo is nil, but should not be because" +
576+
" the package is eligible for binary cache",
577+
)
513578
}
514579

515580
httpClient := &http.Client{
@@ -521,14 +586,14 @@ func (p *Package) IsInBinaryCache() (bool, error) {
521586
if err != nil {
522587
if os.IsTimeout(err) {
523588
isNarInfoInCache[p.Raw] = false // set this to avoid re-tries
524-
return false, nil
589+
return nil
525590
}
526-
return false, err
591+
return err
527592
}
528593
fmt.Printf("res status code %d\n", res.StatusCode)
529594

530595
isNarInfoInCache[p.Raw] = res.StatusCode == 200
531-
return isNarInfoInCache[p.Raw], nil
596+
return nil
532597
}
533598

534599
// InputAddressedPath is the input-addressed path in /nix/store
@@ -614,3 +679,24 @@ func newStorePathParts(path string) *storePathParts {
614679
func (p *storePathParts) Equal(other *storePathParts) bool {
615680
return p.hash == other.hash && p.name == other.name && p.version == other.version
616681
}
682+
683+
// FillNarInfoCache checks the remote binary cache for the narinfo of each
684+
// package in the list, and caches the result.
685+
// Callers of IsInBinaryCache must call this function first.
686+
func FillNarInfoCache(ctx context.Context, packages ...*Package) error {
687+
g, ctx := errgroup.WithContext(ctx)
688+
689+
for _, p := range packages {
690+
// If the package's NarInfo status is already known, skip it
691+
if _, ok := isNarInfoInCache[p.Raw]; ok {
692+
continue
693+
}
694+
g.Go(func() error {
695+
return p.fillNarInfoCache()
696+
})
697+
}
698+
if err := g.Wait(); err != nil {
699+
return err
700+
}
701+
return nil
702+
}

internal/impl/devbox.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ func (d *Devbox) StartProcessManager(
723723
processComposePath, err := utilityLookPath("process-compose")
724724
if err != nil {
725725
fmt.Fprintln(d.writer, "Installing process-compose. This may take a minute but will only happen once.")
726-
if err = d.addDevboxUtilityPackage("github:F1bonacc1/process-compose/v0.43.1"); err != nil {
726+
if err = d.addDevboxUtilityPackage(ctx, "github:F1bonacc1/process-compose/v0.43.1"); err != nil {
727727
return err
728728
}
729729

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)