The Microsoft Dynamics CRM Blog
News and views from the Microsoft Dynamics CRM Team

Member, Static variable and Thread safety in Plug-in for CRM 4.0

Member, Static variable and Thread safety in Plug-in for CRM 4.0

  • Comments 3

Are you looking for best practices in plug-in development? Are you seeing data inconsistency and slow performance issues in the plug-in intermittently? Are you having problems with CRM 3.0 plug-in showing inconsistent/unknown Data after upgrade? If the answer is YES, please continue reading this article.

A CRM 4 Plug-in is an implementation of the IPlugin interface, which means you need to implement the Execute method, which will be called by CRM when the action gets triggered. Although this may appear straight forward, plug-in performance and thread safety plays a vital role based on our implementation of the plugin infrastructure.

How CRM instantiates the plug-in?

CRM instantiates the Plug-in class on the first activation of the plug-in. CRM will call plug-in's constructor. Once the object is instantiated, CRM calls the Execute method on the object. For performance reasons, CRM DOES NOT dispose of the object after it completes execution. So CRM caches the object and calls Execute on the same object instance on the next activation of the plug-in. CRM will flush its cache when any properties are changed on the pluginassembly, plugintype, sdkmessageprocessingstep, sdkmessageprocessingstepimage entities for registration. This means that you need to be very careful when defining class-level variables in the plug-in class as multiple threads can execute the code at one time.

Let me explain this with a MyCounter plugin example and show the State-full behavior of the plug-in. Register the following plugin on Account update so that you get a task created on every update of account.

   1: public class MyCounter : IPlugin
   2: {
   3:     string Config;
   4:     string SecureConfig;
   5:     int Counter;
   6:     PropertyBag InputParams_Data= null;
   7:     string NonCorruptValue;
   8:  
   9:     public MyCounter(string config, string secureConfig)
  10:     {
  11:         Config = config;
  12:         SecureConfig = secureConfig;
  13:         Counter= 0;
  14:         // Assigned in Constructor and not changed later
  15:         NonCorruptValue = "This value is not corrupted";
  16:     }
  17:     public void Execute(IPluginExecutionContext context)
  18:     {
  19:         // Increments the Counter 
  20:         // This is NOT Thread safe as 2 threads can increment the Counter variable (stored once in memory) at the same time
  21:         Counter++;
  22:         // DON'T do this
  23:         InputParams_Data= context.InputParameters;
  24:         string inputSerialized_Unknown = getSerializedInputParams();
  25:  
  26:         task t = new task();
  27:         t.subject = "Counter " + Counter.ToString();
  28:         // NonCorruptValue is thread safe as you just read but never assign except the constructor
  29:         t.description = NonCorruptValue + inputSerialized_Unknown;
  30:         //Creates task with Counter information
  31:         context.CreateCrmService(true).Create(t);
  32:     }
  33:     string getSerializedInputParams()
  34:     {
  35:         XmlSerializer serializer = new XmlSerializer(typeof(PropertyBag));
  36:         StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
  37:         // InputParams data is corrupted if 2 threads call the same IPlugin at same time
  38:         serializer.Serialize(writer, InputParams_Data);
  39:         StringBuilder sb = writer.GetStringBuilder();
  40:         return sb.ToString();    
  41:     }
  42: }

1. Register the Plug-in to trigger on Update of the Account entity

2. Create 2 accounts with last name “Account1” , “Account2”

3. Update Account1 and look at the task that is created. It will show the subject as “Counter Value = 1”

4. Update Account2 and look at the task that is created. It will show the subject as “Counter Value = 2” instead of "Counter Value = 1" for the second task

5. If you update both at the same time, two tasks may potentially be created with same counter value

6. The description of the task will always be "This value is not corrupted" even though the description will have incorrect InputParams if 2 threads fire at the same time.

This proves that CRM only maintains one object in memory and re-uses it every time CRM calls Execute method instead of creating a new instance of the object each time.

CRM 3.0 Callouts: Upgrade issue and data inconsistency (Thread safety)

In CRM 3.0, callouts were instantiated every time for every activation, which means that class-level variables defined in the callout would not be shared across multiple instances of the callout class, since CRM would allocate memory for each instance of the callout and dispose it after execution completed.

   1: public class MyCallout : CrmCalloutBase
   2: {
   3:     string EntityXml = null;
   4:     public override PreCalloutReturnValue PreUpdate(CalloutUserContext userContext, CalloutEntityContext entityContext,
   5:                                                         ref string entityXml, ref string errorMessage)
   6:     {
   7:         EntityXml = entityXml;
   8:         //Do some suff
   9:         entityXml = GetUpdatedStuff();
  10:         return PreCalloutReturnValue.Continue;
  11:     }
  12:     string GetUpdatedStuff()
  13:     {
  14:         //Do Some Stuff;
  15:         //Update EntityXml;
  16:         return EntityXml;
  17:     }
  18: }

For example, the above callout will NOT see data corruption on the EntityXml variable in CRM 3.0. As soon as the callout is upgraded to CRM 4.0, the variable data will be unpredictable. There are a couple of ways to work around this issue.

Workarounds

1. Change the CRM 3.0 callout code NOT to use class-level variables, instead pass the information via method parameters.

   1: public class MyCallout : CrmCalloutBase
   2: {
   3:     //string EntityXml = null; //REMOVE THIS
   4:     public override PreCalloutReturnValue PreUpdate(CalloutUserContext userContext, CalloutEntityContext entityContext,
   5:                                                         ref string entityXml, ref string errorMessage)
   6:     {
   7:         entityXml = GetUpdatedStuff(entityXml);  //PASS as Parameter
   8:         return PreCalloutReturnValue.Continue;
   9:     }
  10:     string GetUpdatedStuff(string updateXml) //Accept Parameters
  11:     {
  12:         //Do Some Stuff;
  13:         //Update EntityXml;
  14:         return EntityXml;
  15:     }
  16: }

2. If it is harder to make these code changes, you can implement a Proxy class that will make the callout stateless. Once you have created the Proxy class, remember to change the callout.config.xml to point to the new Proxy class.

   1: public class MyCalloutProxy : CrmCalloutBase
   2: {
   3:     public override PreCalloutReturnValue PreUpdate(CalloutUserContext userContext, 
   4:         CalloutEntityContext entityContext,  ref string entityXml, ref string errorMessage)
   5:     {
   6:         MyCallout currentOne = new MyCallout();
   7:         return currentOne.PreUpdate(userContext, entityContext, ref entityXml, ref errorMessage);
   8:     }
   9: }

The same Proxy class workaround can be used for the CRM 4.0 Plugins if you really want to use the class level variables to store instance data.

Summary

1. Do NOT use Member variables to store instance data.

2. Do NOT use the Member variables as Data Caches as we recycle them based on the load and it might be less efficient if your cache loaders are expensive operations.

3. ONLY read data from class-level during the execution of the plug-in, setting the values in the Constructor..

4. Static Variables are OK as they will be disposed only with the AppDomain.

5. Use Proxy classes if you need to read and write to class-level variables outside of constructor of the plug-in/callout.

6. DO NOT make any assumptions on the plugin instance being cached / not cached

7. Above behavior is for CRM 4

Please add your comments on your experience changing the code and suggestions for improving this in CRM 5.

Thanks,

Ajith Gande

  • PingBack from http://www.tmao.info/member-static-variable-and-thread-safety-in-plug-in-for-crm-40/

  • This information would have been nice a year ago when I started developing for CRM 4.0

  • David;

    Under the topic “Writing a Plug-in”, the CRM SDK documentation states:

    "Note: For improved performance, Microsoft Dynamics CRM caches plug-in instances. The plug-in's Execute method should be written to be stateless as the constructor is not called for every invocation of the plug-in. All per invocation state information is stored in the context. This means that you cannot use global variables in plug-ins."

    As far as upgrading callouts from CRM 3.0 to 4.0, I have filed a documentation bug to include the callout upgrade information from the blog in a future release of the SDK.

    Thank you for alerting us to this issue.

Page 1 of 1 (3 items)
Leave a Comment
  • Please add 5 and 2 and type the answer here:
  • Post