Is one canvas per thread possible?

I am right now running a successful integration of ROOT in to my program. This program runs one socket per thread and each thread parses real time data. My aim was to run a ROOT canvas on each thread. What I’m doing is to declare the ROOT App globally and then run a canvas instance on each thread. This initially works, in the sense that one canvas per thread with its own definitions gets up and running, but then there seems to be a memory access problem, where it seems like both canvas are on the same memory address, or something above these lines. The app crashes once data starts being passed to ROOT, at the time of painting I believe. Is there a way to achieve one canvas per thread on the same app ? If not, is there any other common technique used to achieve multi canvases ( or multiple apps ) ? I can run multiple apps by running different processes, but I would prefer to run a canvas on each thread if possible. Thank you

IIRC the new TWebCanvas (or at least RCanvas) supports this (correct, @linev ?). The old TCanvas will need one single thread for them all, with locking when other threads add to it etc.

1 Like

Hi,

TWebCanvas class provides web-based display of TCanvas - using THttpServer and web browsers. This implementation behaves much more stable in multi-threading environment than normal X11-based display. But TWebCanvas class cannot resolve intrinsic problem that TCanvas methods in many places using global gPad pointer and rely that this pointer must be set to the interacting canvas.

RCanvas is new ROOT7 class with fully new design. Only web-based display is implemented for RCanvas. All supported in TWebCanvas classes (like TH1, TGraph, …) also works with RCanvas.
And it is fully thread-safe - it is allowed to use different RCanvas instances in different threads.
There is rcanvas_mt.cxx tutorial macro demonstrating such functionality.

Regards,
Sergey

1 Like

So I guess it’s time to build ROOT 7 and give this a go, looking good ! Thank you guys for answering, I will report once I get it running.

Quick question, after solving a couple of imports missing on the rcanvas_mt demo, I still need to link ROOT::Experiemental modules I believe, as getting linker errors.

My current CMAKE is this :

set(ROOT_INCLUDE_DIR /home/xavier/root_src/installdir/include)

set(ROOT_LIBRARY_DIR /home/xavier/root_src/installdir/lib)

set(ROOT_DIR /home/xavier/root_src/builddir)

include_directories(${ROOT_INCLUDE_DIR})

link_directories(${ROOT_LIBRARY_DIR})

add_executable(spectre-analysis ${SOURCE_FILES})

target_link_libraries( spectre-analysis PRIVATE ${ROOT_LIBRARIES} ${PostgreSQL_LIBRARIES} cppzmq )

This way I’m linking all ROOT modules, which is what I want, but it seems that root experimental is not being included.

I compiled with

the CMAKE_CXX_STANDARD cmake variable is set to at least 17

So I believe I compiled it with support … Thank you again

This is some of the output, which only has trouble finding the experimental modules it seems :

/usr/bin/ld: CMakeFiles/spectre-analysis.dir/__/threadtest.cpp.o: warning: relocation against `_ZTVN4ROOT12Experimental14RAxisIrregularE' in read-only section `.text._ZN4ROOT12Experimental14RAxisIrregularD2Ev[_ZN4ROOT12Experimental14RAxisIrregularD5Ev]'
/usr/bin/ld: CMakeFiles/spectre-analysis.dir/__/threadtest.cpp.o: in function `draw_canvas(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, ROOT::Experimental::RColor)':
threadtest.cpp:(.text+0x1e3): undefined reference to `ROOT::Experimental::RCanvas::Create(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
/usr/bin/ld: threadtest.cpp:(.text+0x29f): undefined reference to `ROOT::Experimental::RCanvas::Show(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
/usr/bin/ld: threadtest.cpp:(.text+0x477): undefined reference to `ROOT::Experimental::RCanvas::Update(bool, std::function<void (bool)>)'
/usr/bin/ld: threadtest.cpp:(.text+0x4ac): undefined reference to `ROOT::Experimental::RCanvas::Run(double)'

Hi,

You should try to link with libROOTWebDisplay.so and libROOTGpadv7.so libraries.

Regards,
Sergey

Hi Sergey, unfortunately it seems that I’m unable to link these. The way I have CMAKE configured is for it to link all libraries, and libROOTWebDisplay lives in /home/xavier/root_src/installdir/lib, but it doesn’t find it. Can you please share a way that works to link all of the root modules ? Since I am still testing, I don’t really know which specific modules I need, so it’s more practical to have all of them available perhaps ? Thanks

Hi,

Here is my cmake file to compile code in rcanvas_mt.cxx macro:

project(rcanvas_mt)
cmake_minimum_required(VERSION 3.11)

find_package(ROOT REQUIRED)
include(${ROOT_USE_FILE})

add_executable(rcanvas_mt rcanvas_mt.cxx)

target_link_libraries( rcanvas_mt PRIVATE ${ROOT_LIBRARIES} ROOTWebDisplay ROOTGpadv7 ROOTHist ROOTHistDraw)

If you do not use ROOT7 histogram class - you can just exclude last two libraries.

Regards,
Sergey

1 Like

That worked Sergey, at the moment is compiling. I will keep on reporting my testing. Thank you !

It’s the regular TGraph class working ? as auto gr1 = make_shared<TGraph>();

canvas->Draw(gr1);

since I’m getting

/home/xavier/spectre-analisys/threadtest.cpp:76:20:   required from here
/home/xavier/root_src/installdir/include/ROOT/RPadBase.hxx:114:34: error: no matching function for call to ‘GetDrawable(const std::shared_ptr<TGraph>&)’
  114 |       auto drawable = GetDrawable(what, args...);

Or do I have to import something like RGraph ?

Hi,

You have to use TObjectDrawable class - as it shown in rcanvas/tobject.cxx tutorial.

Regards,
Sergey

1 Like

This is great ! It works as I imaged it, it’s indeed much more stable and works with my thread pool :slight_smile:

there are a couple of things that don’t really work as they did before:

   TradeSubSocket sock("RECEIVER");

   auto msgb = make_unique<MessageBuffer>();
   auto buff = make_unique<TBuffer>(500);
   const char *cdatetime;
   string datetime;
   string p_header;

   // Create histograms
   // RAxisConfig xaxis(500, 0, 500);
   // auto pHist = std::make_shared<RH1D>(xaxis);
   auto gr1 = make_shared<TGraph>();
   auto gr2 = make_shared<TGraph>();

   // Create a canvas to be displayed.
   auto canvas = RCanvas::Create(title);

   canvas->Draw<RFrameTitle>(title);
   // canvas->Draw(pHist);

   auto subpads = canvas->Divide(1, 2);

   subpads[0][0]->Draw<TObjectDrawable>(gr1, "AL");
   subpads[0][1]->Draw<TObjectDrawable>(gr2, "AL");

   gr2->SetTitle("QUANTITY");

   // auto box1 =  subpads[0][1]->Draw<RBox>(RPadPos(0.1_normal, 0.3_normal), RPadPos(0.3_normal, 0.6_normal));


   canvas->SetSize(1200, 500);
   canvas->Show();

   while (true)
   {

      string message = sock.receive();

      auto jm = make_unique<ParseJsonMessage>(message);

      jm->parse();
      buff->add_to_buffer((*jm));
      buff->check_buffer_size();

      std::vector<int> buffersize(boost::counting_iterator<int>(1), boost::counting_iterator<int>(buff->tb.size() + 1));

      for (int i = 0; i < buffersize.size(); i++)
      {
         gr1->SetPoint(i, buffersize[i], buff->tpb[i]);
         gr2->SetPoint(i, buffersize[i], buff->tpq[i]);


         datetime = get_datetime(jm->trade.trade_time);
         p_header = datetime + " | " + " PP " + to_string((*buff).tpb[i]);
         const char *cdatetime = p_header.c_str();
         cdatetime = p_header.c_str();

         gr1->SetTitle(cdatetime);

      }

         canvas->Update();
         canvas->Modified();

This code is a direct translation to what I had that worked on ROOT 6, but on this version, Graphs boundaries are not dynamically updated … it seems that the data gets visually out of bounds and you need to zoom to see the actual data being drawn. Also, setting the title on the loop doesn’t change it anymore … is this I’m missing something with the canvas update ? I have checked all the tutorials but I don’t see what I’m missing. Thank you for the amazing work

Hi,

TGraph re-drawing (like title change) is not tested well enough - I need to check and fix it.

And there is one special feature in jsroot concerning zooming.
Once drawn axes range interactively changed - it will stay so even with changed data.
One need to unzoom twice (via mouse double click or axis context menu) that new range can be visible.

Next days I will try to test and fix problems.

Regards,
Sergey

Yes, I see … there’s the problem of the graph, if it’s updated and its x and y range is dynamic, it doesn’t resize to fit automatically as it was before. I will keep on making more tests with threads and see how far it can go :slight_smile: If you need some help for testing, or really anything let me know

Hi,

I found and fix problem with TGraph updates in jsroot.
You can try latest jsroot code with ROOT master branch by doing:

cd /home/user/git/
git clone https://github.com/root-project/jsroot.git
export JSROOTSYS=/home/user/git/jsroot

Regards,
Sergey

On my way to test it ! will let you know asap

do I need to recompile root somehow ? I have followed your instructions above but I’m not sure how to proceed after that :slight_smile: thanks

No, I just fix jsroot code.
You need to start ROOT from the shell where JSROOTSYS variable set.

Okay I had to set the env variable for jsroot with export instead of set … TGraph almost works as it did on Root 6 now :slight_smile: but there is still one remaining problem, which is the y axis doesn’t get auto-fit to data boundaries … it does for the x axis, but not for the y axis … this is amazing !

On this case, it seems that it’s simply a problem with the zoom controls being set. When I do an F5 while the canvas is running, the Y fits to its boundaries.