mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-16 20:32:13 +01:00
feat(state): improve clean up when LibreOffice and Chromium are restarted
This commit is contained in:
@@ -5,13 +5,14 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// GarbageCollect scans the root path and deletes files or directories with
|
||||
// names containing specific substrings.
|
||||
func GarbageCollect(logger *zap.Logger, rootPath string, includeSubstr []string) error {
|
||||
// names containing specific substrings and before a given experiation time.
|
||||
func GarbageCollect(logger *zap.Logger, rootPath string, includeSubstr []string, expirationTime time.Time) error {
|
||||
logger = logger.Named("gc")
|
||||
|
||||
// To make sure that the next Walk method stays on
|
||||
@@ -36,7 +37,7 @@ func GarbageCollect(logger *zap.Logger, rootPath string, includeSubstr []string)
|
||||
}
|
||||
|
||||
for _, substr := range includeSubstr {
|
||||
if strings.Contains(info.Name(), substr) || path == substr {
|
||||
if (strings.Contains(info.Name(), substr) || path == substr) && info.ModTime().Before(expirationTime) {
|
||||
err := os.RemoveAll(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("garbage collect '%s': %w", path, err)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
@@ -64,7 +65,7 @@ func TestGarbageCollect(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
err := GarbageCollect(zap.NewNop(), tc.rootPath, tc.includeSubstr)
|
||||
err := GarbageCollect(zap.NewNop(), tc.rootPath, tc.includeSubstr, time.Now())
|
||||
|
||||
if !tc.expectError && err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/chromedp/cdproto/runtime"
|
||||
"github.com/chromedp/chromedp"
|
||||
"github.com/dlclark/regexp2"
|
||||
"github.com/shirou/gopsutil/v4/process"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
@@ -162,21 +163,60 @@ func (b *chromiumBrowser) Stop(logger *zap.Logger) error {
|
||||
|
||||
// Always remove the user profile directory created by Chromium.
|
||||
copyUserProfileDirPath := b.userProfileDirPath
|
||||
defer func(userProfileDirPath string) {
|
||||
expirationTime := time.Now()
|
||||
defer func(userProfileDirPath string, expirationTime time.Time) {
|
||||
// See:
|
||||
// https://github.com/SeleniumHQ/docker-selenium/blob/7216d060d86872afe853ccda62db0dfab5118dc7/NodeChrome/chrome-cleanup.sh
|
||||
// https://github.com/SeleniumHQ/docker-selenium/blob/7216d060d86872afe853ccda62db0dfab5118dc7/NodeChromium/chrome-cleanup.sh
|
||||
go func() {
|
||||
// Clean up stuck processes.
|
||||
ps, err := process.Processes()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("list processes: %v", err))
|
||||
} else {
|
||||
for _, p := range ps {
|
||||
func() {
|
||||
cmdline, err := p.Cmdline()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(cmdline, "chromium/chromium") && !strings.Contains(cmdline, "chrome/chrome") {
|
||||
return
|
||||
}
|
||||
|
||||
killCtx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
err = p.KillWithContext(killCtx)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("kill process: %v", err))
|
||||
} else {
|
||||
logger.Info(fmt.Sprintf("Chromium process %d killed", p.Pid))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Chromium seems to recreate the user profile directory
|
||||
// right after its deletion if we do not wait a certain amount
|
||||
// of time before deleting it.
|
||||
<-time.After(10 * time.Second)
|
||||
|
||||
err := os.RemoveAll(userProfileDirPath)
|
||||
err = os.RemoveAll(userProfileDirPath)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("remove Chromium's user profile directory: %s", err))
|
||||
} else {
|
||||
logger.Debug(fmt.Sprintf("'%s' Chromium's user profile directory removed", userProfileDirPath))
|
||||
}
|
||||
|
||||
logger.Debug(fmt.Sprintf("'%s' Chromium's user profile directory removed", userProfileDirPath))
|
||||
// Also remove Chromium specific files in the temporary directory.
|
||||
err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{".org.chromium.Chromium", ".com.google.Chrome"}, expirationTime)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
}(copyUserProfileDirPath)
|
||||
}(copyUserProfileDirPath, expirationTime)
|
||||
|
||||
b.ctxMu.Lock()
|
||||
defer b.ctxMu.Unlock()
|
||||
|
||||
@@ -190,22 +190,23 @@ func (p *libreOfficeProcess) Stop(logger *zap.Logger) error {
|
||||
|
||||
// Always remove the user profile directory created by LibreOffice.
|
||||
copyUserProfileDirPath := p.userProfileDirPath
|
||||
defer func(userProfileDirPath string) {
|
||||
expirationTime := time.Now()
|
||||
defer func(userProfileDirPath string, expirationTime time.Time) {
|
||||
go func() {
|
||||
err := os.RemoveAll(userProfileDirPath)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("remove LibreOffice's user profile directory: %v", err))
|
||||
} else {
|
||||
logger.Debug(fmt.Sprintf("'%s' LibreOffice's user profile directory removed", userProfileDirPath))
|
||||
}
|
||||
|
||||
logger.Debug(fmt.Sprintf("'%s' LibreOffice's user profile directory removed", userProfileDirPath))
|
||||
|
||||
// Also remove LibreOffice specific files in the temporary directory.
|
||||
err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{"OSL_PIPE", ".tmp"})
|
||||
err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{"OSL_PIPE", ".tmp"}, expirationTime)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
}(copyUserProfileDirPath)
|
||||
}(copyUserProfileDirPath, expirationTime)
|
||||
|
||||
p.cfgMu.Lock()
|
||||
defer p.cfgMu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user