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
:
// ... 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
:
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:
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:
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:
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:
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:
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.