20namespace bpo = boost::program_options;
21using bpo::options_description;
22using bpo::variables_map;
25using any_type_compare_map = std::unordered_map<std::type_index, std::function<bool(
const boost::any&
a,
const boost::any& b)>>;
35 auto workwork = boost::asio::make_work_guard(ioctx);
39 sigset_t blocked_signals;
40 sigemptyset(&blocked_signals);
41 sigaddset(&blocked_signals, SIGINT);
42 sigaddset(&blocked_signals, SIGTERM);
43 sigaddset(&blocked_signals, SIGPIPE);
44 sigaddset(&blocked_signals, SIGHUP);
45 pthread_sigmask(SIG_BLOCK, &blocked_signals,
nullptr);
77application::application()
79 io_serv = std::make_shared<boost::asio::io_service>();
81 register_config_type<std::string>();
82 register_config_type<bool>();
83 register_config_type<unsigned short>();
84 register_config_type<unsigned>();
85 register_config_type<unsigned long>();
86 register_config_type<unsigned long long>();
87 register_config_type<short>();
88 register_config_type<int>();
89 register_config_type<long>();
90 register_config_type<long long>();
91 register_config_type<double>();
92 register_config_type<std::vector<std::string>>();
93 register_config_type<boost::filesystem::path>();
107 return my->_version_str;
111 my->_version_str = std::move( v );
115 return my->_full_version_str;
119 my->_full_version_str = std::move( v );
131 return my->_logging_conf;
134void application::wait_for_signal(std::shared_ptr<boost::asio::signal_set> ss) {
135 ss->async_wait([
this, ss](
const boost::system::error_code& ec,
int) {
143void application::setup_signal_handling_on_ios(boost::asio::io_service& ios,
bool startup) {
144 std::shared_ptr<boost::asio::signal_set>
ss = std::make_shared<boost::asio::signal_set>(ios, SIGINT, SIGTERM);
158 boost::asio::io_service startup_thread_ios;
159 setup_signal_handling_on_ios(startup_thread_ios,
true);
160 std::thread startup_thread([&startup_thread_ios]() {
161 startup_thread_ios.run();
163 auto clean_up_signal_thread = [&startup_thread_ios, &startup_thread]() {
164 startup_thread_ios.stop();
165 startup_thread.join();
169 for(
auto plugin : initialized_plugins ) {
175 clean_up_signal_thread();
181 clean_up_signal_thread();
185 std::shared_ptr<boost::asio::signal_set> sighup_set(
new boost::asio::signal_set(
get_io_service(), SIGHUP));
186 start_sighup_handler( sighup_set );
190void application::start_sighup_handler( std::shared_ptr<boost::asio::signal_set> sighup_set ) {
192 sighup_set->async_wait([sighup_set,
this](
const boost::system::error_code& err,
int ) {
196 for(
auto plugin : initialized_plugins ) {
201 start_sighup_handler( sighup_set );
213 my->_any_compare_map.emplace(i, comp);
216void application::set_program_options()
218 for(
auto& plug : plugins) {
219 boost::program_options::options_description plugin_cli_opts(
"Command Line Options for " + plug.second->name());
220 boost::program_options::options_description plugin_cfg_opts(
"Config Options for " + plug.second->name());
221 plug.second->set_program_options(plugin_cli_opts, plugin_cfg_opts);
222 if(plugin_cfg_opts.options().size()) {
223 my->_app_options.add(plugin_cfg_opts);
224 my->_cfg_options.add(plugin_cfg_opts);
226 if(plugin_cli_opts.options().size())
227 my->_app_options.add(plugin_cli_opts);
230 options_description app_cfg_opts(
"Application Config Options" );
231 options_description app_cli_opts(
"Application Command Line Options" );
232 app_cfg_opts.add_options()
233 (
"plugin", bpo::value< vector<string> >()->composing(),
"Plugin(s) to enable, may be specified multiple times");
235 app_cli_opts.add_options()
236 (
"help,h",
"Print this help message and exit.")
237 (
"version,v",
"Print version information.")
238 (
"full-version",
"Print full version information.")
239 (
"print-default-config",
"Print default configuration template")
240 (
"data-dir,d", bpo::value<std::string>(),
"Directory containing program runtime data")
241 (
"config-dir", bpo::value<std::string>(),
"Directory containing configuration files such as config.ini")
242 (
"config,c", bpo::value<std::string>()->default_value(
"config.ini" ),
"Configuration file name relative to config-dir")
243 (
"logconf,l", bpo::value<std::string>()->default_value(
"logging.json" ),
"Logging configuration file name/path for library users");
245 my->_cfg_options.add(app_cfg_opts);
246 my->_app_options.add(app_cfg_opts);
247 my->_app_options.add(app_cli_opts);
251 set_program_options();
253 bpo::variables_map& options = my->_options;
255 bpo::parsed_options parsed = bpo::command_line_parser(argc,
argv).options(my->_app_options).run();
256 bpo::store(parsed, options);
257 vector<string> positionals = bpo::collect_unrecognized(parsed.options, bpo::include_positional);
258 if(!positionals.empty())
259 BOOST_THROW_EXCEPTION(std::runtime_error(
"Unknown option '" + positionals[0] +
"' passed as command line argument"));
260 }
catch(
const boost::program_options::unknown_option& e ) {
261 BOOST_THROW_EXCEPTION(std::runtime_error(
"Unknown option '" + e.get_option_name() +
"' passed as command line argument"));
264 if( options.count(
"help" ) ) {
265 cout << my->_app_options << std::endl;
269 if( options.count(
"version" ) ) {
274 if( options.count(
"full-version" ) ) {
279 if( options.count(
"print-default-config" ) ) {
280 print_default_config(cout);
284 if( options.count(
"data-dir" ) ) {
289 auto workaround = options[
"data-dir"].as<std::string>();
296 if( options.count(
"config-dir" ) ) {
297 auto workaround = options[
"config-dir"].as<std::string>();
304 auto workaround = options[
"logconf"].as<std::string>();
305 bfs::path logconf = workaround;
306 if( logconf.is_relative() )
307 logconf = my->_config_dir / logconf;
308 my->_logging_conf = logconf;
310 workaround = options[
"config"].as<std::string>();
311 my->_config_file_name = workaround;
312 if( my->_config_file_name.is_relative() )
313 my->_config_file_name = my->_config_dir / my->_config_file_name;
315 if(!bfs::exists(my->_config_file_name)) {
316 if(my->_config_file_name.compare(my->_config_dir /
"config.ini") != 0)
318 cout <<
"Config file " << my->_config_file_name <<
" missing." << std::endl;
321 write_default_config(my->_config_file_name);
324 std::vector< bpo::basic_option<char> > opts_from_config;
326 bpo::parsed_options parsed_opts_from_config = bpo::parse_config_file<char>(my->_config_file_name.make_preferred().string().c_str(), my->_cfg_options,
false);
327 bpo::store(parsed_opts_from_config, options);
328 opts_from_config = parsed_opts_from_config.options;
329 }
catch(
const boost::program_options::unknown_option& e ) {
330 BOOST_THROW_EXCEPTION(std::runtime_error(
"Unknown option '" + e.get_option_name() +
"' inside the config file " +
full_config_file_path().
string()));
333 std::vector<string> set_but_default_list;
335 for(
const boost::shared_ptr<bpo::option_description>& od_ptr : my->_cfg_options.options()) {
336 boost::any default_val, config_val;
337 if(!od_ptr->semantic()->apply_default(default_val))
340 if(my->_any_compare_map.find(default_val.type()) == my->_any_compare_map.end()) {
341 std::cerr <<
"APPBASE: Developer -- the type " << default_val.type().name() <<
" is not registered with appbase," << std::endl;
342 std::cerr <<
" add a register_config_type<>() in your plugin's ctor" << std::endl;
346 for(
const bpo::basic_option<char>& opt : opts_from_config) {
347 if(opt.string_key != od_ptr->long_name())
350 od_ptr->semantic()->parse(config_val, opt.value,
true);
351 if(my->_any_compare_map.at(default_val.type())(default_val, config_val))
352 set_but_default_list.push_back(opt.string_key);
356 if(set_but_default_list.size()) {
357 std::cerr <<
"APPBASE: Warning: The following configuration items in the config.ini file are redundantly set to" << std::endl;
358 std::cerr <<
" their default value:" << std::endl;
360 size_t chars_on_line = 0;
361 for(
auto it = set_but_default_list.cbegin(); it != set_but_default_list.end(); ++it) {
363 if(it + 1 != set_but_default_list.end())
365 if((chars_on_line += it->size()) > 65) {
366 std::cerr << std::endl <<
" ";
370 std::cerr << std::endl;
371 std::cerr <<
" Explicit values will override future changes to application defaults. Consider commenting out or" << std::endl;
372 std::cerr <<
" removing these items." << std::endl;
375 if(options.count(
"plugin") > 0)
377 auto plugins = options.at(
"plugin").as<std::vector<std::string>>();
378 for(
auto& arg : plugins)
380 vector<string> names;
381 boost::split(names, arg, boost::is_any_of(
" \t,"));
382 for(
const std::string&
name : names)
387 for (
auto plugin : autostart_plugins)
391 bpo::notify(options);
393 std::cerr <<
"Failed to initialize\n";
401 for(
auto ritr = running_plugins.rbegin();
402 ritr != running_plugins.rend(); ++ritr) {
405 for(
auto ritr = running_plugins.rbegin();
406 ritr != running_plugins.rend(); ++ritr) {
407 plugins.erase((*ritr)->name());
409 running_plugins.clear();
410 initialized_plugins.clear();
416 my->_is_quiting =
true;
421 return my->_is_quiting;
425#if __has_include(<pthread.h>)
426 pthread_t this_thread = pthread_self();
427 struct sched_param
params{};
429 int ret = pthread_getschedparam(this_thread, &policy, &
params);
431 std::cerr <<
"ERROR: Unable to get thread priority" << std::endl;
434 params.sched_priority = sched_get_priority_max(policy);
435 ret = pthread_setschedparam(this_thread, policy, &
params);
437 std::cerr <<
"ERROR: Unable to set thread priority" << std::endl;
444 boost::asio::io_service::work work(*io_serv);
447 while( more || io_serv->run_one() ) {
448 while( io_serv->poll_one() ) {}
458void application::write_default_config(
const bfs::path& cfg_file) {
459 if(!bfs::exists(cfg_file.parent_path()))
460 bfs::create_directories(cfg_file.parent_path());
462 std::ofstream out_cfg( bfs::path(cfg_file).make_preferred().
string());
463 print_default_config(out_cfg);
467void application::print_default_config(std::ostream&
os) {
468 std::map<std::string, std::string> option_to_plug;
469 for(
auto& plug : plugins) {
470 boost::program_options::options_description plugin_cli_opts;
471 boost::program_options::options_description plugin_cfg_opts;
472 plug.second->set_program_options(plugin_cli_opts, plugin_cfg_opts);
474 for(
const boost::shared_ptr<bpo::option_description>& opt : plugin_cfg_opts.options())
475 option_to_plug[opt->long_name()] = plug.second->name();
478 for(
const boost::shared_ptr<bpo::option_description>& od : my->_cfg_options.options())
480 if(!od->description().empty()) {
481 std::string desc = od->description();
482 boost::replace_all(desc,
"\n",
"\n# ");
484 std::map<std::string, std::string>::iterator it;
485 if((it = option_to_plug.find(od->long_name())) != option_to_plug.end())
486 os <<
" (" << it->second <<
")";
490 if(!od->semantic()->apply_default(store))
491 os <<
"# " << od->long_name() <<
" = " << std::endl;
493 auto example = od->format_parameter();
496 os <<
"# " << od->long_name() <<
" = " <<
"false" << std::endl;
497 else if(store.type() ==
typeid(
bool))
498 os <<
"# " << od->long_name() <<
" = " << (boost::any_cast<bool&>(store) ?
"true" :
"false") << std::endl;
501 auto pos = example.find(
"(=");
502 if(pos != string::npos) example = example.substr(pos+2);
503 if(!example.empty()) example.erase(example.length()-1);
504 os <<
"# " << od->long_name() <<
" = " << example << std::endl;
513 auto itr = plugins.find(
name);
514 if(itr == plugins.end()) {
517 return itr->second.get();
523 BOOST_THROW_EXCEPTION(std::runtime_error(
"unable to find plugin: " +
name));
528 return my->_data_dir;
532 return my->_config_dir;
536 return bfs::canonical(my->_config_file_name);
540 sighup_callback = callback;