Endless loop but only with release build

  • Hi,

    The code below works fine in debug builds, and a.f.a.i.k. up to now
    used to work fine also in release builds, but now, in release builds,
    it gets into an endless loop when trying to close the window while
    conversion is ongoing. I don't know whence the sudden change in
    behaviour, but I did find a work around: calling NSLog in the "while
    (_isConverting)" loop causes the loop to correctly exit in release
    builds too. Not just any call or assignment in the loop will result
    in an exit. So far I've found only calling NSLog does.

    Causes endless loop in release build if _isConverting is YES when the
    while statement is reached.
    - (BOOL)windowWillClose:(id)sender
    {
    #pragma unused (sender)
    // Notify other threads that we will close
    _isClosing = YES;

    // Stop observing our sourcePages.
    NSEnumerator *e = [sourcePages objectEnumerator];
    ANSourcePage *sourcePage;
    while (sourcePage = [e nextObject]) {
      [self stopObservingSourcePage:sourcePage];
    }

    // Wait until the converting thread has terminated, if it exists
    while (_isConverting) {
      ;
    }

    return YES;
    }

    With the following change the loop exists when supposed to:
    while (_isConverting) {
      NSLog(@"");
      ;
    }

    Could this be a compiler bug, or is this not a good way to handle
    states across threads? (If I don't wait for _isConverting to become
    NO in windowWillClose then the app may crash when closing a window,
    hence the need for this loop.) Anybody an idea why this code above
    works fine in debug build and loops endlessly in release build?

    XCode 2.4
    MacOS X 10.4.7

    Thanks,
    António

    -----------------------------------------
    Forgiveness is not an occasional act;
    it is a permanent attitude.

    --Martin Luther King, Jr
    -----------------------------------------
  • On Sat, 30 Sep 2006 17:02:18 +0100, Antonio Nunes <antonionunes...>
    said:
    > // Wait until the converting thread has terminated, if it exists
    > while (_isConverting) {
    > ;
    > }
    >
    > Could this be a compiler bug, or is this not a good way to handle
    > states across threads?

    Gosh, I'd say the latter.

    <http://developer.apple.com/documentation/Cocoa/Conceptual/Multithreading/ar
    ticles/CocoaLocks.html
    >

    m.

    --
    matt neuburg, phd = <matt...>, <http://www.tidbits.com/matt/>
    A fool + a tool + an autorelease pool = cool!
    AppleScript: the Definitive Guide - Second Edition!
    <http://www.amazon.com/gp/product/0596102119>
  • On Sep 30, 2006, at 9:02 AM, Antonio Nunes wrote:
    > Could this be a compiler bug, or is this not a good way to handle
    > states across threads? (If I don't wait for _isConverting to become
    > NO in windowWillClose then the app may crash when closing a window,
    > hence the need for this loop.) Anybody an idea why this code above
    > works fine in debug build and loops endlessly in release build?

    The compiler is likely optimizing a loop with no statements in its
    body.  Possibly, a bug, even?

    However, yes, this is not a good way to handle state across
    threads.    It busy-waits the CPU, eating tons and tons of CPU
    spinning madly waiting for something to be done -- and slowing down
    the task that is running in the other thread (and everything else on
    the system).  If this is a Cocoa app and you are busy waiting from
    the main thread, you have also rendered the application completely
    unresponsive.

    For non-Cocoa apps, use a lock.

    For Cocoa, use one of the methods that performs a selector (a method)
    on the main thread to pass notification between the threads.

    b.bum
  • On 30 Sep 2006, at 18:30, Bill Bumgarner wrote:
    > The compiler is likely optimizing a loop with no statements in its
    > body.  Possibly, a bug, even?
    >
    > However, yes, this is not a good way to handle state across
    > threads.    It busy-waits the CPU, eating tons and tons of CPU
    > spinning madly waiting for something to be done -- and slowing down
    > the task that is running in the other thread (and everything else
    > on the system).  If this is a Cocoa app and you are busy waiting
    > from the main thread, you have also rendered the application
    > completely unresponsive.
    >
    > For non-Cocoa apps, use a lock.
    >
    > For Cocoa, use one of the methods that performs a selector (a
    > method) on the main thread to pass notification between the threads.

    Thanks Bill,

    In practice, the loop should end almost immediately, because the task
    in the other thread will check a variable that will cause it to exit,
    so the busy-wait cycle is normally extremely short-lived. This was
    just my rather simplistic way of making sure the secondary thread
    exits before the window closes. But you're probably right that
    compiler optimization is causing the release build to fail.

      I'll implement things more properly the way you suggest. Many
    thanks for the pointers, and thanks to Matt Neuburg for the link to
    the documentation on Cocoa Locks.

    António

    -----------------------------------------
    Forgiveness is the perfume
    that the trampled flower casts
    upon the heel that crushes it.
    -----------------------------------------
  • On Sep 30, 2006, at 12:02 PM, Antonio Nunes wrote:

    > The code below works fine in debug builds

    [...]

    > // Wait until the converting thread has terminated, if it exists
    > while (_isConverting) {
    > ;
    > }

    [...]

    > Could this be a compiler bug

    No. In the release build the compiler is applying optimizations given
    the information at its disposal. It sees that the same value is
    tested over and over again, so arranges to load the value only once
    when optimizations are on. (Look at the disassembly to verify this in
    your Release build.)

    Using the volatlle type qualifier gives the compiler and optimizer
    more information to work with. (But see below.)

    > or is this not a good way to handle states across threads?

    Even if you made changes to get this working, it is still very
    inefficient. You have a busy loop doing nothing but chewing cycles
    waiting for some other task to finish. There is likely a better
    solution, but you aren't going to get a quick or easy answer because
    we don't know the architecture of your program. Writing a multi-
    threaded application correctly is hard. At the very least, you'll
    want to start by reading the documentation Matt pointed you at.

    Jim
  • On Sep 30, 2006, at 11:35 AM, Antonio Nunes wrote:

    > In practice, the loop should end almost immediately, because the
    > task in the other thread will check a variable that will cause it to
    > exit, so the busy-wait cycle is normally extremely short-lived. This
    > was just my rather simplistic way of making sure the secondary
    > thread exits before the window closes. But you're probably right
    > that compiler optimization is causing the release build to fail.

    Regardless, you need to protect your use that shared variable with a
    lock or other synchronization primitive.  Doing so will ensure the
    compiler doesn't optimize it out or keep its value solely in a
    register (which means it won't be shared between the two threads), and
    is also simply correct multithreaded programming practice.

    Ultimately, if you're going to build a multithreaded application, you
    should read up on the producer-consumer pattern and how to use it to
    implement work queues.  Following established and well-understood
    patterns in writing multithreaded code will make it a lot easier to
    ensure your code both performs well and avoids deadlocks or other
    serious and difficult-to-correct threading bugs.  It's also a lot
    easier than winging it...

      -- Chris
  • Chris Hanson wrote:
    > On Sep 30, 2006, at 11:35 AM, Antonio Nunes wrote:
    >
    >> In practice, the loop should end almost immediately, because the task
    >> in the other thread will check a variable that will cause it to exit,
    >> so the busy-wait cycle is normally extremely short-lived. This was
    >> just my rather simplistic way of making sure the secondary thread
    >> exits before the window closes. But you're probably right that
    >> compiler optimization is causing the release build to fail.
    >
    > Regardless, you need to protect your use that shared variable with a
    > lock or other synchronization primitive.  Doing so will ensure the
    > compiler doesn't optimize it out or keep its value solely in a
    > register (which means it won't be shared between the two threads), and
    > is also simply correct multithreaded programming practice.
    The "volatile" keyword on the variable may be enough in this case. (I
    don't remember the OP's question; I'm just reading between the lines.)
    > Ultimately, if you're going to build a multithreaded application, you
    > should read up on the producer-consumer pattern and how to use it to
    > implement work queues.  Following established and well-understood
    > patterns in writing multithreaded code will make it a lot easier to
    > ensure your code both performs well and avoids deadlocks or other
    > serious and difficult-to-correct threading bugs.  It's also a lot
    > easier than winging it...
    I agree. Multithreaded programming is extremely challenging, doubly so
    when you don't know all of the issues. Following preexisting convention
    will give you a solid foundation to work on.
  • On Sep 30, 2006, at 10:16 PM, John Stiles wrote:

    >> Regardless, you need to protect your use that shared variable with
    >> a lock or other synchronization primitive.  Doing so will ensure
    >> the compiler doesn't optimize it out or keep its value solely in a
    >> register (which means it won't be shared between the two threads),
    >> and is also simply correct multithreaded programming practice.
    > The "volatile" keyword on the variable may be enough in this case.
    > (I don't remember the OP's question; I'm just reading between the
    > lines.)

    The "volatile" keyword -- while it may be imbued with a certain
    meaning on certain platforms  -- is not necessarily going to have the
    same meaning across platforms, compilers, and time (e.g. compiler, OS,
    and hardware versions).  Its definition is left quite open in the
    standard relative to what people seem to expect it to mean.

    For example, a platform *could* interpret "volatile" to mean "must
    always store to memory."  But what does that really mean?  After all,
    on some multi-processor platforms that won't be sufficient for
    synchronization.  A store to memory may just write to cache and be
    scheduled by the processor hardware, and not actually go across the
    bus until some point later where it would be snooped by the other
    processors and used to update their own cached values, if any.  Thus
    if your threads happen to be running on different processors on such a
    system, just using "volatile" would *not* cause them to synchronize
    properly.  (I believe the original BeBox -- which used a pair of
    PowerPC 603 CPUs, which didn't have any native multiprocessor support
    -- was just such a platform.)

    This is why it's *always* better to use locks or other synchronization
    primitives such as atomic-increment and atomic-compare-and-set when
    writing multithreaded code.  By using a synchronization primitive,
    you're explicitly stating that you intend for some particular data to
    have the potential to be accessed by multiple flows of control,
    possibly on different processors, and the operating system, libraries,
    compiler implementation, etc. can take appropriate measures on your
    behalf to ensure that this access works as intended.

    If you try to second-guess your runtime environment -- for example, by
    just using "volatile" and hoping that it does the right thing -- you
    *will* end up getting it wrong eventually, because the C memory model
    isn't defined in a way that guarantees you can always get it right in
    all situations.

      -- Chris
  • This is turning into a very interesting and educational discussion. -
    For me anyway. - I'd like to thank everybody for their invaluable
    comments/pointers/advice. I understand now what is going on and why
    the freeze happens, so next on the list is finding a viable solution.

    I've been reading up on locks and threads etc. and looking into
    different ways of solving this, looking for as simple a solution as
    possible.

    I'll explain a bit clearer what is going on when trying to close a
    document window:
    When a user imports a PDF file into my app's document, the app starts
    a helper thread that converts the PDF pages in the background to make
    certain future operations faster. Meanwhile the user has full access
    to the newly loaded document, and almost all functionality is
    available. The conversion process can be lengthy for large PDF files.
    If a user decides to close a document without saving, we no longer
    care for the secondary thread to finish the conversion process, so
    the main thread sets a flag signalling the secondary thread that we
    are closing, causing it to exit the conversion loop on the next
    iteration. The current iteration however may take longer to finish
    than the time it takes the document to close the window and
    deallocate. If that happens the helper thread may crash since it is
    trying to work on data that is no longer valid. Hence the need to
    have the document stick around until the helper thread has exited.

    Trying to keep things simple and to avoid having to set up a
    condition lock, I thought of another way to wait for the desired
    state, blocking the current thread, but without tying up system
    resources: We allow the current thread a short nap.

    while (_isConverting) {
      [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
    }

    From a user experience point of view this code seems quite
    acceptable: this loop is unlikely to execute more than a very few
    times, and it doesn't tie-up the rest of the system, by wasting
    precious processor cycles. Is there anything against this solution?

    If I really should use a condition lock, or any other kind of lock, I
    may need some help learning how to set it up, event hough I've looked
    at code examples and tutorials, including the one on multithreading
    at cocoadevcentral, and some skeleton code showing how to go about it
    would be greatly appreciated.

    All the best,
    António

    -----------------------------------------
    Forgiveness is the perfume
    that the trampled flower casts
    upon the heel that crushes it.
    -----------------------------------------
  • Maybe I don't fully understand your situation here, but couldn't you
    have something like:

    - The secondary thread retains all the data it needs for its operations
    - When asked to close, the main thread sets the flag telling the
    other thread to stop
    - The main thread then closes the document as usual

    This way the interface should always be responsive.  The only
    downside is that parts of your data still take up memory until the
    secondary thread quits, but that shouldn't be an issue because they
    will be deallocated once that thread is done.

    Mike.

    On 1 Oct 2006, at 14:16, Antonio Nunes wrote:

    > This is turning into a very interesting and educational discussion.
    > - For me anyway. - I'd like to thank everybody for their invaluable
    > comments/pointers/advice. I understand now what is going on and why
    > the freeze happens, so next on the list is finding a viable solution.
    >
    > I've been reading up on locks and threads etc. and looking into
    > different ways of solving this, looking for as simple a solution as
    > possible.
    >
    > I'll explain a bit clearer what is going on when trying to close a
    > document window:
    > When a user imports a PDF file into my app's document, the app
    > starts a helper thread that converts the PDF pages in the
    > background to make certain future operations faster. Meanwhile the
    > user has full access to the newly loaded document, and almost all
    > functionality is available. The conversion process can be lengthy
    > for large PDF files. If a user decides to close a document without
    > saving, we no longer care for the secondary thread to finish the
    > conversion process, so the main thread sets a flag signalling the
    > secondary thread that we are closing, causing it to exit the
    > conversion loop on the next iteration. The current iteration
    > however may take longer to finish than the time it takes the
    > document to close the window and deallocate. If that happens the
    > helper thread may crash since it is trying to work on data that is
    > no longer valid. Hence the need to have the document stick around
    > until the helper thread has exited.
    >
    > Trying to keep things simple and to avoid having to set up a
    > condition lock, I thought of another way to wait for the desired
    > state, blocking the current thread, but without tying up system
    > resources: We allow the current thread a short nap.
    >
    > while (_isConverting) {
    > [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:
    > 0.25]];
    > }
    >
    > From a user experience point of view this code seems quite
    > acceptable: this loop is unlikely to execute more than a very few
    > times, and it doesn't tie-up the rest of the system, by wasting
    > precious processor cycles. Is there anything against this solution?
    >
    > If I really should use a condition lock, or any other kind of lock,
    > I may need some help learning how to set it up, event hough I've
    > looked at code examples and tutorials, including the one on
    > multithreading at cocoadevcentral, and some skeleton code showing
    > how to go about it would be greatly appreciated.
    >
    > All the best,
    > António
    >
    > -----------------------------------------
    > Forgiveness is the perfume
    > that the trampled flower casts
    > upon the heel that crushes it.
    > -----------------------------------------
    >
    >
    >
    > _______________________________________________
    > Do not post admin requests to the list. They will be ignored.
    > Cocoa-dev mailing list      (<Cocoa-dev...>)
    > Help/Unsubscribe/Update your Subscription:
    > http://lists.apple.com/mailman/options/cocoa-dev/mike.abdullah%
    > 40gmail.com
    >
    > This email sent to <mike.abdullah...>
  • Well in any case, your secondary thread should really be retaining
    the data it is working, just as good practice anyway :)

    Mike.

    On 2 Oct 2006, at 11:20, Antonio Nunes wrote:

    > On 1 Oct 2006, at 14:23, Mike Abdullah wrote:
    >
    >> Maybe I don't fully understand your situation here, but couldn't
    >> you have something like:
    >>
    >> - The secondary thread retains all the data it needs for its
    >> operations
    >> - When asked to close, the main thread sets the flag telling the
    >> other thread to stop
    >> - The main thread then closes the document as usual
    >
    > Hm, that is how (I thought) I did it originally. Then I discovered
    > there was a snag because of how objects interrelate and what gets
    > deallocated when, but my solution may have been overly complicated.
    > I might indeed just need to retain one extra object at each
    > iteration through the loop. Thanks. I'll give that a go.
    >
    > António
    >
    > -----------------------------------------------------------
    > And you would accept the seasons of your
    > heart, even as you have always accepted
    > the seasons that pass over your field.
    >
    > --Kahlil Gibran
    > -----------------------------------------------------------
    >
    >
    >
previous month september 2006 next month
MTWTFSS
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30  
Go to today