Windows

NAVIGATION
CATEGORIES
REFERRENCE
LINKS
  • Interesting multithreading issue: race condition whentriggering an event handler

    11 answers - 1431 bytes - related search similar search Add To My Delicious Add To My Stumble Upon Add To My Google Mark Add To My Facebook Add To My Digg Add To My Reddit

    I've found an interesting multithreading issue which is related not only to
    ICS component but many many other components I've seen so far. It is a race
    condition between two thread where one is triggering an event handler and
    the other is removing (setting to nil) the event handler.
    Consider the following procedure:
    Line1: procedure TMyComponent.TriggerMyEvent(MyArg : Integer);
    Line2: begin
    Line3: if Assigned(MyEvent) then
    Line4: MyEvent(Self, MyArg);
    Line5: end;
    Imagine this is used in a context where the component run in thread #1 and
    where thread #2 change the MyEvent event handler to nil just when thread
    #1 has evaluated the expression at line 3 as true, experienced a context
    switch and come back up and running at line 4. At that time MyEvent is nil
    and you get an exception !
    The solution is to rewrite the procedure as follow:
    Line1: procedure TMyComponent.TriggerMyEvent(MyArg : Integer);
    Line2: var
    Line3: TMyEventProc EventProc;
    Line4: begin
    Line5: EventProc := MyEvent;
    Line6: if Assigned(EventProc) then
    Line7: EventProc(Self, MyArg);
    Line8: end;
    Saving the event pointer in line5 make sure that it is still valid in the
    case a thread switch between Line 6 and 7 occur and the MyEvent is set to
    nil by the other thread.
    Interesting, isn't it ?
    Contribute to the SSL Effort. Visit
  • No.1 | | 1553 bytes | |

    >I've found an interesting multithreading issue which is related not only
    >to
    >ICS component but many many other components I've seen so far. It is a
    >race
    >condition between two thread where one is triggering an event handler and
    >the other is removing (setting to nil) the event handler.
    >[]
    >

    Quite normal. The proper way to disable a handler is not nil'ing it nor
    grabbing and saving pointer to handler proc and then calling it (because
    then the thread1 may do something that thread2 don't want do), but using
    a global, protected by critical section variable.

    Sorry but I don't agree with you. The only proper way to disable an event
    handler is to nilling it. Using a global variable is a very bad design or
    even wrong design: you may have many instances of the same component.
    Protecting using a critical section doesn't solve anything in that case and
    would have a negative impact on performance. It is thread's #2
    responsability to handle the consequences of nilling the event handler. The
    component having an handler nilled can trigger an null pointer exception
    because of that but is not responsible for what happend outside of his own
    code.

    But the best solution is to not nil any handler in another thread, ie. use
    a logic that doesn't require such actions.

    That is not what I call "a best solution". This is what I call a "work
    around".
    The best solution is mine.
  • No.2 | | 1927 bytes | |

    procedure TMyComponent.TriggerMyEvent(MyArg : Integer);
    begin
    EnterCriticalSection(SomeSection);
    if Assigned(MyEvent) then
    MyEvent(Self, MyArg);
    LeaveCriticalSection(SomeSection);
    end;

    Now we're certain that nothing would change MyEvent between the
    "ifthen" and MyEvent call, and old MyEvent won't be triggered even
    if thread2 would nil it in-between ifthen and MyEvent - thread2 will
    nil it AFTER MyEvent occurence.

    Wrong.
    You have to use the same critical section everywhere you assign a value to
    MyEvent. Better write a setter for that purpose.

    >It is thread's #2
    >responsability to handle the consequences of nilling the event handler.
    >The
    >component having an handler nilled can trigger an null pointer exception
    >because of that but is not responsible for what happend outside of his
    >own
    >code.
    >

    True, but what if thread2 nils the event handler AFTER thread1 saves the
    event handler's procedure address? Thread2 may do this because of
    something
    happened - something that requires the event to NT occur.

    That is why I said: "It is thread's #2 responsability to handle the
    consequences".
    The developer using the component has to manage HIS code so that there is no
    race condition. The component developer has to manage HIS code so that there
    is no null pointer exception when the reace condition occur.

    Your solution isn't bad, it simply can cause some confusion if someone
    relies on events not occuring.

    The race condition is caused by the component user, not the component
    developer.
    In my opinion, the responsibilities are:
    1) The component developer has to manage to not crash for race condition
    where the component user can't handle it.
    2) The component user has to manage the race condition.
  • No.3 | | 287 bytes | |

    Hello Francois,
    Line3: if Assigned(MyEvent) then
    Line4: MyEvent(Self, MyArg);
    Very interesting issue. Because almost all components are coded like
    this. Very interesting solution also, clean and simple :)
    Rgds, Wilfried [TeamICS]
    http://www.mestdagh.biz
  • No.4 | | 796 bytes | |

    Francois PIETTE wrote:
    I've found an interesting multithreading issue which is related not
    The solution is to rewrite the procedure as follow:

    Line1: procedure TMyComponent.TriggerMyEvent(MyArg : Integer);
    Line2: var
    Line3: TMyEventProc EventProc;
    Line4: begin
    Line5: EventProc := MyEvent;
    Line6: if Assigned(EventProc) then
    Line7: EventProc(Self, MyArg);
    Line8: end;
    Saving the event pointer in line5 make sure that it is still valid in
    the case a thread switch between Line 6 and 7 occur and the MyEvent
    is set to nil by the other thread.

    Interesting, isn't it ?

    I think it's better/faster than having critical sections for all triggers,
    do you plan to change all ICS triggers accordingly?

    Arno Garrels [TeamICS]
  • No.5 | | 1117 bytes | |

    Arno Garrels wrote:
    Francois PIETTE wrote:
    >I've found an interesting multithreading issue which is related not
    >The solution is to rewrite the procedure as follow:
    >
    >Line1: procedure TMyComponent.TriggerMyEvent(MyArg : Integer);
    >Line2: var
    >Line3: TMyEventProc EventProc;
    >Line4: begin
    >Line5: EventProc := MyEvent;
    >Line6: if Assigned(EventProc) then
    >Line7: EventProc(Self, MyArg);
    >Line8: end;
    >Saving the event pointer in line5 make sure that it is still valid in
    >the case a thread switch between Line 6 and 7 occur and the MyEvent
    >is set to nil by the other thread.
    >
    >Interesting, isn't it ?


    I think it's better/faster than having critical sections for all
    triggers, do you plan to change all ICS triggers accordingly?

    This requires atomic reading/writing of a Pointer (no thread switch
    while read/write), I'm not sure whether this is true. Byte variables
    are written/read in one go, there's no ploblem.

    Arno Garrels [TeamICS]
  • No.6 | | 369 bytes | |

    Hello Arno,

    This requires atomic reading/writing of a Pointer (no thread switch
    while read/write), I'm not sure whether this is true. Byte variables
    are written/read in one go, there's no ploblem.

    As far as I know it is also the case with a 32 bit. So should be no
    problem.

    Rgds, Wilfried [TeamICS]

    http://www.mestdagh.biz
  • No.7 | | 746 bytes | |

    Message
    From: "Wilfried Mestdagh" <wilfried (AT) mestdagh (DOT) biz>
    To: "ICS support mailing" <twsocket (AT) elists (DOT) org>
    Sent: Monday, August 07, 2006 10:40 AM
    Subject: Re: [twsocket] Interesting multithreading issue:
    raceconditionwhentriggering an event handler

    : Hello Arno,
    :
    : This requires atomic reading/writing of a Pointer (no thread switch
    : while read/write), I'm not sure whether this is true. Byte variables
    : are written/read in one go, there's no ploblem.
    :
    : As far as I know it is also the case with a 32 bit. So should be no
    : problem.

    I think it is related with what the machine "word" is. For Athlon 64, it is
    64 bits!

    Regards,

    SZ
  • No.8 | | 442 bytes | |

    This requires atomic reading/writing of a Pointer (no thread switch
    while read/write), I'm not sure whether this is true.

    I think so since pointer are 32 bits and are accessed by only one memory
    cycle. If you look at the Windows API InterlockedXYZ function, there is none
    to access a single 32 bit value. So I guess it is guaranteed to have a 32
    bit access done in one chunk.

    Contribute to the SSL Effort. Visit
  • No.9 | | 1490 bytes | |

    Francois Piette wrote:
    >This requires atomic reading/writing of a Pointer (no thread switch
    >while read/write), I'm not sure whether this is true.


    I think so since pointer are 32 bits and are accessed by only one
    memory cycle. If you look at the Windows API InterlockedXYZ function,
    there is none to access a single 32 bit value. So I guess it is
    guaranteed to have a 32 bit access done in one chunk.

    In order to clear myself up finally I goggled a bit and found this:

    "Simple reads and writes to properly-aligned 32-bit variables are atomic. In other words, when one thread is updating a 32-bit variable, you will not end up with only one portion of the variable updated; all 32 bits are updated in an atomic fashion. However, access is not guaranteed to be synchronized. If two threads are reading and writing from the same variable, you cannot determine if one thread will perform its read operation before the other performs its write operation.
    Simple reads and writes to properly aligned 64-bit variables are atomic on 64-bit Windows. Reads and writes to 64-bit values are not guaranteed to be atomic on 32-bit Windows. Reads and writes to variables of other sizes are not guaranteed to be atomic on any platform."

    But is this also true for Unix S? Some articles I found say that atomicity
    is garanteed up to native int only.

    Arno Garrels [TeamICS]

    Contribute to the SSL Effort. Visit
  • No.10 | | 134 bytes | |

    This raise the following question : Is a field variable in a class aligned
    in memory ? (MyEvent variable is just a field variable).
  • No.11 | | 653 bytes | |

    This raise the following question : Is a field variable in a class
    aligned in memory ? (MyEvent variable is just a field variable).

    I don't know, and it also raises a second question:

    Reads and writes to variables of
    other sizes are not guaranteed to be atomic on any platform."

    Are i.e. reads/writes of Byte-variables not atomic in any Windows
    version, and if yes what versions are they? If that was true for
    32-Bit Win I would have to change plenty of code :(

    Anything shorter than a 32 bit integer and properly aligned is always
    atomic. As a byt can't be split, it is always read/written atomicly.

Re: Interesting multithreading issue: race condition whentriggering an event handler


max 4000 letters.
Your nickname that display:
In order to stop the spam: 0 + 9 =
QUESTION ON "Windows"

EMSDN.COM