PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.10.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.10.0
4.10.0 4.9.5 4.9.4 4.9.3 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 / ErrorHandler.php
wp-staging / Framework Last commit date
Adapter 1 week ago Analytics 5 days ago Assets 5 days ago BackgroundProcessing 5 days ago CloningProcess 1 week ago Collection 3 years ago Command 5 years ago Component 10 months ago DI 5 days ago Database 5 days ago DependencyResolver 2 years ago Exceptions 2 years ago Experiments 5 days ago Facades 4 months ago Filesystem 5 days ago Interfaces 5 days ago Job 5 days ago Language 5 days ago Logger 2 months ago Mails 5 months ago Network 1 week ago Newsfeed 6 months ago Notices 5 days ago Onboarding 5 days ago Performance 4 months ago Permalinks 1 year ago Queue 6 months ago Rest 4 months ago Security 3 months ago Settings 3 months ago TemplateEngine 8 months ago ThirdParty 1 week ago Traits 5 days ago Upgrade 2 months ago Utils 5 days ago AnalyticsServiceProvider.php 1 month ago AssetServiceProvider.php 1 year ago CommonServiceProvider.php 5 days ago ErrorHandler.php 5 days ago NoticeServiceProvider.php 1 year ago SettingsServiceProvider.php 4 months ago SiteInfo.php 1 week ago Url.php 6 months ago
ErrorHandler.php
164 lines
1 <?php
2
3 namespace WPStaging\Framework;
4
5 use WPStaging\Core\WPStaging;
6 use WPStaging\Framework\Job\JobTransientCache;
7 use WPStaging\Framework\Job\ProcessLock;
8 use WPStaging\Framework\Logger\SseEventCache;
9
10 /**
11 * @package WPStaging\Framework
12 */
13 class ErrorHandler
14 {
15 /** @var string */
16 const ERROR_FILE_EXTENSION = '.wpstgerror';
17
18 public function registerShutdownHandler()
19 {
20 register_shutdown_function([$this, 'shutdownHandler']);
21 }
22
23 public function shutdownHandler()
24 {
25 if (!defined('WPSTG_REQUEST')) {
26 return;
27 }
28
29 if (!defined('WPSTG_UPLOADS_DIR')) {
30 return;
31 }
32
33 /**
34 * Requests for which to check for memory exhaustion
35 * Using hardcoded values below to avoid loading all classes
36 * @var array $wpStagingRequests
37 */
38 $wpStagingRequests = [
39 'wpstg_backup', // @see WPStaging\Backup\Ajax\Backup::WPSTG_REQUEST
40 'wpstg_restore', // @see WPStaging\Backup\Ajax\Restore::WPSTG_REQUEST
41 'wpstg_cloning', // @see WPStaging\Backend\Modules\Jobs\Cloning::WPSTG_REQUEST,
42 'wpstg_remote_sync_pull', // @see WPStaging\Pro\RemoteSync\BackgroundProcessing\PreparePull::WPSTG_REQUEST
43 'wpstg_staging_create', // @see WPStaging\Staging\Ajax\Create::WPSTG_REQUEST
44 'wpstg_staging_update', // @see WPStaging\Staging\Ajax\Update::WPSTG_REQUEST
45 'wpstg_staging_reset', // @see WPStaging\Staging\Ajax\Reset::WPSTG_REQUEST
46 'wpstg_staging_push', // @see WPStaging\Pro\Push\Ajax\Push::WPSTG_REQUEST
47 ];
48
49 $wpStagingRequest = WPSTG_REQUEST;
50 if (!in_array($wpStagingRequest, $wpStagingRequests, true)) {
51 return;
52 }
53
54 $error = error_get_last();
55 if (!is_array($error)) {
56 return;
57 }
58
59 if ($error['type'] !== E_ERROR) {
60 return;
61 }
62
63 preg_match('/Allowed memory size of (\d+) bytes exhausted \(tried to allocate (\d+) bytes\)/', $error['message'], $data);
64 if (!is_array($data) || count($data) !== 3) {
65 $data['time'] = date('Y/m/d H:i:s', time()); // @see WPStaging\Core\Utils\Logger::LOG_DATETIME_FORMAT, use hardcoded value to avoid loading class
66 $this->logSseEvent($data, false);
67 $this->releaseProcessLock();
68 return;
69 }
70
71 // Temporary file to store the error message
72 $errorTmpFile = WPSTG_UPLOADS_DIR . $wpStagingRequest . self::ERROR_FILE_EXTENSION;
73
74 $fileHandler = fopen($errorTmpFile, 'w');
75
76 $data = [
77 'memoryUsage' => memory_get_usage(true),
78 'peakMemoryUsage' => memory_get_peak_usage(true),
79 'phpMemoryLimit' => ini_get('memory_limit'),
80 'wpMemoryLimit' => defined('WP_MEMORY_LIMIT') ? WP_MEMORY_LIMIT : '',
81 'allowedMemoryLimit' => $data[1],
82 'exhaustedMemorySize' => $data[2],
83 'time' => date('Y/m/d H:i:s', time()), // @see WPStaging\Core\Utils\Logger::LOG_DATETIME_FORMAT, use hardcoded value to avoid loading class
84 ];
85
86 if (is_resource($fileHandler)) {
87 fwrite($fileHandler, json_encode($data));
88 fclose($fileHandler);
89 }
90
91 $this->logSseEvent($data);
92 $this->releaseProcessLock();
93 }
94
95 /**
96 * Release the process lock when a fatal error terminates PHP before the job can clean up.
97 * The kernel would drop the lock anyway once this request's handles are closed, but releasing
98 * it here also clears the record the fallback path reads on filesystems that cannot lock.
99 *
100 * @return void
101 */
102 private function releaseProcessLock()
103 {
104 try {
105 WPStaging::make(ProcessLock::class)->unlockProcess();
106 } catch (\Throwable $e) {
107 // No-op: shutdown handler must not throw.
108 }
109 }
110
111 private function logSseEvent(array $data, bool $isMemoryExhaust = true)
112 {
113 /**
114 * @var JobTransientCache $jobTransientCache
115 */
116 $jobTransientCache = WPStaging::make(JobTransientCache::class);
117
118 $jobId = $jobTransientCache->getJobId();
119 if (empty($jobId)) {
120 return;
121 }
122
123 if ($isMemoryExhaust) {
124 $exhaustedMemorySize = isset($data['exhaustedMemorySize']) ? (int)$data['exhaustedMemorySize'] : 0;
125 $memoryUsage = isset($data['memoryUsage']) ? (int)$data['memoryUsage'] : 0;
126 $peakMemoryUsage = isset($data['peakMemoryUsage']) ? (int)$data['peakMemoryUsage'] : 0;
127 $allowedMemoryLimit = isset($data['allowedMemoryLimit']) ? (int)$data['allowedMemoryLimit'] : 0;
128 $phpMemoryLimit = isset($data['phpMemoryLimit']) ? $data['phpMemoryLimit'] : ini_get('memory_limit');
129 $wpMemoryLimit = isset($data['wpMemoryLimit']) ? $data['wpMemoryLimit'] : (defined('WP_MEMORY_LIMIT') ? WP_MEMORY_LIMIT : '');
130
131 $message = sprintf(
132 esc_html__('Memory exhaust issue is detected during the process. Error occurred when allocating more %s on top of current usage of %s. Peak memory usage: %s, Allowed memory limit: %s, PHP memory limit: %s, WP memory limit: %s', 'wp-staging'),
133 size_format($exhaustedMemorySize),
134 size_format($memoryUsage),
135 size_format($peakMemoryUsage),
136 size_format($allowedMemoryLimit),
137 $phpMemoryLimit,
138 $wpMemoryLimit
139 );
140 } else {
141 $message = sprintf(
142 esc_html__('Job failed due to a fatal error! Error data: %s', 'wp-staging'),
143 esc_html(print_r($data, true))
144 );
145 }
146
147 $data['jobId'] = $jobId;
148 $data['message'] = $message;
149
150 /**
151 * @var SseEventCache $sseEventCache
152 */
153 $sseEventCache = WPStaging::make(SseEventCache::class);
154 $sseEventCache->setJobId($jobId);
155 $sseEventCache->load();
156 $sseEventCache->push([
157 'type' => $isMemoryExhaust ? SseEventCache::EVENT_TYPE_MEMORY_EXHAUST : SseEventCache::EVENT_TYPE_FATAL_ERROR,
158 'data' => $data,
159 ]);
160
161 $jobTransientCache->failJob('', $message);
162 }
163 }
164