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 using exceptions #2620

Closed

Conversation

pgScorpio
Copy link
Contributor

@pgScorpio pgScorpio commented Apr 26, 2022

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

  • 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.
Forgot to use the right pApp pointer in headless mode.
Added CErrorExit and CInfoExit exception classes to exit the application
in a clean way.
@pgScorpio pgScorpio mentioned this pull request Apr 26, 2022
4 tasks
Comment on lines +130 to 132
try
{

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.

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

Copy link
Contributor

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.

image

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.
@ann0see ann0see added this to the Release 3.9.0 milestone Apr 27, 2022
@ann0see ann0see added the refactoring Non-behavioural changes, Code cleanup label Apr 27, 2022
@ann0see ann0see requested a review from hoffie April 27, 2022 19:56
ExitCode now also in GenErr
Moved ExitCode param for ErrorExit to last param with default
Catching standard exeptions
Fixed  QCoreApplication/QApplication usage.
@ann0see
Copy link
Member

ann0see commented Apr 28, 2022

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

The diff isn't that clear on GitHub... But see some comments.

src/global.h Show resolved Hide resolved
src/main.cpp Outdated Show resolved Hide resolved
src/main.cpp Outdated Show resolved Hide resolved
Comment on lines +38 to +40
#include "settings.h"
#include "util.h"
#include <memory>
Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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;
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Contributor

Choose a reason for hiding this comment

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

What ann0see said.

src/main.cpp Show resolved Hide resolved
@@ -903,15 +965,15 @@ int main ( int argc, char** argv )

// show dialog
Copy link
Member

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?

Copy link
Contributor Author

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.

pgScorpio and others added 2 commits April 30, 2022 22:17
Co-authored-by: ann0see <20726856+ann0see@users.noreply.github.com>
Co-authored-by: ann0see <20726856+ann0see@users.noreply.github.com>
@atsampson
Copy link
Contributor

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

@pgScorpio
Copy link
Contributor Author

pgScorpio commented Apr 30, 2022

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

@atsampson
Copy link
Contributor

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

@pgScorpio
Copy link
Contributor Author

pgScorpio commented Apr 30, 2022

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

@atsampson
Copy link
Contributor

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.

@pgScorpio
Copy link
Contributor Author

pgScorpio commented May 1, 2022

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


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

int main ( int argc, char** argv )
{
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.

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
Copy link
Member

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.

Comment on lines +828 to +835
if ( bUseGUI )
{
pApplication = new QApplication ( argc, argv );
}
else
{
pCoreApplication = new QCoreApplication ( 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.

Leaving this section up to @hoffie

src/main.cpp Show resolved Hide resolved
@ann0see
Copy link
Member

ann0see commented May 15, 2022

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

@pgScorpio
Copy link
Contributor Author

@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()

@atsampson
Copy link
Contributor

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

@pgScorpio
Copy link
Contributor Author

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

@ann0see
Copy link
Member

ann0see commented May 15, 2022

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?

Copy link
Contributor

@atsampson atsampson left a 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
Copy link
Contributor

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.

Copy link
Contributor Author

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; }
Copy link
Contributor

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

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Member

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 ********************************************/

Copy link
Contributor

Choose a reason for hiding this comment

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

Not used - remove.

Copy link
Contributor Author

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

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 of QCoreApplication 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;
Copy link
Contributor

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 )
Copy link
Contributor

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

Copy link
Contributor Author

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

@ann0see
Copy link
Member

ann0see commented May 15, 2022

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

@pgScorpio
Copy link
Contributor Author

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

@ann0see
Copy link
Member

ann0see commented May 16, 2022

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
Copy link
Member

ann0see commented May 26, 2022

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
Copy link
Member

ann0see commented Jun 5, 2022

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

@pljones pljones modified the milestones: Release 3.9.0, Release 3.9.1 Jun 18, 2022
@gilgongo gilgongo added this to Triage in Tracking (old) via automation Jul 2, 2022
@gilgongo gilgongo moved this from Triage to Waiting on Team in Tracking (old) Jul 2, 2022
@pljones pljones moved this from Waiting on Team to Backlog in Tracking (old) Aug 29, 2022
@pljones pljones moved this from Backlog to Triage in Tracking (old) Sep 4, 2022
@pljones
Copy link
Collaborator

pljones commented Sep 4, 2022

Moving out to 3.10.0 as no clear fix identified.

@pljones pljones modified the milestones: Release 3.9.1, Release 3.10.0 Sep 4, 2022
@pljones pljones moved this from Triage to Backlog in Tracking (old) Oct 5, 2022
@ann0see ann0see changed the base branch from master to main December 26, 2022 19:09
@pljones
Copy link
Collaborator

pljones commented Apr 19, 2023

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

@pljones pljones removed this from the Release 3.10.0 milestone Apr 19, 2023
@pljones pljones moved this from Backlog to Triage in Tracking (old) Apr 19, 2023
@pljones pljones removed this from Triage in Tracking (old) Jul 28, 2023
@pljones pljones added bug Something isn't working and removed refactoring Non-behavioural changes, Code cleanup labels Aug 12, 2023
@pljones
Copy link
Collaborator

pljones commented Sep 18, 2023

Setting to draft as branch needs conflict resolution.

@pljones pljones marked this pull request as draft September 18, 2023 18:29
@pljones pljones added this to the Release 3.12.0 milestone May 6, 2024
@pljones
Copy link
Collaborator

pljones commented May 6, 2024

Closing as no activity in two years.

@pljones pljones closed this May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Memory leak when using Rpc server
5 participants