Add qvm doctor command to diagnose and fix common issues
This commit is contained in:
parent
2aec01b3b2
commit
eb469f1cd8
8 changed files with 660 additions and 170 deletions
78
internal/lock/lock.go
Normal file
78
internal/lock/lock.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Package lock provides file-based locking for VM operations.
|
||||
package lock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/samber/mo"
|
||||
"qvm/internal/config"
|
||||
)
|
||||
|
||||
// Lock represents an exclusive file lock on VM operations.
|
||||
type Lock struct {
|
||||
file *os.File
|
||||
}
|
||||
|
||||
var lockPath = filepath.Join(config.StateDir, "vm.lock")
|
||||
|
||||
// Acquire obtains an exclusive lock on VM operations.
|
||||
// Blocks until the lock is available or timeout is reached.
|
||||
func Acquire(timeout time.Duration) mo.Result[*Lock] {
|
||||
if err := os.MkdirAll(config.StateDir, 0755); err != nil {
|
||||
return mo.Err[*Lock](fmt.Errorf("failed to create state directory: %w", err))
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
return mo.Err[*Lock](fmt.Errorf("failed to open lock file: %w", err))
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
err := syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err == nil {
|
||||
return mo.Ok(&Lock{file: file})
|
||||
}
|
||||
|
||||
if time.Now().After(deadline) {
|
||||
file.Close()
|
||||
return mo.Err[*Lock](fmt.Errorf("timeout waiting for VM lock after %v", timeout))
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// TryAcquire attempts to obtain an exclusive lock without blocking.
|
||||
// Returns error if lock is held by another process.
|
||||
func TryAcquire() mo.Result[*Lock] {
|
||||
if err := os.MkdirAll(config.StateDir, 0755); err != nil {
|
||||
return mo.Err[*Lock](fmt.Errorf("failed to create state directory: %w", err))
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
return mo.Err[*Lock](fmt.Errorf("failed to open lock file: %w", err))
|
||||
}
|
||||
|
||||
err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return mo.Err[*Lock](fmt.Errorf("VM operation in progress by another process"))
|
||||
}
|
||||
|
||||
return mo.Ok(&Lock{file: file})
|
||||
}
|
||||
|
||||
// Release releases the lock.
|
||||
func (l *Lock) Release() error {
|
||||
if l.file == nil {
|
||||
return nil
|
||||
}
|
||||
syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN)
|
||||
return l.file.Close()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue