Files
cursor-byok/internal/backend/forwarder/history_maintenance.go
T
2026-06-30 10:38:52 +08:00

173 lines
4.3 KiB
Go

package forwarder
import (
"errors"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
const historyMaintenanceLockStaleAfter = 30 * time.Minute
const historyMaintenanceTempStaleAfter = 5 * time.Minute
func (service *Service) startHistoryMaintenance() {
if service == nil || service.store == nil {
return
}
go func() {
if err := service.runHistoryMaintenance(); err != nil {
log.Printf("forwarder history maintenance failed: %v", err)
}
}()
}
func (service *Service) runHistoryMaintenance() error {
historyRoot := strings.TrimSpace(service.store.HistoryDir())
if historyRoot == "" {
return nil
}
release, ok, err := acquireHistoryMaintenanceLock(historyRoot)
if err != nil || !ok {
return err
}
defer release()
entries, err := os.ReadDir(historyRoot)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
for _, entry := range entries {
if !entry.IsDir() {
cleanupRootLegacyHistoryArtifact(historyRoot, entry.Name())
continue
}
service.cleanupConversationLegacyArtifacts(filepath.Join(historyRoot, entry.Name()))
}
return nil
}
func cleanupRootLegacyHistoryArtifact(historyRoot string, name string) {
trimmedName := strings.TrimSpace(name)
if trimmedName == "" || trimmedName == ".history-maintenance.lock" {
return
}
if !isRootLegacyHistoryArtifact(trimmedName) {
return
}
path := filepath.Join(historyRoot, trimmedName)
if strings.Contains(trimmedName, ".tmp-") {
cleanupStaleHistoryTempArtifact(path)
return
}
if err := os.RemoveAll(path); err != nil && !errors.Is(err, os.ErrNotExist) {
log.Printf("forwarder root legacy cleanup failed path=%s err=%v", path, err)
}
}
func isRootLegacyHistoryArtifact(name string) bool {
if strings.TrimSpace(name) == usageFileName || strings.TrimSpace(name) == usageFileName+".lock" {
return false
}
if strings.HasSuffix(name, ".json") || strings.HasSuffix(name, ".jsonl") {
return true
}
if strings.Contains(name, ".tmp-") {
return true
}
if strings.HasSuffix(name, ".lock") {
return true
}
return false
}
func (service *Service) cleanupConversationLegacyArtifacts(conversationDir string) {
for _, name := range []string{
"turns",
"active",
"latest.json",
"summary.json",
"replay.json",
"runtime.json",
"request.json",
"recovery.json",
"conversation.json",
"entries.jsonl",
} {
path := filepath.Join(conversationDir, name)
if err := os.RemoveAll(path); err != nil && !errors.Is(err, os.ErrNotExist) {
log.Printf("forwarder legacy cleanup failed path=%s err=%v", path, err)
}
}
entries, err := os.ReadDir(conversationDir)
if err != nil {
return
}
for _, entry := range entries {
if !entry.IsDir() {
if strings.Contains(entry.Name(), ".tmp-") {
cleanupStaleHistoryTempArtifact(filepath.Join(conversationDir, entry.Name()))
}
continue
}
if _, err := strconv.Atoi(strings.TrimSpace(entry.Name())); err != nil {
continue
}
path := filepath.Join(conversationDir, entry.Name())
if err := os.RemoveAll(path); err != nil && !errors.Is(err, os.ErrNotExist) {
log.Printf("forwarder numeric legacy cleanup failed path=%s err=%v", path, err)
}
}
}
func cleanupStaleHistoryTempArtifact(path string) {
info, err := os.Stat(path)
if err != nil {
return
}
if time.Since(info.ModTime()) < historyMaintenanceTempStaleAfter {
return
}
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
log.Printf("forwarder temp cleanup failed path=%s err=%v", path, err)
}
}
func acquireHistoryMaintenanceLock(historyRoot string) (func(), bool, error) {
if err := os.MkdirAll(historyRoot, 0o755); err != nil {
return nil, false, err
}
lockPath := filepath.Join(historyRoot, ".history-maintenance.lock")
for attempt := 0; attempt < 2; attempt++ {
file, err := os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if err == nil {
_, _ = file.WriteString(time.Now().UTC().Format(time.RFC3339Nano))
_ = file.Close()
return func() {
_ = os.Remove(lockPath)
}, true, nil
}
if !errors.Is(err, os.ErrExist) {
return nil, false, err
}
info, statErr := os.Stat(lockPath)
if statErr != nil {
if errors.Is(statErr, os.ErrNotExist) {
continue
}
return nil, false, statErr
}
if time.Since(info.ModTime()) > historyMaintenanceLockStaleAfter {
_ = os.Remove(lockPath)
continue
}
return nil, false, nil
}
return nil, false, nil
}