Welcome to MSDN Blogs Sign in | Join | Help

AddIn FxCop rules released! [Mueez Siddiqui]

We have released FxCop rules to verify view and contract assemblies. This should make it much easier to create views and contracts that follow the version-resilience and isolation guidelines.

Head on over to our CodePlex site on http://www.codeplex.com/clraddins to try them out. As always, we would love to hear your feedback!

Add-In Performance: What can you expect as you cross an isolation boundary and how to make it better [Jesse Kaplan]

Aren’t AppDomains too Slow?

“Aren’t AppDomains too slow?” This is one of most common questions we get when we recommend AppDomain or Process isolation to someone building an extensible application. For some applications the answer is yes, which is why we make it easy to activate without isolation as well, but for many more applications you’ll find the performance hit you take by moving to AppDomain or Process isolation is well within the acceptable range for your requirements. In this article we’re going to talk about the level of performance you can expect in different scenarios and what you can do to your application, object model, and pipeline to speed things up where necessary.  This article is all about optimizing communication once everything is up and running, but the next one is going to discuss the performances of getting everything up and running using different levels of isolation.

About the Numbers

All of the numbers I’m providing here are only designed to give you a rough idea of what to expect. They were recorded in an unscientific manner (read: on my main box with lots of other applications running) and I typically saw a range of += 10% on different test runs. The application was compiled in VS in retail mode and was run without a debugger. Nothing was NGEN’d and only the contract assembly was installed into the GAC.

Baseline for Performance Expectations

Before we get into details on the guidance we should establish the baseline of what type of performance you can expect with different types of calls across an AppDomain boundary without any interesting optimizations. For simple calls that pass and return primitive types you should see between 2000 and 7000 calls per second across an AppDomain boundary. For calls that involve passing or returning a type derived from IContract across the boundary you should see about 1000 calls per second. These numbers decrease by about 30% when you switch from an AppDomain to a Process boundary.

This means that even without any interesting optimizations you should be pretty comfortable with crossing the boundary many times in response to any sort of user input and shouldn’t have to worry about the user noticing slow-down caused by the isolation boundary.

General Guidance on Performance across Isolation Boundaries

There are two basic strategies you should utilize in reducing the impact the isolation boundary has on your application:

1.       Maximize the speed of crossing your particular boundary

2.       Reduce the number of times you cross the boundary

Maximize the Speed of Crossing the Boundary

The key insight you need to understand this bullet is that when using System.AddIn to cross isolation boundaries there are three different remoting paths that you might be taking across those boundaries:

1.       Cross Process

2.       Cross-Domain

3.       Cross-Domain FastPath

If you are going across process boundaries then you don’t have much choice on your path and your main goal, when you need to increase performance at the boundary, is to minimize the number of times you need to cross it. On the other hand if you are only using AppDomain isolation then you have more options. There is a good chance that this is the first time you have heard of the “Cross-Domain FastPath”: the FastPath isn’t exposed as an API or really documented extensively.  The FastPath boils down to a highly optimized short-cut across AppDomains which by-passes as much of the remoting stack and bookkeeping as possible. The runtime uses this short-cut whenever it can quickly prove that a given call does not require any of those remoting facilities.

There are a few things you need to do to enable the FastPath for your application, but once you enable it the runtime will choose between it and the standard cross-domain path on a per transition basis depending on the type of call you are making. Therefore your goal, if you need to increase your performance crossing an AppDomain boundary, is to first enable the FastPath for your app and then to try and stay on it for as many of the performance-critical transitions as make sense.

To enable the fast path you need to make sure that the runtime shares your Contract assembly between your host and add-in AppDomains. To do this you need to mark the main method of your exe with [LoaderOptimization(LoaderOptimization.MultiDomainHost)] and to install your Contract assembly in the GAC. The “MultiDomainHost” loader optimization tells the runtime to share all assemblies that are installed in the GAC between AppDomains.  If you have a native executable and are using the native hosting APIs to start up the runtime you can still enable “MultiDomainHost”: see these docs for more info on exactly how to do it.

Once the fast path is enabled you need to consider which calls you make will actually use it. Basically you will only stray from the fast path on calls that involve either passing or returning an IContract: IContracts take you off the FastPath because they rely on MarshalByRefObject which actually does require a hefty chunk of the remoting bookkeeping. Nearly all calls involving only serializable types will stay on the fast path.  The main restriction for keeping serializable objects on the FastPath is that they are marked serializable using the [Serializable] attribute. If you instead implement ISerializable you will end up using the normal path.  Serializable structs will also stay on the fast path and thus are a good option for passing large amounts data across the boundary when by-value semantics are appropriate.

Reducing the Number of Times you Cross the Boundary

 It is important to realize that the cost of crossing an AppDomain boundary does not grow substantially as the amount of data you pass across increases. For example, assuming no other optimizations, you can pass a byte[100000] in about twice the time it takes to pass across a byte[10]; in this case an increase in data of 10,000 times only results in a 50% slowdown. If you want to look at a MB per second measurement passing across bytes in byte[10] chunks leaves you with a rate of about 0.029 MB/second and passing them in byte[10000000] chunks leaves you with a rate of about 406 MB/second.  If you take advantage of the fast-path those numbers change to 0.75 MB/sec with byte[10] and about 560 MB/sec with byte[10000000].

Now, there are some cases where passing data in byte[] form is very natural (image processing comes to mind) but in many cases you’ll want something that is much easier to consume on the other side. In these cases passing data across in the form of structs can be very useful. Serializable structs can use the fast path across AppDomains. This, combined with the fact that they can contain large amounts of data and can even be passed across efficiently in arrays, means they are very useful in passing large amounts of strongly typed data (as opposed to byte arrays) across boundaries with a minimal impact to performance. The biggest downside to structs when compared to byte arrays is that if you are unable to enable the fast path then the performance of passing large numbers of structs degrades significantly: it is still much faster than using standard IContract calls but it may not be appropriate for passing around very large amounts of data. In our sample you’ll see that we achieve 2.1 MB/S for a struct[10] and 134 MB/S for a struct[10000000] if the fast path is enabled. Falling off the fast path leaves us at 0.085 MB/S for struct[10], 0.297 MB/S for struct[100000], and a DNF (did not finish before I gave up on it) for struct[10000000].

Strings were the last method I used for passing data across the boundary and led to some of the more interesting results: with very large strings my test bed was reporting “infinity bytes per second.” Increasing the size of the string did not increase the time it took to pass it across the AppDomain boundary regardless of whether I enabled the fast path or not. Think for a second about why that may be. As it turns out, strings are special cased by the runtime and shared across all AppDomains in the process. Thus to pass one across the boundary all the runtime needs to do is perform a pointer copy, whose speed doesn’t depend on the size of the string being pointed to. This means that if you have a system where you need to pass large amounts of text around then you’ll typically not have to worry about the size of that text when considering the performance of your AppDomain isolation boundaries.

The Data

The data below was gathered using the sample. To run the tests for yourself you’ll just need to download the sample, build it, install the contract assembly into the gac (gacutil –if [contract path]), and run the app outside of a debugger. There are comments in Program.cs as to how to modify it to test the different scenarios below. In addition to the tests below there are additional methods in the sample that you can experiment wit.

You can find the sample on our codeplex site here.

Calls/Second

Operation

Fast Path

Cross Domain

Cross Process

void DoStuff(void)

166,667

5,555

3,984

void DoStuff(int)

200,000

2,857

2,857

int GetInt()

166,667

6,666

4,016

int GetInt(int)

166,667

4,166

2,617

void DoStuff(struct)

71,428

2,777

2,358

void DoStuff(IContract)

3,205

1,098

657

 

MB/Second

Data Type

Size

Fast Path

Cross Domain

Cross Process

 

10

2.171

0.085

0.061

struct[]

100,000

85.048

0.297

0.265

 (32-bit struct)

10,000,000

134.189

DNF

DNF

 

10

0.751

0.029

0.013

byte[]

100,000

298.642

180.844

88.778

 

10,000,000

563.509

406.132

75.468

 

10

1.221

0.046

0.025

string

100,000

24.414

462.825

36.479

 

10,000,000

2,441,406.25

33,216.51

46.588

 

Bringing it all Together

We’ve just gone into a lot of detail and discussed a lot of different options for increasing performance and now that that’s out of the way I think we can distil it into a simple cheat sheet:

DO: enable sharing of assemblies using the LoaderOptimization attribute.
This is the first step to enabling the fast path. Even if you do not install your contract assembly into the GAC (and thus fully enabling the fast path) you will see an decrease in the activation time of your add-ins beacause this allows the Framework assemblies to be shared between domains.

DO: install the Contract assembly in the GAC when using AppDomain isolation.
This step along with the LoaderOptimization attribute will enable the fast path for your application and will provide dramatic performance increases for many of your calls across the AppDomain boundary. If you are exclusively using Process isolation this is not important.
Note: this step means that your installation program will require admin privileges. 

Consider:  passing data across boundaries using by-value semantics (structs, byte[],strings, ect).
 
If you are passing large amounts of data between your host and add-in you should consider passing it across in one chunk using a by-value data-type rather than in pieces using a deep IContract based hierarchy. This is approach is very natural in cases where an add-in needs to process and possibly modify the data:  just pass it out to the add-in and have the add-in return the modified set. It can be more awkward if the data is dynamic and can change continuously but using a system of events to notify the data consumers of changes can help mitigate this.

DO NOT: dismiss an isolation level without first giving it a try in the scenarios you care about.
Many times you’ll find that in a real application the performance hit you take by moving to an isolation boundary isn’t as much as you expect and perfectly acceptable for your situation. In the cases where it is not, there are a few simple things you can do to make the experience better.

One of the things I hope you get from this article is that for many extensible applications an AppDomain boundary, or even process, boundary is just fine without any performance tweaking and that for those applications that need (or want) better performance there is a range of options for providing dramatic performance increases depending on your needs.

 

 

Posted by CLR Add-In Team | 7 Comments
Filed under: ,

Feb CTP of Add-In Pipeline Generator is Released on Codeplex

We've just release the first update to our Pipeline Generator to Codeplex: https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=clraddins&ReleaseId=9222

This release addresses many of the issues/suggestions the community filed with the first release. Additionally it adds support for defining structs in your contract assemblies. For some extensibility points structs will be an invaluable tool as they allow you to pass large amounts of data across the AppDomain/Process boundary much faster than you can with reference objects.

For a complete list of changes see the release notes in the linked project.

Announcement: New Tool to Auto-Generate Add-In Pipelines [Jesse Kaplan]

This has been one of the most requested features we've had since people started learning more about our model and playing with the bits and I'm very happy to announce that we have published this on CodePlex: http://www.codeplex.com/clraddins/Wiki/View.aspx?title=Pipeline%20Builder

For a particular version of an application writing the code for the views and adapters can be a lot of mechanical repetitious work and generally only gets interesting when you are writing "cross-version" adapters. So what we've done is build a tool that will automatically generate the source code for the views and adapters for a given contract assembly. This tool is really just a library that can be used from various other places and our release includes a simple command line tool as well as a VS 2008 add-in that will automatically generate the projects and set references/build paths for you appropriately.

This tool should still be considered in pre-release state but because of the nature of it that shouldn't discourage you from using it in your products. While we are still working on adding additional features (and will be taking requests/suggestions for them), as long as tool has the features you need you should feel comfortable using it. Since the code this tool generates doesn't take any dependencies on the tool, or related assemblies, new updates will not impact your app and should be easy enough to incorporate in your build process. If we post updates that change the code generated for currently available features we'll make sure that gets noted and describe the changes.

One of the nice things about releasing this as shared source rather than as part of an official release is that we'll be able to do continuous development work on this and can release updates frequently, so please give it a spin and if you have feature requests/bug reports please send them our way.

 

In addition to the PipelineBuilder tool we will be using our team CodePlex site (http://www.codeplex.com/clraddins) to release all samples from now on. Many of you have noticed that because of changes to our API between betas and CTPs of 3.5 some of our existing samples no longer compile. We've posted updated versions of all of these samples on CodePlex and have added links to the them from the original posts.


 

Posted by CLR Add-In Team | 3 Comments
Filed under: ,

Support for Windows Forms in Hosts and Add-Ins

We’ve been receiving a lot of questions recently regarding the support for Windows Forms based add-ins and hosts in a multi-AppDomain environment. Now that we’ve released .NetFX 3.5 with the support for passing WPF controls across AppDomains the lack of direct support from Windows Forms becomes more noticiable.

The good news is that it’s actually possible to use Windows Forms controls in both your hosts and add-ins and have them contribute directly to each others UI. The bad news is that Windows Forms, currently, doesn’t have any dirrect support for this feature and so you’ll have to go through WPF to do this.

In 3.5 the WPF team created the helper functions, in the System.AddIn.Pipeline.FrameworkElementAdapters class, which will convert to/from FrameworkElements and InativeHandleContract. It does this by wrapping the FrameworkElements in an HWND on one side and then parenting this HWND to a FrameworkElement on the other. This means that in actuality the FramworkElement and its children never actually get passed across the bounary.

If, in addition to the FrameworkElementAdapters class, you use the WindowsFormsHost and ElementHost classes to host WindowsForms controls in WPF elements and vice-versa then both your hosts and your add-ins can use WindowsForms controls and you can logically pass them across the AppDomain boundary without actually doing so. The downside to this approach is that this requires that you load WPF into your process. If you already have some WPF code running in your process then WPF requirment is very minimal, but it can be significant overhead in the case of a pure WindowsForms host with pure WindowsForms add-ins and you’ll have to decide for yourself whether it is worth it.

There were also a few lingering questions about whether the above technique was even supported by the WindowsForms teams but there is good news there. We were recently able to clarify this question and the answer is that it is supported because it manages to avoid actually passing across and WindowsForms controls across the AppDomain boundaries: http://msdn2.microsoft.com/en-us/library/8bxxy49h.aspx

 

 

Posted by CLR Add-In Team | 17 Comments
Filed under:

Video: Jack Gudenkauf on the BCL and Add-ins [Jack Gudenkauf]

I was recently interviewed by James Vastbinder, an ISV Platform Strategy Advisor for Microsoft.

We discussed the needs of ISV’s and what’s new in the pending Orcas release (i.e., CLR v3.5 and Visual Studio 2008 v9.0).

And for those interested in hearing more on Application extensibility and the Add-in (Add in) model, including some newly released features, like WPF support, check out the following link.

 

Note the disclaimer on WinForms and WPF utilizing Crossbow over the CLR Add-In model.

Here is a bit more on this subject:

 

The use of "Crossbow" for WPF and WinForms interoperability has been a frequently asked question.

In Orcas, we haven't thoroughly tested Crossbow scenarios utilizing the WPF Add-In offering.

That said, WinForms applications with multiple AppDomains are supported as long as no WinForms controls are passed across the boundary.  In the WPF Add-in implementation we do not pass Add-in controls across domains, therefore, the use of "Crossbow" *should* work.  Your mileage may vary :-)

AppDomain Isolated WPF Add-Ins [Jesse Kaplan]

Earlier Jack announced an exciting new feature in our beta2 release in the ability host WPF controls across AppDomain boundaries. This will allow add-ins to generate their own UI and to pass it back for display within the hosts window. Ever since the add-in model was concieved this has been near the top of our list for feature requests and I want to thank the WPF team for scrambling to get this work done in time for the 3.5 release.

With that in mind I'd like to finally update our calculator samples to be just a bit more interesting. The attached sample requires Orcas Beta 2 and demonstrates a fairly typical add-in scenario in which an AppDomain isolated add-in generates some UI at the request of the host and the host displays directly as part of the application. If you take a look at the screenshot below the lower-right hand corner of the application is the piece that the add-in is generating. Everything else is generated by the host either on its own or using data from its various add-ins. In this sample only one section of the host actually displays the add-ins content but there is nothing limiting that in the product: a host can display multiple FrameworkElements from one or more add-ins anywhere it wishes. 

Calculator Screen Shot

When you download the application you should play around with the "Graphing Calculator" add-in. It really shows how smooth the integration between the AppDomains are in that the add-in doesn't need to mearly display a static image but can accept rich interaction directly from the user even when hosted cross domain.

We'll go into more depth later about this technology, some of the scenarios it enables, and some of the gotcha's you might run into, but for now I'd like to just get the sample into your hands and give you a brief taste of how easy it is to integrate this into your add-in application by comparing the differences between the pipelines of the old calculator and the new graphical add-ins.

First let's take a look at the views:

Numerical Calculator:

    [AddInBase]
    public abstract class Calculator
    {
        public abstract String Name
        {
            get;
        }
        public abstract IList<Operation> Operations
        {
            get;
        }
       public abstract double Operate(Operation op, double[] operands);
    } 

Graphic Calculator:

    [AddInBase]
    public abstract class VisualCalculator
    {
        public abstract String Name
        {
            get;
        }
        public abstract IList<Operation> Operations
        {
            get;
        }
        public abstract UIElement Operate(Operation op, double[] operands);
    }

Only one line has changed, instead of returning a double the view simply returns the basic WPF control UIElement. The changes to the host view are just as simple.

Now let's look at the adapters as this is where the real magic happens. This time we'll just compare the pieces of code that have changed.

 On the add-in side:

        public double Operate(Calculator.Contracts.IOperationContract op, double[] operands)
        {
           return _view.Operate(OperationViewToContractAddInAdapter.ContractToViewAdapter(op), operands);
        }

        public INativeHandleContract Operate(Calculator.Contracts.IOperationContract op, double[] operands)
        {
            return VisualAdapters.ViewToContractAdapter(_view.Operate(OperationViewToContractAddInAdapter.ContractToViewAdapter(op), operands));
        }

The only changes here is that instead of passing back the result directly across the boundary as a double we now pass it back across as an INativeHandleContract and use the new class VisualAdapters to convert from the UIElement the add-in passes back into the INativeHandleContract that gets passed across the boundary. System.AddIn.Pipeline.VisualAdapters is a new class in the new System.Windows.Presentation assembly that contains the core functionality that enables the cross-appdomain hosting; you'll see it's use again in the host side adapters to convert from the INativeHandleContract back into a WPF control.

        public override double Operate(HostView.Operation op, double[] operands)
        {
            return _contract.Operate(OperationHostAdapters.ViewToContractAdapter(op), operands);
        } 

        public override UIElement Operate(HostView.Operation op, double[] operands)
        {
            return VisualAdapters.ContractToViewAdapter(_contract.Operate(OperationHostAdapters.ViewToContractAdapter(op), operands));
        } 

Again, the only changes required were to change the return value to UIElement and to use our new helper class to convert from the contract back into a a new UIElement. This method actually returns an object of type FrameworkElement that lives in the hosts AppDomain and can be manipulated and plugged into the visual tree as any local FrameworkElement can.

That should be enough to get everyone started, as I said we'll be following up with more info and samples in the weeks to come. If you have any questions about this new capability please just let us know.

 

Note: The attached sample was built for a pre-RTM version of .NetFX 3.5 and will not work on the RTM build. For an updated sample please see our codeplex site here: https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=clraddins&ReleaseId=9454

 

Posted by CLR Add-In Team | 35 Comments
Filed under:

Attachment(s): AppDomain Isolated WPF Demo.zip

By Popular Demand [Jack Gudenkauf]

We just released VS 2008 and .Net 3.5 Beta 2 (code name "Orcas").

You may download the release here.

 

What’s new?

 

1.       UI extensibility

By far the most requested feature has been UI extensibility built upon the Add-In model.

We are very happy to announce that we have delivered a solution!

We have added a Native Handle Contract (in System.AddIn.Contract) upon which, the WPF and CLR Add-In teams have built an implementation for enabling an application to Host WPF Add-In’s!

We will be posting more on the subject including a sample(s) showing isolated (i.e., Cross AppDomain) WPF Add-In’s.

Special Thanks to the WPF team for making this happen!

 

2.       Direct Connect Optimization

We have provided an optimization to Add-In activation.

For Hosts and Add-in’s having the same view of each other (e.g., in V1) that forgo unloadability and share the same security context (i.e., run in the same AppDomain), you can choose to optimize the Add-In loading behavior (AddInToken.EnableDirectConnect) by not instantiating the Adapters.

 

3.       Custom Qualification

We now support the ability to add a custom attribute (i.e., QualificationData) to your Add-In and/or Pipeline segments.

There are a number of clever things you could do with this ability.  You could provide your own custom qualification logic for things such as Add-in’s that use this Attribute to inform you they qualify themselves as only running in the default AppDomain, etc…

The attribute data is available via the AddInToken.

 

4.       Find Add-In

We have added a new method (AddInStore.FindAddIn()) that enables you to find an Add-In given a fully qualified path and type.

Among other uses, this feature allows you to forgo the demand for an Add-In Attribute used for Discovery.

This feature also supports scenarios where you may not know the Add-In location until Run Time (e.g., Add-In’s deployed via Click-Once).

 

5.       Passing and Re-Adapting Add-In’s (ContractAdapter)

One of the many benefits of the Adapter model is the ability to Re-Adapt.  Sharing Add-In’s without the demand  to Discover and instantiate a new instance is a powerful capability.

We provide facilities to pass the Add-in around and let each consumer of it choose its own view and let the system find the appropriate adapter between the activated add-in and the requested view.

 

6.       Add-in Activation Security enum

We have added a new enum value for activating the Add-in using the Hosts Security level (i.e., AddInSecurityLevel.Host).

 

7.       View Hierarchies

We now support a hierarchy of view types and the ability to attribute them for discovery.

 

We will be posting more on these features in the coming months.  Stay tuned.
Posted by CLR Add-In Team | 14 Comments
Filed under: ,

Add-In Lifetime Management [Pete Sheill, Jim Miller]

A good question to ask when evaluating a design is ‘how is the lifetime of this object or component managed?’  That is,

·         How do you know it isn’t needed anymore?

·         Is it possible to release the resources while the object is still in use?

·         Is it possible to “leak” the resources by not releasing them at all?

·         How soon after the object isn’t needed are the resources released?

 

How does this apply to the managed add-in model?  If you are content to keep the add-in loaded until the process exits (and the system cleans up the entire process) you can stop reading right away, as this topic isn’t of particular interest to you.

 

One of the greatest benefits of managed code, of course, is automatic lifetime management (garbage collection).  Within an application domain (“appdomain”) objects no longer referenced by “live” objects are automatically reclaimed by the garbage collector and there’s a standard mechanism (finalization and IDisposable) to release any unmanaged resources those objects are using. 

 

The resources to be reclaimed include the memory for the instance members, any operating system handles, database connections, etc.  Another resource, often overlooked in managed code, is the memory taken by the code itself, including the JIT-compiled code.  Releasing this last resource is not possible if the code is loaded in the same appdomain as the host.  We simply don’t have a way to unload an assembly without unloading the appdomain that hosts it.

 

In general, there are three ways to manage the lifetime of objects:

1.      Garbage collection.  This works within a single appdomain as described above.  Unfortunately, there’s no good general purpose solution for use in distributed systems (although it’s been an active research area for years).

2.      Reference counting.  This is probably familiar to you from COM and other systems.  When you create an object you set a variable to 1, whenever you add a new reference to it you increment the variable (“addref”), when you release a reference you decrement the variable (“release”).  When the variable goes to 0 there are no more references and you can release the object and any data it holds.  This works as long as there are no cycles between objects, but leads to memory leaks when a cycle occurs.

3.      Sponsorship.  This is the mechanism used for both DCOM and .NET Remoting.  The idea is that when you create a remote object you declare something as a “sponsor” for the object and periodically ask the sponsor if the object is still needed.  If the sponsor says it isn’t needed, or if you can’t reach the sponsor after some number of tries, you can release the object.  One common way of implementing sponsorship is what’s called the “lease” model where the sponsor provides a lease that states how long the object should be considered alive and the sponsor isn’t contacted until the lease expires.

 

The managed add-in framework uses a combination of all three of these mechanisms.  Within a single appdomain it uses garbage collection.  But it gets a lot more interesting when the add-in is being run in a separate appdomain in the same host process, or in a separate process.  Even though the HostSideAdapter has a reference to the AddInSideAdapter, because the AddInSideAdapter is “remote” it is subject to reclaiming under normal garbage collection.

 

In designing the lifetime management system for managed add-ins, our goal was to make simple cases simple but still allow more complicated systems to be built.  The framework itself takes responsibility for the sponsorship model, but exposes a reference counting model to control the behavior of the sponsor.  In simple cases the reference count is maintained automatically by the framework, too, using the garbage collector and finalization.  The rest of this article talks about the details of this implementation.

 

In the case where the add-in is isolated from the host, either in a different appdomain or a different process, the garbage collector still reclaims objects when they are no longer referenced.  If you release the reference to the HostSideAdapter the garbage collector will release its memory.  But what about the corresponding AddInSideAdapter and the add-in itself?  And what about the case where you allowed the add-in framework to create an appdomain for running the add-in – when is the appdomain released?

 

The standard solution to the issue of keeping the AddInSideAdapter alive in .Net Remoting is to use leases and sponsorship to keep the remote object alive.  While we do use sponsorship underneath, we decided not to expose this to the add-in or pipeline developers.  Instead, we use a variant of the “ref counting” technique to keep remote objects alive by providing the methods AcquireLifetimeToken() and RevokeLifetimeToken().  When the HostSideAdapter is constructed, it immediately makes a call to AcquireLifetimeToken() on the AddInSideAdapter to tell the AddInSideAdapter that it has a reference to it.  When the HostSideAdapter is disposed of or reclaimed, it will make a call to RevokeLifetimeToken() on the AddInSideAdapter to let it know that it is done referring to it.  Usually, this will lead to the AddInSideAdapter and the add-in being reclaimed.

 

Let’s consider a simple scenario first – an add-in in its own appdomain in the host process.  Say you have activated an add-in using the overload of Activate() that takes an AddInSecurityLevel.  This method creates a new appdomain to host the add-in.  As described in the post on activation, the HostSideAdapter calls AcquireLifetimeToken() on the AddInSideAdapter when it is created.  This increments the “ref count” on the AddInSideAdapter to one.  As a convenience, this is usually done by constructing and holding a ContractHandle, passing in the AddInSideAdapter to the constructor -- the constructor of the ContractHandle actually calls AcquireLifetimeToken.  We’ll assume that this is the case for this example.  As long as this ref count is above zero, the AddInSideAdapter will stay alive in memory.  Since it has a reference to the add-in, the add-in will therefore also stay alive in memory.

 

The host uses the add-in through the HostViewOfAddIn (previously called HostAddInView), and then decides that it no longer needs it.  The variable that used to reference it is set to null.  At some later point, when the garbage collector runs, it notices that the HostViewOfAddIn is no longer needed, and therefore neither is the ContractHandle.  It calls the finalize method of the ContractHandle, which then calls RevokeLifetimeToken() on the AddInSideAdapter.  The AddInSideAdapter decrements the ref count to zero.  This triggers cleanup – the add-in is no longer needed.  If the AddInSideAdapter has been implemented by deriving from the framework class called ContractBase (highly recommended), then the following happens:

1)      A flag is set to end the sponsorship (in remoting) that is keeping the object alive.

2)      OnFinalRevoke() is called, allowing the derived class to clean up any native resources it holds.  We chose to name the method this way instead of calling it Dispose() because Dispose() is a public method typically called by an external object, while this method is a protected method called by the object itself.

3)      We unload the appdomain with a call to AppDomain.Unload().  Before unloading completes, the finalizers of all the objects in the appdomain will run.

 

So in this example, the cleanup happened without any explicit instruction to do so by the host or the add-in, similar to normal single-appdomain garbage collection. 

 

The nice part about this approach is that the host code is isolated from the lifetime management of the add-in.  It just uses the HostViewOfAddIn as it would any other managed object, without doing any reference counting itself – because that code is only done in the adapter.  When there are no more live references to the HostViewOfAddIn, all the memory and resources of the adapters and add-in will automatically be reclaimed.

 

There are times where steps #1 and #2 will happen, but not #3.  That is, the add-in is ready for cleanup, but the appdomain is not.  If the appdomain was not created during activation of this add-in, then the AppDoamin will not be unloaded here.  The appdomain may be the default host appdomain, it may have been explicitly created by the host, or it may have been created for another add-in.  In any case, there are other objects in the appdomain that are likely still in use, so we can’t unload it.  Stated another way, if we didn’t create the appdomain, then we won’t unload it.

 

Assuming that the appdomain was created specifically for the add-in, there is another situation that will postpone the cleanup.  Reference counting relies on an object "owning" its own memory and other resources.  That is, the object itself is responsible for deallocating the resources at the right time, once it is sure that no other object needs it. The appdomain created for the add-in will typically have the same lifetime as the add-in.  While we could have kept a separate ref count for the appdomain, having the appdomain own itself, we decided to simplify things and have the primary add-in own the appdomain.  Therefore the two always have the exact same lifetime.  Only when the ref count of the add-in goes to zero does the appdomain get unloaded, causing any objects in that appdomain to be finalized and unavailable.

 

One question that might arise is what happens if the add-in creates an object and passes a reference to it to the host, and then goes out of scope before the host uses the object.  A separate pipeline is created for the object as it is passed to the host, similar to the pipeline between the host and the add-in.  If the add-in's reference count went to zero, the add-in and the appdomain would be reclaimed.  But then if the host still held a reference to the object and tried to call one of its methods, it would fail with an AppDomainUnloadedException. 

 

To address this, we don't allow the add-in's reference count to go to zero in this situation until the host no longer references the object.  When the object is passed to the host and a pipeline is created for it, we look up the owner of the appdomain and increment its ref count.  Then when the object is no longer needed and the pipeline is being finalized we decrement its ref count.  Only at this point can the ref count of the primary add-in go to zero, leading to the cleanup of the appdomain.

 

If the host calls Activate to create a new appdomain for a new add-in, then uses that same appdomain in subsequent calls to activate secondary add-ins, a similar process happens.  The secondary add-ins increment the ref count of the primary add-in, so that the appdomain won't be cleaned up while they are still in use.  The secondary add-ins decrement the primary add-in's ref count when they are no longer referenced, permitting appdomain cleanup to proceed.

 

Note that this version of reference counting improves on some other versions in that you can’t accidentally remove a reference that a different object created.  That’s because AcquireLifetimeToken() returns a unique “token” number that should be stored and passed back to RevokeLifetimeToken().  Revoking a token that you didn’t previously acquire will by and large have no effect, aside from causing an InvalidOperationException to be thrown.

 

There’s more to say about lifetime management, but we’ll leave that for later posts.  It’s a multifaceted issue, but we hope that most hosts and addins will be able to be written as if all the resource cleanup is governed by garbage collection, even though there is a bit more to it than that underneath.

Behind the Scenes: Activation [Pete Sheill]

Let’s discuss the specifics of the construction of the add-in, the isolation boundary and the pipeline in the add-in model – i.e. activation.  I’ll be referring to the discovery and activation as discussed here and the segments of the add-in pipeline as discussed here.  Let’s say you have discovered a single token for an add-in, and you have decided to activate it.  To isolate the add-in from the host you chose to run it in its own new appdomain.  What happens next when you call one of the Activate() methods on the add-in token?  Let’s enumerate all of the steps that the system takes:

 

1.      The appdomain is created with the permissions that you specified in the call to Activate(). 

2.      The add-in assembly is loaded into that appdomain through a call to Assembly.LoadFrom(), passing in the location of the assembly.  The add-in type will not be loaded in the host’s appdomain.  In other uses this call can cause loader context issues if not used carefully, but we’ve taken steps to avoid them here.  In fact, this method was created specifically to support add-in scenarios. 

3.      We use reflection to call the no-argument constructor for the add-in to create an instance in that appdomain.  Naturally, since the add-in derives from the AddInBase class that is deployed in the AddInView assembly, this action causes the loader to load that assembly.  At this point we have an instance of the add-in running in its own appdomain.  Now we need to connect it to the host. 

4.      An instance of the AddInSideAdapter is constructed, passing in the add-in (typed as the add-in base) as the only argument to the constructor.  (This adapter implements a Contract interface and derives from MarshalByRefObject through the intermediate ContractBase class.  Therefore it can appear to live in both the host’s and the add-in’s appdomain—any calls to it from the host’s domain are proxied and executed in the add-in’s appdomain.) 

5.      The activation code passes the add-in adapter back to the host’s appdomain typed as the Contract that it implements. 

6.      An instance of the HostSideAdapter is constructed in the host’s appdomain, with the AddInSideAdapter being passed in as its only argument.  The exact HostSideAdapter type is determined by examining the add-in token.  This adapter derives from (or implements) the HostViewOfAddin that the host compiled against. 

7.      The activation code returns the host-side adapter to the host, typed as the HostViewOfAddin. 

 

The host will sometimes do further initialization of the add-in through a separate call, passing in any needed arguments.  Then it is up to the host and add-in to communicate as needed.  Activation has completed. 

 

Do the adapters need to be public?  Actually, no.  Though the normal rules of visibility would suggest that they would need to be public in order to be created and used by the activation code, the rules don’t apply in this case.  That’s because we use reflection to invoke the constructor, and given enough permission, reflection can invoke non-public constructors.  We didn’t want the adapters to need to be public because they exist as an implementation detail, not things to be documented and supported in themselves.

 

When the HostSideAdapter is constructed with a reference to the AddInSideAdapter, it does something right away that we wouldn’t need to do if all the objects were in the same appdomain.  It needs to call the AcquireLifetimeToken() member, specified in the IContract interface, of that adapter.  That is part of another big topic, lifetime management.  We’ll discuss that in another post.

 

This is one way to activate an addin in this model.  There are also some other different ways you could activate the same addin.  You could choose to activate it in the same appdomain as the host.  You could also choose to activate it in an entirely separate process, if isolation matters above all else.  We’ll explore those options more in later posts.

More on Logging UnhandledExeptions from Managed Add-Ins [Jesse Kaplan]

Last time we discussed some issues to be aware of when trying to build hosts that are resilient to failures in their add-ins.

The central idea was to subscribe to the UnhandledException event on the hosts AppDomain and to use the "sender" parameter to associate the exception to the add-in that caused it. The main limitation of the approach discussed for detecting and logging unhandled exceptions from the add-in's was that it only worked when the host itself was running in the default AppDomain and that this excluded hosts that themselves run inside other hosts (such as IIS).

This time we are taking a slightly different approach that works regardless of which AppDomain the host is running in, but has a different limitation. Instead of subscribing to the event on the host's domain, you can subscribe to them individually on each add-ins AppDomain. This causes a subtle but important difference in the way the handling code for that domain runs: it always executes in the domain that fires the event regardless of where the subscriber came from. This means that the piece of code handling this event needs to be loaded in the add-ins domain and, for versioning reasons, should be as small and have as few dependencies as possible. By keeping this code small and with few dependencies you reduce the chances that the code conflicts with the add-ins dependencies and causes a failure. In the attached sample I have refactored the code for handling exceptions into a separate assembly and made sure it was independent from the host and so brought no other pieces of the host into the add-ins AppDomain. There were a few small tweaks to the host itself, but all the interesting changes take place in the new UnhandledException project.

So the pattern used in the sample can work with hosts in non-default domains at the expense of having a slightly higher risk of causing versioning problems butwith careful coding you can minimize this risk.

 

Note: The attached sample was built for a pre-RTM version of .NetFX 3.5 and will not work on the RTM build. For an updated sample please see our codeplex site here: https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=clraddins&ReleaseId=9456

 

Posted by CLR Add-In Team | 2 Comments
Filed under:

Attachment(s): ReliableExtensibleCalculator.zip

Using AppDomain Isolation to Detect Add-In Failures [Jesse Kaplan]

One of the nice aspects of isolating add-ins in a different AppDomain is that it makes it easier to prevent failures in the Add-In from impacting the host and at the same time improves your ability to detect when a problem has occurred and which piece of code was the culprit. Developers of extensible applications will often quickly discover that a major source of support calls are actually bugs in the add-ins rather than in the application itself: thus being able to identify the failing add-in is an important of many extensible applications.

What actions an application decides to take once it has detected a failing add-in is up to an individual developer, but the pattern for detecting these failures will largely remain the same. There are three major ways an add-in can cause problems for a host: machine state corruption, unhandled exceptions, and resource exhaustion. Machine state corruption can be addressed by sandboxing add-ins with a limited permission set and thus isolating them from the machine. We’ll cover resource exhaustion at a later point, but today’s focus will be unhandled exceptions.

There are actually two types of exceptions that a host has to be wary of: those that are thrown by the add-in during a call into it by the host and those that are thrown by add-in code on threads originating from the add-in itself. The first class are easier as all a host has to do is put a catch block around calls into the add-ins and then decide how to deal with them. Unhandled exceptions on add-in originated threads are harder because the host isn’t on the stack and can’t catch the exceptions. Starting with the CLR v2.0 unhandled exceptions on child threads will now cause the entire process to be torn down and thus it is impossible for a host to completely recover from this. With a little work though it can detect which AppDomain and, assuming it gives each add-in its own domain, add-in caused the problem and log the failure before exiting and even restarting.

In the attached sample we utilize the AppDomain.UnhandledException event and the AddInController class to add failure detection logic to our existing calculator sample by logging failures to disk and tagging add-ins that have previously crashed as potentially unreliable. In this sample most of the interesting work is done in the UnhandledExceptionHelper class inside the host’s project. Each time the host activates and add-in it calls into this class passing in the instance it receives back from AddInToken.Activate. The UnhandledExceptionHelper class will then use the AddInController class to associate that add-in with the AppDomain it was created in and will detect and log failures in that domain as being caused by the add-in activated in it.

The most interesting part of this sample is the fact that it only works for hosts running in the default AppDomain. This is fine for exe based hosts but isn’t so great for others and I have to admit it is not the way I originally intended to write this. The semantics of the UnhandledException event is actually a little odd and pushed me down this path. If you subscribe to this event on the default AppDomain it will file whenever any exception in the process goes unhandled and you need to cast the “sender” parameter to AppDomain in order to discover the originating domain. If you subscribe to the event on other domains it will fire only for exceptions that when unhandled in threads originating in that domain: this is almost what you want in the case of add-ins except for the fact that the delegate fires in that AppDomain and requires that the subscribing type be loaded in that domain. This of course is generally frowned upon in the context of a host as it means loading host code in the add-in’s domain (there are still valid ways to do this but you have to be very careful).  I’ll post a sample showing how to subscribe to this event from non-default domains down the line, but for now most hosts will be able to use the patterns in this sample to detect failing, AppDomain isolated, add-ins and take appropriate actions.  

 

Note: The attached sample was built for a pre-RTM version of .NetFX 3.5 and will not work on the RTM build. For an updated sample please see our codeplex site here: https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=clraddins&ReleaseId=9455

Posted by CLR Add-In Team | 5 Comments
Filed under:

Attachment(s): ReliableExtensibleCalculator.zip

Automation Add-Ins [Jesse Kaplan]

First off I'd like to apologize for the long pause between posts. We've been working heads down on some pretty exciting add-in stuff that we can't announce just yet and haven't had as much time as we'd like to spend on this blog. We're wrapping that work up now though so you should see the stream of posts picking up.

To start us off I'd like to post a quick sample to responds to a lot of questions I've been getting.

All of our samples so far have been pretty simplistic with very shallow object models that are focused on the add-ins providing a service to the host. The two most common questions I recieve about our model is how it applies when the host is actually providing a service to the add-in (think of an automation scenario) and how to express events in the pipeline. Attached is a sample that shows a complete pipeline demonstrating some typical patterns in a host automation scenario. It doesn't include an actual host or add-in but as you'll see when you look at the two views that the programming experience for both would still be smooth even with the more complex object model.

 

Note: The attached samples was built for a pre-RTM version of .NetFX 3.5 and will not work on the RTM build. For an updated sample please see our codeplex site: https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=clraddins&ReleaseId=9474

Additionally there is a sample available demonstrating add-ins exposing events to hosts and includes both the host and the add-in so you can easily step through it and follow the code. It is available here:  https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=clraddins&ReleaseId=10199

 

 

Posted by CLR Add-In Team | 12 Comments
Filed under:

Attachment(s): MyAutomationApplication.zip

Refactoring Work in Orcas March CTP [Jesse Kaplan]

We've had a few questions internally and externally from people who were having problems running some of our earlier samples on the March CTP so I wanted to give everyone a heads up on what has changed and how to move forward any sample code you may have downloaded/written against previous CTPs.

The biggest change we made was to move all of our code out of System.Core and into System.Addin.dll. All you need to do to fix this is to add a reference to that assembly in your projects. One thing to note is that, in this CTP, the setup work for the new System.AddIn.dll is not complete and while it shows up in the framework directory and the GAC it will not be in the prepopulated list of .Net references in VS: you will instead need to manually browse to the 3.5 framework directory and add a dependecy to the 3.5 version of System.AddIn.dll there.

The other change we made was to move the AddInContractAttribute class away from the other attributes and into the System.AddIn.Contract assembly. This way you can build contract assemblies that are not dependent on a specific version of the System.AddIn assembly.

 Attached to this post is a sample from our 2nd MSDN magazine article updated to reflect these changes. The sample we included in the adapters post last week is already updated to work on the March CTP.

Building Data Adapters [Jason He]

Introduction to Adapters

 

If two types (A and B) have to version independently, they cannot have static references between them. Instead an adapter is needed to sits between A and B. It enables them to communicate without a direct reference and thus providing a versioning boundary.

 

An adapter normally takes one type in the constructor and exposes the methods as another type. Here is an example

    public interface A { void methodA();}

    public interface B { void methodB();}

    public class Adapter : A

    {

        B interfaceB;

        public Adapter(B ib) { interfaceB = ib; }

        public void methodA() { interfaceB.methodB(); }

}

 

This adapter looks like an A. It actually implements A by calling functions in B. Since A, B and Adapters are normally in different assembly, A and B don’t have a versioning dependency. For example, if we got a newer version B2, we don’t need to update A assembly to reflect the changes. We only need to update adapters to reroute the function calls. Updating Adapters is easier to do than updating Host and AddIn.

 

When to use data adapters

When you define a rich object model for your host and add-ins to communicate over you need to decide how you want to communicate over the isolation boundary. In our model, for each data type that goes across the isolation boundary we need one Contract, two views and two Adapters. Typically those defining the object model and building the pipeline will have one assembly for each pipeline component (Contract, Add-In/Host View, Add-In Side/Host Side Adapter) and group the pieces for all types in their system into that single assembly.

 

Calculator scenario

Let us consider the calculator sample that Jesse and Jack wrote on the MSDN magazine. In that sample, all the types passing between Host and AddIn are primitive types like “string” “double”. What happens if Host has a complex data type that it expects an AddIn to access? Let us assume we want to treat operator and operands as one object and pass it to the calculator AddIn. In this case, our contract becomes a little more complex.

 

We’ll walk through the important code in the sample below, but for the full example in VS Solution form see the attached file.

 

Here is the contract

 

    [AddInContract]

    public interface ICalculatorContract: IContract

    {  

         string GetAvailableOperations();

        double Operate(IOperateContract operate);

    }

 

    public interface IOperateContract : IContract

    {

 

        string GetOperation();

        double GetA();

        double GetB();

    }

 

We create an operate contract which exposes all the data that calculator AddIn needs to access. This contract will be passed from Host side to the AddIn side.

 

Here is our AddInBase and AddIn

 

 

    [AddInBaseAttribute]

    public abstract class Calculator {

       

        public abstract string Operations {

            get;

        }

       

        public abstract double Operate(Operate operate);

    }  

   

    public abstract class Operate {

       

        public abstract string Operation {

            get;

        }

       

        public abstract double A {

            get;

        }

       

        public abstract double B {

            get;

        }

    }

 

    [AddIn("CalculatorV3AddIn")]

    public class CalulatorV3AddIn : Calculator

    {

        public override string Operations

        {

            get { return "+ - * / **"; }

        }

        public override double Operate(Operate operation)

        {

            switch (operation.Operation)

            {

                case "+":

                    return operation.A + operation.B;

                case "-":

                    return operation.A - operation.B;

                case "*":

                    return operation.A * operation.B;

                case "/":

                    return operation.A / operation.B;

                case "**":

                    return Math.Pow(operation.A, operation.B);

                default:

                    throw new InvalidOperationException("This add-in does not support: " + operation.Operation);

            }

        }

 

    }

The AddIn code looks quite similar to the old calculator sample. The biggest difference is AddIn now takes an Operate as a parameter. From AddIn side, this is how the Host side operate object looks like. This Operate is defined in AddInBase so that both AddIn and AddInAdapter can use it.

 

Here is the AddInAdapter

 

 

    [AddInAdapterAttribute]

    public class CalculatorViewToContractAddInAdapter :

ContractBase, ICalculatorContract {

       

        private Calculators.Extensibility.Calculator _view;

       

        public CalculatorViewToContractAddInAdapter(Calculator view) {

            _view = view;

        }

       

        public virtual string GetAvailableOperations() {

            return _view.Operations;

        }

       

        public virtual double Operate(IOperateContract operate) {

            return _view.Operate(new OperateContractToViewAddInAdapter(operate));

        }

       

    }

 

    public class OperateContractToViewAddInAdapter : Operate {

       

        private IOperateContract _contract;

       

        private System.AddIn.Pipeline.LifetimeTokenHandle _handle;

       

        public OperateContractToViewAddInAdapter(IOperateContract contract) {

            _contract = contract;

            _handle = new System.AddIn.Pipeline.LifetimeTokenHandle(contract);

        }

       

        public override string Operation {

            get {

                return _contract.GetOperation();

            }

        }

       

        public override double A {

            get {

                return _contract.GetA();

            }

        }

       

        public override double B {

            get {

                return _contract.GetB();

            }

        }

       

    }

 

We intentionally created a long type name here OperateContractToViewAddInAdapter.  The name explains its role quite well. It is an Adapter. It is an Adapter at AddIn side. It is an Adapter to adapt a contract to a view. When we look at the AddIn code, we may ask the question who created Operate. Now we know that AddInAdapter created an instance of OperateContractToViewAddInAdapter which derives from Operate and passed it to AddIn. The contract we are adapting here is IOperateContract. The next question will be who created IOperateContract at the host side.

 

Let’s take a look at HostAdapter code.

 

 

    [HostAdapterAttribute]

    public class CalculatorContractToViewHostAdapter :Calculator {

       

        private ICalculatorContract _contract;

       

        private LifetimeTokenHandle _handle;

       

        public CalculatorContractToViewHostAdapter(ICalculatorContract contract) {

            _contract = contract;

            _handle = new LifetimeTokenHandle(contract);

        }

       

        public override string Operations {

            get {

                return _contract.GetAvailableOperations();

            }

        }

       

        public override double Operate(Operate operate) {

            return _contract.Operate(new OperateViewToContractHostAdapter(operate));

        }

       

    }

 

    public class OperateViewToContractHostAdapter : ContractBase, IOperateContract {

       

        private Calculators.Extensibility.Operate _view;

       

        public OperateViewToContractHostAdapter(Operate view) {

            _view = view;

        }

       

        public virtual string GetOperation() {

            return _view.Operation;

        }

       

        public virtual double GetA() {

            return _view.A;

        }

       

        public virtual double GetB() {

            return _view.B;

        }

       

    }

 

We got another adapter at host side called OperateViewToContractHostAdapter. It adapts a host side view to a contract. The host side view looks like

 

 

    public abstract class Calculator {

       

        public abstract string Operations {

            get;

        }

       

        public abstract double Operate(Operate operate);

    }

 

    public abstract class Operate {

       

        public abstract string Operation {

            get;

        }

       

        public abstract double A {

            get;

        }

       

        public abstract double B {

            get;

        }

    }

 

 

 To expose the operate object, we created 1 contract, 2 views and 2 adapters. It stacks up like below

 

Host

Operate

OperateViewToContractHostAdapter

IOperateContract

OperateContractToViewAddInAdapter

Operate

AddIn

 

As you can see our system for adapting data is same one we use for adapting the add-ins themselves: the only difference is that the system activates the add-ins and their adapters directly, rather than relying on other pieces in the pipeline to do so. We may notice from above AddIn and Host adapters that both adapters can adapt IContract to View and View to IContract. It is symmetric. It is a common misconception that adapters adapt one way not the other inside of a given pipeline component.  Sometime, both ViewToContract and ContractToView adapters are required in one assembly if the data type is used as both in and out parameters of a contract.

 

We may also notice that both views and adapters have similar code. It is actually not a crazy idea to merge two views into one assembly and two adapters into one assembly. As long as we put the assemblies at the right location, our AddIn API will be able to discovery and activate the pipeline as expected.

 

In summary, we have illustrated adapters that can link views and IContract at both Host and AddIn side. For each data type that we want to expose from Host to AddIn or from AddIn to Host, we need to define one Contract, two views and two adapters. Adapter is the key to break the versioning dependency.

Posted by CLR Add-In Team | 11 Comments
Filed under: ,

Attachment(s): ExtensibleCalculator.zip
More Posts Next page »
 
Page view tracker