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

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

try {

ctrl = CCS::Controller::_narrow(obj);

}

catch (const CORBA::Exception & e) {

cerr < "Cannot narrow controller reference: " < e < endl; throw 0;

}

if (CORBA::is_nil(ctrl)) {

cerr << "Controller reference has wrong type" << endl; throw 0;

}

// Controller reference is ready to use now...

We show the code with full error handling here. If anything unexpected happens, we print an error message and then throw zero. This technique relies on a catch handler higher up in the call chain to terminate the program cleanly or otherwise take corrective action.

Unfortunately, the specification for the Naming Service does not prohibit binding of a nil reference. This means that resolve can return nil without raising an exception.

The preceding code explicitly tests whether resolve returns a nil reference. This technique allows us to distinguish an advertised nil reference from one that is non-nil and fails to narrow to CCS::Controller. We could have omitted the first test for nil, in which case the code would detect a nil reference following the call to _narrow (but would produce an incorrect error message).

18.7 Iterators

To have a complete interface to the Naming Service, it must be possible to list the bindings in a context. The Naming Service uses iterators for that purpose.

18.7.1 The Need for Iterators

Naming contexts provide a list operation that allows you to retrieve the bindings stored in a context (list is analogous to the UNIX ls command). Before we discuss how list is defined, we first examine a more general problem. This problem is not specific to CORBA but occurs in any synchronous RPC system. Here is the problem statement.

Given a remote collection of items, in which the number of items in the collection is potentially unlimited, how can we list the contents of the collection?

This question is deceptively simple, but it raises a number of important design issues. To illustrate this, let us look at a naive version of a list operation on a collection of strings:

typedef sequence<string> StringList;

691

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

interface StringCollection {

StringList list(); // Naive list operation // ...

};

If we invoke the list operation on a string collection, we simply receive all the strings in the collection as a sequence.

At first glance, our definition of list looks sensible, but it contains a problem: what if there is a very large number of strings in the collection? The entire sequence of strings must be buffered in memory during call dispatch, so eventually the number of strings will grow large enough for the operation to fail because of memory limitations.

The general solution to this problem is to create an iterator object for the client. An iterator object allows the client to retrieve results incrementally. Iterators have one of two styles of interface: a pull iterator or a push iterator.

18.7.2 Pull Iterators

Here is a simple version of a pull iterator:

typedef sequence<string> StringList;

interface StringIterator { StringList next(); void destroy();

};

interface StringCollection {

StringList list(out StringIterator it); // Better // ...

};

To read all the strings in the collection, we call the list operation on a StringCollection object. As with the naive version, the operation returns a sequence of strings as the return value.

If all the strings in the collection can fit onto the sequence without causing memory problems, the return value contains the complete collection. In addition, the out parameter it is nil.

If not all the strings in the collection can fit onto the sequence, the return value contains the first batch of strings. In addition, the operation creates an iterator object and returns a reference to the iterator in the parameter it.

The client uses the iterator object to incrementally retrieve the remaining strings by repeatedly calling next on the iterator. Each call to next returns the next batch of

692

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

strings—for example, 100 strings at a time. When the collection is exhausted, next returns an empty sequence to indicate end-of-collection.

This approach solves the problem. If the collection is short enough, all its contents are returned by the initial call to list. If the collection is too large, the server creates an iterator object on behalf of the client, and the client uses the iterator object to retrieve the results. Between calls to next, the iterator object remembers the current reading position in the collection, so it knows which batch of strings to return next.

The destroy operation of the iterator allows the client to inform the server that it no longer wants to use the iterator. The client can call destroy before it has retrieved all of the collection.

Here is example code that shows how a client can iterate over a string collection:

StringCollection_var sc = ...;

 

// Get reference...

StringList_var sl;

 

 

StringIterator_var it;

 

// Get first batch

sl = sc->list(it);

 

CORBA::ULong i;

 

// Show first batch

for (i = 0; i < sl->length(); i++)

cout < sl[i] < endl;

 

 

if (!CORBA::is_nil(it)) {

 

// More to come?

do {

// Get next batch

sl = it->next();

for (i = 0; i < sl->length(); i++) // Show it

cout < sl[i] < endl;

 

 

} while (sl->length() != 0);

 

// Clean up

it->destroy();

 

}

 

 

There are many variations on the iterator IDL we just discussed. (As you will see in Section 18.7.4, iterators for the Naming Service add some additional features.) The general style of interaction is that of a pull iterator because the receiver of the collection (the client) "pulls" the contents from the sender (the server) by invoking an operation.

18.7.3 Push Iterators

For push iterators, the client passes an iterator reference to the server, and the server invokes an operation on the iterator to deliver the contents of the collection. In other words, client and server roles are reversed; the receiver implements the iterator, and the sender of the collection "pushes" the collection into the receiver. Here is how we could define iteration for our string collection using a push model:

typedef sequence<string> StringList; interface StringIterator {

693

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

void next(in StringList sl);

};

interface StringCollection {

StringList list(in StringIterator it); // Push iterator // ...

};

The client implements a StringIterator object and passes a reference to this iterator to the server in the initial call to list. Again, the return value from list is a sequence of strings.

If all the strings in the collection can fit onto the sequence without causing memory problems, the return value contains the complete collection. In addition, the server indicates that all of the sequence was delivered in the first call by invoking the next operation on the iterator, but it passes an empty sequence as the parameter sl to indicate end-of-collection.

If not all the strings in the collection can fit onto the sequence, the return value contains the first batch of strings. The server delivers the remainder of the collection by invoking next on the iterator to deliver the next batch. When all of the collection has been sent this way, the server calls next one more time with an empty sequence to indicate end- of-collection.

Push iterators are an application of the more general Callback pattern (see Section 20.3). However, they are rarely used because they force the client to also act as a server. This requirement complicates development because the client must run an event loop, and (depending on the ORB) the client may also need to be multithreaded to avoid deadlock. In addition, because IIOP is a unidirectional protocol, push iterators require that an extra connection be opened for the calls on the iterator. With a pull model, on the other hand, all interactions can take place over the same single connection. For these reasons, the Naming Service uses pull iterators.

18.7.4 Naming Service Iterators

Here is the IDL used by the Naming Service to give you access to the bindings in a context:

module CosNaming { // ...

enum BindingType { nobject, ncontext };

struct Binding {

Name binding_name; BindingType binding_type; };

typedef sequence<Binding> BindingList;

interface BindingIterator; // Forward declaration

694

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

interface NamingContext { // ...

void list(

in unsigned long how_many, out BindingList bl,

out BindingIterator it

);

};

interface BindingIterator {

boolean next_one(out Binding b); boolean next_n(

in unsigned long how_many, out BindingList bl

);

void destroy();

};

};

list

The list call for the Naming Service follows the pattern for pull iterators. The initial batch of bindings is returned in the out parameter bl, and the out parameter it contains a reference to an iterator if not all bindings can be returned with the first call.

The how_many parameter allows you to specify the maximum number of bindings to be returned with the first call. A call to list is guaranteed to return no more than how_many bindings in the bl parameter. However, it may return fewer because the Naming Service may enforce a limit lower than the one you request with how_many. Setting how_many to zero permits you to retrieve all results via an iterator because it forces the initial result sequence to be empty.

If the call to list returns all the bindings in the context, the it iterator reference is nil. Otherwise, it points at an iterator of type BindingIterator that you can use to retrieve the remaining bindings.

next_n

The next_n operation on the iterator returns the next how_many bindings in the parameter bl. As with list, there may be fewer sequence elements in bl than you requested with how_many because the operation may choose, for example, to never return more than some fixed number of bindings. A value of zero for how_many raises a BAD_PARAM exception.

The return value from next_n tells you whether the bl parameter contains valid bindings. If this call to next_n returned bindings, the return value is TRUE. If this call

695

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

to next_n returned no bindings, the return value is FALSE, and the value of bl is undefined (most likely the returned sequence will have zero elements).

next_one

The next_one operation returns a single binding at a time in the out parameter b. The return value indicates whether b contains a valid binding. If the return value is TRUE, b contains the next binding. If the return value is FALSE, iteration is complete and the value of b is undefined.

We recommend that you do not use next_one because it requires a remote call for every single binding. It is more efficient to use next_n to retrieve bindings in batches of 100 bindings or so. In addition, next_one is redundant because you can achieve the same thing by calling next_n with a how_many value of 1.

destroy

The destroy operation permanently destroys the iterator. You can call destroy at any time even before you have retrieved all bindings from the context. However, you must call destroy eventually even if you retrieve all bindings.

Interpreting a Binding List

As you saw on page 803, iterator operations return a BindingList:

enum BindingType { nobject, ncontext };

struct Binding {

Name binding_name; BindingType binding_type;

};

typedef sequence<Binding> BindingList;

Each binding in the sequence is a pair. The binding_name member of the Binding structure provides the name of the binding, and the binding_type member indicates the type of object denoted by the binding. If the type is ncontext, the object bound with the name is a naming context. If the type is nobject, the object is an ordinary application object (and therefore a leaf in the naming graph).

A binding list contains only names for the bindings immediately contained in the context; it does not contain bindings in subcontexts. For example, listing the app2 context in Figure 18.7 returns only the bindings devices and collections. As a result, the binding_name member of the Binding structure is always a sequence of length 1.

Iterating Over a Naming Context

696

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

The following code example prints all bindings in a context. The logic to iterate over the context is contained in list_context, which prints the bindings contained in the context passed as the nc parameter. show_chunk is a simple helper function that prints the contents of a binding list:

void

show_chunk(const CosNaming::BindingList & bl) // Helper function

{

for (CORBA::ULong i = 0; i < bl.length(); i++) {

cout << bl[i].binding_name[0].id;

if (bl[i].binding_name[0].kind[0] != '\0')

cout << "(" << bl[i].binding_name[0].kind << ")"; if (bl[i].binding_type == CosNaming::ncontext)

cout << ": context" << endl; else

cout << ": reference" << endl;

}

}

void list_context(CosNaming::NamingContext_ptr nc)

{

CosNaming::BindingIterator_var it;

// Iterator reference

CosNaming::BindingList_var bl;

// Binding list

const CORBA::ULong CHUNK = 100;

// Chunk size

nc->list(CHUNK, bl, it);

// Get first chunk

show_chunk(bl);

// Print first chunk

if (!CORBA::is_nil(it)) {

// More bindings?

while (it->next_n(CHUNK, bl))

// Get next chunk

show_chunk(bl);

// Print chunk

it->destroy();

// Clean up

}

 

 

}

This code prints each binding on a separate line. If the kind field is a non-empty string, it is shown in parentheses following the id field. Each line also shows the binding type. Here is some example output:

user(dir): context controller: reference thermostats: context thermometers: context

Only the user binding in this context has a non-empty kind field with value dir, and the remainder of the bindings use the empty string as the kind field. Also note that the output is not sorted—it is up to you to sort bindings for display purposes.

697

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

To minimize the number of remote calls, list_context retrieves bindings in lots of 100. However, it does not rely on receiving exactly 100 bindings with each call. Instead, the length of the binding list is used to control the loop in show_chunk. This technique ensures that the code works correctly even if the Naming Service chooses to return no more than 50 bindings per call.

The second part of list_context is executed only if list returned an iterator. Note that we take care to call destroy on the iterator before returning.

Destroying Iterators

You must explicitly call destroy on an iterator object. If you do not call destroy, the Naming Service has no way of knowing when you are finished with the iterator. Consider a scenario in which a malicious client calls list repeatedly, creating an iterator object with each call, but never calls destroy on these iterators. The Naming Service creates more and more iterators for the client but never gets a chance to destroy them. Eventually, this leads to failure of the service or at least causes performance problems because of excessive memory consumption.

A high-quality implementation of the service will actively take steps to protect itself against this scenario. There are several ways in which a server can avoid running out of memory. For example, the server could place an upper limit on the total number of iterators that may exist at one time and refuse to create more iterators when that limit is exceeded. Alternatively, a server can monitor activity of its iterator objects and destroy any iterators that have not been used for some time.

The CORBA specification does not state exactly how a server should protect itself against "iterator pileup" (or that it must protect itself at all), so you should ask your vendor exactly how the service deals with this scenario. However, as a client to the service, it can happen to you (albeit rarely) that a perfectly good iterator stops working and that a call to next raises OBJECT_NOT_EXIST. In that case, the server probably found itself with too many iterators and destroyed the one you were using.

A high-quality implementation does not indiscriminately destroy iterators. Instead, it destroys those iterators that have been idle for a long time and are therefore likely to be no longer in use. However, a robust client should deal with an OBJECT_NOT_EXIST exception during iteration. The most likely recovery behavior is to restart iteration from the beginning.

This scenario is not limited to the Naming Service. In fact, it can arise whenever a server provides life cycle operations for objects. The problem is caused by the fact that the server creates an object on behalf of the client but relies on the client to eventually destroy the object. (This is similar to allocating memory in the callee and relying on the caller to deallocate it.)

698