101 lines
2.1 KiB
Go
101 lines
2.1 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/openclaw/discrawl/internal/config"
|
|
)
|
|
|
|
func (r *runtime) withSyncLock(fn func() error) error {
|
|
if r.dbLockHeld {
|
|
return fn()
|
|
}
|
|
lockPath, err := r.syncLockPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
release, err := acquireSyncLock(r.ctx, lockPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.dbLockHeld = true
|
|
r.lockStarted = r.nowUTC()
|
|
r.setSyncLockPhase("locked")
|
|
defer func() {
|
|
r.dbLockHeld = false
|
|
r.lockStarted = time.Time{}
|
|
_ = release()
|
|
}()
|
|
return fn()
|
|
}
|
|
|
|
func (r *runtime) tryWithSyncLock(fn func() error) (bool, error) {
|
|
if r.dbLockHeld {
|
|
return true, fn()
|
|
}
|
|
lockPath, err := r.syncLockPath()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
release, locked, err := tryAcquireSyncLock(lockPath)
|
|
if err != nil || !locked {
|
|
return locked, err
|
|
}
|
|
r.dbLockHeld = true
|
|
r.lockStarted = r.nowUTC()
|
|
r.setSyncLockPhase("locked")
|
|
defer func() {
|
|
r.dbLockHeld = false
|
|
r.lockStarted = time.Time{}
|
|
_ = release()
|
|
}()
|
|
return true, fn()
|
|
}
|
|
|
|
func (r *runtime) setSyncLockPhase(phase string) {
|
|
if !r.dbLockHeld {
|
|
return
|
|
}
|
|
path, err := r.syncLockPath()
|
|
if err != nil {
|
|
return
|
|
}
|
|
started := r.lockStarted
|
|
if started.IsZero() {
|
|
started = r.nowUTC()
|
|
}
|
|
body := fmt.Sprintf("pid=%d\nstarted_at=%s\nupdated_at=%s\nphase=%s\n",
|
|
os.Getpid(),
|
|
started.Format(time.RFC3339Nano),
|
|
r.nowUTC().Format(time.RFC3339Nano),
|
|
phase,
|
|
)
|
|
_ = os.WriteFile(path, []byte(body), 0o600)
|
|
}
|
|
|
|
func (r *runtime) syncLockPath() (string, error) {
|
|
dbPath, err := config.ExpandPath(r.cfg.DBPath)
|
|
if err != nil {
|
|
return "", configErr(err)
|
|
}
|
|
return filepath.Join(filepath.Dir(dbPath), ".discrawl-sync.lock"), nil
|
|
}
|
|
|
|
func syncLockErr(ctx context.Context, path string) error {
|
|
if ctx.Err() != nil {
|
|
if body, err := os.ReadFile(path); err == nil {
|
|
details := strings.TrimSpace(string(body))
|
|
if details != "" {
|
|
return fmt.Errorf("wait for sync lock %s (%s): %w", path, strings.ReplaceAll(details, "\n", ", "), ctx.Err())
|
|
}
|
|
}
|
|
return fmt.Errorf("wait for sync lock %s: %w", path, ctx.Err())
|
|
}
|
|
return nil
|
|
}
|