diff --git a/commands/local_server_start.go b/commands/local_server_start.go index 5e24c816..af3479e4 100644 --- a/commands/local_server_start.go +++ b/commands/local_server_start.go @@ -417,6 +417,11 @@ var localServerStartCmd = &console.Command{ return err } terminal.Eprintln("") + // wait for the PHP Server to be done cleaning up + if p.PHPServer != nil { + <-p.PHPServer.StoppedChan + } + pidFile.CleanupDirectories() ui.Success("Stopped all processes successfully") } return nil diff --git a/local/php/composer.go b/local/php/composer.go index 65fd9ef8..c6f8b5b5 100644 --- a/local/php/composer.go +++ b/local/php/composer.go @@ -78,7 +78,7 @@ func Composer(dir string, args, env []string, stdout, stderr, logger io.Writer, fmt.Fprintln(logger, " WARNING: Unable to find Composer, downloading one. It is recommended to install Composer yourself at https://getcomposer.org/download/") // we don't store it under bin/ to avoid it being found by findComposer as we want to only use it as a fallback binDir := filepath.Join(util.GetHomeDir(), "composer") - if path, err = downloadComposer(binDir); err != nil { + if path, err = downloadComposer(binDir, debugLogger); err != nil { return ComposerResult{ code: 1, 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) { return "", os.ErrNotExist } -func downloadComposer(dir string) (string, error) { +func downloadComposer(dir string, debugLogger zerolog.Logger) (string, error) { if err := os.MkdirAll(dir, 0755); err != nil { return "", err } @@ -193,6 +193,7 @@ func downloadComposer(dir string) (string, error) { SkipNbArgs: 1, Stdout: &stdout, Stderr: &stdout, + Logger: debugLogger, } ret := e.Execute(false) if ret == 1 { diff --git a/local/php/executor.go b/local/php/executor.go index e259449d..91d384cd 100644 --- a/local/php/executor.go +++ b/local/php/executor.go @@ -29,6 +29,7 @@ import ( "runtime" "strings" "syscall" + "time" "github.com/pkg/errors" "github.com/rs/xid" @@ -163,7 +164,11 @@ func (e *Executor) DetectScriptDir() (string, error) { return e.scriptDir, nil } -// Config determines the right version of PHP depending on the configuration (+ its configuration) +// Config determines the right version of PHP depending on the configuration +// (+ its configuration). It also creates some symlinks to ease the integration +// with underlying tools that could try to run PHP. This is the responsibility +// of the caller to clean those temporary files. One can call +// CleanupTemporaryDirectories to do so. func (e *Executor) Config(loadDotEnv bool) error { // reset environment e.environ = make([]string, 0) @@ -220,8 +225,10 @@ func (e *Executor) Config(loadDotEnv bool) error { // prepending the PHP directory in the PATH does not work well if the PHP binary is not named "php" (like php7.3 for instance) // in that case, we create a temp directory with a symlink // we also link php-config for pecl to pick up the right one (it is always looks for something called php-config) - phpDir := filepath.Join(cliDir, "tmp", xid.New().String(), "bin") - e.tempDir = phpDir + if e.tempDir == "" { + e.tempDir = filepath.Join(cliDir, "tmp", xid.New().String()) + } + phpDir := filepath.Join(e.tempDir, "bin") if err := os.MkdirAll(phpDir, 0755); err != nil { return err } @@ -284,6 +291,92 @@ func (e *Executor) Config(loadDotEnv bool) error { return err } +func (e *Executor) CleanupTemporaryDirectories() { + go cleanupStaleTemporaryDirectories(e.Logger) + if e.iniDir != "" { + os.RemoveAll(e.iniDir) + } + if e.tempDir != "" { + os.RemoveAll(e.tempDir) + } +} + +// The Symfony CLI used to leak temporary directories until v5.10.8. The bug is +// fixed but because directories names are random they are not going to be +// reused and thus are not going to be cleaned up. And because they might be +// in-use by running servers we can't simply delete the parent directory. This +// is why we make our best to find the oldest directories and remove then, +// cleaning the directory little by little. +func cleanupStaleTemporaryDirectories(mainLogger zerolog.Logger) { + parentDirectory := filepath.Join(util.GetHomeDir(), "tmp") + mainLogger = mainLogger.With().Str("dir", parentDirectory).Logger() + + if len(parentDirectory) < 6 { + mainLogger.Warn().Msg("temporary dir path looks too short") + return + } + + mainLogger.Debug().Msg("Starting temporary directory cleanup...") + dir, err := os.Open(parentDirectory) + if err != nil { + mainLogger.Warn().Err(err).Msg("Failed to open temporary directory") + return + } + defer dir.Close() + + // the duration after which we consider temporary directories as + // stale and can be removed + cutoff := time.Now().Add(-7 * 24 * time.Hour) + + for { + // we might have a lof of entries so we need to work in batches + entries, err := dir.Readdirnames(30) + if err == io.EOF { + mainLogger.Debug().Msg("Cleaning is done...") + return + } + if err != nil { + mainLogger.Warn().Err(err).Msg("Failed to read entries") + return + } + + for _, entry := range entries { + logger := mainLogger.With().Str("entry", entry).Logger() + + // we generate temporary directory names with + // `xid.New().String()` which is always 20 char long + if len(entry) != 20 { + logger.Debug().Msg("found an entry that is not from us") + continue + } else if _, err := xid.FromString(entry); err != nil { + logger.Debug().Err(err).Msg("found an entry that is not from us") + continue + } + + entryPath := filepath.Join(parentDirectory, entry) + file, err := os.Open(entryPath) + if err != nil { + logger.Warn().Err(err).Msg("failed to read entry") + continue + } else if fi, err := file.Stat(); err != nil { + logger.Warn().Err(err).Msg("failed to read entry") + continue + } else if !fi.IsDir() { + logger.Warn().Err(err).Msg("entry is not a directory") + continue + } else if fi.ModTime().After(cutoff) { + logger.Debug().Any("cutoff", cutoff).Msg("entry is more recent than cutoff, keeping it for now") + continue + } + + logger.Debug().Str("entry", entry).Msg("entry matches the criterias, removing it") + if err := os.RemoveAll(entryPath); err != nil { + logger.Warn().Err(err).Msg("failed to remove entry") + } + } + } +} + // Find composer depending on the configuration func (e *Executor) findComposer(extraBin string) (string, error) { if scriptDir, err := e.DetectScriptDir(); err == nil { @@ -312,14 +405,7 @@ func (e *Executor) Execute(loadDotEnv bool) int { fmt.Fprintln(os.Stderr, err) return 1 } - defer func() { - if e.iniDir != "" { - os.RemoveAll(e.iniDir) - } - if e.tempDir != "" { - os.RemoveAll(e.tempDir) - } - }() + defer e.CleanupTemporaryDirectories() cmd := execCommand(e.Args[0], e.Args[1:]...) environ := append(os.Environ(), e.environ...) gpathname := "PATH" diff --git a/local/php/fpm.go b/local/php/fpm.go index b1092d24..e783dcb3 100644 --- a/local/php/fpm.go +++ b/local/php/fpm.go @@ -114,9 +114,5 @@ env['LC_ALL'] = C } func (p *Server) fpmConfigFile() string { - path := filepath.Join(p.homeDir, fmt.Sprintf("php/%s/fpm-%s.ini", name(p.projectDir), p.Version.Version)) - if _, err := os.Stat(filepath.Dir(path)); os.IsNotExist(err) { - _ = os.MkdirAll(filepath.Dir(path), 0755) - } - return path + return filepath.Join(p.tempDir, fmt.Sprintf("fpm-%s.ini", p.Version.Version)) } diff --git a/local/php/php_builtin_server.go b/local/php/php_builtin_server.go index 88638d75..3a394e55 100644 --- a/local/php/php_builtin_server.go +++ b/local/php/php_builtin_server.go @@ -21,7 +21,6 @@ package php import ( "fmt" - "os" "path/filepath" ) @@ -61,9 +60,5 @@ require $script; `) func (p *Server) phpRouterFile() string { - path := filepath.Join(p.homeDir, fmt.Sprintf("php/%s-router.php", name(p.projectDir))) - if _, err := os.Stat(filepath.Dir(path)); os.IsNotExist(err) { - _ = os.MkdirAll(filepath.Dir(path), 0755) - } - return path + return filepath.Join(p.tempDir, fmt.Sprintf("%s-router.php", p.Version.Version)) } diff --git a/local/php/php_server.go b/local/php/php_server.go index 44c8a001..4384cc25 100644 --- a/local/php/php_server.go +++ b/local/php/php_server.go @@ -48,8 +48,9 @@ import ( type Server struct { Version *phpstore.Version logger zerolog.Logger + StoppedChan chan bool appVersion string - homeDir string + tempDir string projectDir string documentRoot string passthru string @@ -75,16 +76,22 @@ func NewServer(homeDir, projectDir, documentRoot, passthru, appVersion string, l Version: version, logger: logger.With().Str("source", "PHP").Str("php", version.Version).Str("path", version.ServerPath()).Logger(), appVersion: appVersion, - homeDir: homeDir, projectDir: projectDir, documentRoot: documentRoot, passthru: passthru, + StoppedChan: make(chan bool, 1), }, nil } // Start starts a PHP server func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile, func() error, error) { - var pathsToRemove []string + p.tempDir = pidFile.TempDirectory() + if _, err := os.Stat(p.tempDir); os.IsNotExist(err) { + if err = os.MkdirAll(p.tempDir, 0755); err != nil { + return nil, nil, err + } + } + port, err := process.FindAvailablePort() if err != nil { 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, return nil, nil, errors.WithStack(err) } p.proxy.Transport = &cgiTransport{} - pathsToRemove = append(pathsToRemove, fpmConfigFile) binName = "php-fpm" workerName = "PHP-FPM" 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, if err := os.WriteFile(routerPath, phprouter, 0644); err != nil { return nil, nil, errors.WithStack(err) } - pathsToRemove = append(pathsToRemove, routerPath) binName = "php" workerName = "PHP" 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, BinName: binName, Args: args, scriptDir: p.projectDir, + Logger: p.logger, } p.logger.Info().Int("port", port).Msg("listening") @@ -192,9 +198,8 @@ func (p *Server) Start(ctx context.Context, pidFile *pid.PidFile) (*pid.PidFile, return phpPidFile, func() error { defer func() { - for _, path := range pathsToRemove { - os.RemoveAll(path) - } + e.CleanupTemporaryDirectories() + p.StoppedChan <- true }() return errors.Wrap(errors.WithStack(runner.Run()), "PHP server exited unexpectedly") diff --git a/local/pid/pidfile.go b/local/pid/pidfile.go index b5e41f28..243a2012 100644 --- a/local/pid/pidfile.go +++ b/local/pid/pidfile.go @@ -232,6 +232,19 @@ func (p *PidFile) WorkerPidDir() string { return filepath.Join(util.GetHomeDir(), "var", name(p.Dir)) } +func (p *PidFile) TempDirectory() string { + return filepath.Join(util.GetHomeDir(), "php", name(p.Dir)) +} + +func (p *PidFile) CleanupDirectories() { + os.RemoveAll(p.TempDirectory()) + // We don't want to force removal of log and pid files, we only want to + // clean up empty directories. To do so we use `os.Remove` instead of + // `os.RemoveAll` + os.Remove(p.WorkerLogDir()) + os.Remove(p.WorkerPidDir()) +} + func (p *PidFile) LogReader() (io.ReadCloser, error) { logFile := p.LogFile() if err := os.MkdirAll(filepath.Dir(logFile), 0755); err != nil { diff --git a/local/project/project.go b/local/project/project.go index 53ad6e9f..ac179b12 100644 --- a/local/project/project.go +++ b/local/project/project.go @@ -34,11 +34,9 @@ import ( // Project represents a PHP project type Project struct { - HTTP *lhttp.Server - PHPServer *php.Server - Logger zerolog.Logger - homeDir string - projectDir string + HTTP *lhttp.Server + PHPServer *php.Server + Logger zerolog.Logger } // New creates a new PHP project @@ -49,9 +47,7 @@ func New(c *Config) (*Project, error) { } passthru, err := realPassthru(documentRoot, c.Passthru) p := &Project{ - Logger: c.Logger.With().Str("source", "HTTP").Logger(), - homeDir: c.HomeDir, - projectDir: c.ProjectDir, + Logger: c.Logger.With().Str("source", "HTTP").Logger(), HTTP: &lhttp.Server{ DocumentRoot: documentRoot, Port: c.Port, diff --git a/main.go b/main.go index 2d89d1e9..4d93c765 100644 --- a/main.go +++ b/main.go @@ -68,12 +68,14 @@ func main() { BinName: args[1], Args: args[1:], ExtraEnv: getCliExtraEnv(), + Logger: terminal.Logger, } os.Exit(e.Execute(true)) } // called via "symfony console"? if len(args) >= 2 && args[1] == "console" { if executor, err := php.SymonyConsoleExecutor(args[2:]); err == nil { + executor.Logger = terminal.Logger executor.ExtraEnv = getCliExtraEnv() os.Exit(executor.Execute(false)) } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy