A few years back I faced a couple of elusive issues that caused me many a sleepless night. I called them the “downward spiral into madness” issues. They prompted me to write a guidelines document to assist my fellow Dynamics comrades in avoiding the same pitfalls as I.
The gates to hell
The issue
We often follow our instinct to program plugins just like any other straightforward application. Unfortunately, the plugin pipeline architecture is a little different than what we would expect.
Of notable mention is how Microsoft, in an attempt to improve performance and reduce memory usage, caches plugin instances and reuses them for all triggered executions of the same type. The IPlugin
object is instantiated once, and then the Execute
method is passed a special IServiceProvider
object for every execution.
The following snippet shows the issue:
This fact leads us to conclude that a plugin object must be treated as a static class, in fact. One might ask: what’s the harm in actually treating the object as a static class? When dealing with shared data or objects, we must take into consideration thread safety.
Objects you control fall under your responsibility; so it’s relatively easy to predict their behaviour under threaded access. However, Dynamics objects are not so easy to predict. IOrganisationService
objects are tied to a transaction in the DB. Using the object outside of said transaction causes so many unpredictable issues: data saved in the wrong record, strange transaction-related errors, … etc.
To add salt to the wound, most of those issues pop up mainly in production. This is just another consequence of the nature of threading.
Unfortunately, unless you know what you are looking for, it’s nearly impossible to pinpoint this root cause when facing so many random errors and strange behaviours; at least in a reasonable amount of time, while the gates of hell are open in the production environment.
The solution
One solution is to avoid object-level properties altogether and simply stick to local variables; however, it’s inconvenient to keep passing plugin parameter objects around.
Another solution would be to define a separate class that stores the plugin parameters as properties and refer to them anywhere in the class.
Swift tumble into insanity
The issue
Sometimes business requirements push us towards hiding internal exceptions and handling them internally, unbeknownst to the user. This is fine anywhere other than plugins.
As mentioned before, due to the ‘transaction’ nature of execution of plugins, a service is linked to a DB transaction. When an exception occurs during the Dynamics service call, the plugin pipeline rolls back the transaction. Any further access to the same transaction causes the highly infamous ‘There is no active transaction. This error is usually caused by custom plug-ins that ignore errors from service calls and continue processing.’
This code snippet shows how swallowing an exception looks like:
This error is extremely hard to trace because little information is given as to its source. On the bright side, this error is a bit more consistent than the previous issue; so it’s a little easier to trace.
The solution
Completely avoid swallowing exceptions around any IOrganisationService
access by using throw
in a catch block
. As a matter of fact, it’s bad practice in programming, in general, to swallow exceptions unless absolutely necessary.
Double-edged sword death
The issue
In the modern age of programming, we have been blessed by processors that can handle a ludicrous amount of parallel processing. It is ever so tempting to parallelise whenever possible. I am guilty of such a sinful tendency (sinful because of how fast it could turn on you).
Due to the inherent thread-unsafe and highly managed nature of plugins, it’s prohibited to use threading in plugins. Even simply ‘locking’ is prohibited as well.
I would describe this issue as ‘a death by a thousand cuts.’
The solution
While writing plugins, throw away any ‘threading’ thoughts out the window. If absolutely necessary and beneficial to use threads, move the logic to a web service and call said service from the plugin.
Resources
Guidelines document
Refer to this article for more best practices: link.
Articles
Table of Contents