* feat(core): add single-instance guard to prevent duplicate processes When two cc-connect instances run with the same config, both poll the same connections and process messages, causing duplicate replies. Changes: - Add instance_lock.go with flock-based exclusive locking (Unix) - Add instance_lock_windows.go stub for Windows compatibility - Lock file is placed in config directory as .<config>.lock - Add --force flag to kill existing instance before starting - Write PID to lock file for better error messages Fixes #439 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(lint): handle errcheck errors in instance_lock.go Explicitly ignore return values from Truncate, Seek, Flock, Remove, and WriteFile calls where the errors are non-critical (best-effort cleanup and diagnostics). Fixes CI lint failure on PR #440. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
52 lines
1.2 KiB
Go
52 lines
1.2 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// InstanceLock is a no-op on Windows for now.
|
|
// TODO: implement proper Windows locking using CreateFile with exclusive mode.
|
|
type InstanceLock struct {
|
|
path string
|
|
}
|
|
|
|
// AcquireInstanceLock attempts to acquire an exclusive lock for the given config path.
|
|
// On Windows, this currently always succeeds (no-op).
|
|
func AcquireInstanceLock(configPath string) (*InstanceLock, error) {
|
|
configDir := filepath.Dir(configPath)
|
|
configBase := filepath.Base(configPath)
|
|
lockName := fmt.Sprintf(".%s.lock", configBase)
|
|
lockPath := filepath.Join(configDir, lockName)
|
|
|
|
// Write our PID to the lock file for diagnostics
|
|
pid := os.Getpid()
|
|
// Non-fatal on Windows
|
|
_ = os.WriteFile(lockPath, []byte(fmt.Sprintf("%d\n", pid)), 0644)
|
|
|
|
return &InstanceLock{path: lockPath}, nil
|
|
}
|
|
|
|
// Release releases the instance lock.
|
|
func (l *InstanceLock) Release() {
|
|
if l == nil {
|
|
return
|
|
}
|
|
// Remove lock file
|
|
if l.path != "" {
|
|
_ = os.Remove(l.path)
|
|
}
|
|
}
|
|
|
|
// Path returns the path to the lock file.
|
|
func (l *InstanceLock) Path() string {
|
|
return l.path
|
|
}
|
|
|
|
// KillExistingInstance is not implemented on Windows.
|
|
func KillExistingInstance(configPath string) bool {
|
|
return false
|
|
} |