Skip to content

Make it possible to use Py_Finalize before calling the PythonQt destructor #267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,21 @@ jobs:
PYTHON_VERSION=$(python3 --version | cut -d " " -f 2 | cut -d "." -f1,2) \
PYTHON_DIR=$(which python3 | xargs dirname | xargs dirname)
make -j $(nproc)
PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \
UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \
make check TESTARGS="-platform offscreen"

- name: Run memory tests with sanitizers
run: |
QT_VERSION_FULL=$(qmake -query QT_VERSION)
if [[ "$QT_VERSION_FULL" == 5.12* ]]; then
echo "leak:QPlatformIntegrationFactory::create" >> $PWD/lsan.supp
export LSAN_OPTIONS="suppressions=$PWD/lsan.supp"
fi
FULL_VERSION=$(python3 --version | cut -d " " -f 2 | cut -d "." -f1,2,3)

MIN_VERSION="3.12.0"
MAX_VERSION="3.12.4"
export PYTHONQT_RUN_ONLY_MEMORY_TESTS=1
if printf "%s\n" "$MIN_VERSION" "$FULL_VERSION" "$MAX_VERSION" | sort -V -C; then
unset PYTHONQT_RUN_ONLY_MEMORY_TESTS
fi
PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \
UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=1:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \
PYTHONQT_RUN_ONLY_MEMORY_TESTS=1 \
make check TESTARGS="-platform minimal"
make check

- name: Generate Wrappers
run: |
Expand Down Expand Up @@ -142,7 +142,7 @@ jobs:
"PYTHON_VERSION=${PYTHON_VERSION_SHORT}" "PYTHON_DIR=${PYTHON_DIR}"
make -j $(nproc) && \
PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \
make check TESTARGS="-platform offscreen"
make check

- name: Generate Wrappers
run: |
Expand Down Expand Up @@ -232,7 +232,7 @@ jobs:
make -j $(nproc)
PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \
UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \
make check TESTARGS="-platform offscreen"
make check

- name: Generate Wrappers
if: ${{ contains(matrix.configuration, 'release') }}
Expand Down Expand Up @@ -342,8 +342,8 @@ jobs:
set PYTHONDEVMODE=1
set PYTHONASYNCIODEBUG=1
set PYTHONWARNINGS=error
mingw32-make -j 2 && mingw32-make check "TESTARGS=-platform offscreen" ^
|| set CL=/MP && nmake && nmake check "TESTARGS=-platform offscreen"
mingw32-make -j 2 && mingw32-make check ^
|| set CL=/MP && nmake && nmake check

- name: Generate Wrappers
shell: cmd
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build_latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ jobs:
make -j $(nproc)
PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \
UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \
make check TESTARGS="-platform offscreen"
make check

- name: Build PythonQt Windows
shell: cmd
Expand All @@ -125,4 +125,4 @@ jobs:
set PYTHONDEVMODE=1
set PYTHONASYNCIODEBUG=1
set PYTHONWARNINGS=error
nmake && nmake check "TESTARGS=-platform offscreen"
nmake && nmake check
47 changes: 43 additions & 4 deletions src/PythonQt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,17 @@ void PythonQt::init(int flags, const QByteArray& pythonQtModuleName)
void PythonQt::cleanup()
{
if (_self) {
_self->removeSignalHandlers();
delete _self;
_self = nullptr;
}
}

void PythonQt::preCleanup()
{
_self->priv()->preCleanup();
}

PythonQt* PythonQt::self() { return _self; }

PythonQt::PythonQt(int flags, const QByteArray& pythonQtModuleName)
Expand All @@ -348,6 +354,12 @@ PythonQt::PythonQt(int flags, const QByteArray& pythonQtModuleName)
Py_Initialize();
}

#if defined(PYTHONQT_FULL_THREAD_SUPPORT) && PY_VERSION_HEX < 0x03090000
if (!PyEval_ThreadsInitialized()) {
PyEval_InitThreads();
}
#endif

// add our own python object types for qt object slots
if (PyType_Ready(&PythonQtSlotFunction_Type) < 0) {
std::cerr << "could not initialize PythonQtSlotFunction_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl;
Expand Down Expand Up @@ -414,9 +426,9 @@ PythonQtPrivate::~PythonQtPrivate() {
delete _defaultImporter;
_defaultImporter = nullptr;

{
qDeleteAll(_knownClassInfos);
}
qDeleteAll(_knownClassInfos);
_knownClassInfos.clear();
PythonQtClassInfo::clearInteralStaticData();

PythonQtMethodInfo::cleanupCachedMethodInfos();
PythonQtArgumentFrame::cleanupFreeList();
Expand Down Expand Up @@ -1558,6 +1570,16 @@ PythonQtClassInfo* PythonQtPrivate::currentClassInfoForClassWrapperCreation()
return info;
}

void PythonQtPrivate::preCleanup()
{
_pySourceFileLoader = nullptr;
_pySourcelessFileLoader = nullptr;
_pyEnsureFuture = nullptr;
_pyFutureClass = nullptr;
_pyTaskDoneCallback = nullptr;
_pythonQtModule = nullptr;
}

void PythonQtPrivate::addDecorators(QObject* o, int decoTypes)
{
o->setParent(this);
Expand Down Expand Up @@ -2353,7 +2375,7 @@ const QMetaObject* PythonQtPrivate::buildDynamicMetaObject(PythonQtClassWrapper*
}
if (needsMetaObject) {
type->_dynamicClassInfo->_dynamicMetaObject = builder.toMetaObject();
type->_dynamicClassInfo->_classInfo = new PythonQtClassInfo();
type->_dynamicClassInfo->_classInfo.reset(new PythonQtClassInfo());
type->_dynamicClassInfo->_classInfo->setupQObject(type->_dynamicClassInfo->_dynamicMetaObject);
} else {
// we don't need an own meta object, just use the one from our base class
Expand Down Expand Up @@ -2635,6 +2657,23 @@ PythonQtClassInfo* PythonQtPrivate::getClassInfo( const QByteArray& className )
}
}
}

if (!result) {
bool ambiguity = false;
for(auto &&key: _knownClassInfos.keys()) {
if (key.indexOf(QByteArray("::") + className) >= 0) {
if (!result) {
result = _knownClassInfos.value(key);
} else {
ambiguity = true;
std::cerr << "Multiple candidates found for" << '\n';
}
}
}
if (ambiguity) {
return nullptr;
}
}
return result;
}

Expand Down
4 changes: 4 additions & 0 deletions src/PythonQt.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ class PYTHONQT_EXPORT PythonQt : public QObject {
//! get the singleton instance
static PythonQt* self();

static void preCleanup();

//@}

//! defines the object types for introspection
Expand Down Expand Up @@ -662,6 +664,8 @@ class PYTHONQT_EXPORT PythonQtPrivate : public QObject {
PythonQtPrivate();
~PythonQtPrivate() override;

void preCleanup();

enum DecoratorTypes {
StaticDecorator = 1,
ConstructorDecorator = 2,
Expand Down
50 changes: 9 additions & 41 deletions src/PythonQtClassInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ PythonQtClassInfo::~PythonQtClassInfo()
if (_destructor) {
_destructor->deleteOverloadsAndThis();
}
Q_FOREACH(PythonQtSlotInfo* info, _decoratorSlots) {
info->deleteOverloadsAndThis();
for(auto &&info: _decoratorSlots) {
info->deleteOverloadsAndThis();
}
}

Expand Down Expand Up @@ -290,9 +290,7 @@ bool PythonQtClassInfo::lookForEnumAndCache(const QMetaObject* meta, const char*
if (escapeReservedNames(e.key(j)) == memberName) {
PyObject* enumType = findEnumWrapper(e.name());
if (enumType) {
PythonQtObjectPtr enumValuePtr;
enumValuePtr.setNewRef(PythonQtPrivate::createEnumValueInstance(enumType, e.value(j)));
PythonQtMemberInfo newInfo(enumValuePtr);
PythonQtMemberInfo newInfo(PythonQtPrivate::createEnumValueInstance(enumType, e.value(j)));
_cachedMembers.insert(memberName, newInfo);
#ifdef PYTHONQT_DEBUG
std::cout << "caching enum " << memberName << " on " << meta->className() << std::endl;
Expand Down Expand Up @@ -875,7 +873,7 @@ void PythonQtClassInfo::createEnumWrappers(const QMetaObject* meta)
for (int i = meta->enumeratorOffset();i<meta->enumeratorCount();i++) {
QMetaEnum e = meta->enumerator(i);
PythonQtObjectPtr p;
p.setNewRef(PythonQtPrivate::createNewPythonQtEnumWrapper(e.name(), _pythonQtClassWrapper));
p.setNewRef(PythonQtPrivate::createNewPythonQtEnumWrapper(e.name(), _pythonQtClassWrapper.object()));
// add enum values to the enum type itself, in case enum value names are so generic
// that they are not unique
for (int j = 0; j < e.keyCount(); j++) {
Expand Down Expand Up @@ -1043,6 +1041,11 @@ void PythonQtClassInfo::addGlobalNamespaceWrapper(PythonQtClassInfo* namespaceWr
_globalNamespaceWrappers.insert(0, namespaceWrapper);
}

void PythonQtClassInfo::clearInteralStaticData()
{
_globalNamespaceWrappers.clear();
}

void PythonQtClassInfo::updateRefCountingCBs()
{
if (!_refCallback) {
Expand Down Expand Up @@ -1116,38 +1119,3 @@ bool PythonQtClassInfo::supportsRichCompare()
}
return (_typeSlots & PythonQt::Type_RichCompare);
}

//-------------------------------------------------------------------------

PythonQtMemberInfo::PythonQtMemberInfo( PythonQtSlotInfo* info ) : _slot(info)
{
if (info->metaMethod()->methodType() == QMetaMethod::Signal) {
_type = Signal;
} else {
_type = Slot;
}
_enumValue = nullptr;
_pythonType = nullptr;
}

PythonQtMemberInfo::PythonQtMemberInfo( const PythonQtObjectPtr& enumValue )
{
_type = EnumValue;
_slot = nullptr;
_enumValue = enumValue;
_pythonType = nullptr;
}

PythonQtMemberInfo::PythonQtMemberInfo( const QMetaProperty& prop )
{
_type = Property;
_slot = nullptr;
_property = prop;
_enumValue = nullptr;
_pythonType = nullptr;
}

PythonQtDynamicClassInfo::~PythonQtDynamicClassInfo()
{
delete _classInfo;
}
42 changes: 24 additions & 18 deletions src/PythonQtClassInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,37 +41,40 @@
#include <QByteArray>
#include <QList>

class PythonQtSlotInfo;
class PythonQtClassInfo;
#include <PythonQtMethodInfo.h>

struct PythonQtDynamicClassInfo
{
PythonQtDynamicClassInfo() { _dynamicMetaObject = nullptr; _classInfo = nullptr; }
~PythonQtDynamicClassInfo();

const QMetaObject* _dynamicMetaObject;
PythonQtClassInfo* _classInfo;
const QMetaObject* _dynamicMetaObject {};
QScopedPointer<PythonQtClassInfo> _classInfo;
};

struct PythonQtMemberInfo {
enum Type {
Invalid, Slot, Signal, EnumValue, EnumWrapper, Property, NestedClass, NotFound
};

PythonQtMemberInfo():_type(Invalid),_slot(nullptr),_pythonType(nullptr),_enumValue(nullptr) { }
PythonQtMemberInfo() = default;

PythonQtMemberInfo(PythonQtSlotInfo* info);
explicit PythonQtMemberInfo(PythonQtSlotInfo* info)
: _type(info->metaMethod()->methodType() == QMetaMethod::Signal? Signal : Slot)
, _slot(info)
{}

PythonQtMemberInfo(const PythonQtObjectPtr& enumValue);
explicit PythonQtMemberInfo(PyObject* enumValue)
: _type (EnumValue), _enumValue(enumValue)
{}

PythonQtMemberInfo(const QMetaProperty& prop);
explicit PythonQtMemberInfo(const QMetaProperty& prop)
: _type (Property), _property(prop)
{}

Type _type;
Type _type { Invalid };

// TODO: this could be a union...
PythonQtSlotInfo* _slot;
PyObject* _pythonType;
PythonQtObjectPtr _enumValue;
PythonQtSlotInfo* _slot {};
PyObject* _pythonType {};
PyObject* _enumValue {};
QMetaProperty _property;
};

Expand Down Expand Up @@ -177,10 +180,10 @@ class PYTHONQT_EXPORT PythonQtClassInfo {
void addParentClass(const ParentClassInfo& info) { _parentClasses.append(info); }

//! set the associated PythonQtClassWrapper (which handles instance creation of this type)
void setPythonQtClassWrapper(PyObject* obj) { _pythonQtClassWrapper = obj; }
void setPythonQtClassWrapper(PyObject* obj) { _pythonQtClassWrapper.setNewRef(obj); }

//! get the associated PythonQtClassWrapper (which handles instance creation of this type)
PyObject* pythonQtClassWrapper() { return _pythonQtClassWrapper; }
PyObject* pythonQtClassWrapper() { return _pythonQtClassWrapper.object(); }

//! set the shell set instance wrapper cb
void setShellSetInstanceWrapperCB(PythonQtShellSetInstanceWrapperCB* cb) {
Expand Down Expand Up @@ -244,6 +247,9 @@ class PYTHONQT_EXPORT PythonQtClassInfo {
//! Add a wrapper that contains global enums
static void addGlobalNamespaceWrapper(PythonQtClassInfo* namespaceWrapper);

//! Clear all statically allocated caches and wrappers
static void clearInteralStaticData();

private:
void updateRefCountingCBs();

Expand Down Expand Up @@ -294,7 +300,7 @@ class PYTHONQT_EXPORT PythonQtClassInfo {
QObject* _decoratorProvider;
PythonQtQObjectCreatorFunctionCB* _decoratorProviderCB;

PyObject* _pythonQtClassWrapper;
PythonQtObjectPtr _pythonQtClassWrapper;

PythonQtShellSetInstanceWrapperCB* _shellSetInstanceWrapperCB;

Expand Down
2 changes: 1 addition & 1 deletion src/PythonQtInstanceWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ static PyObject *PythonQtInstanceWrapper_getattro(PyObject *obj,PyObject *name)
PythonQtClassInfo* classInfo = nullptr;
PythonQtClassWrapper* classType = (PythonQtClassWrapper*)Py_TYPE(wrapper);
while (classType->_dynamicClassInfo) {
classInfo = classType->_dynamicClassInfo->_classInfo;
classInfo = classType->_dynamicClassInfo->_classInfo.data();
if (classInfo) {
PythonQtMemberInfo member = classInfo->member(attributeName);
if (member._type == PythonQtMemberInfo::Signal) {
Expand Down
Loading
Loading