[odb-users] EXC_BAD_ACCESS in recursion use

Tschoche.Jonas at ddv-mediengruppe.de Tschoche.Jonas at ddv-mediengruppe.de
Wed Feb 5 09:04:08 EST 2020


Yes the project runs at macos. I use ODB with mysql (C++17). Its not easy to explain. I have a serializer on the odb database classes to generate a json.
I checked already the code for memory leaks. I measure the memory consume util the crash, it was ~20mb. How can it run out of stack?

Here an example (I removed the preprocessor stuff that is used for serialize):

    class Device : public BaseEntity, public ISerializeable<Device> {
    public:
        std::string DeviceID;

        #pragma db null
        odb::lazy_shared_ptr<DeviceGroup> DeviceGroupID;
    };

ISerializeable.hpp:
    template <class T>
    class ISerializeable : public Jsonable<T> {};

    Jsonable.hpp:
    class Jsonable : public IJsonable {
    public:
        [[nodiscard]] Json::Value toJson() const {
            return JsonUtils::toJson(static_cast<const T&>(*this));
        }
    }

JsonUtils.hpp:

    template<typename T>
    static Json::Value toJson(const T& object) {
        Json::Value root;
        Json::Value data;

        constexpr auto nbProperties = std::tuple_size<decltype(T::properties)>::value;

        for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
            constexpr auto property = std::get<i>(T::properties);

            data[property.name] = Converts::simplify(object, object.*(property.member));
        });

        root[T::objectname] = data;

        return root;
    }

Converts::simplify:

- It’s a convertclass with enable if
-> relevant methods:

template <typename T>
static Json::Value simplify(IEntity const& entity, T const& obj, typename std::enable_if<std::__and_<notstd::is_container<T>, is_odb_weak_pointer<typename T::value_type>>::value, T>::type* = 0) {
        return SerializeTraits::containerToSimple<Json::Value>(entity, obj);
}

template <typename T>
static Json::Value simplify(IEntity const& entity, T const& obj, typename std::enable_if<std::__and_<notstd::is_container<T>, is_odb_shared_pointer<typename T::value_type>>::value, T>::type* = 0) {
        return SerializeTraits::containerToSimple<Json::Value>(entity, obj);
}

template <typename T>
static Json::Value simplify(IEntity const& entity, T const& obj, typename std::enable_if<is_odb_shared_pointer<T>::value, T>::type* = 0) {
    return SerializeTraits::nestedObjectToSimple<Json::Value>(entity, obj);
}

template <typename T>
static Json::Value simplify(IEntity const& entity, T const& obj, typename std::enable_if<is_odb_unique_pointer<T>::value, T>::type* = 0) {
    return SerializeTraits::nestedObjectToSimple<Json::Value>(entity, obj);
}

SerializeTraits.hpp:
- Here is the relevant class:

template <typename SERIALIZER, typename ENTITY, typename T>
    static SERIALIZER nestedObjectToSimple(
            ENTITY& currentReferenceObj,
            T const& nestedObj,
            typename std::enable_if<std::is_same<SERIALIZER, Json::Value>::value, T>::type* = 0
    ) {
        std::cout << "-> nestedObjectToSimple::begin::" << typeid(T).name() << " | " << typeid(ENTITY).name() << std::endl;

        bool isValid = false;
        SERIALIZER serializedOut;

        if (!notstd::is_uninitialized(nestedObj)) {
            auto config = EntityConfigurator::instance().getConfig(&currentReferenceObj);

            auto db = config.getDatabase();
            auto mode = config.getNestedSerializeMode();

            if constexpr (is_lazy_odb_ptr<T>::value) {
                auto loadedObj = odbLoadObject(db, nestedObj);

                auto nested_config = EntityConfigurator::instance().getConfig(loadedObj.get());

                isValid = loadedObj != nullptr;
                serializedOut = serializedJsonObject(loadedObj, mode, nested_config);
            }
            if constexpr (is_ptr<T>::value) {
                auto nested_config = EntityConfigurator::instance().getConfig(nestedObj);

                isValid = nestedObj != nullptr;
                serializedOut = serializedJsonObject(nestedObj, mode, nested_config);

            }
        }
        std::cout << "-> nestedObjectToSimple::end" << std::endl;
        return defaultReturn<SERIALIZER>(isValid, serializedOut);
    }

template <typename SERIALIZER, typename ENTITY, typename T>
    static SERIALIZER containerToSimple(
            ENTITY& currentReferenceObj,
            T const& containerObj
    ) {
        SERIALIZER array;

        // for Json::Value
        if constexpr (std::is_same<SERIALIZER, Json::Value>::value) {
            array = Json::Value(Json::arrayValue);

            for (auto &elem : containerObj) {
                array.append(nestedObjectToSimple<SERIALIZER>(currentReferenceObj, elem));
            }
        }
        return array;
    }

    static auto odbLoadObject(std::shared_ptr<odb::core::database>& db, T const& nestedObj, typename std::enable_if<is_lazy_odb_ptr<T>::value, T>::type* = 0) {
        auto conn = db->connection();
        if (!nestedObj.loaded()) {

            // Make only a transaction if needed - we try to use a existing.
            std::shared_ptr<odb::transaction> transaction = nullptr;

            //if (!odb::transaction::has_current()) transaction = std::make_shared<odb::transaction>(conn->begin(), false); // switch for 1. Situation or second (single transaction or multiple)
            std::cout << "LOAD BEGIN" << std::endl;

            if (!notstd::is_uninitialized(nestedObj.get_eager())) {

                auto loaded = nestedObj.load(); // CRASH after a some calls with EXC_BAD_ADDRESS

                std::cout << "LOAD END" << std::endl;

                if (transaction) transaction->commit();

                return loaded;
            }
        }
        return decltype(nestedObj.load())();
    }

Extras:

template <typename T>
struct is_lazy_odb_ptr {
    static constexpr bool value = notstd::satisfies_any_v<T, is_odb_shared_pointer, is_odb_weak_pointer, is_odb_unique_pointer>;
};
template <typename T>
struct is_ptr {
    static constexpr bool value = notstd::satisfies_any_v<T, is_shared_pointer, is_unique_pointer, std::is_pointer>;
};

Calling Code:

    odb::transaction t (DB::instance().get_database()->connection()->begin ());  // << Note transaction
    odb::result r (DB::instance().get_database()->query<CurrentList> (odb::query< CurrentList >::List::ID > 0));

    for (odb::result< CurrentList >::iterator e(r.begin()); e != r.end(); ++e) {
        std::cout << e->toJsonString() << std::endl;
    }

    t.commit ();

Am 05.02.20, 14:09 schrieb "Boris Kolpackov" <boris at codesynthesis.com>:

    Tschoche.Jonas at ddv-mediengruppe.de <Tschoche.Jonas at ddv-mediengruppe.de> writes:
    
    > I serialize my odb databases and use recursion for serialize the
    > relationships. I created a test table with an endless recursion
    > for testing.
    
    This is a very vague description of what you are doing. You will
    need to be more specific and show some concrete code if you want
    us to understand your problem.
    
    
    > On first view the recusion works fine, but after some seconds it
    > crashes with a EXC_BAD_ACCESS in odb.
    
    What platform is this on and which database are you using?
    
    Brief googling seems to indicate this is iOS and the common cause
    seems to be memory management issues. Perhaps your recursion runs
    out of stack and/or memory?
    




More information about the odb-users mailing list