Skip to content
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

iox-#590 Remove error handler calls from hoofs #1027

Closed
Show file tree
Hide file tree
Changes from 12 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
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ codebase follows these rules, things are work in progress.
our code may contain additions which are not compatible with the STL (e.g. `iox::cxx::vector::emplace_back()`
does return a bool)
7) **Always use `iox::log::Logger`**, instead of `printf()`
8) **Always use `iox::ErrorHandler()`**, when an error occurs that cannot or shall not be propagated via an
`iox::cxx::expected`, the `iox::ErrorHandler()` shall be used; exceptions are not allowed
8) **Always use `iox::ErrorHandler()` or `cxx::Expects`/`cxx::Ensures`**, when an error occurs that cannot or shall not be propagated via an
`iox::cxx::expected`
9) **Not more than two-level nested namespaces**, three-level nested namespace can be used sparsely

See [error-handling.md](https://github.com/eclipse-iceoryx/iceoryx/blob/master/doc/design/error-handling.md) for additional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ TEST(c2cpp_enum_translation_test, SubscriberState)
// the clang sanitizer detects this successfully and this leads to termination, and with this the test fails
#if !defined(__clang__)
iox::Error errorValue = iox::Error::kNO_ERROR;
auto errorHandlerGuard = iox::ErrorHandler::setTemporaryErrorHandler(
mossmaurice marked this conversation as resolved.
Show resolved Hide resolved
[&](const iox::Error e, const std::function<void()>, const iox::ErrorLevel) { errorValue = e; });
auto errorHandlerGuard = iox::ErrorHandlerMock::setTemporaryErrorHandler<iox::Error>(
mossmaurice marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though I suggested ErrorHandlerMock and I introduce a LoggerMock, I'm not fully convinced by this name. This is not a request to change it, because currently I can't suggest a better name but if you or another reviewer can find one I would highly appreciate it ... and while writing this, TestingErrorHandler/testing::ErrorHandler came to my mind. What do you think, is this better or worse?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about this also back and forth. My conclusion was: The ErrorHandlerMock is not really a mock more of a ErrorHandlerHook. I kept the name as the term Mock will trigger a "Hey this is only for tests"-thought when developer read it. That's why I think we should both keep LoggerMock and ErrorHandlerMock. Other names could be misleading.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm a bit torn on this but like I said, I don't have an idea where I would say it is much better than the current naming.

Copy link
Contributor

@MatthiasKillat MatthiasKillat Feb 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it has full functionality and is an error handler used in tests it should not be called a Mock of any kind. I think TestingErrorHandler or TestErrorHandler is better for this reason.

[&](const iox::Error e, const iox::ErrorLevel) { errorValue = e; });
EXPECT_EQ(c2cpp::queueFullPolicy(static_cast<iox_QueueFullPolicy>(-1)),
iox::popo::QueueFullPolicy::DISCARD_OLDEST_DATA);
EXPECT_THAT(errorValue, Eq(iox::Error::kBINDING_C__UNDEFINED_STATE_IN_IOX_QUEUE_FULL_POLICY));
Expand Down Expand Up @@ -95,8 +95,8 @@ TEST(c2cpp_enum_translation_test, SubscriberEvent)
// the clang sanitizer detects this successfully and this leads to termination, and with this the test fails
#if !defined(__clang__)
iox::Error errorValue = iox::Error::kNO_ERROR;
auto errorHandlerGuard = iox::ErrorHandler::setTemporaryErrorHandler(
[&](const iox::Error e, const std::function<void()>, const iox::ErrorLevel) { errorValue = e; });
auto errorHandlerGuard = iox::ErrorHandlerMock::setTemporaryErrorHandler<iox::Error>(
Copy link
Contributor

@MatthiasKillat MatthiasKillat Feb 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except that std::function is gone this does not look and feel better (naming with Mock, template arg) to use. We can fix this later of course but really need to think about how this could be used in a nice way.

[&](const iox::Error e, const iox::ErrorLevel) { errorValue = e; });
EXPECT_EQ(c2cpp::subscriberTooSlowPolicy(static_cast<iox_SubscriberTooSlowPolicy>(-1)),
iox::popo::SubscriberTooSlowPolicy::DISCARD_OLDEST_DATA);
EXPECT_THAT(errorValue, Eq(iox::Error::kBINDING_C__UNDEFINED_STATE_IN_IOX_SUBSCRIBER_TOO_SLOW_POLICY));
Expand Down
2 changes: 2 additions & 0 deletions iceoryx_binding_c/test/test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#ifndef IOX_DDS_GATEWAY_TEST_TEST_HPP
#define IOX_DDS_GATEWAY_TEST_TEST_HPP

#include "iceoryx_hoofs/testing/mocks/error_handler_mock.hpp"
mossmaurice marked this conversation as resolved.
Show resolved Hide resolved

#include <gmock/gmock.h>
#include <gtest/gtest.h>

Expand Down
15 changes: 9 additions & 6 deletions iceoryx_hoofs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ if(GTest_FOUND) # only GTest_FOUND, just in case someone want's to use iceoryx_h
testing/mocks/time_mock.cpp
testing/timing_test.cpp
testing/compile_test.cpp
testing/error_handler_mock.cpp
)

add_library(iceoryx_hoofs_testing::iceoryx_hoofs_testing ALIAS iceoryx_hoofs_testing)
Expand All @@ -98,12 +99,14 @@ if(GTest_FOUND) # only GTest_FOUND, just in case someone want's to use iceoryx_h
$<INSTALL_INTERFACE:include/${PREFIX}>
)

target_link_libraries(iceoryx_hoofs_testing PRIVATE
${CODE_COVERAGE_LIBS}
iceoryx_hoofs
GTest::gtest
GTest::gmock
${CMAKE_DL_LIBS}
target_link_libraries(iceoryx_hoofs_testing
PUBLIC
GTest::gtest
GTest::gmock
PRIVATE
${CODE_COVERAGE_LIBS}
iceoryx_hoofs
${CMAKE_DL_LIBS}
)

if(LINUX)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2019 - 2020 by Robert Bosch GmbH. All rights reserved.
// Copyright (c) 2020 - 2021 by Apex.AI Inc. All rights reserved.
// Copyright (c) 2020 - 2022 by Apex.AI Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -17,20 +17,26 @@
#ifndef IOX_HOOFS_ERROR_HANDLING_ERROR_HANDLING_HPP
#define IOX_HOOFS_ERROR_HANDLING_ERROR_HANDLING_HPP

/// @todo #590 rename this file to error_handler.hpp
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #1028

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hard. Furthermore #1028 states the following: Separate errors of posh and C binding and as it seems currently the include guards, include headers, cmake files are not yet adjusted there. Please create a separate PR for this the other one is already huge.

Copy link
Contributor Author

@mossmaurice mossmaurice Feb 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the 2nd thought, I think what we need to do is separate the ErrorHandler from error_handling.hpp. iceoryx_hoofs's error_handling.hpp will have only one entry EXPECTS_ENSURES_FAILED. So in the end:

  • error_handler.hpp (hoofs)
  • error_handling.hpp (hoofs)
  • error_handling.hpp (posh)
  • error_handling.hpp (C binding)

Added to #1099


#include "iceoryx_hoofs/cxx/generic_raii.hpp"
#include "iceoryx_hoofs/cxx/vector.hpp"
#include "iceoryx_hoofs/cxx/type_traits.hpp"
#include "iceoryx_hoofs/log/logger.hpp"
#include "iceoryx_hoofs/log/logging.hpp"
#include "iceoryx_hoofs/log/logmanager.hpp"

#include <cassert>
#include <functional>
#include <iostream>
#include <mutex>
#include <sstream>


namespace iox
{
// clang-format off
#define ICEORYX_ERRORS(error) \
error(NO_ERROR)\
error(FILEREADER__FAILED_TO_OPEN_FILE) \
error(POSH__ROUDI_PROCESS_SHUTDOWN_FAILED) \
error(POSH__ROUDI_PROCESS_SEND_VIA_IPC_CHANNEL_FAILED)\
error(POSH__RUNTIME_FACTORY_IS_NOT_SET) \
Expand Down Expand Up @@ -141,8 +147,6 @@ namespace iox
error(ICEORYX_ROUDI_MEMORY_MANAGER__ROUDI_STILL_RUNNING) \
error(ICEORYX_ROUDI_MEMORY_MANAGER__FAILED_TO_ADD_PORTPOOL_MEMORY_BLOCK) \
error(ICEORYX_ROUDI_MEMORY_MANAGER__FAILED_TO_ADD_MANAGEMENT_MEMORY_BLOCK) \
error(MQ_UNKNOWN_MSG) \
error(MQ_INVALID_MSG) \
error(IPC_INTERFACE__UNABLE_TO_CREATE_APPLICATION_CHANNEL) \
error(IPC_INTERFACE__REG_ROUDI_NOT_AVAILABLE) \
error(IPC_INTERFACE__REG_UNABLE_TO_WRITE_TO_ROUDI_CHANNEL) \
Expand All @@ -151,11 +155,6 @@ namespace iox
error(IPC_INTERFACE__CHECK_MQ_MAPS_TO_FILE) \
error(IPC_INTERFACE__APP_WITH_SAME_NAME_STILL_RUNNING) \
error(IPC_INTERFACE__COULD_NOT_ACQUIRE_FILE_LOCK) \
error(POSIX_WRAPPER__FAILED_TO_CREATE_SEMAPHORE) \
error(POSIX_TIMER__FIRED_TIMER_BUT_STATE_IS_INVALID) \
error(POSIX_TIMER__TIMERPOOL_OVERFLOW) \
error(POSIX_TIMER__INCONSISTENT_STATE) \
error(POSIX_TIMER__CALLBACK_RUNTIME_EXCEEDS_RETRIGGER_TIME) \
error(BINDING_C__UNDEFINED_STATE_IN_IOX_QUEUE_FULL_POLICY) \
error(BINDING_C__UNDEFINED_STATE_IN_IOX_SUBSCRIBER_TOO_SLOW_POLICY) \
error(BINDING_C__PUBLISHER_OPTIONS_NOT_INITIALIZED) \
Expand All @@ -177,12 +176,6 @@ enum class Error : uint32_t
ICEORYX_ERRORS(CREATE_ICEORYX_ERROR_ENUM)
};

/// @brief Convenience stream operator to easily use the Error enum with std::ostream
/// @param[in] stream sink to write the message to
/// @param[in] value to convert to a string literal
/// @return the reference to `stream` which was provided as input parameter
std::ostream& operator<<(std::ostream& stream, Error value) noexcept;

/// @brief the available error levels
/// FATAL
/// - Log message with FATAL
Expand All @@ -209,45 +202,18 @@ enum class ErrorLevel : uint32_t
MODERATE
};

using HandlerFunction = std::function<void(const Error error, const std::function<void()>, const ErrorLevel)>;

/// @brief This handler is needed for unit testing, special debugging cases and
/// other corner cases where we'd like to explicitly suppress the
/// error handling.
class ErrorHandler
{
friend void
errorHandler(const Error error, const std::function<void()>& errorCallBack, const ErrorLevel level) noexcept;

public:
static cxx::GenericRAII setTemporaryErrorHandler(const HandlerFunction& newHandler) noexcept;

static const char* toString(const Error error) noexcept;

protected:
static void reactOnErrorLevel(const ErrorLevel level, const char* errorText) noexcept;

private:
static void defaultHandler(const Error error,
const std::function<void()>& errorCallBack,
const ErrorLevel level = ErrorLevel::FATAL) noexcept;

static const char* ERROR_NAMES[];
static iox::HandlerFunction handler;
/// Needed, if you want to exchange the handler. Remember the old one and call it if it is not your error. The error
/// mock needs to be the last one exchanging the handler in tests.
static std::mutex handler_mutex;
};

/// @brief Howto use the error handler correctly
/// 1.) If the error you would like to handle is not listed in ICEORYX_ERRORS(error)\...
/// macro just add them like:
/// 1.) Use the macro ICEORYX_ERRORS(error)\ to create the enum for your component and
/// add new errors like:
/// error(MODULE_NAME__MY_FUNKY_ERROR)
/// Attention: Create an error after the following convention:
/// MODULE_NAME__A_CLEAR_BUT_SHORT_ERROR_DESCRIPTION
/// And a long name is alright!
///
/// 2.) Call errorHandler(Error::kMODULE_NAME__MY_FUNKY_ERROR);
/// 2.) Specialize the following methods for your NewEnumErrorType:
/// - const char* toString(const NewEnumErrorType error)
///
/// 3.) Call errorHandler(Error::kMODULE_NAME__MY_FUNKY_ERROR);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't recall the decision on the k prefix. Did we decide to remove it? If yes, I guess it will be done in a follow up PR together with the removal of errorCallback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There wasn't a decision on that topic yet. If we will change it, I would for sure do it in a follow-up PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, please keep in mind to let us come to a decision before the callback is removed from the errorHandler since it would fit quite nice with such a PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elBoberido @mossmaurice I think we introduced the k prefix so that it conforms to the autosar guidelines and then we found out that this is actually pretty nice since otherwise we would run into some trouble with our macro.

So whoever does this - be prepared for fun :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elfenpiff that's no problem. One has just to replace #define CREATE_ICEORYX_ERROR_ENUM(name) k##name, with #define CREATE_ICEORYX_ERROR_ENUM(name) name,

/// Please pay attention to the "k" prefix
/// The defaults for errorCallback and ErrorLevel can also be overwritten:
/// errorHandler(
Expand Down Expand Up @@ -276,9 +242,44 @@ class ErrorHandler
/// errorHandler(Error::kTEST__ASSERT_CALLED);
/// ASSERT_TRUE(called);
/// @endcode
/// @tparam[in] Error type of the enum which is used to report the error
/// @todo #590 use enable_if with is_enum
mossmaurice marked this conversation as resolved.
Show resolved Hide resolved
template <typename Error>
void errorHandler(const Error error,
const std::function<void()>& errorCallBack = std::function<void()>(),
const ErrorLevel level = ErrorLevel::FATAL) noexcept;

using HandlerFunction = std::function<void(const uint32_t, const char*, const ErrorLevel)>;

/// @brief This handler is needed for unit testing, special debugging cases and
/// other corner cases where we'd like to explicitly suppress the
/// error handling.
class ErrorHandler
{
template <typename Error>
friend void
errorHandler(const Error error, const std::function<void()>& errorCallBack, const ErrorLevel level) noexcept;

protected:
static void reactOnErrorLevel(const ErrorLevel level, const char* errorText) noexcept;

static void
defaultHandler(const uint32_t error, const char* errorName, const ErrorLevel level = ErrorLevel::FATAL) noexcept;

static iox::HandlerFunction handler;
};

const char* toString(const Error error) noexcept;

/// @todo #590 move the implementations below to .inl
template <typename Error>
inline void errorHandler(const Error error,
const std::function<void()>& errorCallBack IOX_MAYBE_UNUSED,
Copy link
Contributor Author

@mossmaurice mossmaurice Feb 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of the 2nd arg will be done either in #1028 or a follow-up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should create small separate issues for the error handler since I am already a little bit lost. And you have several comments where you state, will be done in this PR or will be done in another follow up or in this issue and so on. And when I look at those PRs, issues etc. either the task is done incompletely or nothing is mentioned in the issue to this particular task.

For me as a reviewer it is hard to track all of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll close this PR and will open an new one based on #1099

const ErrorLevel level) noexcept
{
ErrorHandler::handler(static_cast<typename std::underlying_type<Error>::type>(error), toString(error), level);
}

} // namespace iox

#endif // IOX_HOOFS_ERROR_HANDLING_ERROR_HANDLING_HPP
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
#ifndef IOX_HOOFS_CXX_VARIANT_QUEUE_INL
#define IOX_HOOFS_CXX_VARIANT_QUEUE_INL

#include "iceoryx_hoofs/error_handling/error_handling.hpp"

namespace iox
{
namespace cxx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
#ifndef IOX_HOOFS_FILE_READER_FILE_READER_HPP
#define IOX_HOOFS_FILE_READER_FILE_READER_HPP

#include "iceoryx_hoofs/error_handling/error_handling.hpp"

#include <fstream>

namespace iox
Expand Down
58 changes: 9 additions & 49 deletions iceoryx_hoofs/source/error_handling/error_handling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,26 @@

#include "iceoryx_hoofs/error_handling/error_handling.hpp"

#include "iceoryx_hoofs/log/logger.hpp"
#include "iceoryx_hoofs/log/logging.hpp"
#include "iceoryx_hoofs/log/logmanager.hpp"

#include <iostream>
#include <sstream>

namespace iox
{
const char* ErrorHandler::ERROR_NAMES[] = {ICEORYX_ERRORS(CREATE_ICEORYX_ERROR_STRING)};
const char* ERROR_NAMES[] = {ICEORYX_ERRORS(CREATE_ICEORYX_ERROR_STRING)};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is a good idea since it might clash with ERROR_NAMES from posh once the posh errors are moved there. What was the reason to move it out of the class scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a look at #1028. The names will be unique for each module e.g. C_BINDING_ERROR_NAMES[]. Hence no clashes are possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so it will be taken care of when the split actually happens

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have to do some restructuring. Issue #590 states nothing about the error handling, we have several hints that certain tasks are performed in several PRs or in this issue but this is nowhere really tracked or mentioned.

Why is the error handler part of #590 ? (I know why but this is hard to keep track when this is nowhere mentioned)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. I'll close this PR here and re-open it as part of a new issue.


// NOLINTNEXTLINE(cert-err58-cpp) ErrorHander only used in tests
HandlerFunction ErrorHandler::handler = {ErrorHandler::defaultHandler};
iox::HandlerFunction ErrorHandler::handler = {ErrorHandler::defaultHandler};

std::mutex ErrorHandler::handler_mutex;

std::ostream& operator<<(std::ostream& stream, Error value) noexcept
const char* toString(const Error error) noexcept
{
stream << ErrorHandler::toString(value);
return stream;
return ERROR_NAMES[static_cast<typename std::underlying_type<Error>::type>(error)];
}

void ErrorHandler::defaultHandler(const Error error,
const std::function<void()>& errorCallBack,
void ErrorHandler::defaultHandler(const uint32_t error IOX_MAYBE_UNUSED,
const char* errorName,
const ErrorLevel level) noexcept
{
if (errorCallBack)
{
errorCallBack();
}
else
{
std::stringstream ss;
ss << "ICEORYX error! " << error;
std::stringstream ss;
ss << "ICEORYX error! " << errorName;

reactOnErrorLevel(level, ss.str().c_str());
}
reactOnErrorLevel(level, ss.str().c_str());
}

void ErrorHandler::reactOnErrorLevel(const ErrorLevel level, const char* errorText) noexcept
Expand All @@ -76,27 +59,4 @@ void ErrorHandler::reactOnErrorLevel(const ErrorLevel level, const char* errorTe
}
}

cxx::GenericRAII ErrorHandler::setTemporaryErrorHandler(const HandlerFunction& newHandler) noexcept
{
return cxx::GenericRAII(
[&newHandler] {
std::lock_guard<std::mutex> lock(handler_mutex);
handler = newHandler;
},
[] {
std::lock_guard<std::mutex> lock(handler_mutex);
handler = defaultHandler;
});
}


const char* ErrorHandler::toString(const Error error) noexcept
{
return ErrorHandler::ERROR_NAMES[static_cast<uint32_t>(error)];
}

void errorHandler(const Error error, const std::function<void()>& errorCallBack, const ErrorLevel level) noexcept
{
ErrorHandler::handler(error, errorCallBack, level);
}
} // namespace iox
Loading