Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(707)

Side by Side Diff: vpython/venv/prune.go

Issue 2699063004: vpython: Add VirtualEnv creation package. (Closed)
Patch Set: bootstrap straight out of tempdir Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « vpython/venv/config.go ('k') | vpython/venv/system_posix.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package venv
6
7 import (
8 "io"
9 "os"
10 "strings"
11 "time"
12
13 "github.com/luci/luci-go/common/clock"
14 "github.com/luci/luci-go/common/data/rand/mathrand"
15 "github.com/luci/luci-go/common/errors"
16 "github.com/luci/luci-go/common/logging"
17
18 "github.com/danjacques/gofslock/fslock"
19 "golang.org/x/net/context"
20 )
21
22 // pruneReadDirSize is the number of entries to read in a directory at a time
23 // when pruning.
24 const pruneReadDirSize = 128
25
26 // prune examines environments in cfg's BaseDir. If any are found that are older
27 // than the prune threshold in "cfg", they will be safely deleted.
28 //
29 // If forceKeep is not empty, prune will skip pruning the VirtualEnv named
30 // "forceKeep", even if it is would otherwise be candidate for pruning.
31 func prune(c context.Context, cfg *Config, forceKeep string) error {
32 if cfg.PruneThreshold <= 0 {
33 // Pruning is disabled.
34 return nil
35 }
36 pruneThreshold := clock.Now(c).Add(-cfg.PruneThreshold)
37
38 // Get a listing of all VirtualEnv within the base directory.
39 dir, err := os.Open(cfg.BaseDir)
40 if err != nil {
41 return errors.Annotate(err).Reason("failed to open base director y: %(dir)s").
42 D("dir", cfg.BaseDir).
43 Err()
44 }
45 defer dir.Close()
46
47 // Run a series of independent scan/prune operations.
48 logging.Debugf(c, "Pruning entries in [%s] older than %s.", cfg.BaseDir, cfg.PruneThreshold)
49
50 var (
51 allErrs errors.MultiError
52 totalPruned = 0
53 done = false
54 hitLimitStr = ""
55 )
56 for !done {
57 fileInfos, err := dir.Readdir(pruneReadDirSize)
58 switch err {
59 case nil:
60
61 case io.EOF:
62 done = true
63
64 default:
65 return errors.Annotate(err).Reason("could not read direc tory contents: %(dir)s").
66 D("dir", err).
67 Err()
68 }
69
70 // Shuffle the slice randomly. We do this in case others are als o processing
71 // this directory simultaneously.
72 for i := range fileInfos {
73 j := mathrand.Intn(c, i+1)
74 fileInfos[i], fileInfos[j] = fileInfos[j], fileInfos[i]
75 }
76
77 for _, fi := range fileInfos {
78 // Ignore hidden files. This includes the package loader root.
79 if strings.HasPrefix(fi.Name(), ".") {
80 continue
81 }
82
83 switch pruned, err := maybePruneFile(c, cfg, fi, pruneTh reshold, forceKeep); {
84 case err != nil:
85 allErrs = append(allErrs, errors.Annotate(err).
86 Reason("failed to prune file: %(name)s") .
87 D("name", fi.Name()).
88 D("dir", cfg.BaseDir).
89 Err())
90
91 case pruned:
92 totalPruned++
93 if cfg.MaxPrunesPerSweep > 0 && totalPruned >= c fg.MaxPrunesPerSweep {
94 logging.Debugf(c, "Hit prune limit of %d .", cfg.MaxPrunesPerSweep)
95 done, hitLimitStr = true, " (limit)"
96 }
97 }
98 }
99 }
100
101 logging.Infof(c, "Pruned %d environment(s)%s with %d error(s)", totalPru ned, hitLimitStr, len(allErrs))
102 if len(allErrs) > 0 {
103 return allErrs
104 }
105 return nil
106 }
107
108 // maybePruneFile examines the specified FileIfo within cfg.BaseDir and
109 // determines if it should be pruned.
110 func maybePruneFile(c context.Context, cfg *Config, fi os.FileInfo, pruneThresho ld time.Time,
111 forceKeep string) (pruned bool, err error) {
112
113 name := fi.Name()
114 if !fi.IsDir() || name == forceKeep {
115 logging.Debugf(c, "Not pruning file: %s", name)
116 return
117 }
118
119 // Grab the lock file for this directory.
120 e := cfg.envForName(name)
121 err = fslock.With(e.lockPath, func() error {
122 // Read the complete flag file's timestamp.
123 switch st, err := os.Stat(e.completeFlagPath); {
124 case err == nil:
125 if !st.ModTime().Before(pruneThreshold) {
126 return nil
127 }
128
129 logging.Infof(c, "Env [%s] is older than the prune thres hold (%v < %v); pruning...",
130 e.name, st.ModTime(), pruneThreshold)
131
132 case os.IsNotExist(err):
133 logging.Infof(c, "Env [%s] has no completed flag; prunin g...", e.name)
134
135 default:
136 return errors.Annotate(err).Reason("failed to stat compl ete flag: %(path)s").
137 D("path", e.completeFlagPath).
138 Err()
139 }
140
141 // Delete the environment. We currently hold its lock, so use de leteLocked.
142 if err := e.deleteLocked(c); err != nil {
143 return errors.Annotate(err).Reason("failed to delete Env ").Err()
144 }
145 pruned = true
146 return nil
147 })
148 switch err {
149 case nil:
150 return
151
152 case fslock.ErrLockHeld:
153 // Something else currently holds the lock for this directory, s o ignore it.
154 logging.Warningf(c, "Lock [%s] is currently held; skipping.", e. lockPath)
155 return
156
157 default:
158 err = errors.Annotate(err).Err()
159 return
160 }
161 }
OLDNEW
« no previous file with comments | « vpython/venv/config.go ('k') | vpython/venv/system_posix.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698