Recursion Detection Modes and Limits

This section discusses the use of recursion with triggers.

The following topics are discussed:

Types of Recursion

ENOVIA products allow program objects to execute themselves, a process called direct or explicit recursion.

Explicit recursion is a powerful mechanism when used properly. Event triggers allow indirect or implicit recursion that occurs when a trigger program generates the same event with which it is associated. This form of recursion may or may not be useful. Implicit recursion might be difficult to determine because the event that triggers the running program may be part of a program that was triggered by a totally different event. The amount of indirection could be many levels deep, and difficult to figure out.

Trigger Recursion Scenarios

You can configure how your system detects trigger recursion by setting the MX_TRIGGER_RECURSION_DETECTION environment variable.

There are 3 scenarios:


  1. By default, this variable is set to name, and all recursion encountered through event triggers is treated as a no-op (that is, if an attempt is made to execute a running trigger program, the attempt fails but is not treated as an error). The system stores the name of the executing program, and uses a name matching test to determine if recursion is being attempted. If a case of recursion is encountered, the system simply skips execution of that trigger program but otherwise continues to process in non-error mode.
  2. Since programs can be configured with input arguments, it is reasonable to design programs for reuse, and therefore, it may be desirable to allow running programs to be executed again. For example, you may have a program that promotes an object that it is passed. When this program is executed, it could fire a promote trigger that calls the same program, but passes in a different object. To allow this, you could use a signature matching test by setting the variable to signature. When signature matching is enabled, the system stores the following five properties of a trigger program when it is executed:
    • Program Name
    • Input Arguments
    • Trigger Event
    • Trigger Type
    • Target object ID

    The target object ID is a unique internal number that identifies the business object or relationship instance that is the target of the given event. When the event is create on a business object or on a relationship instance, a unique number is generated but this number does not identify an object (since one does not yet exist). This means that there is a much higher potential for unwanted recursion in these two cases so special attention should be paid when writing trigger programs.

    If a program attempts to execute where all five of these properties match another program on the running stack, recursion is detected and the routine is skipped.

  3. When full recursion is desired, the detection of recursion can be turned off by setting the variable to none. In this case, it is important to avoid infinite recursion using the environment variable MX_TRIGGER_RECURSION_LIMIT.

Limiting the Trigger Stack Depth

The MX_TRIGGER_RECURSION_LIMIT variable sets the number of trigger programs that may be placed on the runtime stack to the value set, helping to avoid server crashes due to the host system running out of stack space. The default value is 30 (which may be too high, particularly when using the Web version of ENOVIA Matrix Navigator through a Windows server).

When the limit is reached, a warning is added to the trigger trace file such as:

"WARNING: Override Trigger for <event>: <program_name> has 
exceeded recursion limit!"
"WARNING: Validate Query for <event>: <program_name> has 
exceeded recursion limit!"

The limit is used in conjunction with MX_TRIGGER_RECURSION_DETECTION, no matter what type of recursion detection is enabled.

Override Example

The following example shows what happens when trigger programs issue commands that cause events to occur that also have triggers.

This example involves the business object "Document Checker 0" and a bit of a squabble between three users, Tom, Dick, and Harry, who fight over ownership. The default recursion test (name matching) is in use.

Assume that these triggers are in place in the Document type on the changeowner event:

# Changeowner Event Check Program on Document Type:
mql
code "output 'DocumentChangeOwnerCheck: OBJECT = ${OBJECT} TYPE 
= ${TYPE}';
output ' NAME = ${NAME} REVISION = ${REVISION} OWNER = 
${OWNER}';
output ' ACCESSFLAG = ${ACCESSFLAG} USER = ${USER}';
output ' CHECKACCESSFLAG = ${CHECKACCESSFLAG}'; 
output ' TIMESTAMP = ${TIMESTAMP}';
output ' NEWOWNER = ${NEWOWNER}';
tcl;
set status 0
set status $env(RETURN_CHECK_CODE)
exit $status
";
#Changeowner event Override Program on Document #Type: 
mql
code "output 'DocumentChangeOwnerOverride: OBJECT = ${OBJECT} 
TYPE =${TYPE}';
output ' NAME = ${NAME} REVISION = ${REVISION} OWNER = 
${OWNER}';
output ' ACCESSFLAG = ${ACCESSFLAG} USER = ${USER}';
output ' CHECKACCESSFLAG = ${CHECKACCESSFLAG}'; 
output ' TIMESTAMP = ${TIMESTAMP}';
output ' NEWOWNER = ${NEWOWNER}';
modify bus ${TYPE} ${NAME} ${REVISION} owner Dick;
tcl;
set status 0
set status $env(RETURN_OVERRIDE_CODE)
exit $status
";

Environment variables handle the exit code from the check and override trigger programs. Their settings are: RETURN_CHECK_CODE=0 and RETURN_OVERRIDE_CODE=1 (which means the check program will not block and the override program will replace the normal event handling). Harry is the current owner of the Document object and has decided to change ownership to Tom. Here is the output:

MQL<7>modify bus Document Checker 0 owner Tom;
DocumentChangeOwnerCheck: OBJECT = Document Checker 0 TYPE = 
Document
    NAME = Checker REVISION = 0 OWNER = Harry
ACCESSFLAG = True USER = creator
    CHECKACCESSFLAG = True
    TIMESTAMP = Thu Jan 6, 2010 11:28:03 AM 
    NEWOWNER = Tom
DocumentChangeOwnerOverride: OBJECT = Document Checker 0 TYPE = 
Document
    NAME = Checker REVISION = 0 OWNER = Harry
ACCESSFLAG = True USER = creator
    CHECKACCESSFLAG = True
    TIMESTAMP = Thu Jan 6, 2011 11:28:04 AM 
    NEWOWNER = Tom
DocumentChangeOwnerCheck: OBJECT = Document Checker 0 TYPE = 
Document
    NAME = Checker REVISION = 0 OWNER = Harry
ACCESSFLAG = True USER = creator
    CHECKACCESSFLAG = True
    TIMESTAMP = Thu Jan 6, 2011 11:28:05 AM 
    NEWOWNER = Dick

The changeowner override program issues its own modify businessobject Document EventTriggers 0 owner Dick command. When Harry attempts to change the owner, this command causes a nested changeowner event to occur, which causes a second set of triggers to fire. The second override program is a no-op because the first override program is still running. Since this second override program is a no-op, the normal event activity (in this case, the first override program) is guaranteed to take place. This is an important point to keep in mind. At this point, the owner has been changed to Dick.

After the nested changeowner event transaction commits and the associated action program, if any, runs, control returns to the original changeowner event. If the exit code from the override program is non-zero, the normal event activity is skipped and the owner name continues to be Dick. If the exit code is zero, the normal activity takes place (overriding the owner change that took place in the nested event transaction). Now the owner name is changed to Tom. In either case, the original event transaction commits and the action program (if any) executes.

Look at the case of the changeowner action program issuing its own modify businessobject Document Checker 0 owner Harry command. Assume that RETURN_OVERRIDE_CODE=0 so that the event is not replaced.

#Changeowner event Action Program on Document Type
mql
code "output 'DocumentChangeOwnerAction: OBJECT = ${OBJECT} TYPE 
=${TYPE}';
output ' NAME = ${NAME} REVISION = ${REVISION} OWNER = 
${OWNER}';
output ' USER = ${USER}';
output ' TIMESTAMP = ${TIMESTAMP}';
output ' NEWOWNER = ${NEWOWNER}';
modify bus ${TYPE} ${NAME} ${REVISION} owner Harry;
quit;";

In this case, the command causes a new changeowner event to occur after the initial event transaction has been committed. When it is time for the action program to be run, the owner has changed to Tom. As before, recursion occurs and the second action program is a no-op since the first action program is still running. Note that the second owner change to Harry, which is caused by the action program, will override the owner change to Tom that took place as the normal event transaction before the action program was executed.

Look what happens when both the changeowner override and action programs generate changeowner events as defined earlier. Assume that RETURN_OVERRIDE_CODE=1 again, so that the event is replaced. In this case, we see the combined affects of both programs. The question is who now owns the Document object? The only way to find out is to trace through the changes in a sequential fashion.

    1.0) Original ChangeOwner event transaction is started. Check fires.
2.0) Override changes owner to Dick.
    2.1) Nested ChangeOwner transaction is started. Check fires.
    2.2) Override is a no-op, since the same Override Program (step 2.0) is still 
running. Transaction is committed.
    2.3) Nested event's Action changes owner to Harry.
       2.3.1) Nested ChangeOwner transaction is started. Check fires.
       2.3.2) Override is a no-op, since the same Override Program (step 2.0) is still 
running. Transaction is committed.
    2.4) Nested Action is a no-op, since the Action (step 2.3) is still running. 
Transaction is committed.
3.0) Original ChangeOwner event Action changes owner to Harry. (redundant).

With the original event transaction now committed, it seems as if the action program changes will prevail.

    3.1) ChangeOwner transaction is started. Check fires.
    3.2) Override changes the owner to Dick.
       3.2.1) Nested Changeowner transaction is started. Check fires.
       3.2.2) Override is a no-op since the Override program (step 3.2) is still 
running. Transaction is committed.
       3.3.3) Nested Action is a no-op since the Action program (step 3.0) is still 
running. Transaction is committed.
    3.3) Action is a no-op since the Action program (step 3.0) is still running. 
Transaction is committed.

The proper conclusion is that an override program that replaces normal event activity will always win out. This example is simplistic because the check program was not involved, and the override and action programs issued single commands that generated the same event. Obviously, trigger programs issue many commands and the events generated by these commands trigger other programs, and so on. Because of this complexity, consider graphing the events, showing time on one axis, and the level of nesting on the other.