Files
yatm/internal/executor/device.go
T
Samuel Cui 4fef653b71 Prepare YATM v1.0.0-alpha.2
Unify file queries and Scan execution, retain indexed Identical results,
improve Job and Preview workflows, and verify release packaging.
2026-10-05 19:08:49 +08:00

113 lines
3.4 KiB
Go

package executor
import (
"context"
"fmt"
"sort"
)
// ListAvailableDevices reports configured Tape drives that no operation currently holds.
func (e *Executor) ListAvailableDevices() []string {
devices := make([]string, 0, len(e.devices))
for _, device := range e.devices {
if !e.resourceHeld(tapeResource(device)) {
devices = append(devices, device)
}
}
sort.Strings(devices)
return devices
}
// OccupyDevice reserves a configured drive for one operator request without waiting.
func (e *Executor) OccupyDevice(dev string) bool {
if !e.configuredDevice(dev) {
return false
}
_, acquired := e.tryAcquire(tapeResource(dev))
return acquired
}
// ReleaseDevice returns a drive reserved by OccupyDevice.
func (e *Executor) ReleaseDevice(dev string) {
e.resources.release(tapeResource(dev))
}
func (e *Executor) configuredDevice(dev string) bool {
for _, device := range e.devices {
if device == dev {
return true
}
}
return false
}
// AcquireTapeDevice waits for a configured Tape drive and holds it until the Job attempt ends.
// Contention with another Client or operator request waits instead of failing the Job.
func (e *Executor) AcquireTapeDevice(ctx context.Context, jobID int64, device string, onWait func()) error {
// An unknown drive is a configuration error, not contention to wait for.
if !e.configuredDevice(device) {
return fmt.Errorf("Tape device is not configured, device=%q", device)
}
return e.holdAttemptResource(ctx, jobID, tapeResource(device), onWait)
}
// AcquireVolume waits for one mounted Volume identity and holds it until the Job attempt ends.
func (e *Executor) AcquireVolume(ctx context.Context, jobID int64, uuid string, onWait func()) error {
return e.holdAttemptResource(ctx, jobID, volumeResource(uuid), onWait)
}
// holdAttemptResource waits for one shared resource and registers it on the Job attempt, so the
// attempt end releases every hold even when the caller never reaches its cleanup.
func (e *Executor) holdAttemptResource(ctx context.Context, jobID int64, key string, onWait func()) error {
// Reuse this attempt's lease when identity resolution and Session setup need the same resource.
e.attemptsLock.Lock()
current := e.attempts[jobID]
if current == nil {
e.attemptsLock.Unlock()
return fmt.Errorf("job attempt is not active, id=%d", jobID)
}
for _, resource := range current.resources {
if resource.key == key {
e.attemptsLock.Unlock()
return nil
}
}
e.attemptsLock.Unlock()
release, err := e.AcquireJobResource(ctx, key, onWait)
if err != nil {
return err
}
// Register the hold before it is used; an attempt that ended meanwhile gives it back.
e.attemptsLock.Lock()
current = e.attempts[jobID]
if current == nil {
e.attemptsLock.Unlock()
release()
return fmt.Errorf("job attempt ended while acquiring %q", key)
}
current.resources = append(current.resources, attemptResource{key: key})
e.attemptsLock.Unlock()
return nil
}
// keepTapeDeviceUnavailable retains a drive whose physical cleanup failed, so later attempts
// wait for an explicit investigation instead of reusing unknown device state.
func (e *Executor) keepTapeDeviceUnavailable(jobID int64, device string) {
e.attemptsLock.Lock()
defer e.attemptsLock.Unlock()
current := e.attempts[jobID]
if current == nil {
return
}
for index := range current.resources {
resource := &current.resources[index]
if resource.key == tapeResource(device) {
resource.retain = true
return
}
}
}