Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Advanced CORBA Programming wit C++ - M. Henning, S. Vinoski.pdf
Скачиваний:
63
Добавлен:
24.05.2014
Размер:
5 Mб
Скачать

IT-SC book: Advanced CORBA® Programming with C++

every single device to be brought into memory by the servant manager up to the point at which we find a match. This presents a worst-case scenario for the Evictor pattern because it causes thrashing.

A better approach is to interrogate the ICP network directly without instantiating a servant for every device. Not only is this more efficient, but it also means that the implementation of find as shown in Chapter 10 does not have to change at all. Instead, the change is confined to the StrFinder function object:

class Controller_impl : public virtual POA_CCS::Controller { public:

//...

private:

//...

class StrFinder {

 

public:

 

StrFinder(

sc,

CCS::Controller::SearchCriterion

const char *

str

) : m_sc(sc), m_str(str) {}

 

bool operator()(

pair<const CCS::AssetType, Thermometer_impl *> & p ) const

{

char buf[32]; switch (m_sc) {

case CCS::Controller::LOCATION:

ICP_get(p.first, "location", buf, sizeof(buf)); break;

case CCS::Controller::MODEL:

ICP_get(p.first, "model", buf, sizeof(buf));

break;

 

 

default:

// Precondition violation

abort();

}

 

 

return strcmp(buf, m_str) == 0;

 

}

 

 

private:

 

m_sc;

CCS::Controller::SearchCriterion

const char *

 

m_str;

};

 

 

};

To search the ICP network directly instead of searching servants, we simply change the implementation of operator().

12.7 Garbage Collection of Servants

In the most general sense, garbage collection is the automatic removal of resources that are no longer in use by a program, without explicit action by the application code. In

515

IT-SC book: Advanced CORBA® Programming with C++

CORBA, garbage collection refers to reclamation of resources that are used by objects that are no longer of interest to clients. Note that we said that garbage collection reclaims the resources used by "objects" instead of using either "CORBA objects" or "servants." For the moment, we avoid using the more precise terms because as you will see in Section 12.8, the distinction between the two is easily blurred.

To help you understand the issues involved, we introduce a simple example that outlines the basic problem.

12.7.1 Dealing with Unexpected Client Behavior

In Sections 12.3 and 12.4, we examine the basic pattern used by clients to create and destroy objects. Specifically, a client invokes a create operation on a factory interface, which returns a reference to a new object to the client. The new object is now ready for use by the client. After the client is finished with the object, the client invokes the remove or destroy operation on the object to destroy it. Here is a simple IDL definition that illustrates this principle:

interface ShortLived { short do_something(); void destroy();

};

interface ShortLivedFactory { ShortLived create();

};

We call the interface ShortLived to indicate that the objects created by the factory are not expected to be used for extended periods. In addition, let us assume for the moment that no persistent state is associated with ShortLived objects, so the server is likely to implement them using the TRANSIENT POA policy. This assumption is not unrealistic; we describe this session-oriented approach on page 528 in Chapter 11. Also, as you will see in Section 18.7, the Factory pattern is frequently used to create transient objects as well as persistent ones.

As long as our clients play the object creation and destruction game by the rules, we do not have a problem. Every call to create is balanced by a corresponding call to destroy, and all objects that are created are eventually destroyed again.

Unfortunately, the rules of this game are dangerous to the server. Every time a client calls create, the server instantiates a servant for the new object and relies on the client to call destroy later. This raises an issue of trust: if a client neglects to call destroy for some reason, the server is left in the situation in which it has an instantiated servant that consumes resources and no way to get rid of it.

There are many reasons that a call to destroy may never happen.

516

IT-SC book: Advanced CORBA® Programming with C++

The client might, from maliciousness or ignorance, neglect to call destroy. A bug in the client might cause it to crash before it can call destroy.

The network between client and server might be disrupted.

A power failure on the client side might prevent the client from ever calling destroy. These are only some of the things that can cause a call to create without a corresponding call to destroy. In a surprisingly short amount of time (possibly only minutes), such problems can cause a server to crash because it runs out of memory or can degrade performance to the point that the server might as well be dead.

What we are looking for is a way for the server to get rid of the unused servants, or to garbage-collect them. We examine a number of techniques to do this in the following sections. For the time being, we restrict ourselves to discussing the garbage collection of servants. In Section 12.8, we turn to the issue of garbage collection of CORBA objects and explain how we might achieve it.

12.7.2 Garbage Collection by Shutting Down

The suggestion may seem naive at first glance, but an entirely viable option can be to get rid of garbage by simply shutting down the server. In fact, this is precisely the strategy used by many production systems to deal with memory leaks. If the leaks are not too serious, it may be sufficient to shut down a server briefly, say at midnight each day, and to restart the server with a clean slate.[3]

[3] We are serious here. We have seen more than one production system employing this strategy. Especially for large systems that have been maintained over years, shutting down once per day can be much more cost-effective than trying to track down and fix all the memory leaks.

For a CORBA server, shutdown may well be a viable option. In particular, as you will see in Section 14.1, most ORBs can automatically activate a server on demand and stop a server after a period of idle time. These features are non-standard, but we might as well take advantage of them if they are available.

If some garbage objects have accumulated in the server but there are periods of idle time in between invocations that are longer than the server's idle time-out, the automatic server shutdown cleans up all the servants for transient objects. (We are tacitly assuming that the server will shut down cleanly and properly destroy its servants. Simply exiting is not an option in environments such as embedded systems or Windows 98, where the operating system does not guarantee to clean up after a process.)

Shutting down the server may not be an option because some servers simply cannot be switched off even for brief periods. In addition, if clients present the server with a continuous work load, there may never be an idle period that is long enough for the server's idle time-out to trigger, so we need better solutions.

12.7.3 Using the Evictor Pattern for Garbage Collection

517

IT-SC book: Advanced CORBA® Programming with C++

The Evictor pattern we discuss in Section 12.6 can make an effective garbage collector. The Evictor pattern takes care of automatically disposing of unused servants. If servants are created and lost by clients, no more invocations arrive for these servants. This means that, quite quickly, unused servants migrate to the head of the evictor queue, where they are reaped when an invocation arrives for a servant that is not yet in memory.

Using the Evictor pattern, the worst case is that all the servants on the evictor queue are garbage and therefore consume memory that may be better used elsewhere. However, we can typically afford this, because we would be consuming the same amount of memory if all these servants were still in use.

Before we continue discussing other options for garbage collection, we strongly recommend that you give serious consideration to using the Evictor pattern for garbage collection of servants. The Evictor pattern, with minor variations, is the only reliable option we are aware of that is easy to implement and non-intrusive. The techniques that follow either are more difficult to design and implement correctly, or they pollute the IDL interfaces with garbage collection operations.

12.7.4 Using Time-Outs for Garbage Collection

Another way to get rid of unused servants is to equip each servant with a timer. When the client creates a new object, it can specify a time-out value via a parameter to the factory operation, or, alternatively, the server can assign a default time-out value. The servant implementation in the server resets each servant's timer whenever a client invokes an operation. (Doing this is especially easy if we use a servant locator's preinvoke operation to reset the timer.) When a servant's timer expires, the servant commits suicide.

Time-outs are quite similar to the Evictor pattern. In both cases, the server applies a heuristic to determine when a servant should be destroyed. In the case of the Evictor pattern, the heuristic is the expected frequency with which new servants are activated, which determines how long it will take on average for an unused servant to get pushed off the end of the evictor queue. With time-outs, the heuristic is simply the amount of time that must elapse before a servant is considered stale and is reaped.

The time-out approach shares some problems with the Evictor pattern. In particular, choosing an appropriate time-out value can be difficult. Allowing the client to select the time-out value is dangerous because clients are likely to play it safe and to select a long time-out. Assigning a default time-out in the server can also be difficult because the server often has little idea of the behavior patterns of its clients. If the time-out is too long, too many garbage servants may accumulate, whereas if the time-out is too short, the server can end up destroying a servant that is still in use by a client.

Apart from the problems shared with the Evictor pattern, time-outs add their own problems. Time-outs can be delivered to a servant asynchronously in the form of a signal or other interrupt-like mechanism, or the server can ask for time-outs synchronously by calling an API call that delivers expired timers. Neither approach is ideal.

518

IT-SC book: Advanced CORBA® Programming with C++

Asynchronous timers have the habit of going off at the most inopportune moments. Often, at the time the signal arrives, the server is not in a state in which it can react to the signal immediately and reap the servant whose timer has expired. In that case, the signal handler must deposit the expired timer information in a global data structure that can be checked later for expired timers. Implementing the required logic can be quite difficult.

Synchronous timers require the server to explicitly check to collect the expired timer information. However, if a server is single-threaded, it may not be able to check. For example, if the server uses the blocking ORB::run operation, an extended period of idle time will cause the server to go to sleep in the ORB's event loop. During that time, there is no way for the server to invoke an API call because its only thread of control is blocked in the event loop. The server could instead write its own event loop that polls the ORB for work items using ORB::work_pending and ORB::perform_work and polls its timers whenever the ORB is not busy, but writing and maintaining these loops can be tedious.

Depending on their implementation, timers can be quite heavyweight. In addition, the number of available timers is often severely limited by the underlying OS, so there may not be enough timers for all servants that need them.

The timer approach is best suited for servers that are multithreaded. In that case, we can use a separate reaper thread to clean up servants. The reaper thread can block until a timer expires, synchronously clean up the expired servant, and go back to sleep until the next timer expires. On the other hand, if a server is not multithreaded, the Evictor pattern is typically easier to use as a garbage collector than are time-outs.

12.7.5 Explicit Keep-Alive

We can make clients responsible for keeping servants alive by adding a ping operation to each interface. By default, the servant will be garbage-collected after some period of idle time. If the client does not want to invoke ordinary operations on the servant for some time but still wants to ensure that the servant is not reaped by the server, the client must call the ping operation to reset the servant's timer.

Because this approach uses timers, it suffers all the drawbacks of the pure timer approach. In addition, it makes the presence of garbage collection visible to the client and requires the client to actively participate. This pollutes IDL interfaces with operations that have nothing to do with the interfaces' logical functions. In addition, requiring the client to call a ping operation simply shifts the problem from server to client without solving it. Having to call a ping operation periodically may be just as inconvenient to the client as polling a timer would be to the server.

12.7.6 Reverse Keep-Alive per Object

Reverse keep-alive requires the client to pass a callback object to the server. For example:

interface ShortLived {

519

IT-SC book: Advanced CORBA® Programming with C++

short do_something(); void destroy();

};

interface KeepAlive {

void ping(in ShortLived obj);

};

interface ShortLivedFactory {

ShortLived create(in KeepAlive cb_obj);

};

When the client creates an object, it must also supply a reference to a KeepAlive object to the server. The KeepAlive object is implemented by the client, and the server periodically invokes the ping operation to see whether the client still wants the object. If the ping from server to client fails—for example, with OBJECT_NOT_EXIST—the server reaps the corresponding servant.

Although initially attractive, this technique has a number of serious drawbacks.

The client must somehow maintain the association between its KeepAlive objects and the references returned by the factory because there are as many KeepAlive objects in the client as there are ShortLived objects in the server. The client must deliberately fail the server's ping operation if it no longer wants to use the corresponding ShortLived object—for example, by destroying its own KeepAlive object. However, garbage objects are often created when the client forgets to call destroy and not just because of network failure. If the client forgets to call destroy, then it presumably will also forget to destroy its KeepAlive object, so the problem we have now may in fact be worse then the original one.

The keep-alive technique doubles the number of objects in the system because each ShortLived object created in the server is paired with its corresponding KeepAlive object in the client. Doubling the number of objects in the system may be too expensive in terms of resources such as memory and network connections.

For the client to offer a callback object to the server, the client must act as a server for the duration of the callback. This complicates the implementation of the client because, at the very least, the client must have a POA and run an event loop. Depending on the features provided by the client's ORB, this may require the client to be multithreaded. It is not reasonable to expect clients to add multithreading to their implementation just so that they can respond to callbacks.

The server is burdened by the need to invoke callbacks on the KeepAlive objects in the client. This not only adds to the complexity of the server but also adds networking overhead. If the number of objects in the system is large or if the time-out interval between keep-alive callbacks is too short, a substantial amount of network bandwidth can

520

IT-SC book: Advanced CORBA® Programming with C++

be lost because of excessive callback traffic. In general, callback-based approaches suffer from a number of scalability problems, as we describe in Chapter 20.

Overall, reverse keep-alive per object adds a lot of complexity to our system without really solving the problem.

12.7.7 Per-Client Reverse Keep-Alive

Per-client reverse keep-alive modifies the preceding idea by having only a single KeepAlive object in the client for all objects created by the client. The server still calls back, but it no longer attempts to detect abandoned individual objects. Instead, if an invocation of the ping operation fails, the server simply destroys all servants created by the client.

This technique helps to detect failures in which the client has crashed and is therefore unable to destroy the objects it has created. However, the approach suffers from two major problems.

Often, objects are leaked because the clients forget to call destroy rather than because the client or the network has crashed. However, the per-client keep-alive approach does not detect objects that are leaked while the client is still able to respond to the callback from the server.

The approach requires a single KeepAlive object to be passed from client to server, but it is difficult to actually achieve this. To maintain a one-to-one association between client and server, they both must establish some form of session concept. However, that goes against the CORBA object model, which does its best to hide from clients how objects are distributed over servers. In other words, the requirement for a one-to-one correspondence destroys server transparency.

12.7.8 Detecting Client Disconnection

Some ORBs provide proprietary extensions that allow the server code to detect when a connection from a client goes down. The server application code can use this as a trigger to destroy the servants the client created. Detecting client disconnects also creates a number of problems.

As you will see in Chapter 13, IIOP does not allow the server-side run time to distinguish orderly from disorderly disconnection. A client is free to close its connection to a server at any time and to reopen that connection later. Consequently, the server cannot assume that a disconnected client is no longer interested in the objects it has created unless the server also makes assumptions about the client's connection management strategy. Any such assumptions are outside the guarantees provided by the CORBA specification.

521

IT-SC book: Advanced CORBA® Programming with C++

To clean up the client's objects on disconnect, the server must know which objects were created by which client and on which connection. This in itself can be a difficult problem if the number of clients and objects is large. In addition, any solution is necessarily proprietary because CORBA does not standardize API calls that would allow the server to detect on which connection it receives an incoming request.

As with the reverse keep-alive per-client approach, detecting disconnect works only if the client actually disconnects. However, it does not work if the client remains connected and continually leaks objects because of a bug.

In summary, detecting disconnects can be useful in limited circumstances, but it does not solve the general garbage collection problem and requires proprietary APIs.

12.7.9 Distributed Reference Counts

CORBA uses the duplicate and release operations to keep track of how many object reference instances denote the same proxy in an address space. It is tempting to extend this idea to the distributed case and to reference-count servants. We can achieve this by creating a RefCountBase interface such as the following:

interface RefCountBase { void increment(); void decrement();

};

The idea is that objects that should be reference-counted inherit from this base interface. The server sets the reference count to 1 when it creates a new object and passes the object reference to the client. The client calls decrement when it is finished with the object. If a server passes a reference to the same object to another client, the server increments the reference count again and expects the second client to call decrement after it is finished with the object. The server destroys the object after all the clients have called decrement, and the reference count drops to zero.

Reference counting looks attractive because it permits an object to be shared among a number of clients. However, we again face serious problems.

If a client crashes before it gets around to calling decrement, the reference count is left too high and will never drop to zero. In other words, crashing clients cause the same problems with reference counting as they do with a normal destroy operation.

Reference counting is error-prone because a single missed call to decrement by a client permanently prevents deletion of the servant, and too many calls to decrement causes premature destruction.

Reference counting is intrusive to the IDL interfaces and requires explicit cooperation from clients. We could create helper classes similar to _var types to make distributed

522