PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.9.2
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.9.2
4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Traits / ResourceTrait.php
wp-staging / Framework / Traits Last commit date
ApplyFiltersTrait.php 2 months ago ArrayableTrait.php 7 months ago BatchSizeCalculateTrait.php 7 months ago BearerTokenTrait.php 4 months ago BenchmarkTrait.php 2 years ago BooleanTransientTrait.php 5 years ago DatabaseSearchReplaceTrait.php 2 weeks ago DbRowsGeneratorTrait.php 3 years ago DebugLogTrait.php 1 year ago DeveloperTimerTrait.php 8 months ago EndOfLinePlaceholderTrait.php 1 year ago EventLoggerTrait.php 1 month ago FileScanToCacheTrait.php 10 months ago FormatTrait.php 11 months ago HttpRequestTrait.php 8 months ago HydrateTrait.php 1 year ago I18nTrait.php 1 year ago IpResolverTrait.php 10 months ago MaintenanceTrait.php 5 months ago MemoryExhaustTrait.php 2 years ago MySQLRowsGeneratorTrait.php 7 months ago NoticesTrait.php 1 day ago PropertyConstructor.php 5 years ago RenameTmpDirectoryTrait.php 5 months ago ResourceTrait.php 4 months ago RestRequestTrait.php 3 months ago RestoreFileExclusionTrait.php 1 year ago SerializeTrait.php 1 year ago SetTimeLimitTrait.php 1 month ago SlashTrait.php 1 year ago SqlCommentTrait.php 3 months ago TablePrefixValidator.php 3 months ago UrlTrait.php 1 year ago ValueGetterTrait.php 1 year ago WindowsOsTrait.php 1 year ago
ResourceTrait.php
455 lines
1 <?php
2
3 namespace WPStaging\Framework\Traits;
4
5 use WPStaging\Core\WPStaging;
6 use WPStaging\Framework\Facades\Hooks;
7 use WPStaging\Framework\Job\Dto\JobDataDto;
8
9 trait ResourceTrait
10 {
11 /** @var int|null */
12 protected $timeLimit;
13
14 protected $resourceTraitSettings;
15 protected $executionTimeLimit;
16 protected $memoryLimit;
17 protected $scriptMemoryLimit;
18
19 public static $defaultMaxExecutionTimeInSeconds = 30;
20 public static $executionTimeGapInSeconds = 5;
21
22 // Set lower maximum execution time for backup restore to avoid 504 errors in large database
23 public static $backupRestoreMaxExecutionTimeInSeconds = 10;
24
25 // Set lower maximum execution time for wait to avoid resource holding in shared hosting
26 public static $waitTaskMaxExecutionTimeInSeconds = 5;
27
28 /**
29 * Let start with a lower value we can try increasing it later when neeeded
30 * @var int
31 */
32 public static $fileAppendMaxExecutionTimeInSeconds = 10;
33
34 /** @var bool Whether this request is taking place in the context of a unit test. */
35 protected $isUnitTest;
36
37 /** @var bool If it is a unit test, whether to allow resource checks. */
38 protected $allowResourceCheckOnUnitTests;
39
40
41 /** @var int|null Cached extraction limit */
42 protected $extractionLimit;
43
44 /**
45 * @return bool
46 */
47 public function isThreshold()
48 {
49 if ($this->isUnitTest() && !Hooks::applyFilters('wpstg.tests.resources.allow_check', $this->allowResourceCheckOnUnitTests)) {
50 return false;
51 }
52
53 $isMemoryLimit = $this->isMemoryLimit();
54 $isTimeLimit = $this->isTimeLimit();
55
56 if (defined('WPSTG_DEBUG') && WPSTG_DEBUG) {
57 if ($isTimeLimit || $isMemoryLimit) {
58 \WPStaging\functions\debug_log('isThreshold: ' . wp_json_encode(['class' => __CLASS__, 'isTimeLimit' => $isTimeLimit, 'isMemoryLimit' => $isMemoryLimit], JSON_UNESCAPED_SLASHES));
59 }
60 }
61
62 if ($isMemoryLimit) {
63 return true;
64 }
65
66 if ($isTimeLimit) {
67 return true;
68 }
69
70 return false;
71 }
72
73 /**
74 * @return bool
75 */
76 public function isFileAppendThreshold()
77 {
78 if ($this->isUnitTest() && !Hooks::applyFilters('wpstg.tests.resources.allow_check', $this->allowResourceCheckOnUnitTests)) {
79 return false;
80 }
81
82 return $this->isMemoryLimit() || $this->isFileAppendTimeLimit();
83 }
84
85 /**
86 * @return bool
87 */
88 public function isDatabaseRestoreThreshold()
89 {
90 return $this->isMemoryLimit() || $this->isDatabaseRestoreTimeLimit();
91 }
92
93 /**
94 * @return bool
95 */
96 public function isWaitTaskThreshold()
97 {
98 return $this->isMemoryLimit() || $this->isWaitTaskTimeLimit();
99 }
100
101 /**
102 * @return bool
103 */
104 public function isMaxExecutionThreshold()
105 {
106 return $this->isMemoryLimit() || $this->isMaxExecutionTimeoutLimit();
107 }
108
109 /**
110 * @see \Codeception\Module\WPLoader::_getConstants
111 *
112 * @return bool Whether this request is in the context of a unit test.
113 */
114 protected function isUnitTest()
115 {
116 if (isset($this->isUnitTest)) {
117 return $this->isUnitTest;
118 }
119
120 $this->isUnitTest = defined('WPCEPT_ISOLATED_INSTALL');
121
122 return $this->isUnitTest;
123 }
124
125 /**
126 * @return float
127 */
128 protected function getRunningTime()
129 {
130 return microtime(true) - WPStaging::$startTime;
131 }
132
133 /**
134 * @return bool
135 */
136 public function isMemoryLimit()
137 {
138 return $this->getScriptMemoryLimit() <= $this->getMemoryUsage();
139 }
140
141 /**
142 * @return bool
143 */
144 public function isTimeLimit()
145 {
146 $timeLimit = $this->findExecutionTimeLimit();
147
148 if (isset($this->timeLimit)) {
149 $timeLimit = $this->timeLimit;
150 }
151
152 return $this->getRunningTime() > $timeLimit;
153 }
154
155 /**
156 * @return bool
157 */
158 public function isDatabaseRestoreTimeLimit()
159 {
160 $timeLimit = (int)Hooks::applyFilters(JobDataDto::FILTER_RESOURCES_BACKUP_RESTORE_MAX_EXECUTION_TIME_IN_SECONDS, static::$backupRestoreMaxExecutionTimeInSeconds);
161 return $this->getRunningTime() > $timeLimit;
162 }
163
164 /**
165 * @return bool
166 */
167 public function isFileAppendTimeLimit(): bool
168 {
169 $timeLimit = (int)Hooks::applyFilters(JobDataDto::FILTER_RESOURCES_FILE_APPEND_TIME_LIMIT, static::$fileAppendMaxExecutionTimeInSeconds);
170 return $this->getRunningTime() > $timeLimit;
171 }
172
173 /**
174 * @return bool
175 */
176 public function isWaitTaskTimeLimit(): bool
177 {
178 $phpTimeLimit = $this->findExecutionTimeLimit();
179 $timeLimit = min($phpTimeLimit, self::$waitTaskMaxExecutionTimeInSeconds);
180
181 return $this->getRunningTime() > $timeLimit;
182 }
183
184 /**
185 * @return bool
186 */
187 public function isMaxExecutionTimeoutLimit()
188 {
189 return $this->getRunningTime() > $this->findExecutionTimeLimit(true);
190 }
191
192 /**
193 * Returns the maximum allowed execution time limit.
194 * The minimum needs to be 10seconds!
195 * @see https://github.com/wp-staging/wp-staging-pro/pull/1492
196 *
197 * @param bool $useMaxTimeout
198 *
199 * @return float|int
200 */
201 public function findExecutionTimeLimit($useMaxTimeout = false)
202 {
203 // Early bail: Cache
204 if (isset($this->executionTimeLimit)) {
205 return $this->executionTimeLimit;
206 }
207
208 $phpMaxExecutionTime = $this->getPhpMaxExecutionTime();
209 $cpuBoundMaxExecutionTime = $this->getCpuBoundMaxExecutionTime();
210
211 // Use the max execution time limit for CPU bound tasks like tables renaming
212 if ($useMaxTimeout) {
213 $this->executionTimeLimit = max(min($phpMaxExecutionTime - static::$executionTimeGapInSeconds, $phpMaxExecutionTime * 0.8), 10);
214
215 // Internal Use only. Allow overwriting of the max execution time limit for testing database rename task.
216 $this->executionTimeLimit = (int)Hooks::applyFilters('wpstg.tests.databaseRenameTaskExecutionTime', $this->executionTimeLimit);
217
218 return $this->executionTimeLimit;
219 }
220
221 // TODO don't overwrite when CLI / SAPI and / or add setting to not overwrite for devs
222 if (!$cpuBoundMaxExecutionTime || $cpuBoundMaxExecutionTime > static::$defaultMaxExecutionTimeInSeconds) {
223 $cpuBoundMaxExecutionTime = static::$defaultMaxExecutionTimeInSeconds;
224 }
225
226 // Never go over PHP own execution time limit, if set.
227 if ($phpMaxExecutionTime > 0) {
228 $cpuBoundMaxExecutionTime = min($phpMaxExecutionTime, $cpuBoundMaxExecutionTime);
229 }
230
231 // Set a max of 30 seconds to avoid NGINX 504 timeouts that are beyond PHP's control, with a minimum of 10 seconds
232 $this->executionTimeLimit = max(min($cpuBoundMaxExecutionTime - static::$executionTimeGapInSeconds, 30), 10);
233
234 // Allow overwriting of the max execution time limit.
235 // Important: Use a value lower than the actual PHP limit. (reduce it by 10seconds or more). Also adjust the nginx/php timeout limit
236 $this->executionTimeLimit = (int)Hooks::applyFilters(JobDataDto::FILTER_RESOURCES_EXECUTION_TIME_LIMIT, $this->executionTimeLimit);
237
238 // Allow disabling of the execution time limit
239 if ((bool)Hooks::applyFilters(JobDataDto::FILTER_RESOURCES_IGNORE_TIME_LIMIT, false)) {
240 $this->executionTimeLimit = PHP_INT_MAX;
241 }
242
243 return $this->executionTimeLimit;
244 }
245
246 public function isWithinMemoryExtractionLimit(int $fileSize): bool
247 {
248 return $fileSize <= $this->getInMemoryExtractionLimit();
249 }
250
251 /**
252 * The limit is determined by the available PHP memory. This can be customized
253 * using the 'wpstg.backup.inmemory_extraction.limit' filter.
254 *
255 * Memory-based extraction limits (adaptive based on PHP memory_limit):
256 * - Memory < 255MB: 1MB limit
257 * - 255-510MB: 2MB limit
258 * - 511-1022MB: 4MB limit
259 * - 1023MB+: 8MB limit
260 */
261 public function getInMemoryExtractionLimit(): int
262 {
263 // Early bail if already calculated
264 if ($this->extractionLimit !== null) {
265 return $this->extractionLimit;
266 }
267
268 $memoryLimit = $this->getMaxMemoryLimit();
269 $limit = $this->calculateInMemoryExtractionLimit($memoryLimit);
270
271 /**
272 * @param int $limit
273 * @param int $memoryLimit
274 * @return int
275 */
276 $this->extractionLimit = (int)Hooks::applyFilters(JobDataDto::FILTER_BACKUP_INMEMORY_EXTRACTION_LIMIT, $limit, $memoryLimit);
277
278 // Ensure the limit is at least 512KB and at most 16MB for safety
279 $this->extractionLimit = max(524288, min($this->extractionLimit, (16 * MB_IN_BYTES)));
280
281 return $this->extractionLimit;
282 }
283
284 /**
285 * @param bool $realUsage
286 *
287 * @return int
288 */
289 protected function getMemoryUsage($realUsage = true)
290 {
291 return memory_get_usage($realUsage);
292 }
293
294 /**
295 * @param bool $realUsage
296 *
297 * @return int
298 */
299 protected function getMemoryPeakUsage($realUsage = true)
300 {
301 return memory_get_peak_usage($realUsage);
302 }
303
304 /**
305 * @return int|null
306 */
307 public function getTimeLimit()
308 {
309 if (!isset($this->timeLimit)) {
310 $this->timeLimit = $this->findExecutionTimeLimit();
311 }
312
313 return $this->timeLimit;
314 }
315
316 /**
317 * @param int|null $timeLimit
318 */
319 public function setTimeLimit($timeLimit)
320 {
321 $this->timeLimit = $timeLimit;
322 }
323
324 /**
325 * @param bool $isAllowed True to check resources on unit tests. False to not check.
326 */
327 public function resourceCheckOnUnitTests($isAllowed)
328 {
329 $this->allowResourceCheckOnUnitTests = $isAllowed;
330 }
331
332 /**
333 * Returns the current PHP memory limit in bytes.
334 *
335 * @return int
336 */
337 protected function getMaxMemoryLimit()
338 {
339 // Early bail: Cache
340 if (isset($this->memoryLimit)) {
341 return $this->memoryLimit;
342 }
343
344 $memoryLimit = wp_convert_hr_to_bytes(ini_get('memory_limit'));
345
346 // No memory limit
347 if ($memoryLimit == -1 || $memoryLimit < 0) {
348 $memoryLimit = 256 * MB_IN_BYTES;
349 }
350
351 // Allow custom overwriting
352 $this->memoryLimit = Hooks::applyFilters(JobDataDto::FILTER_RESOURCES_MEMORY_LIMIT, $memoryLimit);
353
354 // Unexpected memory limit after filter and also make sure it is never below 64MB
355 if (!is_int($this->memoryLimit) || $this->memoryLimit < (64 * MB_IN_BYTES)) {
356 $this->memoryLimit = 64 * MB_IN_BYTES;
357 }
358
359 // Make sure it never exceeds 256MB
360 $this->memoryLimit = (min($this->memoryLimit, 256 * MB_IN_BYTES));
361
362 // Allow disabling the memory limit
363 if ((bool)Hooks::applyFilters(JobDataDto::FILTER_RESOURCES_IGNORE_MEMORY_LIMIT, false)) {
364 $this->memoryLimit = PHP_INT_MAX;
365 }
366
367 return $this->memoryLimit;
368 }
369
370 /**
371 * Returns the actual script memory limit.
372 *
373 * @return int|float The script memory limit, by definition less then the maximum memory limit.
374 */
375 protected function getScriptMemoryLimit()
376 {
377 // For testing purpose only
378 $this->scriptMemoryLimit = Hooks::applyFilters('wpstg.tests.resources.script_memory_limit', $this->scriptMemoryLimit);
379
380 // Early bail: Cache
381 if (isset($this->scriptMemoryLimit)) {
382 return $this->scriptMemoryLimit;
383 }
384
385 // 80% of max memory limit
386 return $this->scriptMemoryLimit = $this->getMaxMemoryLimit() * 0.8;
387 }
388
389 /**
390 * Returns the max execution time value as bound by the "CPU Load" setting.
391 *
392 * @param string|null $cpuLoadSetting Either a specific CPU Load setting to
393 * return the max execution time for, or
394 * `null` to read the current CPU Load
395 * value from the Settings.
396 *
397 * @return int The max execution time as bound by the CPU Load setting.
398 */
399 protected function getCpuBoundMaxExecutionTime($cpuLoadSetting = null)
400 {
401 // Early bail: Cache
402 if (!isset($this->resourceTraitSettings)) {
403 $this->resourceTraitSettings = json_decode(json_encode(get_option('wpstg_settings', [])));
404 }
405
406 if ($cpuLoadSetting === null) {
407 $cpuLoadSetting = isset($this->resourceTraitSettings->cpuLoad) ? $this->resourceTraitSettings->cpuLoad : 'medium';
408 }
409
410 $execution_gap = static::$executionTimeGapInSeconds;
411 switch ($cpuLoadSetting) {
412 case 'low':
413 $cpuBoundMaxExecutionTime = 10 + $execution_gap;
414 break;
415 case 'medium':
416 default:
417 $cpuBoundMaxExecutionTime = 20 + $execution_gap;
418 break;
419 case 'high':
420 $cpuBoundMaxExecutionTime = 25 + $execution_gap;
421 break;
422 }
423
424 return $cpuBoundMaxExecutionTime;
425 }
426
427 /**
428 * Returns the max execution time as set in PHP ini settings.
429 *
430 * @return int The PHP max execution time in seconds. Note that `0` and `-1` would
431 * both indicate there is no time limit set.
432 */
433 private function getPhpMaxExecutionTime()
434 {
435 return (int)ini_get('max_execution_time');
436 }
437
438 private function calculateInMemoryExtractionLimit(int $memoryLimit): int
439 {
440 if ($memoryLimit <= (256 * MB_IN_BYTES)) {
441 return MB_IN_BYTES;
442 }
443
444 if ($memoryLimit <= (512 * MB_IN_BYTES)) {
445 return 2 * MB_IN_BYTES;
446 }
447
448 if ($memoryLimit <= (1024 * MB_IN_BYTES)) {
449 return 4 * MB_IN_BYTES;
450 }
451
452 return 8 * MB_IN_BYTES;
453 }
454 }
455