| @@ -7,8 +7,9 @@ | ||
| 7 | 7 | |
| 8 | 8 | namespace PoweredCache\Async; |
| 9 | 9 | |
| 10 | 10 | use PoweredCache\Preloader; |
| 11 | +use function PoweredCache\Utils\detect_cpu_cores; | |
| 11 | 12 | use function PoweredCache\Utils\is_url_cached; |
| 12 | 13 | use \Powered_Cache_WP_Background_Process as Powered_Cache_WP_Background_Process; |
| 13 | 14 | |
| 14 | 15 | /** |
| @@ -53,17 +54,24 @@ | ||
| 53 | 54 | * |
| 54 | 55 | * @return mixed |
| 55 | 56 | */ |
| 56 | 57 | protected function task( $item ) { |
| 58 | + // Stop early if system load is too high | |
| 59 | + if ( ! $this->should_continue() ) { | |
| 60 | + \PoweredCache\Utils\log( 'Preload task aborted early due to system load' ); | |
| 61 | + return $item; | |
| 62 | + } | |
| 63 | + | |
| 57 | 64 | $this->settings = \PoweredCache\Utils\get_settings(); |
| 65 | + $delay = absint( $this->settings['preload_request_interval'] ) * 1000000; // convert to microseconds | |
| 58 | 66 | |
| 59 | - \PoweredCache\Utils\log( sprintf( 'Preloading...%s', $item ) ); | |
| 67 | + \PoweredCache\Utils\log( sprintf( 'Preloading..: %s', $item ) ); | |
| 60 | 68 | |
| 61 | 69 | if ( filter_var( $item, FILTER_VALIDATE_URL ) ) { |
| 62 | 70 | |
| 63 | 71 | if ( ! is_url_cached( $item, false, $this->settings['gzip_compression'] ) ) { |
| 64 | 72 | Preloader::preload_request( $item ); |
| 65 | - Preloader::wait(); | |
| 73 | + Preloader::wait( $delay ); | |
| 66 | 74 | } |
| 67 | 75 | |
| 68 | 76 | // make the preload request by using mobile agent |
| 69 | 77 | if ( $this->settings['cache_mobile'] && $this->settings['cache_mobile_separate_file'] ) { |
| @@ -68,14 +76,14 @@ | ||
| 68 | 76 | // make the preload request by using mobile agent |
| 69 | 77 | if ( $this->settings['cache_mobile'] && $this->settings['cache_mobile_separate_file'] ) { |
| 70 | 78 | if ( ! is_url_cached( $item, true, $this->settings['gzip_compression'] ) ) { |
| 71 | 79 | Preloader::preload_request( $item, [ 'user-agent' => Preloader::mobile_user_agent() ] ); |
| 72 | - Preloader::wait(); | |
| 80 | + Preloader::wait( $delay ); | |
| 73 | 81 | } |
| 74 | 82 | } |
| 75 | 83 | } |
| 76 | 84 | |
| 77 | - \PoweredCache\Utils\log( sprintf( 'Preloading completed...%s', $item ) ); | |
| 85 | + \PoweredCache\Utils\log( sprintf( 'Preloaded...: %s', $item ) ); | |
| 78 | 86 | |
| 79 | 87 | return false; |
| 80 | 88 | } |
| 81 | 89 | |
| @@ -91,9 +99,9 @@ | ||
| 91 | 99 | } |
| 92 | 100 | |
| 93 | 101 | /** |
| 94 | 102 | * Sometimes canceling a process is glitchy |
| 95 | - * Try to cancell all items in the queue up to $max_attempt | |
| 103 | + * Try to cancel all items in the queue up to $max_attempt | |
| 96 | 104 | */ |
| 97 | 105 | public function cancel_process() { |
| 98 | 106 | $max_attempt = 5; |
| 99 | 107 | $cancelled = 0; |
| @@ -100,9 +108,9 @@ | ||
| 100 | 108 | while ( ! parent::is_queue_empty() ) { |
| 101 | 109 | if ( $cancelled >= $max_attempt ) { |
| 102 | 110 | break; |
| 103 | 111 | } |
| 104 | - parent::cancel_process(); | |
| 112 | + parent::cancel(); | |
| 105 | 113 | $cancelled ++; |
| 106 | 114 | } |
| 107 | 115 | } |
| 108 | 116 | |
| @@ -111,10 +119,97 @@ | ||
| 111 | 119 | * |
| 112 | 120 | * @return bool |
| 113 | 121 | */ |
| 114 | 122 | public function is_process_running() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found |
| 115 | - return parent::is_process_running(); | |
| 123 | + return parent::is_processing(); | |
| 116 | 124 | } |
| 125 | + | |
| 126 | + /** | |
| 127 | + * Determine if the background process should continue. | |
| 128 | + * Stops the process if server load is too high or spikes suddenly. | |
| 129 | + * | |
| 130 | + * @return bool | |
| 131 | + */ | |
| 132 | + public function should_continue() { | |
| 133 | + $should_continue = parent::should_continue(); | |
| 134 | + $halted_by_load = false; | |
| 135 | + | |
| 136 | + $load = function_exists( 'sys_getloadavg' ) | |
| 137 | + ? sys_getloadavg() | |
| 138 | + : [ 0.0, 0.0, 0.0 ]; | |
| 139 | + | |
| 140 | + $load_1min = isset( $load[0] ) ? (float) $load[0] : 0.0; | |
| 141 | + $load_5min = isset( $load[1] ) ? (float) $load[1] : 0.0; | |
| 142 | + $load_15min = isset( $load[2] ) ? (float) $load[2] : 0.0; | |
| 143 | + | |
| 144 | + // Weighted average: gives slightly more weight to short-term load. | |
| 145 | + $weighted_load = ( $load_1min * 0.5 + $load_5min * 0.3 + $load_15min * 0.2 ); | |
| 146 | + | |
| 147 | + // Consider a spike if 1min load is significantly higher than longer-term averages. | |
| 148 | + $load_spike_detected = ( $load_1min > $load_5min * 2 || $load_5min > $load_15min * 2 ); | |
| 149 | + | |
| 150 | + // Fallback max load threshold | |
| 151 | + $default_max_load = 16.0; | |
| 152 | + | |
| 153 | + /** | |
| 154 | + * Filter the max allowed server load before preloading pauses. | |
| 155 | + * | |
| 156 | + * @hook powered_cache_preloader_max_server_load | |
| 157 | + * | |
| 158 | + * @param {float} $default_max_load Default load threshold. | |
| 159 | + * @param {array} $load [1min, 5min, 15min] load averages. | |
| 160 | + * | |
| 161 | + * @return {float} Maximum allowed server load before preloading pauses. | |
| 162 | + * @since 3.6 | |
| 163 | + */ | |
| 164 | + $max_allowed_load = apply_filters( | |
| 165 | + 'powered_cache_preloader_max_server_load', | |
| 166 | + $default_max_load, | |
| 167 | + $load | |
| 168 | + ); | |
| 169 | + | |
| 170 | + // Allow setting a custom maximum server load threshold. | |
| 171 | + if ( defined( 'POWERED_CACHE_PRELOADER_MAX_SERVER_LOAD' ) && is_numeric( POWERED_CACHE_PRELOADER_MAX_SERVER_LOAD ) ) { | |
| 172 | + $max_allowed_load = (float) POWERED_CACHE_PRELOADER_MAX_SERVER_LOAD; | |
| 173 | + } | |
| 174 | + | |
| 175 | + if ( $should_continue && ( $weighted_load > $max_allowed_load || $load_spike_detected ) ) { | |
| 176 | + \PoweredCache\Utils\log( | |
| 177 | + sprintf( | |
| 178 | + 'Preload paused: server load too high or spike detected (1min: %.2f, 5min: %.2f, 15min: %.2f, weighted: %.2f, threshold: %.2f)', | |
| 179 | + $load_1min, | |
| 180 | + $load_5min, | |
| 181 | + $load_15min, | |
| 182 | + $weighted_load, | |
| 183 | + $max_allowed_load | |
| 184 | + ) | |
| 185 | + ); | |
| 186 | + $should_continue = false; | |
| 187 | + $halted_by_load = true; | |
| 188 | + } | |
| 189 | + | |
| 190 | + /** | |
| 191 | + * Allow complete control over continuation logic. | |
| 192 | + * | |
| 193 | + * @hook powered_cache_preloader_should_continue | |
| 194 | + * | |
| 195 | + * @param bool $should_continue Whether to continue processing. | |
| 196 | + * @param bool $halted_by_load True if we halted due to load. | |
| 197 | + * @param array $load [1min, 5min, 15min] load averages. | |
| 198 | + * @param float $max_allowed_load Final load limit used for decision. | |
| 199 | + * | |
| 200 | + * @return bool Whether to continue processing. | |
| 201 | + * @since 3.6 | |
| 202 | + */ | |
| 203 | + return (bool) apply_filters( | |
| 204 | + 'powered_cache_preloader_should_continue', | |
| 205 | + $should_continue, | |
| 206 | + $halted_by_load, | |
| 207 | + $load, | |
| 208 | + $max_allowed_load | |
| 209 | + ); | |
| 210 | + } | |
| 211 | + | |
| 117 | 212 | |
| 118 | 213 | /** |
| 119 | 214 | * Return an instance of the current class |
| 120 | 215 | * |