jamulus icon indicating copy to clipboard operation
jamulus copied to clipboard

Fixing memory leaks using exceptions

Open pgScorpio opened this issue 3 years ago • 20 comments

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

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

pgScorpio avatar Apr 26 '22 23:04 pgScorpio

@dtinth could you please Review this PR?

ann0see avatar Apr 28 '22 20:04 ann0see

Can I suggest a simpler solution that combines ideas from both of your approaches?

The only uses of exit outside of main are in the option-parsing functions, which are only used before any of the dynamic allocations happen anyway. So turning the exit calls in main into straightforward returns will guarantee that stack-allocated objects in main are destroyed properly.

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...

atsampson avatar Apr 30 '22 21:04 atsampson

@atsampson

Can I suggest a simpler solution that combines ideas from both of your approaches?

Thanks for the suggestion ! But... For the current code that would be almost right. Though exit(1) is also used in the commandline parsing functions, and return can't be used there. Also there a several PR's pending that would also need an exception solution, that's why I changed to using exceptions.

EDIT: BTW Where are the with new allocated instances deleted ???, that was the original memory leak problem....

pgScorpio avatar Apr 30 '22 22:04 pgScorpio

For the current code that would be almost right. Though exit(1) is also used in the commandline parsing functions, and return can't be used there.

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.

BTW Where are the with new allocated instances deleted ???

QScopedPointer is a smart pointer class; it's roughly Qt's equivalent of std::unique_ptr. It deletes the pointer it's holding automatically when it goes out of scope, so the object will be destroyed correctly when the function exits (either via return or through an exception). If you haven't encountered smart pointers before, I'd definitely recommend reading up on them - there's rarely a good reason to use delete in modern C++.

atsampson avatar Apr 30 '22 22:04 atsampson

QScopedPointer is a smart pointer class; it's roughly Qt's equivalent of std::unique_ptr. It deletes the pointer it's holding automatically when it goes out of scope, so the object will be destroyed correctly when the function exits (either via return or through an exception)

Yes I know smart pointers, I even considered using them. But they have some disadvantages too. 1: They only work when there is a well defined scope. And the rpc classes don't have a well defined scope in the current implementation. 2: No garantee in wich order they are deleted if they are in the same scope (often same order as creation). And since the pointers are cross referenced we need to delete them in a specific order (reverse order of creation).

So I still think the exception solution is the simplest and most reliable, since it works under all circumstances...

pgScorpio avatar Apr 30 '22 23:04 pgScorpio

No garantee in wich order they are deleted if they are in the same scope (often same order as creation).

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.

And the rpc classes don't have a well defined scope in the current implementation.

... 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 main exits. You want to ensure that the RPC objects are deleted before the objects they're controlling, which matches the declaration order in the current code.

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 avatar May 01 '22 02:05 atsampson

@atsampson Thanks for the information. If this is true (though I encountered several occasions where it didn't work this way) we could use smart pointers here to do the cleanup (and so it also should be done on a lot of other places in the code), but see the note:

Note [2] However, the program can be terminated (by calling std​::​exit() or std​::​abort() for example) without destroying objects with automatic storage duration — end note]

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.

pgScorpio avatar May 01 '22 12:05 pgScorpio

@atsampson @pgScorpio what's the agreement on the other discussed solution I think @pgScorpio wanted to use exceptions?

ann0see avatar May 15 '22 06:05 ann0see

@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 exit() would be used elsewhere, while the other method only works when exiting from main()

pgScorpio avatar May 15 '22 14:05 pgScorpio

@ann0see The only thing I'm really uncomfortable about here is the manual cleanup code using delete - it really should be using smart pointers in modern C++. I'm fine with adding an exception specifically to handle the option parsing errors - there's no advantage in terms of the original fault, but it does simplify the code in the parsing functions. If the exception rework goes beyond that, then I think it would be better to review it later when it's needed.

atsampson avatar May 15 '22 15:05 atsampson

The only thing I'm really uncomfortable about here is the manual cleanup code using delete - it really should be using smart pointers in modern C++.

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...

I'm fine with adding an exception specifically to handle the option parsing errors - there's no advantage in terms of the original fault, but it does simplify the code in the parsing functions.

If the exception rework goes beyond that, then I think it would be better to review it later when it's needed.

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).

pgScorpio avatar May 15 '22 17:05 pgScorpio

Ok. So @atsampson @pgScorpio could you please open an issue on that? What's the state of this PR? Can @atsampson do a review so that we are a bit closer to a merge?

ann0see avatar May 15 '22 19:05 ann0see

@atsampson Thanks for the review ;-). I think it's quite close to the KISS principle and "hard core Jamulus like".

ann0see avatar May 15 '22 21:05 ann0see

@ann0see

I think it's quite close to the KISS principle and "hard core Jamulus like".

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.... That's the whole idea behind my 'improvement' PR's, improving the framework to make maintaining the code easier and less prone to bugs...

pgScorpio avatar May 16 '22 14:05 pgScorpio

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. The goal should be simple, extendable, unbloated code which doesn't do more than it needs to do. I think the refactoring of the Autobuild was a good example on simplification.

ann0see avatar May 16 '22 19:05 ann0see

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.

ann0see avatar May 26 '22 08:05 ann0see

@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.

ann0see avatar Jun 05 '22 11:06 ann0see

Moving out to 3.10.0 as no clear fix identified.

pljones avatar Sep 04 '22 10:09 pljones

Moving back to Triage until a way forward is proposed (and removing milestone).

pljones avatar Apr 19 '23 15:04 pljones

Setting to draft as branch needs conflict resolution.

pljones avatar Sep 18 '23 18:09 pljones

Closing as no activity in two years.

pljones avatar May 06 '24 09:05 pljones