From 7034f7b797998d2ac325455850b972edefe487b7 Mon Sep 17 00:00:00 2001 From: cschantz Date: Mon, 2 Mar 2026 20:54:12 -0500 Subject: [PATCH] FIX: Critical subshell scope bug - CRON_OFFSET not persisting across iterations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The staggered cron scheduling was completely broken due to bash subshell scope issue. The pattern was: cron_time=$(generate_staggered_cron) # Creates subshell! This caused CRON_OFFSET to increment in the subshell but not persist to the parent shell, resulting in ALL 200 sites getting cron time 0 * * * *. BEFORE (broken): All 200 sites → 0 * * * * (massive load spikes!) AFTER (fixed): Sites distributed as: 0, 3, 6, 9, 12, ... 57 (repeats) 200 sites: 10 sites per time slot (perfect distribution) Solution: Changed from command substitution to global variable approach: - generate_staggered_cron now sets LAST_CRON_TIME instead of echo - Callers read $LAST_CRON_TIME after function call - CRON_OFFSET increments now properly persist across loop iterations Fixed three locations: - Option 2: disable for domain - Option 3: disable for user - Option 4: disable server-wide All 200 sites will now run with proper load distribution across the hour. Co-Authored-By: Claude Haiku 4.5 --- .../wordpress/wordpress-cron-manager.sh | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/modules/website/wordpress/wordpress-cron-manager.sh b/modules/website/wordpress/wordpress-cron-manager.sh index 7c00bd5..ac77e03 100755 --- a/modules/website/wordpress/wordpress-cron-manager.sh +++ b/modules/website/wordpress/wordpress-cron-manager.sh @@ -201,6 +201,9 @@ BATCH_MODE=false # Global counter for staggering cron times CRON_OFFSET=0 +# Global variable to hold generated cron time (avoids subshell scope issues) +declare -g LAST_CRON_TIME="" + # PERFORMANCE OPTIMIZATION: Global cache for find results # Instead of running find 23 times, run once and reuse results declare -g WP_SITES_CACHE="" @@ -1421,6 +1424,7 @@ generate_staggered_cron() { # CRITICAL: Stagger cron times to prevent all WordPress sites from running at once # This prevents server load spikes from concurrent execution # Each site gets a different minute within the hour, spread across 60 minutes + # NOTE: Uses global variable LAST_CRON_TIME instead of echo to avoid subshell issues # Calculate staggered minute (0-59) - each site gets a different minute local minute=$((CRON_OFFSET % 60)) @@ -1431,12 +1435,12 @@ generate_staggered_cron() { local stagger_minute=$((minute * 3)) stagger_minute=$((stagger_minute % 60)) - # Increment offset for next site + # Increment offset for next site (THIS PERSISTS in parent shell, not in subshell) CRON_OFFSET=$((CRON_OFFSET + 1)) - # Return cron schedule: run every hour at the staggered minute - # This ensures each WordPress site runs at a different time to prevent load spikes - echo "$stagger_minute * * * *" + # Set global variable instead of using echo (avoids subshell scope issue) + # This ensures CRON_OFFSET increments persist across loop iterations + LAST_CRON_TIME="$stagger_minute * * * *" } # Function to extract user from WordPress site path @@ -1968,7 +1972,8 @@ case "$choice" in fi # Generate staggered cron time and add to crontab - cron_time=$(generate_staggered_cron) + generate_staggered_cron # Sets LAST_CRON_TIME globally (avoids subshell) + cron_time="$LAST_CRON_TIME" if safe_add_cron_job "$user" "$cron_time" "$cron_cmd"; then echo -e "${GREEN}✓${NC} Added cron job ($cron_time)" else @@ -2091,7 +2096,8 @@ case "$choice" in fi if ! cron_job_exists "$user" "$site_path"; then - cron_time=$(generate_staggered_cron) + generate_staggered_cron # Sets LAST_CRON_TIME globally (avoids subshell) + cron_time="$LAST_CRON_TIME" if [ "$DRY_RUN" = "true" ]; then echo " [DRY-RUN] Would add cron job ($cron_time)" else @@ -2212,7 +2218,8 @@ case "$choice" in cron_cmd=$(build_cron_command "$site_path") if ! cron_job_exists "$user" "$site_path"; then - cron_time=$(generate_staggered_cron) + generate_staggered_cron # Sets LAST_CRON_TIME globally (avoids subshell) + cron_time="$LAST_CRON_TIME" if [ "$DRY_RUN" = "true" ]; then echo " [DRY-RUN] Would add cron job ($cron_time)" else