strange problem about trigger linkage

Posted by Zhenzh on Tue 03 Sep 2019 05:25 AM — 14 posts, 44,887 views.

China #0
I have two functions as below:

function test1()
  AddTriggerEx("test1", "^trigger test1$", "Note('trigger1 executed') test2()",
               bit.bor(trigger_flag.Enabled,
                       trigger_flag.RegularExpression,
                       trigger_flag.Temporary,
                       trigger_flag.OneShot,
                       trigger_flag.KeepEvaluating),
               -1, 0, "", "", sendto.script, 100)
end

function test2()
  AddTriggerEx("", "^trigger test2$", "Note('trigger2 executed')",
               bit.bor(trigger_flag.Enabled,
                       trigger_flag.RegularExpression,
                       trigger_flag.Temporary,
                       trigger_flag.OneShot),
               -1, 0, "", "", sendto.script, 99)
end


What I expected is:
1. run test1(), trigger "test1" is created
2. trigger line "^trigger test1$" is coming, trigger "test1" do inline script
3. test2() is run by trigger "test1", trigger "test2" is created
4. trigger "test1" is deleted by one shot flag
5. trigger line "^trigger test2$" is coming, trigger "test2" do inline script
6. trigger "test2" is deleted by one shot flag

While the actual result is the inline script of trigger "test1" being invoked constantly (seems get dead loop), and the mushclient get hung at the end.


[2019-09-03 13:32:18] 
[2019-09-03 13:32:18] 
[2019-09-03 13:32:18] 
[2019-09-03 13:32:19] 
[2019-09-03 13:32:19] 
[2019-09-03 13:32:19] 
[2019-09-03 13:32:19] 
[2019-09-03 13:32:19] 
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
[2019-09-03 13:32:43] trigger1 executed
...
China #1
I have had some investigation and find such issue only occurs when a smaller sequence AddTriggerEx() is invoked in inline script of another bigger sequence AddTriggerEx().

Any trigger should only be active once for the same matching line, isn't it?

I'm puzzled what cause the trigger "test1" get active all the time over the same trigger line?
Australia Forum Administrator #2

Thank you for the very good description. :)

It seems to me that what is happening is that, by adding a lower-sequence trigger you are “pushing” up all the triggers in the list of the triggers to be evaluated.

So, for example, you start with:

test1 trigger  <-- 1: current trigger
something else <-- 2: next trigger to be evaluated

Now the line arrives with test1 in it and that adds a new trigger:

test2 trigger  <-- 1: current trigger
test1 trigger  <-- 2: next trigger to be evaluated
something else <-- 3: some other trigger

The client now moves onto trigger 2 in the list (which is now test1) and evaluates it again. This adds a new test2 trigger, so that now the test1 trigger is in position 3, and is evaluated again. And so on.

You will note from my page How MUSHclient processes text arriving from the MUD that deleting one-shot triggers is one of the last things to be done when processing an input line.

I’m not sure of a good rule to avoid this, which won’t affect existing trigger processing. Possibly a flag in the trigger processing which makes it skip any trigger which has already been processed might do it.

Amended on Tue 03 Sep 2019 07:10 AM by Nick Gammon
Australia Forum Administrator #3
As a work-around, can you instead enable and disable triggers, rather than adding and deleting them? That would avoid the issue of changing the length of the triggers list.
China #4
I'm now turn off the KeepEvaluating for the trigger test1 as a work around.

It can avoid the issue, but I have to carefully think over the use of the KeepEvaluating switch for all exiting triggers to avoid new issues being involved by turning of KeepEvaluating.
Australia Forum Administrator #5
I think playing with the trigger list, whilst triggers are being evaluated, is an inherently dangerous thing to do. It's like iterating through an array, and adding a new item to the start if some condition is met.

Assuming you don't have to do this hundreds of times, it would be safer to have a disabled trigger with the correct sequence sitting there waiting to be enabled. You could always change what the match text is before enabling it.
China #6
I have an idea for resolving such issue.

For all adding/removing trigger requests from "send to" and "script file", compare the sequence.

If the sequence of trigger to be added/removed is larger than the processing trigger, do add/remove immediately as current design.(updating order of trigger after processing trigger will not impact on the result of processed triggers)

If the sequence of trigger to be added/removed is lessequal than the processing trigger, record the add/remove action in a temporary array instead of doing immediately. Add one more step for processing these recorded adding/removing at the end of the process arriving text.(Your page "How MUSHclient processes text arriving from the MUD" mentioned step 21 is deleting one-shot triggers. Additional step 22 is need to processing adding/removing triggers in temporary array)
Australia Forum Administrator #7
This could get rather painful. What if you add a trigger with a lower sequence number (and it goes into this special array) and then you do something like enable the trigger? Since it won't be found in the normal trigger list the enable will fail. Unless trigger commands (like delete trigger, enable, get information, etc.) all take this second array of triggers into account, then these operations won't work.
China #8
How about using two trigger list, one for process order only?

At the beginning of processing arriving text, create a copy of the trigger list to be used for processing order.

Any higher sequence trigger operation may impact on the order list such as add/remove/enable/disable will update both original and copy list and the other operation updates the original list only.

So that getting info operation can always get the exact trigger info from original list while the arriving text process flow read copy list to handle the correct trigger list.
Australia Forum Administrator #9

OK, but what if a low-sequence trigger is designed to add a high-sequence trigger, so that the same line can match the second (newly added) trigger? If we make a copy of the list then the new trigger won’t be in the copy.

Probably the least-impact change would be, at the start of trigger processing, to mark all triggers as not evaluated, and then when evaluating them mark them as evaluated. When working down the list, disregard any triggers that have already been evaluated.

Even then I can imagine problems. Say a trigger inserts a whole lot of lower-sequence triggers, then where does evaluation of the next trigger start?

And your idea of making a copy doesn’t totally work, because if we copy pointers then we could get a crash if the trigger has been deleted (and thus the pointer invalidated) and if we do a deep copy (copy the whole trigger) then any changes made by earlier triggers won’t affect the copy.

I don’t think there is an easy solution, except to not add triggers during trigger evaluation, particularly lower-sequence ones.

This is starting to sound like an X-Y Problem - why do you want to do this?

China #10
The evaluation mark solution also has a problem.
Imagine such scenario:

function fail_retry()
  AddTriggerEx("", "failed message", "fail_retry()", ...)
  <delay some time>
  <do something>
end


A failure retry function, player do some action, different function will be invoked based on the result.
When the action failed, the new added trigger will be matched by the failed message so that fail_retry() will be invoked again until action success.

Here comes the problem:
As each time the retry function is invoked, a new trigger will be added.
Since both match line and sequence created by retry function are the same, the new trigger may be added follow the lasted created trigger.
If the keep_evaluation is enabled, process flow will continue matching triggers. The new added trigger happened to be on the next position and has no evaluated mark.

Thus cause a dead loop like: retry() -> add trigger -> do action -> process added trigger -> retry() -> add trigger -> process added trigger -> retry() -> ...
China #11
Nick Gammon said:

if we do a deep copy (copy the whole trigger) then any changes made by earlier triggers won’t affect the copy.


Is it possible to manually update both list? For example, when new higher sequence trigger is inserted to the original list, do additional array insert to the copy list at the same position as I mentioned.

Zhenzh said:

Any higher sequence trigger operation may impact on the order list such as add/remove/enable/disable will update both original and copy list
Amended on Fri 20 Sep 2019 09:04 AM by Zhenzh
Australia Forum Administrator #12
What is wrong with my suggestion of enabling and disabling the trigger, rather than adding and deleting it?
China #13
enabling/disabling soluation has no problem. I'm now using it.
The only disadvantage is reading code get a little harder as operation against trigger name can not fully indicate the use of a trigger compare to what an addtrigger funtion do.