Guard arr cache writes against in-flight rescans, add rescan monitor tool
A direct arr_cache_write() call mid-rescan wrote a partial snapshot that looked like real data loss to every consumer of the cache. The guard now lives in arr_cache_write() itself so every caller is protected, not just arr_get_tracked_data(). arr_rescan_monitor.sh closes the resulting gap for rescans triggered outside arr_full_rescan.sh's own trigger-and-wait path.
This commit is contained in:
@@ -2287,6 +2287,13 @@ declare -gA ARR_LIBRARY_ENDPOINT=(
|
||||
[radarr]="movie"
|
||||
)
|
||||
|
||||
# API version per arr — Lidarr is still v1, Sonarr/Radarr are v3.
|
||||
declare -gA ARR_API_VERSION=(
|
||||
[lidarr]="v1"
|
||||
[sonarr]="v3"
|
||||
[radarr]="v3"
|
||||
)
|
||||
|
||||
arr_cache_file() { echo "${DATA_DIR}/${1}_tracked_cache.json"; }
|
||||
arr_rescan_duration_db() { echo "${DATA_DIR}/${1}_rescan_duration.db"; }
|
||||
|
||||
@@ -2296,11 +2303,31 @@ arr_rescan_duration_db() { echo "${DATA_DIR}/${1}_rescan_duration.db"; }
|
||||
# same class of bug the queue-pagination fix (2026-07-16, arrs_failed_stalled_recovery.sh)
|
||||
# hit before. Fixed the same way: write the payload to a temp file and use --slurpfile,
|
||||
# which reads from disk instead of argv.
|
||||
#
|
||||
# Refuses to write while a rescan-type command is active for arr_type (2026-07-17). Every
|
||||
# direct caller here fetches the library list for its own purposes and writes it through as
|
||||
# a side effect — none of them go through arr_get_tracked_data()'s fresh/stale/active-rescan
|
||||
# branching, so none of them knew to check scan state first. Confirmed live: a caller wrote
|
||||
# Lidarr's cache mid-RescanFolders at 22% of the real total, and that partial number then
|
||||
# looked exactly like real data loss to every consumer (check_tracked_count_floor et al.)
|
||||
# until the scan finished. The guard lives here instead of in each caller so it applies
|
||||
# uniformly — last-known-good stays in place until something writes through the real
|
||||
# post-scan number (arr_full_rescan.sh does this itself on completion; for a rescan someone
|
||||
# else triggered, see arr_rescan_monitor.sh in Tools/).
|
||||
# Args: arr_type, items_json (the full library-list response body)
|
||||
arr_cache_write() {
|
||||
local arr_type="$1" items_json="$2"
|
||||
local expr="${ARR_TRACKED_COUNT_EXPR[$arr_type]:-}"
|
||||
[[ -z "$expr" ]] && return 1
|
||||
|
||||
local url_var="${arr_type^^}_URL" key_var="${arr_type^^}_API_KEY"
|
||||
local url="${!url_var:-}" api_key="${!key_var:-}" api_version="${ARR_API_VERSION[$arr_type]:-}"
|
||||
if [[ -n "$url" && -n "$api_key" && -n "$api_version" ]]; then
|
||||
local active_cmd
|
||||
active_cmd=$(arr_active_rescan_command "$arr_type" "$url" "$api_key" "$api_version")
|
||||
[[ -n "$active_cmd" ]] && return 1
|
||||
fi
|
||||
|
||||
local total
|
||||
total=$(echo "$items_json" | jq "$expr" 2>/dev/null)
|
||||
[[ -z "$total" || "$total" == "null" ]] && return 1
|
||||
@@ -2388,6 +2415,29 @@ arr_active_rescan_command() {
|
||||
)] | .[0].name // empty' 2>/dev/null
|
||||
}
|
||||
|
||||
# Waits for an already-active rescan-type command for arr_type to finish — unlike
|
||||
# trigger_and_await_command(), this never triggers anything, it only watches. For a rescan
|
||||
# someone/something else started (manual intervention, another script) where the caller just
|
||||
# needs to know when it's safe to fetch+cache the real post-scan numbers. Returns 0 once no
|
||||
# active rescan-type command remains (including immediately if none was active to begin
|
||||
# with), 1 on timeout.
|
||||
# Args: arr_type, url, api_key, api_version, poll_timeout (default 7200s), poll_interval (default 15s)
|
||||
arr_wait_for_active_rescan() {
|
||||
local arr_type="$1" url="$2" api_key="$3" api_version="$4"
|
||||
local poll_timeout="${5:-7200}" poll_interval="${6:-15}"
|
||||
local polled=0 active
|
||||
active=$(arr_active_rescan_command "$arr_type" "$url" "$api_key" "$api_version")
|
||||
[[ -z "$active" ]] && return 0
|
||||
while [[ "$polled" -lt "$poll_timeout" ]]; do
|
||||
sleep "$poll_interval"
|
||||
(( polled += poll_interval ))
|
||||
active=$(arr_active_rescan_command "$arr_type" "$url" "$api_key" "$api_version")
|
||||
[[ -z "$active" ]] && return 0
|
||||
[[ $(( polled % 300 )) -lt "$poll_interval" ]] && log " Still waiting on ${arr_type}'s ${active} (${polled}s elapsed)"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Main entry point — the only function consuming scripts should call for tracked library
|
||||
# data. Handles fresh/stale-no-rescan/stale-rescan-active branching and always writes through
|
||||
# on any live fetch it performs. Echoes the library-list JSON array on success, returns 1 if
|
||||
|
||||
Reference in New Issue
Block a user