I recently had a conversation with a highly experienced CRM developer, who did not understand why CRM plugins need to be stateless. He was trying to write a plugin in a stateful manner, and I had to show him the error of his ways. If such a senior developer can make a mistake like this, then I think it would be beneficial to re-explain this for everyone.
For improved performance, Microsoft Dynamics CRM caches plug-in instances. The plug-in’s Execute method should be written to be stateless because the constructor is not called for every invocation of the plug-in.
That’s an excerpt from the Dynamics CRM developer documentation. It’s a very important note, but unfortunately not all CRM developers read that documentation, and even fewer actually understand what it means. This post may get a bit long, so if you want to skip all the longwinded explanation here’s the TLDR: “Never use global variables in CRM plugins”. And for anyone interested in the long explanation, please read on.
Let’s break down what the statement is saying:”For improved performance, Microsoft Dynamics CRM caches plug-in instances”
Caching improves performance – I think everyone can agree on that. And CRM is caching our custom plugins, thereby improving their performance. Sounds good, but let’s dig a bit deeper. Normally when we talk about caching, we are caching data. But we’re not talking about data here, we’re talking about code. So how exactly does a plugin get cached? And what does that mean to us? I’ll demonstrate this with some rough code:
1 2 3 4 5 6 7 8 9 10 11 |
Private IPlugin GetCachedPlugin(Guid pluginId) { // assume that cachedPlugins is a dictionary object in the greater scope // something like this: Dictionary<Guid, IPlugin> If (!cachedPlugins.Contains(pluginId)) { cachedPlugins.Add(pluginId, new YourCustomPlugin()); } Return cachedPlugins[pluginId]; } |
That above code is a rough approximation of what CRM is doing before it executes your plugin code. The important thing that you need to take away is that it only executes your constructor once. After it has been initialized, it will continue to re-use the same instance of your plugin class. That is how a plugin gets cached. CRM stores the IPlugin object in memory, so that it never needs to re-initialize it after the first execution. It’s actually pretty neat, and you can see how they can save some processing overhead by doing that. But it comes with some consequences, and it’s up to us as plugin developers to handle those consequences.
The next statement is telling us what we need to do with that information: “The plug-in’s Execute method should be written to be stateless”
The problem of course with this statement is that many developers don’t truly know what it means to be ‘stateless’. So I’ll take a moment to explain that concept. If a method has state, or is ‘stateful’, that means that it will remember some pieces of data between executions, and use that data in subsequent executions. A ‘stateless’ method does not remember any details from previous executions, and the only data it uses is the data directly passed into it.
1 2 3 4 5 6 7 |
Int num = 0; Public int getNextNumber() { Int i = num + 1; return i; } int nextNum = getNextNumber(); |
1 2 3 4 5 6 |
Int num = 0; Public int getNextNumber(int i) { Return i + 1; } int nextNum = getNextNumber(num); |
These two methods will return exactly the same value. But the first method is ‘stateful’ because it is dependent on the outside state, specifically the ‘num’ variable. While the second method is ‘stateless’ because it is not dependent on any external or pre-existing data. It gets its state (or context) solely from its input parameter(s).
Let’s translate this into a CRM plugin. What would it look like when it is stateless or stateful
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Public class MyPlugin : IPlugin { IPluginExecutionContext PluginExecutionContext; public void Execute(IServiceProvider serviceProvider) { this.PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); SomeMethod(); } Private void SomeMethod() { // Do some work using PluginExecutionContext } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Public class MyPlugin : IPlugin { public void Execute(IServiceProvider serviceProvider) { IPluginExecutionContext PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); SomeMethod(PluginExecutionContext ); } Private void SomeMethod(IPluginExecutionContext PluginExecutionContext) { // Do some work using PluginExecutionContext } } |
See the difference? That’s all you need to write plugins properly. Now that we know how and why we need to write plugins properly, let’s not stop. I want to know how bad things can get if we do it the wrong way, which is where it gets interesting. In 99% of all cases, there will most likely not be any problems.
You may get rare reports from users of “strangeness” when they do something that triggers your plugin. And in 100% of cases when you attempt to reproduce that problem while debugging, you will NOT be able to ever reproduce the problem. And any attempt you make to do some kind of ‘hacky’ fix will not work. At some point you will probably give up, and leave it unfixed, because it’s such a rare issue anyway, that it doesn’t pose much of a problem.
Here’s what you’ve been missing: The reason for all this “strangeness” that users will see will only happen when two operations occur at the same time, and both trigger the same plugin. Due to the way CRM executes the plugins, if they are written as “Stateful”, and they both execute at the same time, then that could cause the context of the second plugin to overwrite the context of the first plugin at some point during its execution. Re-read that last sentence, and let that sink in for a moment. It’s very difficult to predict what exactly will be the behavior when that happens. The behavior would be highly dependent on what type of plugin it is, and which operation step it is registered on, and of course it will depend on what you are doing with the data you get from the context.
Just for the fun of it, let’s write a plugin the wrong way, and try to force something to go seriously wrong, and see what happens.
Here’s our “Bad Plugin”, designed to run pre-create of a Contact record:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class myBadPlugin : IPlugin { IPluginExecutionContext PluginExecutionContext; public void Execute(IServiceProvider serviceProvider) { this.PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); var targetContact = (Entity)PluginExecutionContext.InputParameters[“Target”]; var originalemail = targetContact[“emailaddress1”].ToString(); SomeMethod(targetContact); } public void SomeMethod(Entity originalemail) { Random r = new Random(); Thread.Sleep(r.Next(1000)); var targetContact = (Entity)PluginExecutionContext.InputParameters[“Target”]; targetContact[“emailaddress2”] = originalemail; } } |
Basically this plugin reads the execution context and saves it to a global variable, then reads the target record from that context. Then, after a random delay, it reads the execution context again. After that, it takes the emailaddress1 from the first context, and writes that into the emailaddress2 of the second context. If this code was written statelessly, then emailaddress1 should always equal emailaddress2 in the created records. But since the context is being stored global, it can be overwritten by the context from other executions. And we end up with some Contacts where emailaddress1 != emailaddress2.
It took me awhile to get a plugin that forced the problem to happen. Turns out the key to guaranteeing the problem is the random delay. Without that, the plugin runs too fast for the context switch to occur. And making it random just makes sure that the executions are nice and shuffled.
Now we need a small console app to test it with. This went through a few revisions and experimentation, but this is what I came up with:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
static void Main(string[] args) { Parallel.For(1, 100, (n) => { CreateContact(n.ToString()); }); Console.WriteLine(); Console.WriteLine(“Done.”); Console.ReadKey(); } static void CreateContact(string num) { Task.Run(() => { Entity contact = new Entity(“contact”); contact[“firstname”] = “contact “ + num; contact[“emailaddress1”] = “contact” + num + “@email.com”; contact[“emailaddress2”] = “contact” + num + “@email.com”; using (CrmOrganizationServiceContext context = new CrmOrganizationServiceContext(connection)) { contact.Id = context.Create(contact); Console.WriteLine($“Created #{num}”); var createdContact = context.Retrieve(“contact”, contact.Id, new Microsoft.Xrm.Sdk.Query.ColumnSet(true)); if (contact.GetAttributeValue(“emailaddress1”) != createdContact.GetAttributeValue(“emailaddress2”)) { Console.WriteLine($“Context switch detected! Expected: {contact[“emailaddress1“]} Actual: {createdContact[“emailaddress2“]}”); } } }); } |
This app essentially tries to create 100 Contacts at the same time. Each one gets a unique email address. And after it creates each one, it checks to see if the two emails match. And if they don’t, then we know the context switched inside the plugin.
Here’s a snippet of the output:
Created #5
Created #3
Created #2
Created #4
Created #88
Created #1
Context switch detected! Expected: [email protected] Actual: [email protected]
Created #6
Context switch detected! Expected: [email protected] Actual: [email protected]
Context switch detected! Expected: [email protected] Actual: [email protected]
Context switch detected! Expected: [email protected] Actual: [email protected]
Created #7
Created #8
Created #9
And sure enough this is what we see in CRM:
So there you go, it’s a real thing. Context switching can occur between plugins, if they are not written properly. The chances of this occurring in real life may be slim, but it’s not impossible. And the consequences could be pretty serious.
The good news is that it’s easy to avoid this problem. Here is the same plugin, doing the same work, but written statelessly to guarantee that this context switching can never occur:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class myBadPlugin : IPlugin { // IPluginExecutionContext PluginExecutionContext; public void Execute(IServiceProvider serviceProvider) { this.PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); var targetContact = (Entity)PluginExecutionContext.InputParameters[“Target”]; var originalemail = targetContact[“emailaddress1”].ToString(); SomeMethod(targetContact, PluginExecutionContext); } public void SomeMethod(Entity originalemail, IPluginExecutionContext PluginExecutionContext) { Random r = new Random(); Thread.Sleep(r.Next(1000)); var targetContact = (Entity)PluginExecutionContext.InputParameters[“Target”]; targetContact[“emailaddress2”] = originalemail; } } |
I’m using the context itself as my example. But any global variable is at risk of being overwritten. So, hopefully you learned something from this post. Be sure to spread the word: CRM Plugins must always be stateless! And make sure the people you tell that to actually know what “Stateless” means!