API page: https://api.drupal.org/api/drupal/modules%21node%21node.module/function/...

First, although it may seem to be, this is not a duplicate of previous issue threads about rebuilding content access permissions. (I have scanned them all, and none of them surface this issue, which is present in the 7.28 version of node.module.)

The expected behavior of this function is that it terminates once all its processing of the batch in question has been completed.

However, this is not always the case. At times, due to matters outside this function's scope of concerns, the assignment statement:

$nids = db_query_range( ... )

returns an empty array. But the code assumes this can never happen, and thus does not check for it. In failing to do so, the function opens itself up to triggering an infinite loop, because it repeatedly performs the assignment:

$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];

which will always yield a fraction (less than 1).

The following coding of the function resolves this issue, plus it cleans up two pieces of coding sloppiness (as noted in the added comments):

function _node_access_rebuild_batch_operation(&$context) {

  if (empty($context['sandbox'])) {
    // Initiate multistep processing.
    $context['sandbox']['progress'] = 0;
    $context['sandbox']['current_node'] = 0;
    // carry max as a proper integer in the array, not as the string '1'
    $context['sandbox']['max'] = intval( db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField() );
  }

  // Process the next 20 nodes.
  $limit = 20;
  $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", 0, $limit, array(':nid' => $context['sandbox']['current_node']))->fetchCol();

  // look for the corner case
  if( ! empty( $nids ) ) {
	$nodes = node_load_multiple($nids, array(), TRUE);
	foreach ($nodes as $nid => $node) {
	  // To preserve database integrity, only acquire grants if the node
	  // loads successfully.
	  if (!empty($node)) {
		node_access_acquire_grants($node);
	  }
	  $context['sandbox']['progress']++;
	  $context['sandbox']['current_node'] = $nid;
	}
  }
  // handle the corner case
  else {
  	$context['sandbox']['progress'] = $context['sandbox']['max'];
  }

  // Multistep processing : report progress.
  if ($context['sandbox']['progress'] < $context['sandbox']['max']) {
    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  }
  // do not assume that the passed-in value was 1
  else {
    $context['finished'] = 1;
  }
  
}

Comments

damienmckenna’s picture

Status: Active » Closed (duplicate)

Thanks for submitting this, Bob.

This would IMHO boil down to one major question: what causes the multiple iterations of $nids to fall out of sync with $context['sandbox']['max']? The function assumes that the 'max' at the beginning of the execution cycle would stay consistent until the batch process was finished, something else would have to occur during that period of time. So I would start looking there.

That said, because the node access rebuild process can take a long time on sites with lots of content. Also, this fact does in fact make this issue a duplicate of #315302: Node Access Rebuild never finishes (infinite loop), so I'm going to mark this ticket as such.

damienmckenna’s picture

Anonymous’s picture

"This would IMHO boil down to one major question: what causes the multiple iterations of $nids to fall out of sync with $context['sandbox']['max']? The function assumes that the 'max' at the beginning of the execution cycle would stay consistent until the batch process was finished, something else would have to occur during that period of time."

That said, the code does not handle the corner case. Speaking as a veteran Java Enterprise framework-level architect and engineer, that is just plain bad coding.

I know lots of Drupalists will bridle at my assertion, but they are just plain wrong. All code should handle all cases, and do so gracefully.

This function fails that standard.

Being a report on the corner case, you may do what you wish, but this issue is not a duplicate. I have done my bit, so now I will gracefully bow out.

damienmckenna’s picture

@Bob: First off, nobody is denying there's a bug, I'm just saying that nobody has provided a patch that the core maintainers were sufficiently happy with in order for the bug to be resolved - o bviously the bug does not happen often enough to cause major problems, otherwise it would have been fixed by now. You could help with that by working on a properly formatted patch for Drupal 8 that would resolve the issue, which can then be backported to Drupal 7 and 6.

Further, what you describe is exactly the same as the other cases - the 'max' number of values at the start of the batch operation runs out of sync with the ongoing batch process, i.e. something else is interfering with the data, something outside of the batch process itself. Yes, the system should be able to handle this, and yes there has been an issue open since 2008 to resolve it - I ran into the issue myself in 2009, spent a day digging through the code execution process to identify and confirm what was going on, which led to a partial solution which was not quite sufficient to be committed.

What we're left with are two related issues trying to work on the problem from two different directions: