Libzypp/Design/Exceptions
From openSUSE
It's about Objects and Exceptions.
Not ResObject, but any C++ Object (Class). An object is constructed, used and (hopefully) destructed when no longer needed. That's nothing new, but think about what actually happens:
//---------------------------------
struct Foo : public Base
{
Foo()
: _data( 0 )
{
// ctor code <=== What happens here?
}
~Foo()
{
// dtor code <=== What happens here?
}
Type _data;
};
Foo f; <=== What happens here?
//---------------------------------
Well, a Foo is created, and once it exists you can use it, until is is deleted.
But look at the ctor code: Does the Foo object already exist here?
The compiler already allocated the space for the Foo, Base classes are initialized, _data members created and initialzed.
Same in the dtor code: Does the Foo object still exist?
There are still data and base classes accessible.
Answer to both questions is:
NO IT DOES NOT! NO IT DOES NOT!
And this is VERY IMORTANT TO REMBER, if you are USING EXCEPTIONS.
Rules
If there are no exceptions, the ctor and dtor code will return, and then the object exists, or it is gone.
Roughly:
An object exist when the LAST CTOR RETURNED.
And is gone as soon as the FIRST DTOR is CALLED.
An uncaught exception is NO RETURN.
NO DTOR is called for OBJECTS which do NOT EXIST.
A try remebers the STACK.
A catch restores the STACK.
Why is this so important to know?
NEVER let an EXCEPTION ESCAPE a DTOR
For more reasons than I can explain, and you want to read. If you dtor executes code that might throw, enclose it in TRY, and CATCH it.
If an EXCEPTION ESCAPEs a CTOR, the OBJECT has NEVER EXISTED!
NO DTOR is CALLED.
So one thing you CAN'T do, is making surrounding objects refer to the object being created, and then throw. The object you tried to create does not exist, never existed!
Making some other object being aware of your presence in the ctor, is the PROMISE that you will exist soon. If you break it by not chatching, or throwing,... ...No, you won't be punished. No one can punish something that never existed. But you will leave a very sad user in front of a very black screen.
SO DO NOT DO THIS!
NOTE: NO DTOR is called for the object your about to create, this is the object in whichs ctor you are. For the Base classes and data members which already exist (inside the not yet existing object), their dtor is called!
This is why it so important, to care about things which must be undone (new/delete, open/close, lock/unlock,...). This is why RAII (Resource Acquisition Is Initialisation) is important and very helpfull.
(see. About exceptions.., posted Dec 07 on zypp-devel; here)
TRY and CATCH operate on the STACK
That's a not minor important thing to remember. And it does not apply to exceptions in ctors only.
Throwing an exception, the stack is unwound up to location of a previous try.
THIS IS NOT UNDO EVERYTHING I DID!
So if you do (or cause) something within an object, which is not correct in case your function throws, use e.g. RAII to undo it. (outside the ctor 'within an object' applies to 'this' as well)
So try to group the actions within a method or function, so that throwing stuff is done first, and once you're sure it will perform, start actually changing the state of objects.
For example, the lazy parsing of Source metadata into ResObjects:
The Source is already created, metadata were downloaded, first access to the ResStore:
Parsing starts, and may throw a parse exception. As we parse lazy (not in the ctor, where the exception would lead to noSource), The Source still exists after someone caught the exception.
What about the ResStore? If the parsers feed directly into the public Store, the Source is unsusable, as the Store offers partial content. This can't be. If parsing fails, the Store must stay empty.
(A SourceImpl task, to enforce this. I'll have a look at this over the weekend.
Exception is not like a boomerang! It won't hit you, if you throw it right. But like a boomerang it's easy to throw it wrong.
> But this ends with: > > <5> [DEFINE_LOGGROUP] Exception.cc(log):83 > YUMSourceImpl.cc(YUMSourceImpl):94 > THROW: YUMSourceImpl.cc(YUMSourceImpl):94: > Cannot read repomd file, cannot initialize source > <5> [DEFINE_LOGGROUP] ReferenceCounted.cc(~ReferenceCounted):37 > ~ReferenceCounted: nonzero reference count >terminate called after throwing an instance of 'std::out_of_range' > what(): ~ReferenceCounted: nonzero reference count
Fortunately Stano found this, so we have time ;) to review and think about the code we wrote. Esp. about exceptions used in Ctor, Dtor, and other methods.
The problem here is that the {{{YUMSourceImpl]]] ctor gives away at least one reference to itself, to some other object. But then decides not to exist by throwing an exception from the ctor.
YUMSourceImpl's base class ReferenceCounted, is already constructed. It exists inside YUMSourceImpl. Thus it's dtor is called. (~YUMSourceImpl is not called as it does not exist).
As some other object already holds a reference on YUMSourceImpl, the reference counter is not zero, as it is expected to be in the dtor of a ReferenceCounted object.
So the YUMSourceImpl ctor must not throw, after having passed a reference to 'this' to the outer world.
And after all this writing, you should be able to spot the 2nd and the 3rd
fundamental bug revealed by these two loglines.
Fortunately, one may say.
Otherwise the calling code would have caught the YUMSourceImpl exception. And at sometime sowhere someone would have accessed the YUMSourceImpl, that never existed. Either the other object itself or the dtor of the reference given to the object (the _Ptr or _Ref).
And you all know how painfull it is, to hunt such a kind of bug.
2nd: ReferenceCounted dtor throws, and this leads to an exception within an exception. This is not catchable and terminates the application.
One of the reasons, why one should not let exceptions escape a dtor.
3rd: Exceptions are catchable, thus the application is able to ignore the error, and may continue. ReferenceCounted has to abort(), because the outstanding references to the deleted object WILL access it from their dtor (to decrease the refcount)
That's an internal error which must abort, so we are able to find it while testing. Too dangerous to stay undetected.
Last edit in Trac '02/04/06 14:01:18' by 'kkaempf'
Last edit in Trac '02/04/06 14:01:18' by 'kkaempf'
Last edit in Trac '02/04/06 14:01:18' by 'kkaempf'
Last edit in Trac '02/04/06 14:01:18' by 'kkaempf'

