mirror of
https://github.com/openappsec/openappsec.git
synced 2025-09-30 03:34:26 +03:00
First release of open-appsec source code
This commit is contained in:
6
core/mainloop/CMakeLists.txt
Normal file
6
core/mainloop/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
if("${PLATFORM_TYPE}" STREQUAL "arm32_openwrt")
|
||||
ADD_DEFINITIONS(-Wno-unused-parameter)
|
||||
endif()
|
||||
add_library(mainloop mainloop.cc coroutine.cc)
|
||||
|
||||
add_subdirectory(mainloop_ut)
|
84
core/mainloop/coroutine.cc
Normal file
84
core/mainloop/coroutine.cc
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "coroutine.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void defaultPull(boost::coroutines2::detail::push_coroutine<void> &) {}
|
||||
|
||||
RoutineWrapper::RoutineWrapper(
|
||||
I_MainLoop::RoutineType _pri,
|
||||
I_MainLoop::Routine func,
|
||||
bool _is_primary,
|
||||
const string &_routine_name)
|
||||
:
|
||||
pri(_pri),
|
||||
pull(defaultPull), // will be replace by `invoke` on the first invokation
|
||||
routine( [func] (pull_type &pull) { invoke(pull, func); } ),
|
||||
is_primary(_is_primary),
|
||||
routine_name(_routine_name)
|
||||
{
|
||||
}
|
||||
|
||||
RoutineWrapper::~RoutineWrapper()
|
||||
{
|
||||
pull = pull_type(defaultPull);
|
||||
}
|
||||
|
||||
bool
|
||||
RoutineWrapper::isActive() const
|
||||
{
|
||||
return static_cast<bool>(routine);
|
||||
}
|
||||
|
||||
bool
|
||||
RoutineWrapper::shouldRun(const I_MainLoop::RoutineType &limit) const
|
||||
{
|
||||
return !is_halt && pri<=limit;
|
||||
}
|
||||
|
||||
void
|
||||
RoutineWrapper::run()
|
||||
{
|
||||
active = this; // Will be used by `invoke` to set the `pull` on the first invokation
|
||||
routine();
|
||||
}
|
||||
|
||||
void
|
||||
RoutineWrapper::yield()
|
||||
{
|
||||
pull();
|
||||
}
|
||||
|
||||
void
|
||||
RoutineWrapper::halt()
|
||||
{
|
||||
is_halt = true;
|
||||
}
|
||||
|
||||
void
|
||||
RoutineWrapper::resume()
|
||||
{
|
||||
is_halt = false;
|
||||
}
|
||||
|
||||
void
|
||||
RoutineWrapper::invoke(pull_type &pull, I_MainLoop::Routine func)
|
||||
{
|
||||
dbgAssert(active != nullptr) << "Trying to invoke without an active routine";
|
||||
active->pull = move(pull); // First invokation (other invokaction will start inside `func`), set the `pull` object
|
||||
func();
|
||||
}
|
||||
|
||||
RoutineWrapper *RoutineWrapper::active = nullptr;
|
63
core/mainloop/coroutine.h
Normal file
63
core/mainloop/coroutine.h
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef __COROUTINE_H__
|
||||
#define __COROUTINE_H__
|
||||
|
||||
#include <boost/coroutine2/all.hpp>
|
||||
|
||||
#include "i_mainloop.h"
|
||||
#include "maybe_res.h"
|
||||
|
||||
class RoutineWrapper
|
||||
{
|
||||
using pull_type = boost::coroutines2::coroutine<void>::pull_type;
|
||||
using push_type = boost::coroutines2::coroutine<void>::push_type;
|
||||
|
||||
public:
|
||||
RoutineWrapper(
|
||||
I_MainLoop::RoutineType _pri,
|
||||
I_MainLoop::Routine func,
|
||||
bool is_primary,
|
||||
const std::string &_routine_name
|
||||
);
|
||||
|
||||
~RoutineWrapper();
|
||||
RoutineWrapper(const RoutineWrapper &) = delete;
|
||||
RoutineWrapper(RoutineWrapper &&) = default;
|
||||
RoutineWrapper & operator=(const RoutineWrapper &) = delete;
|
||||
RoutineWrapper & operator=(RoutineWrapper &&) = default;
|
||||
|
||||
bool isPrimary() { return is_primary; }
|
||||
const std::string & getRoutineName() const { return routine_name; }
|
||||
bool isActive() const;
|
||||
bool shouldRun(const I_MainLoop::RoutineType &limit) const;
|
||||
void run();
|
||||
void yield();
|
||||
void halt();
|
||||
void resume();
|
||||
|
||||
private:
|
||||
static void invoke(pull_type &pull, I_MainLoop::Routine func);
|
||||
static RoutineWrapper *active; // Used by `invoke` to set the value of `pull`
|
||||
|
||||
I_MainLoop::RoutineType pri;
|
||||
// `pull` will hold the object that returns the flow control back to the mainloop.
|
||||
pull_type pull;
|
||||
push_type routine;
|
||||
bool is_primary;
|
||||
bool is_halt = false;
|
||||
std::string routine_name;
|
||||
};
|
||||
|
||||
#endif // __COROUTINE_H__
|
592
core/mainloop/mainloop.cc
Normal file
592
core/mainloop/mainloop.cc
Normal file
@@ -0,0 +1,592 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mainloop.h"
|
||||
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "coroutine.h"
|
||||
#include "singleton.h"
|
||||
#include "debug.h"
|
||||
#include "i_time_get.h"
|
||||
#include "report/log_rest.h"
|
||||
#include "mainloop/mainloop_metric.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_MAINLOOP);
|
||||
|
||||
bool fini_signal_flag = false;
|
||||
|
||||
class MainloopStop {};
|
||||
|
||||
class MainloopComponent::Impl : Singleton::Provide<I_MainLoop>::From<MainloopComponent>
|
||||
{
|
||||
using RoutineMap = map<RoutineID, RoutineWrapper>;
|
||||
|
||||
public:
|
||||
void run() override;
|
||||
|
||||
RoutineID
|
||||
addOneTimeRoutine(
|
||||
RoutineType priority,
|
||||
Routine func,
|
||||
const string &routine_name,
|
||||
bool is_primary
|
||||
) override;
|
||||
|
||||
RoutineID
|
||||
addRecurringRoutine(
|
||||
RoutineType priority,
|
||||
chrono::microseconds time,
|
||||
Routine func,
|
||||
const string &routine_name,
|
||||
bool is_primary
|
||||
) override;
|
||||
|
||||
RoutineID
|
||||
addFileRoutine(
|
||||
RoutineType priority,
|
||||
int fd,
|
||||
Routine func,
|
||||
const string &routine_name,
|
||||
bool is_primary
|
||||
) override;
|
||||
|
||||
bool doesRoutineExist(RoutineID id) override;
|
||||
|
||||
Maybe<RoutineID> getCurrentRoutineId() const override;
|
||||
|
||||
void yield(bool force) override;
|
||||
void yield(chrono::microseconds time) override;
|
||||
void stopAll() override;
|
||||
void stop() override;
|
||||
void stop(RoutineID id) override;
|
||||
|
||||
void halt() override;
|
||||
void halt(RoutineID id) override;
|
||||
|
||||
void resume(RoutineID id) override;
|
||||
|
||||
void
|
||||
init()
|
||||
{
|
||||
fini_signal_flag = false;
|
||||
addOneTimeRoutine(
|
||||
RoutineType::Offline,
|
||||
[this](){ reportStartupEvent(); },
|
||||
"Nano service startup report",
|
||||
false
|
||||
);
|
||||
|
||||
metric_report_interval = chrono::seconds(
|
||||
getConfigurationWithDefault<uint>(600, "Mainloop", "metric reporting interval")
|
||||
);
|
||||
mainloop_metric.init(
|
||||
"Mainloop sleep time data",
|
||||
ReportIS::AudienceTeam::AGENT_CORE,
|
||||
ReportIS::IssuingEngine::AGENT_CORE,
|
||||
metric_report_interval,
|
||||
false
|
||||
);
|
||||
mainloop_metric.registerListener();
|
||||
}
|
||||
|
||||
void
|
||||
fini()
|
||||
{
|
||||
timer = nullptr;
|
||||
fini_signal_flag = false;
|
||||
}
|
||||
|
||||
private:
|
||||
void reportStartupEvent();
|
||||
void stop(const RoutineMap::iterator &iter);
|
||||
void updateCurrentStress(bool is_busy);
|
||||
uint32_t getCurrentTimeSlice(uint32_t current_stress);
|
||||
RoutineID getNextID();
|
||||
|
||||
I_TimeGet *
|
||||
getTimer()
|
||||
{
|
||||
if (timer == nullptr) timer = Singleton::Consume<I_TimeGet>::by<MainloopComponent>();
|
||||
return timer;
|
||||
}
|
||||
|
||||
I_TimeGet *timer = nullptr;
|
||||
|
||||
RoutineMap routines;
|
||||
RoutineMap::iterator curr_iter = routines.end();
|
||||
RoutineID next_routine_id = 0;
|
||||
|
||||
bool do_stop = false;
|
||||
bool is_running = false;
|
||||
chrono::microseconds stop_time;
|
||||
uint32_t current_stress = 0;
|
||||
|
||||
chrono::seconds metric_report_interval;
|
||||
MainloopEvent mainloop_event;
|
||||
MainloopMetric mainloop_metric;
|
||||
};
|
||||
|
||||
static I_MainLoop::RoutineType rounds[] = {
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::Timer,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::System,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::Timer,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
I_MainLoop::RoutineType::Offline,
|
||||
};
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::reportStartupEvent()
|
||||
{
|
||||
chrono::microseconds curr_time = Singleton::Consume<I_TimeGet>::by<MainloopComponent>()->getWalltime();
|
||||
|
||||
ReportIS::AudienceTeam audience_team = ReportIS::AudienceTeam::NONE;
|
||||
auto i_env = Singleton::Consume<I_Environment>::by<MainloopComponent>();
|
||||
auto team = i_env->get<ReportIS::AudienceTeam>("Audience Team");
|
||||
if (team.ok()) audience_team = *team;
|
||||
|
||||
Report startup_message(
|
||||
"Nano service successfully started",
|
||||
curr_time,
|
||||
ReportIS::Type::EVENT,
|
||||
ReportIS::Level::LOG,
|
||||
ReportIS::LogLevel::INFO,
|
||||
ReportIS::Audience::INTERNAL,
|
||||
audience_team,
|
||||
ReportIS::Severity::INFO,
|
||||
ReportIS::Priority::HIGH,
|
||||
chrono::seconds(0),
|
||||
LogField("agentId", Singleton::Consume<I_AgentDetails>::by<MainloopComponent>()->getAgentId()),
|
||||
ReportIS::Tags::INFORMATIONAL
|
||||
);
|
||||
|
||||
string fog_event_uri = getConfigurationWithDefault<string>(
|
||||
"/api/v1/agents/events",
|
||||
"Logging",
|
||||
"Fog Log URI"
|
||||
);
|
||||
|
||||
LogRest startup_message_client_rest(startup_message);
|
||||
|
||||
Singleton::Consume<I_Messaging>::by<MainloopComponent>()->sendObjectWithPersistence(
|
||||
startup_message_client_rest,
|
||||
I_Messaging::Method::POST,
|
||||
fog_event_uri,
|
||||
"",
|
||||
true,
|
||||
MessageTypeTag::REPORT
|
||||
);
|
||||
|
||||
dbgInfo(D_MAINLOOP) << "Startup report was successfully sent to fog";
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::run()
|
||||
{
|
||||
dbgAssert(!is_running) << "MainloopComponent::Impl::run was called while it was already running";
|
||||
is_running = true;
|
||||
|
||||
bool has_primary_routines = true;
|
||||
uint round = 0;
|
||||
uint64_t sleep_count = 0;
|
||||
dbgInfo(D_MAINLOOP) << "Starting the Mainloop";
|
||||
chrono::microseconds last_iter = getTimer()->getMonotonicTime();
|
||||
const chrono::seconds one_sec(1);
|
||||
|
||||
string service_name = "Unnamed Nano Service";
|
||||
auto name = Singleton::Consume<I_Environment>::by<MainloopComponent>()->get<string>("Service Name");
|
||||
if (name.ok()) service_name = *name;
|
||||
|
||||
string error_prefix = "Service " + service_name + " crashed. Error details: ";
|
||||
string error;
|
||||
|
||||
while (has_primary_routines) {
|
||||
mainloop_event.setStressValue(current_stress);
|
||||
int time_slice_to_use = getCurrentTimeSlice(current_stress);
|
||||
mainloop_event.setTimeSlice(time_slice_to_use);
|
||||
chrono::microseconds basic_time_slice(time_slice_to_use);
|
||||
chrono::milliseconds large_exceeding(getConfigurationWithDefault(100u, "Mainloop", "Exceed Warning"));
|
||||
auto start_time = getTimer()->getMonotonicTime();
|
||||
has_primary_routines = false;
|
||||
|
||||
curr_iter = routines.begin();
|
||||
while (curr_iter != routines.end()) {
|
||||
if (fini_signal_flag) {
|
||||
break;
|
||||
}
|
||||
if (!curr_iter->second.isActive()) {
|
||||
curr_iter = routines.erase(curr_iter);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (curr_iter->second.isPrimary()) has_primary_routines = true;
|
||||
|
||||
if (curr_iter->second.shouldRun(rounds[round])) {
|
||||
// Set the time upon which `hasAdditionalTime` will yield.
|
||||
stop_time = getTimer()->getMonotonicTime() + basic_time_slice;
|
||||
dbgTrace(D_MAINLOOP) <<
|
||||
"Starting execution of corutine. Routine named: " <<
|
||||
curr_iter->second.getRoutineName();
|
||||
|
||||
try {
|
||||
curr_iter->second.run();
|
||||
} catch (const exception &e) {
|
||||
error =
|
||||
error_prefix
|
||||
+ "Routine: '"
|
||||
+ curr_iter->second.getRoutineName()
|
||||
+ "' thrown exception: "
|
||||
+ e.what();
|
||||
} catch (...) {
|
||||
error =
|
||||
error_prefix
|
||||
+ "Unknown generic error exception thrown during execution of mainloop. Routine name: '"
|
||||
+ curr_iter->second.getRoutineName()
|
||||
+ "'";
|
||||
}
|
||||
|
||||
if (error != "") {
|
||||
cerr << error << endl;
|
||||
if (Singleton::exists<I_SignalHandler>()) {
|
||||
Singleton::Consume<I_SignalHandler>::by<MainloopComponent>()->dumpErrorReport(error);
|
||||
}
|
||||
fini_signal_flag = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
dbgTrace(D_MAINLOOP) <<
|
||||
"Ending execution of corutine. Routine named: " <<
|
||||
curr_iter->second.getRoutineName();
|
||||
if (getTimer()->getMonotonicTime() > stop_time + large_exceeding) {
|
||||
dbgError(D_MAINLOOP)
|
||||
<< "Routine execution exceeded run time. Routine name: "
|
||||
<< curr_iter->second.getRoutineName();
|
||||
}
|
||||
}
|
||||
|
||||
curr_iter++;
|
||||
}
|
||||
round = (round + 1) % (sizeof(rounds)/sizeof(rounds[0]));
|
||||
|
||||
uint64_t signed_sleep_time = 0;
|
||||
chrono::microseconds current_time = getTimer()->getMonotonicTime();
|
||||
if (start_time + basic_time_slice > current_time) {
|
||||
chrono::microseconds sleep_time = start_time + basic_time_slice - current_time;
|
||||
signed_sleep_time = sleep_time.count();
|
||||
sleep_count += signed_sleep_time;
|
||||
usleep(signed_sleep_time);
|
||||
}
|
||||
|
||||
mainloop_event.setSleepTime(signed_sleep_time);
|
||||
mainloop_event.notify();
|
||||
|
||||
if (start_time - last_iter > one_sec) {
|
||||
dbgTrace(D_MAINLOOP) <<
|
||||
"During the last second the process slept for " <<
|
||||
sleep_count <<
|
||||
" microseconds, stress: " <<
|
||||
current_stress <<
|
||||
", time slice: " <<
|
||||
time_slice_to_use;
|
||||
sleep_count = 0;
|
||||
last_iter = start_time;
|
||||
}
|
||||
}
|
||||
|
||||
dbgInfo(D_MAINLOOP) << "Mainloop ended - stopping all routines";
|
||||
stopAll();
|
||||
routines.clear();
|
||||
}
|
||||
|
||||
string
|
||||
getRoutineTypeString(I_MainLoop::RoutineType priority)
|
||||
{
|
||||
switch (priority) {
|
||||
case I_MainLoop::RoutineType::RealTime: return "RealTime";
|
||||
case I_MainLoop::RoutineType::Timer: return "Timer";
|
||||
case I_MainLoop::RoutineType::System: return "System";
|
||||
case I_MainLoop::RoutineType::Offline: return "Offline";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
I_MainLoop::RoutineID
|
||||
MainloopComponent::Impl::addOneTimeRoutine(
|
||||
RoutineType priority,
|
||||
Routine func,
|
||||
const string &_routine_name,
|
||||
bool is_primary)
|
||||
{
|
||||
auto id = getNextID();
|
||||
|
||||
string routine_name = _routine_name.empty() ? string("Generic routine, id: " + to_string(id)) : _routine_name;
|
||||
auto env = Singleton::Consume<I_Environment>::by<MainloopComponent>()->createEnvironment();
|
||||
Routine func_wrapper = [this, env, func, routine_name] () mutable {
|
||||
Singleton::Consume<I_Environment>::by<MainloopComponent>()->loadEnvironment(move(env));
|
||||
|
||||
try {
|
||||
if (this->do_stop) return;
|
||||
func();
|
||||
} catch (MainloopStop) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
routines.emplace(id, RoutineWrapper(priority, func_wrapper, is_primary, routine_name));
|
||||
dbgDebug(D_MAINLOOP)
|
||||
<< "Added new routine. Name: "
|
||||
<< routine_name
|
||||
<< ", Priority: "
|
||||
<< getRoutineTypeString(priority)
|
||||
<< ", total routines: "
|
||||
<< routines.size();
|
||||
return id;
|
||||
}
|
||||
|
||||
I_MainLoop::RoutineID
|
||||
MainloopComponent::Impl::addRecurringRoutine(
|
||||
RoutineType priority,
|
||||
chrono::microseconds time,
|
||||
Routine func,
|
||||
const string &routine_name,
|
||||
bool is_primary
|
||||
)
|
||||
{
|
||||
Routine func_wrapper = [this, time, func] () {
|
||||
while (true) {
|
||||
func();
|
||||
yield(time);
|
||||
}
|
||||
};
|
||||
return addOneTimeRoutine(priority, func_wrapper, routine_name, is_primary);
|
||||
}
|
||||
|
||||
I_MainLoop::RoutineID
|
||||
MainloopComponent::Impl::addFileRoutine(
|
||||
RoutineType priority,
|
||||
int fd,
|
||||
Routine func,
|
||||
const string &routine_name,
|
||||
bool is_primary)
|
||||
{
|
||||
Routine func_wrapper = [this, fd, func, priority] () {
|
||||
while (true) {
|
||||
struct pollfd s_poll;
|
||||
s_poll.fd = fd;
|
||||
s_poll.events = POLLIN;
|
||||
s_poll.revents = 0;
|
||||
int rc = poll(&s_poll, 1, 0);
|
||||
if (rc > 0 && (s_poll.revents & POLLIN) != 0) {
|
||||
func();
|
||||
if (priority == I_MainLoop::RoutineType::RealTime) {
|
||||
if (s_poll.revents & POLLHUP) {
|
||||
updateCurrentStress(false);
|
||||
} else {
|
||||
updateCurrentStress(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (priority == I_MainLoop::RoutineType::RealTime) updateCurrentStress(false);
|
||||
}
|
||||
yield(true);
|
||||
}
|
||||
};
|
||||
|
||||
return addOneTimeRoutine(priority, func_wrapper, routine_name, is_primary);
|
||||
}
|
||||
|
||||
bool
|
||||
MainloopComponent::Impl::doesRoutineExist(RoutineID id)
|
||||
{
|
||||
return routines.find(id) != routines.end();
|
||||
}
|
||||
|
||||
Maybe<I_MainLoop::RoutineID>
|
||||
MainloopComponent::Impl::getCurrentRoutineId() const
|
||||
{
|
||||
if (curr_iter == routines.end()) return genError("No routine currently runs");
|
||||
return curr_iter->first;
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::yield(bool force)
|
||||
{
|
||||
dbgAssert(curr_iter != routines.end()) << "Calling 'yield' without a running current routine";
|
||||
if (!force && getTimer()->getMonotonicTime() < stop_time) return;
|
||||
|
||||
auto env = Singleton::Consume<I_Environment>::by<MainloopComponent>()->saveEnvironment();
|
||||
curr_iter->second.yield();
|
||||
Singleton::Consume<I_Environment>::by<MainloopComponent>()->loadEnvironment(move(env));
|
||||
if (do_stop) throw MainloopStop();
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::yield(chrono::microseconds time)
|
||||
{
|
||||
if (time == chrono::microseconds::zero()) {
|
||||
yield(true);
|
||||
return;
|
||||
}
|
||||
chrono::microseconds restart_time = getTimer()->getMonotonicTime() + time;
|
||||
while (getTimer()->getMonotonicTime() < restart_time) {
|
||||
yield(true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::stopAll()
|
||||
{
|
||||
for (auto iter = routines.begin(); iter != routines.end(); iter++) {
|
||||
// We can't stop the current routine from inside the loop, since this will also stop the loop and we won't
|
||||
// reach the routines that come after the current routine. So we skip the current routine and come back to
|
||||
// it (if it exists) after the end of the loop.
|
||||
if (iter != curr_iter) stop(iter);
|
||||
}
|
||||
try {
|
||||
if (curr_iter != routines.end()) stop(curr_iter);
|
||||
} catch (MainloopStop) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::stop()
|
||||
{
|
||||
dbgAssert(curr_iter != routines.end()) << "Attempting to stop a routine when none is running";
|
||||
stop(curr_iter);
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::stop(RoutineID id)
|
||||
{
|
||||
auto iter = routines.find(id);
|
||||
if (iter == routines.end()) {
|
||||
dbgError(D_MAINLOOP) << "Attempting to stop the routine " << id << " that does not exist";
|
||||
return;
|
||||
}
|
||||
stop(iter);
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::halt()
|
||||
{
|
||||
dbgAssert(curr_iter != routines.end()) << "Calling 'halt' without a running current routine";
|
||||
curr_iter->second.halt();
|
||||
yield(true);
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::halt(RoutineID id)
|
||||
{
|
||||
auto iter = routines.find(id);
|
||||
dbgAssert(iter != routines.end()) << "No routine " << id << " to halt";
|
||||
iter->second.halt();
|
||||
if (iter == curr_iter) yield(true);
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::resume(RoutineID id)
|
||||
{
|
||||
auto iter = routines.find(id);
|
||||
dbgAssert(iter != routines.end()) << "No routine " << id << " to resume";
|
||||
iter->second.resume();
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::stop(const RoutineMap::iterator &iter)
|
||||
{
|
||||
if (iter == curr_iter) {
|
||||
dbgDebug(D_MAINLOOP) << "Stoping the current routine " << iter->first;
|
||||
throw MainloopStop();
|
||||
}
|
||||
if (iter->second.isActive()) {
|
||||
dbgDebug(D_MAINLOOP) << "Stoping the routine " << iter->first;
|
||||
do_stop = true;
|
||||
// We are going to let the routine run one last time, so it can throw an exception which will cause the stack
|
||||
// to clean up nicely.
|
||||
iter->second.run();
|
||||
do_stop = false;
|
||||
}
|
||||
}
|
||||
|
||||
I_MainLoop::RoutineID
|
||||
MainloopComponent::Impl::getNextID()
|
||||
{
|
||||
next_routine_id++;
|
||||
while (routines.find(next_routine_id) != routines.end()) {
|
||||
next_routine_id++;
|
||||
}
|
||||
|
||||
return next_routine_id;
|
||||
}
|
||||
|
||||
void
|
||||
MainloopComponent::Impl::updateCurrentStress(bool is_busy)
|
||||
{
|
||||
const int stress_factor = 6; // calculated by trial and error, should be revisited
|
||||
if (is_busy) {
|
||||
if (current_stress < 95) {
|
||||
current_stress += stress_factor;
|
||||
} else {
|
||||
current_stress = 100;
|
||||
}
|
||||
} else {
|
||||
if (current_stress > 0) current_stress--;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
MainloopComponent::Impl::getCurrentTimeSlice(uint32_t current_stress)
|
||||
{
|
||||
int idle_time_slice = getConfigurationWithDefault<int>(1000, "Mainloop", "Idle routine time slice");
|
||||
int busy_time_slice = getConfigurationWithDefault<int>(1, "Mainloop", "Busy routine time slice");
|
||||
return idle_time_slice - (((idle_time_slice - busy_time_slice) * current_stress) / 100);
|
||||
}
|
||||
|
||||
MainloopComponent::MainloopComponent() : Component("MainloopComponent"), pimpl(make_unique<Impl>())
|
||||
{
|
||||
}
|
||||
|
||||
MainloopComponent::~MainloopComponent()
|
||||
{
|
||||
}
|
||||
|
||||
void MainloopComponent::init() { pimpl->init(); }
|
||||
void MainloopComponent::fini() { pimpl->fini(); }
|
||||
|
||||
void
|
||||
MainloopComponent::preload()
|
||||
{
|
||||
registerExpectedConfiguration<int>("Mainloop", "Idle routine time slice");
|
||||
registerExpectedConfiguration<int>("Mainloop", "Busy routine time slice");
|
||||
registerExpectedConfiguration<uint>("Mainloop", "metric reporting interval");
|
||||
registerExpectedConfiguration<uint>("Mainloop", "Exceed Warning");
|
||||
}
|
11
core/mainloop/mainloop_ut/CMakeLists.txt
Normal file
11
core/mainloop/mainloop_ut/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
if ("${GEN_BUNDLE_VERSION}" STREQUAL "FALSE")
|
||||
find_package(Boost REQUIRED)
|
||||
endif()
|
||||
link_directories(${Boost_LIBRARY_DIRS})
|
||||
|
||||
add_unit_test(
|
||||
mainloop_ut
|
||||
"mainloop_ut.cc"
|
||||
"mainloop;singleton;boost_context;rest;metric;event_is;-lboost_regex"
|
||||
)
|
||||
|
531
core/mainloop/mainloop_ut/mainloop_ut.cc
Normal file
531
core/mainloop/mainloop_ut/mainloop_ut.cc
Normal file
@@ -0,0 +1,531 @@
|
||||
#include "i_mainloop.h"
|
||||
#include "mainloop.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <chrono>
|
||||
|
||||
#include "cptest.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
#include "mock/mock_environment.h"
|
||||
#include "mock/mock_messaging.h"
|
||||
#include "mock/mock_agent_details.h"
|
||||
#include "scope_exit.h"
|
||||
#include "metric/all_metric_event.h"
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
using namespace chrono;
|
||||
|
||||
USE_DEBUG_FLAG(D_MAINLOOP);
|
||||
|
||||
class EndTest
|
||||
{
|
||||
};
|
||||
|
||||
class MainloopTest : public Test
|
||||
{
|
||||
public:
|
||||
MainloopTest()
|
||||
{
|
||||
EXPECT_CALL(mock_env, getActiveContexts()).WillRepeatedly(ReturnRef(active_context));
|
||||
|
||||
Debug::setUnitTestFlag(D_MAINLOOP, Debug::DebugLevel::DEBUG);
|
||||
Debug::setNewDefaultStdout(&capture_debug);
|
||||
}
|
||||
|
||||
~MainloopTest()
|
||||
{
|
||||
Debug::setUnitTestFlag(D_MAINLOOP, Debug::DebugLevel::INFO);
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
mainloop_comp.fini();
|
||||
}
|
||||
|
||||
void
|
||||
expectPersistentMessage()
|
||||
{
|
||||
EXPECT_CALL(
|
||||
mock_msg,
|
||||
mockSendPersistentMessage(false, _, _, "/api/v1/agents/events", _, _, _)
|
||||
).Times(2).WillRepeatedly(
|
||||
WithArgs<1, 6>(
|
||||
Invoke(
|
||||
[this](const string &req_body, MessageTypeTag tag)
|
||||
{
|
||||
EXPECT_TRUE(tag == MessageTypeTag::REPORT || tag == MessageTypeTag::METRIC);
|
||||
if (tag == MessageTypeTag::REPORT) startup_report_body = req_body;
|
||||
static bool should_throw = false;
|
||||
if (should_throw) {
|
||||
should_throw = false;
|
||||
throw EndTest();
|
||||
} else {
|
||||
should_throw = true;
|
||||
}
|
||||
|
||||
return string();
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
I_Environment::ActiveContexts active_context;
|
||||
|
||||
NiceMock<MockTimeGet> mock_time;
|
||||
MainloopComponent mainloop_comp;
|
||||
NiceMock<MockEnvironment> mock_env;
|
||||
StrictMock<MockMessaging> mock_msg;
|
||||
NiceMock<MockAgentDetails> mock_agent_details;
|
||||
I_MainLoop *mainloop = Singleton::Consume<I_MainLoop>::from(mainloop_comp);
|
||||
ConfigComponent conf;
|
||||
Config::I_Config *config = nullptr;
|
||||
ostringstream capture_debug;
|
||||
string startup_report_body;
|
||||
bool stop_test = false;
|
||||
};
|
||||
|
||||
|
||||
TEST_F(MainloopTest, do_nothing)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, start_with_nothing_to_do)
|
||||
{
|
||||
mainloop->run();
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, basic_metrics_check)
|
||||
{
|
||||
string startup_body_sent;
|
||||
expectPersistentMessage();
|
||||
|
||||
mainloop_comp.init();
|
||||
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
[this] ()
|
||||
{
|
||||
while (true) {
|
||||
mainloop->yield(true);
|
||||
}
|
||||
},
|
||||
"internal test cb",
|
||||
true
|
||||
);
|
||||
|
||||
try {
|
||||
mainloop->run();
|
||||
} catch (...) {
|
||||
}
|
||||
AllMetricEvent all_mt_event;
|
||||
all_mt_event.setReset(false);
|
||||
all_mt_event.notify();
|
||||
|
||||
|
||||
string mainloop_str =
|
||||
"{\n"
|
||||
" \"Metric\": \"Mainloop sleep time data\",\n"
|
||||
" \"Reporting interval\": 600,\n"
|
||||
" \"mainloopMaxTimeSliceSample\": 1000,\n"
|
||||
" \"mainloopAvgTimeSliceSample\": 1000.0,\n"
|
||||
" \"mainloopLastTimeSliceSample\": 1000,\n"
|
||||
" \"mainloopMaxSleepTimeSample\": 1000,\n"
|
||||
" \"mainloopAvgSleepTimeSample\": 1000.0,\n"
|
||||
" \"mainloopLastSleepTimeSample\": 1000,\n"
|
||||
" \"mainloopMaxStressValueSample\": 0,\n"
|
||||
" \"mainloopAvgStressValueSample\": 0.0,\n"
|
||||
" \"mainloopLastStressValueSample\": 0\n"
|
||||
"}";
|
||||
|
||||
EXPECT_THAT(all_mt_event.performNamedQuery(), ElementsAre(Pair("Mainloop sleep time data", mainloop_str)));
|
||||
|
||||
static const string expected_message =
|
||||
"{\n"
|
||||
" \"log\": {\n"
|
||||
" \"eventTime\": \"\",\n"
|
||||
" \"eventName\": \"Nano service successfully started\",\n"
|
||||
" \"eventSeverity\": \"Info\",\n"
|
||||
" \"eventPriority\": \"High\",\n"
|
||||
" \"eventType\": \"Event Driven\",\n"
|
||||
" \"eventLevel\": \"Log\",\n"
|
||||
" \"eventLogLevel\": \"info\",\n"
|
||||
" \"eventAudience\": \"Internal\",\n"
|
||||
" \"eventAudienceTeam\": \"\",\n"
|
||||
" \"eventFrequency\": 0,\n"
|
||||
" \"eventTags\": [\n"
|
||||
" \"Informational\"\n"
|
||||
" ],\n"
|
||||
" \"eventSource\": {\n"
|
||||
" \"agentId\": \"\",\n"
|
||||
" \"eventTraceId\": \"\",\n"
|
||||
" \"eventSpanId\": \"\",\n"
|
||||
" \"issuingEngineVersion\": \"\",\n"
|
||||
" \"serviceName\": \"Unnamed Nano Service\"\n"
|
||||
" },\n"
|
||||
" \"eventData\": {}\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
EXPECT_EQ(startup_report_body, expected_message);
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, no_sleep_time_metrics_check)
|
||||
{
|
||||
mainloop_comp.preload();
|
||||
|
||||
string startup_body_sent;
|
||||
expectPersistentMessage();
|
||||
|
||||
chrono::microseconds time(0);
|
||||
EXPECT_CALL(
|
||||
mock_time,
|
||||
getMonotonicTime()
|
||||
).WillRepeatedly(InvokeWithoutArgs([&]{ time += microseconds(3000); return time; } ));
|
||||
|
||||
setConfiguration<int>(
|
||||
2,
|
||||
string("Mainloop"),
|
||||
string("Idle routine time slice")
|
||||
);
|
||||
|
||||
mainloop_comp.init();
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
[this] ()
|
||||
{
|
||||
while (true) {
|
||||
mainloop->yield(true);
|
||||
}
|
||||
},
|
||||
"internal test cb",
|
||||
true
|
||||
);
|
||||
|
||||
try {
|
||||
mainloop->run();
|
||||
} catch (...) {
|
||||
}
|
||||
AllMetricEvent all_mt_event;
|
||||
all_mt_event.setReset(false);
|
||||
all_mt_event.notify();
|
||||
|
||||
string mainloop_str =
|
||||
"{\n"
|
||||
" \"Metric\": \"Mainloop sleep time data\",\n"
|
||||
" \"Reporting interval\": 600,\n"
|
||||
" \"mainloopMaxTimeSliceSample\": 2,\n"
|
||||
" \"mainloopAvgTimeSliceSample\": 2.0,\n"
|
||||
" \"mainloopLastTimeSliceSample\": 2,\n"
|
||||
" \"mainloopMaxSleepTimeSample\": 0,\n"
|
||||
" \"mainloopAvgSleepTimeSample\": 0.0,\n"
|
||||
" \"mainloopLastSleepTimeSample\": 0,\n"
|
||||
" \"mainloopMaxStressValueSample\": 0,\n"
|
||||
" \"mainloopAvgStressValueSample\": 0.0,\n"
|
||||
" \"mainloopLastStressValueSample\": 0\n"
|
||||
"}";
|
||||
|
||||
EXPECT_THAT(all_mt_event.query(), ElementsAre(mainloop_str));
|
||||
|
||||
static const string expected_message =
|
||||
"{\n"
|
||||
" \"log\": {\n"
|
||||
" \"eventTime\": \"\",\n"
|
||||
" \"eventName\": \"Nano service successfully started\",\n"
|
||||
" \"eventSeverity\": \"Info\",\n"
|
||||
" \"eventPriority\": \"High\",\n"
|
||||
" \"eventType\": \"Event Driven\",\n"
|
||||
" \"eventLevel\": \"Log\",\n"
|
||||
" \"eventLogLevel\": \"info\",\n"
|
||||
" \"eventAudience\": \"Internal\",\n"
|
||||
" \"eventAudienceTeam\": \"\",\n"
|
||||
" \"eventFrequency\": 0,\n"
|
||||
" \"eventTags\": [\n"
|
||||
" \"Informational\"\n"
|
||||
" ],\n"
|
||||
" \"eventSource\": {\n"
|
||||
" \"agentId\": \"\",\n"
|
||||
" \"eventTraceId\": \"\",\n"
|
||||
" \"eventSpanId\": \"\",\n"
|
||||
" \"issuingEngineVersion\": \"\",\n"
|
||||
" \"serviceName\": \"Unnamed Nano Service\"\n"
|
||||
" },\n"
|
||||
" \"eventData\": {}\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
EXPECT_EQ(startup_report_body, expected_message);
|
||||
}
|
||||
|
||||
TEST(MainloopTestWithoutComponent, register_config)
|
||||
{
|
||||
ConfigComponent config;
|
||||
|
||||
StrictMock<MockTimeGet> mock_timer;
|
||||
MainloopComponent mainloop_comp;
|
||||
::Environment env;
|
||||
|
||||
env.preload();
|
||||
mainloop_comp.preload();
|
||||
env.init();
|
||||
|
||||
string config_json =
|
||||
"{\n"
|
||||
" \"Mainloop\": {\n"
|
||||
" \"Idle routine time slice\": [\n"
|
||||
" {\n"
|
||||
" \"value\": 200\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
istringstream ss(config_json);
|
||||
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss);
|
||||
|
||||
int time_slice = getConfigurationWithDefault<int>(100, "Mainloop", "Idle routine time slice");
|
||||
EXPECT_EQ(time_slice, 200);
|
||||
|
||||
env.fini();
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, call_single_cb)
|
||||
{
|
||||
int num_called = 0;
|
||||
auto cb = [&num_called] () {
|
||||
num_called++;
|
||||
};
|
||||
|
||||
mainloop->addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, cb, "call single cb test", true);
|
||||
mainloop->run();
|
||||
EXPECT_EQ(1, num_called);
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, call_single_yield)
|
||||
{
|
||||
int num_called = 0;
|
||||
auto ml = mainloop;
|
||||
auto cb = [&num_called, ml] () {
|
||||
num_called++;
|
||||
ml->yield(true);
|
||||
num_called++;
|
||||
};
|
||||
|
||||
mainloop->addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, cb, "call_single_yield test", true);
|
||||
mainloop->run();
|
||||
EXPECT_EQ(2, num_called);
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, stop_from_cb)
|
||||
{
|
||||
bool dtor_called = false;
|
||||
auto stop_cb = [&dtor_called, this] () {
|
||||
auto scope_guard = make_scope_exit([&dtor_called] () { dtor_called = true; });
|
||||
mainloop->stop();
|
||||
ADD_FAILURE() << "Should stop before this";
|
||||
};
|
||||
|
||||
mainloop->addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, stop_cb, "stop_from_cb test", true);
|
||||
mainloop->run();
|
||||
EXPECT_TRUE(dtor_called); // Verifying that we exited the routine cleanly, invoking the dtor
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, stop_other_cb)
|
||||
{
|
||||
int num_called = 0;
|
||||
auto stoped_cb = [&num_called] () { num_called++; };
|
||||
auto stop_id = mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::Offline,
|
||||
stoped_cb,
|
||||
"stop_other_cb test - cb to stop",
|
||||
true
|
||||
);
|
||||
|
||||
auto stopping_bc = [stop_id, this] () { mainloop->stop(stop_id); };
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
stopping_bc,
|
||||
"stop_other_cb test - cb that stops",
|
||||
true
|
||||
);
|
||||
|
||||
mainloop->run();
|
||||
EXPECT_EQ(0, num_called);
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, call_recurring_cb)
|
||||
{
|
||||
int num_called = 0;
|
||||
auto cb = [&num_called, this] () {
|
||||
num_called++;
|
||||
if (num_called == 3) mainloop->stop();
|
||||
};
|
||||
|
||||
mainloop->addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
chrono::microseconds(0),
|
||||
cb,
|
||||
"call_recurring_cb",
|
||||
true
|
||||
);
|
||||
mainloop->run();
|
||||
EXPECT_EQ(3, num_called);
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, call_file_cb)
|
||||
{
|
||||
CPTestTempfile file({ "a", "b", "c" });
|
||||
int fd = open(file.fname.c_str(), O_RDONLY);
|
||||
ASSERT_LT(0, fd);
|
||||
|
||||
int num_called = 0;
|
||||
auto cb = [&num_called, fd, this] () {
|
||||
char ch;
|
||||
ASSERT_EQ(1, read(fd, &ch, 1));
|
||||
if (ch == 'c') mainloop->stop();
|
||||
num_called++;
|
||||
};
|
||||
mainloop->addFileRoutine(I_MainLoop::RoutineType::RealTime, fd, cb, "call_file_cb test", true);
|
||||
|
||||
mainloop->run();
|
||||
EXPECT_EQ(4, num_called);
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, stop_while_routines_are_running)
|
||||
{
|
||||
int num_called = 0;
|
||||
auto routine = [&num_called] () {
|
||||
num_called++;
|
||||
};
|
||||
|
||||
auto stop_cb = [this] () {
|
||||
mainloop->stopAll();
|
||||
};
|
||||
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
routine,
|
||||
"stop_while_routines_are_running test - cb to stop",
|
||||
true
|
||||
);
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::Timer,
|
||||
stop_cb,
|
||||
"stop_while_routines_are_running test - cb that stops",
|
||||
true
|
||||
);
|
||||
mainloop->run();
|
||||
// "routine" is of higher priority than "stop", so it should run at least once before "stop" is called.
|
||||
EXPECT_LT(0, num_called);
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, halt_self)
|
||||
{
|
||||
int num_called = 0;
|
||||
auto routine = [&num_called, this] () {
|
||||
mainloop->halt();
|
||||
num_called++;
|
||||
};
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
routine,
|
||||
"halt_self test - cb to stop",
|
||||
true
|
||||
);
|
||||
|
||||
auto stop_cb = [this] () {
|
||||
mainloop->stopAll();
|
||||
};
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
stop_cb,
|
||||
"halt_self test - cb that stops",
|
||||
true
|
||||
);
|
||||
|
||||
mainloop->run();
|
||||
EXPECT_EQ(0, num_called);
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, halt_resume)
|
||||
{
|
||||
auto stop_cb = [this] () {
|
||||
mainloop->stopAll();
|
||||
};
|
||||
auto id = mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::Timer,
|
||||
stop_cb,
|
||||
"halt_resume test - cb that stops",
|
||||
true
|
||||
);
|
||||
|
||||
int num_called = 0;
|
||||
auto routine = [&num_called, this, id] () {
|
||||
mainloop->halt(id);
|
||||
|
||||
while (true) {
|
||||
if (num_called == 100) mainloop->resume(id);
|
||||
num_called++;
|
||||
mainloop->yield(true);
|
||||
}
|
||||
};
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
routine,
|
||||
"halt_resume test - cb to stop",
|
||||
true
|
||||
);
|
||||
|
||||
mainloop->run();
|
||||
EXPECT_LT(100, num_called);
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, death_on_run_twice)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
auto cb = [this] () {
|
||||
EXPECT_DEATH(mainloop->run(), "MainloopComponent::Impl::run was called while it was already running");
|
||||
};
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
cb,
|
||||
"death_on_run_twice test",
|
||||
true
|
||||
);
|
||||
|
||||
mainloop->run();
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, get_routine_id)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
auto cb = [this] () {
|
||||
EXPECT_EQ(mainloop->getCurrentRoutineId().unpack(), 1);
|
||||
EXPECT_DEATH(mainloop->run(), "MainloopComponent::Impl::run was called while it was already running");
|
||||
};
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
cb,
|
||||
"get_routine_id test",
|
||||
true
|
||||
);
|
||||
|
||||
mainloop->run();
|
||||
}
|
||||
|
||||
TEST_F(MainloopTest, check_routine_name)
|
||||
{
|
||||
int num_called = 0;
|
||||
auto cb = [&num_called] () {
|
||||
num_called++;
|
||||
};
|
||||
Debug::setUnitTestFlag(D_MAINLOOP, Debug::DebugLevel::TRACE);
|
||||
mainloop->addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, cb, "check routine name test", true);
|
||||
EXPECT_THAT(capture_debug.str(), HasSubstr("Added new routine. Name: check routine name test"));
|
||||
mainloop->run();
|
||||
EXPECT_THAT(
|
||||
capture_debug.str(),
|
||||
HasSubstr("Starting execution of corutine. Routine named: check routine name test")
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user