facebook/tornado — pull request #396 by justinrosenthal (2011-11-04 06:50:46+00:00)
Title: Prevent duplicate callbacks when PeriodicCallback is stopped and restarted before _next_timeout

Since start() sets _running to True and always adds a new Timeout to the ioloop, any old Timeouts that are waiting for their deadline to occur have effectively been reactivated (when they execute, _running is now True). Both the old and the new Timeouts will schedule successive Timeouts during their callback, so the duplication will continue until the PeriodicCallback is stopped for long enough.

By holding onto the Timeout, it can be actively removed from the ioloop when stop() is called.

Simple repro:

import time
from tornado import ioloop

def print_time():
print int(time.time())

periodic = ioloop.PeriodicCallback(print_time, 5000)
periodic.start()
periodic.stop()
periodic.start()

ioloop.IOLoop.instance().start()