Documentation for Ice 3.5. The latest release is Ice 3.7. Refer to the space directory for other releases.

On this page:

Factories and Collection Operations

The factory we defined earlier is what is known as a pure object factory because create is the only operation it provides. However, it is common for factories to do double duty and also act as collection managers that provide additional operations, such as list and find:

Slice
// ...

sequence<PhoneEntry*> PhoneEntries;

interface PhoneEntryFactory {
    PhoneEntry* create(string name, string phNum)
        throws PhoneEntryExists;

    idempotent PhoneEntry find(string name);
    idempotent PhoneEntries list();
};

find returns a proxy for the phone entry with the given name, and a null proxy if no such entry exists. list returns a sequence that contains the proxies of all existing entries.

Here is a simple implementation of find:

C++
PhoneEntryPrx
PhoneEntryFactory::find(const string& name, const Current& c)
{
    CommunicatorPtr comm = c.adapter?>getCommunicator();

    PhoneEntryPrx pe;
    Identity id = comm?>stringToIdentity(name);
    if (c.adapter?>find(id)) {
        pe = PhoneEntryPrx::uncheckedCast(c.adapter?>createProxy(id));
    }
    return pe;
}

If an entry exists in the Active Servant Map (ASM) for the given name, the code creates a proxy for the corresponding Ice object and returns it. This code works correctly even for threaded servers: because the look?up of the identity in the ASM is atomic, there is no problem with other threads concurrently modifying the ASM (for example, while servicing calls from other clients to create or destroy).

Cyclic Dependencies between Factories and Objects

Unfortunately, implementing list is not as simple because it needs to iterate over the collection of entries, but the object adapter does not provide any iterator for ASM entries.

The reason for this is that, during iteration, the ASM would have to be locked to protect it against concurrent access, but locking the ASM would prevent call dispatch during iteration and easily cause deadlocks.

Therefore, we must maintain our own list of entries inside the factory:

C++
class PhoneEntryFactoryI : public PhoneEntryFactory
{
public:
    // ...

    void remove(const string&, const ObjectAdapterPtr&) {
        IceUtil::Mutex::Lock lock(_namesMutex);

        set<string>::iterator i = _names.find(name);
        if (i != _names.end())
            _names.erase(i);
    }

private:
    IceUtil::Mutex _namesMutex;
    set<string> _names;
};

The idea is to have a set of names of existing entries, and to update that set in create and destroy as appropriate. However, for threaded servers, that raises a concurrency issue: if we have clients that can concurrently call create, destroy, and list, we need to interlock these operations to avoid corrupting the _names set (because STL containers are not thread-safe). This is the purpose of the mutex _namesMutex in the factory: create, destroy, and list can each lock this mutex to ensure exclusive access to the _names set.

Another issue is that our implementation of destroy must update the set of entries that is maintained by the factory. This is the purpose of the remove member function: it removes the specified name from the _names set (of course, under protection of the _namesMutex lock). However, destroy is a method on the PhoneEntryI servant, whereas remove is a method on the factory, so the servant must know how to reach the factory. Because the factory is a singleton, we can fix this by adding a static _factory member to the PhoneEntryI class:

C++
class PhoneEntryI : public PhoneEntry {
public:
    // ...

    static PhoneEntryFactoryIPtr _factory;

private:
    const string _name;
    string _phNum;
};

The code in main then creates the factory and initializes the static member variable, for example:

C++
PersonI::_factory = new PersonFactoryI;

// Add factory to ASM and activate object
// adapter here...

This works, but it leaves a bad taste in our mouth because it sets up a cyclic dependency between the phone entry servants and the factory: the factory knows about the servants, and each servant knows about the factory so it can call remove on the factory. In general, such cyclic dependencies are a bad idea: if nothing else, they make a design harder to understand.

We could remove the cyclic dependency by moving the _names set and its associated mutex into a separate class instance that is referenced from both PhoneEntryFactoryI and PhoneEntryI. That would get rid of the cyclic dependency as far as the C++ type system is concerned but, as we will see later, it would not really help because the factory and its servants turn out to be mutually dependent regardless (because of concurrency issues). So, for the moment, we'll stay with this design and examine better alternatives after we have explored the concurrency issues in more detail.

With this design, we can implement list as follows:

C++
PhoneEntries
PhoneEntryFactoryI::list(const Current& c)
{
    Mutex::Lock lock(_namesMutex);

    CommunicatorPtr comm = c.adapter?>getCommunicator();

    PhoneEntries pe;
    set<string>::const_iterator i;
    for (i = _names.begin(); i != _names.end(); ++i) {
        ObjectPrx o = c.adapter?>createProxy(comm?>stringToIdentity(name));
        pe.push_back(PhoneEntryPrx::uncheckedCast(o));
    }

    return pe;
}

Note that list acquires a lock on the mutex, to prevent concurrent modification of the _names set by create and destroy. In turn, our create implementation now also locks the mutex:

PhoneEntryPrx
PhoneEntryFactory::create(const string& name, const string& phNum, const Current& c)
{
    Mutex::Lock lock(_namesMutex);

    PhoneEntryPrx pe;
    try {
        CommunicatorPtr comm = c.adapter?>getCommunicator();
        PhoneEntryPtr servant = new PhoneEntryI(name, phNum);
        pe = PhoneEntryPrx::uncheckedCast(c.adapter?>add(servant,
                                          comm?>stringToIdentity(name)));
    } catch (const Ice::AlreadyRegisteredException&) {
        throw PhoneEntryExists(name, phNum);
    }
    _names.insert(name);

    return pe;
}

With this implementation, we are safe if create and list run concurrently: only one of the two operations can acquire the lock at a time, so there is no danger of corrupting the _names set.

destroy is now trivial to implement: it simply removes the ASM entry and calls remove on the factory:

C++
void
PhoneEntryI::destroy(const Current& c)
{
    // Note: not quite correct yet.
    c.adapter?>remove(_name);
    _factory?>remove(_name, c.adapter);
}

Note that this is not quite correct because we have not yet considered concurrency issues for destroy. We will consider this issue next.

See Also
  • No labels