Prevent circular refs using idle and timeout manager when running

If an IdleManager or TimeoutManager had been scheduled, it would not get
destroyed until it was executed by the main loop, causing criticals
if the objects enclosed by its callback had been destroyed.

This adds a weak reference to the manager object itself when scheduling
on the main loop, so it can get safely dropped.
This commit is contained in:
Michael Gratton 2019-02-16 15:58:08 +11:00
parent 292498b00f
commit 3cda1b5c6b
4 changed files with 171 additions and 21 deletions

View file

@ -12,8 +12,34 @@ class Geary.TimeoutManagerTest : TestCase {
private const double MILLISECONDS_EPSILON = 0.1;
private class WeakRefTest : GLib.Object {
public TimeoutManager test { get; private set; }
public WeakRefTest() { // Pass in an arg to ensure the closure is non-trivial
string arg = "my hovercraft is full of eels";
this.test = new TimeoutManager.milliseconds(
10, () => {
do_stuff(arg);
}
);
// Pass
this.test.start();
}
private void do_stuff(string arg) {
// This should never get called
assert(false);
}
}
public TimeoutManagerTest() {
base("Geary.TimeoutManagerTest");
add_test("weak_ref", callback_weak_ref);
add_test("start_reset", start_reset);
if (Test.slow()) {
add_test("seconds", seconds);
@ -22,6 +48,25 @@ class Geary.TimeoutManagerTest : TestCase {
}
}
public void callback_weak_ref() throws GLib.Error {
WeakRefTest? owner = new WeakRefTest();
double duration = owner.test.interval;
GLib.WeakRef weak_ref = GLib.WeakRef(owner.test);
// Should make both objects null even though the even loop
// hasn't run and hence the callback hasn't been called.
owner = null;
assert_null(weak_ref.get());
// Pump the loop until the timeout has passed so that the
// callback can get called.
Timer timer = new Timer();
timer.start();
while (timer.elapsed() < (duration / 1000) * 2) {
this.main_loop.iteration(false);
}
}
public void start_reset() throws Error {
TimeoutManager test = new TimeoutManager.seconds(1, () => { /* noop */ });
assert(!test.is_running);