Saturday, January 28, 2017

You don't own that thread

Well if you have created a thread of course you can manage its timeline, that’s not my point. My point is not all threads created equally. So, where does this come from?

It comes from a problem my team have experienced very recently:

The requirement was very simple actually, create an AppDomain, load some assemblies into it then unload the dynamically created AppDomain. Now the first problem is “who is going to unload the AppDomain?”. In our scenario (besides one exceptional case) it’s the dynamic AppDomain that decides that its work is completed and now it’s safe to be unloaded. Of course, the dynamic AppDomain cannot unload itself. So what it does is notify the main AppDomain and ask it to unload itself. Simple…

So how do you send that notification to the main AppDomain? Call a method on a remote object (MarshalByRefObject maybe) or trigger an event and hope that someone on the other side of the wall is listening? These are all valid decisions but may lead to some problems. In either scenario you “call” a method, which also means there’s a thread running and you use that thread to “execute” the “Dear main AppDomain… please unload me” command.
In that very same thread the main AppDomain may try to unload the dynamically AppDomain. See the problem now ?
We spawn a thread in the dynamic domain which travels across memory (and execution) boundaries and goes into the main domain and then should rewind (stack based programming sucks ha?) in the originating (dynamic) domain.

So in order to properly unload an AppDomain (unless you don’t care about the work it is doing) you need to make sure that all your worker threads are synchronized and preferably gracefully shutdown or completed. If there’s one thread that goes back and forth between domains, that is trying to unload the dynamic AppDomain you are very likely to have problems.

The solution seems obvious now. Make sure the dynamic AppDomain is unloaded by another thread, which has nothing to do with (actually “in”) the dynamic domain. One easy solution could be; having the request thread (RT, the one that requests the main AppDomain to unload the dynamic one) spawn unloader thread (UT, that will actually unload the AppDomain).

And make sure UT and RT are synchronized before unloading the dynamic AppDomain. This will ensure that the RT’s call is returned and stack is rewound properly. Something like UT.Join(RT) then Unload(DynamicAppDomain).