| 1 |
<?php |
| 2 |
|
| 3 |
namespace ImportWP\Common\Runner; |
| 4 |
|
| 5 |
use ImportWP\Common\Properties\Properties; |
| 6 |
use ImportWP\Common\Util\Logger; |
| 7 |
|
| 8 |
abstract class Runner |
| 9 |
{ |
| 10 |
/** |
| 11 |
* @var Properties |
| 12 |
*/ |
| 13 |
protected $properties; |
| 14 |
|
| 15 |
/** |
| 16 |
* @var int |
| 17 |
*/ |
| 18 |
protected $memory_limit; |
| 19 |
|
| 20 |
/** |
| 21 |
* @var boolean |
| 22 |
*/ |
| 23 |
protected $is_timeout = false; |
| 24 |
|
| 25 |
protected $object_type = 'importer'; |
| 26 |
|
| 27 |
/** |
| 28 |
* @param Properties $properties |
| 29 |
*/ |
| 30 |
public function __construct($properties) |
| 31 |
{ |
| 32 |
$this->properties = $properties; |
| 33 |
} |
| 34 |
|
| 35 |
abstract function process_row($id, $user, $state, $session, $section, $progress); |
| 36 |
abstract function setup($state, $state_data, $config, $user, $id); |
| 37 |
|
| 38 |
/** |
| 39 |
* @param int $id |
| 40 |
* @param string $user |
| 41 |
* @param RunnerState $state |
| 42 |
*/ |
| 43 |
public function process($id, $user, $state) |
| 44 |
{ |
| 45 |
$time_limit = $this->properties->get_setting('timeout'); |
| 46 |
Logger::info('time_limit ' . $time_limit . 's'); |
| 47 |
|
| 48 |
$start = microtime(true); |
| 49 |
$max_record_time = 0; |
| 50 |
$memory_max_usage = 0; |
| 51 |
$i = 0; |
| 52 |
|
| 53 |
$this->try_process_dangling_state($id, $user, $state, $user); |
| 54 |
|
| 55 |
$config = get_option('iwp_' . $this->object_type . '_config_' . $id, []); |
| 56 |
|
| 57 |
while ( |
| 58 |
($i == 0 || ( |
| 59 |
($time_limit === 0 || $this->has_enough_time($start, $time_limit, $max_record_time)) |
| 60 |
&& $this->has_enough_memory($memory_max_usage)) |
| 61 |
) |
| 62 |
&& $state |
| 63 |
&& $state->is_resumable() |
| 64 |
) { |
| 65 |
|
| 66 |
$memory_usage = $this->get_memory_usage(); |
| 67 |
|
| 68 |
$this->is_timeout = false; |
| 69 |
|
| 70 |
$state = $state->update(function ($state_data) use ($state, $config, $user, $id) { |
| 71 |
return $this->setup($state, $state_data, $config, $user, $id); |
| 72 |
}); |
| 73 |
|
| 74 |
if (!$state || $this->is_timeout || !$state->is_running()) { |
| 75 |
break; |
| 76 |
} |
| 77 |
|
| 78 |
$record_time = microtime(true); |
| 79 |
$this->process_row($id, $user, $state, $state->get_session(), $state->get_section(), $state->get_progress()); |
| 80 |
|
| 81 |
$max_record_time = max($max_record_time, microtime(true) - $record_time); |
| 82 |
|
| 83 |
if (!wp_using_ext_object_cache()) { |
| 84 |
wp_cache_flush(); |
| 85 |
} |
| 86 |
|
| 87 |
// keep track of largest memory change |
| 88 |
$memory_delta = $this->get_memory_usage() - $memory_usage; |
| 89 |
if ($memory_delta > $memory_max_usage) { |
| 90 |
$memory_max_usage = $memory_delta; |
| 91 |
} |
| 92 |
|
| 93 |
$i++; |
| 94 |
} |
| 95 |
} |
| 96 |
|
| 97 |
/** |
| 98 |
* @param string $user |
| 99 |
* @param RunnerState $state |
| 100 |
*/ |
| 101 |
public function try_process_dangling_state($id, $user, $state, $user_to_check = null) |
| 102 |
{ |
| 103 |
$dangling = $this->has_dangling_state($id, $user_to_check); |
| 104 |
if (!empty($dangling)) { |
| 105 |
$fixed = 0; |
| 106 |
foreach ($dangling as $dangling_id) { |
| 107 |
|
| 108 |
// TODO: Should the option be renamed to the current user first? to make sure its not ran multiple times. |
| 109 |
// TODO: status should not be overwritten like this. |
| 110 |
$GLOBALS['wp_object_cache']->delete($dangling_id, 'options'); |
| 111 |
$status = get_option($dangling_id); |
| 112 |
if ($status && $status['last_modified'] < current_time('timestamp') - 30) { |
| 113 |
|
| 114 |
Logger::write('try_import_dangling_rows -id=' . $id . ' -user=' . $user . ' -dangling=' . $dangling_id); |
| 115 |
|
| 116 |
$GLOBALS['wp_object_cache']->delete($dangling_id, 'options'); |
| 117 |
$status['last_modified'] = current_time('timestamp'); |
| 118 |
update_option($dangling_id, $status); |
| 119 |
|
| 120 |
|
| 121 |
$this->process_row($id, $user, $state, $state->get_session(), $status['section'], $status['progress'][$status['section']]); |
| 122 |
delete_site_option($dangling_id); |
| 123 |
$fixed++; |
| 124 |
} |
| 125 |
} |
| 126 |
|
| 127 |
if ($fixed !== count($dangling)) { |
| 128 |
// escape due to dangling records that have not timed out |
| 129 |
return false; |
| 130 |
} |
| 131 |
} |
| 132 |
|
| 133 |
return true; |
| 134 |
} |
| 135 |
|
| 136 |
function has_dangling_state($id, $user = null) |
| 137 |
{ |
| 138 |
/** |
| 139 |
* @var \WPDB $wpdb |
| 140 |
*/ |
| 141 |
global $wpdb; |
| 142 |
|
| 143 |
$key_prefix = 'iwp\_' . $this->object_type . '\_state'; |
| 144 |
$query = "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '{$key_prefix}\_{$id}\_"; |
| 145 |
|
| 146 |
if (!empty($user)) { |
| 147 |
$query .= $user; |
| 148 |
} else { |
| 149 |
$query .= '%'; |
| 150 |
} |
| 151 |
|
| 152 |
$query .= "'"; |
| 153 |
|
| 154 |
$option_names = $wpdb->get_col($query); |
| 155 |
return $option_names; |
| 156 |
} |
| 157 |
|
| 158 |
|
| 159 |
|
| 160 |
function has_enough_time($start, $time_limit, $max_record_time) |
| 161 |
{ |
| 162 |
return (microtime(true) - $start) < $time_limit - $max_record_time; |
| 163 |
} |
| 164 |
|
| 165 |
function get_memory_usage() |
| 166 |
{ |
| 167 |
return memory_get_usage(true); |
| 168 |
} |
| 169 |
|
| 170 |
function has_enough_memory($memory_max_usage) |
| 171 |
{ |
| 172 |
$limit = $this->get_memory_limit(); |
| 173 |
|
| 174 |
// Has unlimited memory |
| 175 |
if ($limit == '-1') { |
| 176 |
return true; |
| 177 |
} |
| 178 |
|
| 179 |
$limit *= 0.9; |
| 180 |
$current_usage = $this->get_memory_usage(); |
| 181 |
|
| 182 |
if ($current_usage + $memory_max_usage < $limit) { |
| 183 |
return true; |
| 184 |
} |
| 185 |
|
| 186 |
Logger::error(sprintf("Not Enough Memory left to use %s, %s/%s", Logger::formatBytes($memory_max_usage, 2), Logger::formatBytes($current_usage, 2), Logger::formatBytes($limit, 2))); |
| 187 |
|
| 188 |
return false; |
| 189 |
} |
| 190 |
|
| 191 |
function get_memory_limit($force = false) |
| 192 |
{ |
| 193 |
if ($force || is_null($this->memory_limit)) { |
| 194 |
|
| 195 |
$memory_limit = ini_get('memory_limit'); |
| 196 |
if (preg_match('/^(\d+)(.)$/', $memory_limit, $matches)) { |
| 197 |
if ($matches[2] == 'G') { |
| 198 |
$memory_limit = $matches[1] * 1024 * 1024 * 1024; // nnnM -> nnn MB |
| 199 |
} elseif ($matches[2] == 'M') { |
| 200 |
$memory_limit = $matches[1] * 1024 * 1024; // nnnM -> nnn MB |
| 201 |
} else if ($matches[2] == 'K') { |
| 202 |
$memory_limit = $matches[1] * 1024; // nnnK -> nnn KB |
| 203 |
} |
| 204 |
} |
| 205 |
|
| 206 |
$this->memory_limit = $memory_limit; |
| 207 |
|
| 208 |
Logger::info('memory_limit ' . $this->memory_limit . ' bytes'); |
| 209 |
} |
| 210 |
|
| 211 |
return $this->memory_limit; |
| 212 |
} |
| 213 |
} |
| 214 |
|