libavg/libavg — pull request #521 by hth- (2014-08-31 09:46:48+00:00)
Title: Bugfixes/set timeout bug

I found, that under a certain condition a callback set by player.setTimeout is
called twice. The bug can be reproduced by the following steps:

1. Add a long running timeout with setTimeout
2. Call a function from an ON_FRAME handler, which:

a. Removes the long running timeout
b. Adds a new timeout. The callback set with this timeout will be called twice.

```python
#!/usr/bin/env python

from __future__ import print_function
from libavg import player
from libavg import app

class MainDiv(app.MainDiv):
def onInit(self):
self.counter = 0
self.calledOnce = False

# long running dummy timeout. Will be removed before the timer elapses.
self.longTimeoutId = player.setTimeout(10000, lambda: print('test timeout'))

def onFrame():
if not self.calledOnce:
self.registerTimeout()
self.calledOnce = True

player.subscribe(player.ON_FRAME, onFrame)

def registerTimeout(self):
def onTimeOut():
self.counter += 1
print('onTimeout called {} times'.format(self.counter))

# Remove the dummy timeout.
player.clearInterval(self.longTimeoutId)
self.longTimeoutId = None

# Now add a new timeout. This timeout will be called twice
callbackId = player.setTimeout(100, onTimeOut)
print('added timeout with id: {}'.format(callbackId))

if __name__ == '__main__':
app.App().run(MainDiv())
```

The reason for this is, that the ON_FRAME signal is emitted from Player::handleTimers while a flag m_bInHandleTimers is set. This flag tells Player::clearInterval that it has been called from handleTimers
and special care needs to be taken not to invalidate handleTimers iterators in case it is called by an currently processed time-out handler (notified by m_bCurrentTimeoutDeleted).

Because the ON_FRAME signal is emitted after all timers have been processed, invalidating iterators is not an issue. Despite that, removing an timer and adding a new one, the latter will be flagged as already deleted and called twice on successive handleTimers calls.

To remedy this problem, m_bCurrentTimeoutDeleted needs be reseted before and not after each timer execution, as it is only of interest for the currently processed timer.