0

I have a really horrible intermittent bug where a collection of nodes will reset to a previous revision.

Here is an image of the revisions tab on the node:

enter image description here

There are three unusual aspects:

  • The 'current revision' is at the bottom of the list and has lower vid than the rest.
  • The date of the 'current revision' is yesterday but the low vid implies it was created a while ago.
  • The revision log says "Updated by FeedsNodeProcessor" yesterday but the Feeds log tells me "Last run 1 week ago".

I'm hoping someone can point me in the right direction on where to look for a cause since I can't replicate the behaviour.

It feels like the vid in the {node} table has been changed. Along with the created date and revision timestamp. I'm really not sure how that could happen, though...

EDIT: Current Hypothesis

I have a custom module that updates a field. It uses a cron queue, so I'm wondering if the queue contains stale node data:

/**
 * Implements hook_cron().
 */
function ih_transcript_import_cron() {
  // Load all Video nodes.
  $videos = ih_transcript_import_load_video_nodes();

  $queue = DrupalQueue::get('ih_video_transcripts');
  foreach($videos as $video) {
    $queue->createItem($video);
  }
}

In my queue's 'worker callback', I should probably reload the node in case the object has been left in the queue for some time? And ensure I set $node->revision = TRUE before node_save?

  • 1
    When do the nodes revert? Upon cron run, updating the node or something else? Which modules are you using? I suggest cloning the site, and then disabling modules one by one and testing this; sometimes everything goes awry and you can't debug these things reliably, so you might even need to hack core temporarily to see what's going on and where the values become wrong. – Aram Boyajyan Mar 11 '16 at 8:01
  • Cron does sound like a likely candidate but I've re-ran cron several times and the behaviour doesn't replicate. Node update is fine too. I have a custom module, which updates nodes but running the code doesn't replicate either. Finding out when this is happening is tricky, it was fine for a week then bam. – littledynamo Mar 11 '16 at 8:13
  • I'm wondering if $node->revision = TRUE must be set when using node_save. Turning on revisions is a recent thing and I didn't update my code with $node->revision = TRUE – littledynamo Mar 11 '16 at 8:21
  • Either that or the node object is cached somehow and pulling in an old version on node_save. – littledynamo Mar 11 '16 at 8:22
  • I'm using a cron queue, so I'm now wondering if the nodes loaded into the queue are stale. See post edit. – littledynamo Mar 11 '16 at 8:44
0

I tested my hypothesis above and it was correct. The queue isn't fully processed during a cron run, so there are stale node objects sitting in the queue table in the database. The solution was to reload the node in the worker callback.

Original Code

/**
 * Implements hook_cron().
 */
function ih_transcript_import_cron() {
  // Load all Video nodes.
  $videos = ih_transcript_import_load_video_nodes();

  $queue = DrupalQueue::get('ih_video_transcripts');
  foreach($videos as $video) {
    $queue->createItem($video);
  }
}

/**
 * Implements hook_cron_queue_info().
 */
function ih_transcript_import_cron_queue_info() {
  $queues['ih_video_transcripts'] = array(
    'worker callback' => 'ih_transcript_import_import_transcript',
    'time' => '60',
  );
  return $queues;
}

/**
 * Implements callback_queue_worker().
 */
function ih_transcript_import_import_transcript($video_node) {
  // THIS IS THE PROBLEMATIC PART, $video_node is stale since it is
  // pulled from the queue table of the database and there may have been
  // multiple cron runs since it was created (2 days in my case).
  $video_node->field_transcript_text[$video_node->language][0]['value'] = "My value";
  node_save($video_node);
}

The Fix

/**
 * Implements callback_queue_worker().
 */
function ih_transcript_import_import_transcript($video_node) {
  // THIS IS THE IMPORTANT FIX, reload the video node to ensure
  // we are saving the most up to date data.
  $video_node = node_load($video_node->nid);

  $video_node->field_transcript_text[$video_node->language][0]['value'] = $transcript_text->data;
  node_save($video_node);
}

Better Fix

Save ONLY the node Id in the queue rather than the entire node. Also, I added some code to ensure that there are no duplicates in the queue. My queue table had 64000 records!!

/**
 * Implements hook_cron().
 */
function ih_transcript_import_cron() {
  // Load all Video nodes.
  $videos = ih_transcript_import_load_video_nodes();
  $currentQueueItems = array();
  $claimedItems = array();

  $queue = DrupalQueue::get('ih_video_transcripts');

  // We only want to add an item to a queue if it doesn't already exist in the
  // queue to avoid the queue table becoming massive.  Retrieve a list of
  // node ids that already exist in the queue.
  while($item = $queue->claimItem()) {
    $currentQueueItems[] = $item->data;

    // Store a copy of the claimed items, so we can release them again. We
    // only claim them in order to grab the node Id, so need to release again
    // to allow processing.
    $claimedItems[] = $item;
  }

  // Release claimed queue items to allow them to be processed by other workers.
  foreach($claimedItems as $claimedItem) {
    $queue->releaseItem($claimedItem);
  }

  foreach($videos as $video) {
    // Don't add videos that already exist in the queue.
    if(!in_array($video->nid, $currentQueueItems)) {
      $queue->createItem($video->nid);
    }
  }
}

/**
 * Implements hook_cron_queue_info().
 */
function ih_transcript_import_cron_queue_info() {
  $queues['ih_video_transcripts'] = array(
    'worker callback' => 'ih_transcript_import_import_transcript',
    'time' => '60',
  );
  return $queues;
}

/**
 * Implements callback_queue_worker().  Can also be called independently of cron
 * to import a single transcript.
 */
function ih_transcript_import_import_transcript($video_nid) {

  $video_node = node_load($video_nid);

  $video_node->field_transcript_text[$video_node->language][0]['value'] = $transcript_text->data;
  node_save($video_node);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.