A cap that aborts cannot drain a backlog bigger than itself, so make it a per-run budget and let the queue clear over consecutive nights

This commit is contained in:
Gmer4Lfe
2026-08-26 17:55:21 -04:00
parent 7f4921de49
commit 0431e720de
2 changed files with 87 additions and 6 deletions
Executable → Regular
+48
View File
@@ -2734,6 +2734,54 @@ check_delete_size_threshold() {
fi
}
# Applies a per-run delete budget instead of refusing the whole run. Reads "<size>\t<ctime>\t<path>"
# from $1, writes the paths that fit inside $3 GB to $2 (oldest ctime first), and reports what was
# held back in _BUDGET_*.
#
# A cap that aborts cannot drain a backlog larger than itself. Every run re-finds the same lump,
# exceeds the same ceiling and stops, so the queue is stuck permanently and only a human with
# --i-know-what-im-doing can clear it. Observed 2026-08-24..26: a partnership merge restored ten
# months of superseded Radarr files in one night, 126GB against a 100GB cap, and the daily
# orchestrator then failed three nights running on a queue it could never drain. Budgeting keeps
# the ceiling meaning exactly what it meant — no single run removes more than max_gb — while the
# backlog clears over consecutive runs with nobody touching it.
#
# Strict oldest-first, stopping at the first file that does not fit rather than skipping it for a
# smaller one further down. Skipping would drain more per run but lets a large file be passed over
# indefinitely; stopping guarantees every deferred file is newer than everything already removed,
# so nothing can starve.
#
# One case is deliberately left for a human: a single file larger than the entire budget can never
# fit, so _BUDGET_STUCK is set and the caller says so rather than looping forever on it.
apply_delete_budget() {
local src="$1" dst="$2" max_gb="$3"
local max_bytes tab
tab=$(printf '\t')
max_bytes=$(awk "BEGIN {printf \"%d\", $max_gb * 1073741824}")
_BUDGET_KEPT_BYTES=0; _BUDGET_KEPT_COUNT=0
_BUDGET_DEFERRED_BYTES=0; _BUDGET_DEFERRED_COUNT=0
_BUDGET_STUCK=""
: > "$dst"
[[ -s "$src" ]] || return 0
local out
out=$(sort -t"$tab" -k2,2n "$src" | awk -F'\t' -v max="$max_bytes" -v dst="$dst" '
{
if (stop) { defb += $1; defc++; next }
if (used + $1 > max) { stop = 1
if (kept == 0) stuck = $3
defb += $1; defc++; next }
used += $1; kept++
print $3 > dst
}
END { printf "%d\t%d\t%d\t%d\t%s", used+0, kept+0, defb+0, defc+0, stuck }
')
IFS=$'\t' read -r _BUDGET_KEPT_BYTES _BUDGET_KEPT_COUNT \
_BUDGET_DEFERRED_BYTES _BUDGET_DEFERRED_COUNT _BUDGET_STUCK <<< "$out"
}
# True if filepath's extension (case-insensitive) matches one of the given extensions.
# Usage: has_extension "$filepath" "${LIDARR_EXTENSIONS[@]}"
has_extension() {