Fix rsync.sh global lock bug and add a max-runtime cap
acquire_lock (no args) ran before profile inference, so every rsync.sh invocation — regardless of share — fought over one generic, unparameterized lock. The per-profile acquire_rsync_lock() further down (with RSYNC_MAX_CONCURRENT) never got a chance to matter: a single slow transfer (e.g. Movies during the HOST2 rebuild) monopolized the lock and starved every other profile, including Critical-Data's 30-minute sync, for days. Removed the generic acquire_lock call; acquire_rsync_lock "$PROFILE_NAME" already provides correct per-profile locking on its own. Also added RSYNC_MAX_RUNTIME_HOURS (default 23): any single transfer attempt exceeding it is terminated via timeout, logged as paused rather than failed, and resumes from where it left off next scheduled run (safe because --partial is already in DEFAULT_RSYNC_OPTS). Bounds the worst case for one huge/stuck share instead of letting it hold its lock indefinitely.
This commit is contained in:
@@ -531,6 +531,7 @@
|
|||||||
BW_LIMIT=12500 # KB/s — 12500 ≈ 100Mbit
|
BW_LIMIT=12500 # KB/s — 12500 ≈ 100Mbit
|
||||||
RETRY_COUNT=3 # retry attempts before giving up
|
RETRY_COUNT=3 # retry attempts before giving up
|
||||||
SLEEP=300 # seconds between retry attempts
|
SLEEP=300 # seconds between retry attempts
|
||||||
|
RSYNC_MAX_RUNTIME_HOURS=23 # cap per transfer attempt — pauses and resumes next scheduled run
|
||||||
CRITICAL_CONTAINER_NAMES=() # containers stopped on REMOTE before rsync — profiles override
|
CRITICAL_CONTAINER_NAMES=() # containers stopped on REMOTE before rsync — profiles override
|
||||||
DELAYED_CONTAINERS=() # containers needing delay before starting — profiles override
|
DELAYED_CONTAINERS=() # containers needing delay before starting — profiles override
|
||||||
CONTAINER_DELAY=5 # seconds before starting delayed containers
|
CONTAINER_DELAY=5 # seconds before starting delayed containers
|
||||||
|
|||||||
+29
-7
@@ -98,6 +98,13 @@
|
|||||||
# SLEEP
|
# SLEEP
|
||||||
# Default seconds between retry attempts. (default: 60)
|
# Default seconds between retry attempts. (default: 60)
|
||||||
#
|
#
|
||||||
|
# RSYNC_MAX_RUNTIME_HOURS
|
||||||
|
# Max hours a single transfer attempt may run before it's terminated and paused
|
||||||
|
# for the next scheduled run. Protects the per-profile lock from being held
|
||||||
|
# indefinitely by one huge/stuck transfer, starving other profiles of a turn.
|
||||||
|
# Safe because DEFAULT_RSYNC_OPTS includes --partial — a paused transfer resumes
|
||||||
|
# from where it left off, not from scratch. (default: 23)
|
||||||
|
#
|
||||||
# ROOTFS_WARN
|
# ROOTFS_WARN
|
||||||
# Abort threshold for remote rootfs percentage full. (default: 75)
|
# Abort threshold for remote rootfs percentage full. (default: 75)
|
||||||
#
|
#
|
||||||
@@ -177,8 +184,6 @@ if [[ "$EUID" -ne 0 ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
acquire_lock
|
|
||||||
|
|
||||||
if ! command -v docker &>/dev/null; then
|
if ! command -v docker &>/dev/null; then
|
||||||
error "Docker command not found"
|
error "Docker command not found"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -232,6 +237,8 @@ BW_LIMIT=${PROFILE_BW_LIMIT[$PROFILE_NAME]:-$BW_LIMIT}
|
|||||||
RETRY_COUNT=${PROFILE_RETRY_COUNT[$PROFILE_NAME]:-$RETRY_COUNT}
|
RETRY_COUNT=${PROFILE_RETRY_COUNT[$PROFILE_NAME]:-$RETRY_COUNT}
|
||||||
SLEEP=${PROFILE_SLEEP[$PROFILE_NAME]:-$SLEEP}
|
SLEEP=${PROFILE_SLEEP[$PROFILE_NAME]:-$SLEEP}
|
||||||
CONTAINER_DELAY=${PROFILE_CONTAINER_DELAY[$PROFILE_NAME]:-$CONTAINER_DELAY}
|
CONTAINER_DELAY=${PROFILE_CONTAINER_DELAY[$PROFILE_NAME]:-$CONTAINER_DELAY}
|
||||||
|
RSYNC_MAX_RUNTIME_HOURS=${RSYNC_MAX_RUNTIME_HOURS:-23}
|
||||||
|
RSYNC_MAX_RUNTIME_SECONDS=$(( RSYNC_MAX_RUNTIME_HOURS * 3600 ))
|
||||||
|
|
||||||
read -r -a CRITICAL_CONTAINER_NAMES <<< "${PROFILE_CRITICAL_CONTAINER_NAMES[$PROFILE_NAME]:-}"
|
read -r -a CRITICAL_CONTAINER_NAMES <<< "${PROFILE_CRITICAL_CONTAINER_NAMES[$PROFILE_NAME]:-}"
|
||||||
read -r -a DELAYED_CONTAINERS <<< "${PROFILE_DELAYED_CONTAINERS[$PROFILE_NAME]:-}"
|
read -r -a DELAYED_CONTAINERS <<< "${PROFILE_DELAYED_CONTAINERS[$PROFILE_NAME]:-}"
|
||||||
@@ -284,13 +291,15 @@ if [[ "$MERGE_RUN" == false && "$SEED" == false \
|
|||||||
&& -z "${PROFILE_RSYNC_OPTS[$PROFILE_NAME]+x}" ]]; then
|
&& -z "${PROFILE_RSYNC_OPTS[$PROFILE_NAME]+x}" ]]; then
|
||||||
_remote_top=$(ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes \
|
_remote_top=$(ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes \
|
||||||
root@"$REMOTE_SERVER" "ls -1A '$DIRECTORY' 2>/dev/null | sort" 2>/dev/null)
|
root@"$REMOTE_SERVER" "ls -1A '$DIRECTORY' 2>/dev/null | sort" 2>/dev/null)
|
||||||
_remote_count=$(echo "$_remote_top" | grep -c . 2>/dev/null || echo 0)
|
_remote_count=$(echo "$_remote_top" | grep -c .)
|
||||||
|
_remote_count=${_remote_count:-0}
|
||||||
|
|
||||||
if [[ "$_remote_count" -gt 0 ]]; then
|
if [[ "$_remote_count" -gt 0 ]]; then
|
||||||
_local_top=$(ls -1A "$DIRECTORY" 2>/dev/null | sort)
|
_local_top=$(ls -1A "$DIRECTORY" 2>/dev/null | sort)
|
||||||
_overlap=$(comm -12 \
|
_overlap=$(comm -12 \
|
||||||
<(echo "$_local_top") \
|
<(echo "$_local_top") \
|
||||||
<(echo "$_remote_top") | grep -c . 2>/dev/null || echo 0)
|
<(echo "$_remote_top") | grep -c .)
|
||||||
|
_overlap=${_overlap:-0}
|
||||||
_overlap_pct=$(( _overlap * 100 / _remote_count ))
|
_overlap_pct=$(( _overlap * 100 / _remote_count ))
|
||||||
|
|
||||||
if [[ "$_overlap_pct" -ge 75 ]]; then
|
if [[ "$_overlap_pct" -ge 75 ]]; then
|
||||||
@@ -375,6 +384,9 @@ if [[ "$MERGE_RUN" == true ]]; then
|
|||||||
echo "$ICON_DONE Merge pass 1 complete — ${_pull_bytes:-0} bytes pulled"
|
echo "$ICON_DONE Merge pass 1 complete — ${_pull_bytes:-0} bytes pulled"
|
||||||
else
|
else
|
||||||
warn "Merge pass 1 failed (exit $_pull_exit) — continuing with push"
|
warn "Merge pass 1 failed (exit $_pull_exit) — continuing with push"
|
||||||
|
echo "$_pull_output" | grep -iE "error|rsync:|permission denied" | while read -r _line; do
|
||||||
|
warn " $_line"
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
unset _pull_opts _pull_output _pull_exit _pull_bytes
|
unset _pull_opts _pull_output _pull_exit _pull_bytes
|
||||||
# Pass 2: push with --delete — local is now the authoritative superset
|
# Pass 2: push with --delete — local is now the authoritative superset
|
||||||
@@ -383,14 +395,15 @@ fi
|
|||||||
|
|
||||||
START=$(date +%s)
|
START=$(date +%s)
|
||||||
RSYNC_SUCCESS=false
|
RSYNC_SUCCESS=false
|
||||||
|
RSYNC_TIMED_OUT=false
|
||||||
BYTES_TRANSFERRED=0
|
BYTES_TRANSFERRED=0
|
||||||
ATTEMPT=0
|
ATTEMPT=0
|
||||||
|
|
||||||
for (( ATTEMPT=1; ATTEMPT<=RETRY_COUNT; ATTEMPT++ )); do
|
for (( ATTEMPT=1; ATTEMPT<=RETRY_COUNT; ATTEMPT++ )); do
|
||||||
log "$ICON_RETRY Attempt $ATTEMPT of $RETRY_COUNT..."
|
log "$ICON_RETRY Attempt $ATTEMPT of $RETRY_COUNT..."
|
||||||
echo "$ICON_SYNC Rsync running — this may take a while..."
|
echo "$ICON_SYNC Rsync running — this may take a while (capped at ${RSYNC_MAX_RUNTIME_HOURS}h)..."
|
||||||
|
|
||||||
RSYNC_OUTPUT=$(rsync "${RSYNC_OPTS[@]}" \
|
RSYNC_OUTPUT=$(timeout "${RSYNC_MAX_RUNTIME_SECONDS}s" rsync "${RSYNC_OPTS[@]}" \
|
||||||
-e "ssh -i $SSH_KEY -T -o Compression=no -o IPQoS=throughput" \
|
-e "ssh -i $SSH_KEY -T -o Compression=no -o IPQoS=throughput" \
|
||||||
"$DIRECTORY" "root@${REMOTE_SERVER}:$(dirname "$DIRECTORY")/" 2>&1)
|
"$DIRECTORY" "root@${REMOTE_SERVER}:$(dirname "$DIRECTORY")/" 2>&1)
|
||||||
|
|
||||||
@@ -405,6 +418,12 @@ for (( ATTEMPT=1; ATTEMPT<=RETRY_COUNT; ATTEMPT++ )); do
|
|||||||
echo "$ICON_DONE Rsync complete — $BYTES_TRANSFERRED bytes transferred"
|
echo "$ICON_DONE Rsync complete — $BYTES_TRANSFERRED bytes transferred"
|
||||||
RSYNC_SUCCESS=true
|
RSYNC_SUCCESS=true
|
||||||
break
|
break
|
||||||
|
elif [[ "$RSYNC_EXIT" -eq 124 ]]; then
|
||||||
|
# Hit the runtime cap, not a failure — --partial means next run resumes from here.
|
||||||
|
# No retry: retrying an already-huge transfer 2 more times just wastes the window.
|
||||||
|
warn "$ICON_RETRY Rsync exceeded ${RSYNC_MAX_RUNTIME_HOURS}h cap — pausing, will resume next scheduled run"
|
||||||
|
RSYNC_TIMED_OUT=true
|
||||||
|
break
|
||||||
else
|
else
|
||||||
warn "$ICON_RETRY Rsync failed (attempt $ATTEMPT/$RETRY_COUNT)"
|
warn "$ICON_RETRY Rsync failed (attempt $ATTEMPT/$RETRY_COUNT)"
|
||||||
log "Exit code: $RSYNC_EXIT"
|
log "Exit code: $RSYNC_EXIT"
|
||||||
@@ -491,6 +510,7 @@ BANDWIDTH_MONITOR="$SCRIPT_DIR/../Monitors/bandwidth_monitor.sh"
|
|||||||
if [[ "$DRY_RUN" == false ]] && [[ -f "$BANDWIDTH_MONITOR" ]]; then
|
if [[ "$DRY_RUN" == false ]] && [[ -f "$BANDWIDTH_MONITOR" ]]; then
|
||||||
STATUS="success"
|
STATUS="success"
|
||||||
[[ "$RSYNC_SUCCESS" == false ]] && STATUS="failed"
|
[[ "$RSYNC_SUCCESS" == false ]] && STATUS="failed"
|
||||||
|
[[ "$RSYNC_TIMED_OUT" == true ]] && STATUS="timeout"
|
||||||
bash "$BANDWIDTH_MONITOR" --log-transfer \
|
bash "$BANDWIDTH_MONITOR" --log-transfer \
|
||||||
"$PROFILE_NAME" "$DURATION" "$STATUS" "$BYTES_TRANSFERRED"
|
"$PROFILE_NAME" "$DURATION" "$STATUS" "$BYTES_TRANSFERRED"
|
||||||
log "$ICON_BANDWIDTH Transfer logged to bandwidth monitor ($BYTES_TRANSFERRED bytes)"
|
log "$ICON_BANDWIDTH Transfer logged to bandwidth monitor ($BYTES_TRANSFERRED bytes)"
|
||||||
@@ -512,6 +532,8 @@ if [[ "$DRY_RUN" == true ]]; then
|
|||||||
warn "DRY RUN — no changes made"
|
warn "DRY RUN — no changes made"
|
||||||
elif [[ "$RSYNC_SUCCESS" == true ]]; then
|
elif [[ "$RSYNC_SUCCESS" == true ]]; then
|
||||||
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
|
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
|
||||||
|
elif [[ "$RSYNC_TIMED_OUT" == true ]]; then
|
||||||
|
echo "$ICON_DONE Status: PAUSED — exceeded ${RSYNC_MAX_RUNTIME_HOURS}h cap, resumes next scheduled run"
|
||||||
else
|
else
|
||||||
echo "$ICON_ERROR Status: FAILED after $RETRY_COUNT attempts"
|
echo "$ICON_ERROR Status: FAILED after $RETRY_COUNT attempts"
|
||||||
notify "Rsync FAILED — $DIRECTORY ($PROFILE_NAME) after $RETRY_COUNT attempts on $(hostname)" \
|
notify "Rsync FAILED — $DIRECTORY ($PROFILE_NAME) after $RETRY_COUNT attempts on $(hostname)" \
|
||||||
@@ -524,5 +546,5 @@ exec 1>&- 2>&-; wait
|
|||||||
cp "$VV_LIVE_LOG" "$VV_LAST_LOG" 2>/dev/null
|
cp "$VV_LIVE_LOG" "$VV_LAST_LOG" 2>/dev/null
|
||||||
rm -f "$VV_LIVE_LOG"
|
rm -f "$VV_LIVE_LOG"
|
||||||
|
|
||||||
[[ "$RSYNC_SUCCESS" == false ]] && [[ "$DRY_RUN" == false ]] && exit 1
|
[[ "$RSYNC_SUCCESS" == false ]] && [[ "$RSYNC_TIMED_OUT" == false ]] && [[ "$DRY_RUN" == false ]] && exit 1
|
||||||
exit 0
|
exit 0
|
||||||
Reference in New Issue
Block a user