While building complex logic using the plugins, we always end up having n number of plugins for various processes. If these processes are implemented around a single entity and if one plugin is triggering another, we need to consider the depth of the plugin which decides the sequence of plugin execution.
IPluginExecutionContext context = localContext.PluginExecutionContext; if (context.Depth > 1) return;
What is plugin depth
Depth is the sequential number of the plugin/workflow which is executed in one transaction in the call stack. Depth is used solely to manage potential infinite loops that could occur in plug-in or custom workflow activities.
When a plugin/custom workflow triggers another plugin/trigger then the depth property is incremented by one. If the depth property increments to its maximum value within the configured time limit, the platform considers this behavior an infinite loop and further plug-in or Workflow execution is aborted.
Configure Depth
The maximum depth (8) and time limit (one hour) are configurable by the Microsoft Dynamics 365 administrator using the PowerShell command Set-CrmSetting. The setting is WorkflowSettings.MaxDepth.
Even though this depth is configurable, we should not change the default configuration as it affects the performance of our system. It is always advisable to develop the logic in such a way that it will avoid this infinite loop without affecting the performance of the system.
Way to avoid infinite loop – Best practices
Using the following tricks, we can avoid the infinite loop
• Develop the plugins in Pre event if possible so that it will avoid post events which is the main cause of infinite loops.
• If the plugins are developed in Post event and if the target entity needs to be updated, don’t use the target entity to update directly.
For example,
Entity entity = (Entity)context.InputParameters["Target"]; If(entity.LogicalName == “contact”) { (some logic here and if I want to update one of the custom fields of Contact record called Is VIP which is a boolean field) Entity[“new_isvip”] = true; Service.Update(entity); }
In the above example, we are directly updating the target entity which may cause infinite loop as it will try to save all the fields of target entity.
The correct way to update the field is as follows:
Entity entity = (Entity)context.InputParameters["Target"]; If(entity.LogicalName == “contact”) { (some logic here and if I want to update one of the custom fields of Contact record called Is VIP which is a boolean field) Entity objContact = new Entity(“contact”); objContact.Id = entity.Id; objContact [“new_isvip”] = true; Service.Update(objContact); }
The above logic will update only one field of the Contact record which is “Is VIP” and it will also avoid triggering of the other plugin which are registered on the Contact update.
• Whenever the target entity gets updated in any of the plugins, it is a good practice to check the plugin depth before starting the logic execution. If it is not running at certain depth, break the logic using “return”.
• While retrieving the entity fields, specify the fields you want to fetch else it will increase the execution time.
For example,
Entity objContact = Service.Retrieve(“contact”, entity.Id, new ColumnSet(true)); Instead of using true, specify the fields you want to fetch. Entity objContact = Service.Retrieve(“contact”, entity.Id, new ColumnSet(“fullname”));
• You can also use shared variables to pass the data between the plugins running in a sequence.
• If all the above fixes don’t work and if it is unavoidable to have so many plugins which exceeds the maximum depth limit (i.e. 8), the best way is to break the plugin execution using a cloud flow and then retrigger the next plugin using the same cloud flow which restarts the plugin depth from 1.
We would like to share the scenario which we had implemented using a cloud flow.
There were a bunch of records which were dynamic and were processing the record status from New to Ready. After changing the status to Ready, the plugin was encountering an issue of infinite loop. It was difficult to use any other components than plugin as the logic was too complex. Hence we decided to break the sequence of plugin execution by introducing a cloud flow. This cloud flow retriggered the next plugin by using a custom action and the depth counting restarted from one again.
Shared Variables
Shared variables are used to pass the data between two plugins. This is a collection of key\value pairs. At run time, plug-ins can add, read, or modify properties in the SharedVariables collection. This provides a method of information communication among plug-ins.
In the below example, we are passing the Guid of the Owner to whom the Ticket I assigned.
Plugin 1
context.SharedVariables.Add("TicketAssignedTo", fsOwnerID.ToString());
If you want to check if the Plugin 1 is triggered, you can add the following lines of code to see if Plugin 2 is triggered after Plugin 1.
Plugin 2
if (context.SharedVariables.Contains("TicketAssignedTo")) { Guid AssignedTo = new Guid((string)context.SharedVariables["TicketAssignedTo"]); }
In this way, you can find out the sequence of the plugins or can pass the required information from one plugin to another and can avoid infinite loops.
Summary
To avoid infinite loop in the plugins, you can use the following ways:
• Use shared variables between the plugins
• Check plugin depth and parentcontext
• If possible, trigger the plugins in pre-create/pre-update event.
• Instead of using context entity to update the values in the record, define new entity instance and update only those fields which are required to update.