issue that should be reported as a defect.
% HA_PAUSE_CLIENT_LISTENER_ILLEGAL Pausing multi-threaded HTTP processing failed: %1
-This error message is emitted when attempting to pause HA's HTTP client and
-listener threads from owned thread. This error indicates that a command run on
-the listener threads is trying to use a critical section which would result in
-a dead-lock.
+This error message is emitted when attempting to pause HA's HTTP client or
+listener thread pools from a worker thread. This error indicates that a command
+run on the listener threads is trying to use a critical section which would
+result in a dead-lock.
% HA_RESET_COMMUNICATIONS_FAILED failed to send ha-reset command to %1: %2
This warning message indicates a problem with communication with a HA peer
void
HAService::checkPermissionsClientAndListener() {
- // Since we're used as CS callback we need to suppress
- // any exceptions, unlikely though they may be.
+ // Since this function is used as CS callback all exceptions must be
+ // suppressed (except the @ref MultiThreadingInvalidOperation), unlikely
+ // though they may be.
+ // The @ref MultiThreadingInvalidOperation is propagated to the scope of the
+ // @ref MultiThreadingCriticalSection constructor.
try {
if (client_) {
client_->checkPermissions();
void
HAService::pauseClientAndListener() {
- // Since we're used as CS callback we need to suppress
- // any exceptions, unlikely though they may be.
+ // Since this function is used as CS callback all exceptions must be
+ // suppressed, unlikely though they may be.
try {
if (client_) {
client_->pause();
void
HAService::resumeClientAndListener() {
- // Since we're used as CS callback we need to suppress
- // any exceptions, unlikely though they may be.
+ // Since this function is used as CS callback all exceptions must be
+ // suppressed, unlikely though they may be.
try {
if (client_) {
client_->resume();
/// perform thread pool state transition.
///
/// @throw MultiThreadingInvalidOperation if the state transition is done on
- /// any of the owned threads
+ /// any of the worker threads.
void checkPermissionsClientAndListener();
/// @brief Start the client and(or) listener instances.
StatCmds::statLease4GetHandler(CalloutHandle& handle) {
try {
LeaseStatCmdsImpl impl;
- MultiThreadingCriticalSection sc;
+ MultiThreadingCriticalSection cs;
return (impl.statLease4GetHandler(handle));
} catch (const std::exception& ex) {
StatCmds::statLease6GetHandler(CalloutHandle& handle) {
try {
LeaseStatCmdsImpl impl;
- MultiThreadingCriticalSection sc;
+ MultiThreadingCriticalSection cs;
return (impl.statLease6GetHandler(handle));
} catch (const std::exception& ex) {
/// transition.
///
/// @throw MultiThreadingInvalidOperation if the state transition is done on
- /// any of the owned threads
+ /// any of the worker threads.
void checkPermissions();
/// @brief Starts running the listener's thread pool.
isc::Exception(file, line, what) {}
};
-/// \brief Exception thrown when an owned thread is trying to stop or pause the
+/// \brief Exception thrown when a worker thread is trying to stop or pause the
/// respective thread pool (which would result in a dead-lock).
class MultiThreadingInvalidOperation : public Exception {
public:
/// transition.
///
/// @throw MultiThreadingInvalidOperation if the state transition is done on
- /// any of the owned threads
+ /// any of the worker threads.
void checkPermissions() {
if (thread_pool_) {
thread_pool_->checkPausePermissions();
/// transition.
///
/// @throw MultiThreadingInvalidOperation if the state transition is done on
- /// any of the owned threads
+ /// any of the worker threads.
void checkPermissions();
/// @brief Starts running the client's thread pool, if multi-threaded.
auto id = std::this_thread::get_id();
if (checkThreadId(id)) {
isc_throw(MultiThreadingInvalidOperation, "invalid thread pool state change to "
- << HttpThreadPool::stateToText(state) << " performed by owned thread");
+ << HttpThreadPool::stateToText(state) << " performed by worker thread");
}
}
/// @brief Check current thread permissions to transition to the new PAUSED
/// state.
+ ///
+ /// This function throws @ref MultiThreadingInvalidOperation if the calling
+ /// thread is one of the worker threads. This would prevent a dead-lock if
+ /// the calling thread would try to perform a thread pool state transition
+ /// to PAUSED state.
+ ///
/// @throw MultiThreadingInvalidOperation if the state transition is done on
- /// any of the owned threads
+ /// any of the worker threads.
void checkPausePermissions();
private:
/// @brief Check current thread permissions to transition to the new state.
///
+ /// This function throws @ref MultiThreadingInvalidOperation if the calling
+ /// thread is one of the worker threads. This would prevent a dead-lock if
+ /// the calling thread would try to perform a thread pool state transition.
+ ///
/// @param state The new transition state for the pool.
/// @throw MultiThreadingInvalidOperation if the state transition is done on
- /// any of the owned threads
+ /// any of the worker threads.
void checkPermissions(State state);
/// @brief Check specified thread id against own threads.
///
/// @param state The new transition state for the pool.
/// @throw MultiThreadingInvalidOperation if the state transition is done on
- /// any of the owned threads.
+ /// any of the worker threads.
void setState(State state);
/// @brief Thread-safe fetch of the pool's operational state.
return (request);
}
- /// @brief Test that owned threads are not permitted to change thread pool
+ /// @brief Test that worker threads are not permitted to change thread pool
/// state.
void testIllegalThreadPoolActions() {
ASSERT_THROW(client_->start(), MultiThreadingInvalidOperation);
void
MultiThreadingMgr::exitCriticalSection() {
- checkCallbacksPermissions();
std::lock_guard<std::mutex> lk(mutex_);
if (critical_section_count_ == 0) {
isc_throw(InvalidOperation, "invalid negative value for override");
void
MultiThreadingMgr::checkCallbacksPermissions() {
if (getMode()) {
- for (const auto& cb : cs_callbacks_.getCallbackPairs()) {
+ for (const auto& cb : cs_callbacks_.getCallbackSets()) {
try {
(cb.check_cb_)();
} catch (const isc::MultiThreadingInvalidOperation& ex) {
void
MultiThreadingMgr::callEntryCallbacks() {
if (getMode()) {
- const auto& callbacks = cs_callbacks_.getCallbackPairs();
+ const auto& callbacks = cs_callbacks_.getCallbackSets();
for (auto cb_it = callbacks.begin(); cb_it != callbacks.end(); cb_it++) {
try {
(cb_it->entry_cb_)();
void
MultiThreadingMgr::callExitCallbacks() {
if (getMode()) {
- const auto& callbacks = cs_callbacks_.getCallbackPairs();
+ const auto& callbacks = cs_callbacks_.getCallbackSets();
for (auto cb_it = callbacks.rbegin(); cb_it != callbacks.rend(); cb_it++) {
try {
(cb_it->exit_cb_)();
void
MultiThreadingMgr::addCriticalSectionCallbacks(const std::string& name,
- const CSCallbackPair::Callback& check_cb,
- const CSCallbackPair::Callback& entry_cb,
- const CSCallbackPair::Callback& exit_cb) {
- cs_callbacks_.addCallbackPair(name, check_cb, entry_cb, exit_cb);
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb) {
+ cs_callbacks_.addCallbackSet(name, check_cb, entry_cb, exit_cb);
}
void
MultiThreadingMgr::removeCriticalSectionCallbacks(const std::string& name) {
- cs_callbacks_.removeCallbackPair(name);
+ cs_callbacks_.removeCallbackSet(name);
}
void
}
void
-CSCallbackPairList::addCallbackPair(const std::string& name,
- const CSCallbackPair::Callback& check_cb,
- const CSCallbackPair::Callback& entry_cb,
- const CSCallbackPair::Callback& exit_cb) {
+CSCallbackSetList::addCallbackSet(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb) {
if (name.empty()) {
- isc_throw(BadValue, "CSCallbackPairList - name cannot be empty");
+ isc_throw(BadValue, "CSCallbackSetList - name cannot be empty");
}
if (!check_cb) {
- isc_throw(BadValue, "CSCallbackPairList - check callback for " << name
+ isc_throw(BadValue, "CSCallbackSetList - check callback for " << name
<< " cannot be empty");
}
if (!entry_cb) {
- isc_throw(BadValue, "CSCallbackPairList - entry callback for " << name
+ isc_throw(BadValue, "CSCallbackSetList - entry callback for " << name
<< " cannot be empty");
}
if (!exit_cb) {
- isc_throw(BadValue, "CSCallbackPairList - exit callback for " << name
+ isc_throw(BadValue, "CSCallbackSetList - exit callback for " << name
<< " cannot be empty");
}
- for (auto const& callback : cb_pairs_) {
+ for (auto const& callback : cb_sets_) {
if (callback.name_ == name) {
- isc_throw(BadValue, "CSCallbackPairList - callbacks for " << name
+ isc_throw(BadValue, "CSCallbackSetList - callbacks for " << name
<< " already exist");
}
}
- cb_pairs_.push_back(CSCallbackPair(name, check_cb, entry_cb, exit_cb));
+ cb_sets_.push_back(CSCallbackSet(name, check_cb, entry_cb, exit_cb));
}
void
-CSCallbackPairList::removeCallbackPair(const std::string& name) {
- for (auto it = cb_pairs_.begin(); it != cb_pairs_.end(); ++it) {
+CSCallbackSetList::removeCallbackSet(const std::string& name) {
+ for (auto it = cb_sets_.begin(); it != cb_sets_.end(); ++it) {
if ((*it).name_ == name) {
- cb_pairs_.erase(it);
+ cb_sets_.erase(it);
break;
}
}
}
void
-CSCallbackPairList::removeAll() {
- cb_pairs_.clear();
+CSCallbackSetList::removeAll() {
+ cb_sets_.clear();
}
-const std::list<CSCallbackPair>&
-CSCallbackPairList::getCallbackPairs() {
- return (cb_pairs_);
+const std::list<CSCallbackSet>&
+CSCallbackSetList::getCallbackSets() {
+ return (cb_sets_);
}
} // namespace util
namespace util {
-/// @brief Embodies a named pair of CriticalSection callbacks.
+/// @brief Embodies a named set of CriticalSection callbacks.
///
-/// This class associates with a name, a pair of callbacks, one to be invoked
-/// before CriticalSection entry and exit callbacks to validate current thread
+/// This class associates with a name, a set of callbacks, one to be invoked
+/// before CriticalSection entry and exit callbacks to check current thread
/// permissions to perform such actions, one to be invoked upon CriticalSection
-/// entry and one to be invoked upon CriticalSection exit,
-/// The name allows the pair to be uniquely identified such that they can be
+/// entry and one to be invoked upon CriticalSection exit.
+/// The name allows the set to be uniquely identified such that they can be
/// added and removed as needed.
-struct CSCallbackPair {
+/// The check current thread permissions callback needs to throw
+/// @ref MultiThreadingInvalidOperation if the thread is not allowed to perform
+/// CriticalSection entry and exit. Any other exception thrown by the check
+/// permission callbacks will be silently ignored.
+/// The CriticalSection entry and exit callbacks exceptions will be silently
+/// ignored.
+struct CSCallbackSet {
/// @brief Defines a callback as a simple void() functor.
typedef std::function<void()> Callback;
/// the CriticalSection entry and exit callbacks.
/// @param entry_cb Callback to invoke upon CriticalSection entry.
/// @param exit_cb Callback to invoke upon CriticalSection exit.
- CSCallbackPair(const std::string& name, const Callback& check_cb,
- const Callback& entry_cb, const Callback& exit_cb)
+ CSCallbackSet(const std::string& name, const Callback& check_cb,
+ const Callback& entry_cb, const Callback& exit_cb)
: name_(name), check_cb_(check_cb), entry_cb_(entry_cb),
exit_cb_(exit_cb) {}
Callback exit_cb_;
};
-/// @brief Maintains list of unique CSCallbackPairs.
+/// @brief Maintains list of unique CSCallbackSets.
///
/// The list emphasizes iteration order and speed over
/// retrieval by name. When iterating over the list of
-/// callback pairs, they are returned in the order they were
+/// callback sets, they are returned in the order they were
/// added, not by name.
-class CSCallbackPairList {
+class CSCallbackSetList {
public:
/// @brief Constructor.
- CSCallbackPairList() {}
+ CSCallbackSetList() {}
- /// @brief Adds a callback pair to the list.
+ /// @brief Adds a callback set to the list.
///
/// @param name Name of the callback to add.
/// @param check_cb The check permissions callback to add.
///
/// @throw BadValue if the name is already in the list,
/// the name is blank, or either callback is empty.
- void addCallbackPair(const std::string& name,
- const CSCallbackPair::Callback& check_cb,
- const CSCallbackPair::Callback& entry_cb,
- const CSCallbackPair::Callback& exit_cb);
+ void addCallbackSet(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb);
- /// @brief Removes a callback pair from the list.
+ /// @brief Removes a callback set from the list.
///
/// @param name Name of the callback to remove.
/// If no such callback exists, it simply returns.
- void removeCallbackPair(const std::string& name);
+ void removeCallbackSet(const std::string& name);
/// @brief Removes all callbacks from the list.
void removeAll();
- /// @brief Fetches the list of callback pairs.
- const std::list<CSCallbackPair>& getCallbackPairs();
+ /// @brief Fetches the list of callback sets.
+ const std::list<CSCallbackSet>& getCallbackSets();
private:
- /// @brief The list of callback pairs.
- std::list<CSCallbackPair> cb_pairs_;
+ /// @brief The list of callback sets.
+ std::list<CSCallbackSet> cb_sets_;
};
/// @brief Multi Threading Manager.
/// configured, 0 for unlimited size
void apply(bool enabled, uint32_t thread_count, uint32_t queue_size);
- /// @brief Adds a pair of callbacks to the list of CriticalSection callbacks.
+ /// @brief Adds a set of callbacks to the list of CriticalSection callbacks.
///
/// @note Callbacks must be exception-safe, handling any errors themselves.
///
/// @param exit_cb Callback to invoke upon CriticalSection exit. Cannot be
/// empty.
void addCriticalSectionCallbacks(const std::string& name,
- const CSCallbackPair::Callback& check_cb,
- const CSCallbackPair::Callback& entry_cb,
- const CSCallbackPair::Callback& exit_cb);
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb);
/// @brief Removes the set of callbacks associated with a given name
/// from the list of CriticalSection callbacks.
///
/// Has no effect in single-threaded mode.
///
- /// @note This function swallows exceptions thrown by validate
- /// callbacks without logging to avoid breaking the CS
- /// chain.
+ /// @note This function swallows exceptions thrown by all check permissions
+ /// callbacks without logging to avoid breaking the CS chain, except for the
+ /// @ref MultiThreadingInvalidOperation which needs to be propagated to the
+ /// scope of the @ref MultiThreadingCriticalSection constructor.
/// @throw MultiThreadingInvalidOperation if current thread has no
/// permission to enter CriticalSection.
void checkCallbacksPermissions();
///
/// Has no effect in single-threaded mode.
///
- /// @note This function swallows exceptions thrown by validate
- /// callbacks without logging to avoid breaking the CS
- /// chain.
+ /// @note This function swallows exceptions thrown by all entry callbacks
+ /// without logging to avoid breaking the CS chain.
void callEntryCallbacks();
/// @brief Class method which invokes CriticalSection entry callbacks.
///
/// Has no effect in single-threaded mode.
///
- /// @note This function swallows exceptions thrown by validate
- /// callbacks without logging to avoid breaking the CS
- /// chain.
+ /// @note This function swallows exceptions thrown by all exit callbacks
+ /// without logging to avoid breaking the CS chain.
void callExitCallbacks();
/// @brief Class method stops non-critical processing.
///
- /// Stops the DHCP thread pool if it's running and invokes
- /// all CriticalSection entry callbacks. Has no effect
- /// in single-threaded mode.
+ /// Stops the DHCP thread pool if it's running and invokes all
+ /// CriticalSection entry callbacks. Has no effect in single-threaded mode.
///
- /// @note This function swallows exceptions thrown by exit
- /// callbacks without logging to avoid breaking the CS
- /// chain.
+ /// @note This function swallows exceptions thrown by all exit callbacks
+ /// without logging to avoid breaking the CS chain.
void stopProcessing();
/// @brief Class method (re)starts non-critical processing.
///
- /// Starts the DHCP thread pool according to current configuration,
- /// and invokes all CriticalSection exit callbacks. Has no effect
- /// in single-threaded mode.
+ /// Starts the DHCP thread pool according to current configuration, and
+ /// invokes all CriticalSection exit callbacks. Has no effect in
+ /// single-threaded mode.
///
- /// @note This function swallows exceptions thrown by entry
- /// callbacks without logging to avoid breaking the CS
- /// chain.
+ /// @note This function swallows exceptions thrown by all entry callbacks
+ /// without logging to avoid breaking the CS chain.
void startProcessing();
/// @brief The current multi-threading mode.
///
/// The multi-threading flag: true if multi-threading is enabled, false
- /// otherwise
+ /// otherwise.
bool enabled_;
/// @brief The critical section count.
/// @brief Packet processing thread pool.
ThreadPool<std::function<void()>> thread_pool_;
- /// @brief List of CriticalSection entry and exit callback pairs.
- CSCallbackPairList cs_callbacks_;
+ /// @brief List of CriticalSection entry and exit callback sets.
+ CSCallbackSetList cs_callbacks_;
/// @brief Mutex to protect the internal state.
std::mutex mutex_;
// Cannot add with a blank name.
ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("", [](){}, [](){}, [](){}),
- BadValue, "CSCallbackPairList - name cannot be empty");
+ BadValue, "CSCallbackSetList - name cannot be empty");
// Cannot add with an empty check callback.
ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", nullptr, [](){}, [](){}),
- BadValue, "CSCallbackPairList - check callback for bad cannot be empty");
+ BadValue, "CSCallbackSetList - check callback for bad cannot be empty");
// Cannot add with an empty exit callback.
ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", [](){}, nullptr, [](){}),
- BadValue, "CSCallbackPairList - entry callback for bad cannot be empty");
+ BadValue, "CSCallbackSetList - entry callback for bad cannot be empty");
// Cannot add with an empty exit callback.
ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", [](){}, [](){}, nullptr),
- BadValue, "CSCallbackPairList - exit callback for bad cannot be empty");
+ BadValue, "CSCallbackSetList - exit callback for bad cannot be empty");
// Should be able to add foo.
ASSERT_NO_THROW_LOG(mgr.addCriticalSectionCallbacks("foo", [](){}, [](){}, [](){}));
// Should not be able to add foo twice.
ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("foo", [](){}, [](){}, [](){}),
- BadValue, "CSCallbackPairList - callbacks for foo already exist");
+ BadValue, "CSCallbackSetList - callbacks for foo already exist");
// Should be able to add bar.
ASSERT_NO_THROW_LOG(mgr.addCriticalSectionCallbacks("bar", [](){}, [](){}, [](){}));
void wait() {
auto id = std::this_thread::get_id();
if (checkThreadId(id)) {
- isc_throw(MultiThreadingInvalidOperation, "thread pool stop called by owned thread");
+ isc_throw(MultiThreadingInvalidOperation, "thread pool stop called by worker thread");
}
queue_.wait();
}
bool wait(uint32_t seconds) {
auto id = std::this_thread::get_id();
if (checkThreadId(id)) {
- isc_throw(MultiThreadingInvalidOperation, "thread pool stop called by owned thread");
+ isc_throw(MultiThreadingInvalidOperation, "thread pool stop called by worker thread");
}
return (queue_.wait(seconds));
}
void stopInternal() {
auto id = std::this_thread::get_id();
if (checkThreadId(id)) {
- isc_throw(MultiThreadingInvalidOperation, "thread pool stop called by owned thread");
+ isc_throw(MultiThreadingInvalidOperation, "thread pool stop called by worker thread");
}
queue_.disable();
for (auto thread : threads_) {