-
Notifications
You must be signed in to change notification settings - Fork 226
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 using exceptions #2620
Fixing memory leaks using exceptions #2620
Conversation
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.
Forgot to use the right pApp pointer in headless mode.
Added CErrorExit and CInfoExit exception classes to exit the application in a clean way.
try | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, but because of moving this try, the indenting changed on a large part of the code and now, in combination with some moved code, the diff becomes very confusing...
You probably better open both files side by side....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a "Hide whitespace" option for Github, and for git there is git diff -w
-w
--ignore-all-space
Ignore whitespace when comparing lines. This ignores differences even if one line has whitespace where the other line has none.
Making shure the type of pApp stays the same as it used to be. Bugfix exit_code CGenErr on gui.
activity and pApp don't exist anymore outside the try block.
ExitCode now also in GenErr Moved ExitCode param for ErrorExit to last param with default Catching standard exeptions Fixed QCoreApplication/QApplication usage.
@dtinth could you please Review this PR? |
In the code we use bUseGUI to select between QApplication and QCoreApplication. But since QApplication is not defined in HEADLESS builds we have to use a lot of extra ifdef's. But by defining QApplication the same as QCoreApplication in HEADLESS builds we no longer need those ifdefs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The diff isn't that clear on GitHub... But see some comments.
#include "settings.h" | ||
#include "util.h" | ||
#include <memory> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you just move code around here? Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep things more clear....
All those ifdef's are confusing enough, so whenever possible let's keep common things together...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I hope it doesn't have any side-effects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to avoid making unnecessary changes entirely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to avoid making unnecessary changes entirely.
In my opinion that is one of the main problems in the Jamulus development.
Just fixing bugs locally, without solving the real cause of the bug... often poor framework...
Improvements of the framework aren't "unnecessary changes"!
Real development is continuously improving the framework to prevent future similar bugs.
So you should strive for solutions that work in any case, not just for the bug you are fixing.
|
||
// Implementation ************************************************************** | ||
|
||
int main ( int argc, char** argv ) | ||
{ | ||
int exit_code = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Give a comment here why we need the variable. (I still don't like having an exit code variable here...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm Why not ? Its the return value of main, and it's quite common practice to declare the returned value as first in a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its the return value of main, and it's quite common practice to declare the returned value as first in a function.
But I don't see any other places in Jamulus where functions have this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I don't see any other places in Jamulus where functions have this?
Perhaps you have to look better than ;=))
Just do a search on return ret
, return res
or return result
and you will already find a bunch of them...
Or search for return _id;
since also all qt_metacall moc_ functions use this coding pattern!
Or take a look look at:
CChannel::SetSockBufNumFrames
CChannel::PutAudioData
CChannel::GetData
CProtocol::GetValFromStream
CNetBuf::Get
CNetBufWithStats::Get
CBuffer::GetAvailData()
CClient::GetAndResetbJitterBufferOKFlag()
CServer::CreateChannelList()
CServer::FindChannel
CServer::PutAudioData
CServer::CreateLevelsForAllConChannels
CSettings::GetIniSetting
CSettings::GetNumericIniSet
CSettings::GetFlagIniSet
...
All the same pattern, just different variable names....
This really is a standard coding practice....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. This is not necessary - just use return
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. This is not necessary - just use
return
directly.
No! actually we should use Application.exit() anywhere outside main, and so we need the return value of Application.exec()
// TODO create settings in default state, if loading from file do that next, then come back here to | ||
// override from command line options, then create client or server, letting them do the validation | ||
// | ||
// See https://github.com/pgScorpio/jamulus/tree/first-big-thing-for-settings for a possible solution on that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this comment since it's just a short therm one. I hope in future we'd have a PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TODO is not my comment....
And I added the link to my solution in another attempt to get the settings discussion going... (After several failed attempts.)
Nevertheless, there are still a lot PR to merge in main, so I will remove it eventually... ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still the link seems to be new. I'd rather NOT have future fixes in the code. Either fix them right away - that's out of scope of this PR - or document it on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still the link seems to be new.
Yes. As I said "I added the link"... "in another attempt to get the settings discussion going"
But I get your point... I hope you get my point too. ;=))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What ann0see said.
@@ -903,15 +965,15 @@ int main ( int argc, char** argv ) | |||
|
|||
// show dialog |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't the exit-code variable here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since here main always returns 0 and we only did set an exit code with exit(1)
, but in the new situation we wanna do the clean up, so we don't wanna use exit()
, but we will return from main normally, so main must return the exit code.
Co-authored-by: ann0see <[email protected]>
Co-authored-by: ann0see <[email protected]>
Can I suggest a simpler solution that combines ideas from both of your approaches? The only uses of You can then convert the pointer variables into smart pointers, adding new smart pointers for the RPC classes that didn't have them already as you've done above. I've put a quick attempt at this on the atsampson:scopedexit branch. I haven't tested this with all the combinations of configuration options but it should give you an idea of what I mean... |
Thanks for the suggestion ! EDIT: BTW Where are the with |
That's fine, because the option-parsing functions are only used before any of the dynamic allocation happens. There's a comment noting this in my changes.
|
Yes I know smart pointers, I even considered using them. But they have some disadvantages too. So I still think the exception solution is the simplest and most reliable, since it works under all circumstances... |
No, that's not correct. In C++, variables with automatic storage duration (i.e. locals) are guaranteed to be destroyed in reverse order of construction - see the language standard or the ISO C++ FAQ. If that wasn't the case, then lots of RAII idioms wouldn't work.
... which means they do have a well-defined lifetime if you convert them to smart pointers - they will be deleted in reverse order of the declarations when There's absolutely nothing wrong with using exceptions to cause a clean exit from elsewhere in the code - one of the reasons smart pointers were added to the standard library was to simplify memory management when exceptions are being used, and if you're adding more use of exceptions then you may well find that there are other places in the code where you can replace manual memory management with smart pointers to avoid complex cleanup code. |
@atsampson
So we still should use exceptions instead of exit. I don't like to rely on the fact that no pointers or other objects are assigned before using exit. This might (and will) change in the future, so it would be safer to state "NEVER use exit but use the CErrorExit exception. |
|
||
// Implementation ************************************************************** | ||
|
||
int main ( int argc, char** argv ) | ||
{ | ||
int exit_code = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its the return value of main, and it's quite common practice to declare the returned value as first in a function.
But I don't see any other places in Jamulus where functions have this?
// TODO create settings in default state, if loading from file do that next, then come back here to | ||
// override from command line options, then create client or server, letting them do the validation | ||
// | ||
// See https://github.com/pgScorpio/jamulus/tree/first-big-thing-for-settings for a possible solution on that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still the link seems to be new. I'd rather NOT have future fixes in the code. Either fix them right away - that's out of scope of this PR - or document it on GitHub.
if ( bUseGUI ) | ||
{ | ||
pApplication = new QApplication ( argc, argv ); | ||
} | ||
else | ||
{ | ||
pCoreApplication = new QCoreApplication ( argc, argv ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving this section up to @hoffie
@atsampson @pgScorpio what's the agreement on the other discussed solution I think @pgScorpio wanted to use exceptions? |
Correct. Since the exception solution will also work where normally |
@ann0see The only thing I'm really uncomfortable about here is the manual cleanup code using |
I agree that using smart pointers would be a better solution, but AFAIK, besides in CSignalHandler, it's nowhere else used in the code, but though not specifically the solution for the original problem, it could be implemented here as well, though I think that should better be a separate PR, replacing all pointers with smart pointers...
And as you say yourself, it's already used in the option parsing functions... And this will be even more neccesay if we implement the commandline options class(es). |
Ok. So @atsampson @pgScorpio could you please open an issue on that? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review added as requested.
/* Classes ********************************************************************/ | ||
/* Exception Classes **********************************************************/ | ||
|
||
class CInfoExit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't add this class. It's only used to replace exit(0);
from main
, which should just be return 0;
. It's bad style in C++ to use exceptions for handling normal situations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's bad style in C++ to use exceptions for handling normal situations.
It's what you call a "normal" situation...
But I agree that, just for the --help and --version, it could be a return ( 0 ).
|
||
QString GetErrorMessage() const { return strErrorMsg; } | ||
|
||
int GetExitCode() const { return iExitCode; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exit code is always 1 on error in the code below, so iExitCode
isn't needed. (You might want to add this in the future if there's really a good reason to have different exit codes, but I think that's very unlikely to be useful in Jamulus, and it's certainly not needed now.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exit code is always 1 on error in the code below, so
iExitCode
isn't needed. (You might want to add this in the future if there's really a good reason to have different exit codes, but I think that's very unlikely to be useful in Jamulus, and it's certainly not needed now.)
I agree the exit code is (strangely enough) always 1 in this code, but as said we should provide a framework with general solutions, not prohibiting any 'normal' behavior.
Prohibiting any 'normal' behavior now will possibly lead to unwanted 'solutions' in the future.
QString strInfoMsg; | ||
}; | ||
|
||
class CErrorExit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd call this something like CCmdLineErr
, to indicate what it's actually being used to report.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd call this something like
CCmdLineErr
, to indicate what it's actually being used to report.
Again, this is a 'universal' solution, so not only usable for commandline errors, so why name it that way just because we now use it for commandline errors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume we can leave it as is - if we use it in future (and I think there is work coming up by @pgScorpio which uses it)
@@ -359,6 +394,9 @@ class CCustomEvent : public QEvent | |||
}; | |||
|
|||
/* Prototypes for global functions ********************************************/ | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not used - remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, Provide a Framework that should (and will) be used in the near future...
@@ -22,10 +22,23 @@ | |||
* | |||
\******************************************************************************/ | |||
|
|||
#ifdef HEADLESS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't change this - keep the changes to the minimum necessary to fix the problem.
// TODO create settings in default state, if loading from file do that next, then come back here to | ||
// override from command line options, then create client or server, letting them do the validation | ||
// | ||
// See https://github.com/pgScorpio/jamulus/tree/first-big-thing-for-settings for a possible solution on that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What ann0see said.
|
||
// clicking on the Mac application bundle, the actual application |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explain in the commit message why removing this is safe (presumably it wasn't reached originally because of the qCritical
- but I'd check the Git history to see why it was added).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this was a strange exception....
Since 1. I tested it on macOS and didn't see any strange behavior, and 2. the same exit ( 1 ) is used in multiple places, so if it really was a problem it should have been implemented in multiple places...
But yeah, I should have mentioned it...
|
||
// Application/GUI setup --------------------------------------------------- | ||
// Application object | ||
#ifdef HEADLESS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where you should have QScopedPointer<QCoreApplication> pApp;
- see my branch. You don't need it to be of different types in the headless/non-headless cases, because QApplication
is a subclass of QCoreApplication
and has a virtual destructor.
You can then remove all of the extra conditionals that you've added below because of splitting this into two variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need it to be of different types in the headless/non-headless cases, because
QApplication
is a subclass ofQCoreApplication
and has a virtual destructor.
Yes it has a virtual destructor, but there are a lot of other functions that are NOT virtual. Even exec()
is NOT virtual and calling exec()
As QCoreApplication while it actually is a QApplication will skip some GUI initialization.
So yes the pointer HAS to be the correct type.
// TEST -> activate the following line to activate the test bench, | ||
//CTestbench Testbench ( "127.0.0.1", DEFAULT_PORT_NUMBER ); | ||
// clang-format on | ||
#endif | ||
|
||
CRpcServer* pRpcServer = nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Smart pointer here - plus smart pointers for CServerRpc and CClientRpc - see my branch.
exit_code = -1; | ||
} | ||
|
||
if ( pServerRpc ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed with a smart pointer. (But even if it was, you never need to write if (x) delete x;
- delete NULL;
is guaranteed to do nothing.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a (good?) habit of me to always check for null pointers ;=)
But you are right, it's not necessary here....
@atsampson Thanks for the review ;-). |
May I note that the KISS principle refers to the implementation of (unnecessary) functionality, NOT the way the code is implemented. "Simple and Stupid" code implementation will always lead to more and more problems and bugs.... |
Yes. Probably. However the history of Jamulus was quite radical with code changes. It doesn't need to be like that - at least it's probably not up to me, but "the community" to decide. |
Not sure if that's a good idea, but I think for your PRs (especially the sound related redesign, I think it would be worth having npostavs and corrados as reviewers). I know corrados is officially no longer part of the Jamulus Team, however he has the most insight probably. |
@jamulussoftware/maindevelopers how do we proceed here? I think the review by @atsampson has valid input, but I'm not fully sure how to continue (especially since - as discussed internally - some team members don't quite see the improvements). I'd like someone like corrados to review sound redesign related changes (maybe not this one) since he's probably most familiar with the sound code. |
Moving out to 3.10.0 as no clear fix identified. |
Moving back to Triage until a way forward is proposed (and removing milestone). |
Setting to draft as branch needs conflict resolution. |
Closing as no activity in two years. |
Short description of changes
Fix some memory leaks.
Alternative for PR #2618 using exceptions to cleanly exit the application.
(#2618 won't solve the problem when still using exit() or on other unhandled exceptions.)
CHANGELOG:
Fixed some memory leaks in main.
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?
Review
Checklist