Section 10.3. Marshaling-by-Reference Activation Modes


10.3. Marshaling-by-Reference Activation Modes

.NET supports two kinds of marshal-by-reference objects: client-activated and server- activated. The two kinds map to three activation modes: client-activated object, server-activated single-call, and server-activated singleton. The different activation modes control object state management, object sharing, the object lifecycle, and the way in which the client binds to an object. The client decides whether to use client-activated or server-activated objects. If the client chooses client-activated objects, just one activation mode is available. If the client chooses server-activated objects, it's up to the hosting app domain to decide whether the client will get a server-activated single-call object or a server-activated singleton object. These objects are called server-activated because it's up to the host to activate the object on behalf of the client and bind it to the client. The hosting app domain indicates to .NET which activation modes it supports, using server registration. The host can support both client- and server-activated objects, or just one of these types; it's completely at the discretion of the host. If it decides to support a server-activated mode, the host must register its objects either as single-call objects or as singleton objects, but not both. This will all become clearer later in this chapter, when you see the actual registration code. The next sections explain the different activation modes.

10.3.1. Client-Activated Object

Client-activated object mode is the classic client/server activation mode: when a client creates a new object, the client gets a new object. That object is dedicated to the client, and it's independent of all other instances of the same class. Different clients in different app domains get different objects when they create new objects on the host (see Figure 10-6). There are no limitations on constructing client-activated objects, and you can use either parameterized constructors or the default constructor. The constructor is called exactly once, when the client creates the new remote object; if parameterized constructors are used, .NET marshals the construction parameters to the new object. Clients can choose to share their objects with other clients, either in their own app domains or in other app domains. Like local objects, client-activated objects can maintain state in memory. To make sure the remote object isn't disconnected from the remoting infrastructure and collected by its local garbage collector, client-activated objects require leasing when they make cross-process calls, to keep the objects alive for as long as the clients are using them. Leasing, discussed later in this chapter, provides a timestamp extending the life of the object.

Client-activated object mode is similar to the default DCOM activation model, with one important difference: the host app domain must register itself as a host willing to accept client-activated calls before remote calls are issued. As mentioned earlier, this means the process containing the host app domain must be running before such calls are made.

Figure 10-6. With client-activated objects, each client gets an independent object to use


10.3.2. Server-Activated Single Call

The fundamental problem with the client-activated object mode is that it doesn't scale well. The server object may hold expensive or scarce resources, such as database connections, communication ports, or files. Imagine an application that has to serve many clients. Typically, these clients create the remote objects they need when the client application starts and dispose of them when the client application shuts down. What impedes scalability with client-activated objects is that the client applications can hold onto objects for long periods of time, while actually using the objects for only a fraction of that time. If your design calls for allocating an object for each client, you will tie up such crucial limited resources for long periods and will eventually run out of resources. A better activation model is to allocate an object for a client only while a call is in progress from the client to the object. That way, you have to create and maintain in memory only as many objects as there are concurrent calls, not as many objects as there are clients. This is exactly what the single-call activation mode is about: when the client uses a server-activated single-call object, for each method call .NET creates a new object, lets it service the call, and then discards it. Between calls, the client holds a reference to a proxy that doesn't have an actual object at the end of the wire. The following list shows how single-call activation works; its steps are illustrated in Figure 10-7.

  1. The object executes a method call on behalf of a remote client.

  2. When the method call returns, if the object implements IDisposable, .NET calls IDisposable.Dispose( ) on it. .NET then releases all references it has to the object, making it a candidate for garbage collection. Meanwhile, the client continues to hold a reference to a proxy and doesn't know that its object is gone.

  3. The client makes another call on the proxy.

  4. The proxy forwards the call to the remote domain.

  5. .NET creates an object and calls the method on it.

Figure 10-7. Single-call activation mode


10.3.2.1 Benefits of single-call objects

The obvious benefit of using single-call objects is the fact that you can dispose of the expensive resources the objects occupy long before the clients dispose of the objects. By the same token, acquiring the resources is postponed until they are actually needed by a client. Keep in mind that creating and destroying the object repeatedly on the object side without tearing down the connection to the client (with its client-side proxy) is a lot cheaper than creating and disposing of the object altogether. Another benefit is that even if the client isn't disciplined enough to explicitly discard the object, this has no effect on scalability, because the object is discarded automatically.

If the client does call IDisposable.Dispose( ) on the object, it has the detrimental effect of recreating the object just so the client can call Dispose( ) on it. This is followed by a second call to Dispose( ) by the remoting infrastructure.


10.3.2.2 Designing a single-call object

Although in theory you can use single-call activation on any component type, in practice, you need to design the component and its interfaces to support the single-call activation mode from the ground up. The main problem is that the client doesn't know it's getting a new object each time it makes a call. Single-call components must be state-aware; that is, they must proactively manage their state, giving the client the illusion of a continuous session. A state-aware object isn't the same as a stateless object. In fact, if the single-call object were truly stateless, there would be no need for single-call activation in the first place. A single-call object is created just before every method call and deactivated immediately after each call. Therefore, at the beginning of each call, the object should initialize its state from values saved in some storage, and at the end of the call, it should return its state to the storage. Such storage is typically either a database or the filesystem. However, not all of an object's state can be saved as-is. For example, if the state contains a database connection, the object must reacquire the connection at the beginning of every call and dispose of the connection at the end of the call, or in its implementation of IDisposable.Dispose( ).

Using single-call activation mode has one important implication for method design: every method call must include a parameter to identify the object whose state needs to be retrieved. The object uses that parameter to gets its state from the storage and not the state of another instance of the same type. Examples of such identifying parameters are the account number for bank account objects, the order number for objects processing orders, and so on. Example 10-3 shows a template for implementing a single-call class. The class provides the MyMethod( ) method, which accepts a parameter of type Param (a pseudo-type invented for this example) that identifies the object.

Example 10-3. Implementing a single-call component
 public class Param {...}    public class MySingleCallComponent : MarshalByRefObject,IDisposable {    public MySingleCallComponent( )    {}    public void MyMethod(Param objectIdentifier)    {       GetState(objectIdentifier);       DoWork( );       SaveState(objectIdentifier);    }    void GetState(Param objectIdentifier)    {...}    void DoWork( )    {...}    void SaveState(Param objectIdentifier)    {...}       public void Dispose( )    {...}    /* Class members/state */ } 

The object then uses the identifier to retrieve its state and to save the state back at the end of the method call.

Another design constraint when dealing with single-call objects has to do with constructors. Because .NET re-creates the object automatically for each method call, it doesn't know how to use parameterized constructors, or which parameters to provide to them. As a result, a single-call object can't have parameterized constructors. In addition, because the object is constructed only when a method call takes place, the actual construction call on the client side is never forwarded to the objects:

     MySingleCallComponent obj;     obj = new MySingleCallComponent( ); //No constructor call is made            obj.MyMethod( );//Constructor executes      obj.MyMethod( );//Constructor executes 

Single-call activation clearly involves a trade-off between performance (the overhead of reconstructing the object's state on each method call) and scalability (holding on to the state and the resources it ties up). There are no hard-and-fast rules as to when and to what extent you should trade performance for scalability. You may need to profile your system and ultimately redesign some objects to use single-call activation and others not to use it.


10.3.2.3 Applying the single-call mode

The single-call activation mode (see Example 10-3) works well when the amount of work to be done in each method call is finite, and there are no more activities to complete in the background once a method returns. You should not spin off background threads or dispatch asynchronous calls back into the object, because the object will be disposed of once the method returns. Because the single-call object retrieves its state from some storage on every method call, single-call objects work very well in conjunction with a load-balancing machine, as long as the state repository is some global resource accessible to all machines. The load balancer can redirect calls to different machines at will, knowing that each single-call object can service the call after retrieving its state.

Enterprise Services JITA

.NET Enterprise Services offer a set of smart instance-management techniques for .NET serviced components. One of those services is just-in-time activation (JITA), which works much like single-call objects. JITA has a few advantages over single-call objects, mainly the ability to combine it with other Enterprise Services instance-management techniques (such as object pooling). Other advantages are JITA's integration with distributed transactions and its ability to manage local as well as remote objects. JITA is described in Chapters 3 and 10 of my book COM and .NET Component Services (O'Reilly).


10.3.3. Server-Activated Singleton

The server-activated singleton activation mode provides a single, well-known object to all clients. Because the clients connect to a single, well-known object, .NET ignores the client calls to new, even if the singleton object has not yet been created (the .NET runtime in the client app domain has no way of knowing what goes on in the host app domain anyway). The singleton is created when the first client tries to access it. Subsequent client calls to create new objects and subsequent access attempts are all channeled to the same singleton object (see Figure 10-8). Example 10-4 demonstrates these points: you can see from the trace output that the constructor is called only once, on the first access attempt, and that obj2 is wired to the same object as obj1.

Example 10-4. A singleton object is created when first accessed, then used by all clients
 public class MySingleton : MarshalByRefObject {    int m_Counter = 0;    public MySingleton( )    {       Trace.WriteLine("MySingleton.MySingleton( )");    }    public void TraceCounter( )    {       m_Counter++;       Trace.WriteLine(m_Counter);    } } //Client-side code: MySingleton obj1; MySingleton obj2;    Trace.WriteLine("Before calling obj1 constructor"); obj1 = new MySingleton( ); Trace.WriteLine("After  calling obj1 constructor");    obj1.TraceCounter( ); //Constructor will be called here obj1.TraceCounter( );    Trace.WriteLine("Before calling obj2 constructor"); obj2 = new MySingleton( ); Trace.WriteLine("After  calling obj2 constructor");    obj2.TraceCounter( ); obj2.TraceCounter( );    //Output: Before calling obj1 constructor After  calling obj1 constructor MySingleton.MySingleton( ) 1 2 Before calling obj2 constructor After  calling obj2 constructor 3 4 

Because the singleton constructor is only called implicitly by .NET under the covers, a singleton object can't have parameterized constructors. Parameterized constructors are also banned because of an important semantic characteristic of the singleton activation mode: at any given point in time, all clients share the same state of the singleton object (see Figure 10-8). If parameterized constructors were allowed, different clients could call them with different parameters, which would result in a different

Figure 10-8. With a server-activated singleton object, all clients share the same well-known object


state for each client. If you try to create a singleton object using a parameterized constructor, .NET throws an exception of type RemotingException.

COM also supported singletons by allowing you to provide a special class factory that always returned the same object. The COM singleton behaved much like a .NET singleton. Using ATL, designating a class as a singleton was done by replacing the default class factory macro with the singleton macro. The main difference between a COM singleton and a .NET singleton is that with .NET, the object becomes a singleton because the host registers it as such. Other hosts can register the same component type as a single-call or client-activated object. With COM, the singleton was always a singleton.


10.3.3.1 Using singleton objects

Singleton objects are the sworn enemy of scalability, because a single object can sustain only so many concurrent client calls. Take care before deciding to use a singleton object. Make sure that the singleton will not be a hotspot for scalability and that your design will benefit from sharing the singleton's object state. In general, you should use a singleton object only if it maps well to a true singleton in the application logic, such as a logbook to which all components should log their activities. Other examples are a single communication port or a single mechanical motor. Avoid using a singleton if there is even the slightest chance that the business logic will allow more than one such object in the future (e.g., if a second communication port or another motor may be added). The reason is clear: if your clients all depend on implicitly being connected to the well-known object, and more than one object is available, the clients will suddenly need to have a way to bind to the correct object. This can have severe implications for the application's programming model. Because of these limitations, I recommend that you generally avoid singletons and instead find ways to share the state of the singleton, instead of the singleton object itself. That said, there are cases when using a singleton is a good idea; for example, class factories are usually implemented as singletons.

10.3.3.2 Singleton object lifecycle

Once a singleton object is created, it should live forever. That presents a problem to the .NET garbage-collection mechanism, because even if no client presently has a reference to the singleton object, the semantics of the singleton activation mode stipulate that the singleton be kept alive so that future clients can connect to it and its state. .NET uses leasing to keep an object in a different process alive, but once the lease expires, .NET disconnects the singleton object from the remoting infrastructure and eventually garbage-collects it. Thus, you need to explicitly provide the singleton with a long enough (or even infinite) lease. The "Leasing and Sponsorship" section in this chapter addresses this issue.

A singleton object shouldn't provide a deterministic mechanism to finalize its state, such as implementing IDisposable. If it's possible to deterministically dispose of a singleton object, it will present you with a problem: once disposed of, the singleton object becomes useless. Furthermore, subsequent client attempts to access or create a new singleton will be channeled to the disposed object. A singleton by its very nature implies that it's acceptable to keep the object alive in memory for a long period of time, and therefore there is no need for deterministic finalization. A singleton object should use only a Finalize( ) method (the C# destructor).

It's important to emphasize again that, in principle, you don't need to cross app domains when using the different activation modes. As long as a proxy is present between the client and the marshal-by-reference object, the client can activate the object as single-call or singleton, even if it's in the same app domain. In practice, however, you're likely to use the server-activated single-call and singleton modes only on remote objects.


10.3.4. Activation Modes and Synchronization

In a distributed application, the hosting domain registers the objects it's willing to expose, and their activation modes, with .NET. Each incoming client call into the host is serviced on a separate thread from the thread pool. That allows the host to serve remote client calls concurrently and maximize throughput. The question is, what effect does this have on the objects' synchronization requirements?

You can use synchronization domains to synchronize access to remote objects, but bear in mind that synchronization domains can't flow across app domains. If a client creates a remote object that requires synchronization, the object will have a new synchronization domain, even if the remote client was already part of a synchronization domain.


10.3.4.1 Client-activated objects and synchronization

Client-activated objects are no different from classic client/server objects with respect to synchronization. If multiple clients share a reference to an object, and the clients can issue calls on multiple threads at the same time, you must provide for synchronization to avoid corrupting the state of the object. As explained in Chapter 8, it would be best if the locking were encapsulated in the component itself, by using either synchronization domains or manual synchronization. The reason is clear: any client-side locking (e.g., via the lock statement) locks only the proxy, not the object itself. Another noteworthy point has to do with thread affinity: because each incoming call can be on a different thread, the client-activated object should not make any assumptions about the thread it's running on and should avoid mechanisms such as thread-relative static members or thread local storage. This is true even if it's always the same client accessing the object and that client always runs on the same thread.

10.3.4.2 Single-call objects and synchronization

In the case of a single-call object, object-state synchronization isn't a problem, because the object's state in memory exists only for the duration of that call and can't be corrupted by other clients. However, synchronization is required when the objects store state between method calls. If you use a database, you have to either explicitly lock the tables or use transactions with the appropriate isolation levels to lock the data. If you use the filesystem, you need to prevent sharing of the files you access while a call is in progress.

10.3.4.3 Singleton objects and synchronization

Unlike client-activated objects, the clients of a singleton object may not even be aware they are actually sharing the same object. As a result, synchronization of a singleton object should be enforced on the object side. You can use either a synchronization domain or manual synchronization, as explained in Chapter 8. Like a client-activated object, a singleton object must avoid thread affinity.



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net