diff --git a/src/dbzero/bindings/python/Memo.cpp b/src/dbzero/bindings/python/Memo.cpp index 572b83c8..7e063870 100644 --- a/src/dbzero/bindings/python/Memo.cpp +++ b/src/dbzero/bindings/python/Memo.cpp @@ -610,8 +610,12 @@ namespace db0::python (*tp_result)->tp_dict = copyDict(base_class->tp_dict); // disable weak-refs (important for Python 3.11.x) (*tp_result)->tp_weaklistoffset = 0; - // explicitly disable instance dict to prevent segfault in Python 3.10 +#if PY_VERSION_HEX < 0x030B0000 // Python < 3.11 + (*tp_result)->tp_dictoffset = MemoImplT::getDictOffset(); +#else + // will use managed dict for Python 3.11+ (*tp_result)->tp_dictoffset = 0; +#endif // replace default __str__ and __repr__ implementations if (base_class->tp_str == PyType_Type.tp_str) { diff --git a/src/dbzero/bindings/python/Memo.hpp b/src/dbzero/bindings/python/Memo.hpp index 03eb80be..e53797d0 100644 --- a/src/dbzero/bindings/python/Memo.hpp +++ b/src/dbzero/bindings/python/Memo.hpp @@ -12,26 +12,16 @@ #include #include "Migration.hpp" #include "MemoTypeDecoration.hpp" +#include "MemoObject.hpp" #include #include #include -namespace db0::object_model - -{ - - class Object; - -} - namespace db0::python { using AccessType = db0::AccessType; - using MemoObject = PyWrapper; - using MemoImmutableObject = PyWrapper; - using MemoAnyObject = PyWrapper; PyObject *PyAPI_wrapPyClass(PyObject *self, PyObject *, PyObject *kwargs); // create a memo object stub diff --git a/src/dbzero/bindings/python/MemoObject.hpp b/src/dbzero/bindings/python/MemoObject.hpp new file mode 100644 index 00000000..a56b4a56 --- /dev/null +++ b/src/dbzero/bindings/python/MemoObject.hpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (c) 2025 DBZero Software sp. z o.o. + +#pragma once + +#include +#include +#include "PyWrapper.hpp" + +namespace db0::object_model + +{ + + class Object; + class ObjectImmutableImpl; + class ObjectAnyImpl; + +} + +namespace db0::python + +{ + +#if PY_VERSION_HEX < 0x030B0000 // Python < 3.11 + // NOTE: since managed dicts were introduced in Python 3.11, we need to use PyWrapperWithDict + using MemoObject = PyWrapperWithDict; + using MemoImmutableObject = PyWrapperWithDict; + using MemoAnyObject = PyWrapperWithDict; +#else + using MemoObject = PyWrapper; + using MemoImmutableObject = PyWrapper; + using MemoAnyObject = PyWrapper; +#endif + +} diff --git a/src/dbzero/bindings/python/PyCommonBase.hpp b/src/dbzero/bindings/python/PyCommonBase.hpp deleted file mode 100644 index 8c120ebf..00000000 --- a/src/dbzero/bindings/python/PyCommonBase.hpp +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later -// Copyright (c) 2025 DBZero Software sp. z o.o. - -#pragma once - -#include -#include "PyWrapper.hpp" -#include - -namespace db0::python - -{ - - // common type for Python counterparts of dbzero object model's objects - using PyCommonBase = PyWrapper; - -} \ No newline at end of file diff --git a/src/dbzero/bindings/python/PyInternalAPI.cpp b/src/dbzero/bindings/python/PyInternalAPI.cpp index 24f07742..0b65c8e7 100644 --- a/src/dbzero/bindings/python/PyInternalAPI.cpp +++ b/src/dbzero/bindings/python/PyInternalAPI.cpp @@ -417,7 +417,7 @@ namespace db0::python // MEMO_OBJECT specialization template <> void dropInstance(PyObject *py_wrapper) { - MemoObject_drop(reinterpret_cast(py_wrapper)); + MemoObject_drop(reinterpret_cast(py_wrapper)); } // DB0_INDEX specialization diff --git a/src/dbzero/bindings/python/PyToolkit.cpp b/src/dbzero/bindings/python/PyToolkit.cpp index 2db2aba8..f24a9966 100644 --- a/src/dbzero/bindings/python/PyToolkit.cpp +++ b/src/dbzero/bindings/python/PyToolkit.cpp @@ -29,7 +29,6 @@ #include #include #include -#include namespace db0::python @@ -801,11 +800,7 @@ namespace db0::python bool PyToolkit::isValid() { return Py_IsInitialized(); } - - bool PyToolkit::hasRefs(ObjectPtr obj_ptr) { - return reinterpret_cast(obj_ptr)->ext().hasRefs(); - } - + bool PyToolkit::hasTagRefs(ObjectPtr obj_ptr) { assert(PyAnyMemo_Check(obj_ptr)); diff --git a/src/dbzero/bindings/python/PyToolkit.hpp b/src/dbzero/bindings/python/PyToolkit.hpp index 50d4c4b9..6b0b71f3 100644 --- a/src/dbzero/bindings/python/PyToolkit.hpp +++ b/src/dbzero/bindings/python/PyToolkit.hpp @@ -11,6 +11,7 @@ #include "PyWorkspace.hpp" #include "PyTypes.hpp" #include "PyLocks.hpp" +#include "MemoObject.hpp" #include #include #include @@ -72,7 +73,7 @@ namespace db0::python template inline static PyWrapper *getWrapperTypeOf(ObjectPtr ptr) { return static_cast *>(ptr); } - + /** * Construct shared type from raw pointer (shared ownership) */ @@ -218,9 +219,6 @@ namespace db0::python // indicate failed operation with a specific value/code static void setError(ObjectPtr err_obj, std::uint64_t err_value); - // Check if the object has reference from other dbzero objects or tags - // NOTE!!! this only works for CommonBase/PyWrapper objects (e.g. all LangCache objects) - static bool hasRefs(ObjectPtr); // Check if the object has references from other language objects (other than LangCache) static bool hasLangRefs(ObjectPtr); // Check if there exist any references except specific number of external references @@ -266,5 +264,5 @@ namespace db0::python static PyWorkspace m_py_workspace; static SafeRMutex m_api_mutex; }; - + } \ No newline at end of file diff --git a/src/dbzero/bindings/python/PyTypeManager.hpp b/src/dbzero/bindings/python/PyTypeManager.hpp index 3fbc2dfb..31315172 100644 --- a/src/dbzero/bindings/python/PyTypeManager.hpp +++ b/src/dbzero/bindings/python/PyTypeManager.hpp @@ -13,6 +13,7 @@ #include "PyTypes.hpp" #include #include +#include "MemoObject.hpp" namespace db0 @@ -53,8 +54,6 @@ namespace db0::python { class MemoTypeDecoration; - using MemoObject = PyWrapper; - using MemoImmutableObject = PyWrapper; /** * The class dedicated to recognition of Python types @@ -69,6 +68,7 @@ namespace db0::python using TypeObjectSharedPtr = typename PyTypes::TypeObjectSharedPtr; using MemoObject = db0::python::MemoObject; using MemoImmutableObject = db0::python::MemoImmutableObject; + using MemoAnyObject = db0::python::MemoAnyObject; using Object = db0::object_model::Object; using ObjectImmutableImpl = db0::object_model::ObjectImmutableImpl; using ObjectAnyImpl = db0::object_model::ObjectAnyImpl; diff --git a/src/dbzero/bindings/python/PyWorkspace.hpp b/src/dbzero/bindings/python/PyWorkspace.hpp index 50e2d392..4ea423f6 100644 --- a/src/dbzero/bindings/python/PyWorkspace.hpp +++ b/src/dbzero/bindings/python/PyWorkspace.hpp @@ -12,6 +12,7 @@ #include "PyWrapper.hpp" #include #include +#include "MemoObject.hpp" namespace db0 { @@ -35,8 +36,6 @@ namespace db0::python { - using MemoObject = PyWrapper; - /** * The class to track python module / fixture associations */ diff --git a/src/dbzero/bindings/python/PyWrapper.hpp b/src/dbzero/bindings/python/PyWrapper.hpp index fc052e7e..832b51b6 100644 --- a/src/dbzero/bindings/python/PyWrapper.hpp +++ b/src/dbzero/bindings/python/PyWrapper.hpp @@ -12,27 +12,26 @@ namespace db0::python { - - struct __PyTypeMould { - PyObject_HEAD - }; - + /** * Adds a mixed-in (but dynamically initialized) * member of type T into the PyObject struct. **/ - template struct PyWrapper: public PyObject + template + struct PyWrapper: public BaseT { + // placeholder for the actual instance (since we're unable to calculate sizeof at compile time) + std::array m_ext_storage; using ExtT = T; inline const T &ext() const { - return *reinterpret_cast((char*)this + sizeof(__PyTypeMould)); + return *reinterpret_cast(&m_ext_storage); } inline T &modifyExt() { // calculate instance offset - auto &result = *reinterpret_cast((char*)this + sizeof(__PyTypeMould)); + auto &result = *reinterpret_cast(&m_ext_storage); // only for ObjectBase derived classes if constexpr (is_object_base) { // the implementation registers the underlying object for detach (on rollback) @@ -43,7 +42,8 @@ namespace db0::python } static constexpr std::size_t sizeOf() { - return sizeof(__PyTypeMould) + sizeof(T); + // adjust size to include actual T size + return sizeof(PyWrapper) + sizeof(T) - sizeof(m_ext_storage); } void destroy() { @@ -68,6 +68,24 @@ namespace db0::python } }; + struct PyObjectWithDict: public PyObject + { + PyObject *m_py_dict = nullptr; + }; + + // This is a wrapper with additional __dict__ slot + // which is required for Python versions before 3.11 (before managed dicts were introduced) + template + struct PyWrapperWithDict: public PyWrapper + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + static constexpr std::size_t getDictOffset() { + return offsetof(PyObjectWithDict, m_py_dict); + } +#pragma GCC diagnostic pop + }; + template struct Shared { std::shared_ptr m_ptr; diff --git a/src/dbzero/bindings/python/types/PyObjectId.hpp b/src/dbzero/bindings/python/types/PyObjectId.hpp index dc57fc49..20b9e145 100644 --- a/src/dbzero/bindings/python/types/PyObjectId.hpp +++ b/src/dbzero/bindings/python/types/PyObjectId.hpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace db0::object_model @@ -23,8 +24,7 @@ namespace db0::object_model namespace db0::python { - - using MemoObject = PyWrapper; + using ListObject = PyWrapper; using IndexObject = PyWrapper; using ObjectId = db0::object_model::ObjectId; diff --git a/src/dbzero/object_model/CommonBase.hpp b/src/dbzero/object_model/CommonBase.hpp deleted file mode 100644 index f0cd3749..00000000 --- a/src/dbzero/object_model/CommonBase.hpp +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later -// Copyright (c) 2025 DBZero Software sp. z o.o. - -#include "ObjectBase.hpp" -#include -#include -#include - -namespace db0::object_model - -{ - -DB0_PACKED_BEGIN - - class DB0_PACKED_ATTR o_common_base: public db0::o_base - { - public: - // common object header - o_unique_header m_header; - }; - - // common base for ObjectBase derived classes - class CommonBase: public ObjectBase, StorageClass::UNDEFINED> - { - public: - }; - -DB0_PACKED_END - -} \ No newline at end of file diff --git a/src/dbzero/object_model/object/ObjectAnyBase.hpp b/src/dbzero/object_model/object/ObjectAnyBase.hpp index 80474529..eeddb8a4 100644 --- a/src/dbzero/object_model/object/ObjectAnyBase.hpp +++ b/src/dbzero/object_model/object/ObjectAnyBase.hpp @@ -65,7 +65,7 @@ namespace db0::object_model using TypeManager = typename LangToolkit::TypeManager; using ObjectStem = ObjectVType; using TypeInitializer = ObjectInitializer::TypeInitializer; - + db0::swine_ptr tryGetFixture() const; db0::swine_ptr getFixture() const; diff --git a/src/dbzero/object_model/value/Member.cpp b/src/dbzero/object_model/value/Member.cpp index da7aa3b0..e9c09ada 100644 --- a/src/dbzero/object_model/value/Member.cpp +++ b/src/dbzero/object_model/value/Member.cpp @@ -648,6 +648,34 @@ namespace db0::object_model functions[static_cast(StorageClass::PACK_2)] = unloadMember; } + template + void unrefMemoObject(db0::swine_ptr &fixture, Address address) + { + auto obj_ptr = fixture->getLangCache().get(address); + if (obj_ptr.get()) { + db0::FixtureLock lock(fixture); + // decref cached instance via language specific wrapper type + auto lang_wrapper = reinterpret_cast(obj_ptr.get()); + auto &object = lang_wrapper->modifyExt(); + object.decRef(false); + if (!object.hasRefs()) { + // NOTE: we'll drop the object immediately on condition it has no language references + if (!LangToolkit::hasLangRefs(*obj_ptr)) { + auto unique_addr = object.getUniqueAddress(); + // drop dbzero instance, replacing it with a "null" placeholder + object.dropInstance(lock); + // might also be removed from lang cache + fixture->getLangCache().erase(unique_addr); + } + } + } else { + T object(fixture, address); + object.decRef(false); + // member will be deleted by GC0 if its ref-count = 0 + } + } + + // Unreference any ObjectBase-derived type (except Memo types) template void unrefObjectBase(db0::swine_ptr &fixture, Address address) { @@ -675,11 +703,12 @@ namespace db0::object_model } } - // OBJECT_REF specialization + // OBJECT_REF specialization (MemoAnyImpl) template <> void unrefMember( db0::swine_ptr &fixture, Value value) { - unrefObjectBase(fixture, value.asAddress()); + using MemoObject = PyToolkit::TypeManager::MemoObject; + unrefMemoObject(fixture, value.asAddress()); } template <> void unrefMember( diff --git a/tests/unit_tests/ObjectTests.cpp b/tests/unit_tests/ObjectTests.cpp index f7804170..be6eafa3 100644 --- a/tests/unit_tests/ObjectTests.cpp +++ b/tests/unit_tests/ObjectTests.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include using namespace std; @@ -132,35 +131,5 @@ namespace tests } workspace.close(); } - - TEST_F( ObjectTest , testObjectCanBeCastToCommonBase ) - { - Workspace workspace("", {}, {}, {}, {}, db0::object_model::initializer()); - auto fixture = workspace.getFixture(prefix_name); - - PosVT::Data data(0); - - using Object = db0::object_model::Object; - // mocked type - std::shared_ptr type = getTestClass(fixture); - { - Object object(fixture, type, std::make_pair(0u, 0u), data, 0); - object.incRef(true); - object.incRef(false); - auto &cut = *reinterpret_cast(&object); - ASSERT_EQ(1u, cut->m_header.m_ref_counter.get().first); - ASSERT_EQ(1u, cut->m_header.m_ref_counter.get().second); - ASSERT_TRUE(cut.hasRefs()); - object.decRef(true); - object.decRef(false); - ASSERT_EQ(0u, cut->m_header.m_ref_counter.get().first); - ASSERT_EQ(0u, cut->m_header.m_ref_counter.get().second); - ASSERT_FALSE(cut.hasRefs()); - // NOTE: incRef preserves the object, otherwise the test would fail on cleanup - object.incRef(true); - } - workspace.close(); - } - } \ No newline at end of file