gc: Prevent recursing into gc from onDestroyed
We end up in a rather weird state otherwise. Also test that we don't mistakenly collect ObjectWrappers created in onDestroyed. Change-Id: Iab158e5b34510979c8ac9a51a75247a2cee100f3 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
parent
fa69a45b38
commit
de436b8525
|
@ -805,9 +805,14 @@ GCState initCallDestroyObjects(GCStateMachine *that, ExtraData &stateData)
|
||||||
stateData = GCIteratorStorage { that->mm->m_weakValues->begin() };
|
stateData = GCIteratorStorage { that->mm->m_weakValues->begin() };
|
||||||
return CallDestroyObjects;
|
return CallDestroyObjects;
|
||||||
}
|
}
|
||||||
GCState callDestroyObject(GCStateMachine *, ExtraData &stateData)
|
GCState callDestroyObject(GCStateMachine *that, ExtraData &stateData)
|
||||||
{
|
{
|
||||||
PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
|
PersistentValueStorage::Iterator& it = get<GCIteratorStorage>(stateData).it;
|
||||||
|
// destroyObject might call user code, which really shouldn't call back into the gc
|
||||||
|
auto oldState = std::exchange(that->mm->gcBlocked, QV4::MemoryManager::Blockness::InCriticalSection);
|
||||||
|
auto cleanup = qScopeGuard([&]() {
|
||||||
|
that->mm->gcBlocked = oldState;
|
||||||
|
});
|
||||||
// avoid repeatedly hitting the timer constantly by batching iterations
|
// avoid repeatedly hitting the timer constantly by batching iterations
|
||||||
for (int i = 0; i < markLoopIterationCount; ++i) {
|
for (int i = 0; i < markLoopIterationCount; ++i) {
|
||||||
if (!it.p)
|
if (!it.p)
|
||||||
|
@ -1235,8 +1240,9 @@ bool MemoryManager::tryForceGCCompletion()
|
||||||
const bool incrementalGCIsAlreadyRunning = m_markStack != nullptr;
|
const bool incrementalGCIsAlreadyRunning = m_markStack != nullptr;
|
||||||
Q_ASSERT(incrementalGCIsAlreadyRunning);
|
Q_ASSERT(incrementalGCIsAlreadyRunning);
|
||||||
auto oldTimeLimit = std::exchange(gcStateMachine->timeLimit, std::chrono::microseconds::max());
|
auto oldTimeLimit = std::exchange(gcStateMachine->timeLimit, std::chrono::microseconds::max());
|
||||||
while (gcStateMachine->inProgress())
|
while (gcStateMachine->inProgress()) {
|
||||||
gcStateMachine->step();
|
gcStateMachine->step();
|
||||||
|
}
|
||||||
gcStateMachine->timeLimit = oldTimeLimit;
|
gcStateMachine->timeLimit = oldTimeLimit;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import QtQml
|
||||||
|
|
||||||
|
QtObject {}
|
|
@ -12,6 +12,7 @@
|
||||||
#include <private/qqmlengine_p.h>
|
#include <private/qqmlengine_p.h>
|
||||||
#include <private/qv4identifiertable_p.h>
|
#include <private/qv4identifiertable_p.h>
|
||||||
#include <private/qv4arraydata_p.h>
|
#include <private/qv4arraydata_p.h>
|
||||||
|
#include <private/qqmlcomponentattached_p.h>
|
||||||
|
|
||||||
#include <QtQuickTestUtils/private/qmlutils_p.h>
|
#include <QtQuickTestUtils/private/qmlutils_p.h>
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ private slots:
|
||||||
void cleanInternalClasses();
|
void cleanInternalClasses();
|
||||||
void createObjectsOnDestruction();
|
void createObjectsOnDestruction();
|
||||||
void sharedInternalClassDataMarking();
|
void sharedInternalClassDataMarking();
|
||||||
|
void gcTriggeredInOnDestroyed();
|
||||||
};
|
};
|
||||||
|
|
||||||
tst_qv4mm::tst_qv4mm()
|
tst_qv4mm::tst_qv4mm()
|
||||||
|
@ -387,6 +389,75 @@ void tst_qv4mm::sharedInternalClassDataMarking()
|
||||||
QCOMPARE(val.toUInt32(), 42u);
|
QCOMPARE(val.toUInt32(), 42u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_qv4mm::gcTriggeredInOnDestroyed()
|
||||||
|
{
|
||||||
|
QQmlEngine engine;
|
||||||
|
QV4::ExecutionEngine &v4 = *engine.handle();
|
||||||
|
|
||||||
|
QPointer<QObject> testObject = new QObject; // unparented, will be deleted
|
||||||
|
auto cleanup = qScopeGuard([&]() {
|
||||||
|
if (testObject)
|
||||||
|
testObject->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
QQmlComponent component(&engine, testFileUrl("simpleObject.qml"));
|
||||||
|
auto toBeCollected = component.create();
|
||||||
|
QVERIFY(toBeCollected);
|
||||||
|
QJSEngine::setObjectOwnership(toBeCollected, QJSEngine::JavaScriptOwnership);
|
||||||
|
QV4::QObjectWrapper::ensureWrapper(&v4, toBeCollected);
|
||||||
|
QVERIFY(qmlEngine(toBeCollected));
|
||||||
|
QQmlComponentAttached *attached = QQmlComponent::qmlAttachedProperties(toBeCollected);
|
||||||
|
QVERIFY(attached);
|
||||||
|
|
||||||
|
|
||||||
|
QV4::Scope scope(v4.rootContext());
|
||||||
|
QCOMPARE(v4.memoryManager->gcBlocked, QV4::MemoryManager::Unblocked);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// let the gc run up to CallDestroyObjects
|
||||||
|
auto sm = v4.memoryManager->gcStateMachine.get();
|
||||||
|
sm->reset();
|
||||||
|
v4.memoryManager->gcBlocked = QV4::MemoryManager::NormalBlocked;
|
||||||
|
while (sm->state != QV4::GCState::CallDestroyObjects && sm->state != QV4::GCState::Invalid) {
|
||||||
|
QV4::GCStateInfo& stateInfo = sm->stateInfoMap[int(sm->state)];
|
||||||
|
sm->state = stateInfo.execute(sm, sm->stateData);
|
||||||
|
}
|
||||||
|
QCOMPARE(sm->state, QV4::GCState::CallDestroyObjects);
|
||||||
|
|
||||||
|
QV4::ScopedValue val(scope);
|
||||||
|
bool calledOnDestroyed = false;
|
||||||
|
auto con = connect(attached, &QQmlComponentAttached::destruction, this, [&]() {
|
||||||
|
calledOnDestroyed = true;
|
||||||
|
// we trigger uncommon code paths:
|
||||||
|
// create ObjectWrapper in destroyed hadnler
|
||||||
|
auto ddata = QQmlData::get(testObject.get(), false);
|
||||||
|
QVERIFY(!ddata); // we don't have ddata yet (otherwise we'd already have an object wrapper)
|
||||||
|
val = QV4::QObjectWrapper::wrap(&v4, testObject.get());
|
||||||
|
QJSEngine::setObjectOwnership(testObject, QJSEngine::JavaScriptOwnership);
|
||||||
|
|
||||||
|
// and also try to trigger a force gc completion
|
||||||
|
bool gcComplete = v4.memoryManager->tryForceGCCompletion();
|
||||||
|
QVERIFY(!gcComplete);
|
||||||
|
});
|
||||||
|
while (!calledOnDestroyed && sm->state != QV4::GCState::Invalid) {
|
||||||
|
QV4::GCStateInfo& stateInfo = sm->stateInfoMap[int(sm->state)];
|
||||||
|
sm->state = stateInfo.execute(sm, sm->stateData);
|
||||||
|
}
|
||||||
|
QVERIFY(!QTest::currentTestFailed());
|
||||||
|
QObject::disconnect(con);
|
||||||
|
QVERIFY(calledOnDestroyed);
|
||||||
|
|
||||||
|
bool gcComplete = v4.memoryManager->tryForceGCCompletion();
|
||||||
|
QVERIFY(gcComplete);
|
||||||
|
val = QV4::Value::undefinedValue(); // no longer keep a reference on the stack
|
||||||
|
QCOMPARE(sm->state, QV4::GCState::Invalid);
|
||||||
|
QVERIFY(testObject); // must not have be deleted, referenced by val
|
||||||
|
|
||||||
|
gc(v4); // run another gc cycle
|
||||||
|
QVERIFY(!testObject); // now collcted by gc
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_qv4mm)
|
QTEST_MAIN(tst_qv4mm)
|
||||||
|
|
||||||
#include "tst_qv4mm.moc"
|
#include "tst_qv4mm.moc"
|
||||||
|
|
Loading…
Reference in New Issue