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

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

Issue 2699063004: vpython: Add VirtualEnv creation package. (Closed)
Patch Set: remake binaries Created 3 years, 10 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
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 func prune(c context.Context, cfg *Config, omit string) error {
iannucci 2017/02/23 00:54:21 what's omit?
dnj 2017/02/23 20:38:49 Done.
29 if cfg.PruneThreshold <= 0 {
30 // Pruning is disabled.
31 return nil
32 }
33 pruneThreshold := clock.Now(c).Add(-cfg.PruneThreshold)
34
35 // Get a listing of all VirtualEnv within the base directory.
36 dir, err := os.Open(cfg.BaseDir)
37 if err != nil {
38 return errors.Annotate(err).Reason("failed to open base director y: %(dir)s").
39 D("dir", cfg.BaseDir).
40 Err()
41 }
42 defer dir.Close()
43
44 // Run a series of independent scan/prune operations.
45 logging.Debugf(c, "Pruning entries in [%s] older than %s.", cfg.BaseDir, cfg.PruneThreshold)
46
47 var (
48 allErrs errors.MultiError
49 totalPruned = 0
50 done = false
51 )
52 for !done {
53 fileInfos, err := dir.Readdir(pruneReadDirSize)
54 switch err {
55 case nil:
56
57 case io.EOF:
58 done = true
59
60 default:
61 return errors.Annotate(err).Reason("could not read direc tory contents: %(dir)s").
62 D("dir", err).
63 Err()
64 }
65
66 // Shuffle the slice randomly. We do this in case others are als o processing
67 // this directory simultaneously.
68 for i := range fileInfos {
69 j := mathrand.Intn(c, i+1)
70 fileInfos[i], fileInfos[j] = fileInfos[j], fileInfos[i]
71 }
72
73 for _, fi := range fileInfos {
74 if strings.HasPrefix(fi.Name(), ".") {
75 // Ignore hidden files.
iannucci 2017/02/23 00:54:20 do we have to worry about them growing unbounded?
dnj 2017/02/23 20:38:49 We shouldn't have to. If someone puts their own st
76 continue
77 }
78
79 switch pruned, err := maybePruneFile(c, cfg, fi, pruneTh reshold, omit); {
80 case err != nil:
81 allErrs = append(allErrs, errors.Annotate(err).
82 Reason("failed to prune file: %(name)s") .
83 D("name", fi.Name()).
84 D("dir", cfg.BaseDir).
85 Err())
86
87 case pruned:
88 totalPruned++
89 if cfg.PruneLimit > 0 && totalPruned >= cfg.Prun eLimit {
90 logging.Debugf(c, "Hit prune limit of %d .", cfg.PruneLimit)
91 done = true
92 }
93 }
94 }
95 }
96
97 logging.Debugf(c, "Pruned %d environment(s) with %d error(s)", totalPrun ed, len(allErrs))
iannucci 2017/02/23 00:54:20 I think this should be info (and look like `Pruned
dnj 2017/02/23 20:38:49 Done.
98 if len(allErrs) > 0 {
99 return allErrs
100 }
101 return nil
102 }
103
104 // maybePruneFile examines the specified FileIfo within cfg.BaseDir and
105 // determines if it should be pruned.
106 func maybePruneFile(c context.Context, cfg *Config, fi os.FileInfo, pruneThresho ld time.Time, omit string) (
107 pruned bool, err error) {
108
109 if !fi.IsDir() || fi.Name() == omit {
110 return
iannucci 2017/02/23 00:54:21 log?
dnj 2017/02/23 20:38:49 Done.
111 }
112
113 // Grab the lock file for this directory.
114 e := cfg.envForName(fi.Name())
115 err = fslock.With(e.lockPath, func() error {
116 // Read the complete flag file's timestamp.
117 switch st, err := os.Stat(e.completeFlagPath); {
118 case err == nil:
119 if !st.ModTime().Before(pruneThreshold) {
120 return nil
121 }
122
123 logging.Infof(c, "Env [%s] is older than the prune thres hold (%v < %v); pruning...",
124 e.name, st.ModTime(), pruneThreshold)
125
126 case os.IsNotExist(err):
127 logging.Infof(c, "Env [%s] has no completed flag; prunin g...", e.name)
128
129 default:
130 return errors.Annotate(err).Reason("failed to stat compl ete flag: %(path)s").
131 D("path", e.completeFlagPath).
132 Err()
133 }
134
135 // Delete the environment. We currently hold its lock, so use de leteLocked.
136 if err := e.deleteLocked(c); err != nil {
iannucci 2017/02/23 00:54:20 does it make sense to mv this to a 'prune' folder
dnj 2017/02/23 20:38:49 There may be a race here with moving. ATM deletion
iannucci 2017/03/11 00:37:34 I'm thinking about the case where it /partially/ p
137 return errors.Annotate(err).Reason("failed to delete Env ").Err()
138 }
139 pruned = true
140 return nil
141 })
142 switch err {
143 case nil:
144 return
145
146 case fslock.ErrLockHeld:
147 // Something else currently holds the lock for this directory, s o ignore it.
148 logging.Warningf(c, "Lock [%s] is currently held; skipping.", e. lockPath)
149 return
150
151 default:
152 err = errors.Annotate(err).Err()
153 return
154 }
155 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698