RFC: GC Collection of `MainWindow`
I'm closing https://github.com/hugopl/gtk4.cr/issues/74 in favour of this, as that one is quite cluttered with irrelevant stuff.
If the GC runs before something gets properly referenced in Crystal land, MainWindow will get collected.
Example: (Using GeopJR's Non::Blocking.spawn to make sure the fiber is spawned to a thread unoccupied by GLib.)
class MainWindow < Gtk::ApplicationWindow
def initialize(application : Gtk::Application)
super(application: application)
label = Gtk::Label.new(label: "Will close in 2 seconds! Oh no!")
self.child = label
Non::Blocking.spawn do
sleep 2.seconds
GC.collect
end
end
end
The above will reliable close itself, after the GC collects MainWindow.
A very simple solution would be to create an array and add self to it, so that a reference exists in Crystal land.
KEEP_ALIVE = [] of GObject::Object
class MainWindow < Gtk::ApplicationWindow
def initialize(application : Gtk::Application)
super(application: application)
KEEP_ALIVE << self # MainWindow now has something referencing it from Crystal
label = Gtk::Label.new(label: "Will not close! Yay!")
self.child = label
spawn do
sleep 2.seconds
GC.collect
end
end
end
The above is not necessary if something else, like a signal on one of its children, keeps it alive already.
I don't see an immediate solution here that wouldn't have downsides, and the needed "fix" is very low-effort, so I was thinking about creating a PR to add this note to the github readme. Would that be alright @hugopl? Or do you have something else in mind?
What's the complete code that causes that? Because usually you will have a GApplication instance with a reference to the main window in some variable, so it's never GC'ed.
I mean, window.present should increase the reference count of the window object, then the toggle ref mechanism that disallow Crystal wrappers to be garbage collected if there are more than gobject 1 references to it will take care, at least that was the plan.
Unfortunately last months I don't have time to give more attention to your recent reports, I had twins the my schedule is "a bit" busy, hope you understand.
This is the the entire code:
require "gtk4"
require "non-blocking-spawn"
class MainWindow < Gtk::ApplicationWindow
def initialize(application : Gtk::Application)
super(application: application)
Non::Blocking.spawn do
sleep 2.seconds
GC.collect
end
end
end
class App < Gtk::Application
def initialize
super(application_id: "my.app")
end
@[GObject::Virtual]
def activate
window = active_window.nil? ? MainWindow.new(application: self) : active_window.not_nil!
window.present
end
end
app = App.new
app.run
Unfortunately last months I don't have time to give more attention to your recent reports, I had twins the my schedule is "a bit" busy, hope you understand.
Oh absolutely, do not worry about it all! I only got into more "serious" GTK development a few months ago, which is why some of my earlier issues are super low quality and cluttered. I don't necessarily expect the issues to be solved or even looked at, I'm just opening them so it's documented somewhere 😄
Thanks for the bindings in the first place, and good luck with the twins!!
After looking at the simple hello world example, the issue seems to stem from subclassing.
The following closes when the GC is run.
class MainWindow < Gtk::ApplicationWindow
def initialize(app : Gtk::Application)
super(application: app)
end
end
Non::Blocking.spawn do
sleep 2.seconds
GC.collect
end
app = Gtk::Application.new
app.activate_signal.connect do
window = MainWindow.new(app)
window.present
end
exit(app.run)
However, doing the following does not close, even though they're essentially the same 🤔
- window = MainWindow.new(app)
+ window = Gtk::ApplicationWindow.new(app)