- Tricky bug - 14 Updates
- Onwards and upwards - 2 Updates
- Refuting the {Linz, Sipser, Kozen} HP Proofs - 1 Update
Bonita Montero <Bonita.Montero@gmail.com>: Dec 13 10:39AM +0100 I had a nice bug: I've got a base-class with a pure virtual function which is overridden in a derived class. This function is virtually called by a thread from a thread pool (through a static proxy-func- tion). In the destructor of the base-class I wait for all threads from the thread pool to have finished their work and that there are no more items in their work-queue. What I missed was, that as soon as the base-class destructor is called the virtual fuction pointer is adjusted to the pointer of the base-class (I'm wondering that is there any because a base-class with a pure vir- tual function shouldn't have a virtual method table at all). So before I could wait for the threads to finish all work the null pure virtual function got called. ;-) Interestingly this doesn't happen if I don't compile the code as debug -code. So the compiler indirectly gives me a hint here for a possible bug. |
Richard Damon <Richard@Damon-Family.org>: Dec 13 08:05AM -0500 On 12/13/20 4:39 AM, Bonita Montero wrote: > Interestingly this doesn't happen if I don't compile the code as debug > -code. So the compiler indirectly gives me a hint here for a possible > bug. The vtable change is because once you enter the base class destructor, there isn't any derived class to enter anymore (it has been destructed), so the object is now just of the base class. This says either you have an ownership issue with the object, where you are destroying it before all the users are done with it, or the wait in the destructor is surperfilous, as all the threads should be done before you got there. Maybe you need that wait in each of the destructors of the derived classes (maybe calling a method in the base), but even that is technically late as you shouldn't be doing even that if there are still things referring to it. |
Marcel Mueller <news.5.maazl@spamgourmet.org>: Dec 13 02:35PM +0100 Am 13.12.20 um 14:05 schrieb Richard Damon: > The vtable change is because once you enter the base class destructor, > there isn't any derived class to enter anymore (it has been destructed), > so the object is now just of the base class. Yes that is the way C++ initializes and destroys the objects. While formally correct I never found a practical use case for this behavior. In contrast it creates the need for initialization or deinitialization functions that need to be called manually for every class instance. So it is correct but useless. From my point of view a language must be first of all useful. Marcel |
Bonita Montero <Bonita.Montero@gmail.com>: Dec 13 02:39PM +0100 > The vtable change is because once you enter the base class destructor, > there isn't any derived class to enter anymore (it has been destructed), > so the object is now just of the base class. As you can read from my posting I already know that. > classes (maybe calling a method in the base), but even that is > technically late as you shouldn't be doing even that if there are > still things referring to it. I'm using a simple function-pointer now. |
Paavo Helde <myfirstname@osa.pri.ee>: Dec 13 04:00PM +0200 13.12.2020 11:39 Bonita Montero kirjutas: > tual function shouldn't have a virtual method table at all). So before > I could wait for the threads to finish all work the null pure virtual > function got called. ;-) That's what std::shared_ptr is for. By its design you cannot have such problems (either during construction or destruction) if you access your shared object via std::shared_ptr pointers only. Whenever you have a shared_ptr to an object, you can be sure that it points to the most derived class object (because a shared_ptr cannot be constructed before the most derived class object is ready, and the destructor of the most derived class is not evoked while there is a shared_ptr alive). Just stop trying to be too clever for no good, always access your shared objects via shared_ptr, and adjust the rest of the code to cope with that simple rule. That's it, a major source of nasty heisenbugs is gone! |
Juha Nieminen <nospam@thanks.invalid>: Dec 13 02:07PM > derived class object (because a shared_ptr cannot be constructed before > the most derived class object is ready, and the destructor of the most > derived class is not evoked while there is a shared_ptr alive). I don't understand what std::shared_ptr has anything to do with this. The problem was that the destructor of the base class was calling a pure virtual method (something that a good compiler should warn about, but it's not an enforceable error). I don't think std::shared_ptr would have remedied that in any way. When the last std::shared_ptr object is destroyed, it will destroy the managed object, and the problem will happen. std::shared_ptr doesn't somehow magically guard against that problem. |
Bonita Montero <Bonita.Montero@gmail.com>: Dec 13 03:13PM +0100 > The problem was that the destructor of the base class was calling a pure > virtual method (something that a good compiler should warn about, but > it's not an enforceable error). Not the destructor but another function from another thread to the same object. |
Bonita Montero <Bonita.Montero@gmail.com>: Dec 13 03:14PM +0100 > derived class object (because a shared_ptr cannot be constructed before > the most derived class object is ready, and the destructor of the most > derived class is not evoked while there is a shared_ptr alive). shared_ptr<> has nothing to do with my issue. |
Paavo Helde <myfirstname@osa.pri.ee>: Dec 13 04:27PM +0200 13.12.2020 16:07 Juha Nieminen kirjutas: > The problem was that the destructor of the base class was calling a pure > virtual method (something that a good compiler should warn about, but > it's not an enforceable error). If you call a pure virtual method from dtor, you get guaranteed crash each time, not a nasty timings-dependent multithreading bug which Bonita was describing. > When the last std::shared_ptr object is destroyed, it will destroy the > managed object, and the problem will happen. std::shared_ptr doesn't > somehow magically guard against that problem. Yes it does. When you call a member function of an object via std::shared_ptr then it means its destructor has not yet started (neither in this nor in another thread), so there is no danger to call a pure virtual method. |
Bonita Montero <Bonita.Montero@gmail.com>: Dec 13 06:55PM +0100 Here's the current state of my thread-pool-class: // debug_exceptions.h #pragma once #if !defined try_debug #if !defined(NDEBUG) #define try_debug try #else #define try_debug
Subscribe to:
Post Comments (Atom)
|
No comments:
Post a Comment