From 024ec0da9931700f25a89e8d80e02f555fe1310a Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Mon, 29 May 2017 18:47:41 -0500 Subject: [PATCH 01/34] Add support for required plugins at startup If no plugins are configured to be started in the config file, apps start up, register no plugins, and do nothing. This is almost guaranteed to be wrong behavior in all situations, so it should be easy to specify some plugins that should always be started. This commit makes this so. --- application.cpp | 18 +++++++++--------- include/appbase/application.hpp | 24 ++++++++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/application.cpp b/application.cpp index d6006794f..a1868e220 100644 --- a/application.cpp +++ b/application.cpp @@ -33,6 +33,11 @@ application::application() application::~application() { } +void application::startup() { + for (auto plugin : initialized_plugins) + plugin->startup(); +} + application& application::instance() { static application _app; return _app; @@ -70,9 +75,7 @@ void application::set_program_options() my->_app_options.add(app_cli_opts); } - -bool application::initialize( int argc, char** argv ) -{ +bool application::initialize_impl(int argc, char** argv, vector autostart_plugins) { set_program_options(); bpo::variables_map options; @@ -117,18 +120,15 @@ bool application::initialize( int argc, char** argv ) get_plugin(name).initialize(options); } } + for (auto plugin : autostart_plugins) + if (plugin != nullptr && plugin->get_state() == abstract_plugin::registered) + plugin->initialize(options); bpo::notify(options); return true; } -void application::startup() -{ - for(auto plug : initialized_plugins) - plug->startup(); -} - void application::shutdown() { for(auto ritr = running_plugins.rbegin(); ritr != running_plugins.rend(); ++ritr) { diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 231148cd5..6b81844df 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -14,21 +14,27 @@ namespace appbase { ~application(); /** - * Looks for the --plugin commandline / config option and calls initialize on those plugins + * @brief Looks for the --plugin commandline / config option and calls initialize on those plugins * - * @return true if the application and plugins were initialized, false or exception on error + * @tparam Plugin List of plugins to initalize even if not mentioned by configuration. For plugins started by + * configuration settings or dependency resolution, this template has no effect. + * @return true if the application and plugins were initialized, false or exception on error */ - bool initialize(int argc, char** argv); - void startup(); - void shutdown(); + template + bool initialize(int argc, char** argv) { + return initialize_impl(argc, argv, {find_plugin()...}); + } + + void startup(); + void shutdown(); /** * Wait until quit(), SIGINT or SIGTERM and then shutdown */ - void exec(); - void quit(); + void exec(); + void quit(); - static application& instance(); + static application& instance(); abstract_plugin* find_plugin(const string& name)const; abstract_plugin& get_plugin(const string& name)const; @@ -66,6 +72,8 @@ namespace appbase { template friend class plugin; + bool initialize_impl(int argc, char** argv, vector autostart_plugins); + /** these notifications get called from the plugin when their state changes so that * the application can call shutdown in the reverse order. */ From 754b9d19783e008aa783a12465594f1fe2a49c29 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 31 May 2017 15:39:24 -0500 Subject: [PATCH 02/34] [IDE Support] Add headers to CMakeLists.txt By listing headers among the sources, we don't confuse cmake at all since it knows better than to compile those, but we do make life easier on IDEs with CMake support, since the IDE will now pick up the headers as more source code for the project. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ffda29459..b1c256ca4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ endif() add_library( appbase application.cpp + ${HEADERS} ) target_link_libraries( appbase ${Boost_LIBRARIES}) From 22efe13a975f540a1d28e4fd45fa9be8c52d242e Mon Sep 17 00:00:00 2001 From: Guanqun Lu Date: Sat, 7 Oct 2017 20:54:37 -0500 Subject: [PATCH 03/34] typo fix and two other small format fixes --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1d5b02ed7..e8730d17e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ plugins are configured, initialized, started, and shutdown in the proper order. ## Key Features - Dynamically Specify Plugins to Load -- Automaticly Load Dependent Plugins in Order +- Automatically Load Dependent Plugins in Order - Plugins can specify commandline arguments and configuration file options - Program gracefully exits from SIGINT and SIGTERM - Minimal Dependencies (Boost 1.60, c++14) @@ -87,7 +87,7 @@ exited cleanly ### Boost ASIO AppBase maintains a singleton `application` instance which can be accessed via `appbase::app()`. This -application owns a `boost::asio::io_service` which starts running when appbase::exec() is called. If +application owns a `boost::asio::io_service` which starts running when `appbase::exec()` is called. If a plugin needs to perform IO or other asynchronous operations then it should dispatch it via `app().get_io_service().post( lambda )`. @@ -105,6 +105,7 @@ To trigger a graceful exit call `appbase::app().quit()` or send SIGTERM or SIGIN To compile boost with c++14 use: - ./b2 ... cxxflags="-std=c++0x -stdlib=libc++" linkflags="-stdlib=libc++" ... - +``` +./b2 ... cxxflags="-std=c++0x -stdlib=libc++" linkflags="-stdlib=libc++" ... +``` From a7e51900004fffb90da4523c6dd9576711a1cad2 Mon Sep 17 00:00:00 2001 From: Guanqun Lu Date: Sat, 7 Oct 2017 20:58:35 -0500 Subject: [PATCH 04/34] typo fix --- include/appbase/application.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 985a608a4..73aded06d 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -82,7 +82,7 @@ namespace appbase { ///@} private: - application(); ///< private because application is a singlton that should be accessed via instance() + application(); ///< private because application is a singleton that should be accessed via instance() map> plugins; ///< all registered plugins vector initialized_plugins; ///< stored in the order they were started running vector running_plugins; ///< stored in the order they were started running From c6c5754e30dd2082b79651d10599c5cf0b0533b3 Mon Sep 17 00:00:00 2001 From: Guanqun Lu Date: Sat, 7 Oct 2017 21:52:10 -0500 Subject: [PATCH 05/34] typo fix --- include/appbase/plugin.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/appbase/plugin.hpp b/include/appbase/plugin.hpp index 26f4372e4..bf0857269 100644 --- a/include/appbase/plugin.hpp +++ b/include/appbase/plugin.hpp @@ -29,7 +29,7 @@ namespace appbase { public: enum state { registered, ///< the plugin is constructed but doesn't do anything - initialized, ///< the plugin has initlaized any state required but is idle + initialized, ///< the plugin has initialized any state required but is idle started, ///< the plugin is actively running stopped ///< the plugin is no longer running }; From 57aff5b15583964973b71bc5654cd54c4833efd6 Mon Sep 17 00:00:00 2001 From: Adam Mitz Date: Fri, 10 Nov 2017 10:20:37 -0600 Subject: [PATCH 06/34] Updated status messages output from CMake to reflect actual project name --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1c256ca4..773e8c4ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,11 +20,11 @@ set( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" ) if( APPLE ) # Apple Specific Options Here - message( STATUS "Configuring ChainBase on OS X" ) + message( STATUS "Configuring AppBase on OS X" ) set( CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -std=c++14 -stdlib=libc++ -Wall -Wno-conversion -Wno-deprecated-declarations" ) else( APPLE ) # Linux Specific Options Here - message( STATUS "Configuring ChainBase on Linux" ) + message( STATUS "Configuring AppBase on Linux" ) set( CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -std=c++14 -Wall" ) set( rt_library rt ) set( pthread_library pthread) From fe1035028d6d6cf142ea463ce7539c6e66e83545 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Mon, 13 Nov 2017 14:49:06 -0600 Subject: [PATCH 07/34] Version handling, required to resolve #664 --- application.cpp | 19 +++++++++++++++++-- include/appbase/application.hpp | 10 ++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/application.cpp b/application.cpp index 93e900dc8..369872527 100644 --- a/application.cpp +++ b/application.cpp @@ -22,7 +22,9 @@ class application_impl { options_description _app_options; options_description _cfg_options; - bfs::path _data_dir; + bfs::path _data_dir; + + string _version; }; application::application() @@ -32,6 +34,14 @@ application::application() application::~application() { } +void application::set_version(string version) { + my->_version = version; +} + +string application::version() const { + return my->_version; +} + void application::startup() { for (auto plugin : initialized_plugins) plugin->startup(); @@ -81,7 +91,12 @@ bool application::initialize_impl(int argc, char** argv, vector_app_options), options); if( options.count( "help" ) ) { - cout << my->_app_options << "\n"; + cout << my->_app_options << std::endl; + return false; + } + + if( options.count( "version" ) ) { + cout << my->_version << std::endl; return false; } diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 985a608a4..e857de015 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -13,6 +13,16 @@ namespace appbase { public: ~application(); + /** @brief Sets version string + * + * @param version Version string output verbatim with -v/--version + */ + void set_version(string version); + /** @brief Gets version string + * + * @return Version string output with -v/--version + */ + string version() const; /** * @brief Looks for the --plugin commandline / config option and calls initialize on those plugins * From bf9a9ffe605056d32f1c741b80b78fe6773c821b Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Tue, 14 Nov 2017 10:51:03 -0600 Subject: [PATCH 08/34] Add integer version number as well. --- application.cpp | 9 +++++++++ include/appbase/application.hpp | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/application.cpp b/application.cpp index 369872527..0f5e6a76b 100644 --- a/application.cpp +++ b/application.cpp @@ -25,6 +25,7 @@ class application_impl { bfs::path _data_dir; string _version; + uint64_t _version_int; }; application::application() @@ -38,10 +39,18 @@ void application::set_version(string version) { my->_version = version; } +void application::set_version(uint64_t version) { + my->_version_int = version; +} + string application::version() const { return my->_version; } +uint64_t application::version_int() const { + return my->_version_int; +} + void application::startup() { for (auto plugin : initialized_plugins) plugin->startup(); diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index e857de015..1d024f537 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -18,11 +18,21 @@ namespace appbase { * @param version Version string output verbatim with -v/--version */ void set_version(string version); + /** @overload + * + * @param version Integer version independent of the string output with -v/--version + */ + void set_version(uint64_t version); /** @brief Gets version string * * @return Version string output with -v/--version */ string version() const; + /** @brief Gets version integer + * + * @return Version integer independent of the string output with -v/--version + */ + uint64_t version_int() const; /** * @brief Looks for the --plugin commandline / config option and calls initialize on those plugins * From 0865e2c59e401c2cca3e37d6fc6770f44b4213e0 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 30 Nov 2017 13:36:37 -0500 Subject: [PATCH 09/34] explicitly destroying io_serv at the end of shutdown when the io_serv is stopped it may still own std::functions/lambdas that have captured data in them. If this captured data relies on another system like a logging system, for instance, waiting for destructors to be called by the atexit call may result in them accessing a destroyed system. By explicitly resetting the io_serve after shutdown we in turn release any resources it has while the systems are still alive and well --- application.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/application.cpp b/application.cpp index 0f5e6a76b..6969b4ac4 100644 --- a/application.cpp +++ b/application.cpp @@ -164,6 +164,7 @@ void application::shutdown() { running_plugins.clear(); initialized_plugins.clear(); plugins.clear(); + io_serv.reset(); } void application::quit() { From 552847cccebe138fddbaaea1913f08efe3df3ca1 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Thu, 30 Nov 2017 13:25:11 -0600 Subject: [PATCH 10/34] Add logging configuration path and clean up version variables. --- application.cpp | 26 ++++++++++++++++---------- include/appbase/application.hpp | 21 ++++++++------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/application.cpp b/application.cpp index 6969b4ac4..83167cc7f 100644 --- a/application.cpp +++ b/application.cpp @@ -23,9 +23,9 @@ class application_impl { options_description _cfg_options; bfs::path _data_dir; + bfs::path _logging_conf; - string _version; - uint64_t _version_int; + uint64_t _version; }; application::application() @@ -35,20 +35,16 @@ application::application() application::~application() { } -void application::set_version(string version) { - my->_version = version; -} - void application::set_version(uint64_t version) { - my->_version_int = version; + my->_version = version; } -string application::version() const { +uint64_t application::version() const { return my->_version; } -uint64_t application::version_int() const { - return my->_version_int; +bfs::path application::get_logging_conf() const { + return my->_logging_conf; } void application::startup() { @@ -87,6 +83,7 @@ void application::set_program_options() ("version,v", "Print version information.") ("data-dir,d", bpo::value()->default_value( "data-dir" ), "Directory containing configuration file config.ini") ("config,c", bpo::value()->default_value( "config.ini" ), "Configuration file name relative to data-dir"); + ("logconf,l", bpo::value()->default_value( "logging.json" ), "Logging configuration file name/path"); my->_cfg_options.add(app_cfg_opts); my->_app_options.add(app_cfg_opts); @@ -109,6 +106,15 @@ bool application::initialize_impl(int argc, char** argv, vector(); + if( logconf.is_relative() ) + logconf = bfs::current_path() / logconf; + } + my->_logging_conf = logconf; + bfs::path data_dir = "data-dir"; if( options.count("data-dir") ) { diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index a43d7f78d..fbe4b3d09 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -13,26 +13,21 @@ namespace appbase { public: ~application(); - /** @brief Sets version string + /** @brief Set version * - * @param version Version string output verbatim with -v/--version - */ - void set_version(string version); - /** @overload - * - * @param version Integer version independent of the string output with -v/--version + * @param version Version output with -v/--version */ void set_version(uint64_t version); - /** @brief Gets version string + /** @brief Get version * - * @return Version string output with -v/--version + * @return Version output with -v/--version */ - string version() const; - /** @brief Gets version integer + uint64_t version() const; + /** @brief Get logging configuration path. * - * @return Version integer independent of the string output with -v/--version + * @return Logging configuration location from command line */ - uint64_t version_int() const; + bfs::path get_logging_conf() const; /** * @brief Looks for the --plugin commandline / config option and calls initialize on those plugins * From 1ecc4779b5d07c63e7193a604d263e278f0bb5a3 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Thu, 30 Nov 2017 14:04:34 -0600 Subject: [PATCH 11/34] CLI documentation clarification --- application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application.cpp b/application.cpp index 83167cc7f..d43e97f48 100644 --- a/application.cpp +++ b/application.cpp @@ -83,7 +83,7 @@ void application::set_program_options() ("version,v", "Print version information.") ("data-dir,d", bpo::value()->default_value( "data-dir" ), "Directory containing configuration file config.ini") ("config,c", bpo::value()->default_value( "config.ini" ), "Configuration file name relative to data-dir"); - ("logconf,l", bpo::value()->default_value( "logging.json" ), "Logging configuration file name/path"); + ("logconf,l", bpo::value()->default_value( "logging.json" ), "Logging configuration file name/path for library users"); my->_cfg_options.add(app_cfg_opts); my->_app_options.add(app_cfg_opts); From 9cf6f43506ac953789c5e97fcde73ffe1b7242c4 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Thu, 30 Nov 2017 17:23:33 -0600 Subject: [PATCH 12/34] Remove semicolon disabling logconf option. --- application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application.cpp b/application.cpp index d43e97f48..4e66ba5d5 100644 --- a/application.cpp +++ b/application.cpp @@ -82,7 +82,7 @@ void application::set_program_options() ("help,h", "Print this help message and exit.") ("version,v", "Print version information.") ("data-dir,d", bpo::value()->default_value( "data-dir" ), "Directory containing configuration file config.ini") - ("config,c", bpo::value()->default_value( "config.ini" ), "Configuration file name relative to data-dir"); + ("config,c", bpo::value()->default_value( "config.ini" ), "Configuration file name relative to data-dir") ("logconf,l", bpo::value()->default_value( "logging.json" ), "Logging configuration file name/path for library users"); my->_cfg_options.add(app_cfg_opts); From 770392a602f4b64b64205369eb0148fa8c261e44 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Thu, 30 Nov 2017 17:42:28 -0600 Subject: [PATCH 13/34] Default logging filename. Command line is parsed very late. Will rearrange later. --- application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application.cpp b/application.cpp index 4e66ba5d5..aa9e10f82 100644 --- a/application.cpp +++ b/application.cpp @@ -23,7 +23,7 @@ class application_impl { options_description _cfg_options; bfs::path _data_dir; - bfs::path _logging_conf; + bfs::path _logging_conf{"logging.json"}; uint64_t _version; }; From 3f5d0601e1ccef8c15c5c96bdf46032b86a4d081 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Fri, 8 Dec 2017 16:06:45 -0600 Subject: [PATCH 14/34] Move default location of logging.json to data directory. --- application.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/application.cpp b/application.cpp index aa9e10f82..f0960696e 100644 --- a/application.cpp +++ b/application.cpp @@ -106,15 +106,6 @@ bool application::initialize_impl(int argc, char** argv, vector(); - if( logconf.is_relative() ) - logconf = bfs::current_path() / logconf; - } - my->_logging_conf = logconf; - bfs::path data_dir = "data-dir"; if( options.count("data-dir") ) { @@ -124,6 +115,15 @@ bool application::initialize_impl(int argc, char** argv, vector_data_dir = data_dir; + bfs::path logconf = data_dir / "logging.json"; + if( options.count("logconf") ) + { + logconf = options["logconf"].as(); + if( logconf.is_relative() ) + logconf = data_dir / logconf; + } + my->_logging_conf = logconf; + bfs::path config_file_name = data_dir / "config.ini"; if( options.count( "config" ) ) { auto config_file_name = options["config"].as(); From bc8a43b706a5ee584d46c6121aa52cc84e6c5128 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Wed, 10 Jan 2018 17:02:53 -0600 Subject: [PATCH 15/34] Remove shadowed variable breaking config filename customization. --- application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application.cpp b/application.cpp index f0960696e..db3d5a3ae 100644 --- a/application.cpp +++ b/application.cpp @@ -126,7 +126,7 @@ bool application::initialize_impl(int argc, char** argv, vector(); + config_file_name = options["config"].as(); if( config_file_name.is_relative() ) config_file_name = data_dir / config_file_name; } From d9f7184eb59f09d484b05e7c979704525060a1d9 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Thu, 18 Jan 2018 17:59:14 -0600 Subject: [PATCH 16/34] Error and exit on missing explicit config file. Resolves #1109. --- application.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application.cpp b/application.cpp index db3d5a3ae..3fe9a99fc 100644 --- a/application.cpp +++ b/application.cpp @@ -132,6 +132,11 @@ bool application::initialize_impl(int argc, char** argv, vector Date: Wed, 14 Feb 2018 13:20:46 -0500 Subject: [PATCH 17/34] Fix boost library link ordering boost filesystem uses boost system. Set the link order accordingly. Fixes boost 1.66 linkage on Linux with ld. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 773e8c4ec..4b8607bc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,8 +8,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS "ON") SET(BOOST_COMPONENTS) LIST(APPEND BOOST_COMPONENTS thread date_time - system filesystem + system chrono program_options unit_test_framework From 93ec299b0ea9f57d941d7b5161ef8a283817bc9b Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Tue, 27 Feb 2018 14:21:18 -0600 Subject: [PATCH 18/34] Add independent config directory and let users set default dirs. Use standard endline. Regularize capitalization of CMake directives. Revise CMake installation with an eye toward more modern versions of CMake. Set explicit installation directory permissions so it works regardless of user's umask. --- CMakeLists.txt | 25 +++++--- .../InstallDirectoryPermissions.cmake | 14 +++++ application.cpp | 61 ++++++++++++------- include/appbase/application.hpp | 25 +++++++- 4 files changed, 91 insertions(+), 34 deletions(-) create mode 100644 CMakeModules/InstallDirectoryPermissions.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b8607bc7..5f7ab147e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,15 @@ project( AppBase ) cmake_minimum_required( VERSION 2.8.12 ) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") + +include( InstallDirectoryPermissions ) + file(GLOB HEADERS "include/appbase/*.hpp") set(CMAKE_EXPORT_COMPILE_COMMANDS "ON") -SET(BOOST_COMPONENTS) -LIST(APPEND BOOST_COMPONENTS thread +set(BOOST_COMPONENTS) +list(APPEND BOOST_COMPONENTS thread date_time filesystem system @@ -15,7 +19,7 @@ LIST(APPEND BOOST_COMPONENTS thread unit_test_framework locale) -FIND_PACKAGE(Boost 1.60 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) +find_package(Boost 1.60 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) set( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" ) if( APPLE ) @@ -48,13 +52,18 @@ target_link_libraries( appbase ${Boost_LIBRARIES}) target_include_directories( appbase PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ${Boost_INCLUDE_DIR}) -INSTALL( TARGETS +set_target_properties( appbase PROPERTIES PUBLIC_HEADER "${HEADERS}" ) + +set(CPACK_PACKAGING_INSTALL_PREFIX /) + +install( TARGETS appbase - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib + RUNTIME DESTINATION usr/bin + LIBRARY DESTINATION usr/lib + ARCHIVE DESTINATION usr/lib + PUBLIC_HEADER DESTINATION usr/include/appbase ) -INSTALL( FILES ${HEADERS} DESTINATION "include/appbase" ) +install_directory_permissions( DIRECTORY usr/include/appbase ) add_subdirectory( examples ) diff --git a/CMakeModules/InstallDirectoryPermissions.cmake b/CMakeModules/InstallDirectoryPermissions.cmake new file mode 100644 index 000000000..b13a80692 --- /dev/null +++ b/CMakeModules/InstallDirectoryPermissions.cmake @@ -0,0 +1,14 @@ +# Fix directory permissions after installation of header files (primarily). +macro(install_directory_permissions) + cmake_parse_arguments(ARG "" "DIRECTORY" "" ${ARGN}) + set(dir ${ARG_DIRECTORY}) + install(DIRECTORY DESTINATION ${dir} + DIRECTORY_PERMISSIONS OWNER_READ + OWNER_WRITE + OWNER_EXECUTE + GROUP_READ + GROUP_EXECUTE + WORLD_READ + WORLD_EXECUTE + ) +endmacro(install_directory_permissions) diff --git a/application.cpp b/application.cpp index 3fe9a99fc..d508f3341 100644 --- a/application.cpp +++ b/application.cpp @@ -22,7 +22,8 @@ class application_impl { options_description _app_options; options_description _cfg_options; - bfs::path _data_dir; + bfs::path _data_dir{"data-dir"}; + bfs::path _config_dir{"config-dir"}; bfs::path _logging_conf{"logging.json"}; uint64_t _version; @@ -43,6 +44,14 @@ uint64_t application::version() const { return my->_version; } +void application::set_default_data_dir(const bfs::path& data_dir) { + my->_data_dir = data_dir; +} + +void application::set_default_config_dir(const bfs::path& config_dir) { + my->_config_dir = config_dir; +} + bfs::path application::get_logging_conf() const { return my->_logging_conf; } @@ -81,8 +90,9 @@ void application::set_program_options() app_cli_opts.add_options() ("help,h", "Print this help message and exit.") ("version,v", "Print version information.") - ("data-dir,d", bpo::value()->default_value( "data-dir" ), "Directory containing configuration file config.ini") - ("config,c", bpo::value()->default_value( "config.ini" ), "Configuration file name relative to data-dir") + ("data-dir,d", bpo::value(), "Directory containing program runtime data") + ("config-dir", bpo::value(), "Directory containing configuration files such as config.ini") + ("config,c", bpo::value()->default_value( "config.ini" ), "Configuration file name relative to config-dir") ("logconf,l", bpo::value()->default_value( "logging.json" ), "Logging configuration file name/path for library users"); my->_cfg_options.add(app_cfg_opts); @@ -106,33 +116,34 @@ bool application::initialize_impl(int argc, char** argv, vector(); + if( options.count( "data-dir" ) ) { + bfs::path data_dir = options["data-dir"].as(); if( data_dir.is_relative() ) data_dir = bfs::current_path() / data_dir; + my->_data_dir = data_dir; } - my->_data_dir = data_dir; - bfs::path logconf = data_dir / "logging.json"; - if( options.count("logconf") ) - { - logconf = options["logconf"].as(); - if( logconf.is_relative() ) - logconf = data_dir / logconf; + if( options.count( "config-dir" ) ) { + bfs::path config_dir = options["config-dir"].as(); + if( config_dir.is_relative() ) + config_dir = bfs::current_path() / config_dir; + my->_config_dir = config_dir; } + + bfs::path logconf = options["logconf"].as(); + if( logconf.is_relative() ) + logconf = my->_config_dir / logconf; my->_logging_conf = logconf; - bfs::path config_file_name = data_dir / "config.ini"; + bfs::path config_file_name = my->_config_dir / "config.ini"; if( options.count( "config" ) ) { config_file_name = options["config"].as(); if( config_file_name.is_relative() ) - config_file_name = data_dir / config_file_name; + config_file_name = my->_config_dir / config_file_name; } if(!bfs::exists(config_file_name)) { - if(config_file_name.compare(data_dir / "config.ini") != 0) + if(config_file_name.compare(my->_config_dir / "config.ini") != 0) { cout << "Config file " << config_file_name << " missing." << std::endl; return false; @@ -208,23 +219,23 @@ void application::write_default_config(const bfs::path& cfg_file) { for(const boost::shared_ptr od : my->_cfg_options.options()) { if(!od->description().empty()) - out_cfg << "# " << od->description() << "\n"; + out_cfg << "# " << od->description() << std::endl; boost::any store; if(!od->semantic()->apply_default(store)) - out_cfg << "# " << od->long_name() << " = \n"; + out_cfg << "# " << od->long_name() << " = " << std::endl; else { auto example = od->format_parameter(); if(example.empty()) // This is a boolean switch - out_cfg << od->long_name() << " = " << "false\n"; + out_cfg << od->long_name() << " = " << "false" << std::endl; else { // The string is formatted "arg (=)" example.erase(0, 6); example.erase(example.length()-1); - out_cfg << od->long_name() << " = " << example << "\n"; + out_cfg << od->long_name() << " = " << example << std::endl; } } - out_cfg << "\n"; + out_cfg << std::endl; } out_cfg.close(); } @@ -245,8 +256,12 @@ abstract_plugin& application::get_plugin(const string& name)const { return *ptr; } -bfs::path application::data_dir()const { +bfs::path application::data_dir() const { return my->_data_dir; } +bfs::path application::config_dir() const { + return my->_config_dir; +} + } /// namespace appbase diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index fbe4b3d09..1e79c2501 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -23,6 +23,28 @@ namespace appbase { * @return Version output with -v/--version */ uint64_t version() const; + /** @brief Set default data directory + * + * @param data_dir Default data directory to use if not specified + * on the command line. + */ + void set_default_data_dir(const bfs::path& data_dir = "data-dir"); + /** @brief Get data directory + * + * @return Data directory, possibly from command line + */ + bfs::path data_dir() const; + /** @brief Set default config directory + * + * @param config_dir Default configuration directory to use if not + * specified on the command line. + */ + void set_default_config_dir(const bfs::path& config_dir = "etc"); + /** @brief Get config directory + * + * @return Config directory, possibly from command line + */ + bfs::path config_dir() const; /** @brief Get logging configuration path. * * @return Logging configuration location from command line @@ -78,9 +100,6 @@ namespace appbase { return *ptr; } - bfs::path data_dir()const; - - boost::asio::io_service& get_io_service() { return *io_serv; } protected: template From 15ecfca2953d6a5e01d651bcfe8ce482c576fc3b Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Mon, 5 Mar 2018 14:25:48 -0500 Subject: [PATCH 19/34] Add plugin per config option in config file descriptions Now shows (for example) # Track only transactions whose scopes involve the listed accounts. Default is to track all transactions. (eosio::account_history_plugin) # filter_on_accounts = Instead of # Track only transactions whose scopes involve the listed accounts. Default is to track all transactions. # filter_on_accounts = Which makes it a little easier to determine the context of the config option and description --- application.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/application.cpp b/application.cpp index d508f3341..04771c561 100644 --- a/application.cpp +++ b/application.cpp @@ -215,11 +215,26 @@ void application::write_default_config(const bfs::path& cfg_file) { if(!bfs::exists(cfg_file.parent_path())) bfs::create_directories(cfg_file.parent_path()); + std::map option_to_plug; + for(auto& plug : plugins) { + boost::program_options::options_description plugin_cli_opts; + boost::program_options::options_description plugin_cfg_opts; + plug.second->set_program_options(plugin_cli_opts, plugin_cfg_opts); + + for(const boost::shared_ptr& opt : plugin_cfg_opts.options()) + option_to_plug[opt->long_name()] = plug.second->name(); + } + std::ofstream out_cfg( bfs::path(cfg_file).make_preferred().string()); for(const boost::shared_ptr od : my->_cfg_options.options()) { - if(!od->description().empty()) - out_cfg << "# " << od->description() << std::endl; + if(!od->description().empty()) { + out_cfg << "# " << od->description(); + std::map::iterator it; + if((it = option_to_plug.find(od->long_name())) != option_to_plug.end()) + out_cfg << " (" << it->second << ")"; + out_cfg << std::endl; + } boost::any store; if(!od->semantic()->apply_default(store)) out_cfg << "# " << od->long_name() << " = " << std::endl; From b28ce4c9b8be9b7fa7407bed6329527f321808e3 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Thu, 8 Mar 2018 15:55:23 -0600 Subject: [PATCH 20/34] Use CMake standard variables to install. --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f7ab147e..4d8a53ea9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,11 +59,11 @@ set(CPACK_PACKAGING_INSTALL_PREFIX /) install( TARGETS appbase - RUNTIME DESTINATION usr/bin - LIBRARY DESTINATION usr/lib - ARCHIVE DESTINATION usr/lib - PUBLIC_HEADER DESTINATION usr/include/appbase + RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/appbase ) -install_directory_permissions( DIRECTORY usr/include/appbase ) +install_directory_permissions( DIRECTORY ${CMAKE_INSTALL_FULL_INCLUDEDIR}/appbase ) add_subdirectory( examples ) From 665e865c7a61480c9758bd820d7d6dd7c78f9384 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Thu, 29 Mar 2018 14:28:50 -0400 Subject: [PATCH 21/34] When a plugin throws during startup, cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some of our plugins will throw an exception on startup. For example, the http or net plugin will throw if it can’t bind to an address. Exceptions thrown at this stage are uncaught and cause the process to exit. However, in the process of unwinding the stack to exit, application’s dtor will call all of the already started plugins dtors. Some of these plugins don’t handle the case where their dtor is called without shtudown() being called on the plugin first. Result can be a (benign, but still embarrassing) crash on exit. Change appbase so that any exception thrown during startup() is caught, then all plugins shutdown, then exception is rethrown so that the app continues to exit. --- application.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/application.cpp b/application.cpp index 04771c561..4b493aaaf 100644 --- a/application.cpp +++ b/application.cpp @@ -57,8 +57,13 @@ bfs::path application::get_logging_conf() const { } void application::startup() { - for (auto plugin : initialized_plugins) - plugin->startup(); + try { + for (auto plugin : initialized_plugins) + plugin->startup(); + } catch(...) { + shutdown(); + throw; + } } application& application::instance() { From 5761e03a993b07e2c9cee4cb9d67d8b15f18ca52 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Mon, 2 Apr 2018 18:10:00 -0400 Subject: [PATCH 22/34] Add option to print default configuration template Add an option to print the default configuration template. This could be useful to packagers (to create an /etc/ template without making /etc writable) or for users to see a diff of their config.ini compared to future defaults. --- application.cpp | 28 +++++++++++++++++++--------- include/appbase/application.hpp | 1 + 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/application.cpp b/application.cpp index 4b493aaaf..82344ddd7 100644 --- a/application.cpp +++ b/application.cpp @@ -95,6 +95,7 @@ void application::set_program_options() app_cli_opts.add_options() ("help,h", "Print this help message and exit.") ("version,v", "Print version information.") + ("print-default-config", "Print default configuration template") ("data-dir,d", bpo::value(), "Directory containing program runtime data") ("config-dir", bpo::value(), "Directory containing configuration files such as config.ini") ("config,c", bpo::value()->default_value( "config.ini" ), "Configuration file name relative to config-dir") @@ -121,6 +122,11 @@ bool application::initialize_impl(int argc, char** argv, vector(); if( data_dir.is_relative() ) @@ -220,6 +226,12 @@ void application::write_default_config(const bfs::path& cfg_file) { if(!bfs::exists(cfg_file.parent_path())) bfs::create_directories(cfg_file.parent_path()); + std::ofstream out_cfg( bfs::path(cfg_file).make_preferred().string()); + print_default_config(out_cfg); + out_cfg.close(); +} + +void application::print_default_config(std::ostream& os) { std::map option_to_plug; for(auto& plug : plugins) { boost::program_options::options_description plugin_cli_opts; @@ -230,34 +242,32 @@ void application::write_default_config(const bfs::path& cfg_file) { option_to_plug[opt->long_name()] = plug.second->name(); } - std::ofstream out_cfg( bfs::path(cfg_file).make_preferred().string()); for(const boost::shared_ptr od : my->_cfg_options.options()) { if(!od->description().empty()) { - out_cfg << "# " << od->description(); + os << "# " << od->description(); std::map::iterator it; if((it = option_to_plug.find(od->long_name())) != option_to_plug.end()) - out_cfg << " (" << it->second << ")"; - out_cfg << std::endl; + os << " (" << it->second << ")"; + os << std::endl; } boost::any store; if(!od->semantic()->apply_default(store)) - out_cfg << "# " << od->long_name() << " = " << std::endl; + os << "# " << od->long_name() << " = " << std::endl; else { auto example = od->format_parameter(); if(example.empty()) // This is a boolean switch - out_cfg << od->long_name() << " = " << "false" << std::endl; + os << od->long_name() << " = " << "false" << std::endl; else { // The string is formatted "arg (=)" example.erase(0, 6); example.erase(example.length()-1); - out_cfg << od->long_name() << " = " << example << std::endl; + os << od->long_name() << " = " << example << std::endl; } } - out_cfg << std::endl; + os << std::endl; } - out_cfg.close(); } abstract_plugin* application::find_plugin(const string& name)const diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 1e79c2501..571d26492 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -124,6 +124,7 @@ namespace appbase { void set_program_options(); void write_default_config(const bfs::path& cfg_file); + void print_default_config(std::ostream& os); std::unique_ptr my; }; From e44c85f2798964d0107231429703094936e0e565 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Tue, 10 Apr 2018 14:54:39 -0500 Subject: [PATCH 23/34] Work around boost defect at intersection of program_options and filesystem See https://svn.boost.org/trac10/ticket/8535 Also remove config options count check: defaulted options always have a nonzero count. --- application.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/application.cpp b/application.cpp index 82344ddd7..e47378c52 100644 --- a/application.cpp +++ b/application.cpp @@ -96,10 +96,10 @@ void application::set_program_options() ("help,h", "Print this help message and exit.") ("version,v", "Print version information.") ("print-default-config", "Print default configuration template") - ("data-dir,d", bpo::value(), "Directory containing program runtime data") - ("config-dir", bpo::value(), "Directory containing configuration files such as config.ini") - ("config,c", bpo::value()->default_value( "config.ini" ), "Configuration file name relative to config-dir") - ("logconf,l", bpo::value()->default_value( "logging.json" ), "Logging configuration file name/path for library users"); + ("data-dir,d", bpo::value(), "Directory containing program runtime data") + ("config-dir", bpo::value(), "Directory containing configuration files such as config.ini") + ("config,c", bpo::value()->default_value( "config.ini" ), "Configuration file name relative to config-dir") + ("logconf,l", bpo::value()->default_value( "logging.json" ), "Logging configuration file name/path for library users"); my->_cfg_options.add(app_cfg_opts); my->_app_options.add(app_cfg_opts); @@ -128,30 +128,35 @@ bool application::initialize_impl(int argc, char** argv, vector(); + // Workaround for 10+ year old Boost defect + // See https://svn.boost.org/trac10/ticket/8535 + // Should be .as() but paths with escaped spaces break bpo e.g. + // std::exception::what: the argument ('/path/with/white\ space') for option '--data-dir' is invalid + auto workaround = options["data-dir"].as(); + bfs::path data_dir = workaround; if( data_dir.is_relative() ) data_dir = bfs::current_path() / data_dir; my->_data_dir = data_dir; } if( options.count( "config-dir" ) ) { - bfs::path config_dir = options["config-dir"].as(); + auto workaround = options["config-dir"].as(); + bfs::path config_dir = workaround; if( config_dir.is_relative() ) config_dir = bfs::current_path() / config_dir; my->_config_dir = config_dir; } - bfs::path logconf = options["logconf"].as(); + auto workaround = options["logconf"].as(); + bfs::path logconf = workaround; if( logconf.is_relative() ) logconf = my->_config_dir / logconf; my->_logging_conf = logconf; - bfs::path config_file_name = my->_config_dir / "config.ini"; - if( options.count( "config" ) ) { - config_file_name = options["config"].as(); - if( config_file_name.is_relative() ) - config_file_name = my->_config_dir / config_file_name; - } + workaround = options["config"].as(); + bfs::path config_file_name = workaround; + if( config_file_name.is_relative() ) + config_file_name = my->_config_dir / config_file_name; if(!bfs::exists(config_file_name)) { if(config_file_name.compare(my->_config_dir / "config.ini") != 0) From 039e2316417c7c2e0bc87b2f6b3df55481b3d64c Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 18 Apr 2018 15:01:51 -0400 Subject: [PATCH 24/34] initial support for channels and methods --- include/appbase/application.hpp | 36 +++++++- include/appbase/channel.hpp | 158 ++++++++++++++++++++++++++++++++ include/appbase/method.hpp | 157 +++++++++++++++++++++++++++++++ 3 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 include/appbase/channel.hpp create mode 100644 include/appbase/method.hpp diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 571d26492..9011688dd 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -1,8 +1,9 @@ #pragma once #include +#include +#include #include #include -#include namespace appbase { namespace bpo = boost::program_options; @@ -13,6 +14,7 @@ namespace appbase { public: ~application(); + /** @brief Set version * * @param version Version output with -v/--version @@ -100,6 +102,34 @@ namespace appbase { return *ptr; } + template + auto get_method() -> std::enable_if_t::value, typename MethodDecl::method_type&> + { + using method_type = typename MethodDecl::method_type; + auto key = std::type_index(typeid(MethodDecl)); + auto itr = methods.find(key); + if(itr != methods.end()) { + return *method_type::get_method(itr->second); + } else { + methods.emplace(std::make_pair(key, method_type::make_unique())); + return *method_type::get_method(methods.at(key)); + } + } + + template + auto get_channel() -> std::enable_if_t::value, typename ChannelDecl::channel_type&> + { + using channel_type = typename ChannelDecl::channel_type; + auto key = std::type_index(typeid(ChannelDecl)); + auto itr = channels.find(key); + if(itr != channels.end()) { + return *channel_type::get_channel(itr->second); + } else { + channels.emplace(std::make_pair(key, channel_type::make_unique(io_serv))); + return *channel_type::get_channel(channels.at(key)); + } + } + boost::asio::io_service& get_io_service() { return *io_serv; } protected: template @@ -120,6 +150,10 @@ namespace appbase { map> plugins; ///< all registered plugins vector initialized_plugins; ///< stored in the order they were started running vector running_plugins; ///< stored in the order they were started running + + map methods; + map channels; + std::shared_ptr io_serv; void set_program_options(); diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp new file mode 100644 index 000000000..22342b770 --- /dev/null +++ b/include/appbase/channel.hpp @@ -0,0 +1,158 @@ +#pragma once + +//clashes with BOOST PP and Some Applications +#pragma push_macro("N") +#undef N + +#include +#include +#include + +namespace appbase { + + using erased_channel_ptr = std::unique_ptr; + + struct drop_exceptions { + drop_exceptions() = default; + using result_type = void; + + template + result_type operator()(InputIterator first, InputIterator last) { + while (first != last) { + try { + *first; + } catch (...) { + // drop + } + ++first; + } + } + }; + + /** + * A channel is a loosely bound asynchronous data pub/sub concept. + * + * This removes the need to tightly couple different plugins in the application for the use-case of + * sending data around + * + * Data passed to a channel is *copied*, consider using a shared_ptr if the use-case allows it + * + * @tparam Data - the type of data to publish + */ + template + class channel final : private boost::signals2::signal { + public: + using ios_ptr_type = std::shared_ptr; + using handle_type = boost::signals2::connection; + + /** + * Publish data to a channel + * @param data + */ + void publish(const Data& data) { + if (has_subscribers()) { + // this will copy data into the lambda + ios_ptr->post([this, data]() { + (*this)(data); + }); + } + } + + /** + * subscribe to data on a channel + * @tparam Callback the type of the callback (functor|lambda) + * @param cb the callback + * @return handle to the subscription + */ + template + handle_type subscribe(Callback cb) { + return this->connect(cb); + } + + /** + * unsubscribe from data on a channel + * @param handle + */ + void unsubscribe(const handle_type& handle) { + handle.disconnect(); + } + + /** + * set the dispatcher according to the DispatchPolicy + * this can be used to set a stateful dispatcher + * + * This method is only available when the DispatchPolicy is copy constructible due to implementation details + * + * @param policy - the DispatchPolicy to copy + */ + auto set_dispatcher(const DispatchPolicy& policy ) -> std::enable_if_t::value,void> + { + (*this).set_combiner(policy); + } + + /** + * Returns whether or not there are subscribers + */ + bool has_subscribers() { + return (*this).num_slots() > 0; + } + + private: + channel(const ios_ptr_type& ios_ptr) + :ios_ptr(ios_ptr) + { + } + + virtual ~channel() {}; + + /** + * Proper deleter for type-erased channel + * note: no type checking is performed at this level + * + * @param erased_channel_ptr + */ + static void deleter(void* erased_channel_ptr) { + channel *ptr = reinterpret_cast(erased_channel_ptr); + delete ptr; + } + + /** + * get the channel back from an erased pointer + * + * @param ptr - the type-erased channel pointer + * @return - the type safe channel pointer + */ + static channel* get_channel(erased_channel_ptr& ptr) { + return reinterpret_cast(ptr.get()); + } + + /** + * Construct a unique_ptr for the type erased method poiner + * @return + */ + static erased_channel_ptr make_unique(const ios_ptr_type& ios_ptr) + { + return erased_channel_ptr(new channel(ios_ptr), &deleter); + } + + ios_ptr_type ios_ptr; + + friend class appbase::application; + }; + + template< typename Tag, typename Data, typename DispatchPolicy = drop_exceptions > + struct channel_decl { + using channel_type = channel; + using tag_type = Tag; + }; + + template + std::true_type is_channel_decl_impl(const channel_decl*); + + std::false_type is_channel_decl_impl(...); + + template + using is_channel_decl = decltype(is_channel_decl_impl(std::declval())); +} + +#pragma pop_macro("N") diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp new file mode 100644 index 000000000..e088dd0a2 --- /dev/null +++ b/include/appbase/method.hpp @@ -0,0 +1,157 @@ +#pragma once + +//clashes with BOOST PP and Some Applications +#pragma push_macro("N") +#undef N + +#include +#include + +namespace appbase { + + using erased_method_ptr = std::unique_ptr; + + template + struct dispatch_policy_helper_impl { + using result_type = Ret; + }; + + template + struct function_sig_helper; + + template + struct function_sig_helper { + using result_type = typename dispatch_policy_helper_impl::result_type; + using args_tuple_type = std::tuple; + }; + + template + struct first_success_policy { + using result_type = typename function_sig_helper::result_type; + std::string err; + + /** + * Iterate through the providers, calling (dereferencing) each + * if the provider throws, then store then try the next provider + * if none succeed throw an error with the aggregated error descriptions + * + * @tparam InputIterator + * @param first + * @param last + * @return + */ + template + result_type operator()(InputIterator first, InputIterator last) { + while (first != last) { + try { + return *first; // de-referencing the iterator causes the provider to run + } catch (...) { + if (!err.empty()) { + err += "\",\""; + } + + err += boost::current_exception_diagnostic_information(); + } + + ++first; + } + + throw new std::length_error(std::string("No Result Available, All providers returned exceptions[") + err + "]"); + } + }; + + /** + * A method is a loosely linked application level function. + * Callers can grab a method and call it + * Providers can grab a method and register themselves + * + * This removes the need to tightly couple different plugins in the application. + * + * @tparam FunctionSig - the signature of the method (eg void(int, int)) + * @tparam DispatchPolicy - the policy for dispatching this method + */ + template + class method final : private boost::signals2::signal { + public: + using args_tuple_type = typename function_sig_helper::args_tuple_type; + using result_type = typename function_sig_helper::result_type; + + /** + * Register a provider of this method + * + * @tparam T - the type of the provider (functor, lambda) + * @param provider - the provider + * @param priority - the priority of this provider, lower is called before higher + */ + template + void register_provider(T provider, int priority = 0) { + this->connect(priority, provider); + } + + /** + * inhereted call operator from boost::signals2 + * + * @throws exception depending on the DispatchPolicy + */ + template + auto operator()(Args ... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> + { + return this->operator()(args...); + } + + protected: + method() = default; + virtual ~method() {}; + + /** + * Proper deleter for type-erased method + * note: no type checking is performed at this level + * + * @param erased_method_ptr + */ + static void deleter(void* erased_method_ptr) { + method *ptr = reinterpret_cast(erased_method_ptr); + delete ptr; + } + + /** + * get the method* back from an erased pointer + * + * @param ptr - the type-erased method pointer + * @return - the type safe method pointer + */ + static method* get_method(erased_method_ptr& ptr) { + return reinterpret_cast(ptr.get()); + } + + /** + * Construct a unique_ptr for the type erased method poiner + * @return + */ + static erased_method_ptr make_unique() { + return erased_method_ptr(new method(), &deleter); + } + + friend class appbase::application; + }; + + + template< typename Tag, typename FunctionSig, typename DispatchPolicy = first_success_policy > + struct method_decl { + using method_type = method; + using tag_type = Tag; + }; + + template + std::true_type is_method_decl_impl(const method_decl*); + + std::false_type is_method_decl_impl(...); + + template + using is_method_decl = decltype(is_method_decl_impl(std::declval())); + + +} + +#pragma pop_macro("N") + From b82f863361768fe13e1b4c81fb4873e8d93d41c3 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 25 Apr 2018 22:31:12 -0400 Subject: [PATCH 25/34] remove inheritance from signal, now composed. also fix bad throw --- include/appbase/channel.hpp | 17 +++++++++-------- include/appbase/method.hpp | 14 ++++++++------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index 22342b770..9792541ed 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -40,7 +40,7 @@ namespace appbase { * @tparam Data - the type of data to publish */ template - class channel final : private boost::signals2::signal { + class channel final { public: using ios_ptr_type = std::shared_ptr; using handle_type = boost::signals2::connection; @@ -53,7 +53,7 @@ namespace appbase { if (has_subscribers()) { // this will copy data into the lambda ios_ptr->post([this, data]() { - (*this)(data); + _signal(data); }); } } @@ -66,7 +66,7 @@ namespace appbase { */ template handle_type subscribe(Callback cb) { - return this->connect(cb); + return _signal.connect(cb); } /** @@ -87,23 +87,23 @@ namespace appbase { */ auto set_dispatcher(const DispatchPolicy& policy ) -> std::enable_if_t::value,void> { - (*this).set_combiner(policy); + _signal.set_combiner(policy); } /** * Returns whether or not there are subscribers */ bool has_subscribers() { - return (*this).num_slots() > 0; + return _signal.num_slots() > 0; } private: - channel(const ios_ptr_type& ios_ptr) + explicit channel(const ios_ptr_type& ios_ptr) :ios_ptr(ios_ptr) { } - virtual ~channel() {}; + virtual ~channel() = default; /** * Proper deleter for type-erased channel @@ -112,7 +112,7 @@ namespace appbase { * @param erased_channel_ptr */ static void deleter(void* erased_channel_ptr) { - channel *ptr = reinterpret_cast(erased_channel_ptr); + auto ptr = reinterpret_cast(erased_channel_ptr); delete ptr; } @@ -136,6 +136,7 @@ namespace appbase { } ios_ptr_type ios_ptr; + boost::signals2::signal _signal; friend class appbase::application; }; diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp index e088dd0a2..d95abea17 100644 --- a/include/appbase/method.hpp +++ b/include/appbase/method.hpp @@ -56,7 +56,7 @@ namespace appbase { ++first; } - throw new std::length_error(std::string("No Result Available, All providers returned exceptions[") + err + "]"); + throw std::length_error(std::string("No Result Available, All providers returned exceptions[") + err + "]"); } }; @@ -71,7 +71,7 @@ namespace appbase { * @tparam DispatchPolicy - the policy for dispatching this method */ template - class method final : private boost::signals2::signal { + class method final { public: using args_tuple_type = typename function_sig_helper::args_tuple_type; using result_type = typename function_sig_helper::result_type; @@ -85,7 +85,7 @@ namespace appbase { */ template void register_provider(T provider, int priority = 0) { - this->connect(priority, provider); + _signal.connect(priority, provider); } /** @@ -96,12 +96,12 @@ namespace appbase { template auto operator()(Args ... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> { - return this->operator()(args...); + return _signal(args...); } protected: method() = default; - virtual ~method() {}; + virtual ~method() = default; /** * Proper deleter for type-erased method @@ -110,7 +110,7 @@ namespace appbase { * @param erased_method_ptr */ static void deleter(void* erased_method_ptr) { - method *ptr = reinterpret_cast(erased_method_ptr); + auto ptr = reinterpret_cast(erased_method_ptr); delete ptr; } @@ -132,6 +132,8 @@ namespace appbase { return erased_method_ptr(new method(), &deleter); } + boost::signals2::signal _signal; + friend class appbase::application; }; From 24cb7425908af922fbf67708f9c8ce447056c85c Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 26 Apr 2018 09:06:16 -0400 Subject: [PATCH 26/34] added forwarding to the methods --- include/appbase/channel.hpp | 1 + include/appbase/method.hpp | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index 9792541ed..ffbe089be 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -44,6 +44,7 @@ namespace appbase { public: using ios_ptr_type = std::shared_ptr; using handle_type = boost::signals2::connection; + using scoped_handle_type = boost::signals2::scoped_connection; /** * Publish data to a channel diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp index d95abea17..8e6c8c3d3 100644 --- a/include/appbase/method.hpp +++ b/include/appbase/method.hpp @@ -16,18 +16,19 @@ namespace appbase { using result_type = Ret; }; - template - struct function_sig_helper; + template + struct method_traits; template - struct function_sig_helper { + struct method_traits { using result_type = typename dispatch_policy_helper_impl::result_type; using args_tuple_type = std::tuple; + }; template struct first_success_policy { - using result_type = typename function_sig_helper::result_type; + using result_type = typename method_traits::result_type; std::string err; /** @@ -73,8 +74,9 @@ namespace appbase { template class method final { public: - using args_tuple_type = typename function_sig_helper::args_tuple_type; - using result_type = typename function_sig_helper::result_type; + using traits = method_traits; + using args_tuple_type = traits::args_tuple_type; + using result_type = traits::result_type; /** * Register a provider of this method @@ -94,9 +96,9 @@ namespace appbase { * @throws exception depending on the DispatchPolicy */ template - auto operator()(Args ... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> + auto operator()(Args&&... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> { - return _signal(args...); + return _signal(std::forward(args...)); } protected: @@ -138,7 +140,7 @@ namespace appbase { }; - template< typename Tag, typename FunctionSig, typename DispatchPolicy = first_success_policy > + template< typename Tag, typename FunctionSig, typename DispatchPolicy = first_success_policy> struct method_decl { using method_type = method; using tag_type = Tag; From f969e0f3d1070b3336c51d046eca7dd50ced7f0f Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 26 Apr 2018 11:46:09 -0400 Subject: [PATCH 27/34] fix typos --- include/appbase/method.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp index 8e6c8c3d3..74ed8ee03 100644 --- a/include/appbase/method.hpp +++ b/include/appbase/method.hpp @@ -75,8 +75,8 @@ namespace appbase { class method final { public: using traits = method_traits; - using args_tuple_type = traits::args_tuple_type; - using result_type = traits::result_type; + using args_tuple_type = typename traits::args_tuple_type; + using result_type = typename traits::result_type; /** * Register a provider of this method @@ -98,7 +98,7 @@ namespace appbase { template auto operator()(Args&&... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> { - return _signal(std::forward(args...)); + return _signal(std::forward(args)...); } protected: From dd2245eca2de769661bb906f30a5195af46faab6 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 27 Apr 2018 18:37:49 -0400 Subject: [PATCH 28/34] wrap handle for unsub --- include/appbase/channel.hpp | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index ffbe089be..0152c281d 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -43,8 +43,29 @@ namespace appbase { class channel final { public: using ios_ptr_type = std::shared_ptr; - using handle_type = boost::signals2::connection; - using scoped_handle_type = boost::signals2::scoped_connection; + + class handle { + public: + ~handle() { + unsubscribe(); + } + + void unsubscribe() { + if (_handle.connected()) { + _handle.disconnect(); + } + } + + private: + using handle_type = boost::signals2::connection; + handle_type _handle; + + handle(handle_type&& _handle) + :_handle(std::move(_handle)) + {} + + friend class channel; + }; /** * Publish data to a channel @@ -66,16 +87,8 @@ namespace appbase { * @return handle to the subscription */ template - handle_type subscribe(Callback cb) { - return _signal.connect(cb); - } - - /** - * unsubscribe from data on a channel - * @param handle - */ - void unsubscribe(const handle_type& handle) { - handle.disconnect(); + handle subscribe(Callback cb) { + return handle(_signal.connect(cb)); } /** From ca2e56c7081d1f29225b8ea711f05e34da7df0d2 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Sat, 28 Apr 2018 15:39:51 -0400 Subject: [PATCH 29/34] delcare and delete constructors so that handles are moved instead of duplicated/copied --- include/appbase/channel.hpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index 0152c281d..3facf5d4c 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -56,6 +56,14 @@ namespace appbase { } } + handle() = default; + handle(handle&&) = default; + handle& operator= (handle&& rhs) = default; + + // dont allow copying since this protects the resource + handle(const handle& ) = delete; + handle& operator= (const handle& ) = delete; + private: using handle_type = boost::signals2::connection; handle_type _handle; @@ -108,7 +116,8 @@ namespace appbase { * Returns whether or not there are subscribers */ bool has_subscribers() { - return _signal.num_slots() > 0; + auto connections = _signal.num_slots(); + return connections > 0; } private: From 189094f209628408a627018ec38f8bfca96123dd Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 30 Apr 2018 11:46:43 -0400 Subject: [PATCH 30/34] add documentation --- include/appbase/application.hpp | 14 ++++++ include/appbase/channel.hpp | 29 ++++++++++- include/appbase/method.hpp | 88 +++++++++++++++++++++++++++------ 3 files changed, 114 insertions(+), 17 deletions(-) diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 9011688dd..761ed6b27 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -102,6 +102,13 @@ namespace appbase { return *ptr; } + /** + * Fetch a reference to the method declared by the passed in type. This will construct the method + * on first access. This allows loose and deferred binding between plugins + * + * @tparam MethodDecl - @ref appbase::method_decl + * @return reference to the method described by the declaration + */ template auto get_method() -> std::enable_if_t::value, typename MethodDecl::method_type&> { @@ -116,6 +123,13 @@ namespace appbase { } } + /** + * Fetch a reference to the channel declared by the passed in type. This will construct the channel + * on first access. This allows loose and deferred binding between plugins + * + * @tparam ChannelDecl - @ref appbase::channel_decl + * @return reference to the channel described by the declaration + */ template auto get_channel() -> std::enable_if_t::value, typename ChannelDecl::channel_type&> { diff --git a/include/appbase/channel.hpp b/include/appbase/channel.hpp index 3facf5d4c..f8a710875 100644 --- a/include/appbase/channel.hpp +++ b/include/appbase/channel.hpp @@ -12,6 +12,10 @@ namespace appbase { using erased_channel_ptr = std::unique_ptr; + /** + * A basic DispatchPolicy that will catch and drop any exceptions thrown + * during the dispatch of messages on a channel + */ struct drop_exceptions { drop_exceptions() = default; using result_type = void; @@ -44,18 +48,27 @@ namespace appbase { public: using ios_ptr_type = std::shared_ptr; + /** + * Type that represents an active subscription to a channel allowing + * for ownership via RAII and also explicit unsubscribe actions + */ class handle { public: ~handle() { unsubscribe(); } + /** + * Explicitly unsubcribe from channel before the lifetime + * of this object expires + */ void unsubscribe() { if (_handle.connected()) { _handle.disconnect(); } } + // This handle can be constructed and moved handle() = default; handle(handle&&) = default; handle& operator= (handle&& rhs) = default; @@ -68,6 +81,12 @@ namespace appbase { using handle_type = boost::signals2::connection; handle_type _handle; + /** + * Construct a handle from an internal represenation of a handle + * In this case a boost::signals2::connection + * + * @param _handle - the boost::signals2::connection to wrap + */ handle(handle_type&& _handle) :_handle(std::move(_handle)) {} @@ -76,8 +95,8 @@ namespace appbase { }; /** - * Publish data to a channel - * @param data + * Publish data to a channel. This data is *copied* on publish. + * @param data - the data to publish */ void publish(const Data& data) { if (has_subscribers()) { @@ -164,6 +183,12 @@ namespace appbase { friend class appbase::application; }; + /** + * + * @tparam Tag - API specific discriminator used to distinguish between otherwise identical data types + * @tparam Data - the typ of the Data the channel carries + * @tparam DispatchPolicy - The dispatch policy to use for this channel (defaults to @ref drop_exceptions) + */ template< typename Tag, typename Data, typename DispatchPolicy = drop_exceptions > struct channel_decl { using channel_type = channel; diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp index 74ed8ee03..e7c7408e1 100644 --- a/include/appbase/method.hpp +++ b/include/appbase/method.hpp @@ -11,24 +11,30 @@ namespace appbase { using erased_method_ptr = std::unique_ptr; - template - struct dispatch_policy_helper_impl { - using result_type = Ret; - }; + namespace impl { + template + struct dispatch_policy_helper_impl { + using result_type = Ret; + }; - template - struct method_traits; + template + struct method_traits; - template - struct method_traits { - using result_type = typename dispatch_policy_helper_impl::result_type; - using args_tuple_type = std::tuple; + template + struct method_traits { + using result_type = typename dispatch_policy_helper_impl::result_type; + using args_tuple_type = std::tuple; - }; + }; + } + /** + * Basic DispatchPolicy that will try providers sequentially until one succeeds + * without throwing an exception. that result becomes the result of the method + */ template struct first_success_policy { - using result_type = typename method_traits::result_type; + using result_type = typename impl::method_traits::result_type; std::string err; /** @@ -74,10 +80,56 @@ namespace appbase { template class method final { public: - using traits = method_traits; + using traits = impl::method_traits; using args_tuple_type = typename traits::args_tuple_type; using result_type = typename traits::result_type; + /** + * Type that represents a registered provider for a method allowing + * for ownership via RAII and also explicit unregistered actions + */ + class handle { + public: + ~handle() { + unregister(); + } + + /** + * Explicitly unregister a provider for this channel + * of this object expires + */ + void unregister() { + if (_handle.connected()) { + _handle.disconnect(); + } + } + + // This handle can be constructed and moved + handle() = default; + handle(handle&&) = default; + handle& operator= (handle&& rhs) = default; + + // dont allow copying since this protects the resource + handle(const handle& ) = delete; + handle& operator= (const handle& ) = delete; + + private: + using handle_type = boost::signals2::connection; + handle_type _handle; + + /** + * Construct a handle from an internal represenation of a handle + * In this case a boost::signals2::connection + * + * @param _handle - the boost::signals2::connection to wrap + */ + handle(handle_type&& _handle) + :_handle(std::move(_handle)) + {} + + friend class method; + }; + /** * Register a provider of this method * @@ -86,8 +138,8 @@ namespace appbase { * @param priority - the priority of this provider, lower is called before higher */ template - void register_provider(T provider, int priority = 0) { - _signal.connect(priority, provider); + handle register_provider(T provider, int priority = 0) { + return handle(_signal.connect(priority, provider)); } /** @@ -140,6 +192,12 @@ namespace appbase { }; + /** + * + * @tparam Tag - API specific discriminator used to distinguish between otherwise identical method signatures + * @tparam FunctionSig - the signature of the method + * @tparam DispatchPolicy - dispatch policy that dictates how providers for a method are accessed defaults to @ref first_success_policy + */ template< typename Tag, typename FunctionSig, typename DispatchPolicy = first_success_policy> struct method_decl { using method_type = method; From 9142e69832e4c1376bc66110d8a097a8bf178314 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 30 Apr 2018 18:45:02 -0400 Subject: [PATCH 31/34] explicitly include typeindex headers --- include/appbase/application.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/appbase/application.hpp b/include/appbase/application.hpp index 761ed6b27..7ec19fae3 100644 --- a/include/appbase/application.hpp +++ b/include/appbase/application.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace appbase { namespace bpo = boost::program_options; From 25dc1907db4f0bb25e62d638f893b045baaa9058 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 3 May 2018 21:58:27 -0400 Subject: [PATCH 32/34] add a new dispatch method which calls the best provider by priority ONLY made it easier to use different dispatch policies made it possible to have void methods --- include/appbase/method.hpp | 187 ++++++++++++++++++++++++++++--------- 1 file changed, 143 insertions(+), 44 deletions(-) diff --git a/include/appbase/method.hpp b/include/appbase/method.hpp index e7c7408e1..b7f2d0373 100644 --- a/include/appbase/method.hpp +++ b/include/appbase/method.hpp @@ -11,31 +11,16 @@ namespace appbase { using erased_method_ptr = std::unique_ptr; - namespace impl { - template - struct dispatch_policy_helper_impl { - using result_type = Ret; - }; - - template - struct method_traits; - - template - struct method_traits { - using result_type = typename dispatch_policy_helper_impl::result_type; - using args_tuple_type = std::tuple; - - }; - } - /** * Basic DispatchPolicy that will try providers sequentially until one succeeds * without throwing an exception. that result becomes the result of the method */ template - struct first_success_policy { - using result_type = typename impl::method_traits::result_type; - std::string err; + struct first_success_policy; + + template + struct first_success_policy { + using result_type = Ret; /** * Iterate through the providers, calling (dereferencing) each @@ -48,7 +33,8 @@ namespace appbase { * @return */ template - result_type operator()(InputIterator first, InputIterator last) { + Ret operator()(InputIterator first, InputIterator last) { + std::string err; while (first != last) { try { return *first; // de-referencing the iterator causes the provider to run @@ -67,6 +53,136 @@ namespace appbase { } }; + template + struct first_success_policy { + using result_type = void; + + /** + * Iterate through the providers, calling (dereferencing) each + * if the provider throws, then store then try the next provider + * if none succeed throw an error with the aggregated error descriptions + * + * @tparam InputIterator + * @param first + * @param last + * @return + */ + template + void operator()(InputIterator first, InputIterator last) { + std::string err; + + while (first != last) { + try { + *first; // de-referencing the iterator causes the provider to run + } catch (...) { + if (!err.empty()) { + err += "\",\""; + } + + err += boost::current_exception_diagnostic_information(); + } + + ++first; + } + + throw std::length_error(std::string("No Result Available, All providers returned exceptions[") + err + "]"); + } + }; + + + /** + * Basic DispatchPolicy that will only call the first provider throwing or returning that providers results + */ + template + struct first_provider_policy; + + template + struct first_provider_policy { + using result_type = Ret; + + /** + * Call the first provider as ordered by registered priority, return its result + * throw its exceptions + * + * @tparam InputIterator + * @param first + * @param + * @return + */ + template + Ret operator()(InputIterator first, InputIterator) { + return *first; + } + }; + + template + struct first_provider_policy { + using result_type = void; + + /** + * Call the first provider as ordered by registered priority, return its result + * throw its exceptions + * + * @tparam InputIterator + * @param first + * @param + * @return + */ + template + void operator()(InputIterator first, InputIterator) { + *first; + } + }; + + namespace impl { + template + class method_caller; + + template + class method_caller { + public: + using signal_type = boost::signals2::signal; + using result_type = Ret; + + method_caller() + {} + + /** + * call operator from boost::signals2 + * + * @throws exception depending on the DispatchPolicy + */ + Ret operator()(Args&&... args) + { + return _signal(std::forward(args)...); + } + + signal_type _signal; + }; + + template + class method_caller { + public: + using signal_type = boost::signals2::signal; + using result_type = void; + + method_caller() + {} + + /** + * call operator from boost::signals2 + * + * @throws exception depending on the DispatchPolicy + */ + void operator()(Args&&... args) + { + _signal(std::forward(args)...); + } + + signal_type _signal; + }; + } + /** * A method is a loosely linked application level function. * Callers can grab a method and call it @@ -78,12 +194,8 @@ namespace appbase { * @tparam DispatchPolicy - the policy for dispatching this method */ template - class method final { + class method final : public impl::method_caller { public: - using traits = impl::method_traits; - using args_tuple_type = typename traits::args_tuple_type; - using result_type = typename traits::result_type; - /** * Type that represents a registered provider for a method allowing * for ownership via RAII and also explicit unregistered actions @@ -139,18 +251,7 @@ namespace appbase { */ template handle register_provider(T provider, int priority = 0) { - return handle(_signal.connect(priority, provider)); - } - - /** - * inhereted call operator from boost::signals2 - * - * @throws exception depending on the DispatchPolicy - */ - template - auto operator()(Args&&... args) -> typename std::enable_if_t, args_tuple_type>::value, result_type> - { - return _signal(std::forward(args)...); + return handle(this->_signal.connect(priority, provider)); } protected: @@ -186,8 +287,6 @@ namespace appbase { return erased_method_ptr(new method(), &deleter); } - boost::signals2::signal _signal; - friend class appbase::application; }; @@ -198,14 +297,14 @@ namespace appbase { * @tparam FunctionSig - the signature of the method * @tparam DispatchPolicy - dispatch policy that dictates how providers for a method are accessed defaults to @ref first_success_policy */ - template< typename Tag, typename FunctionSig, typename DispatchPolicy = first_success_policy> + template< typename Tag, typename FunctionSig, template class DispatchPolicy = first_success_policy> struct method_decl { - using method_type = method; + using method_type = method>; using tag_type = Tag; }; - template - std::true_type is_method_decl_impl(const method_decl*); + template class DispatchPolicy> + std::true_type is_method_decl_impl(const method_decl*); std::false_type is_method_decl_impl(...); From 2a923b0ff5fc4a74bd103a63a38859a007616605 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Mon, 21 May 2018 16:01:08 -0400 Subject: [PATCH 33/34] Added SIGUSR1 for bad_alloc --- application.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/application.cpp b/application.cpp index e47378c52..15500469c 100644 --- a/application.cpp +++ b/application.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -210,6 +211,7 @@ void application::quit() { } void application::exec() { + bool was_bad_alloc = false; std::shared_ptr sigint_set(new boost::asio::signal_set(*io_serv, SIGINT)); sigint_set->async_wait([sigint_set,this](const boost::system::error_code& err, int num) { quit(); @@ -222,9 +224,18 @@ void application::exec() { sigterm_set->cancel(); }); + std::shared_ptr sigfail_set(new boost::asio::signal_set(*io_serv, SIGUSR1)); + sigfail_set->async_wait([sigfail_set,this,&was_bad_alloc](const boost::system::error_code& err, int num) { + quit(); + sigfail_set->cancel(); + was_bad_alloc = true; + }); + io_serv->run(); shutdown(); /// perform synchronous shutdown + if (was_bad_alloc) + throw boost::interprocess::bad_alloc(); } void application::write_default_config(const bfs::path& cfg_file) { From 70e23f7ebbdccb64f9ac11ade9fa41ba78b31b5e Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Mon, 21 May 2018 16:27:32 -0400 Subject: [PATCH 34/34] Small change --- application.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/application.cpp b/application.cpp index 15500469c..a32d85047 100644 --- a/application.cpp +++ b/application.cpp @@ -234,6 +234,7 @@ void application::exec() { io_serv->run(); shutdown(); /// perform synchronous shutdown + if (was_bad_alloc) throw boost::interprocess::bad_alloc(); }