mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2025-12-06 13:54:41 +08:00
101 lines
2.1 KiB
Go
101 lines
2.1 KiB
Go
package backtest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
lockFileName = "lock"
|
|
lockHeartbeatInterval = 2 * time.Second
|
|
lockStaleAfter = 10 * time.Second
|
|
)
|
|
|
|
// RunLockInfo 表示回测运行的锁文件结构。
|
|
type RunLockInfo struct {
|
|
RunID string `json:"run_id"`
|
|
PID int `json:"pid"`
|
|
Host string `json:"host"`
|
|
StartedAt time.Time `json:"started_at"`
|
|
LastHeartbeat time.Time `json:"last_heartbeat"`
|
|
}
|
|
|
|
func lockFilePath(runID string) string {
|
|
return filepath.Join(runDir(runID), lockFileName)
|
|
}
|
|
|
|
func loadRunLock(runID string) (*RunLockInfo, error) {
|
|
path := lockFilePath(runID)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var info RunLockInfo
|
|
if err := json.Unmarshal(data, &info); err != nil {
|
|
return nil, err
|
|
}
|
|
return &info, nil
|
|
}
|
|
|
|
func saveRunLock(info *RunLockInfo) error {
|
|
if info == nil {
|
|
return fmt.Errorf("lock info nil")
|
|
}
|
|
return writeJSONAtomic(lockFilePath(info.RunID), info)
|
|
}
|
|
|
|
func deleteRunLock(runID string) error {
|
|
err := os.Remove(lockFilePath(runID))
|
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func lockIsStale(info *RunLockInfo) bool {
|
|
if info == nil {
|
|
return true
|
|
}
|
|
return time.Since(info.LastHeartbeat) > lockStaleAfter
|
|
}
|
|
|
|
func acquireRunLock(runID string) (*RunLockInfo, error) {
|
|
if err := ensureRunDir(runID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if existing, err := loadRunLock(runID); err == nil {
|
|
if !lockIsStale(existing) {
|
|
return nil, fmt.Errorf("run %s is locked by pid %d", runID, existing.PID)
|
|
}
|
|
} else if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
return nil, err
|
|
}
|
|
|
|
host, _ := os.Hostname()
|
|
info := &RunLockInfo{
|
|
RunID: runID,
|
|
PID: os.Getpid(),
|
|
Host: host,
|
|
StartedAt: time.Now().UTC(),
|
|
LastHeartbeat: time.Now().UTC(),
|
|
}
|
|
|
|
if err := saveRunLock(info); err != nil {
|
|
return nil, err
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
func updateRunLockHeartbeat(info *RunLockInfo) error {
|
|
if info == nil {
|
|
return fmt.Errorf("lock info nil")
|
|
}
|
|
info.LastHeartbeat = time.Now().UTC()
|
|
return saveRunLock(info)
|
|
}
|