Evil Reentrance

Everyone knows parallel programming is hard, and we talk about it a lot. We talk much less about which I think of as the little brother of parallel programming and also a popular source of bugs in complex systems: reentrance.

Just recently, we discovered a bug in Microsoft Office where mysteriously, one of the events that Office add-ins expect, OnStartupComplete, did not arrive. We could never reproduce the error, and only after one of us disassembled the Office binary around the OnStartupComplete call site, we understood what is going on. Office is iterating quite harmlessly over the collection of add-ins that are stored in a vector-like data structure accessed by index (not iterator). Now it so happens that another Excel add-in dynamically removes items from the add-in list in OnStartupComplete. Removing items from the vector shifts the item indices, resulting in items being skipped. If a skipped item happens to be our add-in, we never receive OnStartupComplete.

Now Microsoft is in the process of deciding what to do. Rewriting the loop to be completely reentrance-proof makes the simple loop much more complex and less efficient, so there is little chance that they will go that route.

Sometimes, though, reentrance can be handled quite gracefully. We talked about it a bit when covering tc::change. Another case is optional. When emplacing into a std::optional<T>, the standard does not specify if the optional is reporting to be engaged or not during the time the constructor of T runs. I think this is an unnecessary complication.

In our code, we have constructors which are also performing tasks which must follow the actual construction but are not really part of it. For example, the representation of a PowerPoint window stores its currently visible slide. After construction, a new window always updates its visible slide for the very first time. So the function to update the visible slide is not only called regularly during the lifetime of a window but also at the end of the constructor. For simplicity, this function should not need to do anything special if called from the constructor, so the state of the window object should already be "constructed".

To handle such situations, our tc::optional reports "engaged" as soon as the constructor starts, "disengaged" as soon as the destructor starts, and has asserts if the constructor or destructor is reentered while another constructor or destructor is still running.

The same logic applies to smart pointers such as unique_ptr or shared_ptr, but we did not need them yet. Feel free to contribute them to our library :-)

— by Arno Schödl

Do you have feedback? Send us a message at devblog@think-cell.com !

Sign up for blog updates

Don't miss out on new posts! Sign up to receive a notification whenever we publish a new article.

Just submit your email address below. Be assured that we will not forward your email address to any third party.

Please refer to our privacy policy on how we protect your personal data.