-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GC frees objects and signal handlers #49
Comments
Heya, thanks for the report. It looks like something @Papierkorb would be most qualified to comment on. Stefan do you have some feedback? Thanks! |
I played a little bit with the code. For some reason it is okay to keep only the layout in an instance variable. The button does not have to be. Consider the following example (without the callback, which is a different story): # Variant 5: no callback, no instance variables *CRASHES*
# Don't click the button but somewhere else in widget
class MyWidget < Qt::Widget
def initialize
super
button = Qt::PushButton.new("Press Me")
layout = Qt::VBoxLayout.new
layout.add_widget(button)
self.layout = layout
resize(400, 300)
end
def mouse_release_event(ev)
puts "Click"
GC.collect
end
end This crashed. If we keep the layout in an instance variable, everything works: # Variant 6: no callback, layout in instance variables *WORKS*
# Don't click the button but somewhere else in widget
class MyWidget < Qt::Widget
def initialize
super
button = Qt::PushButton.new("Press Me")
@layout = Qt::VBoxLayout.new
@layout.add_widget(button)
self.layout = @layout
resize(400, 300)
end
def mouse_release_event(ev)
puts "Click"
GC.collect
end
end And to make everything even more confusing: the described problems appear when compiling the test program in "debug" mode. However, variants 4 and 5 do work (without crash) when compiled in "release" mode (i.e. keeping the button and/or the layout in instance variables is no longer necessary, the issue with the callback persists, however). |
Hello. I haven't used Crystal in three years at this point, and considering the effort it'd take me to fix this .. I don't think I'm willing to dedicate so much time anymore to FOSS stuff now or for years to come. Sorry, but I'm out... Though @f-fr is on the right track I think. The pointers/objects passed to Qt seem to vanish from the GC, which'll then do its job and destroy them at some point. Keeping them around in Crystal-land thus works around this issue. Iirc, there's something similar with Qt signals, but honestly I don't remember anymore under which circumstances it's triggered. |
Sure Stefan, no worries. I mean even just your comments/thoughts are valuable & appreciated by everyone. Thanks! |
@f-fr On a side note, did you have to make any other changes in Bindgen or Qt5 bindings to get it to compile & run the test program? If you did, even if they are not polished it would be great to submit them as PRs. Thanks! |
The only thing I had to do is the hint from #48 (removing that one line). |
Unfortunately the PR #53 does not solve this issue. If I understand it correctly, there is another problem: Qt objects that are not created by qt5.cr, i.e. everything allocated somewhere within Qt, typically lives in memory that is not traced by the GC. Hence, if we create some object from crystal, say a button, and add it to some widget, the widget will contain a pointer to that button. But because the widget's data is not traced by the GC, this pointer will not be seen by the GC. If we do not keep the button in an instance variable in Crystal, there will not be a GC-visible reference to the button and it will be deleted. I have no idea if there is a way to make Qt-memory traced by the GC. Otherwise one would need a more complex wrapper object ... |
I worked a little on the memory management issue. You can see the efforts in this branch of bindgen and this branch of qt5.cr. The bindgen commit basically changes every return type and argument type of the generated glue functions from This is then used in the qt5.cr code. The basic idea is as follows: for each class that is part of Qt's parent based memory management (i.e. basically everything inheriting from Everything seems to work quite fine so far. However, there are few rough edges:
app = Qt::Application.new
win = MainWindow.new
win.show
Qt::Application.exec Now if compiled with "--release" then Crystal is too clever and deduces that "app" is not used anymore after the initialization and immediately throws it away. Qt will then complain (when creating the A simple workaround is to access the application object after the app = Qt::Application.new
win = MainWindow.new
win.show
Qt::Application.exec
app.style_sheet # useless but prevents the GC from collecting the application object In C++ one would keep the application object on the stack or behind a The code works in my tests but it is largely untested. I would really appreciate any comments or improvements. |
Or maybe @HertzDevil would have a comment, this could be interesting enough to summon his wisdom :) |
As another step: I added another commit to bindgen (see this branch). This one modifies the generated code for binding to Qt signals. The Crystal proc data is moved to GC traced memory and then passed to the callback lambda bound to the Qt signal. That way the Crystal proc will not be removed by the GC as long as the lambda exists (i.e. as long as it is bound to signal). This should solve the GC issue with signals. The downside is that it requires C++14 (it is certainly possible with C++11 too, but probably a little bit more complicate and requires more changes to bindgen, and maybe it's not worth the effort since C++14 should be pretty standard nowadays). If one wants to test all of these GC-related changes, just use the |
I ran into this same issue, Ill try to give this issue a review this week. Nice job! |
So im currently running into this problem using your branch. going to continue to look into it, but looks like the generator re-defining
|
Well, I didn't change anything at the generated crystal code. If this is is a superclass mismatch ... where is the other definition for this superclass (i.e. there should be another "class Application < ..." somewhere)? Or are there two different definitions for "GuiApplication"? Maybe there are multiple generated binding files (i.e. multiple files of the form "binding_linux-gnu-......cr")? These are the generated Crystal files and, AFAICT, the file "binding.cr" includes everything it could find. Maybe deleting all of them and then rerunning the generator solves the problem. |
I'm thinking this may be a change in crystal 1.1 or 1.2. I did not see any definitions of
and
I think because the |
Getting past that error leads me to a second:
|
Really I don't know. I use crystal 1.2.2 and qt 5.15. Did you start with a fresh install? If you refer to my "integrate" branch of qt5.cr from some fresh project's shard.yml and run "shards update" then it should compile qt5.cr with your system's qt dev files. This is what I do and I get no error of that kind.
|
Consider the following simple example, a window with a single button whose
clicked
signal triggers the GC (in this example explicitly for illustration, but it may run automatically as well and cause the same problem).The program crashes after clicking a few times on the button (3 times in may case). The reason seems to be (if I understand everything correctly) that the GC frees the objects instantiated in the constructor, namely the button, the layout AND the call (i.e. the proc bound to
on_clicked
). Given that Qt is a C++ library with different memory management strategies, this may not be that surprising (although certainly unexpected for a Crystal-dev without knowledge of the C++-Qt-stuff).Anyway, a simple workaround would be to keep a reference to the objects in instance variables (just the code of
initialize
):Unfortunately, this also crashes. It took me some time to find out (because it was not obvious to me): connecting the callback to the
clicked
signal via theon_clicked
method creates an internal object (somewhere in the bindgen generated binding code) and no reference to that object seems to be stored somewhere. The problem is that there does not seem to be a way the get this reference. Therefore I copied the binding code directly:Just to make sure that it is not sufficient to store only the callback, the following variant (which stores the callback but neither the button nor the layout) also crashed (after a few more clicks, 10 or so):
So may main questions is: is there another proper way to work with signals? Did I do something totally wrong? I could not find anything about memory management in docs (but I may have overlooked) ...
I've used the current
master
branch and Qt 5.15 on linux.The text was updated successfully, but these errors were encountered: