The following is a simple scenario for some GNOME extension:
Clutter.Actor
.myActor
and adds it:
this.add_child(myActor)
.this._tcFunction()
which in the end does something
with myActor
.Here's where I run into a problem:
We disable (run this.destroy()
) the extension immediately after enabling it.
On disabling, this.destroy()
runs GObject's this.run_dispose()
to
collect garbage. However, if this._tcFunction()
has not finished running,
it'll later try to access myActor
which might have been already
deallocated by this.run_dispose()
.
One way to go about this, is to define a boolean variable in this.destroy()
destroy() {
this._destroying = true
// ...
this.run_dispose;
}
and then add a check in this._tcFunction()
, e.g.
async _tcFunction() {
await this._timeConsumingStuff();
if (this._destroying === true) { return; }
myActor.show();
}
My question: is there a nicer way to deal with these situations? Maybe with Gio.Cancellable()
? AFAIK, there's no easy way to stop an async function in javascript...
Firstly, two things to be aware of:
Avoid calling low-level memory management functions like GObject.run_dispose()
as there are a cases in the C libraries where these objects are being cached for reuse and aren't actually being disposed when you think they are. There is also no dispose signal and other objects may need notification.
Avoid overriding functions that trigger disposal like Clutter.Actor.destroy()
and instead use the destroy signal. GObject signal callback always get the emitting object as the first argument and it is safe to use that in a destroy callback.
There are a couple ways I could think of solving this, but it depends on the situation. If the async function is a GNOME library async function, it probably does have a cancellable argument:
let cancellable = new Gio.Cancellable();
let actor = new Clutter.Actor();
actor.connect('destroy', () => cancellable.cancel());
Gio.File.new_for_path('foo.txt').load_contents_async(cancellable, (file, res) => {
try {
let result = file.load_contents_finish(res);
// This shouldn't be necessary if the operation succeeds (I think)
if (!cancellable.is_cancelled())
log(actor.width);
} catch (e) {
// We know it's not safe
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
log('it was cancelled');
// Probably safe, but let's check
else if (!cancellable.is_cancelled())
log(actor.width);
}
});
// The above function will begin but not finish before the
// cancellable is triggered
actor.destroy();
Of course, you can always use a cancellable with a Promise, or just a regular function/callback pattern:
new Promise((resolve, reject) => {
// Some operation
resolve();
}).then(result => {
// Check the cancellable
if (!cancellable.is_cancelled())
log(actor.width);
});
Another option is to null
out your reference, since you can safely check for that:
let actor = new Clutter.Actor();
actor.connect('destroy', () => {
actor = null;
});
if (actor !== null)
log(actor.width);