cocos2d / cocos2d-x

Cocos2d-x is a suite of open-source, cross-platform, game-development tools utilized by millions of developers across the globe. Its core has evolved to serve as the foundation for Cocos Creator 1.x & 2.x.
https://www.cocos.com/en/cocos2d-x
18.24k stars 7.06k forks source link

Scheduler - unscheduled callback is called & crash at CCDownloader #17611

Open goleoh opened 7 years ago

goleoh commented 7 years ago

if _elapsed is 2 more times than interval trigger(interval) is called 2 or more times in a tick.

in the same condition if the first callback call unschedule, the successive callback - which is unscheduled - is called.

this happened when we use CCDownloader. the download's job is finished and the all objects are released, but the _onSchedule is called again in the same tick.

and.. yeah, the program was dead.

at CCScheduler.cpp


void TImer::update(float dt)
{
...
    while (_elapsed >= interval)
    {
        trigger(interval);
        _elapsed -= interval;
        _timesExecuted += 1;

        if (!_runForever && _timesExecuted > _repeat)
        {
            cancel();
            break;
        }

        if (_elapsed <= 0.f)
        {
            break;
        }
    }
}
ibaranskyi commented 7 years ago

@goleoh, do you have some maybe temporary fix this crash? We have same crash on iOS platform with cocos 3.13.1 Also we got fix https://github.com/cocos2d/cocos2d-x/commit/67d16bee98015c49c962e783b854436122f540ca but crash is strill reproduced.

toreinkun commented 7 years ago

i add a guard when the Scheduler::_scriptHandlerEntries updating, like CCEventDispatcher.

goleoh commented 7 years ago

@ibaranskyi we replaced the while to the if condition to prevent enter trigger multiple times in the same tick. and you should know there is the same problem if the _useDelay is true.

our game don't need the multiple runs in a tick when the elapsed time is too long than the interval. so we fixed like below.

    // deal with delay
    if (_useDelay)
    {
        if (_elapsed < _delay)
        {
            return;
        }
        trigger(_elapsed);
        _timesExecuted += 1;
        _elapsed = 0;
        _useDelay = false;
        // after delay, the rest time should compare with interval
        if (!_runForever && _timesExecuted > _repeat)
        {    //unschedule timer
            cancel();
            return;
        }
        return;
    }

    // if _interval == 0, should trigger once every frame
    float interval = (_interval > 0) ? _interval : _elapsed;
    if (_elapsed >= interval)
    {
        trigger(_elapsed);
        _timesExecuted += 1;
        _elapsed = 0;

        if (!_runForever && _timesExecuted > _repeat)
        {
            cancel();
        }        
    }