Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Advanced Host Control

In this section, I’ll mention some more advanced topics related to hosting the CLR. My intent is to give you a taste of what is possible, and this will help you to understand more of what the CLR is capable of. I encourage you to seek out other texts if you find this information particularly interesting.

 

Managing the CLR by Using Managed Code

The System.AppDomainManager class allows a host to override CLR default behavior by using managed code instead of using unmanaged code. Of course, using managed code makes imple- menting a host easier. All you need to do is define your class and derive it from the System.App­ DomainManager class, overriding any virtual methods where you want to take over control. Your class should then be built into its very own assembly and installed into the global assembly cache (GAC) because the assembly needs to be granted full-trust, and all assemblies in the GAC are always granted full-trust.

Then, you need to tell the CLR to use your AppDomainManager-derived class. In code, the best way to do this is to create an AppDomainSetup object initializing its AppDomainManagerAssembly and AppDomainManagerType properties, both of which are of type String. Set the AppDomain­ ManagerAssembly property to the string identifying the strong-name identity of the assembly that defines your AppDomainManager-derived class, and then set the AppDomainManagerType property to the full name of your AppDomainManager-derived class. Alternatively, AppDomainManager can be set in your application’s XML configuration file by using the appDomainManagerAssembly and appDomainManagerType elements. In addition, a native host could query for the ICLRControl interface and call this interface’s SetAppDomainManagerType function, passing in the identity of the GAC-installed assembly and the name of the AppDomainManager-derived class.5

Now, let’s talk about what an AppDomainManager-derived class can do. The purpose of the AppDomainManager-derived class is to allow a host to maintain control even when an add-in tries to create AppDomains of its own. When code in the process tries to create a new AppDomain, the App­ DomainManager-derived object in that AppDomain can modify security and configuration settings.

 

 

 
 

5 It is also possible to configure an AppDomainManager by using environment variables and registry settings, but these mechanisms are more cumbersome than the methods mentioned in the text and should be avoided except for some testing scenarios.


It can also decide to fail an AppDomain creation, or it can decide to return a reference to an existing AppDomain instead. When a new AppDomain is created, the CLR creates a new AppDomainManager- derived object in the AppDomain. This object can also modify configuration settings, how execution context is flowed between threads, and permissions granted to an assembly.

 

Writing a Robust Host Application

A host can tell the CLR what actions to take when a failure occurs in managed code. Here are some examples (listed from least severe to most severe):



■ The CLR can abort a thread if the thread is taking too long to execute and return a response. (I’ll discuss this more in the next section.)

■ The CLR can unload an AppDomain. This aborts all of the threads that are in the AppDomain and causes the problematic code to be unloaded.

■ The CLR can be disabled. This stops any more managed code from executing in the process, but unmanaged code is still allowed to run.

■ The CLR can exit the Windows process. This aborts all of the threads and unloads all of the

AppDomains first so that cleanup operations occur, and then the process terminates.

 

The CLR can abort a thread or AppDomain gracefully or rudely. A graceful abort means that cleanup code executes. In other words, code in finally blocks runs, and objects have their Finalize methods executed. A rude abort means that cleanup code does not execute. In other words, code in finally blocks may not run, and objects may not have their Finalize methods executed. A graceful abort cannot abort a thread that is in a catch or finally block. However, a rude abort will abort a thread that is in a catch or finally block. Unfortunately, a thread that is in unmanaged code or in a constrained execution region (CER) cannot be aborted at all.

A host can set what is called an escalation policy, which tells the CLR how to deal with managed code failures. For example, SQL Server tells the CLR what to do should an unhandled exception be thrown while the CLR is executing managed code. When a thread experiences an unhandled excep- tion, the CLR first attempts to upgrade the exception to a graceful thread abort. If the thread does not abort in a specified time period, the CLR attempts to upgrade the graceful thread abort to a rude thread abort.

What I just described is what usually happens. However, if the thread experiencing the unhandled exception is in a critical region, the policy is different. A thread that is in a critical region is a thread that has entered a thread synchronization lock that must be released by the same thread, for example, a thread that has called Monitor.Enter, Mutex’s WaitOne, or one of ReaderWriterLock’s Acquire­ ReaderLock or AcquireWriterLock methods.6 Successfully waiting for an AutoResetEvent, ManualResetEvent, or Semaphore doesn’t cause the thread to be in a critical region because another

 

 
 

6 All of these locks internally call Thread’s BeginCriticalRegion and EndCriticalRegion methods to indicate when they enter and leave critical regions. Your code can call these methods too if you need to. Normally, this would be neces- sary only if you are interoperating with unmanaged code.


thread can signal these synchronization objects. When a thread is in a critical region, the CLR believes that the thread is accessing data that is shared by multiple threads in the same AppDomain. After all, this is probably why the thread took the lock. If the thread is accessing shared data, just terminating the thread isn’t good enough, because other threads may then try to access the shared data that is now corrupt, causing the AppDomain to run unpredictably or with possible security vulnerabilities.

So, when a thread in a critical region experiences an unhandled exception, the CLR first attempts to upgrade the exception to a graceful AppDomain unload in an effort to get rid of all of the threads and data objects that are currently in use. If the AppDomain doesn’t unload in a specified amount of time, the CLR upgrades the graceful AppDomain unload to a rude AppDomain unload.

 

How a Host Gets Its Thread Back

Normally, a host application wants to stay in control of its threads. Let’s take a database server as an example. When a request comes into the database server, a thread picks up the request and then dis- patches the request to another thread that is to perform the actual work. This other thread may need to execute code that wasn’t created and tested by the team that produced the database server. For example, imagine a request coming into the database server to execute a stored procedure written in managed code by the company running the server. It’s great that the database server can run the stored procedure code in its own AppDomain, which is locked down with security. This prevents the stored procedure from accessing any objects outside of its own AppDomain, and it also prevents the code from accessing resources that it is not allowed to access, such as disk files or the clipboard.

But what if the code in the stored procedure enters an infinite loop? In this case, the database server has dispatched one of its threads into the stored procedure code, and this thread is never com- ing back. This puts the server in a precarious position; the future behavior of the server is unknown.

For example, the performance might be terrible now because a thread is in an infinite loop. Should the server create more threads? Doing so uses more resources (such as stack space), and these threads could also enter an infinite loop themselves.

To solve these problems, the host can take advantage of thread aborting. Figure 22-3 shows the typical architecture of a host application trying to solve the runaway thread problem. Here’s how it works (the numbers correspond to the circled numbers in the figure):

1.A client sends a request to the server.

2.A server thread picks up this request and dispatches it to a thread pool thread to perform the actual work.

3.A thread pool thread picks up the client request and executes trusted code written by the company that built and tested the host application.


4.This trusted code then enters a try block, and from within the try block, calls across an AppDomain boundary (via a type derived from MarshalByRefObject). This AppDomain contains the untrusted code (perhaps a stored procedure) that was not built and tested by the company that produced the host application. At this point, the server has given control of its thread to some untrusted code; the server is feeling nervous right now.

5.When the host originally received the client’s request, it recorded the time. If the untrusted code doesn’t respond to the client in some administrator-set amount of time, the host calls Thread’s Abort method, asking the CLR to stop the thread pool thread, forcing it to throw a ThreadAbortException.

6.At this point, the thread pool thread starts unwinding, calling finally blocks so that cleanup code executes. Eventually, the thread pool thread crosses back over the AppDomain bound- ary. Because the host’s stub code called the untrusted code from inside a try block, the host’s stub code has a catch block that catches the ThreadAbortException.

7.In response to catching the ThreadAbortException, the host calls Thread’s ResetAbort

method. I’ll explain the purpose of this call shortly.

 

8.Now that the host’s code has caught the ThreadAbortException, the host can return some sort of failure back to the client and allow the thread pool thread to return to the pool so that it can be used for a future client request.

 

 

FIGURE 22-3How a host application gets its thread back.


Let me now clear up a few loose ends about this architecture. First, Thread’s Abort method is asynchronous. When Abort is called, it sets the target thread’s AbortRequested flag and returns immediately. When the runtime detects that a thread is to be aborted, the runtime tries to get the thread to a safe place. A thread is in a safe place when the runtime feels that it can stop what the thread is doing without causing disastrous effects. A thread is in a safe place if it is performing a managed blocking operation such as sleeping or waiting. A thread can be corralled to a safe place by using hijacking (described in Chapter 21). A thread is not in a safe place if it is executing a type’s class constructor, code in a catch or finally block, code in a CER, or unmanaged code.

After the thread reaches a safe place, the runtime will detect that the AbortRequested flag is set for the thread. This causes the thread to throw a ThreadAbortException. If this exception is not caught, the exception will be unhandled, all pending finally blocks will execute, and the thread will kill itself gracefully. Unlike all other exceptions, an unhandled ThreadAbortException does not cause the application to terminate. The runtime silently eats this exception and the thread dies, but the application and all of its remaining threads continue to run just fine.

In my example, the host catches the ThreadAbortException, allowing the host to regain control of the thread and return it to the pool. But there is a problem: What is to stop the untrusted code from catching the ThreadAbortException itself to keep control of the thread? The answer is that the CLR treats the ThreadAbortException in a very special manner. Even when code catches the ThreadAbortException, the CLR doesn’t allow the exception to be swallowed. In other words, at the end of the catch block, the CLR automatically rethrows the ThreadAbortException exception.

This CLR feature raises another question: If the CLR rethrows the ThreadAbortException at the end of a catch block, how can the host catch it to regain control of the thread? Inside the host’s catch block, there is a call to Thread’s ResetAbort method. Calling this method tells the CLR to stop rethrowing the ThreadAbortException at the end of each catch block.

This raises yet another question: What’s to stop the untrusted code from catching the Thread­ AbortException and calling Thread’s ResetAbort method itself to keep control of the thread? The answer is that Thread’s ResetAbort method requires the caller to have the SecurityPermission with the ControlThread flag set to true. When the host creates the AppDomain for the untrusted code, the host will not grant this permission, and now, the untrusted code cannot keep control of the host’s thread.

I should point out that there is still a potential hole in this story: while the thread is unwinding from its ThreadAbortException, the untrusted code can execute catch and finally blocks. Inside these blocks, the untrusted code could enter an infinite loop, preventing the host from regaining control of its thread. A host application fixes this problem by setting an escalation policy (discussed earlier). If an aborting thread doesn’t finish in a reasonable amount of time, the CLR can upgrade the thread abort to a rude thread abort, a rude AppDomain unload, disabling of the CLR, or killing of the process. I should also note that the untrusted code could catch the ThreadAbortException and, inside the catch block, throw some other kind of exception. If this other exception is caught, at the end of the catch block, the CLR automatically rethrows the ThreadAbortException.


It should be noted, though, that most untrusted code is not actually intended to be malicious; it is just written in such a way so as to be taking too long by the host’s standards. Usually, catch and finally blocks contain very little code, and this code usually executes quickly without any infinite loops or long-running tasks. And so it is very unlikely that the escalation policy will have to go into effect for the host to regain control of its thread.

By the way, the Thread class actually offers two Abort methods: one takes no parameters, and the other takes an Object parameter allowing you to pass anything. When code catches the Thread­ AbortException, it can query its read-only ExceptionState property. This property returns the object that was passed to Abort. This allows the thread calling Abort to specify some additional information that can be examined by code catching the ThreadAbortException. The host can use this to let its own handling code know why it is aborting threads.


 


C HA P T E R 2 3


Date: 2016-03-03; view: 665


<== previous page | next page ==>
Nbsp;   How Hosts Use AppDomains | Nbsp;   Assembly Loading
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.008 sec.)