Need advice: Object ownership and threading issue, causing a rare crash.

  • Hello.

    I have two NSObject subclasses -  say MyA and MyB.

    - Each MyA instance. creates and owns a MyB instance.
    - MyB instances create an NSThread, and live their asynchronous life, communicating with a remote internet server, and  sometimes with their owner (the MyA object) who lives in the main thread.

    - It can happen that MyA wants to dispose of its MyB, (in the main thread), and replace it with another instance of MyB.
    - However, MyB cannot be released instantaneously, or synchronously, because its thread should gracefully close and disconnect from the server, which takes long seconds and even more.

    Currently, MyB retains itself within its thread, and releases itself just before the thread exits. I know it is bad practice (self retaining objects), but I did not yet think of a better way.

    This way, when the owner releases its MyB and replaces its reference with a new MyB object, the MyB is just released once, and does not get deallocated. When its thread finishes its cycle in the background, the thread function releases the objects again, and returns.

    This seems to be OK --- but in rare cases the program crashes trying to REENTER the "dealloc" of MyB!!!! It seems that the actual "release" method gets called both from the thread context and the main thread's context.

    What's the best way to get out of this? How can I simply synchronize the "release" calls, without blocking both threads? Maybe I should restrict myself to retaining/releasing in the main thread only? Is there some syntactic Obj-C sugar to ease such things?

    Thanks!

    Motti Shneor, Mac OS X Software Architect & Team Leader
    Spectrum Reflections Ltd.
    +972-54-3136621
  • On 1 May 2013, at 7:10 AM, Motti Shneor <sumac...> wrote:

    > - Each MyA instance. creates and owns a MyB instance.
    > - MyB instances create an NSThread, and live their asynchronous life, communicating with a remote internet server, and  sometimes with their owner (the MyA object) who lives in the main thread.
    >
    > - It can happen that MyA wants to dispose of its MyB, (in the main thread), and replace it with another instance of MyB.
    > - However, MyB cannot be released instantaneously, or synchronously, because its thread should gracefully close and disconnect from the server, which takes long seconds and even more.
    >
    > Currently, MyB retains itself within its thread, and releases itself just before the thread exits. I know it is bad practice (self retaining objects), but I did not yet think of a better way.

    Probably this is a solved problem, and I'm talking through my hat. As a matter of first impression…

    I'd try a broker object. When the MyA is done with the MyB, it hands the MyB over to the broker, which handles the negotiations with the MyB to wind it up, then releases the MyB:

    MyA:
    hand the MyB over to the broker.
    Broker:
      is the MyB already finished? Do nothing and return. Otherwise:
      retain the MyB (by keeping it in a mutable array?)
      tell the MyB that the broker needs a callback when MyB is done (that is, assign the broker as a delegate for this purpose)
      tell the MyB to wind itself up
    release the MyB and replace it with a new one
    MyB:
      complete the windup (which may already have happened)
      tell the broker it's done
    Broker:
      release the MyB

    I tried sketching it out, but I wasn't going to resolve all the locking and threading issues in the time I can give an email.

    If it works at all, it solves the self-release problem, it gives you a one-stop-shopping place to put the asynchrony issues, and I think it handles the irreducible tasks of stopping and disposing of a task the owner no longer wants to deal with.

    — F

    --
    Fritz Anderson
    Xcode 4 Unleashed: 4.5 supplement for free!
    http://www.informit.com/store/xcode-4-unleashed-9780672333279
  • On May 1, 2013, at 5:10 AM, Motti Shneor <sumac...> wrote:

    > Currently, MyB retains itself within its thread, and releases itself just before the thread exits. I know it is bad practice (self retaining objects), but I did not yet think of a better way.
    >
    > This way, when the owner releases its MyB and replaces its reference with a new MyB object, the MyB is just released once, and does not get deallocated. When its thread finishes its cycle in the background, the thread function releases the objects again, and returns.

    That's fine, IMHO, and I've both read and written code that does this.

    —Jens
  • On Wed, May 1, 2013, at 05:10 AM, Motti Shneor wrote:
    > Hello.
    >
    > I have two NSObject subclasses -  say MyA and MyB.
    >
    > - Each MyA instance. creates and owns a MyB instance.
    > - MyB instances create an NSThread, and live their asynchronous life,
    > communicating with a remote internet server, and  sometimes with their
    > owner (the MyA object) who lives in the main thread.

    Dumb question: can you not rely on Cocoa's built-in asynchronous
    networking APIs?

    > What's the best way to get out of this? How can I simply synchronize the
    > "release" calls, without blocking both threads? Maybe I should restrict
    > myself to retaining/releasing in the main thread only? Is there some
    > syntactic Obj-C sugar to ease such things?

    In such a circumstance, I would normally require MyA to use a factory
    method to construct instances of MyB, and have that factory method add
    the MyB instances to an object pool. When MyB operations are cancelled,
    they perform their cleanup and message the thread they were created on
    to remove themselves from the pool. Using dispatch queues should
    simplify this quite a bit.

    --Kyle Sluder
  • Hi and thanks Kyle, Fritz, Jens and everyone else....

    On 2 במאי 2013, at 20:31, Kyle Sluder <kyle...> wrote:

    > On Wed, May 1, 2013, at 05:10 AM, Motti Shneor wrote:
    >> Hello.
    >>
    >> I have two NSObject subclasses -  say MyA and MyB.
    >>
    >> - Each MyA instance. creates and owns a MyB instance.
    >> - MyB instances create an NSThread, and live their asynchronous life,
    >> communicating with a remote internet server, and  sometimes with their
    >> owner (the MyA object) who lives in the main thread.
    >
    > Dumb question: can you not rely on Cocoa's built-in asynchronous
    > networking APIs?

    Unfortunately, MyB wraps in Obj-C a (rather weird) 15 year old C++ socket implementation which is nonstandard in many ways, and therefore cannot use the nicer and more modern asynchronous networking APIs.

    >
    >> What's the best way to get out of this? How can I simply synchronize the
    >> "release" calls, without blocking both threads? Maybe I should restrict
    >> myself to retaining/releasing in the main thread only? Is there some
    >> syntactic Obj-C sugar to ease such things?
    >
    > In such a circumstance, I would normally require MyA to use a factory
    > method to construct instances of MyB, and have that factory method add
    > the MyB instances to an object pool. When MyB operations are cancelled,
    > they perform their cleanup and message the thread they were created on
    > to remove themselves from the pool. Using dispatch queues should
    > simplify this quite a bit.
    >

    That doesn't change the problem much, because now the "object pool" will have the same issues as MyA. The problem is in the multithreaded allocation/deallocation domain.

    However, I think I found a good workaround the problem.

    If MyB calls [self retain] just BEFORE detaching its thread-function, and then calls [self release] AFTER the thread exits, and the thread-function returns, then all retain and release (hence dealloc) calls happen in the main-thread, and thus --- synchronized.

    I think I solved the problem. but now I'll exercise this code heavily, to see it is good.

    Last word --- I wonder why it isn't clearly written for every cocoa API (and especially for the very fundamental initialize/alloc/init/release/dealloc methods whether or not they're reentrant! In a multithreaded environment, that's a reasonable requirement from the documentation.

    Thanks again!

    Motti Shneor, Mac OS X Software Architect & Team Leader
    Spectrum Reflections Ltd.
  • On May 2, 2013, at 11:53 AM, Jens Alfke wrote:

    > On May 1, 2013, at 5:10 AM, Motti Shneor <sumac...> wrote:
    >
    >> Currently, MyB retains itself within its thread, and releases itself just before the thread exits. I know it is bad practice (self retaining objects), but I did not yet think of a better way.
    >>
    >> This way, when the owner releases its MyB and replaces its reference with a new MyB object, the MyB is just released once, and does not get deallocated. When its thread finishes its cycle in the background, the thread function releases the objects again, and returns.
    >
    > That's fine, IMHO, and I've both read and written code that does this.

    I agree.  And Cocoa's -[NSObject performSelectorInBackground:withObject:] and +[NSThread detachNewThreadSelector:toTarget:withObject:] retain the target and argument objects for the lifetime of the thread.  The releases occur on the background thread because there's really no other way.  The same happens with asynchronously dispatched blocks and the objects they retain.

    In other words, what you've done (as you've described it), is not at all unusual.

    If you're concerned about your implementation, you should probably use the Cocoa methods to manage the thread and have it handle memory management for you.

    On May 1, 2013, at 7:10 AM, Motti Shneor wrote:

    > This seems to be OK --- but in rare cases the program crashes trying to REENTER the "dealloc" of MyB!!!! It seems that the actual "release" method gets called both from the thread context and the main thread's context.

    The -[NSObject release] method is thread-safe.  Multi-threaded Cocoa programming would be infeasible otherwise.  You haven't overridden it and implemented your own have you?

    Honestly, I doubt that -dealloc is reentered.  I suspect you've misdiagnosed what is happening or, perhaps, there's some other severe problem such as heap corruption that's leading to that.  It *definitely* won't happen merely because two threads are racing in -release for the same object (assuming they are both entitled to release the object because they each have an ownership stake).

    > What's the best way to get out of this? How can I simply synchronize the "release" calls, without blocking both threads? Maybe I should restrict myself to retaining/releasing in the main thread only? Is there some syntactic Obj-C sugar to ease such things?

    There's no way to do so and no need.

    Regards,
    Ken
  • Hello Ken, and so-many-thanks for the information.

    On 4 במאי 2013, at 07:24, Ken Thomases <ken...> wrote:

    > On May 2, 2013, at 11:53 AM, Jens Alfke wrote:
    >
    >> On May 1, 2013, at 5:10 AM, Motti Shneor <sumac...> wrote:
    >>
    >>> Currently, MyB retains itself within its thread, and releases itself just before the thread exits. I know it is bad practice (self retaining objects), but I did not yet think of a better way.
    >>>
    >>> This way, when the owner releases its MyB and replaces its reference with a new MyB object, the MyB is just released once, and does not get deallocated. When its thread finishes its cycle in the background, the thread function releases the objects again, and returns.
    >>
    >> That's fine, IMHO, and I've both read and written code that does this.
    >
    > I agree.  And Cocoa's -[NSObject performSelectorInBackground:withObject:] and +[NSThread detachNewThreadSelector:toTarget:withObject:] retain the target and argument objects for the lifetime of the thread.  The releases occur on the background thread because there's really no other way.  The same happens with asynchronously dispatched blocks and the objects they retain.

    I wonder how I missed this so-relevant-info until now. Shame on me.... I use +[NSThread detachNewThreadSelector:toTarget:withObject:] and thus I may remove my [self retain] and [self release] altogether!
    >
    >
    > In other words, what you've done (as you've described it), is not at all unusual.

    Actually streamlined with NSThread's usage intent. Makes me feel better about this implementation.

    >
    > If you're concerned about your implementation, you should probably use the Cocoa methods to manage the thread and have it handle memory management for you.
    >

    I do --- that's the weird thing about it. I'm using +[NSThread detachNewThreadSelector:toTarget:withObject:] which does the same thing I intended to do, which is just fine.

    >
    > On May 1, 2013, at 7:10 AM, Motti Shneor wrote:
    >
    >> This seems to be OK --- but in rare cases the program crashes trying to REENTER the "dealloc" of MyB!!!! It seems that the actual "release" method gets called both from the thread context and the main thread's context.
    >
    > The -[NSObject release] method is thread-safe.  Multi-threaded Cocoa programming would be infeasible otherwise.  You haven't overridden it and implemented your own have you?
    >

    Nope. No override of "release" in the whole huge app.

    > Honestly, I doubt that -dealloc is reentered.  I suspect you've misdiagnosed what is happening or, perhaps, there's some other severe problem such as heap corruption that's leading to that.  It *definitely* won't happen merely because two threads are racing in -release for the same object (assuming they are both entitled to release the object because they each have an ownership stake).

    Well, the crash-log provides  a call-stack  which quite clearly shows the "dealloc" re-enrered. Now I'm really puzzled, because if NSThread protected me against premature deallocation on its own, How on earth could I reach the "dealloc" before the thread was done?

    >
    >> What's the best way to get out of this? How can I simply synchronize the "release" calls, without blocking both threads? Maybe I should restrict myself to retaining/releasing in the main thread only? Is there some syntactic Obj-C sugar to ease such things?
    >
    > There's no way to do so and no need.

    OK --- I wont :)

    Thanks again.

    >
    > Regards,
    > Ken
    >

    Motti Shneor
    ----------------------------------------
    Ceterum censeo Microsoftinem delendam esse
  • On May 6, 2013, at 5:26 PM, Motti Shneor wrote:

    > Hello Ken, and so-many-thanks for the information.

    You're welcome.  More below…

    > On 4 במאי 2013, at 07:24, Ken Thomases <ken...> wrote:
    >
    >> Honestly, I doubt that -dealloc is reentered.  I suspect you've misdiagnosed what is happening or, perhaps, there's some other severe problem such as heap corruption that's leading to that.  It *definitely* won't happen merely because two threads are racing in -release for the same object (assuming they are both entitled to release the object because they each have an ownership stake).
    >
    > Well, the crash-log provides  a call-stack  which quite clearly shows the "dealloc" re-enrered. Now I'm really puzzled, because if NSThread protected me against premature deallocation on its own, How on earth could I reach the "dealloc" before the thread was done?

    Let me clarify.  I doubt that -dealloc is reentered *for a given instance*.  It's very possible that one instance of your class held (perhaps through a chain of other objects) the last strong reference to another instance of your class.  So, during its -dealloc, it released that reference, causing the other instance to also deallocate.  That would be consistent with the stack trace you describe.  There would be two frames of -[YourClass dealloc] listed, but they would be for different instances.

    Now, that shouldn't cause a crash in and of itself.

    What does your -dealloc do?

    Also, have you taken the easy steps to verify your memory management?  Use the static analyzer (Build and Analyze) and clean up any problems it identifies (or understand them to the point where you're sure they're false positives).  Run your app under the Zombies instrument.

    Regards,
    Ken
previous month may 2013 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 31    
Go to today