Skip to content
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

Fixing memory leaks. #2618

Closed

Conversation

pgScorpio
Copy link
Contributor

Short description of changes
Moved pointers to with new assigned objects outside main and added a
cleanup function that is called before every exit to delete those objects again.

CHANGELOG:
Fixed some memory leaks.

Context: Fixes an issue?
Fixes #2614

Does this change need documentation? What needs to be documented and how?
No documentation changes

Status of this Pull Request
Bugfix

What is missing until this pull request can be merged?
Ready to merge.

Checklist

  • I've verified that this Pull Request follows the general code principles
  • I tested my code and it does what I want
  • My code follows the style guide
  • [-] I waited some time after this Pull Request was opened and all GitHub checks completed without errors.
  • I've filled all the content above

Moved pointers to with new assigned objects outside main and added a
cleanup function that is called before every exit to delete those objects again.
@ann0see
Copy link
Member

ann0see commented Apr 26, 2022

@dtinth could you please Review?

@hoffie hoffie added this to the Release 3.9.0 milestone Apr 26, 2022
@ann0see
Copy link
Member

ann0see commented Apr 26, 2022

@pgScorpio thank you again ;-)

Copy link
Member

@ann0see ann0see left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if that's the standard C++ clean up way? It looks a bit strange to me (but I have a Java and PHP background)

src/main.cpp Outdated
@@ -999,15 +1044,21 @@ int main ( int argc, char** argv )
#endif
{
qCritical() << qUtf8Printable ( QString ( "%1: %2" ).arg ( APP_NAME ).arg ( generr.GetErrorText() ) );
exit ( 1 );

if ( exit_code == 0 )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks a bit odd in my eyes...

Copy link
Contributor Author

@pgScorpio pgScorpio Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What looks strange to you ?

This is the "normal" way of exception handling in main.
catch the exception, set the exit code, and exit main normally with that exit code....

Though what looks strange to me is that the try block covers only part of the main() code. and that there are so many exit() calls. The "normal" way to exit from main would be to throw an exception. In that case the cleanup can be done just before exiting main. And that would be the better solution, but that would also have a lot more code changed....
Of course I can do that implementation too if you want that ;=)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole handling here - as you said. Mostly the "if exit_code == 0 then set exit code to 1" thing. I'd really prefer the exit ( ) function to be extended with cleanup() but I assume exit() is not a method of a class, so you'd write a function calling exit instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. exit() is a C/C++ library function, and is not the best way to end the program when using exceptions. In that case throwing special exceptions to exit would be the better way.

@pgScorpio
Copy link
Contributor Author

Not sure if that's the standard C++ clean up way? It looks a bit strange to me (but I have a Java and PHP background)

Well.... what's the standard C++ cleanup way ?
The only "standard" is that every function should delete all allocated data before exiting. This is best achieved by using class instances, where the destructor cleans up everything when the instance goes out of scope. Unfortunately, because of the way things are implemented, there is no clear scope here.

src/main.cpp Outdated
@@ -768,9 +811,9 @@ int main ( int argc, char** argv )
bIsClient = true; // Client only - TODO: maybe a switch in interface to change to server?

// bUseMultithreading = true;
QApplication* pApp = new QApplication ( argc, argv );
pApp = new QApplication ( argc, argv );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, for iOS apps this used to be of type QApplication instead of QCoreApplication. Can you give a quick explanation why this is safe to do?

It's probably hard or artificial to put that into an individual commit, but at least the commit description should mention that this was intentional and why it is considered safe (I guess because iOS doesn't use any methods from the broader interface either?).

Copy link
Contributor Author

@pgScorpio pgScorpio Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

I didn't change the type, I just moved the pointer declaration outside main() to be able to implement the cleanup() function. For IOS it is still QApplication:

#ifdef HEADLESS
    QCoreApplication* pApp = new QCoreApplication ( argc, argv );
#else
#    if defined( Q_OS_IOS )
    bUseGUI        = true;
    bIsClient      = true; // Client only - TODO: maybe a switch in interface to change to server?

    // bUseMultithreading = true;
    pApp = new QApplication ( argc, argv );
#    else
    pApp = bUseGUI ? new QApplication ( argc, argv ) : new QCoreApplication ( argc, argv );
#    endif
#endif

EDIT: Hmmmm, but now I see I also missed something in headless mode ;=) So thanks !

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I understand the reason for moving. I just wanted to hint at the side effect (maybe it's a noop, but I'm not sure):
pApp was previously either of type QApplication or QCoreApplication based on platform and/or GUI/non-GUI.
With the new code, it's always a QCoreApplication which is probably the lowest common denominator. It might be fine, but it's still a change for iOS, isn't it?

Copy link
Contributor Author

@pgScorpio pgScorpio Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still wondering why you think anything changed for IOS, AFAIK It was, and still is, always QApplication for IOS.

Current version:

#ifdef HEADLESS
    QCoreApplication* pApp = new QCoreApplication ( argc, argv );
#else
#    if defined( Q_OS_IOS )
    bUseGUI        = true;
    bIsClient      = true; // Client only - TODO: maybe a switch in interface to change to server?

    // bUseMultithreading = true;
    QApplication* pApp = new QApplication ( argc, argv );
#    else
    QCoreApplication* pApp = bUseGUI ? new QApplication ( argc, argv ) : new QCoreApplication ( argc, argv );
#    endif
#endif

New version of this PR:

#ifdef HEADLESS
    pApp = new QCoreApplication ( argc, argv );
#else
#    if defined( Q_OS_IOS )
    bUseGUI        = true;
    bIsClient      = true; // Client only - TODO: maybe a switch in interface to change to server?

    // bUseMultithreading = true;
    pApp = new QApplication ( argc, argv );
#    else
    pApp = bUseGUI ? new QApplication ( argc, argv ) : new QCoreApplication ( argc, argv );
#    endif
#endif

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was, and still is, always QApplication for IOS.

As I read it, the old code for iOS was essentially this:

QApplication* pApp = new QApplication ( argc, argv );

The new code is this:

QCoreApplication* pApp = NULL;
pApp = new QApplication ( argc, argv );

I belive (and this is where I might be wrong!) that QCoreApplication is the narrower base class and QApplication is the richer GUI class. Therefore, I suspected that this assignment would limit the interface of pApp to QCoreApplication here (what had been QApplication, i.e. the richter interface, before).
I suspect that it does not make a relevant difference, otherwise I would have expected build failures (e.g. when accessing methods which are only part of QApplication and not QCoreApplication), but I don't claim to have full understanding here so I was asking about whether this was intentional and/or known to be side-effect free.

Thinking some more about it, it's likely to be OK as the non-iOS code is written in a similar way (the type is always QCoreApplication, even when the instance is QApplication). It still makes sense to think explicitly about it as it is more than a syntax change or code moving.

The inheritance chain as I read it is:

Copy link
Contributor Author

@pgScorpio pgScorpio Apr 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, Now I see your point. Thanks for noting!

QCoreApplication is the base type of all QApplications, that's why I can use that type to store the pointer.
But I expected the basetype to have virtual functions, but now I checked it and that isn't the case...
Though all QApplications will call QCoreApplication::exec() eventually there might be a difference in behavior, so I will have to find a better solution for this.

EDIT: But how about this line in the current code ???

QCoreApplication* pApp = bUseGUI ? new QApplication ( argc, argv ) : new QCoreApplication ( argc, argv );

Shouldn't there be the same problem ??

src/main.cpp Show resolved Hide resolved
src/main.cpp Outdated Show resolved Hide resolved
@@ -55,6 +55,42 @@ extern void qt_set_sequence_auto_mnemonic ( bool bEnable );

// Implementation **************************************************************

// NOTE: To prevent memory leaks any pointers assigned with new
// should be declared here and checked and deleted in reverse order of creation in cleanup().
// cleanup() should be called before any exit() !
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it was harder to forget calling cleanup if we had a generic CleanExit(int) function?

Copy link
Contributor Author

@pgScorpio pgScorpio Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it was harder to forget calling cleanup if we had a generic CleanExit(int) function?

Yes I thought about that too, but then I could not use cleanup() for the normal exit. (And also one could still call exit() instead of CleanExit(), the same kind of mistake as forgetting to call cleanup().)

I would prefer using an exception instead of using exit() even more...
(Note that calling exit() instead of normally returning from main with a return code may skip some cleanup in windows itself, and maybe it's the same on other OS's, so this increases the chance on memory leaks.)

@@ -63,6 +99,7 @@ int main ( int argc, char** argv )
// Qt will not show these with underline characters in the GUI on MacOS. (#1873)
qt_set_sequence_auto_mnemonic ( true );
#endif
int exit_code = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is about improving return code correctness, right? At least I don't see first-hand how it is related to the memory leak fix.

I guess it's halfway OK to do it in this PR, but it should be an individual commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.
I added exit_code to be able to do the cleanup and a normal return on an exception.

You are correct I now also use the pApp->exec() result so when exiting via QApplication also the correct exit code is returned by main, and that is indeed not directly related to the problem.

Forgot to use the right pApp pointer in headless mode.
@pgScorpio
Copy link
Contributor Author

I opened a second PR #2620 on the same issue.
That one is the solution using exceptions.
Please pick your solution of choice.
As said I prefer the exception solution...

src/main.cpp Outdated
// should be declared here and checked and deleted in reverse order of creation in cleanup().
// cleanup() should be called before any exit() !

QCoreApplication* pApp = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the conversation in the other place, but this is the place I'm referring to wrt. QCoreApplication vs. QApplication on iOS.

src/main.cpp Outdated
@@ -768,9 +811,9 @@ int main ( int argc, char** argv )
bIsClient = true; // Client only - TODO: maybe a switch in interface to change to server?

// bUseMultithreading = true;
QApplication* pApp = new QApplication ( argc, argv );
pApp = new QApplication ( argc, argv );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was, and still is, always QApplication for IOS.

As I read it, the old code for iOS was essentially this:

QApplication* pApp = new QApplication ( argc, argv );

The new code is this:

QCoreApplication* pApp = NULL;
pApp = new QApplication ( argc, argv );

I belive (and this is where I might be wrong!) that QCoreApplication is the narrower base class and QApplication is the richer GUI class. Therefore, I suspected that this assignment would limit the interface of pApp to QCoreApplication here (what had been QApplication, i.e. the richter interface, before).
I suspect that it does not make a relevant difference, otherwise I would have expected build failures (e.g. when accessing methods which are only part of QApplication and not QCoreApplication), but I don't claim to have full understanding here so I was asking about whether this was intentional and/or known to be side-effect free.

Thinking some more about it, it's likely to be OK as the non-iOS code is written in a similar way (the type is always QCoreApplication, even when the instance is QApplication). It still makes sense to think explicitly about it as it is more than a syntax change or code moving.

The inheritance chain as I read it is:

@pgScorpio
Copy link
Contributor Author

I'm closing this PR in favor of #2620

Too many problems with this solution and it also won't work when exit(1) is used in other places, like in PR #2599
I'm also working on a CCommandlineParameters class and new settings handling which all need exception handling to solve this problem.

@pgScorpio pgScorpio closed this Apr 28, 2022
@pljones pljones removed this from the Release 3.9.0 milestone May 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Memory leak when using Rpc server
4 participants