60
Views
17
Comments
recursive interation throw for each

hi guys, i m trying to do a batch but i keep having this error, how can i pass it? notice that var1 is my index in the for each so that i wouldnt get any timeout

Timer TimerMonthComissions error (inside action 'TimerMonthComissions'). Timer duration = 0 secs:Detected a recursive iteration over the same list. [retry 1 of 3 scheduled] 

Hello,

The Server Actions inside the loop are also doing a for each with the same list?

Hi João Pedro Espinheira, 

You are using var1 for Start index, means if it is a number default value will be 0.

After each iteration var1 value should be increased so it will iterate for all records. Make sure var1 is increasing as var1 = var1 + 1 after if condition for True and False branch. 


Regards,

Shubham

That's nonsense. The Start Index will only be evaluated at the start of the loop.

Hi João,

I'm not sure where that error message comes from, as I don't see any obvious recursive loops. Can it be that one of the Actions inside the For Each loop has the full list as input, and tries to iterate over the list?

That said, the restart logic of your Timer is flawed. What happens when the loop is finished (20 records have been processed), is that:

  1. You adjust var1;
  2. You try to restart the Timer;
  3. You execute the query again;
  4. You restart the loop again, in the same Timer.

First, restarting a Timer doesn't do anything while the Timer is still running. Check my article here on the gritty details. The only thing that'll happen is that when the Timer is finished, it will restart. But... your Timer never finishes, until all records have been processed.

Secondly, you execute the query each iteration. This is typically not what you want, as if the data changes, you'll process different records, and may even reprocess certain records, depending on the criteria in the Aggregate.

Thirdly, why do you process only 20 records, if you are going to process all records anyway (since the Timer won't stop until Var1 (very bad name btw!) has a value greater than the number of records in the query result.

Fourthly, why do you limit the number of records in the For Each, instead of in the Aggregate? Unless you adhere to a number of specific limitations, you will transfer way more data from the database server than you need, which is slow.

So what did you intend to do, functionally? Process only 20 records, then restart the Timer, then process the next 20? (Btw, Timers can conveniently run for large stretches of time, the processing must be really slow to warrent only 20 records, 1000 records is more typical.) Note that you cannot set Var1 and then expect it to keep its value between Timer runs - each Timer run is fully independent of the previous one!

What you would typically want is this:

  • Query only the limited set you want to process. Make sure that the query does not select records you already processed (e.g. by left-joining an Entity that only gets filled when processing, or by keeping track of a processing status per record, etc.);
  • After processing the records in the For Each (don't use a Start Index or Maximum Iterations, that's not needed if you design your query correctly), check whether the number of records the Aggregate returns against the Max. Records you specified - if it's less, you know everything's been processed and you can End the Timer, otherwise call the Timer's Wake action, then End the action.

That's it. The Timer will restart itself, query a fresh set of records, process them, etc. until there's nothing more to query.

Hi @Kilian Hekhuis,

At above message what is nonsense in that. will you iterate the loop for same record 20 times. I have suggested that variable var1 needs to be increased. Did you see any var1 increased at True branch after if condition? May be if var1 given correctly, then needs to be check other server actions if they are causing iterative error. So there may be other conditions need to be check.

And one more question, Are you disliking above comment? It is hampering my rank, and I don't see that was incorrect suggestion.

@Shubham Janbandhu ,

This part is nonsense :

you are suggesting that he needs to increase the var1, that is the StartIndex for the ForEach, after an if.  The only If's I can see are inside the loop, so you are saying he needs to increase var1 inside that loop.

I don't see how doing that would solve the error he's getting.  But I also don't see how hou think it would solve anything else.  He is already increasing it after the ForEach is done.

Admittedly, there is a lot wrong in the logic, also surrounding the Var1, see extensive answer of @Kilian Hekhuis .  But increasing it inside the loop isn't the solution !

Dorine

Oh sorry. My Apologies. I understood my mistake. Usually,  I don't do that kind of wrong things but I think I have took that in wrong way. Thanks for correcting me. 

i m adjusting var, when i wake up timer isnt that suposed to save the last data? i checked and none of my server actions have a for each inside. they do have agregates with sum values

Hi @João Pedro Espinheira ,

i m adjusting var, when i wake up timer isnt that suposed to save the last data? 

Most certainly not, the Var1 only exists within the scope of this single execution of the timer, when it stops, value is lost.

i checked and none of my server actions have a for each inside. they do have agregates with sum values 

On your first screenprint, I can see the full list going into the ImediateCommissionSwitch action.  So can you show us the logic in there, particularly all the statements that involve the input list ??

Are there any other of the actions that your full list goes into as input.  Those are all suspects.

Dorine

On a design note : if you are iterating a list, and then calling a lower action to process the current item of the iteration, I think it is bad form to pass in the full list, your logic will be much cleaner if you just adjust the input type if ImediateCommissionSwitch from list to single item, and only pass the current record from the iteration.  If I had to guess, I'd say this is the result of you doing an "Extract to Action" earlier, which unfortunately can produce some ugly code.

I noticed Killian already gave a good explanation but I would like to visualize it a bit more to make it more clear. 

  • I see you wake the timer again after you have finished a batch of records. Why do you do that? The current action will not end because the next action is the aggregate again. This will schedule the timer to run after the current action/timer is stopped and that will then have nothing to do since you already processed everything.
  • You have an exit based on a validation of what I presume a check if the current record is the last record? Why not let the for each handle that? That is what it is for.
  • You don't follow the best practice's for a Timer. This should include a timeout prevention capability but most important of all a Commit Transaction (or delete record) to make sure that if you rerun the timer you don't reprocess already processed data.
    Please look at the Timer masterclass course that will go into detail about this. You will also notice that the flow will be different and that the timer restart will be the last widget at one of the end nodes.
  • The starting index variable should not be needed in your case. I don't see any method to set this value so the value is 0. Make things more clear by not using variables that don't get a value set. You fellow developers will thank you later on.
  • Edit: One last thing. Here you probably update the "IsProcessed" property of the record but nowhere are you updating this change to the database. All other actions are Ifs and could possible follow the false line and therefor not be handled at all.
  • Edit 2: Why are you checking if the current record is already processed? This should be included in you aggregate so you don't even get those records at all. This will simplify your logic and you will have a smaller memory food print.


i did this because i wanted to prevent time outs, this way becuase if there are 50.000 records and it takes longer than 20min, i intend to finish the batch and not having time out. that first if, yes, it has a condition for the last record, the second one, near the for each is comparing a "fake atribute" (i added to the agregate (green color), but i removed it since i dont need it (i have the var1 (now indexCounter).

Hello João Pedro Espinheira,

I can see your timer has infinite loop, as soon as your foreach loop ends, you are again redirecting to aggregate and that will output a list which will iterate in loop again so it will never ends.

You should check some condition if want to process remaining records(In this case also there should be a flag in entity which you need to update in any server action post process.

another thing will be, you dont need to again call aggregate after WakeTimer action, that aggregate will never execute. Waketimer will simply re-execute your server action from Start node.


so you saying that i could put the wake up right before the end node?

Yes, this will schedule a new execution of the Timer. For this use case waking up the timer just before the end node is the only correct placement. Don't use it in a loop. 

Yup. A WakeTimer action must always put before End node.

for the resolution of my problem, was changing the list i was passing to an object, i was passing the list of the for each to the action instead of the current.

Still i m finding quite usefull this topic so, Vincent Koning do you suggest another aproach?

Nice to hear you have found the problem. I think that this approach is the correct one. Please mark the response that was the most helpful as "Solution".

Community GuidelinesBe kind and respectful, give credit to the original source of content, and search for duplicates before posting.