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

Here is a very simple implementation of our PhoneEntryI servant. (Methods are inlined for convenience only. Also note that, for the time being, this code ignores concurrency issues, which we return to in Life Cycle and Normal Operations.)

C++
class PhoneEntryI : public PhoneEntry {
public:
    PhoneEntryI(const string& name, const string& phNum)
        : _name(name), _phNum(phNum)
    {
    }

    virtual string
    name(const Current&) {
        return _name;
    }

    virtual string
    getNumber(const Current&) {
        return _phNum;
    }

    virtual void
    setNumber(const string& phNum, const Current&) {
        _phNum = phNum;
    }

    virtual void
    destroy(const Current& c) {
        try {
           c.adapter?>remove(c.id);
        } catch (const Ice::NotRegisteredException&)
           throw Ice::ObjectNotExistException(__FILE__, __LINE__);
        }
    }

private:
    const string _name;
    string _phNum;
};

With this servant, destroy does just the right thing: it calls delete on the servant once the servant is idle, which in turn calls the destructor, so the memory used by the _name and _phNum data members is reclaimed.

However, real servants are rarely this simple. In particular, destruction of an Ice object may involve non-trivial actions, such as flushing a file, committing a transaction, making a remote call on another object, or updating a hardware device. For example, instead of storing the details of a phone entry in member variables, the servant could be implemented to store the details in a file; in that case, destroying the Ice object would require closing the file. Seeing that the Ice run time calls the destructor of a servant only once the servant becomes idle, the destructor would appear to be an ideal place to perform such actions, for example:

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

    ~PhoneEntryI()
    {
        _myStream.close(); // Bad idea
    }

private:
    fstream _myStream;
};

The problem with this code is that it can fail, for example, if the file system is full and buffered data cannot be written to the file. Such clean-up failure is a general issue for non-trivial servants: for example, a transaction can fail to commit, a remote call can fail if the network goes down, or a hardware device can be temporarily unresponsive.

If we encounter such a failure, we have a serious problem: we cannot inform the client of the error because, as far as the client is concerned, the destroy call completed just fine. The client will therefore assume that the Ice object was correctly destroyed. However, the system is now in an inconsistent state: the Ice object was destroyed (because its ASM entry was removed), but the object's state still exists (possibly with incorrect values), which can cause errors later.

Another reason for avoiding such state clean-up in C++ destructors is that destructors cannot throw exceptions: if they do, and do so in the process of being called during unwinding of the stack due to some other exception, the program goes directly to terminate and does not pass "Go". (There are a few exotic cases in which it is possible to throw from a destructor and get away with it but, in general, is an excellent idea to maintain the no-throw guarantee for destructors.) So, if anything goes wrong during destruction, we are in a tight spot: we are forced to swallow any exception that might be encountered by the destructor, and the best we can do is log the error, but not report it to the client.

Finally, using destructors to clean up servant state does not port well to languages such as Java and C#. For these languages, similar considerations apply to error reporting from a finalizer and, with Java, finalizers may not run at all. Therefore, we recommend that you perform any clean-up actions in the body of destroy instead of delaying clean-up until the servant's destructor runs.

Note that the foregoing does not mean that you cannot reclaim servant resources in destructors; after all, that is what destructors are for. But it does mean that you should not try to reclaim resources from a destructor if the attempt can fail (such as deleting records in an external system as opposed to, for example, deallocating memory or adjusting the value of variables in your program).

See Also
  • No labels