cms_content_sync-3.0.x-dev/src/Controller/UpdateLock.php

src/Controller/UpdateLock.php
<?php

namespace Drupal\cms_content_sync\Controller;

use Drupal\Core\Controller\ControllerBase;

/**
 * Class UpdateLock.
 *
 * Make sure we never try to update the same entity simultaneously. This can
 * happen if e.g. the execution time of PHP is at 5 minutes and our request
 * timeout is at 1 minute. In this case the update request will fail after 1
 * minute and the Sync Core will retry the update between 1-60 seconds later.
 * But as the execution limit is 5 minutes, the previous update is actually
 * still running, so now we have two updates on the same entity running
 * simultaneously, leading to unexpected behavior.
 * Updates can go missing, references like paragraphs can be removed etc.
 * It's also hard to recover from if the "skip-unchanged" optimization is on,
 * because we assume that the previous update was a success even though it left
 * the content in a broken state.
 */
class UpdateLock extends ControllerBase {
  /**
   * @var string COLLECTION_NAME
   *             The prefix to use for saving entity update state.
   */
  public const COLLECTION_NAME = 'cms_content_sync:update_lock';

  /**
   * @var string COLLECTION_NAME
   *             The prefix to use for saving entity update state.
   */
  public const CACHE_TAG_UPDATE_LOCK = 'cms_content_sync:update_lock';

  /**
   * When the lock on an entity expires automatically, in seconds, assuming that
   * the previous update failed or was cancelled.
   * Defaults to the max execution time or 60 if none is given.
   *
   * Can be set by environment variable, e.g.:
   * CMS_CONTENT_SYNC_UPDATE_LOCK_EXPIRATION="120"
   *
   * @return int
   */
  public static function getLockExpiration() {
    static $value = NULL;
    if ($value !== NULL) {
      return $value;
    }

    $env = getenv('CMS_CONTENT_SYNC_UPDATE_LOCK_EXPIRATION');
    if ($env && (int) $env) {
      return $value = (int) $env;
    }

    return $value = (int) ini_get('max_execution_time') ?? 60;
  }

  protected static $locked = [];

  protected static $last_lock = NULL;

  /**
   *
   */
  public static function lock($entity_type_id, $shared_entity_id) {
    $cache = \Drupal::cache();
    $cache_item_id = self::COLLECTION_NAME . ':' . $entity_type_id . ':' . $shared_entity_id;

    $cache_item_data = new \stdClass();
    $cache_item_data->lock = time();
    $expiration = time() + self::getLockExpiration();
    $cache->set($cache_item_id, $cache_item_data, $expiration, [self::CACHE_TAG_UPDATE_LOCK]);

    self::renew();

    if (!in_array($cache_item_id, UpdateLock::$locked)) {
      UpdateLock::$locked[] = $cache_item_id;
    }
  }

  /**
   *
   */
  public static function isLocked($entity_type_id, $shared_entity_id) {
    $cache = \Drupal::cache();
    $cache_item_id = self::COLLECTION_NAME . ':' . $entity_type_id . ':' . $shared_entity_id;
    $cache_item = $cache->get($cache_item_id);

    return !!$cache_item;
  }

  /**
   *
   */
  public static function unlock($entity_type_id, $shared_entity_id) {
    $cache = \Drupal::cache();
    $cache_item_id = self::COLLECTION_NAME . ':' . $entity_type_id . ':' . $shared_entity_id;

    $cache->delete($cache_item_id);

    UpdateLock::$locked = array_diff(UpdateLock::$locked, [$cache_item_id]);
  }

  /**
   *
   */
  public static function renew() {
    $now = time();
    $expire_after = self::getLockExpiration();

    // Locks have recently been renewed (<20% of expiry passed).
    if (self::$last_lock && self::$last_lock > $now - $expire_after / 5) {
      return;
    }

    $cache = \Drupal::cache();
    $cache_item_data = new \stdClass();
    $cache_item_data->lock = time();
    $expiration = $now + $expire_after;

    foreach (UpdateLock::$locked as $cache_item_id) {
      $cache->set($cache_item_id, $cache_item_data, $expiration, [self::CACHE_TAG_UPDATE_LOCK]);
    }

    self::$last_lock = $now;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc