Export to GitHub

cocos2d-iphone - issue #1144

Bad access when unscheduling selector in a function which is called by an CCCallFunc action


Posted on Mar 24, 2011 by Quick Panda

What steps will reproduce the problem? 1. add object A to main layer 2. scheduleUpdate for this object 3. start delay + callfunc action on main layer 4. unscheduleUpdate for A in the method called by the callfunc action 5. This will result in bad access in the tick of CCScheduler

What is the expected output? What do you see instead? expected: no crash, I get: crash :)

What cocos2d version / SVN revision are you using ? 1.0.0b

What iPhoneSDK are you using ? 4.2/4.3

Debug or Release ? Both

Does this happens on device ? or on the simulator ? or on both ? both

Please provide any additional information below. A sample derived from CCActionTest is attached

Attachments

Comment #1

Posted on Mar 24, 2011 by Quick Panda

(No comment was entered for this change.)

Comment #2

Posted on Mar 26, 2011 by Helpful Panda

Problem also exists in the 0.99.5 in a different setup see: http://www.cocos2d-iphone.org/forum/topic/14842

Comment #3

Posted on Mar 31, 2011 by Swift Ox

Just did some analysis on this in the 0.99.5 checkout.

What's happening is that entry->impMethod is a faulty value. What's happening is that during one tick of CCScheduler, unscheduleUpdateForTarget is being called for a target that's going to be used next in the DL_FOREACH_SAFE loop.

DL_FOREACH_SAFE actually isn't totally safe. Because it pre-fetches "next" into its third argument, if that "next" is deleted by DL_DELETE (in unscheduleUpdateForTarget) (which maintains the correct "next" pointer) then DL_FOREACH_SAFE still tries to use the old "next", which has been freed.

So DL_FOREACH_SAFE is safe for deleting the current schedule, but not the next schedule.

The workaround is not to call unscheduleUpdate: with a CCCallFuncN but call something which sets a flag, and have [self unscheduleUpdate] called in update().

The fix is... tricky. Since anything could be deleted during the call-out, you can't copy the current node, preload the next pointer, keep a pointer to prev's next pointer, or any other combination I can think of.

Your choices are to either iterate a copy of the linked list, or (like CCTouchDispatcher) delay removes until after the list iteration is complete. (Adds are safe, they will either be skipped this tick, or performed) However, delaying removes might produce the unwanted side-effect of ticking update: one last time after unscheduleUpdate is called.

Comment #4

Posted on Mar 31, 2011 by Quick Panda

I would suggest to handle a list of timer that should be removed after the current tick. Calling unschedule will set a "remove" flag for this timer. In the FOR_EACH_SAFE loop only timers that are not marked as "remove" will be called.

Comment #5

Posted on Apr 1, 2011 by Quick Panda

I send a pull request to Riq which fixes this bug.

Status: Fixed

Labels:
Type-Defect Priority-Medium Milestone-Release1.0