Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6004fc6

Browse filesBrowse files
authored
Merge pull request #566 from tucksaun/fix/553
fix: cleanup temporary directories
2 parents e3b7acc + e0b9e03 commit 6004fc6
Copy full SHA for 6004fc6

File tree

9 files changed

+139
-40
lines changed
Filter options

9 files changed

+139
-40
lines changed

‎commands/local_server_start.go

Copy file name to clipboardExpand all lines: commands/local_server_start.go
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,11 @@ var localServerStartCmd = &console.Command{
417417
return err
418418
}
419419
terminal.Eprintln("")
420+
// wait for the PHP Server to be done cleaning up
421+
if p.PHPServer != nil {
422+
<-p.PHPServer.StoppedChan
423+
}
424+
pidFile.CleanupDirectories()
420425
ui.Success("Stopped all processes successfully")
421426
}
422427
return nil

‎local/php/composer.go

Copy file name to clipboardExpand all lines: local/php/composer.go
+3-2Lines 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

Copy file name to clipboardExpand all lines: local/php/executor.go
+97-11Lines changed: 97 additions & 11 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"
@@ -163,7 +164,11 @@ func (e *Executor) DetectScriptDir() (string, error) {
163164
return e.scriptDir, nil
164165
}
165166

166-
// Config determines the right version of PHP depending on the configuration (+ its configuration)
167+
// Config determines the right version of PHP depending on the configuration
168+
// (+ its configuration). It also creates some symlinks to ease the integration
169+
// with underlying tools that could try to run PHP. This is the responsibility
170+
// of the caller to clean those temporary files. One can call
171+
// CleanupTemporaryDirectories to do so.
167172
func (e *Executor) Config(loadDotEnv bool) error {
168173
// reset environment
169174
e.environ = make([]string, 0)
@@ -220,8 +225,10 @@ func (e *Executor) Config(loadDotEnv bool) error {
220225
// prepending the PHP directory in the PATH does not work well if the PHP binary is not named "php" (like php7.3 for instance)
221226
// in that case, we create a temp directory with a symlink
222227
// we also link php-config for pecl to pick up the right one (it is always looks for something called php-config)
223-
phpDir := filepath.Join(cliDir, "tmp", xid.New().String(), "bin")
224-
e.tempDir = phpDir
228+
if e.tempDir == "" {
229+
e.tempDir = filepath.Join(cliDir, "tmp", xid.New().String())
230+
}
231+
phpDir := filepath.Join(e.tempDir, "bin")
225232
if err := os.MkdirAll(phpDir, 0755); err != nil {
226233
return err
227234
}
@@ -284,6 +291,92 @@ func (e *Executor) Config(loadDotEnv bool) error {
284291
return err
285292
}
286293

294+
func (e *Executor) CleanupTemporaryDirectories() {
295+
go cleanupStaleTemporaryDirectories(e.Logger)
296+
if e.iniDir != "" {
297+
os.RemoveAll(e.iniDir)
298+
}
299+
if e.tempDir != "" {
300+
os.RemoveAll(e.tempDir)
301+
}
302+
}
303+
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+
287380
// Find composer depending on the configuration
288381
func (e *Executor) findComposer(extraBin string) (string, error) {
289382
if scriptDir, err := e.DetectScriptDir(); err == nil {
@@ -312,14 +405,7 @@ func (e *Executor) Execute(loadDotEnv bool) int {
312405
fmt.Fprintln(os.Stderr, err)
313406
return 1
314407
}
315-
defer func() {
316-
if e.iniDir != "" {
317-
os.RemoveAll(e.iniDir)
318-
}
319-
if e.tempDir != "" {
320-
os.RemoveAll(e.tempDir)
321-
}
322-
}()
408+
defer e.CleanupTemporaryDirectories()
323409
cmd := execCommand(e.Args[0], e.Args[1:]...)
324410
environ := append(os.Environ(), e.environ...)
325411
gpathname := "PATH"

‎local/php/fpm.go

Copy file name to clipboardExpand all lines: local/php/fpm.go
+1-5Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,5 @@ env['LC_ALL'] = C
114114
}
115115

116116
func (p *Server) fpmConfigFile() string {
117-
path := filepath.Join(p.homeDir, fmt.Sprintf("php/%s/fpm-%s.ini", name(p.projectDir), p.Version.Version))
118-
if _, err := os.Stat(filepath.Dir(path)); os.IsNotExist(err) {
119-
_ = os.MkdirAll(filepath.Dir(path), 0755)
120-
}
121-
return path
117+
return filepath.Join(p.tempDir, fmt.Sprintf("fpm-%s.ini", p.Version.Version))
122118
}

‎local/php/php_builtin_server.go

Copy file name to clipboardExpand all lines: local/php/php_builtin_server.go
+1-6Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ package php
2121

2222
import (
2323
"fmt"
24-
"os"
2524
"path/filepath"
2625
)
2726

@@ -61,9 +60,5 @@ require $script;
6160
`)
6261

6362
func (p *Server) phpRouterFile() string {
64-
path := filepath.Join(p.homeDir, fmt.Sprintf("php/%s-router.php", name(p.projectDir)))
65-
if _, err := os.Stat(filepath.Dir(path)); os.IsNotExist(err) {
66-
_ = os.MkdirAll(filepath.Dir(path), 0755)
67-
}
68-
return path
63+
return filepath.Join(p.tempDir, fmt.Sprintf("%s-router.php", p.Version.Version))
6964
}

‎local/php/php_server.go

Copy file name to clipboardExpand all lines: local/php/php_server.go
+13-8Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ import (
4848
type Server struct {
4949
Version *phpstore.Version
5050
logger zerolog.Logger
51+
StoppedChan chan bool
5152
appVersion string
52-
homeDir string
53+
tempDir string
5354
projectDir string
5455
documentRoot string
5556
passthru string
@@ -75,16 +76,22 @@ func NewServer(homeDir, projectDir, documentRoot, passthru, appVersion string, l
7576
Version: version,
7677
logger: logger.With().Str("source", "PHP").Str("php", version.Version).Str("path", version.ServerPath()).Logger(),
7778
appVersion: appVersion,
78-
homeDir: homeDir,
7979
projectDir: projectDir,
8080
documentRoot: documentRoot,
8181
passthru: passthru,
82+
StoppedChan: make(chan bool, 1),
8283
}, nil
8384
}
8485

8586
// Start starts a PHP server
8687
func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile, func() error, error) {
87-
var pathsToRemove []string
88+
p.tempDir = pidFile.TempDirectory()
89+
if _, err := os.Stat(p.tempDir); os.IsNotExist(err) {
90+
if err = os.MkdirAll(p.tempDir, 0755); err != nil {
91+
return nil, nil, err
92+
}
93+
}
94+
8895
port, err := process.FindAvailablePort()
8996
if err != nil {
9097
p.logger.Debug().Err(err).Msg("unable to find an available port")
@@ -123,7 +130,6 @@ func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile,
123130
return nil, nil, errors.WithStack(err)
124131
}
125132
p.proxy.Transport = &cgiTransport{}
126-
pathsToRemove = append(pathsToRemove, fpmConfigFile)
127133
binName = "php-fpm"
128134
workerName = "PHP-FPM"
129135
args = []string{p.Version.ServerPath(), "--nodaemonize", "--fpm-config", fpmConfigFile}
@@ -149,7 +155,6 @@ func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile,
149155
if err := os.WriteFile(routerPath, phprouter, 0644); err != nil {
150156
return nil, nil, errors.WithStack(err)
151157
}
152-
pathsToRemove = append(pathsToRemove, routerPath)
153158
binName = "php"
154159
workerName = "PHP"
155160
args = []string{p.Version.ServerPath(), "-S", "127.0.0.1:" + strconv.Itoa(port), "-d", "variables_order=EGPCS", routerPath}
@@ -161,6 +166,7 @@ func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile,
161166
BinName: binName,
162167
Args: args,
163168
scriptDir: p.projectDir,
169+
Logger: p.logger,
164170
}
165171
p.logger.Info().Int("port", port).Msg("listening")
166172

@@ -192,9 +198,8 @@ func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile,
192198

193199
return phpPidFile, func() error {
194200
defer func() {
195-
for _, path := range pathsToRemove {
196-
os.RemoveAll(path)
197-
}
201+
e.CleanupTemporaryDirectories()
202+
p.StoppedChan <- true
198203
}()
199204

200205
return errors.Wrap(errors.WithStack(runner.Run()), "PHP server exited unexpectedly")

‎local/pid/pidfile.go

Copy file name to clipboardExpand all lines: local/pid/pidfile.go
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,19 @@ func (p *PidFile) WorkerPidDir() string {
232232
return filepath.Join(util.GetHomeDir(), "var", name(p.Dir))
233233
}
234234

235+
func (p *PidFile) TempDirectory() string {
236+
return filepath.Join(util.GetHomeDir(), "php", name(p.Dir))
237+
}
238+
239+
func (p *PidFile) CleanupDirectories() {
240+
os.RemoveAll(p.TempDirectory())
241+
// We don't want to force removal of log and pid files, we only want to
242+
// clean up empty directories. To do so we use `os.Remove` instead of
243+
// `os.RemoveAll`
244+
os.Remove(p.WorkerLogDir())
245+
os.Remove(p.WorkerPidDir())
246+
}
247+
235248
func (p *PidFile) LogReader() (io.ReadCloser, error) {
236249
logFile := p.LogFile()
237250
if err := os.MkdirAll(filepath.Dir(logFile), 0755); err != nil {

‎local/project/project.go

Copy file name to clipboardExpand all lines: local/project/project.go
+4-8Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,9 @@ import (
3434

3535
// Project represents a PHP project
3636
type Project struct {
37-
HTTP *lhttp.Server
38-
PHPServer *php.Server
39-
Logger zerolog.Logger
40-
homeDir string
41-
projectDir string
37+
HTTP *lhttp.Server
38+
PHPServer *php.Server
39+
Logger zerolog.Logger
4240
}
4341

4442
// New creates a new PHP project
@@ -49,9 +47,7 @@ func New(c *Config) (*Project, error) {
4947
}
5048
passthru, err := realPassthru(documentRoot, c.Passthru)
5149
p := &Project{
52-
Logger: c.Logger.With().Str("source", "HTTP").Logger(),
53-
homeDir: c.HomeDir,
54-
projectDir: c.ProjectDir,
50+
Logger: c.Logger.With().Str("source", "HTTP").Logger(),
5551
HTTP: &lhttp.Server{
5652
DocumentRoot: documentRoot,
5753
Port: c.Port,

‎main.go

Copy file name to clipboardExpand all lines: main.go
+2Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.