8000 fix: cleanup stale temporary directories · symfony-cli/symfony-cli@e0b9e03 · GitHub
[go: up one dir, main page]

Skip to content

Commit e0b9e03

Browse files
committed
fix: cleanup stale temporary directories
please note that this might take a while because we clean by batches, use `find ~/.symfony5/tmp -type d -mindepth 1 -maxdepth 1 -delete` if you are eager
1 parent f7b227d commit e0b9e03

File tree

4 files changed

+84
-2
lines changed

4 files changed

+84
-2
lines changed

local/php/composer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func Composer(dir string, args, env []string, stdout, stderr, logger io.Writer,
7878
fmt.Fprintln(logger, " WARNING: Unable to find Composer, downloading one. It is recommended to install Composer yourself at https://getcomposer.org/download/")
7979
// we don't store it under bin/ to avoid it being found by findComposer as we want to only use it as a fallback
8080
binDir := filepath.Join(util.GetHomeDir(), "composer")
81-
if path, err = downloadComposer(binDir); err != nil {
81+
if path, err = downloadComposer(binDir, debugLogger); err != nil {
8282
return ComposerResult{
8383
code: 1,
8484
error: errors.Wrap(err, "unable to find composer, get it at https://getcomposer.org/download/"),
@@ -157,7 +157,7 @@ func findComposer(extraBin string, logger zerolog.Logger) (string, error) {
157157
return "", os.ErrNotExist
158158
}
159159

160-
func downloadComposer(dir string) (string, error) {
160+
func downloadComposer(dir string, debugLogger zerolog.Logger) (string, error) {
161161
if err := os.MkdirAll(dir, 0755); err != nil {
162162
return "", err
163163
}
@@ -193,6 +193,7 @@ func downloadComposer(dir string) (string, error) {
193193
SkipNbArgs: 1,
194194
Stdout: &stdout,
195195
Stderr: &stdout,
196+
Logger: debugLogger,
196197
}
197198
ret := e.Execute(false)
198199
if ret == 1 {

local/php/executor.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"runtime"
3030
"strings"
3131
"syscall"
32+
"time"
3233

3334
"github.com/pkg/errors"
3435
"github.com/rs/xid"
@@ -291,6 +292,7 @@ func (e *Executor) Config(loadDotEnv bool) error {
291292
}
292293

293294
func (e *Executor) CleanupTemporaryDirectories() {
295+
go cleanupStaleTemporaryDirectories(e.Logger)
294296
if e.iniDir != "" {
295297
os.RemoveAll(e.iniDir)
296298
}
@@ -299,6 +301,82 @@ func (e *Executor) CleanupTemporaryDirectories() {
299301
}
300302
}
301303

304+
// The Symfony CLI used to leak temporary directories until v5.10.8. The bug is
305+
// fixed but because directories names are random they are not going to be
306+
// reused and thus are not going to be cleaned up. And because they might be
307+
// in-use by running servers we can't simply delete the parent directory. This
308+
// is why we make our best to find the oldest directories and remove then,
309+
// cleaning the directory little by little.
310+
func cleanupStaleTemporaryDirectories(mainLogger zerolog.Logger) {
311+
parentDirectory := filepath.Join(util.GetHomeDir(), "tmp")
312+
mainLogger = mainLogger.With().Str("dir", parentDirectory).Logger()
313+
314+
if len(parentDirectory) < 6 {
315+
mainLogger.Warn().Msg("temporary dir path looks too short")
316+
return
317+
}
318+
319+
mainLogger.Debug().Msg("Starting temporary directory cleanup...")
320+
dir, err := os.Open(parentDirectory)
321+
if err != nil {
322+
mainLogger.Warn().Err(err).Msg("Failed to open temporary directory")
323+
return
324+
}
325+
defer dir.Close()
326+
327+
// the duration after which we consider temporary directories as
328+
// stale and can be removed
329+
cutoff := time.Now().Add(-7 * 24 * time.Hour)
330+
331+
for {
332+
// we might have a lof of entries so we need to work in batches
333+
entries, err := dir.Readdirnames(30)
334+
if err == io.EOF {
335+
mainLogger.Debug().Msg("Cleaning is done...")
336+
return
337+
}
338+
if err != nil {
339+
mainLogger.Warn().Err(err).Msg("Failed to read entries")
340+
return
341+
}
342+
343+
for _, entry := range entries {
344+
logger := mainLogger.With().Str("entry", entry).Logger()
345+
346+
// we generate temporary directory names with
347+
// `xid.New().String()` which is always 20 char long
348+
if len(entry) != 20 {
349+
logger.Debug().Msg("found an entry that is not from us")
350+
continue
351+
} else if _, err := xid.FromString(entry); err != nil {
352+
logger.Debug().Err(err).Msg("found an entry that is not from us")
353+
continue
354+
}
355+
356+
entryPath := filepath.Join(parentDirectory, entry)
357+
file, err := os.Open(entryPath)
358+
if err != nil {
359+
logger.Warn().Err(err).Msg("failed to read entry")
360+
continue
361+
} else if fi, err := file.Stat(); err != nil {
362+
logger.Warn().Err(err).Msg("failed to read entry")
363+
continue
364+
} else if !fi.IsDir() {
365+
logger.Warn().Err(err).Msg("entry is not a directory")
366+
continue
367+
} else if fi.ModTime().After(cutoff) {
368+
logger.Debug().Any("cutoff", cutoff).Msg("entry is more recent than cutoff, keeping it for now")
369+
continue
370+
}
371+
372+
logger.Debug().Str("entry", entry).Msg("entry matches the criterias, removing it")
373+
if err := os.RemoveAll(entryPath); err != nil {
374+
logger.Warn().Err(err).Msg("failed to remove entry")
375+
}
376+
}
377+
}
378+
}
379+
302380
// Find composer depending on the configuration
303381
func (e *Executor) findComposer(extraBin string) (string, error) {
304382
if scriptDir, err := e.DetectScriptDir(); err == nil {

local/php/php_server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile,
166166
BinName: binName,
167167
Args: args,
168168
scriptDir: p.projectDir,
169+
Logger: p.logger,
169170
}
170171
p.logger.Info().Int("port", port).Msg("listening")
171172

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,14 @@ func main() {
6868
BinName: args[1],
6969
Args: args[1:],
7070
ExtraEnv: getCliExtraEnv(),
71+
Logger: terminal.Logger,
7172
}
7273
os.Exit(e.Execute(true))
7374
}
7475
// called via "symfony console"?
7576
if len(args) >= 2 && args[1] == "console" {
7677
if executor, err := php.SymonyConsoleExecutor(args[2:]); err == nil {
78+
executor.Logger = terminal.Logger
7779
executor.ExtraEnv = getCliExtraEnv()
7880
os.Exit(executor.Execute(false))
7981
}

0 commit comments

Comments
 (0)
0