tetsurom / rxqt

The Reactive Extensions for Qt.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Memory Usage Increase /w rxqt::run_loop

Noggog opened this issue · comments

Hey tetsurom! Thanks again for the library. 8)

I've boiled down what I think its some interaction that is leading to a consistent memory increase when using rxqt_run_loop. Might be something else at play, but hopefully will get some clarity with discussion.

My testing setup is like this:

  • New GUI project /w stock main window
  • Add a rxqt::run_loop
  • One subject
  • MainWindow subscribes to subject
  • One QThread
  • Background QThread continuously fires the subject at 25ms interval

What I've noticed is that if MainWindow's subscription to the subject adds observe_on(rxqt_run_loop.observe_on_run_loop()) it leads to a slow and steady memory usage increase over time (with seemingly no upper limit?). Over like 10-15 minutes it'll go up a megabyte or so, and just keep at that rate.

However, if that observe_on() instead uses rxcpp::observe_on_new_thread() or rxcpp::observe_on_event_loop() the memory usage is stable /w no increase over time.


Here's my source:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include <rx.hpp>
#include <rxqt.hpp>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  struct PublishThread : public QThread
  {
    PublishThread()
    : stop_(false)
    {
    }
    void run();
    rxcpp::subjects::subject<bool> subj_;
    bool stop_;
  };

  explicit MainWindow(QWidget *parent = nullptr);
  ~MainWindow();
  void closeEvent(QCloseEvent *event);

private:
  Ui::MainWindow *ui;
  rxqt::run_loop rxqt_run_loop_;
  PublishThread thread_;
};

#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QCloseEvent>

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
  ui->setupUi(this);

  thread_.subj_.get_observable()

    // No (or way less?) growth when observed on new thread
//    .observe_on(rxcpp::observe_on_new_thread())

    // No (or way less?) growth when observed on event loop
//    .observe_on(rxcpp::observe_on_event_loop())

    // Mem growth when observed on rxqt run loop
    .observe_on(rxqt_run_loop_.observe_on_run_loop())

    .subscribe([](bool p) {
      // Do some random work
      if (p) {
        p = !p;
      }
    });

  thread_.start();
}

MainWindow::~MainWindow()
{
  delete ui;
}

void MainWindow::PublishThread::run()
{
  while(!stop_) {
    subj_.get_subscriber().on_next(true);
    msleep(25);
  }
  QApplication::quit();
}

void MainWindow::closeEvent(QCloseEvent *event) {
  thread_.stop_ = true;
  event->ignore();
}

This very well could be me misunderstanding some C++ fundamentals, but I feel like I've simplified the scenario sufficiently to where I feel like the cause has something to do with the rxqt run loop.

Let me know what you think.
Thanks for your time! 8)

Out of curiosity, I rolled back the latest commit f5373b6
Just to see if the actual implementation change to QSignalMapper as the way of swapping to another thread was the cause.

I did not see the same memory growth, which leads me to one of two conclusions:

  1. It's caused somehow by the usage of QSignalMapper. (They do mention it's depreciated, so maybe it's not behaved?)
  2. The growth is caused by swapping threads, and since the old way potentially wasn't doing that properly, maybe it's just coincidentally not causing the growth because it's not actually getting the job done.

Heya. I believe I have fixed the issue.
https://stackoverflow.com/questions/21646467/how-to-execute-a-functor-or-a-lambda-in-a-given-thread-in-qt-gcd-style
I followed this page's advice, and utilized QMetaObject::invokeMethod, passing in the rxqt_loop itself, and then just using a basic lambda to call the timer's start method.
This let me remove the QSignalMapper, which seemed to remove the memory usage growth.

Unfortunately I don't know if I can actually post the code, as I developed the fix at work... but hopefully you can follow that same post and get to the same conclusions.


I also made an upgrade/fix for the same code location. the timer's start wasn't taking into account the fact that there might be an earlier wakeup registered. The logic that ran if it was on the same thread did that check, but not the side that ran if it wasn't on the same thread. I changed the parameter to be passed in the when rather than the milliseconds (as it was before), and utilized the when inside the lambda call to calculate the possibility of an earlier wakeup.

Hi Noggog, thank you very much for using rxqt in production!
And sorry for the late reply.

Yes, I know QMetaObject::invokeMethod can take a lambda, but unfortunately that overload was introduced in Qt 5.10.
When I introduced that hacky QSignalMapper, even it was already deprecated, I tried to support older Qt versions, at least while they were supported by The Qt Company.

No problem, I know how it can be. 8)
Ah yeah, I wasn't considering any backwards compatibility concepts. Do whatever feels best to you on that front.

Hi Noggog,

I have removed the hacky use of QSignalMapper.
I'm not sure it also fixes your problem but I'm happy if you let me know the result!

Good to hear! Will play around with it soon and report back