Memory leak caused by AddExec


ROOT Version: 6.30/02
Platform: Linux Ubuntu 22.04
Compiler: gcc 11.4.0


I am having an issue with a memory leak that appears to be caused by TCanvas::AddExec. If I run this very simple example:

void dummy_func() {
  return;
}

void test_memoryleak(){
  TCanvas *c1 = new TCanvas();
  TH1D *hist = new TH1D("hist", "hist", 100, 0, 100);
  hist->Draw();

  c1->AddExec("ex", "dummy_func()");
}

And then watch the memory consumed by ROOT as I move my cursor continuously around the canvas, it slowly creeps up. If I stop moving the cursor, the used memory stays constant. The memory doesn’t seem to be freed, even after calling

c1->DeleteExec("ex");

This eventually causes problems with long interactive sessions where all available memory is used up.

I see it also on Mac. The memory consumption grows very slowly. This needs to be checked. Thanks to have seen it.

Most probably leak caused by invocation of `gROOT->ProcessLine()` method.

It is how TExec invokes provided code.

Yes, the line gets re-jitted at every invocation of the Exec (and afaik the memory used by the interpreter never gets released).
Seems like the “intended” behavior, though obviously calling the compiler at every mouse movement is not ideal. My suggestion is to avoid the use of Exec altogether.

TExec is a useful functionality. I just did a some debugging and after some tests it seems clear the leak is somewhere in TCling::ProcessLine.
May be @devajith might have some idea ?

I’m sure there are better ways to bind a function call to mouse movement / canvas update that don’t involve recompiling the function almost every frame…

This could be an alternative:

In the usage TimGray and I found the leak in, it’s part of a library, rather than through a standalone macro or on the CLI.

In the library that the leak was noticed in, the usage of AddExec(), goes something to this effect

TObject *obj = nullptr;
canvas->AddExec("ex", myfuncname);
while(true){
  obj = canvas->WaitPrimitive();
// do stuff
}
// do stuff
canvas->DeleteExec("ex");

Looking at this pattern it’s possible that the AddExec() function was intentionally not supposed to release the memory as in it being an architectural choice (or an oversight). Given that, it’d be assumed that by using DeleteExec() it would release the memory, so maybe the issue is actually there instead, or even both?

Either way, would the usage of calling Connect() and Disconnect() to achieve the equivalent function be something like the following? Working on some analysis and this memory leak has delayed me more than I care to admit.

TObject *obj = nullptr;
canvas->Connect("ProcessedEvent(Int_t,Int_t,Int_t,TObject*)", nullptr, nullptr,
               "MyEventHandler(Int_t,Int_t,Int_t,TObject*)");
while(true){
  obj = canvas->WaitPrimitive();
// do stuff
}
// do stuff
canvas->Disconnect("ProcessedEvent(Int_t,Int_t,Int_t,TObject*)", nullptr, nullptr,
               "MyEventHandler(Int_t,Int_t,Int_t,TObject*)");

Thank you for this suggestion - I can implement a version of what I needed AddExec for using this. The memory leak still exists in AddExec but I can get all the functionality I need without it.