CRM Plugins – Stopping infinite loops and understanding PluginExecutionContext.Depth

In the previous post I looked at Plugin Deployment Options, which looked at the various different deployment options Database, Disk or GAC.  The article looked at the async architecture and effects of ISSResets and CRM service restarts.

This post I am going to look at a couple of the parameters in the PluginExecutionContext which I needed to help me resolve a problem.

What is PluginExecutionContext

Unit testing plugins is difficult due to a plugins dependency on

IServiceProvider serviceProvider

The serviceProvider has these variables inside

  • IPluginExecutionContext
  • ITracingService
  • IOrganizationServiceFactory
  • you use the IOrganizationServiceFactory to create IOrganizationService

Mocking the IServiceProvider and IPluginExecutionContext can be tricky because they contain a lot of variables you would need to setup.

I prefer to put the code logic into separate classes which take IOrganisationService, ITracingService, Entity being changed and other variables I need.  I focus on testing the plugin logic and not get bogged down creating variables

The IPluginExecutionContext holds contextual information passed into a plugin. This information is different for each type of plugin because it contains things like

  • Stage
  • primaryentityid
  • parentContext
  • InputParameters (e.g. which holds the Target)
  • etc

To get the plugin runtime variables you need to get the PluginExecutionContect in your plugin, I’m going to assume you are using the CRM Developer toolkit

LocalPluginContext localContext
IPluginExecutionContext context = localContext.PluginExecutionContext;

You now have access to the plugin runtime variables, here are a few

context.Depth
localContext.PluginExecutionContext.Depth;

IOrganizationService service = localContext.OrganizationService;

ITracingService tracingService = localContext.TracingService;

context.InputParameters.Contains("Target")

context.ParentContext

This is how CRM SDK describes it (remember always start with the CRM SDK)

Defines the contextual information passed to a plug-in at run-time. Contains information that describes the run-time environment that the plug-in is executing in, information related to the execution pipeline, and entity business information.

A list of all the available parameters is listed on PluginExecutionContext  properties page.

PluginExecutionContext is a fancy way of saying the plugin variables at run time.

Depth and ParentContext are two of the many properties passed into plugins in the PluginExecutionContext.  I shall start with depth first

What does Depth do?

CRM SDK Definition of depth at the ready

Int32 – The the current depth of execution in the call stack.

Hosk Definition

Depth is the number of times a plugin/custom workflow has been called in one transaction

What is depth used for?

Depth is often used to stop infinite loops where a plugin updates a value which triggers the plugin to run again

A common cause of this is bad programming practise where a plugin retrieves the target entity and at the end of the plugin updates the entity.

e.g.

A post plugin triggered on update of account name.

Plugin retrieves target entity

plugin updates name field on target entity field to name + unique number

plugin does CrmService.Update (target entity);

Plugin has updated field value which triggers plugin again
The way around this is to put code in a pre plugin. It’s possible to check the depth of the plugin

e.g.

IPluginExecutionContext context = localContext.PluginExecutionContext;
if (context.Depth > 1)
     return;

The code above is checking to see if the context.Depth is greater than one. If a plugin is triggered from a record being saved then the context.Depth = 1 (it has been run once). If the plugin called itself again the depth would then be incremented to 2.

I would put the context checking code as bad programming practise, it’s fixing the problem of stopping the infinite plugin but it’s working around the plugin being triggered in the wrong time (e.g. post instead of pre).

There are some cases when a plugin might need to update itself but for the majority of the time avoid doing because it’s confusing/complex and there is usually a better way.

Good practice when updating entity

When you want to update fields on a record, it’s good practice to create a new entity or early bound type of the record and only add the fields you want to update.

By only updating the fields you are changing you reduce triggering other plugins running needlessly.

Do not update the retrieve Target entity because it will update all fields included in the target entity.

How does depth work?

When a plugin/custom workflow triggers another plugin/trigger then the depth property is incremented by one.

The depth value has a maximum value called WorkflowSettings.MaxDepth.  The CRM SDK (Depth)states this is set to maximum depth of 8 within one hour.

You can adjust this setting by changing WorkflowSettings.MaxDepth via powershell. My advice is to not change this setting because it usually means you are trying to work round bad practice rather than resolve the bad practice.

e.g. fix the problem not react to the problem

If a plugin/workflow hits the depth of 8 or greater then it will not run and I think throw an error.

The maximum depth is not such a problem for plugins because if it reaches 8 it’s usually because there is a loop and it happens quickly.

Workflows are different, it’s easily possible for a workflow to get triggered plenty of times by one record. I have seen workflows which have been created to keep polling/checking a value hit the depth max.

What is ParentContext?

PluginExecutionContext.ParentContext will return a IPluginExecutionContext context object of the triggering plugin/custom workflow if there is one, otherwise ParentContext will be null (if it was called from the Form)
Plugins don’t just trigger themselves in a loop, often the CRM developer will trigger another plugin by doing a CRMService.update() or an assign using the CrmService(IOrganisationService)

When you trigger a plugin in the same transaction then you will increment the context.Depth by 1.

Not only does the depth get incremented, so the depth = 2 in the triggered plugin. The context.ParentContext will now contain a value.

The CRM SDK describes ParentContext

The execution context from the parent pipeline operation.

and

This property may be populated for a plug-in registered in stage 20 or 40 when a Create, Update, Delete, or RetrieveExchangeRate request is processed by the execution pipeline.

This can be useful if you want to know if your plugin was triggered by a CRM form update or triggered from another plugin.

If a plugin is triggered from another plugin then the context.ParentContext will not be null and the localContext.PluginExecutionContext.Depth will have a count of 2 (or higher if it has been called more than once).

The ParentContext can be useful because it can give you more information about the calling plugin which you might want to use/use for filtering.

Depth checking and stopping infinite loops in plugins

The common method to avoid a recurring plugin is to check if  a plugins depth > 1.  This would stop the plugin from being run if was triggered from any other plugin.  The plugin would only run if triggered from the CRM form.

This can resolve the problem of plugins firing more than once but it stops plugins being triggered from other plugins, this might not be the functionality you require.

This article below shows you how test for infinite loops using depth but it’s basically this

if (context.Depth > 1)
	return;

or you could do this

if (context.Depth > 1 || context.ParentContext != null)
	return;

CRM 2011 Plugins – Avoiding Infinite Loops

An alternative solution is to use SharedVariables, talked about on this page Pass data between plug-ins.  You can use ShareVariables to pass information between plugins registered on different stages but in the same transaction e.g a pre plugin can pass information to a post plugin.

It’s possible to set a shared Variable to a value and then check to see if the share variable exists, if it doesn’t have a value you know the plugin has not been run before, if it does have a value the plugin has ran in the transaction.

This blog gives you an example of a shared variables

These two forum discussion shows an interesting example of trying a parent record updating child records of the same type.

How to prevent infinite looping without ExecutionContext.CallerOrigin in Microsoft Dynamics CRM 2011?

13 thoughts on “CRM Plugins – Stopping infinite loops and understanding PluginExecutionContext.Depth

  1. Charles Wattson July 3, 2015 / 12:27 pm

    CRM developer often will trigger another plugin by doing a CRMService.update() and the thing needs to remember is that plugins don’t just trigger themselves in a loop.

    Like

  2. rishi July 7, 2015 / 4:12 pm

    Nice article. Also I think one important thing needs to be considered while checking depth i.e. Data Import. So if you want your Plug-In to fire on data import as well, you must increase depth property value by 1 inside Plug-In. Because when you are creating/modifying a record using data Import operation the depth will be increased by 1. It adds one for Asynchronous Process.

    Like

    • Hosk July 7, 2015 / 4:24 pm

      great comment, I didn’t know plugins triggered through data import had a depth greater than one.

      Like

  3. John Tsui March 1, 2016 / 7:16 pm

    Ben, thank you for your helpful posts, as always. I’m wondering if you can post some insights regarding SetStateDyamicEntity and SetState messages. Unsure why we need both registered.
    Thank you in advance.
    John

    Like

  4. Pingback: CRM Plugins – Infinite loops and PluginExecutionContext Depth – Danilo Capuano's CRM365 Blog
  5. Florian Krönert February 2, 2018 / 11:56 am

    Hey Hosk,

    I created a free and open source project especially for handling updates on existing records and preventing to update attributes that you don’t need to.
    It has an automatic logic for building the update objects and assists you in doing clean updates.
    You can find it here: https://github.com/DigitalFlow/Xrm-Update-Context

    Kind Regards,
    Florian

    Like

  6. Eddie February 24, 2020 / 8:18 pm

    Great post, really useful info for the issue I’m working on

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.