Actions only triggers one action per node page load

bennybobw - March 31, 2008 - 20:46
Project:Drupal
Version:7.x-dev
Component:trigger.module
Category:bug report
Priority:critical
Assigned:Unassigned
Status:needs review
Description

I discovered this bug while using Actions 5.x-2.0, but am posting here so that it can be fixed in 6x first and then backported. It will need to be confirmed in 6.x. Here's my original post on dev list:

I'm using Actions 5.x-2.0 and Image 5.x-2.x-dev and I wrote a configurable action that automatically adds nodes to a nodequeue on node insert (in this case, images).

It works fine when adding one image, but when I use image_import it only runs for the first image. Image import uses image_create_node_from() which in turn uses node_submit() and node_save() just like node module, so I'm having trouble understanding why it's only running once.

pwolanin responded:

From playing with actions/triggers in 6.x I noticed that seemed
impossible to get it to work on more than one node per page load due
(I think) to internal caching. I'm guessing the same may be true in
5.x.

Not being able to run more than one action per node per page load is problematic for modules that use bulk editing/import features.

#1

Pedro Lozano - October 3, 2008 - 23:32
Version:6.1» 6.x-dev
Priority:normal» critical
Status:active» needs review

I think this is a bug in the trigger.module. It keeps a static flag to avoid recursion when calling actions from trigger_nodeapi but it fails to unset the flag at the end of the function avoiding the actions from been executed again for a different node.

AttachmentSizeStatusTest resultOperations
trigger-issue-241013.patch529 bytesIdleFailed: 7199 passes, 4 fails, 0 exceptionsView details | Re-test

#2

Pedro Lozano - October 3, 2008 - 23:37

Probably we should add an index to the recursion array so recursion is prevented only for the same node. So recursive invocations of the hook for other nodes will also trigger the actions.

This version of the patch also implements this.

AttachmentSizeStatusTest resultOperations
trigger-issue-241013_2.patch1.1 KBIdleFailed: Failed to install HEAD.View details | Re-test

#3

Pedro Lozano - October 3, 2008 - 23:45

This bug is also present in 7.x

#4

Pedro Lozano - October 4, 2008 - 00:11
Version:6.x-dev» 7.x-dev

Rerrolled against HEAD.

Doesn't break the trigger module tests. Maybe the tests should be modified to test the case where a nodeapi op is invoked more than one time (non recursively) for two different nodes.

AttachmentSizeStatusTest resultOperations
trigger-issue-241013_7.x.patch1.09 KBIdleFailed: Failed to apply patch.View details | Re-test

#5

System Message - November 16, 2008 - 21:45
Status:needs review» needs work

The last submitted patch failed testing.

#6

lilou - November 17, 2008 - 14:00

#7

AlexisWilke - December 12, 2008 - 09:04

Pedro,

Good catch, but your patch is still wrong since there are two return statements after the recursive[...] = TRUE statement.

Of course, a much better way of doing this would be to use RAII, but I guess that does not exist in Drupal. So I'll keep quiet.

<?php
/**
* Implementation of hook_nodeapi().
*/
function trigger_nodeapi(&$node, $op, $a3, $a4) {
 
// Keep objects for reuse so that changes actions make to objects can persist.
 
static $objects;
 
// Prevent recursion by tracking which operations have already been called.
 
static $recursion;
 
// Support a subset of operations.
 
if (!in_array($op, array('view', 'update', 'presave', 'insert', 'delete')) || isset($recursion[$op])) {
    return;
  }
 
$recursion[$op] = TRUE;

 
$aids = _trigger_get_hook_aids('nodeapi', $op);
  if (!
$aids) {

// NEED FIX HERE

   
return;
  }
 
$context = array(
   
'hook' => 'nodeapi',
   
'op' => $op,
  );

 
// We need to get the expected object if the action's type is not 'node'.
  // We keep the object in $objects so we can reuse it if we have multiple actions
  // that make changes to an object.
 
foreach ($aids as $aid => $action_info) {
    if (
$action_info['type'] != 'node') {
      if (!isset(
$objects[$action_info['type']])) {
       
$objects[$action_info['type']] = _trigger_normalize_node_context($action_info['type'], $node);
      }
     
// Since we know about the node, we pass that info along to the action.
     
$context['node'] = $node;
     
$result = actions_do($aid, $objects[$action_info['type']], $context, $a4, $a4);
    }
    else {
     
actions_do($aid, $node, $context, $a3, $a4);
    }
  }

// AND NEED FIX THERE
}
?>

#8

System Message - December 19, 2008 - 15:50
Status:needs review» needs work

The last submitted patch failed testing.

#9

mrothroc - March 19, 2009 - 18:09

Subscribing. I'm running into this issue in 6.9.

#10

Pedro Lozano - April 19, 2009 - 22:36

Just a reroll, needs work... and a test!

AttachmentSizeStatusTest resultOperations
trigger-issue-241013_7.x.patch1 KBIdleFailed: Failed to apply patch.View details | Re-test

#11

drewish - July 15, 2009 - 20:20

subscribing. it's freaking comical that 6 was release with this bug. i'd already thought the whole triggers/actions thing was half-baked and not core worthy but this just confirms it. i'll see what i can do about some tests.

#12

drewish - July 15, 2009 - 20:54
Status:needs work» needs review

I actually think that keying by node id defeats the purpose of this check. It seems that you'd first and foremost be trying to stop the case where a node is inserted and a trigger fires and action that inserts another node causing a cascade.

I think the attached patch is sufficient to make trigger useful for the majority of cases where you'd be making bulk changes to nodes. It reorders the _trigger_get_hook_aids() call so that it is checked before the recursion flag is set and unsets the recursion flag at the only remaining exit point.

Still could use specific tests but I want the test bot's feedback.

AttachmentSizeStatusTest resultOperations
trigger_241013.patch868 bytesIdleFailed: 12073 passes, 0 fails, 1 exceptionView details | Re-test

#13

drewish - July 15, 2009 - 20:58

Here's a D6 version of the same.

AttachmentSizeStatusTest resultOperations
trigger_241013.D6.patch1.12 KBIgnoredNoneNone

#14

drewish - July 16, 2009 - 21:16
Title:Actions only triggers one action per node page load» Actions only triggers one action per node/user page load
Status:needs review» needs work

just realized that trigger_user() has the same problem.

#15

drewish - July 16, 2009 - 21:17
Title:Actions only triggers one action per node/user page load» Actions only triggers one action per node page load
Status:needs work» needs review

uh... i'll quickly pull my foot out of my mouth. it's not a problem.

#16

sun - July 18, 2009 - 05:28
Status:needs review» needs work

   static $recursion;
-  if (isset($recursion[$op])) {
-    return;
-  }
-  $recursion[$op] = TRUE;

   $aids = _trigger_get_hook_aids('node', $op);
   if (!$aids) {
     return;
   }
+
+  if (isset($recursion[$op])) {
+    return;
+  }
+  $recursion[$op] = TRUE;

Sorry, but I don't see a difference in here. I'm not sure which edge-case this patch makes work, but it can surely only be an edge-case, where $op is different for another node. Not really sure whether that wasn't already covered by the existing code.

If I'm mistaken, then please take into account that I did not read this issue, only the latest patch. If I'm mistaken, many other developers will be mistaken, and it clearly means that documentation is missing.

#17

AlexisWilke - July 18, 2009 - 07:16

Got to agree that if $aids is NULL the first time, it will always be NULL and thus there is no need to unset() the $recursion. Then next time you can exit the function real quickly since $recursion is TRUE and we already know that $aids is NULL.

A comment would help, however, so we explain why we don't need to unset the $recursion flag in that case!

Thank you.
Alexis

#18

drewish - July 19, 2009 - 17:08

sun, it might not make much difference at all but from the perspective of making the code look saner—you only set the value if it'll be unset on return. i'm not sure the change really needs a comment but i'm happy to put one in if that's the consensus.

#19

drewish - July 19, 2009 - 18:37
Status:needs work» needs review

sun / AlexisWilke: given it more thought and i think it's a bad idea to reuse the recursion flag just to avoid a call to _trigger_get_hook_aids(). we don't do that in _trigger_comment() or _trigger_user(). if we want it from a performance standpoint we should add a separate static value to each of those functions.

i just want to fix the bug in a way that can be backported to D6 so i'm going to leave the fix in a way that's correct and as simple as possible.

#20

AlexisWilke - July 20, 2009 - 03:55

drewish,

As I mentioned in an earlier comment, a better way is to use an RAII approach. In that case, you create an object and when the object is deleted, the flag is reset to FALSE. This is the cleanest way to write this sort of a thing.

More info: http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

In this case, the resource is what you are protecting. The static would still be the boolean flag.

In other words, the most correct approach (to my point of view) would be even slower than the existing solution which resets a flag in two places.

Thank you.
Alexis Wilke

#21

System Message - August 11, 2009 - 07:17
Status:needs review» needs work

The last submitted patch failed testing.

#22

AlexisWilke - August 11, 2009 - 20:26

Looks to me (although I did not look in details) that a test generates an exception and needs to be updated to take the change in account...

#23

stormsweeper - November 8, 2009 - 20:56

Please tell me this did not miss code freeze.

#24

stormsweeper - November 8, 2009 - 20:57
Status:needs work» needs review

Let's see if the last patch works.

 
 

Drupal is a registered trademark of Dries Buytaert.