Details
-
Bug
-
Resolution: Unresolved
-
P2: Important
-
5.15.2
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; });