Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-94566

QQmlEngine garbage collector does not work for JS scripts

    XMLWordPrintable

Details

    • Windows

    Description

      Faced an automatic garbage collection issue when using QQmlEngine for JavaScript scripts (without QML, only user scripts).
      The task is to use the built-in scripting engine to dynamically create your objects inherited from QObject with the ability to control their life by the built-in garbage collector.
      I wrote a small example for the test.
      The test script generates a large number of objects with large arrays of ints in a separate function and I expect that when the function exits, these objects will be removed by the garbage collector.
      Or, they will be deleted after a while, because there are no links to them and when they are created, they are set to the ownership to QQmlEngine::JavaScriptOwnership.
      When using the old QtScript module, the garbage collector worked fine, but when switching to QML, similar problems began.
      Qt 5.15.2 compiller MSVC 2019 x64
      Forum topic: https://forum.qt.io/topic/127617/qqmlengine-garbage-collector

       

      Script handler:

      H file:
      class CScriptHandle : public QObject
      {
      	Q_OBJECT
      
      private:
      	class CScriptEngine *m_scriptEngine = nullptr;
      
      public:
      	CScriptHandle() : QObject(nullptr) {}
      
      	QQmlEngine *m_jsEngine = nullptr;
      
      	void RunEngine(const QString &functionName, const QString &scriptBody);
      
      	template<class T>
      	inline QJSValue AddNewScriptObject(T *obj)
      	{
      		obj->setParent(m_jsEngine);
      		m_jsEngine->setObjectOwnership(obj, QQmlEngine::JavaScriptOwnership);
      		return m_jsEngine->newQObject(obj);
      	}
      
      	inline QJSValue AddNewScriptArray()
      	{
      		return m_jsEngine->newArray();
      	}
      
      	inline QJSValue NullScriptObject()
      	{
      		return m_jsEngine->newQObject(nullptr);
      	}
      };
      
      
      
      CPP file:
      void CScriptHandle::RunEngine(const QString &functionName, const QString &scriptBody)
      {
      	m_jsEngine = new QQmlEngine();
      
      	m_jsEngine->installExtensions(QJSEngine::GarbageCollectionExtension, m_jsEngine->globalObject());
      
      	QJSValue metaObjectFindItemProperty = m_jsEngine->newQMetaObject(&CScriptObject::staticMetaObject);
      	m_jsEngine->globalObject().setProperty("MyObj", metaObjectFindItemProperty);
      
      	m_scriptEngine = new CScriptEngine(this);
      	m_jsEngine->globalObject().setProperty("MyCore", AddNewScriptObject(m_scriptEngine));
      
      	QJSValue result = m_jsEngine->evaluate(scriptBody);
      
      	if (result.isError())
      	{
      		qDebug() << "Evaluate script error! Line:" << result.property("lineNumber").toString() << "\n" << result.toString();
      	}
      	else
      	{
      		result = m_jsEngine->globalObject().property(functionName).call();
      
      		if (result.isError())
      		{
      			qDebug() << "Running script error! Line:" << result.property("lineNumber").toString() << "\n" << result.toString();
      		}
      	}
      
      	m_jsEngine->collectGarbage();
      
      	delete m_jsEngine;
      	m_jsEngine = nullptr;
      }
      

      Global script class, named MyCore:

      H file:
      class CScriptEngine : public QObject
      {
      	Q_OBJECT
      
      private:
      	CScriptHandle *m_scriptHandle = nullptr;
      
      public:
      	CScriptEngine(CScriptHandle *scriptHandle);
      
      	Q_INVOKABLE void SleepMs(int delay);
      
      	Q_INVOKABLE void Debug(const QString &text);
      
      	Q_INVOKABLE QJSValue GenerateObjectWithArray();
      };
      
      
      
      CPP file:
      CScriptEngine::CScriptEngine(CScriptHandle *scriptHandle)
      : QObject(nullptr), m_scriptHandle(scriptHandle)
      {
      }
      
      void CScriptEngine::SleepMs(int delay)
      {
      	QApplication::processEvents();
      	thread()->msleep(delay);
      	QApplication::processEvents();
      	m_scriptHandle->m_jsEngine->collectGarbage();
      	QApplication::processEvents();
      }
      
      void CScriptEngine::Debug(const QString &text)
      {
      	qDebug() << text;
      }
      
      QJSValue CScriptEngine::GenerateObjectWithArray()
      {
      	return m_scriptHandle->AddNewScriptObject(new CScriptObject());
      }
      

      Test class with big int array (to track memory allocation/cleanup with the built-in garbage collector):

      H file:
      class CScriptObject : public QObject
      {
      	Q_OBJECT
      
      public:
      	Q_INVOKABLE CScriptObject();
      	Q_INVOKABLE CScriptObject(const CScriptObject &so) :QObject(nullptr) {};
      	virtual ~CScriptObject();
      
      	int m_array[50000];
      };
      Q_DECLARE_METATYPE(CScriptObject);
      
      CPP file:
      int g_scriptObjectsCount = 0;
      
      CScriptObject::CScriptObject()
      : QObject(nullptr)
      {
      	g_scriptObjectsCount++;
      	qDebug() << "new CScriptObject, total:" << g_scriptObjectsCount;
      }
      
      CScriptObject::~CScriptObject()
      {
      	g_scriptObjectsCount--;
      	qDebug() << "delete CScriptObject, total:" << g_scriptObjectsCount;
      }

      JavaScript script to run - main:

      function main()
      {
         while (true)
         {
            MyCore.Debug("test");
            CreateSOA();
            MyCore.SleepMs(1000);
         }
      }
      
      function CreateSOA()
      {
         for (var i = 0; i < 10000; i++)
         {
            MyCore.GenerateObjectWithArray();
         }
      }
      

      Also, I tried to call gc() in the CreateSOA() function, but the garbage collector did not work after the fact.

       

      The script runs in a separate thread (at the main thread - the save behavior):

      QtConcurrent::run([this]()
      {
      	m_scriptHandle = new CScriptHandle();
      	m_scriptHandle->RunEngine("main", ui->pte_Script->toPlainText());
      	delete m_scriptHandle;
      	m_scriptHandle = nullptr;
      });
      

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            qtqmlteam Qt Qml Team User
            rugaru Timur Mustakimov
            Votes:
            1 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

              Created:
              Updated:

              Gerrit Reviews

                There are no open Gerrit changes