Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
application.cpp
Go to the documentation of this file.
2#include <appbase/version.hpp>
3
4#include <boost/algorithm/string.hpp>
5#include <boost/filesystem.hpp>
6#include <boost/asio/signal_set.hpp>
7#include <boost/algorithm/string.hpp>
8
9#include <iostream>
10#include <fstream>
11#include <unordered_map>
12#include <future>
13#include <optional>
14
15#include <unistd.h>
16#include <signal.h>
17
18namespace appbase {
19
20namespace bpo = boost::program_options;
21using bpo::options_description;
22using bpo::variables_map;
23using std::cout;
24
25using any_type_compare_map = std::unordered_map<std::type_index, std::function<bool(const boost::any& a, const boost::any& b)>>;
26
28 public:
29 application_impl():_app_options("Application Options"){
30#ifndef _WIN32
31 // Create a separate thread to handle signals, so that they don't interrupt I/O.
32 // stdio does not recover from EINTR.
34 _signal_catching_thread = std::thread([&ioctx = *_signal_catching_io_ctx]() {
35 auto workwork = boost::asio::make_work_guard(ioctx);
36 ioctx.run();
37 });
38
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);
46#endif
47 }
48
50 if(_signal_catching_thread.joinable()) {
53 }
54 }
55
56 options_description _app_options;
57 options_description _cfg_options;
58 variables_map _options;
59
60 bfs::path _data_dir{"data-dir"};
61 bfs::path _config_dir{"config-dir"};
62 bfs::path _logging_conf{"logging.json"};
64
68
69 std::atomic_bool _is_quiting{false};
70
72
74 std::optional<boost::asio::io_context> _signal_catching_io_ctx;
75};
76
77application::application()
78:my(new application_impl()){
79 io_serv = std::make_shared<boost::asio::io_service>();
80
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>();
94}
95
97
99 my->_version = version;
100}
101
103 return my->_version;
104}
105
107 return my->_version_str;
108}
109
110void application::set_version_string( std::string v ) {
111 my->_version_str = std::move( v );
112}
113
115 return my->_full_version_str;
116}
117
119 my->_full_version_str = std::move( v );
120}
121
122void application::set_default_data_dir(const bfs::path& data_dir) {
123 my->_data_dir = data_dir;
124}
125
126void application::set_default_config_dir(const bfs::path& config_dir) {
127 my->_config_dir = config_dir;
128}
129
131 return my->_logging_conf;
132}
133
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) {
136 if(ec)
137 return;
138 quit();
139 wait_for_signal(ss);
140 });
141}
142
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);
145#ifdef SIGPIPE
146 ss->add(SIGPIPE);
147#endif
148#ifdef SIGHUP
149 if( startup ) {
150 ss->add(SIGHUP);
151 }
152#endif
153 wait_for_signal(ss);
154}
155
157 //during startup, run a second thread to catch SIGINT/SIGTERM/SIGPIPE/SIGHUP
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();
162 });
163 auto clean_up_signal_thread = [&startup_thread_ios, &startup_thread]() {
164 startup_thread_ios.stop();
165 startup_thread.join();
166 };
167
168 try {
169 for( auto plugin : initialized_plugins ) {
170 if( is_quiting() ) break;
171 plugin->startup();
172 }
173
174 } catch( ... ) {
175 clean_up_signal_thread();
176 shutdown();
177 throw;
178 }
179
180 //after startup, shut down the signal handling thread and catch the signals back on main io_service
181 clean_up_signal_thread();
182 setup_signal_handling_on_ios(get_io_service(), false);
183
184#ifdef SIGHUP
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 );
187#endif
188}
189
190void application::start_sighup_handler( std::shared_ptr<boost::asio::signal_set> sighup_set ) {
191#ifdef SIGHUP
192 sighup_set->async_wait([sighup_set, this](const boost::system::error_code& err, int /*num*/) {
193 if( err ) return;
194 app().post(priority::medium, [sighup_set, this]() {
195 sighup_callback();
196 for( auto plugin : initialized_plugins ) {
197 if( is_quiting() ) return;
199 }
200 });
201 start_sighup_handler( sighup_set );
202 });
203#endif
204}
205
207 static application _app;
208 return _app;
209}
211
213 my->_any_compare_map.emplace(i, comp);
214}
215
216void application::set_program_options()
217{
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);
225 }
226 if(plugin_cli_opts.options().size())
227 my->_app_options.add(plugin_cli_opts);
228 }
229
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");
234
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");
244
245 my->_cfg_options.add(app_cfg_opts);
246 my->_app_options.add(app_cfg_opts);
247 my->_app_options.add(app_cli_opts);
248}
249
250bool application::initialize_impl(int argc, char** argv, vector<abstract_plugin*> autostart_plugins) {
251 set_program_options();
252
253 bpo::variables_map& options = my->_options;
254 try {
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"));
262 }
263
264 if( options.count( "help" ) ) {
265 cout << my->_app_options << std::endl;
266 return false;
267 }
268
269 if( options.count( "version" ) ) {
270 cout << version_string() << std::endl;
271 return false;
272 }
273
274 if( options.count( "full-version" ) ) {
275 cout << full_version_string() << std::endl;
276 return false;
277 }
278
279 if( options.count( "print-default-config" ) ) {
280 print_default_config(cout);
281 return false;
282 }
283
284 if( options.count( "data-dir" ) ) {
285 // Workaround for 10+ year old Boost defect
286 // See https://svn.boost.org/trac10/ticket/8535
287 // Should be .as<bfs::path>() but paths with escaped spaces break bpo e.g.
288 // std::exception::what: the argument ('/path/with/white\ space') for option '--data-dir' is invalid
289 auto workaround = options["data-dir"].as<std::string>();
290 bfs::path data_dir = workaround;
291 if( data_dir.is_relative() )
292 data_dir = bfs::current_path() / data_dir;
293 my->_data_dir = data_dir;
294 }
295
296 if( options.count( "config-dir" ) ) {
297 auto workaround = options["config-dir"].as<std::string>();
298 bfs::path config_dir = workaround;
299 if( config_dir.is_relative() )
300 config_dir = bfs::current_path() / config_dir;
301 my->_config_dir = config_dir;
302 }
303
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;
309
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;
314
315 if(!bfs::exists(my->_config_file_name)) {
316 if(my->_config_file_name.compare(my->_config_dir / "config.ini") != 0)
317 {
318 cout << "Config file " << my->_config_file_name << " missing." << std::endl;
319 return false;
320 }
321 write_default_config(my->_config_file_name);
322 }
323
324 std::vector< bpo::basic_option<char> > opts_from_config;
325 try {
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()));
331 }
332
333 std::vector<string> set_but_default_list;
334
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))
338 continue;
339
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;
343 return false;
344 }
345
346 for(const bpo::basic_option<char>& opt : opts_from_config) {
347 if(opt.string_key != od_ptr->long_name())
348 continue;
349
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);
353 break;
354 }
355 }
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;
359 std::cerr << " ";
360 size_t chars_on_line = 0;
361 for(auto it = set_but_default_list.cbegin(); it != set_but_default_list.end(); ++it) {
362 std::cerr << *it;
363 if(it + 1 != set_but_default_list.end())
364 std::cerr << ", ";
365 if((chars_on_line += it->size()) > 65) {
366 std::cerr << std::endl << " ";
367 chars_on_line = 0;
368 }
369 }
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;
373 }
374
375 if(options.count("plugin") > 0)
376 {
377 auto plugins = options.at("plugin").as<std::vector<std::string>>();
378 for(auto& arg : plugins)
379 {
380 vector<string> names;
381 boost::split(names, arg, boost::is_any_of(" \t,"));
382 for(const std::string& name : names)
383 get_plugin(name).initialize(options);
384 }
385 }
386 try {
387 for (auto plugin : autostart_plugins)
388 if (plugin != nullptr && plugin->get_state() == abstract_plugin::registered)
389 plugin->initialize(options);
390
391 bpo::notify(options);
392 } catch (...) {
393 std::cerr << "Failed to initialize\n";
394 return false;
395 }
396
397 return true;
398}
399
401 for(auto ritr = running_plugins.rbegin();
402 ritr != running_plugins.rend(); ++ritr) {
403 (*ritr)->shutdown();
404 }
405 for(auto ritr = running_plugins.rbegin();
406 ritr != running_plugins.rend(); ++ritr) {
407 plugins.erase((*ritr)->name());
408 }
409 running_plugins.clear();
410 initialized_plugins.clear();
411 plugins.clear();
412 quit();
413}
414
416 my->_is_quiting = true;
417 io_serv->stop();
418}
419
421 return my->_is_quiting;
422}
423
425#if __has_include(<pthread.h>)
426 pthread_t this_thread = pthread_self();
427 struct sched_param params{};
428 int policy = 0;
429 int ret = pthread_getschedparam(this_thread, &policy, &params);
430 if( ret != 0 ) {
431 std::cerr << "ERROR: Unable to get thread priority" << std::endl;
432 }
433
434 params.sched_priority = sched_get_priority_max(policy);
435 ret = pthread_setschedparam(this_thread, policy, &params);
436 if( ret != 0 ) {
437 std::cerr << "ERROR: Unable to set thread priority" << std::endl;
438 }
439#endif
440}
441
443 {
444 boost::asio::io_service::work work(*io_serv);
445 (void)work;
446 bool more = true;
447 while( more || io_serv->run_one() ) {
448 while( io_serv->poll_one() ) {}
449 // execute the highest priority item
450 more = pri_queue.execute_highest();
451 }
452
453 shutdown();
454 }
455 io_serv.reset();
456}
457
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());
461
462 std::ofstream out_cfg( bfs::path(cfg_file).make_preferred().string());
463 print_default_config(out_cfg);
464 out_cfg.close();
465}
466
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);
473
474 for(const boost::shared_ptr<bpo::option_description>& opt : plugin_cfg_opts.options())
475 option_to_plug[opt->long_name()] = plug.second->name();
476 }
477
478 for(const boost::shared_ptr<bpo::option_description>& od : my->_cfg_options.options())
479 {
480 if(!od->description().empty()) {
481 std::string desc = od->description();
482 boost::replace_all(desc, "\n", "\n# ");
483 os << "# " << desc;
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 << ")";
487 os << std::endl;
488 }
489 boost::any store;
490 if(!od->semantic()->apply_default(store))
491 os << "# " << od->long_name() << " = " << std::endl;
492 else {
493 auto example = od->format_parameter();
494 if(example.empty())
495 // This is a boolean switch
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;
499 else {
500 // The string is formatted "arg (=<interesting part>)"
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;
505 }
506 }
507 os << std::endl;
508 }
509}
510
512{
513 auto itr = plugins.find(name);
514 if(itr == plugins.end()) {
515 return nullptr;
516 }
517 return itr->second.get();
518}
519
521 auto ptr = find_plugin(name);
522 if(!ptr)
523 BOOST_THROW_EXCEPTION(std::runtime_error("unable to find plugin: " + name));
524 return *ptr;
525}
526
527bfs::path application::data_dir() const {
528 return my->_data_dir;
529}
530
531bfs::path application::config_dir() const {
532 return my->_config_dir;
533}
534
536 return bfs::canonical(my->_config_file_name);
537}
538
539void application::set_sighup_callback(std::function<void()> callback) {
540 sighup_callback = callback;
541}
542
543const bpo::variables_map& application::get_options() const{
544 return my->_options;
545}
546
547}
std::string name
@ registered
the plugin is constructed but doesn't do anything
Definition plugin.hpp:31
any_type_compare_map _any_compare_map
std::thread _signal_catching_thread
std::atomic_bool _is_quiting
options_description _cfg_options
std::optional< boost::asio::io_context > _signal_catching_io_ctx
options_description _app_options
const bpo::variables_map & get_options() const
void set_version_string(std::string v)
User provided version string for version_string() which overrides git describe value.
void set_version(uint64_t version)
Set version.
void set_default_config_dir(const bfs::path &config_dir="etc")
Set default config directory.
bfs::path data_dir() const
Get data directory.
bfs::path full_config_file_path() const
Get full config.ini path.
void register_config_type_comparison(std::type_index, config_comparison_f comp)
void set_sighup_callback(std::function< void()> callback)
Set function pointer invoked on receipt of SIGHUP.
auto post(int priority, Func &&func)
string version_string() const
Get version string; generated from git describe if available.
boost::asio::io_service & get_io_service()
bfs::path get_logging_conf() const
Get logging configuration path.
bfs::path config_dir() const
Get config directory.
Plugin & get_plugin() const
bool is_quiting() const
string full_version_string() const
Get full version string; same as version_string() unless set differently.
static application & instance()
Plugin * find_plugin() const
bool initialize_impl(int argc, char **argv, vector< abstract_plugin * > autostart_plugins)
uint64_t version() const
Get version.
void set_full_version_string(std::string v)
User provided full version string for full_version_string()
void set_default_data_dir(const bfs::path &data_dir="data-dir")
Set default data directory.
virtual void startup() override
virtual state get_state() const override
virtual void handle_sighup() override
virtual void initialize(const variables_map &options) override
os_t os
char ** argv
static const Segment ss(Segment::ss)
std::function< bool(const boost::any &a, const boost::any &b)> config_comparison_f
const char * appbase_version_string
application & app()
std::unordered_map< std::type_index, std::function< bool(const boost::any &a, const boost::any &b)> > any_type_compare_map
const GenericPointer< typename T::ValueType > T2 T::AllocatorType & a
Definition pointer.h:1181
unsigned __int64 uint64_t
Definition stdint.h:136
static constexpr int medium
account_query_db::get_accounts_by_authorizers_params params
CK_RV ret