package config import ( "fmt" "os" "path/filepath" "sync" "douyu_go/internal/constants" "douyu_go/internal/types" ) const maxInstanceSlots = 256 // InstanceLease 代表当前进程持有的实例槽位;必须在程序退出时释放。 type InstanceLease struct { paths types.AppPaths name string lock instanceLock releaseOnce sync.Once } // AcquireInstancePaths 按顺序占用空闲实例。第一个进程使用默认实例, // 后续进程分别使用实例 2、实例 3,以隔离账号、配置与运行数据。 func AcquireInstancePaths() (*InstanceLease, error) { baseDir := defaultDataDir() instanceRoot := filepath.Join(baseDir, "instances") lockDir := filepath.Join(instanceRoot, ".locks") if err := osMkdirAll(lockDir); err != nil { return nil, fmt.Errorf("创建实例锁目录失败: %w", err) } for slot := 1; slot <= maxInstanceSlots; slot++ { lock, acquired, err := tryAcquireInstanceLock(filepath.Join(lockDir, instanceLockFileName(slot))) if err != nil { return nil, fmt.Errorf("锁定%s失败: %w", instanceName(slot), err) } if !acquired { continue } dataDir := baseDir if slot > 1 { dataDir = filepath.Join(instanceRoot, fmt.Sprintf("instance-%d", slot)) } paths := appPathsForDataDir(dataDir, slot == 1) if slot > 1 && isWailsDev() && os.Getenv("DOUYU_LOG_DIR") == "" { paths.LogDir = resolvePath(filepath.Join(constants.DefaultLogDir, "instances", fmt.Sprintf("instance-%d", slot))) } return &InstanceLease{ paths: paths, name: instanceName(slot), lock: lock, }, nil } return nil, fmt.Errorf("最多支持同时运行 %d 个实例", maxInstanceSlots) } // Paths 返回当前实例独立的数据路径。 func (l *InstanceLease) Paths() types.AppPaths { if l == nil { return types.AppPaths{} } return l.paths } // Name 返回面向用户的实例名称。 func (l *InstanceLease) Name() string { if l == nil { return "" } return l.name } // Release 释放实例锁,允许新启动的程序复用该实例。 func (l *InstanceLease) Release() { if l == nil || l.lock == nil { return } l.releaseOnce.Do(func() { _ = l.lock.Close() }) } func instanceName(slot int) string { if slot <= 1 { return "默认实例" } return fmt.Sprintf("实例 %d", slot) } func instanceLockFileName(slot int) string { if slot <= 1 { return "default.lock" } return fmt.Sprintf("instance-%d.lock", slot) }