Suppose you have a function that is registered to be called
the next time something gets updated,
and suppose that the notification is a one-shot
notification and
needs to be re-armed each time you want to
wait for the next notification.
(For example, the
RegNotifyChangeKeyValue
function behaves this way.)
Consider the following code fragment:
void onUpdateThing()
{
// get the updated properties of the thing
getThingProperties();
// ask to be called back the next time it updates
registerUpdateCallback(onUpdateThing);
}
mainProgram()
{
// get the thing’s initial properties
// and register for updates
onUpdateThing();
}
There is a race condition here if the thing updates
twice in rapid succession.
On the first update, your onUpdateThing
function is called.
If the second update occurs
while getThingProperties is running,
then your call to
registerUpdateCallback will be too late,
and you will miss the second update.
The solution is to register for the next update before studying the previous one.
void onUpdateThing()
{
// ask to be called back the next time it updates
registerUpdateCallback(onUpdateThing);
// get the updated properties of the thing
getThingProperties();
}
That way, if a second update comes in while you’re studying the first one, your update callback will be called because you already registered it. (I’m assuming you’re only interested in the last update.)
Of course, this assumes that update requests are queued
if the receiving thread is busy.
If updates can be received during the execution of
getThingProperties,
then you will end up in a bad re-entrant situation:
During the processing of one update,
you start processing a new update.
Then when the nested update finishes,
you return to the original update,
which is now actually performing the second half
of the second update.
Suppose your update code wants to keep the colors of two additional objects in sync with the color of the thing:
void getThingProperties()
{
Color currentThingColor = getThingColor();
object1.setColor(currentThingColor);
object2.setColor(currentThingColor);
}
If the setColor method
creates a re-entrancy window, you can have this problem:
- Thing changes color to red.
onUpdateThingbegins.- Register update callback.
getThingPropertiesreads current color as red.getThingPropertiessets object 1’s color to red. ThesetColormethod creates an opportunity for re-entrancy by some means. (For example, it may send a message to another thread, causing inbound sent messages to be processed.)- Thing changes color to blue.
onUpdateThingbegins.- Register update callback.
getThingPropertiesreads current color as blue.getThingPropertiessets object 1’s color to blue.getThingPropertiessets object 2’s color to blue.getThingPropertiesreturns.onUpdateThingreturns.
getThingPropertiessets object 2’s color to red. (Oops.)getThingPropertiesreturns.onUpdateThingreturns.
One solution is to use a sequence number (also known as a change counter) that gets incremented each time the thing changes. If there is only one thread which updates the thing, you can try to update it atomically. For example, if the information is in the registry, you can put all the information into a single registry value or use registry transactions.
If you can associate a change counter with the data, then you can use the following algorithm:
// start with a known invalid value // (If you have multiple listeners, then this naturally // needs to be instance data rather than global.) LONG lLastChange = 0;void onUpdateThing() { bool finished = false; do { // record the most recent change we’ve processed lLastChange = getThingChangeCount();
getThingProperties();
// ask to be called back the next time it updates registerUpdateCallback(onUpdateThing);
// did it change while we were busy? LONG lNewChange = getThingChangeCount();
finished = lLastChange == lNewChange; if (!finished) { // cancel the update callback because we don’t // want to be re-entered unregisterUpdateCallback(onUpdateThing); } } while (!finished); }
Another solution would be to detect the re-entrancy and just remember that there is more work to be done after the previous update finishes.
// 0 = not busy // 1 = busy // 2 = busy, and a change occurred while we were busy // (If you have multiple listeners, then this naturally // needs to be instance data rather than global.) int iBusy = 0;void onUpdateThing() { // ask to be called back the next time it updates registerUpdateCallback(onUpdateThing);
if (iBusy) { iBusy = 2; } else { iBusy = 1; do { getThingProperties(); } while (–iBusy); } }
Note that all of the above examples assume that the
onUpdateThing function
has thread affinity.
0 comments