The animation frame timer was re-armed with setTime(), setOneShot() and then setEnabled(true). In the systemd event loop backend setEnabled(true) means SD_EVENT_ON, a repeating source, and setOneShot() had just set SD_EVENT_ONESHOT. The re-armed timer was therefore repeating at a time that had already passed, and the event loop fired it again on every iteration: the main thread of Fcitx5 sat at 100% CPU and typing froze. The first animation was not affected, the timer is only re-armed from the second one on, which is why it showed up as a sudden freeze after typing for a while. setOneShot() alone is correct. tests/timer_rearm.cpp arms a timer twice the way the window does: with the old calls it fires 198725 times in 200 ms, with the fix exactly twice. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
78 lines
3.0 KiB
C++
78 lines
3.0 KiB
C++
// Regression test for the animation frame timer.
|
|
//
|
|
// Re-arming a Fcitx5 time event for one more shot has to be setTime() followed by
|
|
// setOneShot(). Calling setEnabled(true) afterwards switches the source back to
|
|
// SD_EVENT_ON, and with a time that has already passed the event loop fires it
|
|
// again on every iteration, which keeps the main thread at 100% CPU.
|
|
//
|
|
// g++ -std=c++20 tests/timer_rearm.cpp -o /tmp/timer_rearm \
|
|
// $(pkg-config --cflags --libs Fcitx5Utils) && /tmp/timer_rearm
|
|
#include <fcitx-utils/event.h>
|
|
#include <fcitx-utils/log.h>
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <memory>
|
|
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
static uint64_t usecIn(std::chrono::milliseconds ms) {
|
|
return std::chrono::duration_cast<std::chrono::microseconds>(
|
|
(Clock::now() + ms).time_since_epoch())
|
|
.count();
|
|
}
|
|
|
|
// Arms a timer twice, like two animations in a row, and counts how often the
|
|
// callback runs during 200 ms.
|
|
static int run(bool buggy) {
|
|
fcitx::EventLoop loop;
|
|
int fired = 0;
|
|
std::unique_ptr<fcitx::EventSourceTime> timer;
|
|
auto arm = [&] {
|
|
if (!timer) {
|
|
timer = loop.addTimeEvent(CLOCK_MONOTONIC, usecIn(std::chrono::milliseconds(10)), 1000,
|
|
[&](fcitx::EventSourceTime *, uint64_t) {
|
|
fired++;
|
|
return true;
|
|
});
|
|
timer->setOneShot();
|
|
} else {
|
|
timer->setTime(usecIn(std::chrono::milliseconds(10)));
|
|
timer->setOneShot();
|
|
if (buggy) {
|
|
timer->setEnabled(true); // what scheduleFrame used to do
|
|
}
|
|
}
|
|
};
|
|
auto stop = loop.addTimeEvent(CLOCK_MONOTONIC, usecIn(std::chrono::milliseconds(200)), 1000,
|
|
[&](fcitx::EventSourceTime *, uint64_t) {
|
|
loop.exit();
|
|
return true;
|
|
});
|
|
auto second = loop.addTimeEvent(CLOCK_MONOTONIC, usecIn(std::chrono::milliseconds(60)), 1000,
|
|
[&](fcitx::EventSourceTime *, uint64_t) {
|
|
arm(); // the second animation
|
|
return true;
|
|
});
|
|
arm(); // the first animation
|
|
loop.exec();
|
|
return fired;
|
|
}
|
|
|
|
int main() {
|
|
const int buggy = run(true);
|
|
const int fixed = run(false);
|
|
std::printf("old pattern (setTime, setOneShot, setEnabled): %d callbacks in 200 ms\n", buggy);
|
|
std::printf("new pattern (setTime, setOneShot): %d callbacks in 200 ms\n", fixed);
|
|
// Two animations, so two shots are right.
|
|
if (fixed != 2) {
|
|
std::printf("FAIL: expected exactly 2 callbacks\n");
|
|
return 1;
|
|
}
|
|
if (buggy <= 2) {
|
|
std::printf("note: the old pattern did not misbehave on this build\n");
|
|
}
|
|
std::printf("ok\n");
|
|
return 0;
|
|
}
|