|
User event source destructors |
Karadoc ~~
Member #2,749
September 2002
|
It just took me ages to work out that my game was crashing because I hadn't called al_destroy_user_event_source before exiting. I found this kind of surprising because my rule of thumb is that destroy only needs to be called on things that allegro actually creates (such as al_create_event_queue). I figured that since allegro only initialised the event source, it didn't need to be destroyed. Anyway I was apparently wrong about that. Neglecting to call al_destroy_user_event_source before the end of the program resulted in a potential crash. (It doesn't crash all programs.) So I was wondering, is there anything else I need to know about this? For example, if I register a user event source with an event queue, do I need to unregister it before destroying the queue? or before destroying the event_source? Does it matter which order I destroy the event sources and the event queue? I don't want any more nasty surprises... ----------- |
Thomas Fjellstrom
Member #476
June 2000
|
Does it still crash if you unregister the event source before exiting? -- |
Karadoc ~~
Member #2,749
September 2002
|
In my tests I was making it crash without registering it in the first place. Here's the simplest program I came up with which produced a crash: 1#include <allegro5/allegro5.h>
2#include <stdio.h>
3
4class Test
5{
6public:
7 ALLEGRO_EVENT_SOURCE source;
8 Test()
9 {
10 al_init_user_event_source(&source);
11 }
12 ~Test()
13 {
14 al_destroy_user_event_source(&source);
15 }
16};
17
18int main(void)
19{
20 if (!al_init())
21 {
22 printf("error\n");
23 }
24 Test t; // if this line is commented out, the program will not crash
25
26 ALLEGRO_EVENT_SOURCE source2;
27 al_init_user_event_source(&source2);
28 //al_destroy_user_event_source(&source2); // if this line is uncommented, the program will not crash
29
30 printf("ok then..\n");
31 return 0; // crashes on exit
32}
To compile, I used "g++ -Wall crash.cpp -lallegro" (g++ is mingw in this case) ----------- |
Thomas Fjellstrom
Member #476
June 2000
|
You probably shouldn't use allegro types in statically allocated class variables like that. But I'm not sure it isn't a bug in allegro. -- |
Matthew Leverton
Supreme Loser
January 1999
|
This is all you need to trigger the crash: int main(void) { al_init(); ALLEGRO_EVENT_SOURCE source2; al_init_user_event_source(&source2); return 0; }
_al_event_source_free (es=0x7fffffffe1e0) at 4.9/src/evtsrc.c:62 62 al_unregister_event_source(*slot, es); Seems like a bug if it's trying to unregister an event source when none were ever registered. Edit: Actually, on second thought, it's not really a bug. It's more of a limitation imposed by the global destructors. The source2 is going out of scope before the global destructor is being called, which is likely leading to garbage data. But I'm not sure that Allegro should register a destructor when al_init_user_event_source is called. It seems that it would be very hard for that behavior to actually ever work properly. Karadoc ~~ said: For example, if I register a user event source with an event queue, do I need to unregister it before destroying the queue? or before destroying the event_source? Does it matter which order I destroy the event sources and the event queue? It doesn't matter. |
Thomas Fjellstrom
Member #476
June 2000
|
Matthew Leverton said: But I'm not sure that Allegro should register a destructor when al_init_user_event_source is called. I'm not sure it would help. I assume the existing global destructor is whats causing the crash, so another global destructor wont help at all. Yeah, I'm not sure theres a proper way around that. Other than to say "always destroy that object before main exits if your user event source struct is allocated statically on the stack". -- |
Matthew Leverton
Supreme Loser
January 1999
|
Thomas Fjellstrom said: I'm not sure it would help. I assume the existing global destructor is whats causing the crash, so another global destructor wont help at all. I'm saying Allegro probably shouldn't even register the one it currently is registering. Even if it's not on the stack, it will probably look like: So in that case, you'd have to manually destroy the event source as well. It seems simpler to remove the destructor and just make the programmer explicitly call it in every case. |
Thomas Fjellstrom
Member #476
June 2000
|
Matthew Leverton said: So in that case, I would suggest not doing that. Obviously if your "object" has some special init needs, you also need to clean up after it. That example there is more user bug than anything. Quote: It seems simpler to remove the destructor and just make the programmer explicitly call it in every case. I don't really have anything against making al_exit() manual. Its not really up to me though. -- |
torhu
Member #2,727
September 2002
|
Thomas Fjellstrom said: Yeah, I'm not sure theres a proper way around that. Other than to say "always destroy that object before main exits if your user event source struct is allocated statically on the stack". "statically on the stack"? You can't have your cake and eat it too |
Matthew Leverton
Supreme Loser
January 1999
|
Thomas Fjellstrom said: Obviously if your "object" has some special init needs, you also need to clean up after it. Of course. My point is simply that the "at exit" destructor doesn't really ever work for the user event sources, and therefore, probably shouldn't ever be registered for the user event sources. Quote: I don't really have anything against making al_exit() manual. I'm only referring to this particular (user event) destructor, not the concept in general. (Although I wouldn't be opposed to removing the implicit shutdown call.) |
ImLeftFooted
Member #3,935
October 2003
|
I don't like manually releasing events -- that's not Allegro style. What's the argument against the old style? ALLEGRO_EVENT_SOURCE *source2 = al_init_user_event_source();
|
Matthew Leverton
Supreme Loser
January 1999
|
See the documentation: struct THING { ALLEGRO_EVENT_SOURCE event_source; int field1; int field2; /* etc. */ }; The idea behind the init style is that you can "extend" the user event structure. Allegro could have both al_init_user_event_source() and al_create_user_event_source(). (I believe the create function is how it used to be.) It would make sense for something initialized with the create style to have a global destructor, but I think it leads to problems with the init version. |
Thomas Fjellstrom
Member #476
June 2000
|
Matthew Leverton said: I'm only referring to this particular (user event) destructor, not the concept in general. (Although I wouldn't be opposed to removing the implicit shutdown call.) Well technically there is only one atexit destructor registered. But I suppose you mean the code in that destructor that goes and cleans up user event sources. -- |
Karadoc ~~
Member #2,749
September 2002
|
Matthew Leverton said: This is all you need to trigger the crash: int main(void) { al_init(); ALLEGRO_EVENT_SOURCE source2; al_init_user_event_source(&source2); return 0; } That's might well trigger the problem that causes the crash, but on my computer that code does not crash (it's the same as my original example with the Test t; line commented out, which I noted does not crash.) I suppose the actual behaviour is a bit unpredictable, as is often the way with these memory bugs. I don't really know what al_destroy_user_event actually does, but from my naïve point of view it seems wrong that I have to call "al_destroy" on something that allegro didn't actually create – and then the thing doesn't even get destroyed anyway, it only get 'deinitialized'. Also, I can't guess what important atexit stuff actually needs to be done for these event sources. Does al_init_user_event_source really do something that can't be undone by simply terminating the program? That seems unlikely to me. I suppose I should look at the allegro code to get a better understanding of what's actually going on; but I get the impression that people more knowledgeable than me are already thinking about it... ----------- |
Matthew Leverton
Supreme Loser
January 1999
|
al_init_user_event_source() dynamically allocates some memory, so you'd get a memory leak if it's not destroyed. The changes I would make to Allegro are:
|
Karadoc ~~
Member #2,749
September 2002
|
If al_destroy_user_event_source is basically just freeing some memory, then I completely agree with your suggested changes. As you pointed out earlier, the automatic destruction of these event sources cannot work anyway because they will always be out of scope by the time they get automatically destroyed (unless they are a global variable on the stack, or a variable on the heap that the user didn't free). This problem of trying to free memory when the pointer has already been lost reminds me of why I made my 'salloc' library ages ago... (a 'safe' alternative to malloc) ----------- |
Peter Wang
Member #23
April 2000
|
It's not only a memory leak. If you free the memory for a user event source without unregistering it from all event queues then you are left with dangling pointers. I agree with Matthew's changes.
|
|