annotate core/modules/migrate/src/MigrateExecutable.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\migrate;
Chris@0 4
Chris@0 5 use Drupal\Component\Utility\Bytes;
Chris@0 6 use Drupal\Core\Utility\Error;
Chris@0 7 use Drupal\Core\StringTranslation\StringTranslationTrait;
Chris@0 8 use Drupal\migrate\Event\MigrateEvents;
Chris@0 9 use Drupal\migrate\Event\MigrateImportEvent;
Chris@0 10 use Drupal\migrate\Event\MigratePostRowSaveEvent;
Chris@0 11 use Drupal\migrate\Event\MigratePreRowSaveEvent;
Chris@0 12 use Drupal\migrate\Event\MigrateRollbackEvent;
Chris@0 13 use Drupal\migrate\Event\MigrateRowDeleteEvent;
Chris@0 14 use Drupal\migrate\Exception\RequirementsException;
Chris@0 15 use Drupal\migrate\Plugin\MigrateIdMapInterface;
Chris@0 16 use Drupal\migrate\Plugin\MigrationInterface;
Chris@0 17 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
Chris@0 18
Chris@0 19 /**
Chris@0 20 * Defines a migrate executable class.
Chris@0 21 */
Chris@0 22 class MigrateExecutable implements MigrateExecutableInterface {
Chris@0 23 use StringTranslationTrait;
Chris@0 24
Chris@0 25 /**
Chris@0 26 * The configuration of the migration to do.
Chris@0 27 *
Chris@0 28 * @var \Drupal\migrate\Plugin\MigrationInterface
Chris@0 29 */
Chris@0 30 protected $migration;
Chris@0 31
Chris@0 32 /**
Chris@0 33 * Status of one row.
Chris@0 34 *
Chris@0 35 * The value is a MigrateIdMapInterface::STATUS_* constant, for example:
Chris@0 36 * STATUS_IMPORTED.
Chris@0 37 *
Chris@0 38 * @var int
Chris@0 39 */
Chris@0 40 protected $sourceRowStatus;
Chris@0 41
Chris@0 42 /**
Chris@0 43 * The ratio of the memory limit at which an operation will be interrupted.
Chris@0 44 *
Chris@0 45 * @var float
Chris@0 46 */
Chris@0 47 protected $memoryThreshold = 0.85;
Chris@0 48
Chris@0 49 /**
Chris@0 50 * The PHP memory_limit expressed in bytes.
Chris@0 51 *
Chris@0 52 * @var int
Chris@0 53 */
Chris@0 54 protected $memoryLimit;
Chris@0 55
Chris@0 56 /**
Chris@0 57 * The configuration values of the source.
Chris@0 58 *
Chris@0 59 * @var array
Chris@0 60 */
Chris@0 61 protected $sourceIdValues;
Chris@0 62
Chris@0 63 /**
Chris@0 64 * An array of counts. Initially used for cache hit/miss tracking.
Chris@0 65 *
Chris@0 66 * @var array
Chris@0 67 */
Chris@0 68 protected $counts = [];
Chris@0 69
Chris@0 70 /**
Chris@0 71 * The source.
Chris@0 72 *
Chris@0 73 * @var \Drupal\migrate\Plugin\MigrateSourceInterface
Chris@0 74 */
Chris@0 75 protected $source;
Chris@0 76
Chris@0 77 /**
Chris@0 78 * The event dispatcher.
Chris@0 79 *
Chris@0 80 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
Chris@0 81 */
Chris@0 82 protected $eventDispatcher;
Chris@0 83
Chris@0 84 /**
Chris@0 85 * Migration message service.
Chris@0 86 *
Chris@0 87 * @todo https://www.drupal.org/node/2822663 Make this protected.
Chris@0 88 *
Chris@0 89 * @var \Drupal\migrate\MigrateMessageInterface
Chris@0 90 */
Chris@0 91 public $message;
Chris@0 92
Chris@0 93 /**
Chris@0 94 * Constructs a MigrateExecutable and verifies and sets the memory limit.
Chris@0 95 *
Chris@0 96 * @param \Drupal\migrate\Plugin\MigrationInterface $migration
Chris@0 97 * The migration to run.
Chris@0 98 * @param \Drupal\migrate\MigrateMessageInterface $message
Chris@14 99 * (optional) The migrate message service.
Chris@0 100 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
Chris@14 101 * (optional) The event dispatcher.
Chris@0 102 *
Chris@0 103 * @throws \Drupal\migrate\MigrateException
Chris@0 104 */
Chris@14 105 public function __construct(MigrationInterface $migration, MigrateMessageInterface $message = NULL, EventDispatcherInterface $event_dispatcher = NULL) {
Chris@0 106 $this->migration = $migration;
Chris@14 107 $this->message = $message ?: new MigrateMessage();
Chris@18 108 $this->getIdMap()->setMessage($this->message);
Chris@0 109 $this->eventDispatcher = $event_dispatcher;
Chris@0 110 // Record the memory limit in bytes
Chris@0 111 $limit = trim(ini_get('memory_limit'));
Chris@0 112 if ($limit == '-1') {
Chris@0 113 $this->memoryLimit = PHP_INT_MAX;
Chris@0 114 }
Chris@0 115 else {
Chris@0 116 $this->memoryLimit = Bytes::toInt($limit);
Chris@0 117 }
Chris@0 118 }
Chris@0 119
Chris@0 120 /**
Chris@0 121 * Returns the source.
Chris@0 122 *
Chris@0 123 * Makes sure source is initialized based on migration settings.
Chris@0 124 *
Chris@0 125 * @return \Drupal\migrate\Plugin\MigrateSourceInterface
Chris@0 126 * The source.
Chris@0 127 */
Chris@0 128 protected function getSource() {
Chris@0 129 if (!isset($this->source)) {
Chris@0 130 $this->source = $this->migration->getSourcePlugin();
Chris@0 131 }
Chris@0 132 return $this->source;
Chris@0 133 }
Chris@0 134
Chris@0 135 /**
Chris@0 136 * Gets the event dispatcher.
Chris@0 137 *
Chris@0 138 * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
Chris@0 139 */
Chris@0 140 protected function getEventDispatcher() {
Chris@0 141 if (!$this->eventDispatcher) {
Chris@0 142 $this->eventDispatcher = \Drupal::service('event_dispatcher');
Chris@0 143 }
Chris@0 144 return $this->eventDispatcher;
Chris@0 145 }
Chris@0 146
Chris@0 147 /**
Chris@0 148 * {@inheritdoc}
Chris@0 149 */
Chris@0 150 public function import() {
Chris@0 151 // Only begin the import operation if the migration is currently idle.
Chris@0 152 if ($this->migration->getStatus() !== MigrationInterface::STATUS_IDLE) {
Chris@0 153 $this->message->display($this->t('Migration @id is busy with another operation: @status',
Chris@0 154 [
Chris@0 155 '@id' => $this->migration->id(),
Chris@0 156 '@status' => $this->t($this->migration->getStatusLabel()),
Chris@0 157 ]), 'error');
Chris@0 158 return MigrationInterface::RESULT_FAILED;
Chris@0 159 }
Chris@0 160 $this->getEventDispatcher()->dispatch(MigrateEvents::PRE_IMPORT, new MigrateImportEvent($this->migration, $this->message));
Chris@0 161
Chris@0 162 // Knock off migration if the requirements haven't been met.
Chris@0 163 try {
Chris@0 164 $this->migration->checkRequirements();
Chris@0 165 }
Chris@0 166 catch (RequirementsException $e) {
Chris@0 167 $this->message->display(
Chris@0 168 $this->t(
Chris@0 169 'Migration @id did not meet the requirements. @message @requirements',
Chris@0 170 [
Chris@0 171 '@id' => $this->migration->id(),
Chris@0 172 '@message' => $e->getMessage(),
Chris@0 173 '@requirements' => $e->getRequirementsString(),
Chris@0 174 ]
Chris@0 175 ),
Chris@0 176 'error'
Chris@0 177 );
Chris@0 178
Chris@0 179 return MigrationInterface::RESULT_FAILED;
Chris@0 180 }
Chris@0 181
Chris@0 182 $this->migration->setStatus(MigrationInterface::STATUS_IMPORTING);
Chris@0 183 $return = MigrationInterface::RESULT_COMPLETED;
Chris@0 184 $source = $this->getSource();
Chris@18 185 $id_map = $this->getIdMap();
Chris@0 186
Chris@0 187 try {
Chris@0 188 $source->rewind();
Chris@0 189 }
Chris@0 190 catch (\Exception $e) {
Chris@0 191 $this->message->display(
Chris@0 192 $this->t('Migration failed with source plugin exception: @e', ['@e' => $e->getMessage()]), 'error');
Chris@0 193 $this->migration->setStatus(MigrationInterface::STATUS_IDLE);
Chris@0 194 return MigrationInterface::RESULT_FAILED;
Chris@0 195 }
Chris@0 196
Chris@0 197 $destination = $this->migration->getDestinationPlugin();
Chris@0 198 while ($source->valid()) {
Chris@0 199 $row = $source->current();
Chris@0 200 $this->sourceIdValues = $row->getSourceIdValues();
Chris@0 201
Chris@0 202 try {
Chris@0 203 $this->processRow($row);
Chris@0 204 $save = TRUE;
Chris@0 205 }
Chris@0 206 catch (MigrateException $e) {
Chris@18 207 $this->getIdMap()->saveIdMapping($row, [], $e->getStatus());
Chris@0 208 $this->saveMessage($e->getMessage(), $e->getLevel());
Chris@0 209 $save = FALSE;
Chris@0 210 }
Chris@0 211 catch (MigrateSkipRowException $e) {
Chris@0 212 if ($e->getSaveToMap()) {
Chris@0 213 $id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED);
Chris@0 214 }
Chris@0 215 if ($message = trim($e->getMessage())) {
Chris@0 216 $this->saveMessage($message, MigrationInterface::MESSAGE_INFORMATIONAL);
Chris@0 217 }
Chris@0 218 $save = FALSE;
Chris@0 219 }
Chris@0 220
Chris@0 221 if ($save) {
Chris@0 222 try {
Chris@0 223 $this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROW_SAVE, new MigratePreRowSaveEvent($this->migration, $this->message, $row));
Chris@17 224 $destination_ids = $id_map->lookupDestinationIds($this->sourceIdValues);
Chris@17 225 $destination_id_values = $destination_ids ? reset($destination_ids) : [];
Chris@17 226 $destination_id_values = $destination->import($row, $destination_id_values);
Chris@0 227 $this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROW_SAVE, new MigratePostRowSaveEvent($this->migration, $this->message, $row, $destination_id_values));
Chris@0 228 if ($destination_id_values) {
Chris@0 229 // We do not save an idMap entry for config.
Chris@0 230 if ($destination_id_values !== TRUE) {
Chris@0 231 $id_map->saveIdMapping($row, $destination_id_values, $this->sourceRowStatus, $destination->rollbackAction());
Chris@0 232 }
Chris@0 233 }
Chris@0 234 else {
Chris@0 235 $id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_FAILED);
Chris@0 236 if (!$id_map->messageCount()) {
Chris@0 237 $message = $this->t('New object was not saved, no error provided');
Chris@0 238 $this->saveMessage($message);
Chris@0 239 $this->message->display($message);
Chris@0 240 }
Chris@0 241 }
Chris@0 242 }
Chris@0 243 catch (MigrateException $e) {
Chris@18 244 $this->getIdMap()->saveIdMapping($row, [], $e->getStatus());
Chris@0 245 $this->saveMessage($e->getMessage(), $e->getLevel());
Chris@0 246 }
Chris@0 247 catch (\Exception $e) {
Chris@18 248 $this->getIdMap()->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_FAILED);
Chris@0 249 $this->handleException($e);
Chris@0 250 }
Chris@0 251 }
Chris@0 252
Chris@0 253 $this->sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
Chris@0 254
Chris@0 255 // Check for memory exhaustion.
Chris@0 256 if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
Chris@0 257 break;
Chris@0 258 }
Chris@0 259
Chris@0 260 // If anyone has requested we stop, return the requested result.
Chris@0 261 if ($this->migration->getStatus() == MigrationInterface::STATUS_STOPPING) {
Chris@0 262 $return = $this->migration->getInterruptionResult();
Chris@0 263 $this->migration->clearInterruptionResult();
Chris@0 264 break;
Chris@0 265 }
Chris@0 266
Chris@0 267 try {
Chris@0 268 $source->next();
Chris@0 269 }
Chris@0 270 catch (\Exception $e) {
Chris@0 271 $this->message->display(
Chris@0 272 $this->t('Migration failed with source plugin exception: @e',
Chris@0 273 ['@e' => $e->getMessage()]), 'error');
Chris@0 274 $this->migration->setStatus(MigrationInterface::STATUS_IDLE);
Chris@0 275 return MigrationInterface::RESULT_FAILED;
Chris@0 276 }
Chris@0 277 }
Chris@0 278
Chris@0 279 $this->getEventDispatcher()->dispatch(MigrateEvents::POST_IMPORT, new MigrateImportEvent($this->migration, $this->message));
Chris@0 280 $this->migration->setStatus(MigrationInterface::STATUS_IDLE);
Chris@0 281 return $return;
Chris@0 282 }
Chris@0 283
Chris@0 284 /**
Chris@0 285 * {@inheritdoc}
Chris@0 286 */
Chris@0 287 public function rollback() {
Chris@0 288 // Only begin the rollback operation if the migration is currently idle.
Chris@0 289 if ($this->migration->getStatus() !== MigrationInterface::STATUS_IDLE) {
Chris@0 290 $this->message->display($this->t('Migration @id is busy with another operation: @status', ['@id' => $this->migration->id(), '@status' => $this->t($this->migration->getStatusLabel())]), 'error');
Chris@0 291 return MigrationInterface::RESULT_FAILED;
Chris@0 292 }
Chris@0 293
Chris@0 294 // Announce that rollback is about to happen.
Chris@0 295 $this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROLLBACK, new MigrateRollbackEvent($this->migration));
Chris@0 296
Chris@0 297 // Optimistically assume things are going to work out; if not, $return will be
Chris@0 298 // updated to some other status.
Chris@0 299 $return = MigrationInterface::RESULT_COMPLETED;
Chris@0 300
Chris@0 301 $this->migration->setStatus(MigrationInterface::STATUS_ROLLING_BACK);
Chris@18 302 $id_map = $this->getIdMap();
Chris@0 303 $destination = $this->migration->getDestinationPlugin();
Chris@0 304
Chris@0 305 // Loop through each row in the map, and try to roll it back.
Chris@18 306 $id_map->rewind();
Chris@18 307 while ($id_map->valid()) {
Chris@0 308 $destination_key = $id_map->currentDestination();
Chris@0 309 if ($destination_key) {
Chris@0 310 $map_row = $id_map->getRowByDestination($destination_key);
Chris@0 311 if ($map_row['rollback_action'] == MigrateIdMapInterface::ROLLBACK_DELETE) {
Chris@0 312 $this->getEventDispatcher()
Chris@0 313 ->dispatch(MigrateEvents::PRE_ROW_DELETE, new MigrateRowDeleteEvent($this->migration, $destination_key));
Chris@0 314 $destination->rollback($destination_key);
Chris@0 315 $this->getEventDispatcher()
Chris@0 316 ->dispatch(MigrateEvents::POST_ROW_DELETE, new MigrateRowDeleteEvent($this->migration, $destination_key));
Chris@0 317 }
Chris@0 318 // We're now done with this row, so remove it from the map.
Chris@0 319 $id_map->deleteDestination($destination_key);
Chris@0 320 }
Chris@0 321 else {
Chris@0 322 // If there is no destination key the import probably failed and we can
Chris@0 323 // remove the row without further action.
Chris@0 324 $source_key = $id_map->currentSource();
Chris@0 325 $id_map->delete($source_key);
Chris@0 326 }
Chris@18 327 $id_map->next();
Chris@0 328
Chris@0 329 // Check for memory exhaustion.
Chris@0 330 if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
Chris@0 331 break;
Chris@0 332 }
Chris@0 333
Chris@0 334 // If anyone has requested we stop, return the requested result.
Chris@0 335 if ($this->migration->getStatus() == MigrationInterface::STATUS_STOPPING) {
Chris@0 336 $return = $this->migration->getInterruptionResult();
Chris@0 337 $this->migration->clearInterruptionResult();
Chris@0 338 break;
Chris@0 339 }
Chris@0 340 }
Chris@0 341
Chris@0 342 // Notify modules that rollback attempt was complete.
Chris@0 343 $this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROLLBACK, new MigrateRollbackEvent($this->migration));
Chris@0 344 $this->migration->setStatus(MigrationInterface::STATUS_IDLE);
Chris@0 345
Chris@0 346 return $return;
Chris@0 347 }
Chris@0 348
Chris@0 349 /**
Chris@18 350 * Get the ID map from the current migration.
Chris@18 351 *
Chris@18 352 * @return \Drupal\migrate\Plugin\MigrateIdMapInterface
Chris@18 353 * The ID map.
Chris@18 354 */
Chris@18 355 protected function getIdMap() {
Chris@18 356 return $this->migration->getIdMap();
Chris@18 357 }
Chris@18 358
Chris@18 359 /**
Chris@0 360 * {@inheritdoc}
Chris@0 361 */
Chris@0 362 public function processRow(Row $row, array $process = NULL, $value = NULL) {
Chris@0 363 foreach ($this->migration->getProcessPlugins($process) as $destination => $plugins) {
Chris@0 364 $multiple = FALSE;
Chris@0 365 /** @var $plugin \Drupal\migrate\Plugin\MigrateProcessInterface */
Chris@0 366 foreach ($plugins as $plugin) {
Chris@0 367 $definition = $plugin->getPluginDefinition();
Chris@0 368 // Many plugins expect a scalar value but the current value of the
Chris@0 369 // pipeline might be multiple scalars (this is set by the previous
Chris@0 370 // plugin) and in this case the current value needs to be iterated
Chris@0 371 // and each scalar separately transformed.
Chris@0 372 if ($multiple && !$definition['handle_multiples']) {
Chris@0 373 $new_value = [];
Chris@0 374 if (!is_array($value)) {
Chris@0 375 throw new MigrateException(sprintf('Pipeline failed at %s plugin for destination %s: %s received instead of an array,', $plugin->getPluginId(), $destination, $value));
Chris@0 376 }
Chris@0 377 $break = FALSE;
Chris@0 378 foreach ($value as $scalar_value) {
Chris@0 379 try {
Chris@0 380 $new_value[] = $plugin->transform($scalar_value, $this, $row, $destination);
Chris@0 381 }
Chris@0 382 catch (MigrateSkipProcessException $e) {
Chris@0 383 $new_value[] = NULL;
Chris@0 384 $break = TRUE;
Chris@0 385 }
Chris@0 386 }
Chris@0 387 $value = $new_value;
Chris@0 388 if ($break) {
Chris@0 389 break;
Chris@0 390 }
Chris@0 391 }
Chris@0 392 else {
Chris@0 393 try {
Chris@0 394 $value = $plugin->transform($value, $this, $row, $destination);
Chris@0 395 }
Chris@0 396 catch (MigrateSkipProcessException $e) {
Chris@0 397 $value = NULL;
Chris@0 398 break;
Chris@0 399 }
Chris@0 400 $multiple = $plugin->multiple();
Chris@0 401 }
Chris@0 402 }
Chris@0 403 // Ensure all values, including nulls, are migrated.
Chris@0 404 if ($plugins) {
Chris@0 405 if (isset($value)) {
Chris@0 406 $row->setDestinationProperty($destination, $value);
Chris@0 407 }
Chris@0 408 else {
Chris@0 409 $row->setEmptyDestinationProperty($destination);
Chris@0 410 }
Chris@0 411 }
Chris@0 412 // Reset the value.
Chris@0 413 $value = NULL;
Chris@0 414 }
Chris@0 415 }
Chris@0 416
Chris@0 417 /**
Chris@0 418 * Fetches the key array for the current source record.
Chris@0 419 *
Chris@0 420 * @return array
Chris@0 421 * The current source IDs.
Chris@0 422 */
Chris@0 423 protected function currentSourceIds() {
Chris@0 424 return $this->getSource()->getCurrentIds();
Chris@0 425 }
Chris@0 426
Chris@0 427 /**
Chris@0 428 * {@inheritdoc}
Chris@0 429 */
Chris@0 430 public function saveMessage($message, $level = MigrationInterface::MESSAGE_ERROR) {
Chris@18 431 $this->getIdMap()->saveMessage($this->sourceIdValues, $message, $level);
Chris@0 432 }
Chris@0 433
Chris@0 434 /**
Chris@0 435 * Takes an Exception object and both saves and displays it.
Chris@0 436 *
Chris@0 437 * Pulls in additional information on the location triggering the exception.
Chris@0 438 *
Chris@0 439 * @param \Exception $exception
Chris@0 440 * Object representing the exception.
Chris@0 441 * @param bool $save
Chris@0 442 * (optional) Whether to save the message in the migration's mapping table.
Chris@0 443 * Set to FALSE in contexts where this doesn't make sense.
Chris@0 444 */
Chris@0 445 protected function handleException(\Exception $exception, $save = TRUE) {
Chris@0 446 $result = Error::decodeException($exception);
Chris@0 447 $message = $result['@message'] . ' (' . $result['%file'] . ':' . $result['%line'] . ')';
Chris@0 448 if ($save) {
Chris@0 449 $this->saveMessage($message);
Chris@0 450 }
Chris@0 451 $this->message->display($message, 'error');
Chris@0 452 }
Chris@0 453
Chris@0 454 /**
Chris@0 455 * Checks for exceptional conditions, and display feedback.
Chris@0 456 */
Chris@0 457 protected function checkStatus() {
Chris@0 458 if ($this->memoryExceeded()) {
Chris@0 459 return MigrationInterface::RESULT_INCOMPLETE;
Chris@0 460 }
Chris@0 461 return MigrationInterface::RESULT_COMPLETED;
Chris@0 462 }
Chris@0 463
Chris@0 464 /**
Chris@0 465 * Tests whether we've exceeded the desired memory threshold.
Chris@0 466 *
Chris@0 467 * If so, output a message.
Chris@0 468 *
Chris@0 469 * @return bool
Chris@0 470 * TRUE if the threshold is exceeded, otherwise FALSE.
Chris@0 471 */
Chris@0 472 protected function memoryExceeded() {
Chris@0 473 $usage = $this->getMemoryUsage();
Chris@0 474 $pct_memory = $usage / $this->memoryLimit;
Chris@0 475 if (!$threshold = $this->memoryThreshold) {
Chris@0 476 return FALSE;
Chris@0 477 }
Chris@0 478 if ($pct_memory > $threshold) {
Chris@0 479 $this->message->display(
Chris@0 480 $this->t(
Chris@0 481 'Memory usage is @usage (@pct% of limit @limit), reclaiming memory.',
Chris@0 482 [
Chris@0 483 '@pct' => round($pct_memory * 100),
Chris@0 484 '@usage' => $this->formatSize($usage),
Chris@0 485 '@limit' => $this->formatSize($this->memoryLimit),
Chris@0 486 ]
Chris@0 487 ),
Chris@0 488 'warning'
Chris@0 489 );
Chris@0 490 $usage = $this->attemptMemoryReclaim();
Chris@0 491 $pct_memory = $usage / $this->memoryLimit;
Chris@0 492 // Use a lower threshold - we don't want to be in a situation where we keep
Chris@0 493 // coming back here and trimming a tiny amount
Chris@0 494 if ($pct_memory > (0.90 * $threshold)) {
Chris@0 495 $this->message->display(
Chris@0 496 $this->t(
Chris@0 497 'Memory usage is now @usage (@pct% of limit @limit), not enough reclaimed, starting new batch',
Chris@0 498 [
Chris@0 499 '@pct' => round($pct_memory * 100),
Chris@0 500 '@usage' => $this->formatSize($usage),
Chris@0 501 '@limit' => $this->formatSize($this->memoryLimit),
Chris@0 502 ]
Chris@0 503 ),
Chris@0 504 'warning'
Chris@0 505 );
Chris@0 506 return TRUE;
Chris@0 507 }
Chris@0 508 else {
Chris@0 509 $this->message->display(
Chris@0 510 $this->t(
Chris@0 511 'Memory usage is now @usage (@pct% of limit @limit), reclaimed enough, continuing',
Chris@0 512 [
Chris@0 513 '@pct' => round($pct_memory * 100),
Chris@0 514 '@usage' => $this->formatSize($usage),
Chris@0 515 '@limit' => $this->formatSize($this->memoryLimit),
Chris@0 516 ]
Chris@0 517 ),
Chris@0 518 'warning');
Chris@0 519 return FALSE;
Chris@0 520 }
Chris@0 521 }
Chris@0 522 else {
Chris@0 523 return FALSE;
Chris@0 524 }
Chris@0 525 }
Chris@0 526
Chris@0 527 /**
Chris@0 528 * Returns the memory usage so far.
Chris@0 529 *
Chris@0 530 * @return int
Chris@0 531 * The memory usage.
Chris@0 532 */
Chris@0 533 protected function getMemoryUsage() {
Chris@0 534 return memory_get_usage();
Chris@0 535 }
Chris@0 536
Chris@0 537 /**
Chris@0 538 * Tries to reclaim memory.
Chris@0 539 *
Chris@0 540 * @return int
Chris@0 541 * The memory usage after reclaim.
Chris@0 542 */
Chris@0 543 protected function attemptMemoryReclaim() {
Chris@0 544 // First, try resetting Drupal's static storage - this frequently releases
Chris@0 545 // plenty of memory to continue.
Chris@0 546 drupal_static_reset();
Chris@0 547
Chris@0 548 // Entity storage can blow up with caches so clear them out.
Chris@0 549 $manager = \Drupal::entityManager();
Chris@0 550 foreach ($manager->getDefinitions() as $id => $definition) {
Chris@0 551 $manager->getStorage($id)->resetCache();
Chris@0 552 }
Chris@0 553
Chris@0 554 // @TODO: explore resetting the container.
Chris@0 555
Chris@0 556 // Run garbage collector to further reduce memory.
Chris@0 557 gc_collect_cycles();
Chris@0 558
Chris@0 559 return memory_get_usage();
Chris@0 560 }
Chris@0 561
Chris@0 562 /**
Chris@0 563 * Generates a string representation for the given byte count.
Chris@0 564 *
Chris@0 565 * @param int $size
Chris@0 566 * A size in bytes.
Chris@0 567 *
Chris@0 568 * @return string
Chris@0 569 * A translated string representation of the size.
Chris@0 570 */
Chris@0 571 protected function formatSize($size) {
Chris@0 572 return format_size($size);
Chris@0 573 }
Chris@0 574
Chris@0 575 }