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

Drain io_context after shutdown of plugins #34

Merged
merged 12 commits into from
Oct 21, 2024
18 changes: 13 additions & 5 deletions application_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ void application_base::startup(boost::asio::io_service& io_serv) {

} catch( ... ) {
clean_up_signal_thread();
shutdown();
shutdown_plugins();
throw;
}

Expand Down Expand Up @@ -441,7 +441,7 @@ void application_base::handle_exception(std::exception_ptr eptr, std::string_vie
}
}

void application_base::shutdown() {
void application_base::shutdown_plugins() {
std::exception_ptr eptr = nullptr;

for(auto ritr = running_plugins.rbegin();
Expand All @@ -454,8 +454,17 @@ void application_base::shutdown() {
handle_exception(std::current_exception(), (*ritr)->name());
}
}
for(auto ritr = running_plugins.rbegin();
ritr != running_plugins.rend(); ++ritr) {

// if we caught an exception while shutting down a plugin, rethrow it so that main()
// can catch it and report the error
if (eptr)
std::rethrow_exception(eptr);
}

void application_base::destroy_plugins() {
std::exception_ptr eptr = nullptr;

for(auto ritr = running_plugins.rbegin(); ritr != running_plugins.rend(); ++ritr) {
try {
plugins.erase((*ritr)->name());
} catch(...) {
Expand All @@ -474,7 +483,6 @@ void application_base::shutdown() {
eptr = std::current_exception();
handle_exception(std::current_exception(), "plugin cleanup");
}
quit();

// if we caught an exception while shutting down a plugin, rethrow it so that main()
// can catch it and report the error
Expand Down
25 changes: 20 additions & 5 deletions include/appbase/application_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ class application_base {
}

void startup(boost::asio::io_service& io_serv);
void shutdown();

/**
* Wait until quit(), SIGINT or SIGTERM and then shutdown.
Expand All @@ -126,15 +125,15 @@ class application_base {
std::exception_ptr eptr = nullptr;
{
auto& io_serv{exec.get_io_service()};
boost::asio::io_service::work work(io_serv);
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work = boost::asio::make_work_guard(io_serv);
(void)work;
bool more = true;

while (more || io_serv.run_one()) {
if (is_quiting())
break;
try {
io_serv.poll(); // queue up any ready; allowing high priority item to get into the queue
if (io_serv.stopped())
break;
// execute the highest priority item
more = exec.execute_highest();
} catch (...) {
Expand All @@ -145,9 +144,22 @@ class application_base {
}
}

try {
shutdown_plugins(); // may rethrow exceptions
} catch (...) {
if (!eptr)
eptr = std::current_exception();
}

work.reset();
io_serv.restart();
// plugins shutdown down, drain io_context of anything posted while shutting down before destroying plugins
while (io_serv.poll())
;

try {
exec.clear(); // make sure the queue is empty
shutdown(); // may rethrow exceptions
destroy_plugins();
} catch (...) {
if (!eptr)
eptr = std::current_exception();
Expand Down Expand Up @@ -290,6 +302,9 @@ class application_base {
}
///@}

void shutdown_plugins();
void destroy_plugins();

application_base(std::shared_ptr<void>&& e); ///< protected because application is a singleton that should be accessed via instance()

/// !!! must be dtor'ed after plugins
Expand Down
79 changes: 79 additions & 0 deletions tests/shutdown_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <appbase/application.hpp>
Copy link
Member

Choose a reason for hiding this comment

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

You added this file as a test but didn't plumb it through cmake etc. not sure if that's intentional or not

Copy link
Member Author

@heifner heifner Sep 28, 2024

Choose a reason for hiding this comment

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

tests/CMakeFiles.txt has file(GLOB UNIT_TESTS "*.cpp")
I verified it runs.

Copy link
Member

Choose a reason for hiding this comment

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

I completely missed the glob 👍

#include <thread>

#include <boost/test/unit_test.hpp>

namespace bpo = boost::program_options;
using bpo::options_description;
using bpo::variables_map;

static bool thing = true;
struct thing_better_be_alive {
~thing_better_be_alive() noexcept(false) {
if(!thing)
throw "BOOM";
}
};

class thready_plugin : public appbase::plugin<thready_plugin> {
public:

template <typename Lambda>
void plugin_requires(Lambda&& l) {}

void set_program_options( options_description& cli, options_description& cfg ) override {}

void thread_work() {
boost::asio::post(ctx, [&]() {
thing_better_be_alive better_be;
boost::asio::post(appbase::app().get_io_service(), [&,is_it=std::move(better_be)]() {
thread_work();
});
});
}

void plugin_initialize( const variables_map& options ) {}
void plugin_startup() {
for(unsigned i = 0; i < 48*4; i++)
thread_work();

for(unsigned i = 0; i < 48; ++i)
threads.emplace_back([this]() {
ctx.run();
});
}
void plugin_shutdown() {
usleep(100000); //oh gee it takes a while to stop
ctx.stop();
for(unsigned i = 0; i < 48; ++i)
threads[i].join();
}

~thready_plugin() {
thing = false;
}

boost::asio::io_context ctx;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> wg = boost::asio::make_work_guard(ctx);

private:
std::vector<std::thread> threads;
};


BOOST_AUTO_TEST_CASE(test_shutdown)
{
appbase::application::register_plugin<thready_plugin>();
appbase::scoped_app app;

const char* argv[] = { "nodoes" };
if( !app->initialize<thready_plugin>( 1, const_cast<char**>(argv) ) )
return;
app->startup();
boost::asio::post(appbase::app().get_io_service(), [&](){
std::this_thread::sleep_for(std::chrono::milliseconds(5));
app->quit();
});
app->exec();

}
Loading