// $Id: Alarm.cc 5197 2006-02-24 20:14:22Z m9710797 $ #include "Alarm.hh" #include #include namespace openmsx { /** * The SDL Timer mechanism is not thread safe. (Shortly) after * SDL_RemoveTimer() has been called it's still possible that the timer * callback is being executed. * * The most likely scenario of this problem is when the callback is being * executed in the timer thread while another thread executes the * Alarm::cancel() method. Even after cancel() returns the timer thread will * still be executing the callback. Ideally cancel() only returns when the * callback is finished. * * The code below tries to minimize the chance that this race occures, but * AFAICS, without a better SDL implementation, it's not possible to completely * eliminate it. So, to be safe write code so that callback can be called even * after it has been canceled. A problem that is more difficult to solve is * deletion of an Alarm object, so try to reuse Alarm objects as much as * possible. */ Alarm::Alarm() : id(NULL), sem(1) { } Alarm::~Alarm() { assert(!pending()); } void Alarm::schedule(unsigned us) { ScopedLock lock(sem); do_cancel(); id = SDL_AddTimer(us / 1000, helper, this); } void Alarm::cancel() { ScopedLock lock(sem); do_cancel(); } void Alarm::do_cancel() { if (id) { SDL_RemoveTimer(static_cast(id)); id = 0; } } bool Alarm::pending() const { ScopedLock lock(sem); return id; } unsigned Alarm::helper(unsigned interval, void* param) { // At this position there is a race condition: // if the timer thread is suspended before the lock is taken and // another thread cancels and deletes this Alarm object, we have a // free memory read when the timer thread continues. // TODO If the other thread just cancels the alarm there is no problem. // (Is this correct???) TODO adjust the documentation at the top Alarm* alarm = static_cast(param); ScopedLock lock(alarm->sem); if (alarm->id) { if (alarm->alarm()) { // reschedule alarm return interval; } alarm->id = 0; } return 0; // cancel timer } } // namespace openmsx