Skip to content

Commit 019abe6

Browse files
committed
[nar info cache] Only fillNarInfoCache in perf-sensitive code path
1 parent 42d2f71 commit 019abe6

File tree

9 files changed

+78
-71
lines changed

9 files changed

+78
-71
lines changed

internal/boxcli/featureflag/remove_nixpkgs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ package featureflag
44
// It leverages the search index to directly map <package>@<version> to
55
// the /nix/store/<hash>-<package>-<version> that can be fetched from
66
// cache.nixpkgs.org.
7-
var RemoveNixpkgs = disable("REMOVE_NIXPKGS")
7+
var RemoveNixpkgs = enable("REMOVE_NIXPKGS")

internal/devpkg/narinfo_cache.go

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ var isNarInfoInCache = struct {
3636
}
3737

3838
// IsInBinaryCache returns true if the package is in the binary cache.
39-
// ALERT: Callers must call FillNarInfoCache before calling this function.
39+
// ALERT: Callers in a perf-sensitive code path should call FillNarInfoCache
40+
// before calling this function.
4041
func (p *Package) IsInBinaryCache() (bool, error) {
4142

4243
if eligible, err := p.isEligibleForBinaryCache(); err != nil {
@@ -47,41 +48,61 @@ func (p *Package) IsInBinaryCache() (bool, error) {
4748

4849
// Check if the narinfo is present in the binary cache
4950
isNarInfoInCache.lock.RLock()
50-
exists, ok := isNarInfoInCache.status[p.Raw]
51+
status, statusExists := isNarInfoInCache.status[p.Raw]
5152
isNarInfoInCache.lock.RUnlock()
52-
if !ok {
53-
return false, errors.Errorf(
54-
"narInfo cache miss: %v. Call FillNarInfoCache before invoking IsInBinaryCache",
55-
p.Raw,
56-
)
53+
if !statusExists {
54+
// Fallback to synchronously filling the nar info cache
55+
if err := p.fillNarInfoCache(); err != nil {
56+
return false, err
57+
}
58+
59+
// Check again
60+
isNarInfoInCache.lock.RLock()
61+
status, statusExists = isNarInfoInCache.status[p.Raw]
62+
isNarInfoInCache.lock.RUnlock()
63+
if !statusExists {
64+
return false, errors.Errorf(
65+
"narInfo cache miss: %v. Should be filled by now",
66+
p.Raw,
67+
)
68+
}
5769
}
58-
return exists, nil
70+
return status, nil
5971
}
6072

6173
// FillNarInfoCache checks the remote binary cache for the narinfo of each
6274
// package in the list, and caches the result.
63-
// Callers of IsInBinaryCache must call this function first.
75+
// Callers of IsInBinaryCache may call this function first as a perf-optimization.
6476
func FillNarInfoCache(ctx context.Context, packages ...*Package) error {
6577
if !featureflag.RemoveNixpkgs.Enabled() {
6678
return nil
6779
}
6880

81+
eligiblePackages := []*Package{}
82+
for _, p := range packages {
83+
// NOTE: isElgibleForBinaryCache also ensures the package is
84+
// resolved in the lockfile, which must be done before the concurrent
85+
// section in this function below.
86+
isEligible, err := p.isEligibleForBinaryCache()
87+
// If the package is not eligible or there is an error in determining that, then skip it.
88+
if isEligible && err == nil {
89+
eligiblePackages = append(eligiblePackages, p)
90+
}
91+
}
92+
if len(eligiblePackages) == 0 {
93+
return nil
94+
}
95+
6996
// Pre-compute values read in fillNarInfoCache
7097
// so they can be read from multiple go-routines without locks
7198
_, err := nix.Version()
7299
if err != nil {
73100
return err
74101
}
75102
_ = nix.System()
76-
for _, p := range packages {
77-
_, err := p.lockfile.Resolve(p.Raw)
78-
if err != nil {
79-
return err
80-
}
81-
}
82103

83104
group, _ := errgroup.WithContext(ctx)
84-
for _, p := range packages {
105+
for _, p := range eligiblePackages {
85106
// If the package's NarInfo status is already known, skip it
86107
isNarInfoInCache.lock.RLock()
87108
_, ok := isNarInfoInCache.status[p.Raw]
@@ -105,15 +126,11 @@ func FillNarInfoCache(ctx context.Context, packages ...*Package) error {
105126
}
106127

107128
// fillNarInfoCache fills the cache value for the narinfo of this package,
108-
// if it is eligible for the binary cache.
129+
// assuming it is eligible for the binary cache. Callers are responsible
130+
// for checking isEligibleForBinaryCache before calling this function.
131+
//
109132
// NOTE: this must be concurrency safe.
110133
func (p *Package) fillNarInfoCache() error {
111-
if eligible, err := p.isEligibleForBinaryCache(); err != nil {
112-
return err
113-
} else if !eligible {
114-
return nil
115-
}
116-
117134
sysInfo, err := p.sysInfoIfExists()
118135
if err != nil {
119136
return err
@@ -146,6 +163,8 @@ func (p *Package) fillNarInfoCache() error {
146163
return nil
147164
}
148165

166+
// isEligibleForBinaryCache returns true if the package is eligible for the binary cache.
167+
// NOTE: package must be resolved.
149168
func (p *Package) isEligibleForBinaryCache() (bool, error) {
150169
sysInfo, err := p.sysInfoIfExists()
151170
if err != nil {

internal/devpkg/package.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -427,9 +427,6 @@ func (p *Package) LegacyToVersioned() string {
427427
// EnsureNixpkgsPrefetched will prefetch flake for the nixpkgs registry for the package.
428428
// This is an internal method, and should not be called directly.
429429
func EnsureNixpkgsPrefetched(ctx context.Context, w io.Writer, pkgs []*Package) error {
430-
if err := FillNarInfoCache(ctx, pkgs...); err != nil {
431-
return err
432-
}
433430
for _, input := range pkgs {
434431
if err := input.ensureNixpkgsPrefetched(w); err != nil {
435432
return err

internal/impl/packages.go

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@ 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-
5145
// addedPackageNames keeps track of the possibly transformed (versioned)
5246
// names of added packages (even if they are already in config). We use this
5347
// to know the exact name to mark as allowed insecure later on.
@@ -65,15 +59,6 @@ func (d *Devbox) Add(ctx context.Context, platforms, excludePlatforms []string,
6559
}
6660
}
6761

68-
// Fill in the narinfo cache for versioned newPackages as well
69-
versionedPackages := map[string]*devpkg.Package{}
70-
for _, pkg := range newPackages {
71-
versionedPackages[pkg.Versioned()] = devpkg.PackageFromString(pkg.Versioned(), d.lockfile)
72-
}
73-
if err := devpkg.FillNarInfoCache(ctx, lo.Values(versionedPackages)...); err != nil {
74-
return err
75-
}
76-
7762
for _, pkg := range newPackages {
7863

7964
// On the other hand, if there's a package with same canonical name, replace
@@ -89,7 +74,7 @@ func (d *Devbox) Add(ctx context.Context, platforms, excludePlatforms []string,
8974

9075
// validate that the versioned package exists in the search endpoint.
9176
// if not, fallback to legacy vanilla nix.
92-
versionedPkg := versionedPackages[pkg.Versioned()]
77+
versionedPkg := devpkg.PackageFromString(pkg.Versioned(), d.lockfile)
9378

9479
packageNameForConfig := pkg.Raw
9580
ok, err := versionedPkg.ValidateExists()
@@ -363,12 +348,7 @@ func (d *Devbox) removePackagesFromProfile(ctx context.Context, pkgs []string) e
363348
return err
364349
}
365350

366-
packages := devpkg.PackageFromStrings(pkgs, d.lockfile)
367-
if err := devpkg.FillNarInfoCache(ctx, packages...); err != nil {
368-
return err
369-
}
370-
371-
for _, pkg := range packages {
351+
for _, pkg := range devpkg.PackageFromStrings(pkgs, d.lockfile) {
372352
index, err := nixprofile.ProfileListIndex(&nixprofile.ProfileListIndexArgs{
373353
Lockfile: d.lockfile,
374354
Writer: d.writer,
@@ -438,9 +418,6 @@ func (d *Devbox) pendingPackagesForInstallation(ctx context.Context) ([]*devpkg.
438418
if err != nil {
439419
return nil, err
440420
}
441-
if err := devpkg.FillNarInfoCache(ctx, packages...); err != nil {
442-
return nil, err
443-
}
444421
for _, pkg := range packages {
445422
_, err := nixprofile.ProfileListIndex(&nixprofile.ProfileListIndexArgs{
446423
Items: items,

internal/impl/update.go

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

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

internal/nix/nixprofile/profile.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,6 @@ type ProfileInstallArgs struct {
201201
func ProfileInstall(ctx context.Context, args *ProfileInstallArgs) error {
202202
input := devpkg.PackageFromString(args.Package, args.Lockfile)
203203

204-
// Fill in the narinfo cache for the input package. It's okay to call this for a single package
205-
// because installing is a slow operation anyway.
206-
if err := devpkg.FillNarInfoCache(ctx, input); err != nil {
207-
return err
208-
}
209-
210204
inCache, err := input.IsInBinaryCache()
211205
if err != nil {
212206
return err

internal/shellgen/flake_input.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,18 +77,13 @@ 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, error) {
80+
func flakeInputs(ctx context.Context, packages []*devpkg.Package) []*flakeInput {
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-
9287
packages = lo.Filter(packages, func(item *devpkg.Package, _ int) bool {
9388
// Include packages (like local or remote flakes) that cannot be
9489
// fetched from a Binary Cache Store.
@@ -120,5 +115,5 @@ func flakeInputs(ctx context.Context, packages []*devpkg.Package) ([]*flakeInput
120115
}
121116
}
122117

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

internal/shellgen/flake_plan.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,14 @@ func newFlakePlan(ctx context.Context, devbox devboxer) (*flakePlan, error) {
3838
if err != nil {
3939
return nil, err
4040
}
41-
flakeInputs, err := flakeInputs(ctx, packages)
42-
if err != nil {
41+
42+
// Fill the NarInfo Cache concurrently as a perf-optimization, prior to invoking
43+
// IsInBinaryCache in flakeInputs() and in the flake.nix template.
44+
if err := devpkg.FillNarInfoCache(ctx, packages...); err != nil {
4345
return nil, err
4446
}
47+
48+
flakeInputs := flakeInputs(ctx, packages)
4549
nixpkgsInfo := getNixpkgsInfo(devbox.Config().NixPkgsCommitHash())
4650

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

testscripts/add/add.test.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
exec devbox init
44

5+
# Add a package that is not part of the Devbox Search index.
6+
# This exercises the fallback codepath for adding packages.
7+
exec devbox add stdenv.cc.cc.lib
8+
json.superset devbox.json expected_devbox1.json
9+
10+
# Add regular packages. Even though this is the more common scenario,
11+
# we test this later, because the source.path below removes "devbox"
12+
# from the PATH.
513
! exec rg --version
614
! exec vim --version
715
exec devbox add ripgrep vim
@@ -10,9 +18,26 @@ exec devbox shellenv
1018
source.path
1119
exec rg --version
1220
exec vim --version
21+
json.superset devbox.json expected_devbox2.json
1322

1423
-- devbox.json --
1524
{
1625
"packages": [
1726
]
1827
}
28+
29+
-- expected_devbox1.json --
30+
{
31+
"packages": [
32+
"stdenv.cc.cc.lib"
33+
]
34+
}
35+
36+
-- expected_devbox2.json --
37+
{
38+
"packages": [
39+
"ripgrep@latest",
40+
"vim@latest",
41+
"stdenv.cc.cc.lib"
42+
]
43+
}

0 commit comments

Comments
 (0)