pax_global_header00006660000000000000000000000064152306521010014505gustar00rootroot0000000000000052 comment=cb3f5b2dc4411e2687b1519eb5e814de246bcb13 sobjectizer-5.8.6/000077500000000000000000000000001523065210100140505ustar00rootroot00000000000000sobjectizer-5.8.6/.gitattributes000066400000000000000000000017441523065210100167510ustar00rootroot00000000000000* text=auto !eol svneol=native#text/plain *.gitattributes text svneol=native#text/plain # Scripts formats *.sh eol=lf svneol=LF#text/x-sh # Data formats *.pem text svneol=native#text/plain *.cer text svneol=native#text/plain *.key text svneol=native#text/plain # Code formats *.c text svneol=native#text/plain *.h text svneol=native#text/plain *.hpp text svneol=native#text/plain *.cpp text svneol=native#text/plain *.ipp text svneol=native#text/plain *.rb text svneol=native#text/plain *.cmake text svneol=native#text/plain *.cmake.in text svneol=native#text/plain CMakeLists.txt text svneol=native#text/plain Doxyfile text svneol=native#text/plain # Text formats LICENSE text svneol=native#text/plain README.md text svneol=native#text/plain THANKS text svneol=native#text/plain *.md text svneol=native#text/plain *.dox text svneol=native#text/plain sobjectizer-5.8.6/.github/000077500000000000000000000000001523065210100154105ustar00rootroot00000000000000sobjectizer-5.8.6/.github/workflows/000077500000000000000000000000001523065210100174455ustar00rootroot00000000000000sobjectizer-5.8.6/.github/workflows/github-actions-ci-5.7-dev.yml000066400000000000000000000036131523065210100245670ustar00rootroot00000000000000name: GitHub Actions CI for 5.7-dev on: push: branches: [5.7-dev] jobs: build_and_check: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: - { name: "Windows Latest MSVC", os: windows-latest, cc: "cl", cxx: "cl", generator: "NMake Makefiles" } - { name: "Ubuntu Latest GCC", os: ubuntu-latest, cc: "gcc", cxx: "g++", generator: "Unix Makefiles" } - { name: "macOS Latest clang", os: macos-latest, cc: "clang", cxx: "clang++", generator: "Unix Makefiles" } steps: - uses: actions/checkout@v2 - uses: ilammy/msvc-dev-cmd@v1.5.0 if: startsWith(matrix.config.os, 'windows') - name: Configure shell: bash run: | mkdir cmake_build cd cmake_build cmake \ -DBUILD_TESTS=ON \ -DBUILD_EXAMPLES=OFF \ -DCMAKE_INSTALL_PREFIX=target \ -DCMAKE_BUILD_TYPE=Release \ -G "${{ matrix.config.generator }}" \ ../dev/ - name: Build shell: bash run: | cd cmake_build cmake --build . --config Release - name: Install shell: bash run: | cd cmake_build cmake --build . --config Release --target install - name: Test-On-Windows if: startsWith(matrix.config.os, 'windows') shell: bash run: | cd cmake_build ./run_tests.bat target Release - name: Test-On-Unixes if: ${{ !startsWith(matrix.config.os, 'windows') }} shell: bash run: | cd cmake_build ctest -C Release --output-on-failure sobjectizer-5.8.6/.github/workflows/github-actions-ci-5.8-dev.yml000066400000000000000000000036131523065210100245700ustar00rootroot00000000000000name: GitHub Actions CI for 5.8-dev on: push: branches: [5.8-dev] jobs: build_and_check: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: - { name: "Windows Latest MSVC", os: windows-latest, cc: "cl", cxx: "cl", generator: "NMake Makefiles" } - { name: "Ubuntu Latest GCC", os: ubuntu-latest, cc: "gcc", cxx: "g++", generator: "Unix Makefiles" } - { name: "macOS Latest clang", os: macos-latest, cc: "clang", cxx: "clang++", generator: "Unix Makefiles" } steps: - uses: actions/checkout@v4 - uses: ilammy/msvc-dev-cmd@v1.5.0 if: startsWith(matrix.config.os, 'windows') - name: Configure shell: bash run: | mkdir cmake_build cd cmake_build cmake \ -DBUILD_TESTS=ON \ -DBUILD_EXAMPLES=OFF \ -DCMAKE_INSTALL_PREFIX=target \ -DCMAKE_BUILD_TYPE=Release \ -G "${{ matrix.config.generator }}" \ ../dev/ - name: Build shell: bash run: | cd cmake_build cmake --build . --config Release - name: Install shell: bash run: | cd cmake_build cmake --build . --config Release --target install - name: Test-On-Windows if: startsWith(matrix.config.os, 'windows') shell: bash run: | cd cmake_build ./run_tests.bat target Release - name: Test-On-Unixes if: ${{ !startsWith(matrix.config.os, 'windows') }} shell: bash run: | cd cmake_build ctest -C Release --output-on-failure sobjectizer-5.8.6/.gitignore000066400000000000000000000001771523065210100160450ustar00rootroot00000000000000.*.swp /dev/local-build.rb /dev/target /dev/__*.vim /dev/__*.cpp /dev/__*.rb /doxygen/doxygen.log /dev/doc/* /dev/cmake_build* sobjectizer-5.8.6/.hgignore000066400000000000000000000002721523065210100156540ustar00rootroot00000000000000syntax: glob .externals *.swp *.core dev/target dev/zbuild dev/__*.vim dev/local-build.rb dev/doc doxygen/doxygen.log dev/*.log o/* cmake_build/* *.sublime-project *.sublime-workspace sobjectizer-5.8.6/.hgtags000066400000000000000000000004641523065210100153320ustar00rootroot000000000000008ca41fea05d49734d832e7f94cd44b9a6dfc8cbb v.5.6.0-b1 fda9ad7963496fb0ec793476c9ab0976fcacb685 v.5.6.0-b2 d379698679e82fec64e6c4d70777e04f56a54a7e v.5.6.0-rc1 acd967c2e7743e9121f283510780fe8ed135a59e v.5.6.0 70e61911190f33a02d5d9ca1351e4a018a7d35db v.5.6.0.1 62fcc3fc0b98f4f35c0ab251e32abc11b880916f v.5.6.0.2 sobjectizer-5.8.6/LICENSE000066400000000000000000000136601523065210100150630ustar00rootroot00000000000000SObjectizer license: /* Copyright (c) 2002-2013, JSC Intervale Copyright (c) 2013-2026, The SObjectizer Project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ SObjectizer subcomponents: The SObjectizer includes a number of subcomponents with separate copyright notices and license terms. Your use of the source code for the these subcomponents is subject to the terms and conditions of the following licenses. For utest_helper_1 component: /* Copyright (c) 2010-2013, JSC Intervale Copyright (c) 2013-2015, The SObjectizer Project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ For various_helpers_1 component: /* Copyright (c) 2013-2016, The SObjectizer Project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ For timertt component: /* Copyright (c) 2014-2022, The SObjectizer Project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ sobjectizer-5.8.6/README.md000066400000000000000000000633701523065210100153400ustar00rootroot00000000000000* [What is SObjectizer?](#what-is-sobjectizer) * [What distinguishes SObjectizer?](#what-distinguishes-sobjectizer) * [SObjectizer is not like TBB, taskflow or HPX](#sobjectizer-is-not-like-tbb-taskflow-or-hpx) * [Show me the code!](#show-me-the-code) * [HelloWorld example](#helloworld-example) * [Ping-Pong example](#ping-pong-example) * [Pub/Sub example](#pubsub-example) * [BlinkingLed example](#blinkingled-example) * [CSP-like Ping-Pong example](#csp-like-ping-pong-example) * [Another CSP-example with Golang's like select() statement](#another-csp-example-with-golangs-like-select-statement) * [Want to know more?](#want-to-know-more) * [There are more useful stuff in a companion project so5extra](#there-are-more-useful-stuff-in-a-companion-project-so5extra) * [Limitations](#limitations) * [Obtaining and building](#obtaining-and-building) * [SObjectizer-5.8 requires Cpp-17!](#sobjectizer-58-requires-c17) * [Building via CMake](#building-via-cmake) * [Building for Android](#building-for-android) * [Building with Android NDK](#building-with-android-ndk) * [Building with CrystaX NDK](#building-with-crystax-ndk) * [Using C Dependency Managers](#using-c-dependency-managers) * [Using via vcpkg](#using-via-vcpkg) * [Using via Conan](#using-via-conan) * [Installing SObjectizer And Adding It To conanfile.txt](#installing-sobjectizer-and-adding-it-to-conanfiletxt) * [Adding SObjectizer To Your CMakeLists.txt](#adding-sobjectizer-to-your-cmakeliststxt) * [License](#license) Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc.go) # What is SObjectizer? SObjectizer is one of a few cross-platform and OpenSource "actor frameworks" for C++. But SObjectizer supports not only Actor Model, but also Publish-Subscribe Model and CSP-like channels. The goal of SObjectizer is significant simplification of development of concurrent and multithreaded applications in C++. SObjectizer allows the creation of a concurrent app as a set of agent-objects which interact with each other through asynchronous messages. It handles message dispatching and provides a working context for message processing. And allows to tune those things by supplying various ready-to-use dispatchers. # What distinguishes SObjectizer? **Maturity**. SObjectizer is based on ideas that have been put forward in 1995-2000. And SObjectizer itself is being developed since 2002. SObjectizer-5 is continuously evolved since 2010. **Stability**. From the very beginning SObjectizer was used for business-critical applications, and some of them are still being used in production. Breaking changes in SObjectizer are rare and we approach to them very carefully. **Cross-platform**. SObjectizer runs on Windows, Linux, FreeBSD, macOS and Android. **Easy-to-use**. SObjectizer provides easy to understand and easy to use API with a lot of examples in the SObjectizer's distributive and a plenty of information in the project's Wiki. **Free**. SObjectizer is distributed under BSD-3-CLAUSE license, so it can be used in development of proprietary commercial software for free. ## SObjectizer is not like TBB, taskflow or HPX SObjectizer is often compared with tools like Intel Threading Building Blocks, taskflow, HPX, and similar to them. Such comparison is just useless. All those tools are intended to be used for solving tasks from Parallel Computing area: they allow to reduce the computational time by utilizing several CPU cores. For example, you can reencode your video file from one format to another within one hour on one CPU core, by it takes only 15 minutes on four cores. That is the main goal of Parallel Computing. SObjectizer is intended for a slightly different area: Concurrent Computing. The main goal of SObjectizer is the simplification of doing many different tasks at once. Sometimes there is no need to use more than just one CPU core for that. But if there are several CPU cores, then SObjectizer makes the handling of those tasks and the interaction between them much easier. The tricky part is the fact that Parallel- and Concurrent Computing use the same concurrency mechanisms and primitives (like threads, mutexes, atomics, and so on) under the hood. But from the high-level point of view Parallel- and Concurrent Computing are used for very different tasks. As examples of applications that were or could be implemented on top of SObjectizer, we can list multithreaded proxy-server, automatic control system, MQ-broker, database server, and so on. # Show me the code! ## HelloWorld example This is a classical example "Hello, World" expressed by using SObjectizer's agents: ```cpp #include class hello_actor final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { std::cout << "Hello, World!" << std::endl; // Finish work of example. so_deregister_agent_coop_normally(); } }; int main() { // Launch SObjectizer. so_5::launch([](so_5::environment_t & env) { // Add a hello_actor instance in a new cooperation. env.register_agent_as_coop( env.make_agent() ); }); return 0; } ``` ## Ping-Pong example Let's look at more interesting example with two agents and message exchange between them. It is another famous example for actor frameworks, "Ping-Pong": ```cpp #include struct ping { int counter_; }; struct pong { int counter_; }; class pinger final : public so_5::agent_t { so_5::mbox_t ponger_; void on_pong(mhood_t cmd) { if(cmd->counter_ > 0) so_5::send(ponger_, cmd->counter_ - 1); else so_deregister_agent_coop_normally(); } public: pinger(context_t ctx) : so_5::agent_t{std::move(ctx)} {} void set_ponger(const so_5::mbox_t mbox) { ponger_ = mbox; } void so_define_agent() override { so_subscribe_self().event( &pinger::on_pong ); } void so_evt_start() override { so_5::send(ponger_, 1000); } }; class ponger final : public so_5::agent_t { const so_5::mbox_t pinger_; int pings_received_{}; public: ponger(context_t ctx, so_5::mbox_t pinger) : so_5::agent_t{std::move(ctx)} , pinger_{std::move(pinger)} {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t cmd) { ++pings_received_; so_5::send(pinger_, cmd->counter_); }); } void so_evt_finish() override { std::cout << "pings received: " << pings_received_ << std::endl; } }; int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop([](so_5::coop_t & coop) { auto pinger_actor = coop.make_agent(); auto ponger_actor = coop.make_agent( pinger_actor->so_direct_mbox()); pinger_actor->set_ponger(ponger_actor->so_direct_mbox()); }); }); return 0; } ``` All agents in the code above are working on the same work thread. How to bind them to different work threads? It is very simple. Just use an appropriate dispatcher: ```cpp int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop( so_5::disp::active_obj::make_dispatcher(env).binder(), [](so_5::coop_t & coop) { auto pinger_actor = coop.make_agent(); auto ponger_actor = coop.make_agent( pinger_actor->so_direct_mbox()); pinger_actor->set_ponger(ponger_actor->so_direct_mbox()); }); }); return 0; } ``` ## Pub/Sub example SObjectizer supports Pub/Sub model via multi-producer/multi-consumer message boxes. A message sent to that message box will be received by all subscribers of that message type: ```cpp #include using namespace std::literals; struct acquired_value { std::chrono::steady_clock::time_point acquired_at_; int value_; }; class producer final : public so_5::agent_t { const so_5::mbox_t board_; so_5::timer_id_t timer_; int counter_{}; struct acquisition_time final : public so_5::signal_t {}; void on_timer(mhood_t) { // Publish the next value for all consumers. so_5::send( board_, std::chrono::steady_clock::now(), ++counter_); } public: producer(context_t ctx, so_5::mbox_t board) : so_5::agent_t{std::move(ctx)} , board_{std::move(board)} {} void so_define_agent() override { so_subscribe_self().event(&producer::on_timer); } void so_evt_start() override { // Agent will periodically recive acquisition_time signal // without initial delay and with period of 750ms. timer_ = so_5::send_periodic(*this, 0ms, 750ms); } }; class consumer final : public so_5::agent_t { const so_5::mbox_t board_; const std::string name_; void on_value(mhood_t cmd) { std::cout << name_ << ": " << cmd->value_ << std::endl; } public: consumer(context_t ctx, so_5::mbox_t board, std::string name) : so_5::agent_t{std::move(ctx)} , board_{std::move(board)} , name_{std::move(name)} {} void so_define_agent() override { so_subscribe(board_).event(&consumer::on_value); } }; int main() { so_5::launch([](so_5::environment_t & env) { auto board = env.create_mbox(); env.introduce_coop([board](so_5::coop_t & coop) { coop.make_agent(board); coop.make_agent(board, "first"s); coop.make_agent(board, "second"s); }); std::this_thread::sleep_for(std::chrono::seconds(4)); env.stop(); }); return 0; } ``` ## BlinkingLed example All agents in SObjectizer are finite-state machines. Almost all functionality of hierarchical finite-states machines (HSM) are supported: child states and handlers inheritance, on_enter/on_exit handlers, state timeouts, deep- and shallow state history, except orthogonal states. Let's see how an agent that implements the following statechart can look like: ![Blinking Led Statechart](dev/sample/so_5/blinking_led/statechart_horizontal.png) This is a very simple example that demonstrates an agent for the statechart shown above: ```cpp #include using namespace std::literals; class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off : public so_5::signal_t {}; blinking_led(context_t ctx) : so_5::agent_t{std::move(ctx)} { this >>= off; off.just_switch_to(blinking); blinking.just_switch_to(off); blink_on .on_enter([]{ std::cout << "ON" << std::endl; }) .on_exit([]{ std::cout << "off" << std::endl; }) .time_limit(1250ms, blink_off); blink_off .time_limit(750ms, blink_on); } }; int main() { so_5::launch([](so_5::environment_t & env) { so_5::mbox_t m; env.introduce_coop([&](so_5::coop_t & coop) { auto led = coop.make_agent< blinking_led >(); m = led->so_direct_mbox(); }); const auto pause = [](auto duration) { std::this_thread::sleep_for(duration); }; std::cout << "Turn blinking on for 10s" << std::endl; so_5::send(m); pause(10s); std::cout << "Turn blinking off for 5s" << std::endl; so_5::send(m); pause(5s); std::cout << "Turn blinking on for 5s" << std::endl; so_5::send(m); pause(5s); std::cout << "Stopping..." << std::endl; env.stop(); } ); return 0; } ``` ## CSP-like Ping-Pong example SObjectizer allows to write concurrent applications even without agents inside. Only plain threads and CSP-like channels can be used. This is plain-thread implementation of Ping-Pong example (please note that main() is not exception-safe): ```cpp #include struct ping { int counter_; }; struct pong { int counter_; }; void pinger_proc(so_5::mchain_t self_ch, so_5::mchain_t ping_ch) { so_5::send(ping_ch, 1000); // Read all message until channel will be closed. so_5::receive( so_5::from(self_ch).handle_all(), [&](so_5::mhood_t cmd) { if(cmd->counter_ > 0) so_5::send(ping_ch, cmd->counter_ - 1); else { // Channels have to be closed to break `receive` calls. so_5::close_drop_content(so_5::exceptions_enabled, self_ch); so_5::close_drop_content(so_5::exceptions_enabled, ping_ch); } }); } void ponger_proc(so_5::mchain_t self_ch, so_5::mchain_t pong_ch) { int pings_received{}; // Read all message until channel will be closed. so_5::receive( so_5::from(self_ch).handle_all(), [&](so_5::mhood_t cmd) { ++pings_received; so_5::send(pong_ch, cmd->counter_); }); std::cout << "pings received: " << pings_received << std::endl; } int main() { so_5::wrapped_env_t sobj; auto pinger_ch = so_5::create_mchain(sobj); auto ponger_ch = so_5::create_mchain(sobj); std::thread pinger{pinger_proc, pinger_ch, ponger_ch}; std::thread ponger{ponger_proc, ponger_ch, pinger_ch}; ponger.join(); pinger.join(); return 0; } ``` ## Another CSP-example with Golang's like select() statement SObjectizer provides a select() function that is similar to Golang's select statement. This function allows waiting for incoming messages from several message chains. It also allows to wait for the readiness of message chains for accepting a new outgoing message. So select() allows to do non-blocking send() calls with the handling of incoming messages while the target message chain is full. There is a Fibonacci calculation example that uses select() as the back-pressure mechanism (number producer thread will wait if number reader thread doesn't read the previous number yet). Note also that main() function in this example is exception-safe. ```cpp #include #include using namespace std; using namespace std::chrono_literals; using namespace so_5; struct quit {}; void fibonacci( mchain_t values_ch, mchain_t quit_ch ) { int x = 0, y = 1; mchain_select_result_t r; do { r = select( from_all().handle_n(1), // Sends a new message of type 'int' with value 'x' inside // when values_ch is ready for a new outgoing message. send_case( values_ch, message_holder_t::make(x), [&x, &y] { // This block of code will be called after the send(). auto old_x = x; x = y; y = old_x + y; } ), // Receive a 'quit' message from quit_ch if it is here. receive_case( quit_ch, [](quit){} ) ); } // Continue the loop while we send something and receive nothing. while( r.was_sent() && !r.was_handled() ); } int main() { wrapped_env_t sobj; thread fibonacci_thr; auto thr_joiner = auto_join( fibonacci_thr ); // The chain for Fibonacci number will have limited capacity. auto values_ch = create_mchain( sobj, 1s, 1, mchain_props::memory_usage_t::preallocated, mchain_props::overflow_reaction_t::abort_app ); auto quit_ch = create_mchain( sobj ); auto ch_closer = auto_close_drop_content( values_ch, quit_ch ); fibonacci_thr = thread{ fibonacci, values_ch, quit_ch }; // Read the first 10 numbers from values_ch. receive( from( values_ch ).handle_n( 10 ), // And show every number to the standard output. []( int v ) { cout << v << endl; } ); send< quit >( quit_ch ); } ``` ## Want to know more? More information about SObjectizer can be found in the corresponding section of the [project's Wiki](https://github.com/Stiffstream/sobjectizer/wiki). # There are more useful stuff in a companion project so5extra There is a separate companion project [so5extra](https://github.com/Stiffstream/so5extra) that contains a lot of various useful things like Asio's based dispatchers, additional types of mboxes, revocable timer, synchronous requests, and more. For example, there is how synchronous interaction looks like (by using `so_5::extra::sync` stuff): ```cpp #include #include // Short alias for convenience. namespace sync_ns = so_5::extra::sync; using namespace std::chrono_literals; // The type of service provider. class service_provider_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( []( sync_ns::request_mhood_t cmd ) { // Transform the incoming value, convert the result // to string and send the resulting string back. cmd->make_reply( std::to_string(cmd->request() * 2) ); } ); } }; // The type of service consumer. class consumer_t final : public so_5::agent_t { // Message box of the service provider. const so_5::mbox_t m_service; public : consumer_t( context_t ctx, so_5::mbox_t service ) : so_5::agent_t{ std::move(ctx) } , m_service{ std::move(service) } {} void so_evt_start() override { // Issue a request and wait for the result no more than 500ms. auto result = sync_ns::request_reply( // The destination for the request. m_service, // Max waiting time. 500ms, // Request's value. 4 ); std::cout << "The result: " << result << std::endl; so_deregister_agent_coop_normally(); } }; int main() { so_5::launch( [](so_5::environment_t & env) { env.introduce_coop( // Every agent should work on its own thread. so_5::disp::active_obj::make_dispatcher( env ).binder(), [](so_5::coop_t & coop) { auto service_mbox = coop.make_agent< service_provider_t >() ->so_direct_mbox(); coop.make_agent< consumer_t >( service_mbox ); } ); } ); } ``` SObjectizer itself is intended to be a relatively small project without external dependencies. so5extra has no this constraint. That is why Asio's based dispatchers and environment infrastructures are implemented in so5extra, not in the SObjectizer. Another significant property of SObjectizer is stability. We're trying to keep SObjectizer as stable as possible, but there is a need to try some new features, even if we don't know yet how successful and demanded they will be. so5extra is a good place to experiment with new features, some of them could be moved to the SObjectizer with time. So if you don't find a helpful feature in the SObjectizer, let's try to look at [so5extra](https://github.com/Stiffstream/so5extra). Maybe it is already there. # Limitations SObjectizer is an in-process message dispatching framework. It doesn't support distributed applications just out of box. But external tools and libraries can be used in that case. Please take a look at our mosquitto_transport experiment: https://github.com/Stiffstream/mosquitto_transport # Obtaining and building SObjectizer can be checked out from [GitHub](https://github.com/stiffstream/sobjectizer). Archives with SObjectizer's source code can be downloaded from [GitHub](https://github.com/Stiffstream/sobjectizer/releases) or from [SourceForge](https://sourceforge.net/projects/sobjectizer/files/sobjectizer). Since v.5.8.6 there is just one way of building SObjectizer -- by using [CMake](https://cmake.org). NOTE. Since v.5.5.15.2 there is a support of Android platform. Building for Android is possible by CMake only. See the corresponding section below. SObjectizer can also be installed and used via **vcpkg** and **Conan** dependency managers. See the appropriate sections below. To build html-format documentation for SObjectizer the Doxygen tool is necessary. If it is installed then: ```sh git clone https://github.com/stiffstream/sobjectizer sobjectizer cd sobjectizer/doxygen doxygen ``` Generated html-files will be located in sobjectizer/dev/doc/html. ## SObjectizer-5.8 requires C++17! The 5.8-branch of SObjectizer requires C++17. If you need support for C++14 or C++11 try to look to older versions of SObjectizer on [SourceForge](https://sourceforge.net/projects/sobjectizer). Or contact [stiffstream](https://stiffstream.com/en/services.html) to discuss porting of SObjectizer-5.8 to older C++ standards. ## Building via CMake To build SObjectizer via CMake it is necessary to have CMake and some knowledge of how to use it. The following action is just a demonstration. For more detailed info about cmake build system for SObjectizer see dev/cmake/CmakeQuickHowto.txt To get and build SObjectizer under Linux/FreeBSD in command line run: ```sh git clone https://github.com/stiffstream/sobjectizer cd sobjectizer mkdir cmake_build cd cmake_build cmake -DCMAKE_INSTALL_PREFIX=target -DCMAKE_BUILD_TYPE=Release ../dev cmake --build . --config Release cmake --build . --config Release --target install ``` Those commands will create all necessary Makefile, then build SObjectizer. If it necessary to build examples and tests too, use ```sh cmake -DBUILD_ALL=ON -DCMAKE_INSTALL_PREFIX=target ../dev ``` When 'make install' finished './target' will contain two subfolders './bin' with samples and './lib' with shared libso.5.x.x.so CMake build system currently supports this options: * `SOBJECTIZER_BUILD_STATIC`. Enable building SObjectizer as a static library [default: ON] * `SOBJECTIZER_BUILD_SHARED`. Enable building SObjectizer as a shared library [default: ON] * `BUILD_ALL`. Enable building examples and tests [default: OFF] * `BUILD_EXAMPLES`. Enable building examples [default: OFF] * `BUILD_TESTS`. Enable building tests [default: OFF] * `SOBJECTIZER_SANITIZE`. Enable a specific sanitizer. By default this option is set to `OFF` (no sanitizers are used). Supported values: `thread` for ThreadSanitizer and `address` for AddressSanitizer. Note that those sanitizers are supported form GCC and clang only. Please note that if `BUILD_ALL` or `BUILD_EXAMPLES` or `BUILD_TESTS` is turned ON then both `SOBJECTIZER_BUILD_STATIC` and `SOBJECTIZER_BUILD_SHARED` must be turned ON. It means that if `SOBJECTIZER_BUILD_STATIC` or `SOBJECTIZER_BUILD_SHARED` is turned OFF then `BUILD_ALL`/`BUILD_EXAMPLES`/`BUILD_TESTS` all must be turned OFF. To build SObjectizer under Windows by MS Visual Studio 2013 from command line: ```sh git clone https://github.com/stiffstream/sobjectizer cd sobjectizer mkdir cmake_build cd cmake_build cmake -DCMAKE_INSTALL_PREFIX=target -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 15 2017" ../dev cmake --build . --config Release cmake --build . --config Release --target install ``` If it necessary to build examples too, use `BUILD_ALL` in cmake invocation: ```sh cmake -DCMAKE_INSTALL_PREFIX=target -DCMAKE_BUILD_TYPE=Release -DBUILD_ALL=ON -G "Visual Studio 15 2017" ../dev ``` Since v.5.5.24 SObjectizer provides sobjectizer-config.cmake files. These files are automatically installed into `/lib/cmake/sobjectizer` subfolder. It allows to use SObjectizer via CMake's find_package command. ## Building for Android Building for Android is possible via a rather fresh Android NDK or [CrystaX NDK](https://www.crystax.net/android/ndk). ### Building with Android NDK You need Android SDK and Android NDK installed in your system. As well as an appropriate version of CMake. You have also need properly set environment variables `ANDROID_HOME`, `ANDROID_NDK`. Then you can issue the following commands: ```sh git clone https://github.com/stiffstream/sobjectizer cd sobjectizer mkdir cmake_build cd cmake_build cmake -DBUILD_ALL -DCMAKE_INSTALL_PREFIX=target -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake \ -G Ninja \ -DANDROID_ABI=arm64-v8a \ -DANDROID_NDK=${ANDROID_NDK} \ -DANDROID_NATIVE_API_LEVEL=23 \ -DANDROID_TOOLCHAIN=clang \ ../dev cmake --build . --config=Release cmake --build . --config=Release --target install ``` ### Building with CrystaX NDK You need CrystaX NDK v.10.4.0 or higher already installed in your system. CMake is used for building SObjectizer: ```sh git clone https://github.com/stiffstream/sobjectizer cd sobjectizer mkdir cmake_build cd cmake_build export NDK=/path/to/the/crystax-ndk cmake -DBUILD_ALL -DCMAKE_INSTALL_PREFIX=result -DCMAKE_TOOLCHAIN_FILE=$NDK/cmake/toolchain.cmake -DANDROID_ABI=arm64-v8a ../dev make make test make install ``` ## Using C++ Dependency Managers ### Using via vcpkg To use SObjectizer via [vcpkg](https://github.com/Microsoft/vcpkg) it is necessary to do the following steps. Install `sobjectizer` package: ```sh vcpkg install sobjectizer ``` Add the following lines into your CMakeLists.txt file: ```cmake find_package(sobjectizer CONFIG REQUIRED) target_link_libraries(your_target sobjectizer::SharedLib) # or sobjectizer::StaticLib ``` ### Using via Conan **NOTE.** Since Feb 2021 new versions of SObjectizer are available via [conan-center](https://conan.io/center/) only. #### Installing SObjectizer And Adding It To conanfile.txt To use SObjectizer via Conan it is necessary to add SObjectizer to `conanfile.txt` of your project: ``` [requires] sobjectizer/5.8.0 ``` It also may be necessary to specify `shared` option for SObjectizer. For example, for build SObjectizer as a static library: ``` [options] sobjectizer:shared=False ``` Install dependencies for your project: ``` conan install SOME_PATH --build=missing ``` #### Adding SObjectizer To Your CMakeLists.txt ```cmake ... include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup() ... target_link_libraries(your_target ${CONAN_LIBS}) ``` # License SObjectizer is distributed under 3-clause BSD license. For license information please see LICENSE file. sobjectizer-5.8.6/THANKS000066400000000000000000000004061523065210100147630ustar00rootroot00000000000000Alex Syrnikov (san [at] masterspline.eu) Andrei Uhlik (uglika [at] gmail.com) Dmitry Moskalchuk (dm [at] crystax.net) Pavel Vainerman (pv [at] etersoft.ru) Pavel Begunkov Silence (asml.silence [at] gmail.com) Andrey Alekseenko (andrei.alekseenko [at] gmail.com) sobjectizer-5.8.6/android/000077500000000000000000000000001523065210100154705ustar00rootroot00000000000000sobjectizer-5.8.6/android/.gitignore000066400000000000000000000000061523065210100174540ustar00rootroot00000000000000/out/ sobjectizer-5.8.6/android/GNUmakefile000066400000000000000000000057771523065210100175620ustar00rootroot00000000000000override MYDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) override TOPDIR := $(abspath $(MYDIR)/..) ABI ?= armeabi-v7a armeabi-v7a-hard x86 arm64-v8a x86_64 TOOLCHAIN_VERSION ?= gcc5 #======================================================================================== __comma := , __empty := __space := $(__empty) $(__empty) define commas-to-spaces $(subst $(__comma),$(__space),$(1)) endef define spaces-to-commas $(subst $(__space),$(__comma),$(1)) endef # $1: ABI define builddir $(or $(strip $(OUTDIR)),$(MYDIR)/out/$(1))/build endef # $1: ABI define installdir $(or $(strip $(OUTDIR)),$(MYDIR)/out/$(1))/install endef define abis $(call commas-to-spaces,$(ABI)) endef define downcase $(strip \ $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,\ $(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,\ $(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,\ $(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1)))))))))))))))))))))))))))) endef define is-true $(if $(filter 1 yes true on,$(call downcase,$(1))),yes) endef define is-verbose $(or $(call is-true,$(VERBOSE)),$(call is-true,$(V))) endef #======================================================================================== .PHONY: all all: build .PHONY: rebuild rebuild: @$(MAKE) --no-print-directory clean @$(MAKE) --no-print-directory build .PHONY: configure configure: $(addprefix configure-,$(call abis)) .PHONY: build build: $(addprefix build-,$(call abis)) .PHONY: test test: $(addprefix test-,$(call abis)) .PHONY: install install: $(addprefix install-,$(call abis)) .PHONY: clean clean: $(addprefix clean-,$(call abis)) define add-build-rule .PHONY: configure-$(1) configure-$(1): | $$(call builddir,$(1)) $$(if $$(strip $$(wildcard $$(NDK))),\ $$(eval override NDK := $$(wildcard $$(NDK))),\ $$(error NDK is not specified)\ ) $$(strip cd $$(call builddir,$(1)) && \ cmake \ -DBUILD_ALL=ON \ -DCMAKE_INSTALL_PREFIX=$$(call installdir,$(1)) \ -DCMAKE_TOOLCHAIN_FILE=$$(NDK)/cmake/toolchain.cmake \ -DANDROID_ABI=$(1) \ -DANDROID_TOOLCHAIN_VERSION=$(TOOLCHAIN_VERSION) \ $$(TOPDIR)/dev \ ) .PHONY: build-$(1) build-$(1): configure-$(1) $$(MAKE) --no-print-directory -C $$(call builddir,$(1)) $$(if $$(is-verbose),VERBOSE=1) .PHONY: test-$(1) test-$(1): $$(MAKE) --no-print-directory -C $$(call builddir,$(1)) test ARGS="$$(strip \ $$(if $$(is-verbose),-V) \ $$(foreach __t,$$(call commas-to-spaces,$$(TEST) $$(TESTS)),-R $$(__t)) \ )" .PHONY: install-$(1) install-$(1): build-$(1) $$(MAKE) --no-print-directory -C $$(call builddir,$(1)) install .PHONY: clean-$(1) clean-$(1): $$(if $$(wildcard $$(call builddir,$(1))),rm -Rf $$(call builddir,$(1))) @dir=$$(dir $$(call builddir,$(1))); \ while true; do \ rmdir $$$$dir >/dev/null 2>&1 || exit 0; \ dir=`dirname $$$$dir`; \ done $$(call builddir,$(1)): mkdir -p $$@ endef $(foreach __abi,$(call abis),\ $(eval $(call add-build-rule,$(__abi)))\ ) sobjectizer-5.8.6/dev/000077500000000000000000000000001523065210100146265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/CMakeLists.txt000066400000000000000000000100071523065210100173640ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.14) project(SObjectizer LANGUAGES CXX) set(SOBJECTIZER_MASTER_PROJECT OFF) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) set(SOBJECTIZER_MASTER_PROJECT ON) endif() option(BUILD_ALL "Enable building examples and tests [default: OFF]" OFF) option(BUILD_EXAMPLES "Enable building examples [default: OFF]" OFF) option(BUILD_TESTS "Enable building tests [default: OFF]" OFF) option(SOBJECTIZER_BUILD_STATIC "Build static SObjectizer library [default: ON]" ON) option(SOBJECTIZER_BUILD_SHARED "Build shared SObjectizer library [default: ON]" ON) option(SOBJECTIZER_INSTALL "Generate install target for SObjectizer" ${SOBJECTIZER_MASTER_PROJECT}) option(SOBJECTIZER_SANITIZE "Sanitizer to use (thread|address) [default: OFF]" OFF) if((NOT SOBJECTIZER_BUILD_STATIC) AND (NOT SOBJECTIZER_BUILD_SHARED)) message(FATAL_ERROR "at least one of SOBJECTIZER_BUILD_STATIC/SOBJECTIZER_BUILD_SHARED must be defined") endif() if(NOT SOBJECTIZER_BUILD_STATIC) if(BUILD_ALL OR BUILD_EXAMPLES) message(FATAL_ERROR "BUILD_ALL/BUILD_EXAMPLES can't be used" " if SOBJECTIZER_BUILD_STATIC is OFF") endif() endif() if(NOT SOBJECTIZER_BUILD_SHARED) if(BUILD_ALL OR BUILD_EXAMPLES OR BUILD_TESTS) message(FATAL_ERROR "BUILD_ALL/BUILD_EXAMPLES/BUILD_TESTS can't be used" " if SOBJECTIZER_BUILD_SHARED is OFF") endif() endif() if (ANDROID) if (ANDROID_NDK_ABI_NAME STREQUAL "armeabi") message(FATAL_ERROR "'${ANDROID_NDK_ABI_NAME}' target is not supported due to lack of the lock-free atomics") endif() if (ANDROID_NDK_ABI_NAME STREQUAL "mips") message(FATAL_ERROR "'${ANDROID_NDK_ABI_NAME}' target is not supported due to lack of the lock-free 64-bit atomics") endif() if (NOT ANDROID_NDK_ABI_NAME MATCHES "^(armeabi-v7a|armeabi-v7a-hard|arm64-v8a|x86|x86_64|mips64)$") message(FATAL_ERROR "Unsupported ABI: '${ANDROID_NDK_ABI_NAME}'") endif() endif() set(SOBJECTIZER_SUPPORTED_SANITIZERS "OFF" "thread" "address") if(NOT (SOBJECTIZER_SANITIZE IN_LIST SOBJECTIZER_SUPPORTED_SANITIZERS)) message(FATAL_ERROR "Unsupported sanitizer: ${SOBJECTIZER_SANITIZE}") endif() set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) if(NOT CMAKE_CXX_STANDARD) message(STATUS "C++17 will be used by default") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CXX_EXTENSIONS NO) else() message(STATUS "C++ standard is set to C++${CMAKE_CXX_STANDARD}") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") add_compile_options( -Wall -Wextra -Werror -Wpedantic -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-noreturn -Wno-documentation-unknown-command -Wno-documentation-deprecated-sync -Wno-documentation -Wno-weak-vtables -Wno-missing-prototypes -Wno-missing-variable-declarations -Wno-exit-time-destructors -Wno-global-constructors ) if(SOBJECTIZER_SANITIZE STREQUAL "thread") add_compile_options(-fsanitize=thread) add_link_options(-fsanitize=thread) elseif(SOBJECTIZER_SANITIZE STREQUAL "address") add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options( -Wextra -Wpedantic -Werror -Wall -Wno-unused-function ) if(SOBJECTIZER_SANITIZE STREQUAL "thread") add_compile_options(-fsanitize=thread) add_link_options(-fsanitize=thread) elseif(SOBJECTIZER_SANITIZE STREQUAL "address") add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) endif() else() if(NOT(SOBJECTIZER_SANITIZE STREQUAL "OFF")) message(FATAL_ERROR "Sanitizers supported for Clang and GCC only") endif() endif() add_subdirectory(so_5) if(BUILD_ALL OR BUILD_TESTS) enable_testing() add_subdirectory(test/so_5) if(WIN32) configure_file(${CMAKE_SOURCE_DIR}/cmake/run_tests.bat ${CMAKE_BINARY_DIR} NEWLINE_STYLE WIN32) endif() endif() if(BUILD_ALL OR BUILD_EXAMPLES) add_subdirectory(sample/so_5) endif() sobjectizer-5.8.6/dev/cmake/000077500000000000000000000000001523065210100157065ustar00rootroot00000000000000sobjectizer-5.8.6/dev/cmake/CmakeQuickHowto.txt000066400000000000000000000116511523065210100215110ustar00rootroot00000000000000CMake Quick HowTo ================== NOTE. This way of building is not used by SObjectizer developers. The projects files for CMake were contributed by SObjectizer users and now these files are maintained by The SObjectizer Team (but these files are not used in SObjectizer development). cmake & Linux/FreeBSD ===================== # first download so sources by svn # avalable tags and branches You can see in repository # http://sourceforge.net/p/sobjectizer/repo/HEAD/tree/ # cmake build supported after version 5.5.2 svn export http://svn.code.sf.net/p/sobjectizer/repo/branches/so_5 so_5 cd so_5 mkdir cmake_build cd cmake_build cmake -DCMAKE_INSTALL_PREFIX=target -DCMAKE_BUILD_TYPE=Release ../dev cmake --build . --config Release cmake --build . --config Release --target install Those commands will create all necessary Makefile, then build SObjectizer. CMAKE_INSTALL_PREFIX - standard cmake variable, it contains path for final files base installation dir. At make install files will be copied here If it necessary to build examples and tests too, use cmake -DBUILD_ALL=ON -DCMAKE_INSTALL_PREFIX=target ../dev When 'make install' finished './target' will contain two subfolders './bin' with samples and './lib' with shared libso.5.x.x.so If You just need to build project CMAKE_INSTALL_PREFIX not needed and You can stop after 'make' command, make install not required. If You need (re)build project files and run tests, just cmake -DBUILD_TESTS=ON ../dev make make test CMAKE_INSTALL_PREFIX and make install not needed (no installation). CMake build system currently supports this custom options: SOBJECTIZER_BUILD_STATIC Enable building SObjectizer as a static library [default: ON] SOBJECTIZER_BUILD_SHARED Enable building SObjectizer as a shared library [default: ON] BUILD_ALL Enable building examples and tests [default: OFF] BUILD_EXAMPLES Enable building examples [default: OFF] BUILD_TESTS Enable building tests [default: OFF] Please note that if BUILD_ALL or BUILD_EXAMPLES or BUILD_TESTS is turned ON then both SOBJECTIZER_BUILD_STATIC and SOBJECTIZER_BUILD_SHARED must be turned ON. It means that if SOBJECTIZER_BUILD_STATIC or SOBJECTIZER_BUILD_SHARED is turned OFF then BUILD_ALL/BUILD_EXAMPLES/BUILD_TESTS all must be turned OFF. cmake & Windows ================ To build SObjectizer under Windows by MS Visual Studio 2013 from command line You need console with properly configured compiler environment variables via vcvarsall.bat (I start it from "Start->All Programms->Visuil Studio 2013->Tools") cd so-5.5.20 mkdir cmake_build cd cmake_build cmake -DCMAKE_INSTALL_PREFIX=target -G "Visual Studio 14 2015" ../dev cmake --build . --config Release cmake --build . --config Release --target install If it necessary to build examples too, use BUILD_ALL in cmake invocation: cmake -DCMAKE_INSTALL_PREFIX=target -DBUILD_ALL=ON -G "Visual Studio 14 2015" ../dev msbuild's Configuration argument usually can be one of "Debug", "Release", "MinSizeRel" or "RelWithDebInfo". The easiest way to use cmake in Windows - use it's graphical interface cmake-gui In cmake-gui window You can choose directory, where sources placed, where to create cmake build files. After this check "Advanced" checkbox, push "Configure" button, choose compilation way and press "Finish". Few seconds later You will see cmake variables list. Find "CMAKE_INSTALL_PREFIX" and set it to directory, where compiled files will be placed after installation stage. Check required BUILD_ALL, BUILD_TESTS, BUILD_EXAMPLES options. Then press "Configure", wait, then press "Generate" button. After cmake project files generated You can bun build from command line. Running tests from Windows ========================== :: run once cmake -DCMAKE_INSTALL_PREFIX=installed_files -DBUILD_TESTS=ON -G "Visual Studio 14 2015" ../dev :: and after each change msbuild /m /t:Build /p:Configuration=Release INSTALL.vcxproj run_tests.bat run_tests.bat will temporary set PATH and run all tests. It is possible to run tests (it You turned it on at cmake run) after building project (without installing), but You will need to include path to so.5.x.x.dll in PATH manually. It's because msbuild can be runned for different configurations (Debug Release MinSizeRel RelWithDebInfo), so it append Configuration name to compiled files pathes, but mingw32-make will not do that. But after installation stage so.5.x.x.dll will be in known directory. Running tests without installation (set need to be configured once if You will not change Configuration=Release argument) cmake -DCMAKE_INSTALL_PREFIX=installed_files -DBUILD_TESTS=ON -G "Visual Studio 14 2015" ../dev msbuild /m /t:Build /p:Configuration=Release INSTALL.vcxproj set PATH=%PATH%;dir_with_compiled_so.5.x.x.dll ctest --force-new-ctest-process :: or msbuild /m /p:Configuration=Release RUN_TESTS.vcxproj If You certain that You know better way to use cmake crossaplatform - let us know. sobjectizer-5.8.6/dev/cmake/run_tests.bat000066400000000000000000000004671523065210100204330ustar00rootroot00000000000000@echo off setlocal if --%1 == -- (set SO5_TARGET_PATH=target) else (set SO5_TARGET_PATH=%1) if --%2 == -- (set SO5_BUILD_MODE=Release) else (set SO5_BUILD_MODE=%2) set PATH=%PATH%;%~dp0\%SO5_TARGET_PATH%\bin cmd /c ctest -C %SO5_BUILD_MODE% --force-new-ctest-process if %errorlevel% neq 0 exit /b 1 endlocal sobjectizer-5.8.6/dev/cmake/sample_as_unittest.cmake000066400000000000000000000011301523065210100226060ustar00rootroot00000000000000function(add_sample_with_shared_lib_as_test Sample) if (BUILD_ALL OR BUILD_TESTS) if (ANDROID AND DEFINED ADBRUNNER) set(SO_5_TEST_LAUNCHER ${ADBRUNNER}) list(APPEND SO_5_TEST_LAUNCHER -L$) endif() add_test(NAME ${Sample} COMMAND ${SO_5_TEST_LAUNCHER} ${Sample}) endif() endfunction() function(add_sample_with_static_lib_as_test Sample) if (BUILD_ALL OR BUILD_TESTS) if (ANDROID AND DEFINED ADBRUNNER) set(SO_5_TEST_LAUNCHER ${ADBRUNNER}) endif() add_test(NAME ${Sample} COMMAND ${SO_5_TEST_LAUNCHER} ${Sample}) endif() endfunction() sobjectizer-5.8.6/dev/cmake/unittest.cmake000066400000000000000000000020321523065210100205640ustar00rootroot00000000000000if(NOT UNITTEST) message(FATAL_ERROR "UNITTEST is not defined!") endif() if(NOT UNITTEST_SRCFILES) set(UNITTEST_SRCFILES main.cpp) endif() include(${CMAKE_SOURCE_DIR}/so_5/cmake/target.cmake) unset(SO_5_TEST_LAUNCHER) set(UNITTEST_HAS_TO_BE_RUN ON) if (ANDROID AND DEFINED ADBRUNNER) if(UNITTEST_SHOULD_FAIL) message(STATUS "Unit won't be run for Android: ${UNITTEST}") set(UNITTEST_HAS_TO_BE_RUN OFF) else() set(SO_5_TEST_LAUNCHER ${ADBRUNNER}) list(APPEND SO_5_TEST_LAUNCHER -L$) endif() else() if(UNITTEST_SHOULD_FAIL) set(SO_5_TEST_LAUNCHER ${CMAKE_COMMAND}) list(APPEND SO_5_TEST_LAUNCHER -E env) endif() endif() if(UNITTEST_HAS_TO_BE_RUN) add_executable(${UNITTEST} ${UNITTEST_SRCFILES}) target_link_libraries(${UNITTEST} ${SO_5_SHARED_LIB}) add_test(NAME ${UNITTEST} COMMAND ${SO_5_TEST_LAUNCHER} ${UNITTEST}) if(UNITTEST_SHOULD_FAIL) set_property(TEST ${UNITTEST} PROPERTY WILL_FAIL TRUE) endif() endif() unset(UNITTEST) unset(UNITTEST_SHOULD_FAIL) unset(UNITTEST_SRCFILES) sobjectizer-5.8.6/dev/sample/000077500000000000000000000000001523065210100161075ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/000077500000000000000000000000001523065210100167545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/CMakeLists.txt000066400000000000000000000047431523065210100215240ustar00rootroot00000000000000project(sample) add_subdirectory(hello_world) add_subdirectory(hello_world_simple_not_mtsafe) add_subdirectory(hello_evt_handler) add_subdirectory(hello_evt_lambda) add_subdirectory(hello_all) add_subdirectory(hello_delay) add_subdirectory(hello_periodic) add_subdirectory(chstate) add_subdirectory(chstate_msg_tracing) add_subdirectory(selective_msg_tracing) add_subdirectory(individual_msg_tracing) add_subdirectory(nohandler_msg_tracing) add_subdirectory(disp) add_subdirectory(coop_listener) add_subdirectory(exception_logger) add_subdirectory(exception_reaction) add_subdirectory(coop_notification) add_subdirectory(coop_user_resources) add_subdirectory(subscriptions) add_subdirectory(parent_coop) add_subdirectory(chameneos_simple) add_subdirectory(chameneos_prealloc_msgs) add_subdirectory(ping_pong_minimal) add_subdirectory(ping_pong) add_subdirectory(ping_pong_with_owner) add_subdirectory(hardwork_imit) add_subdirectory(many_timers) add_subdirectory(custom_error_logger) add_subdirectory(simple_message_deadline) add_subdirectory(dispatcher_hello) add_subdirectory(dispatcher_restarts) add_subdirectory(dispatcher_for_children) add_subdirectory(redirect_and_transform) add_subdirectory(queue_size_stats) add_subdirectory(delivery_filters) add_subdirectory(make_pipeline) add_subdirectory(machine_control) add_subdirectory(news_board) add_subdirectory(prio_work_stealing) add_subdirectory(mchain_handler_formats) add_subdirectory(mchain_multi_consumers) add_subdirectory(wrapped_env_demo_2) add_subdirectory(producer_consumer_mchain) add_subdirectory(blinking_led) add_subdirectory(intercom_statechart) add_subdirectory(state_deep_history) add_subdirectory(adv_thread_pool_fifo) add_subdirectory(mchain_select) add_subdirectory(mchain_fibonacci) add_subdirectory(wrapped_env_demo_3) add_subdirectory(mutable_msg_agents) add_subdirectory(modify_resend_as_immutable) add_subdirectory(two_handlers) add_subdirectory(stop_guard) add_subdirectory(deadletter_handler) add_subdirectory(make_new_direct_mbox) add_subdirectory(make_agent_ref) add_subdirectory(default_message_limits) add_subdirectory(named_dispatchers_layer) add_subdirectory(custom_work_thread_factory) add_subdirectory(custom_direct_mbox) add_subdirectory(single_sink_binding) add_subdirectory(unique_subscribers_mbox) add_subdirectory(introduce_named_mbox) add_subdirectory(child_soenv) add_subdirectory(bind_transformer) add_subdirectory(agent_name) add_subdirectory(mchain_empty_notificator) add_subdirectory(mchain_empty_notificator_2) sobjectizer-5.8.6/dev/sample/so_5/adv_thread_pool_fifo/000077500000000000000000000000001523065210100231115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/adv_thread_pool_fifo/CMakeLists.txt000066400000000000000000000005611523065210100256530ustar00rootroot00000000000000set(SAMPLE sample.so_5.adv_thread_pool_fifo) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.adv_thread_pool_fifo_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/adv_thread_pool_fifo/main.cpp000066400000000000000000000067221523065210100245500ustar00rootroot00000000000000/* * An example of FIFO types for adv_thread_pool dispatcher. * * Two agents are registered in one coop on adv_thread_pool. * The first time agents use fifo_t::cooperation. * The second time agents use fifo_t::individual. * * At first time events A.e1 and B.e2 must be invoked at the same time, * then A.e3, then B.e2 and A.e1 again at the same time. * * At second time events A.e1, B.e2 and B.e2 must be invoked at the same time, * the A.e2, and only then A.1. */ #include #include // Logging infrastructure. struct trace_msg { const so_5::agent_t * m_who; std::string m_what; std::thread::id m_thread_id; }; void create_logger( so_5::environment_t & env ) { class logger_actor final : public so_5::agent_t { public: logger_actor( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe( so_environment().create_mbox( "log" ) ) .event( [](mhood_t cmd ) { std::cout << cmd->m_thread_id << ": (" << cmd->m_who << ") " << cmd->m_what << std::endl; } ); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< logger_actor >(); } ); } void trace( const so_5::agent_t & agent, std::string what ) { so_5::send< trace_msg >( agent.so_environment().create_mbox( "log" ), &agent, std::move(what), std::this_thread::get_id() ); } // Main part of the example. struct M1 final : public so_5::signal_t {}; struct M2 final : public so_5::signal_t {}; struct M3 final : public so_5::signal_t {}; struct stop final : public so_5::signal_t {}; const std::chrono::milliseconds sleeping_time{ 200 }; class A final : public so_5::agent_t { public : A( context_t ctx ) : so_5::agent_t{ ctx } { so_subscribe( so_environment().create_mbox( "demo" ) ) .event( [this]( mhood_t ) { trace( *this, "A.e1 started" ); std::this_thread::sleep_for( sleeping_time ); trace( *this, "A.e1 finished" ); }, so_5::thread_safe ) .event( [this]( mhood_t ) { trace( *this, "A.e3 started" ); std::this_thread::sleep_for( sleeping_time ); trace( *this, "A.e3 finished" ); } ) .event( [this]( mhood_t ) { so_environment().stop(); } ); } }; class B final : public so_5::agent_t { public : B( context_t ctx ) : so_5::agent_t{ ctx } { so_subscribe( so_environment().create_mbox( "demo" ) ) .event( [this]( mhood_t ) { trace( *this, "B.e2 started" ); std::this_thread::sleep_for( sleeping_time ); trace( *this, "B.e2 finished" ); }, so_5::thread_safe ); } }; void run_sample( so_5::disp::adv_thread_pool::fifo_t fifo_type ) { so_5::launch( [fifo_type]( so_5::environment_t & env ) { create_logger( env ); namespace pool_disp = so_5::disp::adv_thread_pool; env.introduce_coop( pool_disp::make_dispatcher( env, 4 ).binder( pool_disp::bind_params_t{}.fifo( fifo_type ) ), []( so_5::coop_t & coop ) { coop.make_agent< A >(); coop.make_agent< B >(); } ); const auto m = env.create_mbox( "demo" ); so_5::send< M1 >( m ); so_5::send< M2 >( m ); so_5::send< M3 >( m ); so_5::send< M2 >( m ); so_5::send< M1 >( m ); so_5::send< stop >( m ); } ); } int main() { try { std::cout << "cooperation FIFO..." << std::endl; run_sample( so_5::disp::adv_thread_pool::fifo_t::cooperation ); std::cout << "individual FIFO..." << std::endl; run_sample( so_5::disp::adv_thread_pool::fifo_t::individual ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/agent_name/000077500000000000000000000000001523065210100210525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/agent_name/CMakeLists.txt000066400000000000000000000007701523065210100236160ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.agent_name) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.agent_name_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/agent_name/main.cpp000066400000000000000000000111341523065210100225020ustar00rootroot00000000000000/* * A sample of usage of agent name. */ #include #include #include // Name of mbox for sending information to agents registry. const std::string registry_mbox_name{ "agents_registry" }; // Agent that plays role of agents registry. class agents_registry final : public so_5::agent_t { public: // Message for informing about start of an agent. struct agent_started final : public so_5::message_t { const std::string m_name; agent_started( std::string name ) : m_name{ std::move(name) } {} }; // Message for informing that agent finished its work. struct agent_finished final : public so_5::message_t { const std::string m_name; agent_finished( std::string name ) : m_name{ std::move(name) } {} }; agents_registry( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe( so_environment().create_mbox( registry_mbox_name ) ) .event( &agents_registry::evt_agent_started ) .event( &agents_registry::evt_agent_finished ) ; so_subscribe_self() .event( &agents_registry::evt_show_registry ) ; } void so_evt_start() override { // Initiate periodic message for showing the content of the registry. m_show_timer = so_5::send_periodic< show_registry >( *this, std::chrono::milliseconds{ 20 }, std::chrono::milliseconds{ 20 } ); } private: // Periodic signal for showing the current content of the registry. struct show_registry final : public so_5::signal_t {}; // A list of currently working agents. std::set< std::string > m_registry; // Timer for showing the current content of the registry. so_5::timer_id_t m_show_timer; void evt_agent_started( mhood_t cmd ) { m_registry.insert( cmd->m_name ); std::cout << " started: " << cmd->m_name << std::endl; } void evt_agent_finished( mhood_t cmd ) { m_registry.erase( cmd->m_name ); std::cout << "finished: " << cmd->m_name << std::endl; if( m_registry.empty() ) // It's time to complete. so_deregister_agent_coop_normally(); } void evt_show_registry( mhood_t ) { std::cout << "--- registry begin ---" << std::endl; for( const auto & name : m_registry ) std::cout << " " << name << std::endl; std::cout << "--- registry end ---" << std::endl; } }; // Base class for worker. Agents of this type have no names. class simple_worker : public so_5::agent_t { public: simple_worker( context_t ctx, std::chrono::milliseconds work_time ) : so_5::agent_t{ std::move(ctx) } , m_work_time{ work_time } {} void so_define_agent() override { so_subscribe_self().event( &simple_worker::evt_done ); } void so_evt_start() override { // Should inform about the start. so_5::send< agents_registry::agent_started >( so_environment().create_mbox( registry_mbox_name ), so_agent_name().to_string() ); // Limit the work time. so_5::send_delayed< done >( *this, m_work_time ); } void so_evt_finish() override { // Should inform about the finish. so_5::send< agents_registry::agent_finished >( so_environment().create_mbox( registry_mbox_name ), so_agent_name().to_string() ); } private: // Signal for limiting work time of the agent. struct done final : public so_5::signal_t {}; // How long the agent should work. const std::chrono::milliseconds m_work_time; void evt_done( mhood_t ) { so_deregister_agent_coop_normally(); } }; // Type of worker with name. // // It's derived from simple_worker just to make example code shorter. class named_worker : public simple_worker { public: named_worker( context_t ctx, std::string_view name, std::chrono::milliseconds work_time ) : simple_worker{ ctx + name_for_agent(name), work_time } {} // There is no need to define something else. }; int main() { try { // Starting SObjectizer. so_5::launch( // A function for SO Environment initialization. []( so_5::environment_t & env ) { // Starting the registry. env.register_agent_as_coop( env.make_agent< agents_registry >() ); // Starting workers. using namespace std::chrono_literals; env.register_agent_as_coop( env.make_agent< simple_worker >( 30ms ) ); env.register_agent_as_coop( env.make_agent< simple_worker >( 50ms ) ); env.register_agent_as_coop( env.make_agent< named_worker >( "Alice", 50ms ) ); env.register_agent_as_coop( env.make_agent< simple_worker >( 70ms ) ); env.register_agent_as_coop( env.make_agent< named_worker >( "Bob", 90ms ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/bind_transformer/000077500000000000000000000000001523065210100223125ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/bind_transformer/CMakeLists.txt000066400000000000000000000010041523065210100250450ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.bind_transformer) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.bind_transformer_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/bind_transformer/main.cpp000066400000000000000000000204541523065210100237470ustar00rootroot00000000000000#include #include namespace example { // Header data for an operation. // // This type will also be used as a separate message. struct operation_header_t { int m_id; std::string m_header_data; operation_header_t( int id, std::string header_data ) : m_id{ id } , m_header_data{ std::move(header_data) } {} }; // Payload data for an operation. struct operation_payload_t { std::string m_payload_data; explicit operation_payload_t( std::string payload_data ) : m_payload_data{ std::move(payload_data) } {} }; // Description of an operation. // // This type will be used as a message. struct operation_t { operation_header_t m_header; operation_payload_t m_payload; operation_t( int id, std::string header_data, std::string payload_data ) : m_header{ id, std::move(header_data) } , m_payload{ std::move(payload_data) } {} }; // Type of message to be sent to an operation processor agent. struct handle_payload_t { int m_id; std::string m_data; // Constructor extracts all necessary data from an operation_t object. handle_payload_t( const operation_t & op ) : m_id{ op.m_header.m_id } , m_data{ op.m_payload.m_payload_data } {} }; // An agent that handles operation_header_t messages only. class a_op_registrator_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( [](mhood_t cmd) { std::cout << "registering OP: " << cmd->m_id << " '" << cmd->m_header_data << "'" << std::endl; } ); } }; // An agent that handles mutable handle_payload_t messages. // // There will be several such agents and each will have own name. class a_op_processor_t final : public so_5::agent_t { // Name of the processor to be shown. const std::string m_processor_name; // How long this processor should "work" on a message. const std::chrono::milliseconds m_processing_time; public: a_op_processor_t( context_t ctx, std::string processor_name, std::chrono::milliseconds processing_time ) : so_5::agent_t{ std::move(ctx) } , m_processor_name{ std::move(processor_name) } , m_processing_time{ processing_time } {} void so_define_agent() override { so_subscribe_self().event( [this](mutable_mhood_t cmd) { std::cout << m_processor_name << " processing started. OP: " << cmd->m_id << " '" << cmd->m_data << "'" << std::endl; // Just suspend the current thread -- it's an imitation // of some long-lasting data processing. std::this_thread::sleep_for( m_processing_time ); std::cout << m_processor_name << " processing finished. OP: " << cmd->m_id << std::endl; } ); } }; // Type of an agent that generates operation_t messages. // // There will be several such agents and each will have own name. class a_op_initiator_t final : public so_5::agent_t { // Type of signal to be used as a periodic message. struct msg_time_to_generate final : public so_5::signal_t {}; // Destination for generated messages. const so_5::mbox_t m_destination; // Name of this agent. const std::string m_initiator_name; // How often operation_t should be generated. const std::chrono::milliseconds m_generation_period; // Counter for operation IDs. int m_current_id; // ID of periodic msg_time_to_generate timer. so_5::timer_id_t m_generation_timer; public: a_op_initiator_t( context_t ctx, so_5::mbox_t destination, std::string initiator_name, int base_id, std::chrono::milliseconds generation_period ) : so_5::agent_t{ std::move(ctx) } , m_destination{ std::move(destination) } , m_initiator_name{ std::move(initiator_name) } , m_generation_period{ generation_period } , m_current_id{ base_id } {} void so_define_agent() override { so_subscribe_self().event( &a_op_initiator_t::evt_time_to_generate ); } void so_evt_start() override { // Periodic msg_time_to_generate has to be started. m_generation_timer = so_5::send_periodic< msg_time_to_generate >( *this, m_generation_period, m_generation_period ); } private: void evt_time_to_generate( mhood_t ) { so_5::send< operation_t >( m_destination, m_current_id++, "from: " + m_initiator_name, "data generated by: " + m_initiator_name ); } }; // Object of this type will be used for distribution of // handle_payload_t messages between a_op_processor_t agents. struct distribution_data_t { // How many a_op_processor_t required. static constexpr std::size_t handler_count = 3; // Type of a container with op_processor's mboxes. using mbox_array_t = std::array< so_5::mbox_t, handler_count >; // Mboxes of a_op_processor_t agents to be used. mbox_array_t m_destinations; // This counter will be used for simple round-robin distribution scheme. std::atomic< std::size_t > m_current{ 0u }; explicit distribution_data_t( mbox_array_t destinations ) : m_destinations{ destinations } {} }; void make_coop( so_5::environment_t & env ) { env.introduce_coop( // All demo agents will work on separate threads. so_5::disp::active_obj::make_dispatcher( env ).binder(), [](so_5::coop_t & coop) { // Ordinary MPMC mbox to be used for operation_t messages. auto destination = coop.environment().create_mbox(); // A couple of agents to generate operation_t messages. coop.make_agent< a_op_initiator_t >( destination, "Robert", 0, std::chrono::milliseconds{ 125 } ); coop.make_agent< a_op_initiator_t >( destination, "Garry", 1'000'000, std::chrono::milliseconds{ 210 } ); // Single registrator agent. We have to store its mbox to // use it in a message transformer. so_5::mbox_t registrator_mbox = coop.make_agent< a_op_registrator_t >()->so_direct_mbox(); // An instance of distribution_data has to be created dynamically // and stored as shared_ptr to be used in a message transformer. auto distribution_data = std::make_shared< distribution_data_t >( // Create and store mboxes of op_processor agents. distribution_data_t::mbox_array_t{ coop.make_agent< a_op_processor_t >( "Alice", std::chrono::milliseconds{ 150 } )->so_direct_mbox(), coop.make_agent< a_op_processor_t >( "Bob", std::chrono::milliseconds{ 250 } )->so_direct_mbox(), coop.make_agent< a_op_processor_t >( "Eve", std::chrono::milliseconds{ 200 } )->so_direct_mbox() } ); // We need multi_sink_binding because a message has to // be processed by several message transformers. // Each transformer will be a message sink for operation_t message. auto * binding = coop.take_under_control( std::make_unique< so_5::multi_sink_binding_t<> >() ); // The first and simplest transformer. // Takes operation_t and sends operation_t::m_header as // a separate message. so_5::bind_transformer( *binding, destination, [registrator_mbox]( const operation_t & msg ) { return so_5::make_transformed< operation_header_t >( registrator_mbox, msg.m_header ); } ); // The second and complex transformer. // Makes a new mutable message of type handle_payload_t and sends // it to a processor agent using round-robin distribution scheme. so_5::bind_transformer( *binding, destination, [distribution_data]( const operation_t & msg ) { // Which processor to use? const auto index = ( ++(distribution_data->m_current) ) % distribution_data->m_destinations.size(); return so_5::make_transformed< so_5::mutable_msg< handle_payload_t > >( distribution_data->m_destinations[ index ], msg ); } ); } ); } } /* namespace example */ int main() { using namespace example; try { // Starting SObjectizer. so_5::launch( // A function for SO Environment initialization. []( so_5::environment_t & env ) { // Make a coop with demo agents. make_coop( env ); // Give a couple of seconds to work. std::this_thread::sleep_for( std::chrono::seconds{ 2 } ); // Stop the example. env.stop(); }, []( so_5::environment_params_t & params ) { // Turn message delivery tracing on to see message // transformations. params.message_delivery_tracer( so_5::msg_tracing::std_cerr_tracer() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/blinking_led/000077500000000000000000000000001523065210100213755ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/blinking_led/CMakeLists.txt000066400000000000000000000005411523065210100241350ustar00rootroot00000000000000set(SAMPLE sample.so_5.blinking_led) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.blinking_led_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/blinking_led/main.cpp000066400000000000000000000032321523065210100230250ustar00rootroot00000000000000/* * An example of using composite agent state, on_enter/on_exit handlers and * time_limit for states. */ #include #include using namespace std::chrono_literals; class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off final : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off ); blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1250}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { auto m = env.introduce_coop( []( so_5::coop_t & coop ) { return coop.make_agent< blinking_led >()->so_direct_mbox(); } ); std::cout << "Turn blinking on for 10s" << std::endl; so_5::send< blinking_led::turn_on_off >( m ); std::this_thread::sleep_for( 10s ); std::cout << "Turn blinking off for 5s" << std::endl; so_5::send< blinking_led::turn_on_off >( m ); std::this_thread::sleep_for( 5s ); std::cout << "Turn blinking on for 5s" << std::endl; so_5::send< blinking_led::turn_on_off >( m ); std::this_thread::sleep_for( 5s ); std::cout << "Stopping..." << std::endl; env.stop(); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/blinking_led/statechart.png000066400000000000000000000452001523065210100242460ustar00rootroot00000000000000PNG  IHDRY5tEXtcopyleftGenerated by http://plantuml.sourceforge.net:gU>zTXtplantumlxm]k0ڥŐ Yu7"R5jXMGz*/ds=݂RM.XL3 ْ<6640 cp[]dR}JA *Y NxuY܅85Lp+N? @1"y&V `f8_Du1;ڧ)$]Vl&BЏYKRB?U<8`2~A:(R/V -%m}ZHzx/ɝ@Oάx)q, Ц?>, 7"Fl UyCTW{HIDATx\TU)behc7]]i+%٤XcXTXXԲnhҊbfalHE'Mk0IHMI4}S3sg~_5;wΜ{9=szyQdd.KF쒑dd.KFFNT.**J˗+~*bWv̐/C&H]Y^^^``)5kÇ]?eYZ###TĮevUW^XĮ l^Vٵ^ ֦V _b644e͘1CP477Į-%%6оΝR%vn6?A&թT^^*Xbe'?)**hllr%@:222aW.--rhoo%@:+]/T/bgv+((`cppʖu vRRDf7J؆ .ʖuGn@@C^^^IIIuuu[[kTĮcŋ6 v6&Mf͚1#K:Ն͙;͙3P.744jg vd:po馜P vApUUUsssOOγ{Ί @VG0@}5byѱdWx㍒6nww7g vgh__W&9s&xe಻ζ~̶m۶_uWjj|b=tP E K:лTh L&۲eKJJJER;4no63lbx\.M<'v]cp_x_(KKKn3dY NԢs @[b TF8୛st}8F @"*J8iz̝n쭮'[h\Hu=|VFh% d5|-QKϻz@*P'%vEm_;vIEAd.K.KF.Kd.KF.Kd.K.KFd.K.KF.Kd.KF.Kd.K.KFd.K.KF.Kd.KF.Kd.K.KFd.K.KF.Kd.KF.KdĮL {ų穸]qپFa* *(bWt XI4n/CTPĮM4_:مHcؖyO""vEzߋ |V|;bה_u?^^;SzTem)T8Į]S+)ίPb+d˜x[DB캁  م K캍~4UvV9Hnz-tRĮ+훁N|Got3/՟lڇcQ\恔ʐGdӸ~uB@3y9lbmĐ"yF5Gs7>Eeٕzks[}b7GOLAe/1j"̽x~ ߎ|xE/1f&ldLCEꇓ ~722B(bO\.|A瑫O<|9"Օ v- °ːm!Ǻ{`bω\ID[rOOOӧ _bW;Ι2$Jw:<.ݴ/ky*Fշ-ʯ /-NDv`h;BV|ii- v,dcNuu5\/T/;he6uN5&&.!!A"$%=z+s֬)L'kC^^!zzI6L8v5V{7^r5@3%e1U܀m̉_G<<&ZRR\ooo/Ɇ no`5YY`Q8b;Wuf@n>JQQQEEEcccww7b1z@ΜnQ|JAA .kY`'ۻtQG/JJJ%v]PB:RBޮh]bײW@##* k.kYYӟe_ϟo26Įxe1<<70F_k7;Kڍ]hr" 4 'N4.'Qf- bu .K.K.K.K.K|p‘lw -\fwtg.kP2cK~.Kz.K.K.K.K.K.K.K.K6:?w]{Е='v'4 I'Ht#vw>veKIY+(zc#!؝;~SmWc`WJ]Zw" j"G|^nn}OݲUnj=Xvgow>7x0YBHK@^<{-:ݥ>Oq(`ꢢ"]zDawhɏBBڙXyVZp< #vj̮FvO\:\/]iӕI -,,dV^Ke~ZVo<'y_4 'i!B;N o+mAњ Oj~(@Q*Ι72 pZ*]tI0xP}ϝEg#U:ŷ#)hI0mNy Ry\T*s$<ݯwNOz{zzQXgzIu겏V`>]V⑅.u5-P 'rKzP {_#\/ZeQK~>ˍAM([^H-4.9!OcD.O|+eFJvџ3 ^վ==>^x-'Yݳ$ٛ ƌ9cݢsEI-x)<–D8@444TWWd29U6. *YŸ!p,Q(AB*no3EI1|@|X*,wmv>cሠ"4AB*.D<] f_>P]*c?!}9n=ݮY v$`p6z.ZmSb?hjOHGpDN-[Ocw_b&BuСG$8^MG[G;w] Q=|(8!v$e9:?{5wU$WN=Xpv?ZNlG{\ٶnKoB2}/b4w&#=]+ a7tpw.?l>'@@v35rv/_PorӹL!"s'vd4^~lENn "5x\rSgǫvv'X=](BT0z6PCm~ziI 7CʐG='Lh|PEB?yFYZV?K8+(4vnA-Ξ@6 !=Ck˪eßc?/FnzRkN;j.К@g ,cԷj'?J z7jz .z0kR$ bWz "svFyݛ]T܆pMĢ۳]4cJXtKvw:$~6G;6m"WU+D.Ԃ"h$֧;nЙ*{=`RpiR;DgGl5L8 ]QЯt1N6E$`kq 2Q w6{tw=m|b*۞]1R9ݡtZ[ yAMS.gr50>XDng:vVy2#Cg*flGEo,r)Bc Zkԟh[Oy<_ᵶxn޼۳fF;j@;vlL@@.4ɥ'..ؘZ^v-юnV(%êK&&&FX2l[\KD(3Ayyyl _ ԁN ®%zv'aL\Yc7T[[˷r9ℏAM#5wxxdNG vbЇE{캄`'b_vQ%X:i>NoZة7beJ]rAdˮ^2Vkpƺq G6WÀ=Y>9*eGg|W8&w)Mf }TTG@||wf#OKuό a_r:wԸX%ev4Z$]L1dqHhh/z7b0D׺q m)g-h4ZT,VKam~^,, vBqN^]8Ci4&g%8%㕌Π]JսE58} 27d4gfZ䥎,'9,-;nƮ* z"L^7W PoG6WPAo-s=5OwnႧrp1_nIkXd75!]'EJvw̢ ^u=[dc5O3-$9gvЋ.-Jv٭yVXM]*  y4]zl5) Įi2[4kԕH'.&]mo(=* K uN}鸒݆'hukҋbbו*!7OAû]k}RĕO2v^w|}vv_~30DZmZh\.JZ.M?wn4;#Cg Չ6yiIE:O3H(r.V%A%n;ݳP܇I Sy^8р`$Bo/lƙ3`{dDe2MAL7G9ը]7DG}u3_9xwN9rG٬YSDJK{˰ (M8 U?ҿg+av>yV;U*7 q'm+W.C5 侧e2Y]]F1fWgހ0]qv)mv쒒(zzd!8sO-/nOOujj|dt4--(ӾYYɕk'%eqDjuv-w*6cIagccBT8zr")||;"K$3p򉿘[4F= [^^sdNN]Nwq`1em׻aa^jkHHU@ 553!!eѐN@II ?˃WvP{Xo៖skl0Fi}ڃ 'mG1{{Oy}5 Op0ij=e7բnsdv{s7x0P_Yx솇 ӁG亙  psr^38%&. ̯ +Iy6 ŧp088HfWB('X40e\Ts n~~T*U** u*  <---U(صf0.+xTgAUIݼN`sLhfwt+*u2GQ*q2ؙc٥]9PYFx́쎎PI98 =.6<<|lY̊DZחGEEhBf;hvN2jrŋ vNB̳ٳg755utt>j]Y~~%wmooh4/۽{PښoJŭV _b ^ڋ/ވKN /oD7BKd]]]Ncзs]Į-<Fn`~q?)2 (Z\Kn`G6m^q ka]b= V۬ؽr'qĮ؁lbW;P$]3e>6f`hS#߁d4?-⾧iYɦ} b״}u@Fe#2iCa{P;ĮmĐ"W.8k5tA3nCY]iQl n|. _5ͨmx2,cNxMA)F̋gOtvѧi{T/pEm *br̩cxA8lʮgOn.|NҨi}jYB*0 EE@]P5uWuCwv>}Y]h\q7 mi^RՀYvs GRŃ"d>*a]^GGm,"ܽݣ_^U8Ů-iiseJ\cNHmr]ץHو}..///|!7w"0~7Ϝd 'k Pe'fh_t\ʮ2at߀U--ĮׄϖJJ^d!ɓo9SF5 '#'N4/T# ߝhK.74400:vZ;ML$9*lO,tBvC1 47GDL{`)apט/v4ZrR쉄ݮ4Xo៖* z&\ǮF>Z-vALA5==F. RSB?!E,}ধ?d-V*7 '`_jTA⸱sGx͸0ҲjQQQEE:m c_]8Tb8``aݔIๅqv_AjIIh?f'~.Ǩ@&utt ewӦw±dWt|]PhIiix9a />n~~>뱩T*H^bש ;M+ {Mp0vҞ?\j4bױ% m Yi#h6fRNN $܋]K1"vmb7>~5 f͚b;,&MN^dR"5JĮvѾ3{|;t.0 𲾾o{bb5."񉯉 ]\ <""ԩ[ N&v]#S&'U-ֹŅ] ٩! 97ZZrӟJfC :wx .b%v]fM2d0 4Zbإ@]b%v)f`F>ǽp$'g&==i9[ %3ߥ@~.һ.b%v] .K%v]bE/%lO5_ dHN^d~ĮH]3DFFT~k::ӹ Y4#+ؓĮӷs||b=]b\vQ))%su==ٯ)FEfdn:D@LpA|)Ph KC1G:8hiiFDT"=un]l@ d8~BrstRC>qj,`2'qӦ `;'5+[*4::h]K!Av):(7{-m؃ÖY`..I C)SnCV\ #qvY9_;/\Br`$vaKȠP7Qr,_qoLJzl<`2)}wo1.[ &ůB& ?zX`LvY`% Sl(IOO^d֗E5 otI4ήEI!pj.`0EI1wu"JZ.ȫ,bע@pqup"7b7r":@:5م .DҒ+\9ص(eh;G‡fQ(,M &" /"+ v M)<:_qIUzH$3z&v.tБB!"e]0bM- Kҁ ]R@R@jc/rma!kSttrhEjP,(kĮM*x =!w9l-īp946gNR#jT #ȈjFR) C%{OcO솈pq>JVq8GՕ+Wdr}>\ƕk𗜜t;i|Į j  = ['e2~¦%vm ~:Axꠟ,3H3]3݃@'BGoM*wWZ/`[V5U vtdѹqM캘ݤxnQs׏@gIv\^"#'$De} rV?3g>]8 8~% 7$|9DRi:{p~cpͨHāE`UM، xpgg֖e{'$$䋂Y\Bn)0F`Bqqxj|$ߏi$ȒWWddsr^C DDL7>~*^ނŸfX\׶2puv;f *\_ಁ'? d ؎2`4\$Y b];˒ (̲&M$v.&6ZP~ 6X Y>LG3dW8!&|$S3zW&ێ3yЋu.<\:nCCo% v5qj7B͇Q:ÙjC4~8HV+ɹ֕<: :'Yz?3 _~?k+>+**O>:!gݚ]N0t);a,E ")++精;wcٽ4|c񗮴tj|.:J󋊋uȈDz *~}nPSi_&!Ba naa!UUU=== aq5о7J w޽}ww}:]Jh&D. '^e9gq_em .ߎ|x@;6)L[S<b|X6880 &vq8U2;7Cd_=E:uYálgE>hmd7׆ T \ @-GjAV;銉]z!q7ˢ|ZZ\e%ݲ;]SW*r䭷Qjᓯ  FRC脒ވbj/:ք%E/(\1pF Ѐ@K}-?kC]+hb~z'7}[QQG NW|r|!}Q: Fa}쮸R;Y[\%cTܭJA9ZŅe Fb\(jڶ2UӫּUDEG^9j;nbT8\+$`d5 j&26Zw߭7ASs;@pj܄]}QLpƽ#oou7骛2jv㊩-ϮuC"P@֭u!ۃ|3zy4a+vc䶟)Z}P%#vͱ)Z[~heqf_?t_b&Sowl5aC}¿rq;ςW:N}Me'0]Jx8Ş>ݱi\KOp&kA['c'P>@Y :W\2INK;d"ّ9d$v jIR8/->"صlӲzg㫮#(.'n.D{G\IC0fT2dWC#kML^'JN6#]vd-7^fBS$iafb-Bu^-:;`$v5G KJ!]Px/1S<GbWy`F>]KĮ'EJbu?v?yu~aV>$ua翍gĎ]4W?##Cرck׮ή.sWO:'kN>]כ'\8K+%%ih[iiiqqqFByyIp .؉yLoo0f6e~F:?CO3#0d>!:~hdfk]6N p9:p8Jev7c׺|bW8E]O FFFK %''<DŽ@gp8I캓ߵ͛7vYa;DRSS#a0A*0=vY  6B>Id'+.vs_ldz~@oLgwɱ=ȏN(Qc)f@톆SDD!H3]48-Ѣg6n_ɍ.{R]|ɀ97N1/^('2}][(}]}Wsr0!l|W]pgWQ6d2ObWDم{_l`$) s2] }"jq,|"2/1_vޭdWxnٲ ÇK>5$$BnF"e@ß2. Оmt޹񓒒எX@@"^MMMl=PD6.E ;6g)1S;ObWD5®](j]]viurr2:LWwQcs *.0 o 볋 2Oo"©}]{SxrIߙc1b;Ml=?|NV|^@%)Z>|9^xA9D.]ص]ȿs,;e vs5Cs:]b~:˯o䄃6E$.kYӲU;Ow}2z?|.s3wBN?(T]7#Cg:ױW4b-uIaUOO]2LzTlȰIaves CbJ~vgU7۳ܡDžZM3w][J{_wA!ZzdĮ['d!_.C~Szbמ֛w&mЫAb}i;mUm!1t^C)#v=]g]1n޿zF񪝗/.*2E|VيڢHN62ᣕ+w.UcUxwf1:Į m;CeG5;yO CjEGRJ߬1%W.ܭ'4| _~S{{ze\di[)rdRӞ7ud\ozS;\wwF|=s}'?{SMqq|{ov| b~e#pd9QA餰fYZ|=ݏ3&Q #+`1 6b3vwӦ]V$GNy{Ob_'9q K슗)tQ6k|]8Z9ZGy͸vCқ*tz.#CgdӚ#B|v- `"e+6bD|2+9V?' .긠ˆAb5.anm م?RTTT*JB3pMqf,e̙trrR;[ZZP(5 Gpvwi]vKy W9`W8ā^#p:mg:&v@3/4 {W6˦Lr~5/c9aCĮn\ @ <.\]M:a@ h.OEL_pY_/17!v=݉]b%v'*l&` v]'PfN$v] .K.KĮ۲{0#h{HN6 ]gq'OjP2cK~.Kz.K.K.K.K.K.K.K.KN v_w Q.+vv_|9Q+\Įf`H=E QBkw7bg yDs9ȇ[}g~QPP@R6"'}kxuv:2']*iKKL}DT\& ]XVV?f; OLLp/|AZU p}.3yӶىإZp#-**B{~܆Kj,Ҏ5}b~)^u._M¾|WQ RaO\ܟ#G..srAV{-݀]xcvBd[}({1 p.O ؅wAN?avYWa-xMAIw4yJVP pZ*]t=n.s=ڵo~tv>QegF4΂mzz veBրZHqZST麳`H2ӃRTVo,,)OZk V:7&ɘB@Q9`#+\/n_ 5~saʢ%%OWRK^s {!] ! _+\f_;]x %5ʵ f6t΂.NtΠq98}W ̾r4"lhh]qG+M_~8PSPs )^߳{JTE-.uy'X*/q[Cq 8ʼnK R3cA  Ua=b;mju  sj5dpj= :jm|?ѳ"<{F08U@U涆p"8N'A U<$.sApQpƽnkaA%V̩7H9rsTd6{\y,+{CCX _2۬_yiȊ9{izlʐyj*A/nN:DһbtX3:-%#y9ؽ4|amY]i,t5dfPGt<,9^Sc\-x_C,t^|t 3Q FG؝?!]c˜z4Wj(8yx\kz"!$aGłH|`T}N v5Q|]Ya.h4m6:xE]_l{pOQ5>Я߄x⑲?^QFOTvFJEU(H܋8អ(ʊ.*eߒMulNh4R-2_ អ( :C 7GFh.kC݇Z kI~ܹs}}}W\Eyio*]yl3h4u\҄ijWWWOOpOQ'c*辋GTh4ͽ|+}N:e6{{{7O(ʽu 8 {zh4 Z1ïƮ. Ey>/3hKp̞Fhn=~_Z-u v )rc78i4Ҩ-[Ң]{U>x6 4FkY37ڌFl9pOQJrЖ->FhS}z^ԩ۝sE/cױ1yz rރX21Q=73y{h㊶FЮIY &v=EQn+ cYJ7pNT%oyݣ+~`4m\/>&`sអ(Tmhh<%lh{=a m\e%%%2xO(ye^4½p?mvFMό/I3WUUU2xܹA=EQcl{ᾯ?46Cd;:U$[,{2˭D4s?A7L}}}S)r?u$*4Çވb t̯'taaw Пʕwc +]-[/86 X.naL~~7D|l-.9(88k+Ѽ>wO eh4LvO(T<*OZY P^ֵk܂Я|r{|XۉZ!xd"5[}wwݢE QqңмR~oB#0q,,잜!{gǿL'~ZuQ;f6t'S~j|ii^¾.c]p'(8G{&'tؗڍ*ckX]*df>/N z&fs⟃^sj޽{kkkL&Ӕn{Oi^7jgR7)$m ܇ݡʕwk+V*sO4rY rlGx;!Dy;tonn>uអ(=ip+?r$vOOO,>// .ΝM9WLb {p#>:{pv#SE< 5nVG=eE7Wl #pcz~q7<^yp/ ]~p/o,U9[8F'SE<^K)(p999NJv;XRKW̃VѬ\rdU ſJ]&իzJ^G|KN#)"h46q˱h{=EQ{溦ӥ )s\2?2s-F'SEhkxpx޷d{4͝lgcb\⟐h{=EQ{FEQ{FEQ{FEQ{FEi4F#)"h4F("h4F(r*v%yew7h4 mZD)|<F=<|pOQ 7^@(jǑߏQ1=EQC82radd(; pOQ5#!SEQ.{i}%^.(tux=EQ]po׊(rM !SdžVw$ȧIse k[cS&l1z? 8'SEQ]{j?mu5&ɯ vE O)(7)QhH'2pZu"~{?5=G%(DY_K MeQWKpHuҰƍ865{g?H PEQnA{ʊ.*P.[r~p$rj[FcWW 'yY+O{GcA8/CNstXKiQjwf 9xOýc&.ibArpO}.iK uHyIRZZZ=~xxYf.wI{Gsw1 E9T Ը/o2UmْQנKK|542߷F<00@/{F15u=}L;HQ3=5dmcϼ 4ҒwRxݟ"~^^^ZO:C9ٳgt˗[[[prw4WJ;vה$y@Q{jIo ;gz{M*X__ʕw㣍R53ߑ ***@&9s(@|TTV^͛7+];½+3\''6HL3-(=5}l@nzu+B> ܑ1%9Wd0plq222x "`|Ƙ_ %rH2y+qمUUU2x>88Rpp™3gfql<}5[đd_8.%K4W,%%%';V6ei96X羾>=E)T>x0sځaaw\w^1p_{׿斖յwuu Jj_Q^SSBZZaaa↎e8-[nF ˗ˠ1ڵkEpx'\(44Rgo+ c$|B带<8EF>؈d'SឲIO;c6Pۂg)<|x(tsyu@lonnF;fɓW? _ՙ3g_5hd10.=***66 <&&xA|]dBŭ]#""2oK$''Kwꛄ^'cƦ322d<.4r0(N$½\$`RPNVw$T ^h k[cS&lݮc)>dž{pG}71gee痗;::PglXu˱ būi[ڼyH%AAA[C8&$-km0|sZ,\%pqZdq{,<ojj嘕Xݬp| ʼ½j2>%O#ϲ)iWmmmL+>m]l5H+;l'^H~AɭV ꣆v½M:<{23 GKlY1S{nn>++K^ i4<ů&<<|cVV9FDDTn~FwTW^SGN]:C 7Gǃ\Yl˽q!$낫&VSIIIJyPGܯ]ИGQ{z}AAAEEEcc#\oo3X`0kp?88OsMC>##[FUk׮%ܻf!흳~h-;{C||49[$ZNx|^KǿX 1|:lF/-}kLU;w>'Q -cKKVrrNJz\^JA ;$Ș2`ص Ϫ 7I*jb9JimѢEZ[[y[#WW267O=q s-p c3((m(=$qԑ;vhf:I5'ܻn>//-(vuCw#9܋c!Yq=k- šNn<(%&>j5qS@:!UJAͳl6;$ V|s3jJWvQ ]ljN_ޖ'4coB'6&SG<9SSA{pp([>KqlX8% X'o5: ŵmmm}}}tR[ #ք< |߶d2=@F49|0nfW*X__hV4x)K&|ƥ\XvlȘ#(p!!!UJQpU1r dUZzENJ{7kp&ּNZ###eLjBE9:rL[nEc%< Yʱ%92A65c]UbccAћ7o|.Sh. {M@Ճʊ&SaK-Û?&CBHs(,̹$77PE)d~ᄉh@FcWW.\ɘZ|!IC*Φer:OK\\_l_=$!+;pUy۳}GE-*!q~ W{L&O CNst<ܱFvg`P~Gg opbbb4]% Oic^uRR(.W L%~KS}Ẻ~V^Q:PoLjԽkW=up=p?Ilpº'{ý2x/C9$eq޹ЯK*/ZZK<7N$ WlRTHH<+p/o}[q)]NMoͿ68M5^єe/Y;,'~'{ܧ'XNF]!o=Jz{G~tt|/AA[~rݚ313j`j?=^l p[8MB!K^zŸMX* %0uJւ@%H|9կ~%|eHϦ&},޳ʁ½6^]'{[`Jd]vM !|خ9 86gf>ȕ6'p/74eJRvm1 ! I?q{{dSýv/_骪wg&TJIܸE慄h WwZŝBC&bu48@cBĎ;H \rzῦxdl*<K0;p?m}M$;4Ԗ_uٰE455 9(q2_vM۱W3-cbYzYDp^7fבݥ<&ӄ_Z$۲%Q q:6Ƽ_䔔8p I=%k $zL:r׾v@'{6ߏœy½7}LRKKKVfǖ*XR 1Wſ[o4+*?#P%ʕw[ ZP#r|k8^g'''u/_^ 8>Za )))[ndzpߑ8o Me,k)UC[~r۾Y~5I3QhsJFBs~m0P3(Nqr4]ᖐ{c#zpO:;uZ &S)xn z^dkʱ9v X^Ǒ[f rܿ:2pW[>88` ,>qBWZ:̅<9c;X+==;OwMdKGTֵq\}ÇWLVQa1,)2<*)q)}@u \ܻ]aMF=7o<%˴7n~`E̵-5,gG5}~yISQhSܦg_Nژ^~[כwxa'{!Ӽ*[uHcbb2224̟?$ap-,>>QpnR{_I ߧ>>Xy>ӾI-ݷO=$$.~EqmW++N;.o9EH P9Gyg=bst/l|)+Gs*o‥1e,iZ 3=w{8ru~|>a@3))OLnӠ $p/Î3A___0ٳڨMMMjXh)cE?5>;{3>>~,ѲbRc9KM]cO9zZmWW\̩u2WVV:^`K2xmߣϽK?'sOsS ceO]pGb`cy'+acbbn݊QB[e\MV[~Up/︸&R/d깠s1pr*׿+Y^-g>~E߮[F{Ϯ*yN%Ӽ'.8_hzy|FFFXX48T}B#V. 5kp_p~.Ժs|+f&+q_榚rWVB7oF"##4!54D_K6'btd81Yڟ$ӧIgƱptr[${'*jW5m ε;;g"O)uC?X]2|36Մ{/ 322$$R(4,D cC0xˠ 3k|os $As8:AoÇHMF0S:J(JMMuN_x#&ܳ;F-m#Pzj >yRuVt-Z!ul孍]|~._, ed;\%RbVGw y1Xu$NDĝJ%thՓtQˀ9===#o}[V.QӉjB~kM5uށp5Xhhe =lp$ܵksKJJR/&!nhՁcì`Ѣ c_ ew>`zXFݻۑ{y;w>'yjDvܫ`d(ɶڵ P΀{W"Y{T\ǒy6mڔc5%u-Խ;7j=뼣3[ f+WE=͋~d,|MHHr'5}dd!v=lC쫮NYo[np&&&>*]%&\{ypGl?#GrP[E"OW`ҪUpV_p"ۋpOg% x|Q\ 4w{immVٳ(G[[Ɓa8ZD\;Ƙp$. Sm.2>|xezOcwwy%QXpC;Odd`{=5SLKK[=]vsww||OR~CSmsinx,|yX.\ G.Ds4zDgffʰX\6oEa]쾾2 ybbk/pL.6\jUCC^7x Ӭe;ޙFi}ֵzwZr]Xҽϟ_-l نΊvpB4 {\>LJZOnEd]yk]]dtRH=ۮ86N%f0@?<`9mzr#* ٬{@~;{0>p?7po6;::UVMًV^QC'ܻ"""p/)?gEOBh49000k4%(>Ln4|b?(j|5\<K^_|Y֙3gPr-{`G@]_ \YY9mLMMMZZ1. WKb. }`:8SHfpl2 ?))I 7u*;dn&ڵk}||d NpbbR-Ztq,X Sghjj R/i=qJ%$$VMJ '"p NO?>wqT4Wyb}Ӟ+D]fF)ܣ}B+ ȅьZl۶g{'_+Y%7g_XX(d2Ω| VȽ]p7͛7ǖQ*cbbl?B%1ظ6ζ(pCee%ϗV#1O$q )**1cS2썎;'G1S~~($ɝ -?/4'7\]\%Kacek$oج{ (,6^ +"5b,屖frp}{Ҹ?>Ie?f`kn?lHNGuY0w t34B_\!ظ3o}OU6I0rm.ػ'=csZ){٠ 2xhYQQQ~~~NN.M[,%<C_baaa@3xtvvjmk#&.iʓ56nII";t&a72QyOhs~3+Vzu=-Gqt.Ǭl!p/~nynr7%*شӜm TՆ>C W)ѡAMMMׅ(A9Ukqޱj|?^ŗ^pǏ?|3i1՛ި<9cn W:}>)) oݺʕ+%ot/g'mzŜ6^RRWT4~gK (~q/{҂q?ow456j+Ls,a4JpF|ŞFp][,#zթ+yC;ǫFoŐܟ=Ĝ}2?Ew=ZGPUC ʾTx,3d*܃%r;kpIgϪ qkEEEV $$$hjGEE9o,u&<f0pL_C߽MzһG[Z\lI lvl[sQt}@~ٚ+*_\̦=½u.|F NUGҫc07}0{m,6"uV?kp/} ൥ڵk ϟ?&)_!o ԑҜ/((hll4 xӠҿOIn=AFv6Ὰ%KW~+Wލ{RR@0[m|J%!Rrtns~nsl'#eO'* l&;%ߍ0-{Dत$@B%&$$yfLМ5GCH8 w܏\ LJ yFp<~6p]/w$cOyg`n\3mظN0vDwO5V#u=.mpO]$7K1hרq ^ b,_dvGK.Qڼysnn$Vp*jW o A|@^#W$,(b}}Lfᡲ 0|ec&%=n(``W#Gr<`n;gkNP X_ Aq)../+W8lIgCV\^#֮} }*͏6}|xԿlgޡC}Hz8=pLxMHxQp۞}YYGSwioVwQ9'ϰE5@-^Xt9&|())9p^r =]↡6,hO=ҒejRcܔn9r~Xo ̍"" 9À8$}Dn9ý:Xabbl=:!8lu (AG9OY٫w$g_GT?G 5-| {Vkl b;pPذ}XNCEQQ%(n9%uj{x嫚 )'2'gC޽U#򆆆<\YR:kʣ8UF5)[?0%AQdSCVDBB rG{uB栠ۧ/Y\y&&>5 =;Y`/&?"?Ӆ \Jzr {j}stGށ-1k}oҶ(!=KOn\Pת, To{ΖIDܗ̬D=Ϟ={m͜}}}7nܨ k|rN:{zqBpE\;<9م0wj5sb5;8U9[fK-pss#Ҝ8/)6::jcc`#o d$D.Ow]ZZZJJ 3Lg@{ {j2X^2=P8d.}nnniiizY+K'γoͯ IocH&_?AX9ųFPis[Ljjǡý9V^#$ZDWCe7$lI)v%Ʒt`kvS8& #~qA)[I1OT-/ŒVc%nh^#?#Ue8;o-z56OtFy6wvvHyȘ=^j{Zq5G7L#2]#wNyOܑwize~kˑrT% ENjǝ]pEN(pOcsNł9gQt<3jkk붻\S<-:fmhlK:|յzH(`:/1޾+f&po6;:::jժy;vjWGDWSQ!]'g6)'e$bzp]3OfhG[2sk,99n6}=/_De{8pOi{M}G}uǞB3s&aI*f[󧍏@ۚVWu]{Zh–;tQC`k%\?azҘ!%}wWƦ4DE#W+Di~o CގL͛7QN&By6e|r䆫W@ .WBHFFͩWceޭ:=,;{i:nN^ػeDuXӧa ʥ@+Z6~rVx[.X?{lbb俛?~hhhll/pO'S@kҶ$Cg_1N+ًC2Pv.;` :pw:#Vlj |8iGҫ'7X{ޕK7.{Å)k0+KE0zbX >@_.)AFIMHGAxDĬ?S_׾/ |ͷvwߍP{zUȾh4;mŮ|[٠l{iPE[ L6)ql'!EGdnKEc 炣^os8N crrks éۆŹ䔭ƹ'ᨬ A~xx.{=,p{{{JCR1{!GV] @SzD_jnW9S$ -ɱ<`9:IhboPVL^LE=H~H܋r0-1`8Ts1Vl [#N~&ЏBWaJ*ܣ΀ r{4(z(닒Be,_XX(d~`@m=;m䆾F0жD 'iF03yͦlI{@[JVAe)0%CCmj_*j=ͩk,wc8I#2j_)Q)-˭NdD}W-XlM{IZ2C-.m V/ ٦)dzEIM'٪W Y+ Xz}hф$\۞`& ;;;]6& p~مuKA|0ā@UUUׅ(A9Uka]l-:G=HO$Wg#P'%%K,___1ј.YpǏ{ ދsB潯Nx!' R Y0NWd3t8=3 TlL]^=ק ~GGZt;MMMׅ(A9Uk=#{[tI yi[iii{??? wu*.Ze2eNpۋW){D=QW UG#QFe"F!{ C~pF|Ǎ(Nh.|F -zwz=p?dtlqq w$*޳.5;#M {pO, ⱸ~Praad$$J^2sͨ٠+Tn=ޱpoI}iׅ(A1=p>3&OHHϟɰuV@ybbRcǎ'OڵKoH UGlɸpN$AF\ex^~% LGEEEmmz梻$ IuZ-[a&S:x03% uX1MtfڕhF;r$g`=lU yIg}bbjkJKKSNQr4 {XXXll,GO<2UЀZ$n9]r$/bd |ejQ^wCDpϦ}/$S= oooonnVB%*pl.J4 ƶ6ES{%/A{,*j1^}x:]:vyn9CϲqH }wo7_Rgᚠծ}MsOdggKT0 Sn3LTKT+Hp$`z=A? HB569܃}}oT{G]a׹?W=%;@ ~_ELzlPqYxMH";Î':z`='_QQ +3!Al.J4dR3N^Wy!6v%bb3J4c:< [ZJK_ƇF> Gk-Xp`/؝Dj%QDvbB\tt&M|gW;"΄G4o/OyǺX+*MY! # ca\Iπ&%UǘND9W~~% nW9iMOdqPaX|}aaaUU*hBܞ%*\h$ޭ+aawC/]zG͔>>B`z~)( e1QdggpކY%8- e;*j1v>r⻂rvT\"%m v## q}\jVVp4W= \Ifs _lsQ=%f]M20N/\h:.|l@i';Zb֌_{I ǿonn>uhpOQEw{_h!hRKWJv|z8&%=.ˠI`,QxlT@D$=嚰6XL$5u mf9#KMi.ё#9X}ЫApSPXE(0?vLQp 6Pp=oz_QCi難B?B@}&+/D(P8/((ɑ}ֶL&M2=EQ^OFmaaw52]Z|x>4wN=J@՚%)c8Ӏ{/Q|h,2V3N/}yK.p_O뼰-ϻ# E͋x묩/^h:nz(tuGov߽o-z56h>BȘ[Nł(eQ'?G@e,\bl;KQ%[-->9`zҺv<44D("ܻzo?l۸)@ajp5#OF}/=ݒeaUgd%֗^Ww96#/ҼO.\b96G'H~ k.[b+sMU]ZpէSp3L,M g^_hcT,X934\ѾMVg\rpOQEwuHp*YmabYh?k#܋+}pouGc1L9 mDVƮ S˫!/(G ~oNתL?:6Vk%qƦt$ bUި{cm;XıAG ``/'7M!bmu558<%XeXptO鳓Ha, ;>1ڈسB'o.f)" IBpo,ۿk 4GK$g//Z04 K\m_"kWlGISpEʕ~<&}ON>x t}QҾvssttp:U PbXkMbua?2Ȯ2ք< [kZlɇK|2}v6%|Eǒuԧg_0~pS1sssF7spO(pO_*»g B딍KkiZn[}__#dYIeX4f^.єebXv߾YMI\Ppr0h9iڻVި"R2aMN|!D@64VO܆v*{8yIA8&\3}v" 5*uㇺmzQGćSN̅=អ(=~Yd{,B0;7jTtNvܢ- jVsWYK/s̳ss srpNJs̻vŜ?&ެ7POTpe7$D۝0 (i(`hB'A{<*xݸ)*|LRuXY&::Bq;8X%aawXa]k= GItUzz }-΀{/NMRˣd+lGwH:TAAk^b, ܾ,e'nhuܻrE KQQx̪w֣W9K7-^񹤤mmm2l?888GivE KRc &;|xOd]2Iue@E Ax%A = W؂0Nй&pI"_$X) yۊmVCᡖQq}՛R,4g4%Rcc[oP>c{|}v-KH"\4lP}8Ž48gt@Iz{{[}rsk2ed2_T:ѕFj,I*jy555ͨD+5G{i3s4!ir>GLUx26t~iegYw hڧq!j׍ܞª&U"~ppԩSzW^^^#3 .JCQr*Qt%d W}QQ7NRs4lO(pOZcSdscN= <`}X, h", Nh+]wAn|r,ocゅ`|Ы,^]xߓlҩ\o'KV}{{{gg:)"g }stq .I=DM_h VDUt 'SE'L眊K06sC- o|۔$VA.R7xlelooojjVUU5GR`{}b`}MM zDmmmd2 )"gi{ă|wt61<$ YWWlڙ >`` <z >-Nג"tR4"Q***jkk---@dqA =%\I(pOIJ|{J}y@8ٯ|2 :&`ٴUEz>.8_e)q`zt{:::[؀=អ(=AU%TvstP!*Jl"Wrdf*-HTt>' r"ѕL8e8NLn:?x\lpOQp猪z#*.&$ 6e딜+~lAo{$8) 7c^&2N`2LkXO(pOwȟd7[se_{.)ߐeio1%܀ KŒ= +**] >pW\{˄SƉq\ E uf$W.{;a\vZu 5ЏJ*y@%]c.EQ xYfԞXd/*qBF/&vmT'{%κBQW]tȡI=EQ{3,3$+;ᅜȟ?wGt7+XJo>Hx+GWd3tWFX/9GBBCEMK{b̜ٞKIITΠI !{:PԤpOQ^yef(f-{z"%CQhxL(aB)$mDzWJQ F .\E(T<*}0a-O jP\\ Pu5UR jPݨU@KX=EQW%8vddQ|ԔO jP[[ P^Rrlp%lEyVK0Gʾ8x1/cI a4M&j )ʮ'qu8iy=EQ S _̝y{f&~zTTT0ls>nΌ/I3掌rtq.#S偪 SYf}lOӣ2)jT=l̦_LIWQQ؈4n:=EQ~TECQEOO+VUU555Ft Ey/,' CQEz55i^&p|ԩ=EQQWdEQ8Ead ܻwoQQQMMMKKKgg អ(dž }'`stq(rvLC52l~d)))9p^"So`W^57)(T%{]ohB;wʕ+{iOT3 _۵G|r4S"S޺j(aU}xZQEQs鳿lMuhd>/9}޽ⓓ[ZZZWW5lZ=EQ2䁈'c}^A˼\EQԬGV^쫃WHCV/rrr4A0 ùs禜MK(]Cx2%^pd##{REQDZᤁ0'jfDksssSi;;;{{{MK(g2H˽'E|F҆)qFۯG ۛ恁)g) ;wh4ڕP'_Kh4'zPov-nѐ}AAAEEECCC{{da)g)]rP }.}/uth4F%?QiL 2̜QȾ^ן:ugpp'S O=<:;;+wQ/+N+Jh4lZu 5ЋNaؿ2`!6hCអ(dzܹsN߿W?ʧh4mkx*kFNNWM---v=EQ5xg___lV}qqq^^ލ[9?wGt^JFhγo>G"TJycL`}NWQQQ__֦9{<ϝ;yرڲ2<.G_5@BQEQs+{’744zѨ9{<zzzL&SGGGKKK}}=@|y }] EQE9[htQ^^`}UUՁO:e6k؞pOQ=x e4lnnԔ tx((jVFM 4Ceee z`2zzz'SE.:xP;Ѐg((xVTT((jVFM 4C wvvdpOQA :uTGGf off state off { off --> blinking : turn_on_off } state blinking { [*] --> blink_on blinking --> off : turn_on_off state blink_on { blink_on --> blink_off : [1.5s] blink_on : enter/turn_led_on blink_on : exit/turn_led_off } state blink_off { blink_off --> blink_on : [0.75s] } } @enduml sobjectizer-5.8.6/dev/sample/so_5/blinking_led/statechart_plantuml_horizontal.txt000066400000000000000000000005701523065210100304670ustar00rootroot00000000000000@startuml scale 1024 width [*] -right-> off state off { off -right-> blinking : turn_on_off } state blinking { [*] -right-> blink_on blinking --> off : turn_on_off state blink_on { blink_on -right-> blink_off : [1.5s] blink_on : enter/turn_led_on blink_on : exit/turn_led_off } state blink_off { blink_off --> blink_on : [0.75s] } } @enduml sobjectizer-5.8.6/dev/sample/so_5/chameneos_prealloc_msgs/000077500000000000000000000000001523065210100236305ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/chameneos_prealloc_msgs/CMakeLists.txt000066400000000000000000000010221523065210100263630ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.chameneos_prealloc_msgs) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.chameneos_prealloc_msgs_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/chameneos_prealloc_msgs/main.cpp000066400000000000000000000164151523065210100252670ustar00rootroot00000000000000/* * A simple implementation of chameneos benchmark (this implementation is * based on definition which was used in The Great Language Shootout Gate * in 2007). * * There are four chameneos with different colors. * There is a meeting place for them. * * Each creature is trying to go to the meeting place. Only two of them * could do that at the same time. During the meeting they should change * their colors by special rule. Then they should leave the meeting place * and do the next attempt to go to the meeting place again. * * There is a limitation for meeting count. When this limit is reached * every creature should receive a special color FADED and report count of * other creatures met. * * Total count of meetings should be reported at the end of the test. * * This sample is implemented here with two different types of agents: * - the first one is the type of meeting place. Agent of that type does * several task. It handles meetings of creatures and count meetings. * When the limit of meeting is reached that agent inform all creatures * about test shutdown. Then the agent receives shutdown acknowledgements * from creatures and calculates total meeting count; * - the second one is the type of creature. Agents of that type are trying * to reach meeting place. They send meeting requests to meeting place agent * and handle meeting result or shutdown signal. */ #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif enum color_t { BLUE = 0, RED = 1, YELLOW = 2, FADED = 3 }; struct msg_meeting_result final : public so_5::message_t { color_t m_color; msg_meeting_result( color_t color ) : m_color( color ) {} }; using meeting_result_msg_holder_t = so_5::message_holder_t< so_5::mutable_msg, so_5::message_ownership_t::shared >; struct msg_meeting_request final : public so_5::message_t { so_5::mbox_t m_who; color_t m_color; meeting_result_msg_holder_t m_result_message; msg_meeting_request( const so_5::mbox_t & who, color_t color, meeting_result_msg_holder_t result_message ) : m_who( who ) , m_color( color ) , m_result_message( std::move(result_message) ) {} }; using meeting_request_msg_holder_t = so_5::message_holder_t< so_5::mutable_msg, so_5::message_ownership_t::shared >; struct msg_shutdown_request final : public so_5::signal_t {}; struct msg_shutdown_ack final : public so_5::message_t { int m_creatures_met; msg_shutdown_ack( int creatures_met ) : m_creatures_met( creatures_met ) {} }; class a_meeting_place_t final : public so_5::agent_t { public : a_meeting_place_t( context_t ctx, int creatures, int meetings ) : so_5::agent_t( ctx ) , m_creatures_alive( creatures ) , m_remaining_meetings( meetings ) , m_total_meetings( 0 ) {} void so_define_agent() override { this >>= st_empty; st_empty .event( &a_meeting_place_t::evt_first_creature ) .event( &a_meeting_place_t::evt_shutdown_ack ); st_one_creature_inside .event( &a_meeting_place_t::evt_second_creature ); } private : void evt_first_creature( mutable_mhood_t< msg_meeting_request > evt ) { if( m_remaining_meetings ) { this >>= st_one_creature_inside; m_first_creature_info = evt.make_holder(); } else so_5::send< msg_shutdown_request >( evt->m_who ); } void evt_second_creature( mutable_mhood_t< msg_meeting_request > evt ) { evt->m_result_message->m_color = m_first_creature_info->m_color; m_first_creature_info->m_result_message->m_color = evt->m_color; so_5::send( evt->m_who, evt->m_result_message ); so_5::send( m_first_creature_info->m_who, m_first_creature_info->m_result_message ); m_first_creature_info.reset(); --m_remaining_meetings; this >>= st_empty; } void evt_shutdown_ack( const msg_shutdown_ack & evt ) { m_total_meetings += evt.m_creatures_met; if( 0 >= --m_creatures_alive ) { std::cout << "Total: " << m_total_meetings << std::endl; so_environment().stop(); } } const state_t st_empty{ this, "empty" }; const state_t st_one_creature_inside{ this, "one_creature_inside" }; int m_creatures_alive; int m_remaining_meetings; int m_total_meetings; meeting_request_msg_holder_t m_first_creature_info; }; class a_creature_t final : public so_5::agent_t { public : a_creature_t( context_t ctx, so_5::mbox_t meeting_place_mbox, color_t color ) : so_5::agent_t( ctx ) , m_meeting_place_mbox( std::move(meeting_place_mbox) ) , m_meeting_counter( 0 ) , m_response_message( new msg_meeting_result( color ) ) , m_request_message( new msg_meeting_request( so_direct_mbox(), color, m_response_message ) ) {} void so_define_agent() override { so_default_state() .event( &a_creature_t::evt_meeting_result ) .event( &a_creature_t::evt_shutdown_request ); } void so_evt_start() override { so_5::send( m_meeting_place_mbox, m_request_message ); } private : void evt_meeting_result( mutable_mhood_t< msg_meeting_result > evt ) { m_request_message->m_color = complement( evt->m_color ); m_meeting_counter++; so_5::send( m_meeting_place_mbox, m_request_message ); } void evt_shutdown_request( mhood_t< msg_shutdown_request > ) { m_request_message->m_color = FADED; std::cout << "Creatures met: " << m_meeting_counter << std::endl; so_5::send< msg_shutdown_ack >( m_meeting_place_mbox, m_meeting_counter ); } const so_5::mbox_t m_meeting_place_mbox; int m_meeting_counter; meeting_result_msg_holder_t m_response_message; meeting_request_msg_holder_t m_request_message; color_t complement( color_t other ) const noexcept { switch( m_request_message->m_color ) { case BLUE: return other == RED ? YELLOW : RED; case RED: return other == BLUE ? YELLOW : BLUE; case YELLOW: return other == BLUE ? RED : BLUE; case FADED: break; } return m_request_message->m_color; } }; const int creature_count = 4; void init( so_5::environment_t & env, int meetings ) { env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), [meetings]( so_5::coop_t & coop ) { std::array< color_t, std::size_t{creature_count} > creature_colors{ BLUE, RED, YELLOW, BLUE }; auto a_meeting_place = coop.make_agent< a_meeting_place_t >( creature_count, meetings ); for( std::size_t i = 0; i != creature_colors.size(); ++i ) { coop.make_agent< a_creature_t >( a_meeting_place->so_direct_mbox(), creature_colors[ i ] ); } } ); } int main( int argc, char ** argv ) { try { so_5::launch( [argc, argv]( so_5::environment_t & env ) { const int meetings = 2 == argc ? std::atoi( argv[1] ) : 10; init( env, meetings ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/chameneos_simple/000077500000000000000000000000001523065210100222675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/chameneos_simple/CMakeLists.txt000066400000000000000000000010041523065210100250220ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.chameneos_simple) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.chameneos_simple_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/chameneos_simple/main.cpp000066400000000000000000000140631523065210100237230ustar00rootroot00000000000000/* * A simple implementation of chameneos benchmark (this implementation is * based on definition which was used in The Great Language Shootout Gate * in 2007). * * There are four chameneos with different colors. * There is a meeting place for them. * * Each creature is trying to go to the meeting place. Only two of them * could do that at the same time. During the meeting they should change * their colors by special rule. Then they should leave the meeting place * and do the next attempt to go to the meeting place again. * * There is a limitation for meeting count. When this limit is reached * every creature should receive a special color FADED and report count of * other creatures met. * * Total count of meetings should be reported at the end of the test. * * This sample is implemented here with two different types of agents: * - the first one is the type of meeting place. Agent of that type does * several task. It handles meetings of creatures and count meetings. * When the limit of meeting is reached that agent inform all creatures * about test shutdown. Then the agent receives shutdown acknowledgements * from creatures and calculates total meeting count; * - the second one is the type of creature. Agents of that type are trying * to reach meeting place. They send meeting requests to meeting place agent * and handle meeting result or shutdown signal. */ #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif enum color_t { BLUE = 0, RED = 1, YELLOW = 2, FADED = 3 }; struct msg_meeting_request { so_5::mbox_t m_who; color_t m_color; }; struct msg_meeting_result { color_t m_color; }; struct msg_shutdown_request final : public so_5::signal_t {}; struct msg_shutdown_ack { int m_creatures_met; }; class a_meeting_place_t final : public so_5::agent_t { public : a_meeting_place_t( context_t ctx, int creatures, int meetings ) : so_5::agent_t( ctx ) , m_creatures_alive( creatures ) , m_remaining_meetings( meetings ) , m_total_meetings( 0 ) {} void so_define_agent() override { this >>= st_empty; st_empty .event( &a_meeting_place_t::evt_first_creature ) .event( &a_meeting_place_t::evt_shutdown_ack ); st_one_creature_inside .event( &a_meeting_place_t::evt_second_creature ); } private : void evt_first_creature( const msg_meeting_request & evt ) { if( m_remaining_meetings ) { this >>= st_one_creature_inside; m_first_creature_mbox = evt.m_who; m_first_creature_color = evt.m_color; } else so_5::send< msg_shutdown_request >( evt.m_who ); } void evt_second_creature( const msg_meeting_request & evt ) { so_5::send< msg_meeting_result >( evt.m_who, m_first_creature_color ); so_5::send< msg_meeting_result >( m_first_creature_mbox, evt.m_color ); --m_remaining_meetings; this >>= st_empty; } void evt_shutdown_ack( const msg_shutdown_ack & evt ) { m_total_meetings += evt.m_creatures_met; if( 0 >= --m_creatures_alive ) { std::cout << "Total: " << m_total_meetings << std::endl; so_environment().stop(); } } const state_t st_empty{ this, "empty" }; const state_t st_one_creature_inside{ this, "one_creature_inside" }; int m_creatures_alive; int m_remaining_meetings; int m_total_meetings; so_5::mbox_t m_first_creature_mbox; color_t m_first_creature_color = { FADED }; }; class a_creature_t final : public so_5::agent_t { public : a_creature_t( context_t ctx, so_5::mbox_t meeting_place_mbox, color_t color ) : so_5::agent_t( ctx ) , m_meeting_place_mbox( std::move(meeting_place_mbox) ) , m_meeting_counter( 0 ) , m_color( color ) {} void so_define_agent() override { so_default_state() .event( &a_creature_t::evt_meeting_result ) .event( &a_creature_t::evt_shutdown_request ); } void so_evt_start() override { so_5::send< msg_meeting_request >( m_meeting_place_mbox, so_direct_mbox(), m_color ); } private : void evt_meeting_result( const msg_meeting_result & evt ) { m_color = complement( evt.m_color ); m_meeting_counter++; so_5::send< msg_meeting_request >( m_meeting_place_mbox, so_direct_mbox(), m_color ); } void evt_shutdown_request( mhood_t< msg_shutdown_request > ) { m_color = FADED; std::cout << "Creatures met: " << m_meeting_counter << std::endl; so_5::send< msg_shutdown_ack >( m_meeting_place_mbox, m_meeting_counter ); } const so_5::mbox_t m_meeting_place_mbox; int m_meeting_counter; color_t m_color; color_t complement( color_t other ) const noexcept { switch( m_color ) { case BLUE: return other == RED ? YELLOW : RED; case RED: return other == BLUE ? YELLOW : BLUE; case YELLOW: return other == BLUE ? RED : BLUE; case FADED: break; } return m_color; } }; const int creature_count = 4; void init( so_5::environment_t & env, int meetings ) { env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), [meetings]( so_5::coop_t & coop ) { std::array< color_t, std::size_t{creature_count} > creature_colors{ BLUE, RED, YELLOW, BLUE }; auto a_meeting_place = coop.make_agent< a_meeting_place_t >( creature_count, meetings ); for( std::size_t i = 0; i != creature_colors.size(); ++i ) { coop.make_agent< a_creature_t >( a_meeting_place->so_direct_mbox(), creature_colors[ i ] ); } } ); } int main( int argc, char ** argv ) { try { so_5::launch( [argc, argv]( so_5::environment_t & env ) { const int meetings = 2 == argc ? std::atoi( argv[1] ) : 10; init( env, meetings ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/child_soenv/000077500000000000000000000000001523065210100212515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/child_soenv/CMakeLists.txt000066400000000000000000000007721523065210100240170ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.child_soenv) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.child_soenv_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/child_soenv/main.cpp000066400000000000000000000141361523065210100227060ustar00rootroot00000000000000#include #include namespace example { // Signal to be sent to a worker. struct msg_do_something final : public so_5::signal_t {}; // Signal to be sent to finish the work of the child SOEnv. struct msg_shutdown_soenv final : public so_5::signal_t {}; // Type of agent that will work in a child SOEnv. class a_worker_t final : public so_5::agent_t { // Mbox for receiving notifications from. const so_5::mbox_t m_source; public: a_worker_t( context_t ctx, so_5::mbox_t source ) : so_5::agent_t{ std::move(ctx) } , m_source{ std::move(source) } {} void so_define_agent() override { // A subscription to notifications required. so_subscribe( m_source ).event( &a_worker_t::evt_do_something ); } private: void evt_do_something( mhood_t< msg_do_something > ) { std::cout << "*** worker handles msg_do_something in child SOEnv (addr=" << std::addressof(so_environment()) << ")" << std::endl; } }; // Type of agent that will call stop() for SOEnv. class a_soenv_shutdowner_t final : public so_5::agent_t { // Mbox for receiving notifications from. const so_5::mbox_t m_source; public: a_soenv_shutdowner_t( context_t ctx, so_5::mbox_t source ) : so_5::agent_t{ std::move(ctx) } , m_source{ std::move(source) } {} void so_define_agent() override { // A subscription to notifications required. so_subscribe( m_source ).event( &a_soenv_shutdowner_t::evt_shutdown ); } private: void evt_shutdown( mhood_t< msg_shutdown_soenv > ) { std::cout << "*** shutting down SOEnv (addr=" << std::addressof(so_environment()) << ")" << std::endl; // NOTE: we'll stop own SOEnv (in that the agent works). so_environment().stop(); } }; // Type of agent that runs a child SOEnv. // It calls so_5::launch in its so_evt_start (and because so_5::launch is a // synchronous call, this call will block the agent until the child SOEnv has // finished). class a_child_soenv_runner_t final : public so_5::agent_t { // Mbox for delivering notifications to. const so_5::mbox_t m_interaction_mbox; public: a_child_soenv_runner_t( context_t ctx, so_5::mbox_t interaction_mbox ) : so_5::agent_t{ std::move(ctx) } , m_interaction_mbox{ std::move(interaction_mbox) } {} void so_evt_start() override { // The main trick: a child empty coop is necessary with dereg_notificator. // When a coop with a_child_soenv_runner_t is being deregistered, its // children coops will be deregistered first. // It means that a dereg_notificator for this child coop will be called. // And we're using dereg_notificator for sending a special command // to the child SOEnv. // This command is received by the a_soenv_shutdowner_t agent and the // child SOEnv is stopped. When the child SOEnv is stopped the // so_5::launch() call is returned. so_5::introduce_child_coop( *this, [this]( so_5::coop_t & coop ) { coop.add_dereg_notificator( [dest = m_interaction_mbox]( so_5::environment_t &, const so_5::coop_handle_t &, const so_5::coop_dereg_reason_t & ) noexcept { // This message is necessary to shutdown the child SOEnv. so_5::send< msg_shutdown_soenv >( dest ); } ); } ); // Run a child SOEnv. so_5::launch( [this]( so_5::environment_t & env ) { std::cout << "Initialization of child SOEnv (addr=" << std::addressof(env) << ")" << std::endl; // We need an agent that finishes the child SOEnv. env.register_agent_as_coop( env.make_agent< a_soenv_shutdowner_t >( m_interaction_mbox ) ); // We need a worker agent. env.register_agent_as_coop( env.make_agent< a_worker_t >( m_interaction_mbox ), so_5::disp::active_obj::make_dispatcher( env ).binder() ); } ); // The child SOEnv is completely finished when we're here. std::cout << "Child SOEnv stopped" << std::endl; } }; // Type of agent that coordinates the example's work. class a_work_coordinator_t final : public so_5::agent_t { // A signal for finishing the example. struct msg_finish final : public so_5::signal_t {}; // A mbox to be used for notifications/commands. const so_5::mbox_t m_dest; // Timer ID that is necessary for periodic msg_do_something messages. so_5::timer_id_t m_work_timer; public: a_work_coordinator_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_dest{ so_environment().create_mbox() } {} void so_define_agent() override { so_subscribe_self().event( &a_work_coordinator_t::evt_finish ); } void so_evt_start() override { std::cout << "Work coordinator started in SOEnv (addr=" << std::addressof(so_environment()) << ")" << std::endl; // Start a child coop with a_child_soenv_runner_t inside. // NOTE: that agent has to be bound to active_obj dispatcher // to have own worker thread. so_5::introduce_child_coop( *this, so_5::disp::active_obj::make_dispatcher( so_environment() ).binder(), [this]( so_5::coop_t & coop ) { coop.make_agent< a_child_soenv_runner_t >( m_dest ); } ); // Initiate periodic notifications that will be handled in the child SOEnv. m_work_timer = so_5::send_periodic< msg_do_something >( m_dest, std::chrono::milliseconds{75}, std::chrono::milliseconds{75} ); // Limit the example's work time. so_5::send_delayed< msg_finish >( *this, std::chrono::milliseconds{500} ); } public: void evt_finish( mhood_t< msg_finish > ) { std::cout << "Work coordinator is about to finish its work" << std::endl; // Register own coop. It should lead to stopping the child SOEnv. so_deregister_agent_coop_normally(); } }; } /* namespace example */ int main() { using namespace example; try { // Starting SObjectizer. so_5::launch( // A function for SO Environment initialization. []( so_5::environment_t & env ) { std::cout << "Initializing the parent SOEnv (addr=" << std::addressof(env) << ")" << std::endl; env.register_agent_as_coop( env.make_agent< a_work_coordinator_t >() ); } ); std::cout << "The parent SOEnv stopped" << std::endl; } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/chstate/000077500000000000000000000000001523065210100204075ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/chstate/CMakeLists.txt000066400000000000000000000005271523065210100231530ustar00rootroot00000000000000set(SAMPLE sample.so_5.chstate) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.chstate_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/chstate/main.cpp000066400000000000000000000121271523065210100220420ustar00rootroot00000000000000/* * A sample of the simpliest agent which has several states. * The agent uses different handlers for the same message. * At the beginning of its work agent initiates a periodic message. * Then agent handles this messages and switches from one state * to another. * * A work of the SObjectizer Environment is finished after the agent * switched to the final state. * * State switching is fixed by a state listener. */ #if defined( _MSC_VER ) #if defined( __clang__ ) #pragma clang diagnostic ignored "-Wreserved-id-macro" #endif #define _CRT_SECURE_NO_WARNINGS #endif #include #include // Main SObjectizer header file. #include // Periodic message. class msg_periodic final : public so_5::signal_t {}; // State listener for fixing state changes. class state_monitor_t final : public so_5::agent_state_listener_t { const std::string m_type_hint; public: state_monitor_t( const std::string & type_hint ) : m_type_hint( type_hint ) {} void changed( so_5::agent_t &, const so_5::state_t & state ) noexcept override { std::cout << m_type_hint << " agent changed state to " << state.query_name() << std::endl; } }; // A sample agent class. class a_state_swither_t final : public so_5::agent_t { // Agent states. const state_t st_1{ this, "state_1" }; const state_t st_2{ this, "state_2" }; const state_t st_3{ this, "state_3" }; const state_t st_shutdown{ this, "shutdown" }; public: a_state_swither_t( context_t ctx ) : so_5::agent_t{ ctx } {} // Definition of the agent for SObjectizer. void so_define_agent() override; // Reaction to start into SObjectizer. void so_evt_start() override; // Message handler for the default state. void evt_handler_default( mhood_t ); // Message handler for the state_1. void evt_handler_1( mhood_t ); // Message handler for the state_2. void evt_handler_2( mhood_t ); // Message handler for the state_3. void evt_handler_3( mhood_t ); // Message handler for the shutdown_state. void evt_handler_shutdown( mhood_t ); private: // Timer event id. // If we do not store it the periodic message will // be canceled automatically. so_5::timer_id_t m_timer_id; // Helper method for showing that event handler is called. void show_event_invocation( const char * event_name ); }; void a_state_swither_t::so_define_agent() { // Message subsription. so_subscribe_self() .event( &a_state_swither_t::evt_handler_default ); so_subscribe_self().in( st_1 ) .event( &a_state_swither_t::evt_handler_1 ); so_subscribe_self().in( st_2 ) .event( &a_state_swither_t::evt_handler_2 ); so_subscribe_self().in( st_3 ) .event( &a_state_swither_t::evt_handler_3 ); so_subscribe_self().in( st_shutdown ) .event( &a_state_swither_t::evt_handler_shutdown ); } void a_state_swither_t::so_evt_start() { show_event_invocation( "so_evt_start()" ); // Periodic message should be initiated. m_timer_id = so_5::send_periodic< msg_periodic >( *this, std::chrono::seconds( 1 ), std::chrono::seconds( 1 ) ); } void a_state_swither_t::evt_handler_default( mhood_t ) { show_event_invocation( "evt_handler_default" ); // Switching to the next state. so_change_state( st_1 ); } void a_state_swither_t::evt_handler_1( mhood_t ) { show_event_invocation( "evt_handler_1" ); // Switching to the next state. so_change_state( st_2 ); } void a_state_swither_t::evt_handler_2( mhood_t ) { show_event_invocation( "evt_handler_2" ); // Switching to the next state. so_change_state( st_3 ); } void a_state_swither_t::evt_handler_3( mhood_t ) { show_event_invocation( "evt_handler_3" ); // Switching to the next state. so_change_state( st_shutdown ); } void a_state_swither_t::evt_handler_shutdown( mhood_t ) { show_event_invocation( "evt_handler_3" ); // Switching to the default state. so_change_state( so_default_state() ); // Finishing SObjectizer's work. std::cout << "Stop sobjectizer..." << std::endl; so_environment().stop(); } void a_state_swither_t::show_event_invocation( const char * event_name ) { time_t t = time( nullptr ); std::cout << asctime( localtime( &t ) ) << event_name << ", state: " << so_current_state().query_name() << std::endl; } // A state listener. state_monitor_t g_state_monitor( "nondestroyable_listener" ); // The SObjectizer Environment initialization. void init( so_5::environment_t & env ) { auto ag = env.make_agent< a_state_swither_t >(); // Adding the state listener. Its lifetime is not controlled by the agent. ag->so_add_nondestroyable_listener( g_state_monitor ); // Adding another state listener. // Its lifetime is controlled by the agent. ag->so_add_destroyable_listener( std::make_unique< state_monitor_t >( "destroyable_listener" ) ); // Creating and registering a cooperation. env.register_agent_as_coop( std::move(ag) ); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/chstate_msg_tracing/000077500000000000000000000000001523065210100227645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/chstate_msg_tracing/CMakeLists.txt000066400000000000000000000010121523065210100255160ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.chstate_msg_tracing) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.chstate_msg_tracing_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/chstate_msg_tracing/main.cpp000066400000000000000000000067301523065210100244220ustar00rootroot00000000000000/* * A sample of the simpliest agent which has several states. * The agent uses different handlers for the same message. * At the beginning of its work agent initiates a periodic message. * Then agent handles this messages and switches from one state * to another. * * A work of the SObjectizer Environment is finished after the agent * switched to the final state. * * A message delivery tracing is enabled. Trace is going to std::cout. */ #include #include // Main SObjectizer header file. #include // A sample agent class. class a_state_swither_t final : public so_5::agent_t { // Signal for changing agent state. struct change_state_signal final : public so_5::signal_t {}; // Demo message for showing different handlers in different states. struct greeting_message { const std::string m_greeting; }; // Agent states. const state_t st_1{ this, "state_1" }; const state_t st_2{ this, "state_2" }; const state_t st_3{ this, "state_3" }; const state_t st_shutdown{ this, "shutdown" }; public: a_state_swither_t( context_t ctx ) : so_5::agent_t{ ctx } {} // Definition of the agent for SObjectizer. void so_define_agent() override { // Actions for the default state. so_default_state() .event( [this]( mhood_t< change_state_signal > ) { st_1.activate(); } ) .event( []( const greeting_message & msg ) { std::cout << "*** 0) greeting: " << msg.m_greeting << ", ptr: " << &msg << std::endl; } ); // Actions for other states. // st_1: switch to st_2 only, greeting_message is ignored. st_1 .event( [this]( mhood_t< change_state_signal > ) { st_2.activate(); } ); // st_2: switch to st_3, greeting_message is handled. st_2 .event( [this]( mhood_t< change_state_signal > ) { st_3.activate(); } ) .event( []( const greeting_message & msg ) { std::cout << "*** 2) greeting: " << msg.m_greeting << ", ptr: " << &msg << std::endl; } ); // st_3: switch to st_shutdown only, greeting_message is ignored. st_3 .event( [this]( mhood_t< change_state_signal > ) { st_shutdown.activate(); } ); // st_shutdown: handle greeting_message, shutdown environment. st_shutdown .event( [this]( mhood_t< change_state_signal > ) { so_deregister_agent_coop_normally(); } ) .event( []( const greeting_message & msg ) { std::cout << "*** F) greeting: " << msg.m_greeting << ", ptr: " << &msg << std::endl; } ); } // Reaction to start inside SObjectizer. void so_evt_start() override { m_greeting_timer_id = so_5::send_periodic< greeting_message >( *this, std::chrono::milliseconds{ 50 }, std::chrono::milliseconds{ 100 }, "Hello, World!" ); m_change_timer_id = so_5::send_periodic< change_state_signal >( *this, std::chrono::milliseconds{ 80 }, std::chrono::milliseconds{ 100 } ); } private: // Timer event id. // If we do not store it the periodic message will // be canceled automatically. so_5::timer_id_t m_greeting_timer_id; so_5::timer_id_t m_change_timer_id; }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_state_swither_t >(); } ); }, []( so_5::environment_params_t & params ) { // Turn message delivery tracing on. params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/coop_listener/000077500000000000000000000000001523065210100216215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/coop_listener/CMakeLists.txt000066400000000000000000000007761523065210100243730ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.coop_listener) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.coop_listener_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/coop_listener/main.cpp000066400000000000000000000027741523065210100232630ustar00rootroot00000000000000/* * A sample of the simpliest agent. */ #include // Main SObjectizer header. #include // An agent class definition. class a_hello_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; }; // The SObjectizer Environment initialization. void init( so_5::environment_t & env ) { // Creating and registering a cooperation. env.register_agent_as_coop( env.make_agent< a_hello_t >() ); // Stopping SObjectizer. env.stop(); } // A class for listening cooperation events. class coop_listener_impl_t : public so_5::coop_listener_t { public: // A reaction to the cooperation registration. void on_registered( so_5::environment_t &, const so_5::coop_handle_t & coop ) noexcept override { std::cout << "coop_listener: register coop '" << coop << "'\n"; } // A reaction to the cooperation deregistration. void on_deregistered( so_5::environment_t &, const so_5::coop_handle_t & coop, const so_5::coop_dereg_reason_t & reason ) noexcept override { std::cout << "coop_listener: deregister coop '" << coop << "', reason: " << reason.reason() << "\n"; } }; int main() { try { so_5::launch( &init, []( so_5::environment_params_t & p ) { // Adding a cooperation listener to show what happened // with the sample cooperation. p.coop_listener( std::make_unique< coop_listener_impl_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/coop_notification/000077500000000000000000000000001523065210100224625ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/coop_notification/CMakeLists.txt000066400000000000000000000010061523065210100252170ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.coop_notification) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.coop_notification_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/coop_notification/main.cpp000066400000000000000000000046101523065210100241130ustar00rootroot00000000000000/* * A sample for the exception handler and cooperation notifications. */ #include #include // Main SObjectizer header file. #include // A class of an agent which will throw an exception. class a_child_t final : public so_5::agent_t { public : a_child_t( context_t ctx, bool should_throw ) : so_5::agent_t{ ctx } , m_should_throw{ should_throw } {} void so_evt_start() override { if( m_should_throw ) throw std::runtime_error( "A child agent failure!" ); } private : const bool m_should_throw; }; // A class of parent agent. class a_parent_t final : public so_5::agent_t { public : a_parent_t( context_t ctx ) : so_5::agent_t{ ctx } , m_counter{ 0 } , m_max_counter{ 3 } {} void so_define_agent() override { so_default_state() .event( &a_parent_t::evt_child_created ) .event( &a_parent_t::evt_child_destroyed ); } void so_evt_start() override { register_child_coop(); } private : int m_counter; const int m_max_counter; void evt_child_created( const so_5::msg_coop_registered & evt ) { std::cout << "coop_reg: " << evt.m_coop << std::endl; if( m_counter >= m_max_counter ) so_deregister_agent_coop_normally(); // Otherwise should wait for cooperation shutdown. } void evt_child_destroyed( const so_5::msg_coop_deregistered & evt ) { std::cout << "coop_dereg: " << evt.m_coop << ", reason: " << evt.m_reason.reason() << std::endl; ++m_counter; register_child_coop(); } void register_child_coop() { using namespace so_5; introduce_child_coop( *this, [this]( coop_t & coop ) { coop.add_reg_notificator( make_coop_reg_notificator( so_direct_mbox() ) ); coop.add_dereg_notificator( make_coop_dereg_notificator( so_direct_mbox() ) ); coop.set_exception_reaction( deregister_coop_on_exception ); coop.make_agent< a_child_t >( m_counter < m_max_counter ); std::cout << "registering coop: " << coop.handle() << std::endl; } ); } }; int main() { try { so_5::launch( // The SObjectizer Environment initialization. []( so_5::environment_t & env ) { // Creating and registering a cooperation. env.register_agent_as_coop( env.make_agent< a_parent_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/coop_user_resources/000077500000000000000000000000001523065210100230445ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/coop_user_resources/CMakeLists.txt000066400000000000000000000010121523065210100255760ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.coop_user_resources) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.coop_user_resources_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/coop_user_resources/main.cpp000066400000000000000000000062071523065210100245010ustar00rootroot00000000000000/* * A sample for the usage of take_under_control method. */ #include #include #include // Main SObjectizer header file. #include // Logger sample class. // Object of that class will be used as user resource for cooperation. class logger_t { public : logger_t() { std::cout << "[log] -- logger created --" << std::endl; } ~logger_t() { std::cout << "[log] -- logger destroyed --" << std::endl; } void log( const std::string & what ) { std::cout << "[log] " << what << std::endl; } }; // A signal for parent cooperation about child work finish. struct msg_child_finished final : public so_5::signal_t {}; // A class of child agent. class a_child_t final : public so_5::agent_t { public : a_child_t( context_t ctx, std::string agent_name, so_5::mbox_t parent_mbox, logger_t & logger ) : so_5::agent_t( ctx ) , m_agent_name( std::move( agent_name ) ) , m_parent_mbox( std::move( parent_mbox ) ) , m_logger( logger ) { m_logger.log( m_agent_name + ": created" ); } ~a_child_t() override { m_logger.log( m_agent_name + ": destroyed" ); } void so_evt_start() override { m_logger.log( m_agent_name + ": finishing" ); so_5::send< msg_child_finished >( m_parent_mbox ); } private : const std::string m_agent_name; const so_5::mbox_t m_parent_mbox; logger_t & m_logger; }; // A class of parent agent. class a_parent_t final : public so_5::agent_t { public : a_parent_t( context_t ctx, logger_t & logger, size_t child_count ) : so_5::agent_t( ctx ) , m_logger( logger ) , m_child_count( child_count ) , m_child_finished( 0 ) { m_logger.log( "parent created" ); } ~a_parent_t() override { m_logger.log( "parent destroyed" ); } void so_define_agent() override { so_default_state().event( &a_parent_t::evt_child_finished ); } void so_evt_start() override { m_logger.log( "creating child cooperation..." ); register_child_coop(); m_logger.log( "child cooperation created" ); } void evt_child_finished( mhood_t< msg_child_finished > ) { m_logger.log( "child_finished notification received" ); ++m_child_finished; if( m_child_finished >= m_child_count ) { m_logger.log( "stopping so_environment..." ); so_deregister_agent_coop_normally(); } } private : logger_t & m_logger; const size_t m_child_count; size_t m_child_finished; void register_child_coop() { so_5::introduce_child_coop( *this, [this]( so_5::coop_t & coop ) { for( size_t i = 0; i != m_child_count; ++i ) coop.make_agent< a_child_t >( "a_child_" + std::to_string(i+1), so_direct_mbox(), m_logger ); } ); } }; // The SObjectizer Environment initialization. void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto logger = coop.take_under_control( std::make_unique< logger_t >() ); coop.make_agent< a_parent_t >( *logger, 2u ); } ); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/custom_direct_mbox/000077500000000000000000000000001523065210100226455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/custom_direct_mbox/CMakeLists.txt000066400000000000000000000010101523065210100253750ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.custom_direct_mbox) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.custom_direct_mbox_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/custom_direct_mbox/main.cpp000066400000000000000000000170201523065210100242750ustar00rootroot00000000000000/* * A sample of use custom direct mbox for an agent. */ #include // Main SObjectizer header files. #include // Messages to be used for producer-consumer interaction. struct msg_first final : public so_5::message_t { std::string m_payload; msg_first( std::string payload ) : m_payload{ std::move(payload) } {} }; struct msg_second final : public so_5::message_t { std::string m_payload; msg_second( std::string payload ) : m_payload{ std::move(payload) } {} }; // Consumer that receives and handles msg_first/msg_second. // NOTE: it's a final class, we can't inherit from it and change the // behavior in a derived class. class a_consumer_t final : public so_5::agent_t { public: a_consumer_t( context_t ctx, std::string name ) : so_5::agent_t{ std::move(ctx) } , m_name{ std::move(name) } {} void so_define_agent() override { so_subscribe_self() .event( &a_consumer_t::evt_first ) .event( &a_consumer_t::evt_second ) ; } private: const std::string m_name; void evt_first( mutable_mhood_t cmd ) { std::cout << m_name << " => msg_first arrived: " << cmd->m_payload << std::endl; } void evt_second( mutable_mhood_t cmd ) { std::cout << m_name << " => msg_second arrived: " << cmd->m_payload << std::endl; } }; // Producer that generates msg_first/msg_second messages and sends them // to the direct mbox of consumer agent. class a_producer_t final : public so_5::agent_t { // Signal to be used to finish the work. struct msg_quit final : public so_5::signal_t {}; public: a_producer_t( context_t ctx, const a_consumer_t & consumer ) : so_5::agent_t{ std::move(ctx) } , m_dest_mbox{ consumer.so_direct_mbox() } {} void so_define_agent() override { so_subscribe_self().event( &a_producer_t::evt_quit ); } void so_evt_start() override { so_5::send< so_5::mutable_msg >( m_dest_mbox, "Hello, " ); so_5::send< so_5::mutable_msg >( m_dest_mbox, "World!" ); so_5::send< msg_quit >( *this ); } private: const so_5::mbox_t m_dest_mbox; void evt_quit( mhood_t ) { std::cout << "--- Work completed ---" << std::endl; so_deregister_agent_coop_normally(); } }; // Runs SObjectizer and creates a coop with just two agents (producer and // original consumer). void run_normal_scenario() { std::cout << "*** Start of normal scenario ***" << std::endl; so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * consumer = coop.make_agent< a_consumer_t >( "normal" ); coop.make_agent< a_producer_t >( *consumer ); } ); } ); } // Agent that plays role of actual msg_second consumer. class a_actual_consumer_t final : public so_5::agent_t { public: a_actual_consumer_t( context_t ctx, std::string name ) : so_5::agent_t{ std::move(ctx) } , m_name{ std::move(name) } {} void so_define_agent() override { so_subscribe_self() .event( &a_actual_consumer_t::evt_second ) ; } private: const std::string m_name; void evt_second( mutable_mhood_t cmd ) { std::cout << m_name << " => msg_second arrived: " << cmd->m_payload << std::endl; } }; // Special mbox for intercepting msg_second message. class intercepting_mbox_t final : public so_5::abstract_message_box_t { // Source mbox to that all messages, except msg_second, should go. const so_5::mbox_t m_source; // Target mbox for msg_second messages. const so_5::mbox_t m_target; [[nodiscard]] static bool should_intercept( const std::type_index & msg_type ) { // NOTE: msg_second is sent as mutable_msg, so we have to check // type_index for mutable_msg, not for just msg_second. return msg_type == typeid(so_5::mutable_msg); } public: intercepting_mbox_t( so_5::mbox_t source, so_5::mbox_t target ) : m_source{ std::move(source) } , m_target{ std::move(target) } {} so_5::mbox_id_t id() const override { // This mbox has no own ID. return m_source->id(); } void subscribe_event_handler( const std::type_index & msg_type, so_5::abstract_message_sink_t & subscriber ) override { if( !should_intercept( msg_type ) ) m_source->subscribe_event_handler( msg_type, subscriber ); } void unsubscribe_event_handler( const std::type_index & msg_type, so_5::abstract_message_sink_t & subscriber ) noexcept override { if( !should_intercept( msg_type ) ) m_source->unsubscribe_event_handler( msg_type, subscriber ); } std::string query_name() const override { // This mbox has no own name. return m_source->query_name(); } so_5::mbox_type_t type() const override { // This mbox has no own type. return m_target->type(); } void do_deliver_message( so_5::message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const so_5::message_ref_t & message, unsigned int redirection_deep ) override { auto & dest = should_intercept( msg_type ) ? *m_target : *m_source; // Some tracing just for demo purposes. std::cout << "do_deliver_message for '" << msg_type.name() << "', should_intercept: " << should_intercept( msg_type ) << ", dest_id: " << dest.id() << std::endl; dest.do_deliver_message( delivery_mode, msg_type, message, redirection_deep ); } void set_delivery_filter( const std::type_index & msg_type, const so_5::delivery_filter_t & filter, so_5::abstract_message_sink_t & subscriber ) override { if( !should_intercept( msg_type ) ) m_source->set_delivery_filter( msg_type, filter, subscriber ); } void drop_delivery_filter( const std::type_index & msg_type, so_5::abstract_message_sink_t & subscriber ) noexcept override { if( !should_intercept( msg_type ) ) m_source->drop_delivery_filter( msg_type, subscriber ); } so_5::environment_t & environment() const noexcept override { return m_source->environment(); } }; // Runs SObjectizer and creates a coop with three agents: producer, // original consumer, and actual consumer for msg_second. // An intercepting mbox is used to stole msg_second message from // the original consumer and redirect it to the actual consumer. void run_intercepting_scenario() { std::cout << "*** Start of interception scenario ***" << std::endl; so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * actual_consumer = coop.make_agent< a_actual_consumer_t >( "actual" ); // A factory for creation of intercepting mbox. auto mbox_factory = [target_mbox = actual_consumer->so_direct_mbox()] ( so_5::partially_constructed_agent_ptr_t /*agent_ptr*/, so_5::mbox_t source_mbox ) -> so_5::mbox_t { return { std::make_unique< intercepting_mbox_t >( source_mbox, target_mbox ) }; }; // Original consumer has to be created manually because we // will prepare a context for it by outselves. auto * original_consumer = coop.add_agent( std::make_unique< a_consumer_t >( so_5::agent_context_t( coop.environment() ) + so_5::agent_t::custom_direct_mbox_factory( mbox_factory ), "original" ) ); // Now we can create producer and pass a reference to the // original consumer to it. coop.make_agent< a_producer_t >( *original_consumer ); } ); } ); } int main() { try { // First run: normal scenario with producer and consumer only. run_normal_scenario(); // Second run: interception of msg_second. run_intercepting_scenario(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/custom_error_logger/000077500000000000000000000000001523065210100230365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/custom_error_logger/CMakeLists.txt000066400000000000000000000010121523065210100255700ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.custom_error_logger) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.custom_error_logger_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/custom_error_logger/main.cpp000066400000000000000000000040041523065210100244640ustar00rootroot00000000000000/* * A sample for the custom error logger. */ #include #include // Main SObjectizer header file. #include // A class that uses limit_then_redirect incorrectly. // Message is redirected too many times and this lead to error message. class actor_t final : public so_5::agent_t { struct hello final : public so_5::signal_t {}; struct bye final : public so_5::signal_t {}; public : actor_t( context_t ctx ) : so_5::agent_t{ ctx // There is a mistake: message is redirected to the same mbox. + limit_then_redirect< hello >( 1, [this]{ return so_direct_mbox(); } ) + limit_then_abort< bye >( 1 ) } {} void so_define_agent() override { so_subscribe_self() .event( []( mhood_t ) { std::cout << "Hello!" << std::endl; } ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send< hello >( *this ); so_5::send< hello >( *this ); // This message will be redirected. so_5::send< bye >( *this ); } }; // Custom error logger. class custom_logger_t final : public so_5::error_logger_t { public : custom_logger_t() {} void log( const char * file_name, unsigned int line, const std::string & message ) override { std::clog << "############################################################\n" << file_name << "(" << line << "): error: " << message << "\n" "############################################################" << std::endl; } }; int main() { try { so_5::launch( // The SObjectizer Environment initialization. []( so_5::environment_t & env ) { // Creating and registering a cooperation. env.register_agent_as_coop( env.make_agent< actor_t >() ); }, // Parameters for SObjectizer Environment. []( so_5::environment_params_t & params ) { params.error_logger( std::make_shared< custom_logger_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/custom_work_thread_factory/000077500000000000000000000000001523065210100244065ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/custom_work_thread_factory/CMakeLists.txt000066400000000000000000000005751523065210100271550ustar00rootroot00000000000000set(SAMPLE sample.so_5.custom_work_thread_factory) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.custom_work_thread_factory_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/custom_work_thread_factory/main.cpp000066400000000000000000000335041523065210100260430ustar00rootroot00000000000000/* * An example of the use of custom worker thread factories. */ #include #include #include #include using namespace std::chrono_literals; // Implementation of custom worker thread. // It can be reused, so the implementation has to be prepared // for multiple calls to start()/join() methods. class worker_thread final : public so_5::disp::abstract_work_thread_t { // Possible statuses of the worker thread. enum class status_t { // No actual thread body, has to wait. wait_thread_body, // Actual thread body received but execution isn't started yet. thread_body_received, // The execution of thread body has been started but not completed yet. thread_body_accepted, // Shutdown operation started. shutdown_initiated }; // ID of worker thread. std::thread::id m_id; // Actual thread body to be executed. // It has non-empty value only when the status is thread_body_received. body_func_t m_thread_body; // The current status. status_t m_status{ status_t::wait_thread_body }; // A lock for protecting the content of the object. std::mutex m_lock; // A condition variable for waiting changes of m_status. std::condition_variable m_status_cv; // Actual worker thread. std::thread m_thread; public: worker_thread() { std::promise< std::thread::id > id_promise; auto id_future = id_promise.get_future(); m_thread = std::thread{ [this, id_p = std::move(id_promise)]() mutable { // The first step: has to specify ID of this thread. id_p.set_value( std::this_thread::get_id() ); // The second step: run the main loop. body(); } }; m_id = id_future.get(); } ~worker_thread() noexcept override { // Actual thread should receive shutdown notification. { std::lock_guard< std::mutex > lock{ m_lock }; m_status = status_t::shutdown_initiated; m_status_cv.notify_all(); } // Actual thread has to be joined. m_thread.join(); } [[nodiscard]] std::thread::id id() const noexcept { return m_id; } void start( body_func_t thread_body ) override { std::lock_guard< std::mutex > lock{ m_lock }; if( status_t::wait_thread_body == m_status ) { m_thread_body = std::move(thread_body); m_status = status_t::thread_body_received; m_status_cv.notify_one(); } else { throw std::runtime_error{ "Unable to start execution " "of thread_body when worker thread status isn't " "wait_thread_body: " + std::to_string( static_cast(m_status) ) }; } } void join() override { std::unique_lock< std::mutex > lock{ m_lock }; // If thread_body isn't completed yet then we have to wait. while( status_t::thread_body_accepted == m_status ) m_status_cv.wait( lock ); } private: void body() { for(;;) { auto body_to_execute = wait_body_to_execute(); if( body_to_execute ) { body_to_execute(); } else break; } } // Returns empty value if work has to be finished. [[nodiscard]] body_func_t wait_body_to_execute() { body_func_t result; std::unique_lock< std::mutex > lock{ m_lock }; // If it's a repeated call then thread_body_accepted status // has to be changed to wait_thread_body. if( status_t::thread_body_accepted == m_status ) { m_status = status_t::wait_thread_body; // Wakeup 'join()' call if it is here. m_status_cv.notify_one(); } // Do status checking loop until we receive a new body to execute // or shutdown notification. for(;;) { if( status_t::shutdown_initiated == m_status ) break; // Work has to be finished. else if( status_t::thread_body_received == m_status ) { result = std::move(m_thread_body); m_status = status_t::thread_body_accepted; break; } else { m_status = status_t::wait_thread_body; m_status_cv.wait( lock ); } } return result; } }; // Implementation of custom worker thread factory. class thread_factory final : public so_5::disp::abstract_work_thread_factory_t { // Alias for unique_ptr to worker_thread. using thread_unique_ptr = std::unique_ptr< worker_thread >; // Name of the pool. const std::string m_name; // Holder for free threads. std::vector< thread_unique_ptr > m_free_threads; public: thread_factory( std::string pool_name, std::size_t pool_size ) : m_name{ std::move(pool_name) } { m_free_threads.reserve( pool_size ); std::generate_n( std::back_inserter(m_free_threads), pool_size, []() { return std::make_unique< worker_thread >(); } ); } [[nodiscard]] so_5::disp::abstract_work_thread_t & acquire( so_5::environment_t & /*env*/ ) override { if( m_free_threads.empty() ) throw std::runtime_error{ m_name + ": no free threads in the pool" }; thread_unique_ptr thread_holder{ std::move(m_free_threads.back()) }; m_free_threads.pop_back(); std::cout << "*** " << m_name << ": thread acquired, " << "id=" << thread_holder->id() << ", ptr=" << thread_holder.get() << std::endl; return *(thread_holder.release()); } void release( so_5::disp::abstract_work_thread_t & thread ) noexcept override { // If we receive a reference to some different type then // dynamic_cast will throw and that terminates the application. thread_unique_ptr worker{ &( dynamic_cast(thread) ) }; std::cout << "*** " << m_name << ": thread released, " << "id=" << worker->id() << ", ptr=" << worker.get() << std::endl; // Released thread has to be returned to the pool. m_free_threads.push_back( std::move(worker) ); } }; // Global signal for finishing the work. struct shutdown final : public so_5::signal_t { [[nodiscard]] static so_5::mbox_t mbox( so_5::environment_t & env ) { return env.create_mbox( "shutdown" ); } }; // Logging infrastructure. struct trace_msg { const so_5::agent_t * m_who; std::string m_what; std::thread::id m_thread_id; }; void create_logger( so_5::environment_t & env ) { class logger_actor final : public so_5::agent_t { public: logger_actor( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe( so_environment().create_mbox( "log" ) ) .event( [](mhood_t cmd ) { std::cout << cmd->m_thread_id << ": (" << cmd->m_who << ") " << cmd->m_what << std::endl; } ); so_subscribe( shutdown::mbox( so_environment() ) ) .event( [this]( mhood_t ) { // Delay the actual shutdown. so_5::send_delayed< shutdown >( *this, 500ms ); } ); so_subscribe_self() .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< logger_actor >(); } ); } void trace( const so_5::agent_t & agent, std::string what ) { so_5::send< trace_msg >( agent.so_environment().create_mbox( "log" ), &agent, std::move(what), std::this_thread::get_id() ); } // // The main part of the example. // // Worker agent to be used on the context of thread_pool dispatcher. class pool_worker final : public so_5::agent_t { const std::string m_name; public: pool_worker( context_t ctx, std::string name ) : so_5::agent_t{ std::move(ctx) } , m_name{ std::move(name) } {} void so_evt_start() override { trace( *this, m_name + " started" ); // Need to lock the current thread for some time. std::this_thread::sleep_for( 25ms ); } void so_evt_finish() override { trace( *this, m_name + " finished" ); } }; // Manager agent that periodically creates a bunch of pool_workers and runs // them on a separate thread_pool dispatcher. class pool_manager final : public so_5::agent_t { // Signal for creation of a new child coop. struct create_child final : public so_5::signal_t {}; // Message for destruction of the existing child coop. struct destroy_child final : public so_5::message_t { so_5::coop_handle_t m_child; destroy_child( so_5::coop_handle_t child ) : m_child{ std::move(child) } {} }; // Thread factory to be used for dispatcher of new cooperations. std::shared_ptr< thread_factory > m_thread_pool; // Counter for generations of children coops. int m_generation_counter{}; public: pool_manager( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_thread_pool{ std::make_shared< thread_factory >( "pool_manager's factory", 4u ) } {} void so_define_agent() override { so_subscribe_self() .event( &pool_manager::evt_create_child ) .event( &pool_manager::evt_destroy_child ) ; so_subscribe( shutdown::mbox( so_environment() ) ) .event( &pool_manager::evt_shutdown ) ; } void so_evt_start() override { trace( *this, "pool_manager started" ); // Initiate child creation/destruction loop. so_5::send< create_child >( *this ); } void so_evt_finish() override { trace( *this, "pool_manager finished" ); } private: void evt_create_child( mhood_t< create_child > ) { // A new dispatcher for new child coop. auto disp = so_5::disp::thread_pool::make_dispatcher( so_environment(), "child_pool", so_5::disp::thread_pool::disp_params_t{} .thread_count( 4 ) // Custom worker thread factory for this dispatcher. .work_thread_factory( m_thread_pool ) ); auto coop_holder = so_environment().make_coop( // It will be a child for the coop of pool_manager agent. so_coop(), // It will use the created thread_pool dispatcher as the // default dispatcher for all agents. disp.binder( so_5::disp::thread_pool::bind_params_t{} // Every agent will be a separate entity with // own event queue. .fifo( so_5::disp::thread_pool::fifo_t::individual ) ) ); // Fill the coop with agents. for( int i = 0; i < 7; ++i ) coop_holder->make_agent< pool_worker >( "pool_worker_" + std::to_string( m_generation_counter ) + "_" + std::to_string( i ) ); // Now new coop can be registered. auto new_coop_handle = so_environment().register_coop( std::move(coop_holder) ); // Initiate the destruction of the new coop. so_5::send_delayed< destroy_child >( *this, 2s, std::move(new_coop_handle) ); // New generation will be created on the next iteration. ++m_generation_counter; } void evt_destroy_child( mhood_t< destroy_child > cmd ) { // The current child coop has to be deregistered. so_environment().deregister_coop( cmd->m_child, so_5::dereg_reason::normal ); // Initiate the construction of a new child. so_5::send_delayed< create_child >( *this, 1s ); } void evt_shutdown( mhood_t< shutdown > ) { so_deregister_agent_coop_normally(); } }; // Worker agent to be used on an active_obj dispatcher. class separate_worker final : public so_5::agent_t { // Signal for finishing the work. struct quit final : public so_5::signal_t {}; const std::string m_name; public: separate_worker( context_t ctx, std::string name ) : so_5::agent_t{ std::move(ctx) } , m_name{ std::move(name) } {} void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); so_subscribe( shutdown::mbox( so_environment() ) ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { trace( *this, m_name + ": started" ); so_5::send_delayed< quit >( *this, 5s ); } void so_evt_finish() override { trace( *this, m_name + ": finished" ); } }; // Manager agent that periodically creates new separate workers and runs them // on an active_obj dispatcher. class separate_worker_manager final : public so_5::agent_t { // Signal for the creation of a new worker. struct create_new_worker final : public so_5::signal_t {}; // Counter of workers. int m_workers_counter{}; // Instance of active_obj dispatcher to be used. so_5::disp::active_obj::dispatcher_handle_t m_active_obj_disp; public: separate_worker_manager( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_active_obj_disp{ // Do not specify work thread factory, the default one will be used. so_5::disp::active_obj::make_dispatcher( so_environment() ) } {} void so_define_agent() override { so_subscribe_self() .event( &separate_worker_manager::evt_create_new_worker ) ; so_subscribe( shutdown::mbox( so_environment() ) ) .event( &separate_worker_manager::evt_shutdown ) ; } void so_evt_start() override { trace( *this, "separate_worker_manager: started" ); so_5::send< create_new_worker >( *this ); } void so_evt_finish() override { trace( *this, "separate_worker_manager: finished" ); } private: void evt_create_new_worker( mhood_t ) { // We expect exceptions here because global work thread factory // uses a size-limited thread pool. try { so_environment().introduce_coop( m_active_obj_disp.binder(), [this]( so_5::coop_t & coop ) { coop.make_agent< separate_worker >( "separate_worker_" + std::to_string( ++m_workers_counter ) ); } ); } catch( const std::exception & x ) { trace( *this, std::string{ "separate_worker_manager: exception=" } + x.what() ); } so_5::send_delayed< create_new_worker >( *this, 2s ); } void evt_shutdown( mhood_t ) { so_deregister_agent_coop_normally(); } }; void run_example() { so_5::launch( []( so_5::environment_t & env ) { // We need logger. create_logger( env ); // Managers. env.register_agent_as_coop( env.make_agent< pool_manager >() ); env.register_agent_as_coop( env.make_agent< separate_worker_manager >() ); // Allow to work for some time... std::this_thread::sleep_for( 20s ); // ...and finish the example. so_5::send< shutdown >( shutdown::mbox( env ) ); }, []( so_5::environment_params_t & params ) { // Specify global work thread factory for the whole environment. params.work_thread_factory( std::make_shared< thread_factory >( "global factory", 3u ) ); } ); } int main() { try { run_example(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/deadletter_handler/000077500000000000000000000000001523065210100225665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/deadletter_handler/CMakeLists.txt000066400000000000000000000005551523065210100253330ustar00rootroot00000000000000set(SAMPLE sample.so_5.deadletter_handler) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.deadletter_handler_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/deadletter_handler/main.cpp000066400000000000000000000022721523065210100242210ustar00rootroot00000000000000#include // Demo agent which shows deadletter handler. class demo final : public so_5::agent_t { // We need two signals. One will be handler by ordinary handler... struct first_signal final : public so_5::signal_t {}; // ...and second by deadletter handler. struct second_signal final : public so_5::signal_t {}; public: demo(context_t ctx) : so_5::agent_t(std::move(ctx)) { // Create subscriptions. // The first signal will be handled as usual. so_subscribe_self().event([](mhood_t) { std::cout << "first_signal: ordinary handler" << std::endl; }); // A deadletter handler will be used for the second. so_subscribe_deadletter_handler( so_direct_mbox(), [](mhood_t) { std::cout << "second_signal: deadletter handler" << std::endl; }); } void so_evt_start() override { // Send two different signal. so_5::send(*this); so_5::send(*this); // Agent is no more needed. so_deregister_agent_coop_normally(); } }; int main() { // Launch the SObjectizer with just one agent inside. so_5::launch([](so_5::environment_t & env) { env.register_agent_as_coop(env.make_agent()); }); } sobjectizer-5.8.6/dev/sample/so_5/default_message_limits/000077500000000000000000000000001523065210100234655ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/default_message_limits/CMakeLists.txt000066400000000000000000000005661523065210100262340ustar00rootroot00000000000000set(SAMPLE sample.so_5.default_message_limits) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.default_message_limits_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/default_message_limits/main.cpp000066400000000000000000000150671523065210100251260ustar00rootroot00000000000000/* * An example of using default message limits. */ #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; // A base part for every request. struct request_base { // Request ID. int m_id; request_base( int id ) : m_id{ id } {} }; // Messages for different request types. struct req_A final : request_base {}; struct req_B final : request_base {}; struct req_C final : request_base {}; struct req_D final : request_base {}; struct req_E final : request_base {}; struct req_F final : request_base {}; struct req_G final : request_base {}; struct req_I final : request_base {}; // Message for logger. struct log_message { // Text to be logged. std::string m_what; }; // Logger agent. class a_logger_t final : public so_5::agent_t { public : a_logger_t( context_t ctx ) : so_5::agent_t( ctx // Limit the count of messages. // Because we can't lost log messages the overlimit // must lead to application crash. + limit_then_abort< log_message >( 100 ) ) , m_started_at( std::chrono::steady_clock::now() ) {} void so_define_agent() override { so_default_state().event( [this]( const log_message & evt ) { std::cout << "[+" << time_delta() << "] -- " << evt.m_what << std::endl; } ); } private : const std::chrono::steady_clock::time_point m_started_at; std::string time_delta() const { auto now = std::chrono::steady_clock::now(); std::ostringstream ss; ss << double( std::chrono::duration_cast< std::chrono::milliseconds >( now - m_started_at ).count() ) / 1000.0 << "ms"; return ss.str(); } }; // An agent that plays a role of "trash can" for rejected requests. class a_trash_can_t final : public so_5::agent_t { const so_5::mbox_t m_logger; template< typename Request > [[nodiscard]] auto make_event_handler( std::string name ) { return [this, req_name=std::move(name)]( mhood_t cmd ) { so_5::send< log_message >( m_logger, req_name + ": redirected to trash can, id=" + std::to_string(cmd->m_id) ); }; } public : a_trash_can_t( context_t ctx, so_5::mbox_t logger ) : so_5::agent_t{ ctx // Hold no more than 10 instances of any message type. + limit_then_drop< any_unspecified_message >( 10u ) } , m_logger{ std::move(logger) } {} void so_define_agent() override { so_default_state() .event( make_event_handler< req_A >( "req_A" ) ) .event( make_event_handler< req_B >( "req_B" ) ) .event( make_event_handler< req_C >( "req_C" ) ) .event( make_event_handler< req_D >( "req_D" ) ) .event( make_event_handler< req_E >( "req_E" ) ) .event( make_event_handler< req_F >( "req_F" ) ) .event( make_event_handler< req_G >( "req_G" ) ) .event( make_event_handler< req_I >( "req_I" ) ) ; } }; // An agent that does "normal" processing of incoming messages. class a_processor_t final : public so_5::agent_t { const so_5::mbox_t m_logger; template< typename Request > [[nodiscard]] auto make_request_handler( std::string name, std::chrono::milliseconds duration ) { return [this, req_name=std::move(name), duration] ( mhood_t< Request > cmd ) { so_5::send< log_message >( m_logger, "processing request " + req_name + " (" + std::to_string( cmd->m_id ) + ") for " + std::to_string( duration.count() ) + "ms" ); // Imitation of some intensive processing. std::this_thread::sleep_for( std::chrono::milliseconds( duration ) ); }; } public : struct shutdown final : public so_5::signal_t {}; a_processor_t( context_t ctx, so_5::mbox_t logger, so_5::mbox_t trash_can ) : so_5::agent_t{ ctx // Set the personal limits for several of messages. + limit_then_redirect< req_A >( 10u, trash_can ) + limit_then_redirect< req_C >( 8u, trash_can ) + limit_then_abort< shutdown >( 1u ) // All other messages will have the same limit and reaction. + limit_then_redirect< any_unspecified_message >( 4u, trash_can ) } , m_logger( std::move( logger ) ) {} void so_define_agent() override { so_default_state() .event( make_request_handler< req_A >( "req_A", 1ms ) ) .event( make_request_handler< req_B >( "req_B", 15ms ) ) .event( make_request_handler< req_C >( "req_C", 2ms ) ) .event( make_request_handler< req_D >( "req_D", 18ms ) ) .event( make_request_handler< req_E >( "req_E", 22ms ) ) .event( make_request_handler< req_F >( "req_F", 19ms ) ) .event( make_request_handler< req_G >( "req_G", 24ms ) ) .event( make_request_handler< req_I >( "req_I", 23ms ) ) .event( [this]( mhood_t< shutdown > ) { so_deregister_agent_coop_normally(); } ) ; } }; using sender_t = void(*)( const so_5::mbox_t & to, int id ); template< typename Request > [[nodiscard]] sender_t make_sender() { return []( const so_5::mbox_t & to, int id ) { so_5::send< Request >( to, id ); }; } void init( so_5::environment_t & env ) { const so_5::mbox_t processor_mbox = env.introduce_coop( [&env]( so_5::coop_t & coop ) { // Logger will work on the default dispatcher. auto logger = coop.make_agent< a_logger_t >(); // Trash can will work on its own dispatcher. auto trash_can = coop.make_agent_with_binder< a_trash_can_t >( so_5::disp::one_thread::make_dispatcher( env ).binder(), logger->so_direct_mbox() ); // Processor will work on its own dispatcher. auto processor = coop.make_agent_with_binder< a_processor_t >( so_5::disp::one_thread::make_dispatcher( env ).binder(), logger->so_direct_mbox(), trash_can->so_direct_mbox() ); return processor->so_direct_mbox(); }); // Senders for messages of various types. std::array< sender_t, 10 > senders{ make_sender< req_A >(), make_sender< req_B >(), make_sender< req_C >(), make_sender< req_D >(), make_sender< req_E >(), make_sender< req_F >(), make_sender< req_G >(), make_sender< req_I >(), make_sender< req_A >(), make_sender< req_E >() }; // Shuffle senders. { std::random_device rd; std::mt19937 generator{ rd() }; std::shuffle( std::begin(senders), std::end(senders), generator ); } // Do several interations of sending messages. int id = 0; for( int i = 0; i < 15; ++i ) { for( const auto & s : senders ) s( processor_mbox, id++ ); std::this_thread::sleep_for( 13ms ); } // Finish the example. so_5::send< a_processor_t::shutdown >( processor_mbox ); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/delivery_filters/000077500000000000000000000000001523065210100223275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/delivery_filters/CMakeLists.txt000066400000000000000000000010051523065210100250630ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.delivery_filters) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.delivery_filters_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/delivery_filters/main.cpp000066400000000000000000000067431523065210100237710ustar00rootroot00000000000000/* * An example of usage of delivery filter. */ #include #include using namespace std; using namespace so_5; // Message to be filtered. struct msg_sample { int m_key; string m_value; }; // A signal for doing second part of example. struct msg_second_part : public signal_t {}; // A signal for finish the example. struct msg_shutdown : public signal_t {}; // Main example agent. // Ordinary agent is necessary because a delivery filter can be set // only by ordinary agent. class a_example_t final : public agent_t { public : a_example_t( context_t ctx ) : agent_t( ctx ) , m_mbox( so_environment().create_mbox() ) {} void so_define_agent() override { so_subscribe_self() .event( &a_example_t::evt_second_part ) .event( [this](mhood_t< msg_shutdown >) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { // Subscribe for the message. Without filter. so_subscribe( m_mbox ).event( []( const msg_sample & evt ) { cout << "[first]: " << evt.m_key << "-" << evt.m_value << endl; } ); // Sending several messages... // All of them will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "only-subscription" ); send< msg_sample >( m_mbox, 1, "only-subscription" ); // Setting a delivery filter for message. so_set_delivery_filter( m_mbox, []( const msg_sample & evt ) { return 1 == evt.m_key; } ); // Sending several messages... // Only one will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "subscription-and-filter" ); send< msg_sample >( m_mbox, 1, "subscription-and-filter" ); // Take time for processing already queued messages. send< msg_second_part >( *this ); } void evt_second_part(mhood_t< msg_second_part >) { // Drop the subscription. so_drop_subscription< msg_sample >( m_mbox ); // Sending several messages... // No one of them will be stored to the agent's queue nor handled. send< msg_sample >( m_mbox, 0, "only-filter" ); send< msg_sample >( m_mbox, 1, "only-filter" ); // Subscribe for the message again. so_subscribe( m_mbox ).event( []( const msg_sample & evt ) { cout << "[second]: " << evt.m_key << "-" << evt.m_value << endl; } ); // Sending several messages... // Only one will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "subscription-and-filter-2" ); send< msg_sample >( m_mbox, 1, "subscription-and-filter-2" ); // Changing the filter to new one. so_set_delivery_filter( m_mbox, []( const msg_sample & evt ) { return 0 == evt.m_key; } ); // Sending several messages... // Only one will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "subscription-and-filter-3" ); send< msg_sample >( m_mbox, 1, "subscription-and-filter-3" ); // Dropping the filter. so_drop_delivery_filter< msg_sample >( m_mbox ); // Sending several messages... // All of them will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "only-subscription-2" ); send< msg_sample >( m_mbox, 1, "only-subscription-2" ); // Example could be finished. send< msg_shutdown >( *this ); } private : // Separate MPMC-mbox is necessary for delivery filter. const mbox_t m_mbox; }; int main() { try { so_5::launch( []( environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_example_t >() ); } ); return 0; } catch( const std::exception & x ) { cerr << "Exception: " << x.what() << endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/disp/000077500000000000000000000000001523065210100177135ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/disp/CMakeLists.txt000066400000000000000000000005211523065210100224510ustar00rootroot00000000000000set(SAMPLE sample.so_5.disp) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.disp_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/disp/main.cpp000066400000000000000000000064131523065210100213470ustar00rootroot00000000000000/* * A sample of working with dispatchers. */ #include #include // Main SObjectizer header file. #include // A class for an agent. class a_disp_user_t final : public so_5::agent_t { public: a_disp_user_t( context_t ctx, std::string name ) : so_5::agent_t( ctx ) , m_name( std::move(name) ) {} // A reaction to start of work in SObjectizer. void so_evt_start() override { SO_5_LOG_ERROR( so_environment(), log_stream ) { log_stream << m_name << ".so_evt_start(): start pause"; } // Sleeping for some time. std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); SO_5_LOG_ERROR( so_environment(), log_stream ) { log_stream << m_name << ".so_evt_start(): finish pause"; } } // A reaction to finish of work in SObjectizer. void so_evt_finish() override { SO_5_LOG_ERROR( so_environment(), log_stream ) { log_stream << m_name << ".so_evt_finish(): start pause"; } // Sleeping for some time. std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); SO_5_LOG_ERROR( so_environment(), log_stream ) { log_stream << m_name << ".so_evt_finish(): finish pause"; } } private: const std::string m_name; }; // The helper function for making name of an agent. std::string create_agent_name( const std::string & base, int i ) { return base + "_" + std::to_string(i); } // The SObjectizer Environment initialization. void init( so_5::environment_t & env ) { // Creating and registering a cooperation. env.introduce_coop( []( so_5::coop_t & coop ) { // Adding agents which will work on the default dispatcher. for( int i = 0; i < 4; ++i ) { coop.make_agent< a_disp_user_t >( create_agent_name( "default_disp", i+1 ) ); } // Adding agents which will work on the dispatcher // with name 'single_thread'. { auto disp = so_5::disp::one_thread::make_dispatcher( coop.environment(), "single_thread" ); for( int i = 0; i < 3; ++i ) { coop.make_agent_with_binder< a_disp_user_t >( disp.binder(), create_agent_name( "single_thread", i+1 ) ); } } // Adding agents which will work on the dispatcher with active groups // named as 'active_group'. { auto disp = so_5::disp::active_group::make_dispatcher( coop.environment(), "active_group" ); // Agents will be bound to a group 'A'. for( int i = 0; i < 2; ++i ) { coop.make_agent_with_binder< a_disp_user_t >( disp.binder( "A" ), create_agent_name( "active_group_A", i+1 ) ); } // Agents will be bound to a group 'B'. for( int i = 0; i < 2; ++i ) { coop.make_agent_with_binder< a_disp_user_t >( disp.binder( "B" ), create_agent_name( "active_group_B", i+1 ) ); } } // Adding agents which will work on the dispatcher for active objects. // This dispatcher will have name 'active_obj'. { auto disp = so_5::disp::active_obj::make_dispatcher( coop.environment(), "active_obj" ); for( int i = 0; i < 4; ++i ) { coop.make_agent_with_binder< a_disp_user_t >( disp.binder(), create_agent_name( "active_obj", i+1 ) ); } } }); // Stopping SObjectizer. env.stop(); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/dispatcher_for_children/000077500000000000000000000000001523065210100236205ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/dispatcher_for_children/CMakeLists.txt000066400000000000000000000010221523065210100263530ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.dispatcher_for_children) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.dispatcher_for_children_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/dispatcher_for_children/main.cpp000066400000000000000000000065521523065210100252600ustar00rootroot00000000000000/* * Demonstration of usage of the same dispatcher for children * cooperations. */ #include #include #include // Child agent. // Sends a hello message to the coordinator at start. // Then if creation of another child cooperation is enabled creates // child cooperation. class a_child_t final : public so_5::agent_t { public : a_child_t( context_t ctx, // Mbox of the coordinator. so_5::mbox_t coordinator, // Dispatcher to be used for child. so_5::disp::active_obj::dispatcher_handle_t dispatcher, // Current generation. int generation, // Max generation. int max_generation ) : so_5::agent_t( ctx ) , m_coordinator( std::move( coordinator ) ) , m_dispatcher( std::move( dispatcher ) ) , m_generation( generation ) , m_max_generation( max_generation ) {} void so_evt_start() override { send_hello_to_coordinator(); if( m_generation < m_max_generation ) create_child_coop(); } private : const so_5::mbox_t m_coordinator; so_5::disp::active_obj::dispatcher_handle_t m_dispatcher; const int m_generation; const int m_max_generation; void send_hello_to_coordinator() { // Hello message must contain agent name and thread id. std::ostringstream ss; ss << "child at generation " << m_generation << " on thread: " << std::this_thread::get_id(); // Coordinator should receive a hello message. so_5::send< std::string >( m_coordinator, ss.str() ); } void create_child_coop() { so_5::introduce_child_coop( *this, // The same dispatcher will be used for child cooperation. m_dispatcher.binder(), [this]( so_5::coop_t & coop ) { coop.make_agent< a_child_t >( m_coordinator, m_dispatcher, m_generation + 1, m_max_generation ); } ); } }; // Sample coordinator. // Works on the default dispatcher. // Receives messages from children agents. // Finishes sample when all messages are received. class a_coordinator_t final : public so_5::agent_t { public : a_coordinator_t( context_t ctx ) : so_5::agent_t( ctx ) {} void so_define_agent() override { // Just one message must be handled in the default agent state. so_default_state().event( [this]( const std::string & msg ) { std::cout << "hello: " << msg << std::endl; // Work must be stopped if all messages are received. if( 0 == (--m_remaining_messages) ) so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { // Cooperation must be created at the start of agent's work. create_first_child_coop(); } private : unsigned int m_remaining_messages = 6; void create_first_child_coop() { // The dispatcher for the family of child cooperations. auto disp = so_5::disp::active_obj::make_dispatcher( so_environment() ); so_5::introduce_child_coop( // This agent will be parent for new cooperation. *this, // The main dispatcher for the new cooperation. disp.binder(), [&]( so_5::coop_t & coop ) { coop.make_agent< a_child_t >( so_direct_mbox(), disp, 1, 6 ); } ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { // Coordinator agent will work on the default dispatcher. env.register_agent_as_coop( env.make_agent< a_coordinator_t >() ); } ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/dispatcher_for_children_2/000077500000000000000000000000001523065210100240415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/dispatcher_for_children_2/CMakeLists.txt000066400000000000000000000010261523065210100266000ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.dispatcher_for_children_2) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.dispatcher_for_children_2_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/dispatcher_for_children_2/main.cpp000066400000000000000000000062461523065210100255010ustar00rootroot00000000000000/* * Demonstration of usage of the same dispatcher for children * cooperations. The so_5::agent_t::so_this_coop_disp_binder() is * used for this purpose. */ #include #include #include // Child agent. // Sends a hello message to the coordinator at start. // Then if creation of another child cooperation is enabled creates // child cooperation. class a_child_t final : public so_5::agent_t { public : a_child_t( context_t ctx, // Mbox of the coordinator. so_5::mbox_t coordinator, // Current generation. int generation, // Max generation. int max_generation ) : so_5::agent_t( ctx ) , m_coordinator( std::move( coordinator ) ) , m_generation( generation ) , m_max_generation( max_generation ) {} void so_evt_start() override { send_hello_to_coordinator(); if( m_generation < m_max_generation ) create_child_coop(); } private : const so_5::mbox_t m_coordinator; const int m_generation; const int m_max_generation; void send_hello_to_coordinator() { // Hello message must contain agent name and thread id. std::ostringstream ss; ss << "child at generation " << m_generation << " on thread: " << std::this_thread::get_id(); // Coordinator should receive a hello message. so_5::send< std::string >( m_coordinator, ss.str() ); } void create_child_coop() { so_5::introduce_child_coop( *this, // The same dispatcher will be used for child cooperation. so_this_coop_disp_binder(), [this]( so_5::coop_t & coop ) { coop.make_agent< a_child_t >( m_coordinator, m_generation + 1, m_max_generation ); } ); } }; // Sample coordinator. // Receives messages from children agents. // Finishes sample when all messages are received. class a_coordinator_t final : public so_5::agent_t { public : a_coordinator_t( context_t ctx ) : so_5::agent_t( ctx ) {} void so_define_agent() override { // Just one message must be handled in the default agent state. so_default_state().event( [this]( const std::string & msg ) { std::cout << "hello: " << msg << std::endl; // Work must be stopped if all messages are received. if( 0 == (--m_remaining_messages) ) so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { // Cooperation must be created at the start of agent's work. create_first_child_coop(); } private : unsigned int m_remaining_messages = 6; void create_first_child_coop() { so_5::introduce_child_coop( // This agent will be parent for new cooperation. *this, // Use the dispatcher binder of this agent. so_this_agent_disp_binder(), [this]( so_5::coop_t & coop ) { coop.make_agent< a_child_t >( so_direct_mbox(), 1, 6 ); } ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { // The coop of the coordinator agent will use active_obj dispatcher. env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), []( so_5::coop_t & coop ) { coop.make_agent< a_coordinator_t >(); } ); } ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/dispatcher_hello/000077500000000000000000000000001523065210100222655ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/dispatcher_hello/CMakeLists.txt000066400000000000000000000010041523065210100250200ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.dispatcher_hello) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.dispatcher_hello_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/dispatcher_hello/main.cpp000066400000000000000000000064301523065210100237200ustar00rootroot00000000000000/* * Demonstration of very simple case for dispatchers. */ #include #include #include // Sample coordinator. // Works on the default dispatcher. // Receives messages from children agents. // Finishes sample when all messages are received. class a_coordinator_t final : public so_5::agent_t { public : a_coordinator_t( context_t ctx ) : so_5::agent_t( ctx ) {} void so_define_agent() override { // Just one message must be handled in the default agent state. so_default_state().event( [this]( const std::string & msg ) { std::cout << "hello: " << msg << std::endl; // Work must be stopped if all messages are received. if( 0 == (--m_remaining_messages) ) so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { // Cooperation must be created at the start of agent's work. create_first_child_coop(); create_second_child_coop(); } private : unsigned int m_remaining_messages = 6; void create_first_child_coop() { // The first cooperation will use one_thread dispatcher. auto disp = so_5::disp::one_thread::make_dispatcher( so_environment() ); auto coop = so_5::create_child_coop( // This agent will be parent for new cooperation. *this, // The main dispatcher for the new cooperation is // one_thread dispatcher. disp.binder() ); fill_and_register_coop( std::move( coop ), "one_thread" ); } void create_second_child_coop() { // The second cooperation will use active_obj dispatcher. auto disp = so_5::disp::active_obj::make_dispatcher( so_environment() ); auto coop = so_5::create_child_coop( // This agent will be parent for new cooperation. *this, // The main dispatcher for the new cooperation is // active_obj dispatcher. disp.binder() ); fill_and_register_coop( std::move( coop ), "active_obj" ); } void fill_and_register_coop( so_5::coop_unique_holder_t coop, const std::string & agent_name_prefix ) { // Type of agent to be used in child coop. class demo_agent_t final : public so_5::agent_t { const so_5::mbox_t m_mbox; const std::string m_agent_name; public : demo_agent_t( context_t ctx, so_5::mbox_t mbox, std::string agent_name ) : so_5::agent_t{ std::move(ctx) } , m_mbox{ std::move(mbox) } , m_agent_name{ std::move(agent_name) } {} void so_evt_start() override { // Hello message must contain agent name and thread id. std::ostringstream ss; ss << m_agent_name << " on thread: " << std::this_thread::get_id(); // Coordinator should receive a hello message. so_5::send< std::string >( m_mbox, ss.str() ); } }; // Coordinator's mbox to which hello messages must be sent. const auto mbox = so_direct_mbox(); for( int i = 0; i != 3; ++i ) { coop->make_agent< demo_agent_t >( mbox, agent_name_prefix + "-" + std::to_string( i ) ); } so_environment().register_coop( std::move( coop ) ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { // Coordinator agent will work on the default dispatcher. env.register_agent_as_coop( env.make_agent< a_coordinator_t >() ); } ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/dispatcher_restarts/000077500000000000000000000000001523065210100230315ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/dispatcher_restarts/CMakeLists.txt000066400000000000000000000005571523065210100256000ustar00rootroot00000000000000set(SAMPLE sample.so_5.dispatcher_restarts) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.dispatcher_restarts_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/dispatcher_restarts/main.cpp000066400000000000000000000112631523065210100244640ustar00rootroot00000000000000/* * Demonstration of creation and recreation of big amount of * children cooperations with dispatchers. * If dispatchers are not destroyed with cooperation then * the example will earn all the system resources very quickly. */ #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif // Child agent. // // Will be member of agent's chain. // If it is the last member of chain it will deregister // the whole cooperation. class a_chain_member_t final : public so_5::agent_t { public : // A signal to be sent from one chain's member to the next member. struct msg_your_turn final : public so_5::signal_t {}; a_chain_member_t( context_t ctx ) : so_5::agent_t( ctx ) {} void set_next( const so_5::mbox_t & next ) { m_next = next; } void so_define_agent() override { so_default_state().event( [this](mhood_t< msg_your_turn >) { if( m_next ) so_5::send< msg_your_turn >( m_next ); else so_deregister_agent_coop_normally(); } ); } private : so_5::mbox_t m_next; }; // Sample coordinator. // Works on the default dispatcher. // Creates and recreates children cooperations. class a_coordinator_t final : public so_5::agent_t { public : a_coordinator_t( context_t ctx, unsigned int iterations ) : so_5::agent_t( ctx ) , m_remaining_iterations( iterations ) {} void so_define_agent() override { // A notification on complete children cooperation deregistration // must be received and handled. so_default_state().event( [this]( const so_5::msg_coop_deregistered & ) { --m_remaining_iterations; show_remaining_iterations(); if( m_remaining_iterations ) // There are some iterations left. // New child cooperation must be created and started. create_new_child_coop(); else { // Example must be finished. std::cout << "no more iterations left, finishing..." << std::endl; so_deregister_agent_coop_normally(); } } ); } void so_evt_start() override { show_remaining_iterations(); create_new_child_coop(); } private : // How many iterations remains. unsigned int m_remaining_iterations; void show_remaining_iterations() const { std::cout << m_remaining_iterations << ": iterations left...\r" << std::flush; } void create_new_child_coop() { // The cooperation will use active_obj dispatcher. auto disp = so_5::disp::active_obj::make_dispatcher( so_environment() ); auto coop = so_5::create_child_coop( // This agent will be parent for new cooperation. *this, // The main dispatcher for the new cooperation is // active_obj dispatcher. disp.binder() ); // We should receive notification about complete // child cooperation deregistration. coop->add_dereg_notificator( so_5::make_coop_dereg_notificator( so_direct_mbox() ) ); auto first_mbox = fill_coop( *coop ); so_environment().register_coop( std::move( coop ) ); // Initial message must be sent to the first chain's member. so_5::send< a_chain_member_t::msg_your_turn >( first_mbox ); } // Filling the cooperation with the agents. // Return the mbox of the first member in the chain. so_5::mbox_t fill_coop( so_5::coop_t & coop ) { const std::size_t agent_count = 8; // Those containers are necessary for building agents chain. std::vector< so_5::mbox_t > mboxes; mboxes.reserve( agent_count ); std::vector< a_chain_member_t * > agents; agents.reserve( agent_count ); // Create all agents of child cooperation. for( std::size_t i = 0; i != agent_count; ++i ) { auto a = coop.make_agent< a_chain_member_t >(); agents.push_back( a ); mboxes.push_back( a->so_direct_mbox() ); } // Connect all agents to the chain. for( std::size_t i = 0; i != agent_count - 1; ++i ) agents[ i ]->set_next( mboxes[ i + 1 ] ); return mboxes[ 0 ]; } }; unsigned int detect_iteration_count( int argc, char ** argv ) { unsigned int r = 5000; if( 2 == argc ) { auto a = std::atoi( argv[ 1 ] ); if( a > 0 ) r = static_cast< unsigned int >( a ); else throw std::invalid_argument( "invalid iteration count: " + std::to_string( a ) ); } return r; } int main( int argc, char ** argv ) { try { const auto iterations = detect_iteration_count( argc, argv ); so_5::launch( [iterations]( so_5::environment_t & env ) { // Coordinator agent will work on the default dispatcher. env.register_agent_as_coop( env.make_agent< a_coordinator_t >( iterations ) ); } ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/exception_logger/000077500000000000000000000000001523065210100223115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/exception_logger/CMakeLists.txt000066400000000000000000000010041523065210100250440ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.exception_logger) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.exception_logger_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/exception_logger/main.cpp000066400000000000000000000030221523065210100237360ustar00rootroot00000000000000/* * A sample of the exception logger. */ #include #include // Main SObjectizer header file. #include // A class of the exception logger. class sample_event_exception_logger_t final : public so_5::event_exception_logger_t { public: // A reaction to an exception. void log_exception( const std::exception & event_exception, const so_5::coop_handle_t & coop ) noexcept override { std::cerr << "Event_exception, coop:" << coop << "; " " error: " << event_exception.what() << std::endl; } }; // A class of an agent which will throw an exception. class a_hello_t final : public so_5::agent_t { public: a_hello_t( context_t ctx ) : so_5::agent_t( ctx ) {} // A reaction to start work in SObjectizer. void so_evt_start() override { so_environment().install_exception_logger( std::make_unique() ); throw std::runtime_error( "sample exception" ); } // An instruction to SObjectizer for unhandled exception. so_5::exception_reaction_t so_exception_reaction() const noexcept override { return so_5::deregister_coop_on_exception; } }; int main() { try { so_5::launch( // SObjectizer initialization code. []( so_5::environment_t & env ) { // Creating and registering cooperation with a single agent. env.register_agent_as_coop( env.make_agent< a_hello_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/exception_reaction/000077500000000000000000000000001523065210100226365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/exception_reaction/CMakeLists.txt000066400000000000000000000010101523065210100253660ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.exception_reaction) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.exception_reaction_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/exception_reaction/main.cpp000066400000000000000000000014601523065210100242670ustar00rootroot00000000000000/* * A sample for the exception reaction. */ #include #include // Main SObjectizer header file. #include // A class of an agent which will throw an exception. class a_hello_t final : public so_5::agent_t { public: a_hello_t( context_t ctx ) : so_5::agent_t( ctx ) {} void so_evt_start() override { throw std::runtime_error( "sample exception" ); } so_5::exception_reaction_t so_exception_reaction() const noexcept override { return so_5::shutdown_sobjectizer_on_exception; } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_hello_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/hardwork_imit/000077500000000000000000000000001523065210100216175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/hardwork_imit/CMakeLists.txt000066400000000000000000000005431523065210100243610ustar00rootroot00000000000000set(SAMPLE sample.so_5.hardwork_imit) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.hardwork_imit_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/hardwork_imit/main.cpp000066400000000000000000000135111523065210100232500ustar00rootroot00000000000000/* * Sample for demonstrating results of some hard work on different * dispatchers. */ #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif struct msg_do_hardwork { unsigned int m_index; unsigned int m_milliseconds; }; struct msg_hardwork_done { unsigned int m_index; }; struct msg_check_hardwork { unsigned int m_index; unsigned int m_milliseconds; }; struct msg_hardwork_checked { unsigned int m_index; }; class a_manager_t final : public so_5::agent_t { public : a_manager_t( context_t ctx, so_5::mbox_t worker_mbox, so_5::mbox_t checker_mbox, unsigned int requests, unsigned int milliseconds ) : so_5::agent_t( ctx ) , m_worker_mbox( std::move(worker_mbox) ) , m_checker_mbox( std::move(checker_mbox) ) , m_requests( requests ) , m_milliseconds( milliseconds ) {} void so_define_agent() override { so_subscribe_self() .event( &a_manager_t::evt_hardwork_done ) .event( &a_manager_t::evt_hardwork_checked ); } void so_evt_start() override { m_start_time = std::chrono::steady_clock::now(); for( unsigned int i = 0; i != m_requests; ++i ) { so_5::send< msg_do_hardwork >( m_worker_mbox, i, m_milliseconds ); } } void evt_hardwork_done( const msg_hardwork_done & evt ) { so_5::send< msg_check_hardwork >( m_checker_mbox, evt.m_index, m_milliseconds ); } void evt_hardwork_checked( const msg_hardwork_checked & ) { ++m_processed; if( m_processed == m_requests ) { auto finish_time = std::chrono::steady_clock::now(); auto duration = double( std::chrono::duration_cast< std::chrono::milliseconds >( finish_time - m_start_time ).count() ) / 1000.0; std::cout << "Working time: " << duration << "s" << std::endl; so_environment().stop(); } } private : const so_5::mbox_t m_worker_mbox; const so_5::mbox_t m_checker_mbox; const unsigned int m_requests; unsigned int m_processed = 0; const unsigned int m_milliseconds; std::chrono::steady_clock::time_point m_start_time; }; so_5::coop_unique_holder_t create_test_coop( so_5::environment_t & env, so_5::disp_binder_shptr_t disp_binder, unsigned int requests, unsigned int milliseconds ) { class worker_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; void bind_to( const so_5::agent_t & manager ) { so_subscribe_self().event( [&manager]( mhood_t cmd ) { std::this_thread::sleep_for( std::chrono::milliseconds( cmd->m_milliseconds ) ); so_5::send< msg_hardwork_done >( manager, cmd->m_index ); }, so_5::thread_safe ); } }; class checker_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; void bind_to( const so_5::agent_t & manager ) { so_subscribe_self().event( [&manager]( mhood_t cmd ) { std::this_thread::sleep_for( std::chrono::milliseconds( cmd->m_milliseconds ) ); so_5::send< msg_hardwork_checked >( manager, cmd->m_index ); }, so_5::thread_safe ); } }; auto c = env.make_coop( std::move( disp_binder ) ); auto worker = c->make_agent< worker_t >(); auto checker = c->make_agent< checker_t >(); auto manager = c->make_agent< a_manager_t >( worker->so_direct_mbox(), checker->so_direct_mbox(), requests, milliseconds ); worker->bind_to( *manager ); checker->bind_to( *manager ); return c; } using dispatcher_factory_t = std::function< so_5::disp_binder_shptr_t( so_5::environment_t & ) >; dispatcher_factory_t make_dispatcher_factory( std::string_view type ) { dispatcher_factory_t res; if( "active_obj" == type ) { using namespace so_5::disp::active_obj; res = []( so_5::environment_t & env ) { return make_dispatcher( env ).binder(); }; } else if( "thread_pool" == type ) { using namespace so_5::disp::thread_pool; res = []( so_5::environment_t & env ) { return make_dispatcher( env ).binder( []( bind_params_t & p ) { p.fifo( fifo_t::individual ); } ); }; } else if( "adv_thread_pool" == type ) { using namespace so_5::disp::adv_thread_pool; res = []( so_5::environment_t & env ) { return make_dispatcher( env ).binder( []( bind_params_t & p ) { p.fifo( fifo_t::individual ); } ); }; } else if( "one_thread" == type ) { using namespace so_5::disp::one_thread; res = []( so_5::environment_t & env ) { return make_dispatcher( env ).binder(); }; } else throw std::runtime_error( "unknown type of dispatcher: " + std::string{ type } ); return res; } struct config_t { dispatcher_factory_t m_factory; unsigned int m_requests; unsigned int m_milliseconds; }; config_t parse_params( int argc, char ** argv ) { if( 1 == argc ) throw std::runtime_error( "no arguments given!\n\n" "usage:\n\n" "sample.so_5.hardwork_imit [requests] [worktime_ms]" ); config_t r { make_dispatcher_factory( argv[ 1 ] ), 200, 15 }; if( 2 < argc ) r.m_requests = static_cast< unsigned int >( std::atoi( argv[ 2 ] ) ); if( 3 < argc ) r.m_milliseconds = static_cast< unsigned int >( std::atoi( argv[ 3 ] ) ); std::cout << "Config:\n" "\t" "dispatcher: " << argv[ 1 ] << "\n" "\t" "requests: " << r.m_requests << "\n" "\t" "worktime (ms): " << r.m_milliseconds << std::endl; return r; } int main( int argc, char ** argv ) { try { const config_t config = parse_params( argc, argv ); so_5::launch( [&config]( so_5::environment_t & env ) { env.register_coop( create_test_coop( env, config.m_factory( env ), config.m_requests, config.m_milliseconds ) ); } ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/hello_all/000077500000000000000000000000001523065210100207075ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/hello_all/CMakeLists.txt000066400000000000000000000007661523065210100234600ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.hello_all) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.hello_all_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/hello_all/main.cpp000066400000000000000000000050461523065210100223440ustar00rootroot00000000000000/* * A sample of the message interchange. */ #include // Main SObjectizer header files. #include struct msg_hello_to_all { // Sender name. const std::string m_sender; // Sender Mbox. const so_5::mbox_t m_mbox; }; struct msg_hello_to_you { // Sender name. const std::string m_sender; }; // An agent class. class a_hello_t final : public so_5::agent_t { public: a_hello_t( context_t ctx, std::string agent_name ) : so_5::agent_t( ctx ) , m_agent_name( std::move( agent_name ) ) , m_common_mbox( so_environment().create_mbox( "common_mbox" ) ) {} // Definition of the agent for SObjectizer. void so_define_agent() override; // A reaction to start of work in SObjectizer. void so_evt_start() override; void evt_hello_to_all( const msg_hello_to_all & evt_data ); void evt_hello_to_you( const msg_hello_to_you & evt_data ); private: // Agent name. const std::string m_agent_name; // Common mbox for all sample agents. const so_5::mbox_t m_common_mbox; }; void a_hello_t::so_define_agent() { // Message subscription. so_subscribe( m_common_mbox ) .event( &a_hello_t::evt_hello_to_all ); so_subscribe_self() .event( &a_hello_t::evt_hello_to_you ); } void a_hello_t::so_evt_start() { std::cout << m_agent_name << ".so_evt_start" << std::endl; // Send greeting to all agents. so_5::send< msg_hello_to_all >( m_common_mbox, m_agent_name, so_direct_mbox() ); } void a_hello_t::evt_hello_to_all( const msg_hello_to_all & evt_data ) { std::cout << m_agent_name << ".evt_hello_to_all: " << evt_data.m_sender << std::endl; // If this agent is not the sender then reply should be sent. if( m_agent_name != evt_data.m_sender ) { so_5::send< msg_hello_to_you >( evt_data.m_mbox, m_agent_name ); } } void a_hello_t::evt_hello_to_you( const msg_hello_to_you & evt_data ) { std::cout << m_agent_name << ".evt_hello_to_you: " << evt_data.m_sender << std::endl; } // The SObjectizer Environment initialization. void init( so_5::environment_t & env ) { // Creating and registering a cooperation. env.introduce_coop( []( so_5::coop_t & coop ) { // Adding agents to the cooperation. coop.make_agent< a_hello_t >( "alpha" ); coop.make_agent< a_hello_t >( "beta" ); coop.make_agent< a_hello_t >( "gamma" ); } ); // Give some time to agents. std::this_thread::sleep_for( std::chrono::milliseconds( 200 ) ); env.stop(); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/hello_delay/000077500000000000000000000000001523065210100212355ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/hello_delay/CMakeLists.txt000066400000000000000000000005371523065210100240020ustar00rootroot00000000000000set(SAMPLE sample.so_5.hello_delay) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.hello_delay_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/hello_delay/main.cpp000066400000000000000000000042151523065210100226670ustar00rootroot00000000000000/* * A sample of a simple agent which send a delayed message to itself. */ #if defined( _MSC_VER ) #if defined( __clang__ ) #pragma clang diagnostic ignored "-Wreserved-id-macro" #endif #define _CRT_SECURE_NO_WARNINGS #endif #include #include // Main SObjectizer header files. #include // Hello message. struct msg_hello { // Greeting. std::string m_message; }; // Stop message. class msg_stop_signal final : public so_5::signal_t {}; // An agent class. class a_hello_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; // Definition of an agent for SObjectizer. void so_define_agent() override; // A reaction to start of work in SObjectizer. void so_evt_start() override; private: // Delayed message handler. void evt_hello_delay( const msg_hello & msg ); // Stop signal handler. void evt_stop_signal( mhood_t ); // Helper function for printing messages. static void show_message( const std::string & what ); }; void a_hello_t::so_define_agent() { // Message subscription. so_subscribe_self().event( &a_hello_t::evt_hello_delay ); so_subscribe_self().event( &a_hello_t::evt_stop_signal ); } void a_hello_t::so_evt_start() { show_message( "a_hello_t::so_evt_start()" ); so_5::send_delayed< msg_hello >( *this, std::chrono::seconds( 2 ), "Hello, world! This is SObjectizer v.5." ); } void a_hello_t::evt_hello_delay( const msg_hello & msg ) { show_message( msg.m_message ); so_5::send_delayed< msg_stop_signal >( *this, std::chrono::seconds( 2 ) ); } void a_hello_t::evt_stop_signal( mhood_t ) { show_message( "Stop SObjectizer..." ); // Shutting down SObjectizer. so_environment().stop(); } void a_hello_t::show_message( const std::string & what ) { time_t t = time( nullptr ); std::cout << std::asctime( std::localtime( &t ) ) << what << std::endl; } int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_hello_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/hello_evt_handler/000077500000000000000000000000001523065210100224325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/hello_evt_handler/CMakeLists.txt000066400000000000000000000010061523065210100251670ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.hello_evt_handler) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.hello_evt_handler_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/hello_evt_handler/main.cpp000066400000000000000000000021221523065210100240570ustar00rootroot00000000000000/* * A sample of a simple agent which send message to itself. */ #include // Main SObjectizer header files. #include // An agent class definition. class a_hello_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; // Definition of an agent for SObjectizer. void so_define_agent() override { // The hello message subscription. so_subscribe_self().event( &a_hello_t::evt_hello ); } // A reaction to start of work in SObjectizer. void so_evt_start() override { // Send hello message. so_5::send< std::string >( *this, "Hello, world! This is SObjectizer-5."); } private : // Hello message handler. void evt_hello( const std::string & msg ) { std::cout << msg << std::endl; // Shutting down SObjectizer. so_environment().stop(); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_hello_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/hello_evt_lambda/000077500000000000000000000000001523065210100222355ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/hello_evt_lambda/CMakeLists.txt000066400000000000000000000010041523065210100247700ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.hello_evt_lambda) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.hello_evt_lambda_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/hello_evt_lambda/main.cpp000066400000000000000000000020201523065210100236570ustar00rootroot00000000000000/* * A sample of a simple agent which send message to itself. */ #include // Main SObjectizer header files. #include // An agent class definition. class a_hello_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; // Definition of an agent for SObjectizer. void so_define_agent() override { // The hello message subscription. so_subscribe_self().event( [this]( const std::string & msg ) { std::cout << msg << std::endl; // Shutting down SObjectizer. so_environment().stop(); } ); } // A reaction to start of work in SObjectizer. void so_evt_start() override { // Send hello message. so_5::send< std::string >( *this, "Hello, world! This is SObjectizer-5."); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_hello_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/hello_periodic/000077500000000000000000000000001523065210100217355ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/hello_periodic/CMakeLists.txt000066400000000000000000000005451523065210100245010ustar00rootroot00000000000000set(SAMPLE sample.so_5.hello_periodic) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.hello_periodic_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/hello_periodic/main.cpp000066400000000000000000000073201523065210100233670ustar00rootroot00000000000000/* * A sample of a simple agent which sends a periodic message to itself. */ #if defined( _MSC_VER ) #if defined( __clang__ ) #pragma clang diagnostic ignored "-Wreserved-id-macro" #endif #define _CRT_SECURE_NO_WARNINGS #endif // Main SObjectizer header files. #include #include // Hello message. struct msg_hello_periodic { // Greeting. std::string m_message; }; // Stop message. class msg_stop_signal final : public so_5::signal_t {}; // Agent for stopping an example on stop_signal. class a_shutdowner_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe( so_environment().create_mbox( "shutdown" ) ) .event( &a_shutdowner_t::evt_stop_signal ); } private: void evt_stop_signal( mhood_t ) { time_t t = time( nullptr ); std::cout << asctime( localtime( &t ) ) << "Stop SObjectizer..." << std::endl; // Shutting down SObjectizer. so_environment().stop(); } }; // An agent class definition. class a_hello_t final : public so_5::agent_t { public: a_hello_t( context_t ctx ) : so_5::agent_t{ ctx } , m_shutdowner_mbox( so_environment().create_mbox( "shutdown" ) ) , m_evt_count( 0 ) {} // Definition of an agent for SObjectizer. void so_define_agent() override; // A reaction to start of work in SObjectizer. void so_evt_start() override; private: // Shutdowner's mbox. const so_5::mbox_t m_shutdowner_mbox; // Timer events' identifiers. so_5::timer_id_t m_hello_timer_id; so_5::timer_id_t m_stop_timer_id; // How much timer event has been processed. unsigned int m_evt_count; // Hello message handler. void evt_hello_periodic( const msg_hello_periodic & msg ); }; void a_hello_t::so_define_agent() { // Message subscription. so_subscribe_self().event( &a_hello_t::evt_hello_periodic ); } void a_hello_t::so_evt_start() { time_t t = time( nullptr ); std::cout << asctime( localtime( &t ) ) << "a_hello_t::so_evt_start()" << std::endl; // Sending a greeting. m_hello_timer_id = so_5::send_periodic< msg_hello_periodic >( *this, // Delay for a second. std::chrono::seconds(1), // Repeat every 1.25 of seconds. std::chrono::milliseconds(1250), "Hello, periodic!" ); // Sending a stop signal. m_stop_timer_id = so_5::send_periodic< msg_stop_signal >( m_shutdowner_mbox, // Delay for two seconds. std::chrono::seconds(2), // Not a periodic. std::chrono::seconds::zero() ); } void a_hello_t::evt_hello_periodic( const msg_hello_periodic & msg ) { time_t t = time( nullptr ); std::cout << asctime( localtime( &t ) ) << msg.m_message << std::endl; if( 5 == ++m_evt_count ) { // Stops hello message. m_hello_timer_id.release(); } else { // Reschedule a stop signal. // Previous a stop signal should be canceled. m_stop_timer_id = so_5::send_periodic< msg_stop_signal >( m_shutdowner_mbox, // 1300ms but specified in microsecs just for demonstration. std::chrono::microseconds(1300000), std::chrono::seconds::zero() ); } } // Creation of 'hello' cooperation. void create_hello_coop( so_5::environment_t & env ) { // Single agent can be registered as whole cooperation. env.register_agent_as_coop( env.make_agent< a_hello_t >() ); } // Creation of 'shutdowner' cooperation. void create_shutdowner_coop( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_shutdowner_t >() ); } // The SObjectizer Environment initialization. void init( so_5::environment_t & env ) { create_hello_coop( env ); create_shutdowner_coop( env ); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/hello_world/000077500000000000000000000000001523065210100212665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/hello_world/CMakeLists.txt000066400000000000000000000007721523065210100240340ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.hello_world) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.hello_world_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/hello_world/main.cpp000066400000000000000000000020671523065210100227230ustar00rootroot00000000000000/* * A sample of the simpliest agent. */ #include // Main SObjectizer header files. #include // Definition of an agent for SObjectizer. class a_hello_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; // A reaction to start of work in SObjectizer. void so_evt_start() override { std::cout << "Hello, world! This is SObjectizer v.5 (" << SO_5_VERSION << ")" << std::endl; // Shutting down SObjectizer. so_environment().stop(); } // A reaction to finish of work in SObjectizer. void so_evt_finish() override { std::cout << "Bye! This was SObjectizer v.5." << std::endl; } }; int main() { try { // Starting SObjectizer. so_5::launch( // A function for SO Environment initialization. []( so_5::environment_t & env ) { // Creating and registering single agent as a cooperation. env.register_agent_as_coop( env.make_agent< a_hello_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/hello_world_simple_not_mtsafe/000077500000000000000000000000001523065210100250565ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/hello_world_simple_not_mtsafe/CMakeLists.txt000066400000000000000000000010361523065210100276160ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.hello_world_simple_not_mtsafe) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.hello_world_simple_not_mtsafe_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/hello_world_simple_not_mtsafe/main.cpp000066400000000000000000000013451523065210100265110ustar00rootroot00000000000000/* * A sample of "Hello, World" for simple_not_mtsafe environment infrastructure. */ // Main SObjectizer header files. #include using namespace std; using namespace so_5; class hello_actor final : public agent_t { public : using agent_t::agent_t; void so_evt_start() override { cout << "Hello, world!" << endl; } }; int main() { so_5::launch( []( environment_t & env ) { // Creating and registering a cooperation. env.introduce_coop( []( coop_t & coop ) { // Adding agent to the cooperation. coop.make_agent(); }); }, []( environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); return 0; } sobjectizer-5.8.6/dev/sample/so_5/individual_msg_tracing/000077500000000000000000000000001523065210100234615ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/individual_msg_tracing/CMakeLists.txt000066400000000000000000000010201523065210100262120ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.individual_msg_tracing) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.individual_msg_tracing_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/individual_msg_tracing/main.cpp000066400000000000000000000061741523065210100251210ustar00rootroot00000000000000/* * An example to show usage of so_5::msg_tracing::individual_trace function. * * A message delivery tracing is enabled. Trace is going to std::cout. */ // Main SObjectizer header file. #include using namespace std::chrono_literals; // Signals for ping-pong. struct ping final : public so_5::signal_t {}; struct pong final : public so_5::signal_t {}; // A class for implementation of pinger and ponger agents. template< typename Signal_To_Send, typename Signal_To_Receive, bool Use_Individual_Trace > class a_pinger_ponger_t final : public so_5::agent_t { const so_5::mbox_t m_mbox; public : a_pinger_ponger_t( context_t ctx, so_5::mbox_t mbox ) : so_5::agent_t( std::move(ctx) ) , m_mbox( std::move(mbox) ) {} void so_define_agent() override { so_subscribe( m_mbox ).event( [this](mhood_t) { if constexpr( Use_Individual_Trace ) // Use individual tracing for outgoing messages. so_5::send_delayed< Signal_To_Send >( so_5::msg_tracing::individual_trace( m_mbox ), 25ms ); else // Use the usual delivery. so_5::send_delayed< Signal_To_Send >( m_mbox, 25ms ); } ); } }; // Main example agent. class a_example_t final : public so_5::agent_t { // Signal for finishing the example. struct finish final : public so_5::signal_t {}; public : a_example_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( &a_example_t::on_finish ); } virtual void so_evt_start() override { // Limit the work time. so_5::send_delayed< finish >( *this, std::chrono::milliseconds(500) ); // Create a ping-pong pair which will work on separate thread. make_ping_pong_pair( so_5::disp::one_thread::make_dispatcher( so_environment() ).binder() ); } private : void on_finish( mhood_t ) { so_deregister_agent_coop_normally(); } void make_ping_pong_pair( so_5::disp_binder_shptr_t binder ) { // A mbox to be used by pinger and ponger agents. const auto mbox = so_environment().create_mbox(); // Create a new coop with two agents inside. so_5::introduce_child_coop( *this, std::move(binder), [mbox]( so_5::coop_t & coop ) { // Only pinger will use individual_trace. coop.make_agent< a_pinger_ponger_t >( mbox ); coop.make_agent< a_pinger_ponger_t >( mbox ); } ); // Initiate ping-pong exchange. // Enable msg_tracing for this initial ping signal too. so_5::send_delayed< ping >( so_5::msg_tracing::individual_trace(mbox), 25ms ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_example_t >(); } ); }, []( so_5::environment_params_t & params ) { // Turn message delivery tracing on. params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); // Setup filter that allows only messages with individual trace. params.message_delivery_tracer_filter( so_5::msg_tracing::make_individual_trace_filter() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/intercom_statechart/000077500000000000000000000000001523065210100230165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/intercom_statechart/CMakeLists.txt000066400000000000000000000005601523065210100255570ustar00rootroot00000000000000set(SAMPLE sample.so_5.intercom_statechart) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.intercom_statechart_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/intercom_statechart/main.cpp000066400000000000000000000402741523065210100244550ustar00rootroot00000000000000/* * An example of implementation of hierarchical state machine by using * agent's states. */ #include #include #include // Main SObjectizer header file. #include // Messages to be used for interaction with intercom agents. struct key_cancel : public so_5::signal_t {}; struct key_bell : public so_5::signal_t {}; struct key_grid : public so_5::signal_t {}; struct key_digit { char m_value; }; // Private messages for intercom implementation. namespace intercom_messages { struct activated : public so_5::signal_t {}; struct deactivate : public so_5::signal_t {}; struct display_text { std::string m_what; }; // Helper function for sending display_text message. inline void show_on_display( const so_5::mbox_t & intercom_mbox, std::string what ) { so_5::send< display_text >( intercom_mbox, std::move(what) ); } // Helper function for clearing display. inline void clear_display( const so_5::mbox_t & intercom_mbox ) { so_5::send< display_text >( intercom_mbox, "" ); } } // namespace intercom_messages // An agent for controlling inactivity time. // // Listen the same user-activity messages as controller agent and reschedules // delayed message after each of them. This delayed message tells the // controller to change state from active to inactive. // // Please note: this agent switches from inactive to active state by the // signal from controller agent. class inactivity_watcher final : public so_5::agent_t { state_t inactive{ this, "inactive" }; state_t active{ this, "active" }; const std::chrono::seconds inactivity_time{ 10 }; public : inactivity_watcher( context_t ctx, so_5::mbox_t intercom_mbox ) : so_5::agent_t{ ctx } , m_intercom_mbox{ std::move(intercom_mbox) } { inactive .on_enter( [this] { m_timer.release(); } ) .just_switch_to< intercom_messages::activated >( m_intercom_mbox, active ); active .on_enter( [this] { reschedule_timer(); } ) .event( m_intercom_mbox, [this](mhood_t< key_cancel >) { reschedule_timer(); } ) .event( m_intercom_mbox, [this](mhood_t< key_bell >) { reschedule_timer(); } ) .event( m_intercom_mbox, [this](mhood_t< key_grid >) { reschedule_timer(); } ) .event( m_intercom_mbox, [this]( const key_digit & ) { reschedule_timer(); } ) .just_switch_to< intercom_messages::deactivate >( m_intercom_mbox, inactive ); this >>= inactive; } private : const so_5::mbox_t m_intercom_mbox; so_5::timer_id_t m_timer; void reschedule_timer() { m_timer = so_5::send_periodic< intercom_messages::deactivate >( m_intercom_mbox, inactivity_time, std::chrono::seconds::zero() ); } }; // An agent for imitation of keyboard's light equipment. // // Switching from off to on states and back is done by listening // activated and deactivate signals. class keyboard_lights final : public so_5::agent_t { state_t off{ this, "off" }; state_t on{ this, "on" }; public : keyboard_lights( context_t ctx, const so_5::mbox_t & intercom_mbox ) : so_5::agent_t{ ctx } { off .on_enter( []{ std::cout << "keyboard_lights OFF" << std::endl; } ) .just_switch_to< intercom_messages::activated >( intercom_mbox, on ); on .on_enter( []{ std::cout << "keyboard_lights ON" << std::endl; } ) .just_switch_to< intercom_messages::deactivate >( intercom_mbox, off ); this >>= off; } }; // An agent for imitation of intercom's display. // // Switching from off to on states and back is done by listening // activated and deactivate signals. class display final : public so_5::agent_t { state_t off{ this, "off" }; state_t on{ this, "on" }; public : display( context_t ctx, const so_5::mbox_t & intercom_mbox ) : so_5::agent_t{ ctx } { off .on_enter( []{ std::cout << "display OFF" << std::endl; } ) .just_switch_to< intercom_messages::activated >( intercom_mbox, on ); on .on_enter( []{ std::cout << "display ON" << std::endl; } ) .event( intercom_mbox, []( const intercom_messages::display_text & msg ) { std::cout << "display: '" << msg.m_what << "'" << std::endl; } ) .just_switch_to< intercom_messages::deactivate >( intercom_mbox, off ); this >>= off; } }; // An agent for imitation of call to an apartment. // // Just changes a message on display for every 1.5s. class ringer final : public so_5::agent_t { state_t off{ this, "off" }, on{ this, "on" }, ringing{ initial_substate_of{ on }, "ringing" }, sleeping{ substate_of{ on }, "sleeping" }; struct timer : public so_5::signal_t {}; public : struct dial_to { std::string m_number; }; struct stop_dialing : public so_5::signal_t {}; ringer( context_t ctx, so_5::mbox_t intercom_mbox ) : so_5::agent_t{ ctx } , m_intercom_mbox{ std::move(intercom_mbox) } { this >>= off; off .on_enter( [this]{ m_timer.release(); } ) .event( m_intercom_mbox, [this]( const dial_to & msg ) { m_number = msg.m_number; on.activate(); } ); on .on_enter( [this] { m_timer = so_5::send_periodic< timer >( *this, std::chrono::milliseconds::zero(), std::chrono::milliseconds{ 1500 } ); } ) .just_switch_to< stop_dialing >( m_intercom_mbox, off ); ringing .on_enter( [this]{ intercom_messages::show_on_display( m_intercom_mbox, "RING" ); } ) .just_switch_to< timer >( sleeping ); sleeping .on_enter( [this]{ intercom_messages::show_on_display( m_intercom_mbox, m_number ); } ) .just_switch_to< timer >( ringing ); } private : const so_5::mbox_t m_intercom_mbox; so_5::timer_id_t m_timer; std::string m_number; }; // Main agent of intercom example. // Imitates intercom's controller. // // Receives a messages from user and perform various actions. // class controller final : public so_5::agent_t { // States of agent's statechart. state_t inactive{ this, "inactive" }, active{ this, "active" }, wait_activity{ initial_substate_of{ active }, "wait_activity" }, number_selection{ substate_of{ active }, "number_selection" }, dialling{ substate_of{ active }, "dialling" }, dial_apartment{ initial_substate_of{ dialling }, "dial_apartment" }, no_answer{ substate_of{ dialling }, "no_answer" }, special_code_selection{ substate_of{ active }, "special_code_selection" }, special_code_selection_0{ initial_substate_of{ special_code_selection }, "special_code_selection_0" }, user_code_selection{ substate_of{ special_code_selection }, "user_code_selection" }, user_code_apartment_number{ initial_substate_of{ user_code_selection }, "apartment_number" }, user_code_secret{ substate_of{ user_code_selection }, "secret_code" }, service_code_selection{ substate_of{ special_code_selection }, "service_code" }, door_unlocked{ substate_of{ special_code_selection }, "door_unlocked" } ; struct apartment_info { std::string m_number; std::string m_secret_key; apartment_info( std::string n, std::string k ) : m_number( std::move(n) ), m_secret_key( std::move(k) ) {} }; public : controller( context_t ctx, so_5::mbox_t intercom_mbox ) : so_5::agent_t{ ctx } , m_intercom_mbox{ std::move(intercom_mbox) } , m_apartments( make_apartment_info() ) , m_actual_service_code( "12345" ) { // Setting up statechart. inactive .transfer_to_state< key_digit >( m_intercom_mbox, active ) .transfer_to_state< key_grid >( m_intercom_mbox, active ) .transfer_to_state< key_bell >( m_intercom_mbox, active ) .transfer_to_state< key_cancel >( m_intercom_mbox, active ); active .on_enter( &controller::active_on_enter ) .event( m_intercom_mbox, &controller::active_on_grid ) .event( m_intercom_mbox, &controller::active_on_cancel ) .just_switch_to< intercom_messages::deactivate >( m_intercom_mbox, inactive ); wait_activity .transfer_to_state< key_digit >( m_intercom_mbox, number_selection ); number_selection .on_enter( &controller::apartment_number_on_enter ) .event( m_intercom_mbox, &controller::apartment_number_on_digit ) .event( m_intercom_mbox, &controller::apartment_number_on_bell ) .suppress< key_grid >( m_intercom_mbox ); dialling .suppress< key_grid >( m_intercom_mbox ) .suppress< key_bell >( m_intercom_mbox ) .suppress< key_digit >( m_intercom_mbox ); dial_apartment .time_limit( std::chrono::seconds{ 8 }, no_answer ) .on_enter( &controller::dial_apartment_on_enter ) .on_exit( &controller::dial_apartment_on_exit ); no_answer .time_limit( std::chrono::milliseconds{ 1500 }, wait_activity ) .on_enter( &controller::no_answer_on_enter ) .on_exit( &controller::no_answer_on_exit ) .suppress< key_cancel >( m_intercom_mbox ); special_code_selection_0 .transfer_to_state< key_digit >( m_intercom_mbox, user_code_selection ) .just_switch_to< key_grid >( m_intercom_mbox, service_code_selection ); user_code_apartment_number .on_enter( &controller::user_code_apartment_number_on_enter ) .event( m_intercom_mbox, &controller::apartment_number_on_digit ) .just_switch_to< key_grid >( m_intercom_mbox, user_code_secret ); user_code_secret .on_enter( &controller::user_code_secret_on_enter ) .event( m_intercom_mbox, &controller::user_code_secret_on_digit ) .event( m_intercom_mbox, &controller::user_code_secret_on_bell ); service_code_selection .on_enter( &controller::service_code_on_enter ) .event( m_intercom_mbox, &controller::service_code_on_digit ) .event( m_intercom_mbox, &controller::service_code_on_grid ); door_unlocked .time_limit( std::chrono::seconds{ 5 }, wait_activity ) .on_enter( &controller::door_unlocked_on_enter ) .on_exit( &controller::door_unlocked_on_exit ) .suppress< key_grid >( m_intercom_mbox ) .suppress< key_bell >( m_intercom_mbox ) .suppress< key_digit >( m_intercom_mbox ) .suppress< key_cancel >( m_intercom_mbox ); } virtual void so_evt_start() override { // Agent starts in the default state. Switch to the appropriate one. this >>= inactive; } private : // Limitations for various pieces of user input. static const std::size_t max_apartment_number_size = 3u; static const std::size_t max_secret_code_size = 4u; static const std::size_t service_code_size = 5u; // Common mbox for all intercom's parts. const so_5::mbox_t m_intercom_mbox; // Information about apartments and their secret codes. const std::vector< apartment_info > m_apartments; // Accumutator for apartment number. std::string m_apartment_number; // Accumutator for user's secret code. std::string m_user_secret_code; // Accumutator for service code. std::string m_service_code; // Service code value for this intercom instance. const std::string m_actual_service_code; // Helper method for generation of apartments' information. static std::vector< apartment_info > make_apartment_info() { std::vector< apartment_info > result; result.reserve( 10 ); result.emplace_back( "101", "1011" ); result.emplace_back( "102", "1022" ); result.emplace_back( "103", "1033" ); result.emplace_back( "104", "1044" ); result.emplace_back( "105", "1055" ); result.emplace_back( "106", "1066" ); result.emplace_back( "107", "1077" ); result.emplace_back( "108", "1088" ); result.emplace_back( "109", "1099" ); result.emplace_back( "110", "1100" ); return result; } void active_on_enter() { // Special signal must be send on enter to active state. // This signal will activate other parts of intercom. so_5::send< intercom_messages::activated >( m_intercom_mbox ); } void active_on_cancel( mhood_t< key_cancel > ) { this >>= wait_activity; } void active_on_grid( mhood_t< key_grid > ) { this >>= special_code_selection; } void apartment_number_on_enter() { // Old value of accumulator must be dropped to allow to accumulate new // one. m_apartment_number.clear(); } void apartment_number_on_digit( const key_digit & msg ) { if( m_apartment_number.size() < max_apartment_number_size ) m_apartment_number += msg.m_value; intercom_messages::show_on_display( m_intercom_mbox, m_apartment_number ); } void apartment_number_on_bell( mhood_t< key_bell > ) { auto apartment = std::find_if( begin(m_apartments), end(m_apartments), [this]( const apartment_info & info ) { return info.m_number == m_apartment_number; } ); if( apartment != end(m_apartments) ) this >>= dialling; else { intercom_messages::show_on_display( m_intercom_mbox, "Err" ); this >>= wait_activity; } } void dial_apartment_on_enter() { so_5::send< ringer::dial_to >( m_intercom_mbox, m_apartment_number ); } void dial_apartment_on_exit() { so_5::send< ringer::stop_dialing >( m_intercom_mbox ); } void no_answer_on_enter() { intercom_messages::show_on_display( m_intercom_mbox, "No Answer" ); } void no_answer_on_exit() { intercom_messages::clear_display( m_intercom_mbox ); } void user_code_apartment_number_on_enter() { // Old value of accumulator must be dropped to allow to accumulate new // one. m_apartment_number.clear(); } void user_code_secret_on_enter() { // Old value of accumulator must be dropped to allow to accumulate new // one. m_user_secret_code.clear(); intercom_messages::clear_display( m_intercom_mbox ); } void user_code_secret_on_digit( const key_digit & msg ) { if( m_user_secret_code.size() < max_secret_code_size ) m_user_secret_code += msg.m_value; intercom_messages::show_on_display( m_intercom_mbox, std::string( m_user_secret_code.size(), '*' ) ); } void user_code_secret_on_bell( mhood_t< key_bell > ) { auto apartment = std::find_if( begin(m_apartments), end(m_apartments), [this]( const apartment_info & info ) { return info.m_number == m_apartment_number; } ); if( apartment != end(m_apartments) && m_user_secret_code == apartment->m_secret_key ) this >>= door_unlocked; else { intercom_messages::show_on_display( m_intercom_mbox, "Err" ); this >>= wait_activity; } } void door_unlocked_on_enter() { intercom_messages::show_on_display( m_intercom_mbox, "unlocked" ); } void door_unlocked_on_exit() { intercom_messages::clear_display( m_intercom_mbox ); } void service_code_on_enter() { // Old value of accumulator must be dropped to allow to accumulate new // one. m_service_code.clear(); } void service_code_on_digit( const key_digit & msg ) { if( m_service_code.size() < service_code_size ) m_service_code += msg.m_value; intercom_messages::show_on_display( m_intercom_mbox, std::string( m_service_code.size(), '#' ) ); } void service_code_on_grid( mhood_t< key_grid > ) { if( !m_service_code.empty() ) { if( m_service_code == m_actual_service_code ) this >>= door_unlocked; else { intercom_messages::show_on_display( m_intercom_mbox, "Err" ); this >>= wait_activity; } } } }; // Helper for creation of coop with intercom's agents. so_5::mbox_t create_intercom( so_5::environment_t & env ) { return env.introduce_coop( [&]( so_5::coop_t & coop ) { auto intercom_mbox = env.create_mbox(); coop.make_agent< controller >( intercom_mbox ); coop.make_agent< inactivity_watcher >( intercom_mbox ); coop.make_agent< keyboard_lights >( intercom_mbox ); coop.make_agent< display >( intercom_mbox ); coop.make_agent< ringer >( intercom_mbox ); return intercom_mbox; } ); } void demo() { // A SObjectizer instance. so_5::wrapped_env_t sobj/*{ []( so_5::environment_t & ) {}, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } }*/; auto intercom = create_intercom( sobj.environment() ); while( true ) { std::cout << "enter digit or 'c' or 'b' or '#' (or 'exit' to stop): " << std::flush; std::string choice; std::cin >> choice; if( "c" == choice ) so_5::send< key_cancel >( intercom ); else if( "b" == choice ) so_5::send< key_bell >( intercom ); else if( "#" == choice ) so_5::send< key_grid >( intercom ); else if( "exit" == choice ) break; else if( 1 == choice.size() && std::isdigit( choice[ 0 ] ) ) so_5::send< key_digit >( intercom, choice[ 0 ] ); } // SObjectizer will be stopped automatically. } int main() { try { demo(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/intercom_statechart/statechart.jpg000066400000000000000000003540411523065210100256710ustar00rootroot00000000000000JFIFC       C "" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?SK)=&^[խӟiֹ*7Hg#R9,@`(+Ggw{d\xzZcE>~>Zr'Ф^Ѽ4@ x2m}GS( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( x5Ƶ}Jn4Ǐ D};5_;~߱Wÿ¶vZ4E^HfBHn`FU,௿?|K]_m- &-]I}B$11^m }ZEF~-x" >>oE;h fbv_zs\@*zf` ӱ6Uxfrie%K 3UfRAk"lW@U I/ |AHױӱLIƴ4;zѾ;|=|e`}djZ|;uvkz)ժJ1 +^W~ڂ$jƥqui]]X50ڬNIX1 I5꿳7 {| Hc>OF%kc-mci 3PM9N1%tCp}QtBJFt $Y9>%/Q?o`Ҏ 3}^vLV#4`ZL(гG/<#XWoK?ō3槢xwz0xng EiMwH`. 7ǿ|L. j^ Z]Emgx-=+B([VQ?Ajk }D|IO~k|,x>i#ElC"kqyHLHqwG퇦Zu[:ņxu?1=.\Agvr1v|U W_(]q߳O+G}CP E\ԑڇExV$O\ 2/'J_CP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚alc 8 XCIN<~" 6-Ta53y3$1,tPm3R6Xo";krKdu=R>׍~m:KiM?o%QEQEQEQE ŬW#FjH/)# U()Q@Q@Q@Q@yOP5^^Sj? ciPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE|So\ėwvȉ:dK(U+.e=ͳEkuq$  3 hZg~9_ڗxmۄ{;Hf%#EEhKYnz ~i _xZݭimF}V8(Č07.7 EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP<:Gfɫܫf"~zK&r((((((((((үV)5ը(((((((((((((((((((((((((((((((((((((((ٟH޽ɫܫf"~zK&r((((((((((үV*;F/-ž.sYZC,W+%'U(((((((((((((((((((((((((((((((((((((((|߅O,y Eie ΡvCk7+{HٟH޽ɫܫx:¯ x/W}KH-m ^EiKתPEPEPEPEPEPEPEPEPEPX-gu Ś-ks*ܚU+"u=(Ư>+|T?r#;eK!\R_Z)E`a<#Ex)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-uxZ>PڌwrK3on%O,dA:?fx^W2uG9o2JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JkkQ[ϼ(JܞW2;HGx[V*5kYYRy  vOXB ש}}f"1-yv>iUF@REPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP^USYz%__.c=@.#KOit[2mk1$ i3ĚmDꖣVa ΛD`5_ e>( 9uM:gDm{ XS$#d$_gu<3<[{a6wwuqE,L`[x>/&%s9,Uw}5 iU]smdP",zez]m*}s݆gKx"7H*9=My>s^/Kg>R7j2xZ;M?«>ݯe7?6V-x3ȥq^k$dž+K—c R|(ggxwWJ(:ޥ ŞЮMgx~ВCNe~s OkYZj:? ֗ki=ޭo w#xfp A S? /ZԷڝ&ANX3Ȉv bA\ntZc:dgkr>UC4i+ *P>P>PzZ[i/j5b;GڭV#;] OqZNc.9g_8CA>XfMXѴGPx'k3*'3[D_bematnrT@' ghޞ΃q9x/#("/~W]~+¶5垛C+F W}/w~u3V[lYn]Y\@VIpHZ5Pc8g;Is!xGZ<`ڥGO D;}mXm[P"BM eYHqbzsx-u?]tK5Ωc7B[[Z;`dd>Jv0\Gyg-ߏ<;ƒ閒j,uBۥPA eǴNq ^JpdMg $#Si5]/iOihZ;hbx.)nUxٕ|'|-z~>j^g_.$ɇTGAz`p:sfTR($gNX/?|1{hWR䷲#&\dE;g|:7{g}7v6ӓDʤHϵyNk^L &X6i,ˢ]jo)v6*IjGl̊C3)HX'4A1|CM*Anmeh՚@UOCIyGᖝ_|1m kͫۥs'_ω+ G^. ZΖhlbZlQ9ZM['L|u:Z tn8!{HV`denzZ>3X^_F5qyvf m?ۓsp< >!OOa]zHl44? ^h+cI*Ikq%av&B#R޹߄/Z O\W%kNJd0RqEzQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@yW/GZ/~ψ֗fUmo6he@"0+) -=_bZwd2Z@a6BQv@:=מ 4 M.Aj 9 FyFU`U {ǀ ox/B'xt:Sq'?x"E XC|Eݼ<3D9]XGA]QEQEV<^tx쵩eR[WKď2ČX{(;E5z߅^V-喅k%28= Ɗ((((((((((((((((((((((((((((((((((((((*/^^=Xu}_}ƓZ-Ęl 3rD?+l<*i\ˣViu-;l8ܬ AjQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQ^w_/s (P?dik=F B 4r(*\~VVsoK;L;KX ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUjoW%A>ɩXV:ŕ,&-{ t62r½VQ?e x?_?oM?s,Wor8#4|,@sClnzFV2\,uU_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]zOo|Y\|-['0b)YZt>K-Ҽu};Ś*GknbpJ yWFj(((((((((ONWV} 8?+h^^Sj? ciPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEU=gUдoPrus"8ԳP_YZVWu+5OѷGF2b M_ |u_ x_:7uKKm}]FYvgTġ7d>E>IJ?|8xoJ__J,3#7l3[i+|#xw>>3]Y_XY]嵺 `̠ b:FhzwioeKQUҲXGre`N $9P}I lߏCº;Z8DG,FE$(^5.~(/]'~Ҭ=+KPT"\40󶣆 q[6mlj)Bv]-,&[Y2KKR쵡Koω~#\\಼hm̶ XmI$[t >5|G ƱJ% p\l`OK@POsa-44{Ez2+6U~&|JaMJ}wV:`wa]+ibXflK|c9w+-?*Wѳ(֙P4s ]J$DV9n6!dyo>/#ݟ idԼUeX\Gv/~-yNC䜌m»GG+q_hU| 5^֝ YᲚ Pj1=ĹP% #Qgg/ ُxR-e+Zx O.q >- \̶hgWF#!!w Yx7W' iao;[\<^jD#F(; 3ƀ9Wѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳw+-?*VWѳ⫖lm:ǶZCYn/̶{~_~)_?{M{E*h_REPEPEPEPEPEPEPEP^Q UJ:V# W׏|OҾ*_5Ķ8&#*BhXt;ǶK6[ZL1,6I+%0" i HZiB|]Mu,pTE!0¾|~.|j#Ɨ:Ix+RA KD>s$Jd ,rO:X|) j_5{<ocap$VmԺ00RKzշíTՙdkzq(8<ɦq4DQ&x%AbC? !=`c jӔ*[`28x/Ľ_L?jYӬ?ee1PӢ36o|[ӠD'mƎ X 6/Orn\=iYv@;P Fۛ+˫v B,xwe mr3<ַ°>Q&[!+>nqhKÚM?V4xCz_/"{o\{H1YC gq U]w_.u֯ [bY_r oJ?ho&RkDӝX[Э,w;ͭjIwl[,+Ʋ[tB3 T< KAW/b;y[@=5R# g?񶻦xr"Rv::" U1<5x'c>N.u *;8ugXUA\:l> 9.k^@ȏ42ĤG~^Nj#c=A>66Dm˸b@E#^-ů/a664rXOP+-o,/_B+n$,@챩mpYѾ BG{;PVQ{ieiIbfBÚ7mmtK?Ey{qNc2JbqxVQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW?JG}|HWT6Z=qK@Q@Q@Q@Q@Q@Q@Q@Q@yOP5^^=bKtq,q4c;4ZLIxY Ҡѳg*OKh~nk;`]'{`&Ιq~Oøu/ ȚŴZ} KlB(o@_h>?<?m/<#v7v׃c-`_9n#]2\drKbAW xvZjXShl: ̱DE>KgI0yb&/C^"YѼ=J[]^5s,O)6<$X%cGzԺԞ XwK o.eFF&UuPPF@;k}s헄4~ T|9zb0H]F>q^ [| [2H,?],LIr1 ?A+eW?,|g⫯ xoTԠ֦툱Hx(vES#ƺ5_%4N]vK6;VMd>c ňR>}}o:Kɢ$r`ʡ]p Minj,>;!cMݶ,OrI<g.H[;In suy-F#|͌` ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( #_iWW?J==qKQ[Ǵ_/h(((((((()5իǾ G6R-.Uq-E{ Pk˛$F jQA@%c|zXk/ x̯k]Rc vG$.w"::G|wkǥz7/na{cCJucĘP"fJui徶յAZxgxJ3kZg{-E:~oլYD:VFA |՜&ˤiޫm?YA55YJf-Oᧉt.waʂow,ɶ)9a!dFu\ɭ<5_xv]oTˆMO̕Pfd@( :|èz?-*' eoj[Iʭ0s}1~h|ci:{h _Sq%5O+DYa`<-[s3I5Hlch@Aw>V|q1%K+_%uK[nncV;Bym" .S,|uw]KF_DٞF1q@J]/\NxP6B,Y%s,46cX N+ϫik[Ŧ% ~2kh5,aq^O|#|C _(O CV}RimkH6T@B֟34#Qw6n,#T>Uzyl\`t{xOcm/T{E* ( ( ( ( ( ( ( ( ?QkJZ(ҠV((((((((((((((((((((((((((((((((((/~*?.kK=F4iRkɮc# aq,zcFO/"o t-(͒ݰȶks&6\O> xYwAխ`մ54(n.4 v.%Rdb,!l?gzqok}^xJ1AFIt9YG)p:3DFni?W3EdJŴt)]mXwm8$/h/85oe{lHK{Gy Z5Fۚ'+ a2/į|H<3j:c\\i2(3#2*|;N1_:>ZoB:mj,gmBHckJ1fܧp'm^.YſthWKt$ F0ʭ+1ɍ~|2^Lh^J'öXN{:2cn%}j ( ( #_iWW?J==qKQ[Ǵ_/h(((((((()5ի~G*j(>?o3F xOI׾ ukituJR8eHԮK.v jj:gϽ'653wVqn<}Z$m9 lo u-9tORGmhw w _χ\\mKN}`&!FJFV %Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@C5sÏ]M&r{P/UPIQ=,‚W ?IK/WP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uP‚W ?G(/_uPoމgǺ<]ajڲ[Om:Ȳ+FюpP@#^^Si j׫PEP^#S>$*#_iPǴ_/j+oU-QEQEQEQEQEQEQEQE?@?zyOP5@EP߶7U𭝖{6d׭<$yВ۹Xe,|mO5khx)DϷCqkaчZ٫jy.,ErP|2捶mR]m !E`qp%fgpXؐ(UP_Z*G,?kM gFIk7V}6ID&e]4hV}"-+P7b106`!K39 (_VQ?Ajk{m7T[ 5 /|?an |̮p渍~"ZxB ?#חwi孿$aq"\P‚W ?G(/_ui3x V7⹮-b:ۖKhDI}8f!={<2|Yjx换ijDտD g >‚W ?G(/_uꋸ,8 -yW(/_t‚W ?^EyW(/_t‚W ?^EyW(&ϊ?ŏΡ4?|Qaw.`q͝jB@,YC72J">EV5-?Zm5&+#$:GS jx|ͶdۃZ"Q][]%&2_gݵhUP_Z*G5]Cſ.4{oӲQVHOeT` FsV>#~$}?-|*cp +cVnhoOO_h^<]}A+{Ii$TeL9LF@=VQ?Ajk//Ђx|Mu>4NDEt{=A|q3ß 4iՕ: vXl2ێ.ţj:|Z6yaoyM%U>8VÍC ڲYKo $1G*O(G1@ FڪNҼT|?9ѼcZx!nEGtˋdy5Q`UX-ɼ#;HUP_Z*GƢ,$xt-zK uYK>apLĬb;$nTd0;T <aš[GŢ _P[KP᾵\V=R(AjkVQh | Ԭ4h5"M Y/οg1V1&<]Gu$V+q_hT»G@1<Q xYwAխ`մ54(n.4 v.%Rdb,!>- 4 /EK %*joH]Xc=85>;Ѻg-a?k~0{{[ čV 25L+FV>IKњ& > :IYZoB:mj,gmBHckJ1fܧ]Y_Kcqzt[1WEy(4~#bq;˰ 'o7O?b|vt˺UwYgOi76yW=!h`)# ׁï|-;y|j>!wC6 Ò(菀?QkJZ[Yֵ_ xT.xP4%.^8[9Yv9dޣ #^@Q@Q@r~ៈmiWY:%E2c:Jk<Ŀgÿx]V+KS:,Riw0_k5hEyV8#^횮x:S6XmEy&5BooxŞKجa4 ;kmI-$F14@%[kIYx7ԯc>}y03Ƒじ(vxZLs/ts~ y$H9W8d"8do.;Ki@[KfzLG)}RNkh,vxOPּGvrjr6~ ~Fc]v(e{U&^'w<)uoj>OƐJ@, Vz(O&|B. %4@5=^/ T9[0/I$m30{)|=.k$ ψ1xZkۘ#vif 1JXr9(dkZ/l<[OxT}j7vo_6xDVh$sK3k~xsF|`ԛYִ+A$Ui#dPÓ;% .֊(((+fs#^dUO_<=-'—~ox-'ī<Sƣpe䒾T񴍌 P Ծ(7Eh5 QbԤjH)@1v*T.RF*t>;Ѻg|~qeGڵ#GĽo+ /Gor+uX]X'M{QE??Gүn)_?{M{E*h_REPEPEPEPEPEPEPEP^Sj? ciWWeXTQEQEQEQExmB='LMv~9.Pe"qZR/o ; xktL[="G]yDR+|Ua&)|R;{?:ڲXt;ǶK6[ZL1,6I+%0" yGω_<)OjP!4˥m<5P1͉r/r3fOosk-얰Eqm`iVw2syǿu/Z퇆uk]]6/j2j0ɼmĶ_Ht-CO5FHO6زiH*rae5蚏j$MښXn&-R+; "{DA;DI0؜?~{.R$zqZF5%.a {7X-xRKoTmml.RKKKGFyᄱLhWwNO>15-KѮ.׊;Qc{eHW|VsmxO6M"|~}}kۯx~Y5=OBAXoR+Y ;VQp0'  g-K^:o\l$DJ.R`Oȸk "RЭ/>ۨQwWyqFq)g+m@X&(((((((ٟH޽ɫܫf"^=0>LAr((( ZΏxIеoX=%D8)*Aޡ߆<5þVM Ӣ<(үV)|HS|@=2!# 5QEQEQEQEQEQEQEQEQEQEQEQEQEWq7*+YXd07WWD8{1`}A@/x76v> ΝZ_]5|  ;AaP7I(((G}|HWU??GҠih_RVZ((((((((+~G*j?QkJ=Z(((($u ;Xx,4OwjFm9ex* Aini:Ɵm}cw%)PWF0>bWm!m'~[EX@H~=4|? ???Mo^+ÿ~ К445iyhh~&-7}E\ZhZΧmfIs}yu~-’^H>m"m߲S_u մ y'kxB.XKyʥH\n}Exw3OO[vf& /?{??Mo^?~ К44W45iykω> Ӭcci/ U[*26鿴Y_LZ#h4)E|k> . zͨH5k8[ґLѬ$d˖h9cɎT݆ i)Bk^ S K;@Exw3OO[vf& /?{??Mo^?~ К44W45iyhi)Bk3f9,U^Ghƥpe2Fȓ,Ǡ<3G3Ya6xA/5+Ï+r&!MK CĚ>)~8ǀROM5p2EGb vJk;9&Y$٠(((($u ;Xx,4OwjFm9ex* Aini:Ɵm}cw%)PWF0>bWm!m'~[EX@H~=4|? ???Mo^+ÿ~ К445iyhhi)Bk Sq; K;G3OO[v=Ɗf& /???Mo^+ÿ~ К4|c!c 5:5oR]#ݰW";Tt@WQ^ S K;@Exw3OO[vf& /?{??Mo^?~ К44W45iyhi)Bk^ S K;@Exw3OO[vf& /?{??Mo^?> +yg;:rYU$@# J%!Re&Y@r~)~8ǀROM5p2EGb v_ |9[4 tn\etPv$cgg$ҫ2@4QEQEQE??Gүn)_?{M{E*h_REPEPEPEPEPEPEPEP^Sj? ciWWeXTQEQEQEQEQEQ\*zԞN7|b]UKw2@!uɊ)v:tkxYtW0@؅Q5__.+_@Χ9[KqrBrM֡xֵ|Ce [wJiܶ7=8^|8е+X;}eIhn&5T$#$B_z-QEQEWο }?WkW%d?6PnHUyW$KDZȠ ٛ}b+mcַu ]*{kCp%5-Թȓt*[/?~US|Yjܷ[vdz]ivU((((((((((((((((_Q~6>x<3.;z}};3^h~]2df ss*(8#p2$W|8W_=6M#إ^muWF=?tP<jڛŠJkHIJݥ՜P$@)>{Ƨ.8'Zhs%ςO-oLjo!ѢЄ$ sanm^OQWPVr.YwsK1?#) Auْ {ijSF1Z rT<2OK昭A'P܁~/xmuԍ>#O  yt\3oba>q> Kis~yum.m 6p@8 ? CwW:~mxsMA<2iS$w¤}6FUK27o[xvSx}5.n &h7[$jΣ17B>:|CK+h6j"% [|Dx^uScek:ހj$} K X=JS ,s$1K+Yr]ؖbI&((((_-:ZЯU*?iWWSu1š;zx.mSGoǪו~)'O3H݂JV=Ea^@Q@Q@Wľ/G~9J:T0Omy1ZZptу!i2BjJ+ʿDվ?|UR?(P_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*G2/֭м0*˥=Z=^[)atߊauWĺD!SjRSϗi <6y+ѵO4#W6זXԫH*A 7JtK?7N]X`5 QƱDUB@EyW(/_t‚W ?@EyW(/_t‚W ?@EyW(/_t‚W ?@EyW(/_t‚W ?@EyW(/_t‚W ?@EyW(/_t‚W ?@EyW(/_t‚W ?@EyW(/_t‚W ?@EyW(/_t‚W ?@EyW(/_t‚W ?@EyS⿂ }[u49V์x%nV9ûgtx_Nf0C2:Uчjگ)_?{uxOcm/T{E* ( ( ( ( ( ( ( ( ?QkJZ(ҠV((w]/)M}4H>ܪ2eXwKO!\H:_.֭!G,L Q/,xH3PIpc{Վˌ6?|ckD#aᱶGۺW|e*߆|?Þѭ42Jkk4,,Ky$I$Ӣ(((((+ʿf#\ ZU*?_ ;|񧄴P;tQcne|?Þ:ӶzMӑB?^U5Ļǂ_xX'-me͛ڶ;n=V((ONWV} 8?+h^^Sj? ciPQEQEQEQEQEQEQEQEQEQEWUx#yo[ S><n(Tw3JkӫsDk^;guMxcRT4.&H9A\ #4s^嗌^3MuIʢ*Ɗ-7lYEz |.tHT-ͧ|A{\F3k_$#ʾs! 3M4Mo=K? ^>9%'`Ud <Ɛq@=|^|GmCߊu}'LCPi־$o^Nɸ+7ȫ'%jԷeiFA9ϊt!,U'`|C$|8) H &N0J99R1^{V?tk_ FS;A-0DܘU+lr[dִ }s{YWBi0ۘc['v@CGш_2;ex"̓-`'r X- , Ƭq]>?`-<|Qc֡|c5eBzVU*S'Ï}|Gk^KjX/#PEQEW <#GY~%^O#ǚGE7> ?QkJo8l?]vFU<o8l?]vFU<o8l?]vFU<o8l?]vFU<o8l?]vFU<o8l?]vFU;/|_9s{ta bbBe!F9|P WZ6U;gQ 'TږvvF?‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(/_t‚W ?@+q_hT»GG(&ϊ?ŏΡ4?|Qaw.`q͝jB@,YC72J" »GG+q_hUfZmEwc}w66'P{} Y*w+-?*Wѳh*w+-?*oVӬ<{e_>;Ֆoo-~U #_iPǴ_/)_?{M{E*o#_iPǴ_/j+oU-QEQEQEQEQEQEQEQE?@?zyOP5@EP^Sj? ciWWeXTQEQEQEQEQEQEQEQE_7/"Vgg~=+$|YysmCY $%iHm+; a9:fso70CzGq;h?fқ'C$dGg $?7IJ_o5pҶyd>O;ݞvmW|[o;mKVף_Zn',"%1~Λn'-bm3z{~1vc{>uK'a} rc4^ |C⋯^LExRS:rzN1O2X\9d_0|='Ś{Iq ynyݒR"(V_/ ֟/W a; eiH#W;6$g EQEQEQEQE_|N6fnAk#?E{-xoqSävk,ʀ ( #_iWW?J==qxOcm/W|?J==qKQ[Ǵ_/h(((((((()5ի~G*j( ?QkJZ(ҠV((((((((*?Ǟ8_U*wO:ԧT4QEyWŞn3t_oq7}G^Έ~[GPWg=7}V ( ?QkJZ(ҠV(s a?Wי;+I7>-6U߱Wÿ¶vZ4E^HfBHn`FU,w@GG4 Vh`-q]=蘅bbFO+F[Ǿ㿅܃. j~˿>yy~שw{MΡrT#U8QřJV #? GjX~ŏCņ쿵jZr.o[{;P-"L/-x:[;4}Qee4 /;1b2b 0C䜀6?]vFo8l|`|_R<3+o jztmg'0^Q/Fc%;F~70jDӎog]Ze%R5v:b;A»GG+q_hU|FQĻ~Ь1v :kw4"E^x9 n'kDC7||̨YsY=h?]vFo8l_>"xO=׃|F+[8$%[hrG\S/4W Zڦ~FЯ7k)r"=co8l?]vF3/ ?ιό^?~%VZoGF^VKwQpO8;;gQ WZ6UX}4kƑf-D2,yH gkeEt|Yyͫimkl7H^I-Ln+ {K`»GG+q_hU>&xiIs+ O4g|,;T$2^GJ$ڿMe:M5tL>؉_po8l?]vF_¿>&F]xy<;E}Henngˎ?:E%|2?7AC=w·׶ HN7~਻s")c;@»GG+q_hU{+Xh]Yg $]ylHT" MƏ[hww7Ju %G9md&N$ep2u_;gQ WZ6Uq xmMK7Ƿ5rjpG=vL\>67?E~*w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳh#"ݬl 0-p2y^^3׿Y5{QE??Gүn)_?{M{E*o#_iWǴ_/)_?{M{E*h_REPEPEPEPEPEPEPEP^Sj? ciWWeXTQE?@?zyOP5@EPEPEPEPEPEPEPEP^U6qq}> x6zyWǁ@M~uŌR)6H젴Ѳ!#|ᾝk'̟Ez[I)MCDqDU̙v!653{}V}w֞.B0O%"V%I D¿\kY\#\j;Qh%̞jXb!PݓYwLƛxq$+´b@'29rxg_:o5/G5 ݭ&Yf7#Q"RGh`gFuYw7-ihbI,"x}&W{4kG|@}Z[ԳKTNI.HOE$IKH~cX~̟ٚl:x]Kŷ\gu 8$iLFҦؕ7/LK/h_EVJzfl1",aX7]R}Z{rbPe}q $PO'y7?xr=j N]SėVNE<-#dh8(8IE4~6G7zuլiZʹYbV"5^4m-69Pލy;WrIОu{ۉiL1{݈DW8* ßi:^wYt6KV䭃VVd@d`HظUڪ 6|i֣{Sn!N| #U۶͜la`-?[|hO #L k}m[A#|c@=5$x_ID!痋8|h>n_ $8  !y:m:j\Ĭ3<++Tf(7 ߌ|K z<x繺y]Q%[`ɳ !.Nݻz)gn,`ߋ}.EKimcӄO+DҸ u)FD K jw"Jkk!qk][J/%LjϖX2':u=_ixʰ,r<>@Q@Q@Q@Q@Q@3׿Y5{xo|̣.2=Ԭ=Gr(G}|HWU??GҠih_^-S>$*oU?GҠih_RVZ((((((((+~G*j?QkJ=Z((үV)5ը(((((((+ʿg<E/_?ֽVr^(_O/uhhg,iwZKy=͌\i.XI9j_\$9=[Ï&,E0 `(q}9ao'N9^rRj?f E? luDRX*c:9 FJ(=C`A9Q|8aQ  "1-~ Iy џeYTZ;#ǟUάXXJw쫨i_‹RkY5ܟ.  OǹT_?xo¢qD% _AO)BޛbP`9#\ q{\~̇X\ ݇~TW %GHG8ɭ/T_?xo¢qD%kzGR^ SukӢ[&ʪ _ k2/*qyx̣;|HIIݺA߈n+[hFdo۲K!2T>>;5t$$7֯oyh,sq+B!nAy ;Mz&{2ɫ0}4O*h|6GiKw*\yv}fEl,dž9#bg7ÿhZ޻sXWJU% .z_0 `(֛/>k:Gck2$ݵS^.iX4/$+K)~ͷX0 `(EK@>Km?l|,_u  "T_?xo cg )~ͷX0 `(EK@>Km?l|,_u  "T_?xo cg )~ͷX0 `(EK@kRf@'Yq oYwo_x=UXirO =lYBX5GOJC/A,ic-m,p9v4Wo?/e&phwΣk*3ܲC4c` b6rŞ֛?߀uK WV\-Pgc˱x^W}k¾O_j}wsh7,LJ8\M.0C >4dž*AΖH[B˾Y`?QEy߉|KxÛ4C hGM2) 4,檀DNq[[Y,3l-b~(U=VP_Z*GU=VP_Z*GU=VP_Z*GU=VP_Z*GU=VP_Z*GU=VP_Z*GU=V!?||>1f:>Wb-Mw6'dW?|UeWZҼK|Iy^^qG%͇&%hKEVF9ynKОhE<|0mydW-gs];}OFԴ;oRK%-x̰ey_j䎺Yenu67QX)z{֧(/_tKLѮ`P܊B[U|3_iڦ#yn4닽b9dcn:?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUWgP{HbPOzt(DV}fѵ!jk |ə%id[iiƓyݍ\DےXC# A5f ( #_iWW?J==qxOcm/W|?J==qKQ[Ǵ_/h(((((((()5ի~G*j( ?QkJZ(ҠV((((((((*8h xshh+˿iiq4~Ko[" K\~mvwQږgZN k{ txJ$P0^[Ewk* G"V{jZ/٫P=NfPlzM.m'5WeXU?@?zQ@Q@y_It iXxMiXav H"mKC[=:F ˱~MM1_$Aݟ0tZi5 xGwKMOT/ -$ncP^\.蘒K懡Qt=;JQ^4K>c,acDF1LQM!^6aQjTkK&{iVD`p()|Uդ_>g1WWeXTQEQEQEQEQE3j O_oj>;jKf LّC zr.c wE}>;yb灡q0d%V$m*sԑyZ(9? w@|~\XK}Y/&mel~ |Y NM MU{X^nRUHemHaU,6Q)Oj^)+q}aw}$bHmA GேW?Ý"VRu w\/o/r#%ىp@0hӮJ^Es<"񕦁o&#*,'b$[r0ΙokwQqro' 6?uG`V}>b|H R7<=׋>X|NU|_{Y. c~3мy;WAMKPQYggu24` PQEQEQEQEQEQE_|N6fnAk#?E{-|;žG"tM6WH" o'rcu=|+l΅0XG9=.{t4_6B Rˆ Q@xOc۫~ ď@mʼ[}|HWU6ž ď@mʥ/TQEQEQEQEQEQEQEQEWeXU?@?zQ@yOP5^^Sj? ciPQEQEQEQEQEQEQEQEW~Ϝi<_O n_U((.g j|AaկuֆnfmȈL8@7VQZ(ʿAjk4#Kmoj3osO| (ҠUP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UP_Z*Gh*UzN/wcƩo<ذsdrI55?@?zQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@_ ej:.?5=Jio4[P{yl Z}$/>W UxM}w+i9R-P|ȥ#&n g'j*P_Z*G񟃵υ߁5ϏuxIյeudV G"ӏ(?ՠV)_?{uxOcm/W|?J=qxOcm/T{E* ( ( ( ( ( ( ( ( ?QkJZ(ҠV()5ի~G*j( ( ( ( ( ( ( ( G}> k_^oorYU}Fs gĿhh((+~G*j?QkJ=Z(((((>+CxT_|  ekoy g?<\[pp~b=V|[ J/4[}F9&UO42)VD*z@TW^|Ӥ)Vah~G7}˶CnS‹^% V_?(]nS‹^% V_?(]nS‹^% V_?(]nS‹^% V_?(]nS‹^% V_?(]nS‹^% V_?(]nS‹^% V_?(]p,?ڟm ȷWi_ԴW•O /G)[-tW•O /G)[-tW•O /G)[-tW•O /G)[-tW•O /G)[-tW•O /G)[-tW•O /G)[-t:?ÿ ]XYf@ B$oW*' nO $x#kx_<-Eε۬ox[m ]'À,!ch<WG>iڄ}ɯ`"4}k+ @h9c+v N?hVZӏ(?ՠV)_?{uxOcm/W|?J=qxOcm/T{E* ( ( ( ( ( ( ( ( ?QkJZ(ҠV()5ի~G*j( ( ( ( ( ( ( ( OVo?_l>oA+/,4wا9 n+ Vk]Ewa[ukqnIC# Au_ּM:S&z>xĽѧKP;aٛzQEQE?@?zyOP5@EPEPEPEPEPEPony0G|(5| (5| (5| (5| (5| (5| +ßu-gGtF6%"1F~Q9oEy/3Ϳ??P5G3Ϳ??P5^Ey/3Ϳ??P5G3Ϳ??P5^Ey/3Ϳ??P5G3Ϳ??P5^Ey/3Ϳ??P5G3Ϳ??P5^Ey/3Ϳ??P5\+ƞ#>e*TSb$En^6h@@gŎw?? {d6iVz,ۋmjkyo d%$~fs#8yoLx|Bˤ@/Z=6+c ][i'֘&,~,3 fs“l~AO:xI%IsNFh_Q #(F HNO^ /<wttVI7\K_0+:(c Kπ^i5߉vM7#eǀ$9x]qW<9K cdg | c9ɯK]g @Uitv\3[7Gő|J*XxE-fo핚@Dx Ie =i$8 ]h$?jOZ7wk7xiRBYt n^y~ZV~,J*G~[D sT|. x<9}R71M +diQxe|(R"|TnV@4 W_(]:}[s|B4;ԭ,mq(#UK,H@#:()_?{uxOcm/W|?J=qxOcm/T{E* ( ( ( ( ( ( ( ( ?QkJZ(ҠV()5ի~G*j( ( ( ( ( ( +x~MaMݪDVk _; s?$~&_t .u(EGvs`@Vd'նyk2k}iiw%g,$; O@je>ggkM=5ffb$X_? ׬"bLQkص{w <oT5^/> H6oh61C/n,Q"vT 9 @hD طل2W+(e`AG~3)sS}pԵ #hnZ/3;BH;yld{=ևju{f>\m,ZK~pX!浜QEQEWxG#Vocm!+wG/|iCEfo2\$ʽ&E!rJȅ]OB(jπvt4,-܈ov5_Q+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ+ĿJkkQ2v4!];>4~+;fhvb_$O=kJkkQ/|%WM5 3𦋨Fwh*`3uu_nS‹ V_?(]{m_nS‹ V_?(]{m_nS‹ V_?(]{m_nS‹ V_?(]z|COC/uK}7KӡkQROI My=S<qv7ZWēZܦc{x%\nD$n7?ixsk7$*PxOcm/T{E* ( ( ( ( ( ( ( ( ?QkJZ(ҠV()5ի~G*j( ( ( ( ( (<¿/~&U5,Z[*&fI!g+rd%/uT֓y/J .5@оFFQr(PIQ@kC.&:Kb+&gEܹs|g ˘H.╭IE ` +2v4!];>4~+;fhvb_$O=k+J1{CKGKy$Эbg׌@J$Ў7%|Hs'4WMyqh,V2#bޱ#>h5_xHǏsX]f=6..ebʑP,۸ oxWGd2KlB8ρ|-mVz5}R;8P,d]\'SK(/W$?%? ` K=F2p ӓUxoF񗆵_njm>,G_l@W|6(lok7o_I<Y\@HZ]Y͜mOb=^ ( (FȦ{sɂ?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxG٭?TP_fxOTTE =)P^#S>$*#_iPǴ_/j+oU-QEQEQEQEQEQEQEQE?@?zyOP5@EP^Sj? ciWWeXTQEQEWľ/G~9J:T0Omy1ZZptу!i2Bj\~VVsoK;L;KX Z+ʿAjkVQZ+ʿAjkVQZ+ʿAjkVQZ+ʿAjkVQZ+ʿAjkVQZ+ʿAjkVQZiU/A$O.x )dm9 y(/_t‚W ?@:~>!WOipZO_hZ5Ưoeͧ^^c"%CO:-.^//o~/ҟBgįz֛$Lz 䈩*꬧29 W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|U +=$@1cUP~(#ӥE5߈%Ƨ6v UXMg;E L+Lp$gi֛ii7]A͵M%2:C@h(((+~ ď^^#S>$*oU-Emʥ(((((((((үV)5ը+~G*j?QkJ=Z(()|Uդ_>g1WWeXUQEQEQEQEQEQEQEQEQEQEi߇)+Y4$x*JXnq+?^4x{W^(l^>񽶥In҉XJr 1? hh_xmt`UUF :0e s&^Gx6ZuBU ;[ 9aIaAL6GݓXf&ֿn=#º 3xqa-x/ɪI n`emۘ&Mi{ŷxZwtx%x5:w|2yј!%ʧ8cG5$^'o7ſWhq#O^6֖7xR<:-|Gያ&"[΅N0<$FWbs_[z]>ϦIEF C 8wbA[ |{^|M3WIޕx:~hx;)[Ww[>nyw HF2F}ğWïڵE_n& Zγ3_nGn̿< Cg|GYfK-&mJCdf;lٚbI1$'T [S^{vn',@nĄV&w  xr[xȤ_p (!I@'3wд]DǍ4{OV:;UHE+b!pUќ:6|g_:Iy%´y@E eGw"-Cď|u x'~4൚^iæ_7# R= &U״?Kzz.-T[|-s{i:mBFeK+ƅU^*d<)xW>57=\4R۪$\;Τr9?ψO,SÐVMlSԱ?oD e"Gɜa{_9}W]NP6>Z^qN]x5vZx:?/uH3ɃΑF[h /Z ?~+ӯ<;:EC-[>WeXs}xe>⎽xNK="}>v\^KG ̭221S,!rUi; u;O_>u^[\ X!XIWGE#O#xVS*VH!ZԦYNgh~o AѳÊoA4/xƾ |Igⅿԯid/.8RT(r$47`﵍CCxQȸJ w$c(`kG >)xs^<;wsS4`̑xEGleQ|q& 4ox-o5$𰿍4nܪDȬ;yR+'gl@s㋿ ç]iZwudfk8!"PHCp+@AoSYO⏍]z[e)ɭVX"YYC ܐʌWigWxO6 S@oxVŞX|@ XVgB}r &30w$|e/Z ?4ۄ $V\)66L((*x^G¹|/&*.#i5|hnmyr/ =OY{^𶖒kIgi٣R|#@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@x|ͶdۃZ"Q^^3|K&r(((()_?{uxOcm/T{E* ( ( ( ( ( ( ( ( ?QkJZ(ҠV()5ի~G*j( (>oa:γ*>dik=F B 4r;o8l?]vFNq[[Y,3l-b~(UWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GUWѳw+-?*P_Z*GÕT q WZ6U;gT:G/,?}*+`|;*|>[}OpD[IBr>YO=+5ը((+>2~Ց|(mxkCi(B]C̑sOCsp2$Ё>nk~+|. ƚvY^۵#w. b @49|JA|)I@HlIH ,ac.dIk]xxiVcw-mKd/qI'a5'|}nƷ#|;{;S6[YGApH$B#7No¶6hڝhNw"$\h̑[' >> Qݬ\k UYOss|U6xLծ5𽦦&r4R1;+3Zc c?F_H_l&Ֆ,->OʥJ}a|usOiAG srT]˗I X ^⾛gKJ pw}w:AoEvb/vB ok7>𭕮Zt7,HRIUݖ˞1xb|9i7 W:V,vpʲ4['FThǏ#փ~úe8nuIREHEW aگO;{\juዿ6>)v-y^k|[ 01 ]ߡ^DWokm=~Tuj–2 _so͸y2>.'s^5!0[Tki#g&)Ŷg۵umGOAʌ `x#~(m>nMWJ?4 jв~ۧG fmAI#= ҼciV֏Oj&𥾟x.4v2N5Hm@_q۳5kr!ni0VZFy 0 qO?fk>ҴK6_wtI@ڥcfZjِ-X'rƏ|[Icxv/I6e{vKTt2Co h̄.k1}^wß|O?/ [3Eoe43O6$ɆwG.Bk?᝴3x/]n5^zwW7j\CqD!` #+"J.6wt gSr!Њ-Bc5չmDHؤusz_}Mk>.^K;n;EoS,[ɸy3!݁~*[gk@DG񧷺Lmn`1 H`FJσ5 o/-K{4{nPe{Y(7` y|>wZDW7Whֵθi2 ! x8  Cxį:7G;N^%ƣg< n$*#_iPǴ_/j+oU-QEQEQEQEQEQEQEQE?@?zyOP5@EP^Sj? ciWWeXTQEQEyOP5^^Sj? ciWP653wVqn<}Z$m9blٴ/h'6m#΄.X7煷cjx_^Dw5N&}ƀ27??w%mW~-b{+Wj3q#~?w#1/٫jy.(o<a;_VԼM9DM~rf #e x_-BCT.O:yp<_nl '55?o zGD$~ƀ=Z(((((((((((((((((7g'j*ٟHf77hܨ((((+f"~zK&r w,=ʊ((((G}|HWU??GҠih_RVZ((((((((+~G*j?QkJ=Z((үV)5ը(?QkJZ(үV|0íMEP])e$z`EQEQEQEQEN|#\%Vt}NZH[ͧ39OceA>U-3Mִ'Xn1\[\IJ*ЌP+Ưe~Ze[>e ݑ*> wv=Ɗf& /???Mo^+ÿ~ К445iyhh$*oU-Emʥ(((((((((үV)5ը+~G*j?QkJ=Z(()5ի~G*j((((((((~$|8oi]E]n~[{dӷxOW|gJ6d{ELNBܖBr6x#Wï_COnqC%渺ӷ9WW_| ouL\E+9ܝ-ޣ`x4b=GnU={U1)IM^Ҵ62Ym48 zd=MlEQEQEQEQEQEyW_<=KèeF ?+kʴOi/φAl߼I_m@Q@to.,bo/"ۡR`':Ku+͕vz$U %1Z-A$`s^g#xDoF Bm|s|P#{XeI1rcs^ڏ~E賾i E,+9(τ/A6{ռ-6 4-BOEC%pِ|巟x7t 7fF<""˝©˲)+#ag5OZQϚv9ihCZo _IV{> J.mqbсc"Z\+)#'Ү5; me{Wʎ7G c9ާi_#iioqxxJ-Dqs4F[<;,0Yʍ%n<~ӣn ɡ+P?0݇<إ#մ cú{axdO>F2UEer 1ck?O"/ƋyX౳xKC :ǃXZ~=o9}S'us{gX#P A:ChЯ5g-G AkVj m{7qo#OHyhCng!YxgMi[RNr}P|N!yu>//8Ai8\LZ,Rx7nէɬ~~mOxu,-vÚx }a<$̢I<#_i@ol\kin)xgN!Ү|kS49n6n<ԯ8ZxC(5[}SUtF8,u U%frtdIëd|+W>vzLj~%xKoJӬm%-[_*5]hHg />O|Sjicg!JZc-JGI'X71񇆮m<7o"9}*Dp] q$B)(ٿצeW_i>~ ^OerӇeEsJcq/(t_iI*Y}n:,a0Yفs^ _h &BN啥x"*!FHg ( ( ( ( ( ( xt{WP^3׿Y5{xo$Oÿ^dTQEQEQEQE??Gүn)_?{M{E*h_REPEPEPEPEPEPEPEP^Sj? ciWWeXTQE?@?zyOP5@EPEPeXU?@?zQEQEQEQEQE?@?zyOP5@EPEP <#GY~%^O#ǚGE7>w+-?*(үV*w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳh*w+-?*WѳGľ%<_sQҡx{o}Ɋ#{ IhUFw(RjYgZP WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]|U WZ6U;gQ W_(]o|Y\|-['0b)YZt>Ko8l?]vF-Ҽu};Ś*GknbpJ yWFj<o8l?]vFU<o8l?]vFU<#\x~x^LvM'l{xo$Oÿ^dTWD;M^^3׿Y5{Q@Q@Q@Q@xOc۫~ ď@mʥ/TQEQEQEQEQEQEQEQEWeXU?@?zQ@yOP5^^Sj? ciPQEQE?@?zyOP5^@Q@Q@Q@Q@Q@yOP5^^Sj? ciPQEQE?@?zyOP5^@֙#iz<'uvsm:%V) 2jWv%h ahlCq~싏Z Lwthޓ a?Wי;+I7>-6T~_7lw:')#״o;;c&- _n@!hf"?-Gi#[k[{г(rbG Ďmi??/ڹjeyHKU@$Se!->kZkȗw%T[W9F3KšZx9|;_x@@8ogӭDbL}1''TI}~ggicVoW^1 Ko .,r854'|)G%~l76;w.t9տm433x/EO'YG"w.MC4܈Ѫ0.1 hE\ZWw%A$Nɾ77 Vʲkӿf-Ѽ߉>펌wnf2W6&Y ^b@8_͡iZ_-u-G+5XGl[hQ4w,CHF r+'+{7xAF5M"+MB $ s-(dU,ŗjd:O͠h} ^iC!f0wa$a$Rc-um+-? K}}nIm']D|-DYXӓ@! xGaOElhwfb{g׀{ьZo`c.XmYqI'm..>(x8]m{wkg/2Y"gCltad\ed\~ךߎ'e[|an/@P'XIsN6NߔYhҴkm:Eƫ4%񭖍$Omd䲸@љ+:B<;o~_4h> t{MvT7|7*٪yya_x ]1«x=X^ceݥq#F҇.x9$q@I/>{uxv#_keVxXA L+7sp6 /¿½7^*kZmʲyٴQ}es:K᷈EE\x[On6o3Bq 1B~> y;7^)_Z,|O3*l b's?!hּYPφSK74wo$hw so*W7C<)?~#Bx2^85)% ׭?7 e1*3"oMn/麦g2ߦc6cj~!gEjpcKvTQMpI2[h&񿇞]Km1 a ѱG'[_~x_7*,LmQr*#I*2O7^''5{bO.O F ?p>`vZxoMV;;]K\1l7pkV{{ 䌩߲5+O]S{cq^B5 Yl5(R43'~߇s%v<5x?E}Ov+F1C!I O\閗:X=ͲO,2I8Zwi:ѐ!hk1mnd?{ GC_7x#G~?e{-5> ;on;㝑7{k5o7Zo=9jxBI1((ٟH޽ɫܫf"~zK&r7g'j*ٟH޽ɨܨ(((+~ ď^^#S>$*oU-Emʥ(((((((((үV)5ը+~G*j?QkJ=Z(()5ի~G*j(((((+~G*j?QkJ=Z(()5ի~G*jfмQ,BG>7C4d:@;w+2`ߞEF~-x" >>oE;hn-b? :0@5~_?e߇<'_ěmBDkR 1ŻJ / ڽ[N@j'5-m+qy) 9QS:>A{M{E*eρ?#N9#k-;N9[%MRtr$A׼W)gI__JޡwRvM4^I$kt,ǒI$y$M\_%~ Nh뫠 w,ʼ7g'j*+f"~zK&r w,=ʊ((((G}|HWU??GҠih_RVZ((((((((+~G*j?QkJ=Z((үV)5ը(?QkJZ(үV((((((үV)5ը(?QkJZ(үV((($u ;Xx,4OwjFm9ex* Aini:Ɵm}cw%)PWF0>bWm!m'~[EX@H~=4|? ???Mo^+ÿ~ К445iyhhi)Bk Sq; K;G3OO[v=Ɗf& /?`x/xbڗn"Ԯ{۩\G Y]#PHd>o7+O Yﹽ6n9BCh "Wa7Mo45iyhh>|YocXZF?{d^ Sq; K;G3OO[v=Ɗf& /???Mo^+ÿ~ К445iyhhi)Bk Sq; K;G3OO[v=ƹqxQyV/w\JB<6'< K;[9hF:ySjS9cbƀ6oZJfknH|p>a=ԭ/V=Ӹ^GBQ Z>,+I]]rGI/ؿ4u)I_?_ӿ:f"~zK&r w,ʀ ٟH޽ɫܫf"~zK&r(((()_?{uxOcm/T{E* ( ( ( ( ( ( ( ( ?QkJZ(ҠV()5ի~G*j( (<(үV)5ը((((()5ի~G*j( (<(үV)5ը((((S⿊4?mtxU&xа##}뫮Kׄu|1Ot 4%#G ̠@ ]*PP֙I-lau2Kh$c4|H)*7 ɠT7n4+WOu66,LJF ʝ ,gC|%i>"Wo3ukq{3-3ȗLF /sg>G|Oe./ %Ox}'#Tgh })ӎݛAq=t $2Rcx0N2 5x?ÏjwO5ytt=2{{4|by≢";lf~ |#z h%euk{6[dsϋ+B?kK}RO #0~x&/N0@=TwL8Q[=6n-i`U2k,0YQfk4K5? u?}<]. mʏVT=;-~V☉-mȺ>i2Xs ĭ.O xSŷ~ ^=k"&'Fp@ gʼ3s焼!qqexܿ@hnOt@dndXXZj~&l xN{MC]7zl%4M$r AΙsvSNh:UAcZZ"*F8UU:@袊?QkJZ(үV(((?AzWJx^AkY-6 l WMGUᶿТyKQ@f]MUdg[/~?2s3]"Imk=AYteb[e|/3m:-^I&0-n9g*>ƐomѪ_2xPisQ2Á}EGA>mnHm3_ܥ@rrHwfW/ Vt%~ae3?v)mu_㍵ _ZGǞc ۧT?4~g]6Ӵr+üAgj .jo퀰MsLѻ(o8 `9eg4|C_C>RUΟkn2_+d$*oU-Emʥ(((((((((үV)5ը+~G*j?QkJ=Z(()5ի~G*j((⽬$.k[fBx{G^{V(eQrO/R^[xEWSyGKl|@J7pq&W]jiqy9G۰K1]탔w>5] IS3$qC = qowU|ir[ WikmB+mIv8tHdsW>*LqM3DG+xݾ"K#Yym.Yx3E`@>k$rJM+x{.8< X+ uSn-SJh-k,6`kxw[#և6mKX[kk$ҖZRQ!+搁324?<'ާx^.-K[LE4r$(Aey ᗆq%kztW @ɹ5|eXU|$/fuksZ\MjC#7 Cr@?zQ@Q@Sj? ciWWeXUQEQEQEQEQEQEQEQE^/㗋9~y^%iuaL]ˁv>\O k>$:ǃ⍼[K׼=k-J֮s l+;0Et^ n 3>YyoIQ]A"RE9VCʲhj(,tGkC r+='\hm˪$TemFqp W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_|UW_ŵW(\[FM # 2AZ(¯d~/h6!q%d- E cxkZK@r8#<׼@R`>?d_x%7ƀu <åPڛ9ۓΊP_Z*GUVP_Z*G꾣9Y}Ε|bqiy 7R}$W@4*BҬM*"O-"B'ItQ@3׿Y5{xo$Oÿ^dTWD;M^^3׿Y5{Q@Q@Q@Q@xOc۫~ ď@mʥ/TQEQEQEQEQEQEQEQEWeXU?@?zQ@yOP5^^Sj? ciPQEQE?@?zyOP5^@Q@Q@Q@Q@Q@yOP5^^Sj? ciPQEQE?@?zyOP5^@Q@Q@Q@Q@Q@Q@Q@Q@yW|ZIsvH5 c$z/O zx'EѴ6wxF@M8 \ӣK )'h^2ѣa4o^^kl;q5^JM:xķVof(:((((((((((ٟH޽ɫܫf"~zK&r7g'j*ٟH޽ɨܨ(((+~ ď^^#S>$*oU-Emʥ(((((((((үV)5ը+~G*j?QkJ=Z(()5ի~G*j(((((+~G*j?QkJ=Z(()5ի~G*j((((}ß7iGJA /;&+KX0d-&RhYUuKj*zgigikcAI''TI}~ggicV*UP_Z*Gh*UP_Z*Gh*UP_Z*GK2QϲA$7cv'W5Ҿ2ڏ E}Q/ n4‚W ?G(/_u><=/ x4[ #νwmx^amn8Dт2AK_ xs][}&=?4աm=BنYJ򸐂X{(/_t‚W ?]j <=`CUPĮ 9Ox㗍$>c#] J'F7Hrx|?,gv6~ Gk5k[t8Hif<֧(/_uxǍu;j kfme-me6֦&[fMf ߴ7|i-q:x@x[hcȪEY ,q-ܚP_Z*GUūsGeg∼!+Z{ =+aD}/q/s}2́‚W ?G(/_u?mmưxWռ=gt{f ?Awo8fl!r3(/]^յ-;B|?<1mnYd-. $kiwlY@|84|U W_(]r=>'|*s}? [GltwGoJaIX<*,e17xo x^5F\[{I)^@id{cQFHAjkVQ|`ďz:/@_HE&W1s#ٔelv*UP_Z*Gh*UP_Z*Gh*|WDx#η>j<1s7OӠ1Xw~ n ,V;]F0Cs=VHfO7VGCʺ0[U_dӭu!ͥC/~xrcZ):Z((ٟH޽ɫܫf"~zK&r7g'j*ٟH޽ɨܨ(((+~ ď^^#S>$*oU-Emʥ(((((((((үV)5ը+~G*j?QkJ=Z(()5ի~G*j(((((+~G*j?QkJ=Z(()5ի~G*j(((?QkJZ(үV((m:4r)%#~FK'>j<_kؼiyFa.yq#* rܨo5MB[㿴hW?Im: !$E,ńkWlN5xN%vڕm"f(E#F:4k0KCzwBlYBqQmd^϶>)U׏nj[c] uSKrHy^9U 0HAb]e]=V]<]fI ݼiet'K1eP7I+?߃G5x^5>iqꚢ*ic()M.GP? x["xgL]jzI i)qoqG[Cq6~1o=J3WoEmk3E<.P7J<7C|Mѵr;Z[jZd%l\Sљ>SݤrW@N@+篇 $*#_iPǴ_/j+oU-QEQEQEQEQEQEQEQE?@?zyOP5@EP^Sj? ciWWeXTQEQEyOP5^^Sj? ciWPEPEPEPEPEP^Sj? ciWWeXTQEQEyOP5^^Sj? ciWPEPEPEPeXU^x<_~եźX`k+[{c9uQEQEQEQEQEQEQEQEQEQEQEQEW?Jw>!'ĺе(ש'& u-CޫInSd=Nbx"q7F=r=qKQ[Ǵ_/h$_/?M% 3;Iؿ4|%W&:(<7g'j*ٟH޽ɫܨ w,ʼ7g'j*( ( ( ( #_iWW?J==qKQ[Ǵ_/h(((((((()5ի~G*j( ?QkJZ(ҠV((~G*j?QkJZ ( ( ( ( ( ?QkJZ(ҠV((~G*j?QkJZ ( ( (1|[ J/4[}F9&UO42)VD*z^iyN[⥅^m.Y潖FȠ+uE_R_Z)EM)>m><_Q?Jkkh5| >m><_Q?Jkkh5| >m><_Q?Jkkh5| >m><_Q?Jkkh5| >m><_Q)x ^GwVylͿx5| 򯏶W18g)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}x)[-t•O /^ko}}yOfk귞$>e2O^N46A(đƭFq^ /3E$Ҷm>0{ PBQErGI/ؿ4t|YW/&JbWEPD;M^^3׿Y5{ῳ??{WWD;M@EPEPEPEP^#S>$*#_iPǴ_/j+oU-QEQEQEQEQEQEQEQE?@?zyOP5@EP^Sj? ciWWeXTQEQEyOP5^^Sj? ciWPEPEPEPEPEP^Sj? ciWWeXTQEQEyOP5^^Sj? ciWPEPEPEPEPEPEPEPEPEP^Si j׫W~qeGڴQEQEQE?4OT6IOh1Mqx^K%&vbJy+,Gˮ|Pruψt% >lIgGіFɋlHI3ĚmDꖣVa ΛD`55 iU]smdP",zeziώ`m퇄uֆMǝ1mthI|9U: o,,}# ]|A[J\6ai/R 9 $OS\w~/i~3nfЍ6UieȚV2I"8MjxW[6s41K{ 4ec3EUC y>|C׬A=UXZ i7e al,9ZkO>!/OOkMMKy!L)o\s? _6,5}^V/>F D{v-5Rv|@=(Sؿ4|%W&?JebQI_?_ӿ:袊ٟH޽ɫܫf"~zK&r7g'j*ٟH޽ɨܨ(((+~ ď^^#S>$*oU-Emʥ(((((((((үV)5ը+~G*j?QkJ=Z(()5ի~G*j(((((+~G*j?QkJ=Z(()5իʼx#⿎K7uEoˀVsŻ w #h(((((((((_'ý^vf JMkk[[)"8<aE_|U W_(]z_|U W_(]z_|U W_(]z_|U W_(]z_ŵW(\[FM # 2AZĿ<vky؄]ƣM%h$ ^Ex7c^~,qr)yfH85ѷ|iA uPxcE7ZhPμ:^ O.U/ϩiE4_rk3Ⱦ)vZJ+f"~zK&r w,=ʊ((((G}|HWU??GҠih_RVZ((((((((+~G*j?QkJ=Z((үV)5ը(?QkJZ(үV(((((ErO uuqoSR5 w4lgPpv9:ǩO <+׵5(nlu40u2ckXn")ם@L"8oMŋxñ/,v:a, n|?y-PU4_I]kB-5:%uatu%YHA պoȤ$*#_iPǴ_/j+oU-QEQEQEQEQEQEQEQE?@?zyOP5@EP^Sj? ciWWeXTQEQEyOP5^^Sj? ciWPEPľ/G~9J:T0Omy1ZZptу!i2Bj\~VVsoK;L;KX O?:]ZO/{;;H(zyW(/_t‚W ?^EyW(/_t‚W ?^EyW(/_t‚W ?^EyW(/_t‚W ?^EyW(/_t‚W ?^EyW(/_t‚W ?Z?>/CG9E-彟Z}}#|q,׿m]ÿ5JQ[DfѼ<FW}Yc>\;Wx_,d5wÞ'~%Cխs}<|\ׄa/^<9o%i7vr2:1@ ѡ>ER<6Ϳbu$6#ZYi"LVF< J6|1۽W H/x5kg^nP)X@A}uvF W_(]|Ui\KG#0mI}<3vy]O~>FI O.F?biȩ%թt0J'@-(/_t‚W ?^!.]&h.x^Jva\V]/fĻc?jyH _xYגKm[ݧe`!zVQ?AjkS/G|CW/|%?<3/+M,jbe ,g寠(ʿAjkVQZ(ʿAjkVQZ(ʿAjkVQZ(ʿAjkVQZ(ʿAjkWgP{Hb^Ey&QGKktKkOGlRfwȊVH -3R6Xo";krKdu=R>׍~m:KiM?o%QEQEQEQEQEQEQEῳ??{WWD;M@EPEPEPEP^#S>$*#_iPǴ_/j+oU-QEQEQEQEQEQEQEQE?@?zyOP5@EP^Sj? ciWWeXTQEQEyOP5^^Sj? ciWPEPeXU?@?zQEQEQEQEQEs9{E]*xwN0jq@C*c7CNyg#G! Z'1hRC0O c?G;s{uz? 'e?u][ڕƧ],(6,f8|4z/ƭc㭥ƬufխyA#Qqa KxQA2sPZ xOƾ2X k{Mk{9O6xn]Uf 6֗hzhhcŌL]\G,I!D7 tP0xJƃ=A-ψngF#s\eXxZOuK'ap rc5gWY|=w6~mYWI乗 k4sK$hNQTZ$*#_iPǴ_/j+oU-QEQEQEQEQEQEQEQE?@?zyOP5@EP^Sj? ciWWeXTQEQEyOP5^^Sj? ciWPEPeXU?@?zQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE _ƿ慠xúnOay>k2;}^3׿Y5z| MFЯ$ qPT*).X\\W :*()4kի~?8Zj(((((+f"~zK&r w,=ʊ((((G}|HWU??GҠih_RVZ((((((((+~G*j?QkJ=Z((үV)5ը(?QkJZ(үV(*;F/-ž.sYZC,W+%'U_3:҆QdI{yM C*䕑 W^|Ӥ)Vah~G7}˶CeKQ?JkkmKQ?JkkmKQ?JkkmKQ?JkkmKQ?JkkmKQ?JkkmKQ?JkkmKQ?JkkmKQ?JkkmKQY/h|k/>^n19h+uE_R_Z)Eh+uE_R_Z)Eh+uE_R_Z)Eh+uE_R_Z)Eh+uE_R_Z)Eh+uE_R_Z)Eh+uE_R_Z)Eh+uE_R_Z)Eh+uE_R_Z)Eh+uE_R'La]ե2rPQ@Si j׫W~qeGڵQEQEQEQEQEWD;M^^3׿Y5{Q@Q@Q@Q@xOc۫~ ď@mʥ/TQEQEQEQEQEQEQEQEWeXU?@?zQ@yOP5^^Sj? ciPQEQE?@?zyOP5^@Q@!KEFm&Iko-٭?Qko-٭?Qko-٭?Qko-٭?Qko-٭?Qko-٭?Qko-٭?Qko-٭?Qko-٭?W|D|vDoU??"z٭?Qko-٭?Qko-٭?Qko-٭?Qko-٭?Qko-٭?Qko-٭?Qko-٭?Qko-qč/S㍴}xG|UTaTq|y]aV?yECGyj3m;K.G"_'<7^^[Nnb④Gb~r`>tq4E?@zyOP4^@Q@Q@Q@Q@Q@xo$Oÿ^dUῳ??{PQEQEQEQEW?JG}|HWT6Z=qK@Q@Q@Q@Q@Q@Q@Q@Q@yOP5^^Sj? ciPQEWeXU?@?zQ@Q@Sj? ciWWeXUQEQEQEQEQEQEQEQEQEQEQEWDF|CY|GF5R&k CFk[ıL#?(VQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUVQ?AjkUW.@Y˪i>#?e$l6Z$'FY&/0 'U W_(]|Uύ xg0xm{C]&ΊX}_4LK$>rYg}Vi}_[nhwB_7S>bVߖm}m5/SC/ ,A ֱ/d~/h6!q%d- E c;Y~s_8u{<h("]. WVh]| Ɵ@A 3ğGԧUs۵&%y9/|BWoi[ܣY[Y "=Y`?d_x%7ƀu <åPڛ9ۓ淿AjkUVQ?Ajk4kի,g5Z> x#CMJVkar9TRād 8S(((((7g'j*ٟH޽ɨܨ(((+~ ď^^#S>$*oU-Emʥ(((((((((үV)5ը+~G*j?QkJ=Z(()5իʼx#⿎K7uEoˀVsŻ w #h(((((((((((((((((((((((((((((( w,C9Z7VP`o6)#|ҾW Tk:ֳn- nW$w,g A@h9c+c?T @(h(((+~ ď^^#S>$*oU-Emʥ(((((((((үV)5ը+~G*j?QkJ=Zxĺw<K'}BK).AEX;gx5ךE4wk+Sk1,n#]nv=r{PSFu6(ӥԵTjL9%gԦTvd;7|MGh"{ooPEi7܆l˽R0{Ri?o}cϓOkZH<61m7$*oU-Emʥ(((((((((үV)5ը+~G*jςk i^mr;-u 2KUKh~ХU.rNEyVڇ~᧍^ÓR/?vxE> (~L-Ϻ-]S(h5i/zzSLJSuHc-)I^ Dʒ"GF B(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE??Gүn)_?{M{E*h_REPEPEPEPEPEPEPEP^Sj? ciWWeXTQEQEQET4'\MMBQ-Yc לM1Wn.>7$9'AMzuj? (O[S+&V%x !\~VVsoK;L;KX O?:]ZO/{;;H(zyW(/_t‚W ?^EyW(/_t‚W ?^EyW(/_t‚W ?^EyW(/_t‚W ?^EyW(/_t‚W ?^EyW(/_t‚W ?^EyW(/_t‚W ?Gߌڿ"W/$uGI?AgژYTѪG'P  Ax߁| j>0ֳKk]FHpF~eQ_P_Z*GU ~|9O,+3RVL+-qM$Sl{k|;LtftʟLF $u9%mP@UP_Z*Gӯ~eqyɷ8N3WOHK oZ6ϗ;ps;~g 瞔‚W ?G(/_u~5GYxkG'Fԗ]KR7vXK^ shOῂ9i “xUּBi]]o[YyZHCo ]I WZ6U;gP^?n[j>vN B5 q7 /~~VB4jBa9- T.rmo»GG+q_hT_Eht;M__x>!i 3i$x#E3G452^i潼e!5tꬤ[;gQ WZ6Uf\~~x ľ,-߆4w- ƞ$xgkW- m3u&g?뺖x^ޙ'ROlI=Sʍ/YK]%TOWѳw+-?*+6xr/xNt5[;v xuh4dLIH qY]Ե;wS6^3}V9VčrJl0w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳh*w+-?*Wѳ g8);5M^^.G֟0PjI=nAsrl5ľt822Xq%[6xJ%^;62b 0C䜀%4x¿,4qé X.fO,o@-8u? ~+>exwJh4F eLXyCp  E5K=Άk]ZX-'8ďUI ;']As 5Ee<2iE xY&q:ڸ"8ߜߏmɠ/+[~Ls]:\8XGn zemֺ${mBW:QIȒ|F7XlNl^|{!{7BYsQ,YWbbGlN~U.㿆.p tY.m."ԭ[Vcpdhgx1y1mخ{wB(-t]JXH4&*6 5)LIh߃&.ckBEi\{kFII0Z28Gqɭ=Gw_m Xk84DPHNn^1?ռW* BKkENI$p'2ѵ[mRmVN,,GV-+\>wU_|{KŖCgZ%^6kg{""VĮA\e쯾 |94$hv1hKnyarJCJqo*K"3v3e^{򽅦D,5<3Br'w$`z((((((((((((( w,ʼ7g'j*(((((((((((((((+~ ď^^#S>$*oU-Emʥ(((((((((үV)5ը(((7i_[{f$LAj^ *Ԯ/5SMB$tv*r`,ru ;Xx,4OwjFm9ex* Am>5ծ,>xf]y=UmѯՉ,'!3($C94_k޿ 68y5/$/9k]OQ3Q'.[ueKi'4FRAN; xbuiwh u%kͦFzs}@%,x^֭4 fAѴO"mpPXp#WH0*}SBPӴս1.n6PY @#"5_Ci<9jZXa\]h8]vȽyJsNxCx'-5_N(c=Gp}WŞ |@uhw^"X%Թ(-Y5&\NͿ~!^x/Dm>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| >m>"5| z(Q1N ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?sobjectizer-5.8.6/dev/sample/so_5/introduce_named_mbox/000077500000000000000000000000001523065210100231415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/introduce_named_mbox/CMakeLists.txt000066400000000000000000000010141523065210100256750ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.introduce_named_mbox) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.introduce_named_mbox_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/introduce_named_mbox/main.cpp000066400000000000000000000145371523065210100246030ustar00rootroot00000000000000/* * Simple usage of environment_t::introduce_named_mbox() * and unique_subscribers mbox. */ #include #include #include // Data type to be passed between agents. struct data_t { std::string m_stage{ "initial" }; std::string m_prefix; std::string m_payload; std::string m_suffix; }; std::ostream & operator<<( std::ostream & to, const data_t & what ) { return (to << "(<" << what.m_stage << ">:[" << what.m_prefix << "]= '" << what.m_payload << "' =[" << what.m_suffix << "])"); } // Messages to be used for interaction between agents. // Because all messages are similar we use class templates for them. struct preprocess_tag {}; struct process_tag {}; struct postprocess_tag {}; template< typename Tag > struct msg_handle_data final : public so_5::message_t { std::unique_ptr< data_t > m_data; const so_5::mbox_t m_reply_to; msg_handle_data( std::unique_ptr< data_t > data, so_5::mbox_t reply_to ) : m_data{ std::move(data) } , m_reply_to{ std::move(reply_to) } {} }; template< typename Tag > struct msg_handling_finished final : public so_5::message_t { std::unique_ptr< data_t > m_data; explicit msg_handling_finished( std::unique_ptr< data_t > data ) : m_data{ std::move(data) } {} }; using msg_preprocess_data = msg_handle_data< preprocess_tag >; using msg_preprocess_finished = msg_handling_finished< preprocess_tag >; using msg_process_data = msg_handle_data< process_tag >; using msg_process_finished = msg_handling_finished< process_tag >; using msg_postprocess_data = msg_handle_data< postprocess_tag >; using msg_postprocess_finished = msg_handling_finished< postprocess_tag >; // Helper function that makes a named unique-subscriber mbox. [[nodiscard]] so_5::mbox_t make_processing_mbox( so_5::environment_t & env ) { return env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "sample.introduce_named_mbox" }, "processing", [&env]() { return so_5::make_unique_subscribers_mbox( env ); } ); } // Type of agent for coordination of data processing. class processing_manager_t final : public so_5::agent_t { const so_5::mbox_t m_processing_mbox; public: processing_manager_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_processing_mbox{ make_processing_mbox( so_environment() ) } {} void so_define_agent() override { so_subscribe_self() .event( &processing_manager_t::evt_preprocess_finished ) .event( &processing_manager_t::evt_process_finished ) .event( &processing_manager_t::evt_postprocess_finished ) ; } void so_evt_start() override { // Data to be processed by several workers. auto data = std::make_unique< data_t >(); data->m_payload = "Hello, World"; std::cout << "data to be processed: " << *data << std::endl; // Initiate processing. so_5::send< so_5::mutable_msg >( m_processing_mbox, std::move(data), so_direct_mbox() ); } private: void evt_preprocess_finished( mutable_mhood_t< msg_preprocess_finished > cmd ) { std::cout << "preprocessed data: " << *(cmd->m_data) << std::endl; // Initiate the next stage. so_5::send< so_5::mutable_msg >( m_processing_mbox, std::move(cmd->m_data), so_direct_mbox() ); } void evt_process_finished( mutable_mhood_t< msg_process_finished > cmd ) { std::cout << "processed data: " << *(cmd->m_data) << std::endl; // Initiate the next stage. so_5::send< so_5::mutable_msg >( m_processing_mbox, std::move(cmd->m_data), so_direct_mbox() ); } void evt_postprocess_finished( mutable_mhood_t< msg_postprocess_finished > cmd ) { std::cout << "postprocessed data: " << *(cmd->m_data) << std::endl; // Finish the example. so_deregister_agent_coop_normally(); } }; // Type of agent for preprocessing of the data. class preprocessor_t final : public so_5::agent_t { const so_5::mbox_t m_processing_mbox; public: preprocessor_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_processing_mbox{ make_processing_mbox( so_environment() ) } {} void so_define_agent() override { so_subscribe( m_processing_mbox ) .event( []( mutable_mhood_t cmd ) { cmd->m_data->m_stage = "preprocessed"; cmd->m_data->m_prefix = "-=#"; cmd->m_data->m_suffix = "#=-"; so_5::send< so_5::mutable_msg >( cmd->m_reply_to, std::move(cmd->m_data) ); } ); } }; // Type of agent for processing of the data. class processor_t final : public so_5::agent_t { const so_5::mbox_t m_processing_mbox; public: processor_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_processing_mbox{ make_processing_mbox( so_environment() ) } {} void so_define_agent() override { so_subscribe( m_processing_mbox ) .event( []( mutable_mhood_t cmd ) { cmd->m_data->m_stage = "processed"; // Reverse the content of the data. cmd->m_data->m_payload = std::string{ cmd->m_data->m_payload.rbegin(), cmd->m_data->m_payload.rend() }; so_5::send< so_5::mutable_msg >( cmd->m_reply_to, std::move(cmd->m_data) ); } ); } }; // Type of agent for processing of the data. class postprocessor_t final : public so_5::agent_t { const so_5::mbox_t m_processing_mbox; public: postprocessor_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_processing_mbox{ make_processing_mbox( so_environment() ) } {} void so_define_agent() override { so_subscribe( m_processing_mbox ) .event( []( mutable_mhood_t cmd ) { cmd->m_data->m_stage = "postprocessed"; // Add prefix and suffix to the payload. cmd->m_data->m_payload = cmd->m_data->m_prefix + " " + cmd->m_data->m_payload + " " + cmd->m_data->m_suffix; so_5::send< so_5::mutable_msg >( cmd->m_reply_to, std::move(cmd->m_data) ); } ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { // Fill the coop with manager and workers. coop.make_agent< processing_manager_t >(); coop.make_agent< preprocessor_t >(); coop.make_agent< processor_t >(); coop.make_agent< postprocessor_t >(); } ); } ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/machine_control/000077500000000000000000000000001523065210100221205ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/machine_control/CMakeLists.txt000066400000000000000000000005501523065210100246600ustar00rootroot00000000000000set(SAMPLE sample.so_5.machine_control) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.machine_control_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/machine_control/main.cpp000066400000000000000000000407531523065210100235610ustar00rootroot00000000000000/* * An example of using priority-respected dispatcher to imitate * control of some machines with engines and coolers inside. */ #include #include #include enum class engine_state_t { on, off }; enum class cooler_state_t { on, off }; // Signals to turn engine on and off. struct turn_engine_on final : public so_5::signal_t {}; struct turn_engine_off final : public so_5::signal_t {}; // Signals to turn cooler on and off. struct turn_cooler_on final : public so_5::signal_t {}; struct turn_cooler_off final : public so_5::signal_t {}; // Machine status message. struct machine_status { const std::string m_id; const engine_state_t m_engine_status; const cooler_state_t m_cooler_status; const float m_engine_temperature; }; // Type of reaction to be performed on machine. enum class attention_t { none, engine_cooling_done, engine_cooling_needed, engine_overheat_detected }; // Notification about machine which needs some attention. struct machine_needs_attention { const std::string m_id; const attention_t m_attention; const engine_state_t m_engine_status; const cooler_state_t m_cooler_status; }; // Agent for representing a machine. class a_machine_t final : public so_5::agent_t { // Periodic signal to update and distribute status of the machine. struct update_status final : public so_5::signal_t {}; public : a_machine_t( context_t ctx, std::string id, so_5::mbox_t status_distrib_mbox, float initial_temperature, float engine_heating_step, float cooler_impact_step ) : so_5::agent_t{ ctx } , m_id( std::move( id ) ) , m_status_distrib_mbox{ std::move( status_distrib_mbox ) } , m_initial_temperature{ initial_temperature } , m_engine_heating_step{ engine_heating_step } , m_cooler_impact_step{ cooler_impact_step } , m_engine_temperature{ initial_temperature } {} void so_define_agent() override { this >>= st_engine_off; st_engine_on .event( &a_machine_t::evt_turn_engine_off ) .event( &a_machine_t::evt_turn_cooler_on ) .event( &a_machine_t::evt_turn_cooler_off ) .event( &a_machine_t::evt_update_status_when_engine_on ); st_engine_off .event( &a_machine_t::evt_turn_engine_on ) .event( &a_machine_t::evt_turn_cooler_on ) .event( &a_machine_t::evt_turn_cooler_off ) .event( &a_machine_t::evt_update_status_when_engine_off ); } void so_evt_start() override { // Periodic update_status signal must be initiated. m_update_status_timer = so_5::send_periodic< update_status >( *this, std::chrono::milliseconds(0), std::chrono::milliseconds(200) ); } private : const state_t st_engine_on{ this, "on" }; const state_t st_engine_off{ this, "off" }; const std::string m_id; const so_5::mbox_t m_status_distrib_mbox; const float m_initial_temperature; const float m_engine_heating_step; const float m_cooler_impact_step; float m_engine_temperature; engine_state_t m_engine_status = engine_state_t::off; cooler_state_t m_cooler_status = cooler_state_t::off; // Timer ID for periodic update_status. so_5::timer_id_t m_update_status_timer; void evt_turn_engine_off(mhood_t< turn_engine_off >) { this >>= st_engine_off; m_engine_status = engine_state_t::off; } void evt_turn_engine_on(mhood_t< turn_engine_on >) { this >>= st_engine_on; m_engine_status = engine_state_t::on; } void evt_turn_cooler_off(mhood_t< turn_cooler_off >) { m_cooler_status = cooler_state_t::off; } void evt_turn_cooler_on(mhood_t< turn_cooler_on >) { m_cooler_status = cooler_state_t::on; } void evt_update_status_when_engine_on(mhood_t< update_status >) { m_engine_temperature += m_engine_heating_step; if( cooler_state_t::on == m_cooler_status ) m_engine_temperature -= m_cooler_impact_step; distribute_status(); } void evt_update_status_when_engine_off(mhood_t< update_status >) { if( cooler_state_t::on == m_cooler_status ) { m_engine_temperature -= m_cooler_impact_step; if( m_engine_temperature < m_initial_temperature ) m_engine_temperature = m_initial_temperature; } distribute_status(); } void distribute_status() { so_5::send< machine_status >( m_status_distrib_mbox, m_id, m_engine_status, m_cooler_status, m_engine_temperature ); } }; // An agent to collect and periodically show status of all machines. class a_total_status_dashboard_t final : public so_5::agent_t { // A signal to show the current state of all machines to the console. struct show_dashboard final : public so_5::signal_t {}; public : a_total_status_dashboard_t( context_t ctx, so_5::mbox_t status_distrib_mbox ) : so_5::agent_t{ ctx } , m_status_distrib_mbox{ std::move( status_distrib_mbox ) } {} void so_define_agent() override { so_subscribe( m_status_distrib_mbox ) .event( &a_total_status_dashboard_t::evt_machine_status ); so_subscribe_self().event( &a_total_status_dashboard_t::evt_show_dashboard ); } void so_evt_start() override { // Periodic signal must be initiated. const auto period = std::chrono::milliseconds( 1500 ); m_show_timer = so_5::send_periodic< show_dashboard >( *this, period, period ); } private : const so_5::mbox_t m_status_distrib_mbox; // Description of one machine state. struct one_machine_status_t { engine_state_t m_engine_status; cooler_state_t m_cooler_status; float m_engine_temperature; }; // Type of map from machine ID to machine state. using machine_status_map_t = std::map< std::string, one_machine_status_t >; // Current statuses of machines. machine_status_map_t m_machine_statuses; // Timer ID for show_dashboard periodic message. so_5::timer_id_t m_show_timer; void evt_machine_status( const machine_status & status ) { m_machine_statuses[ status.m_id ] = one_machine_status_t{ status.m_engine_status, status.m_cooler_status, status.m_engine_temperature }; } void evt_show_dashboard(mhood_t< show_dashboard >) { auto old_precision = std::cout.precision( 5 ); std::cout << "=== The current status ===" << std::endl; for( const auto & m : m_machine_statuses ) { show_one_status( m ); } std::cout << "==========================" << std::endl; std::cout.precision( old_precision ); } void show_one_status( const machine_status_map_t::value_type & v ) { std::cout << v.first << ": e[" << (engine_state_t::on == v.second.m_engine_status ? "ON " : "off") << "] c[" << (cooler_state_t::on == v.second.m_cooler_status ? "ON " : "off") << "] t=" << v.second.m_engine_temperature << std::endl; } }; // A dictionary to get machine's mbox from machine name. class machine_dictionary_t { public : using dictionary_type_t = std::map< std::string, so_5::mbox_t >; machine_dictionary_t( dictionary_type_t values ) : m_dictionary( std::move( values ) ) {} so_5::mbox_t find_mbox( const std::string & name ) const { auto r = m_dictionary.find( name ); if( r == m_dictionary.end() ) throw std::runtime_error( "machine not found: " + name ); return r->second; } template< typename L > void for_each( L lambda ) const { for( const auto & m : m_dictionary ) lambda( m.first, m.second ); } private : const dictionary_type_t m_dictionary; }; // Agent which does analyzing of machine statuses and produces // machine_needs_attention messages. class a_statuses_analyzer_t final : public so_5::agent_t { public : a_statuses_analyzer_t( context_t ctx, so_5::mbox_t status_distrib_mbox, float safe_temperature, float warn_temperature, float high_temperature) : so_5::agent_t{ ctx } , m_status_distrib_mbox{ std::move( status_distrib_mbox ) } , m_safe_temperature{ safe_temperature } , m_warn_temperature{ warn_temperature } , m_high_temperature{ high_temperature } {} void so_define_agent() override { so_subscribe( m_status_distrib_mbox ).event( &a_statuses_analyzer_t::evt_machine_status ); } private : const so_5::mbox_t m_status_distrib_mbox; const float m_safe_temperature; const float m_warn_temperature; const float m_high_temperature; // Info about last known machine status. struct last_machine_info_t { attention_t m_attention; float m_engine_temperature; }; // Map from machine ID to last known status. using last_info_map_t = std::map< std::string, last_machine_info_t >; last_info_map_t m_last_infos; void evt_machine_status( const machine_status & status ) { auto it = m_last_infos.find( status.m_id ); if( it == m_last_infos.end() ) // There is no information about this machine yet. // It must be added. it = m_last_infos.insert( last_info_map_t::value_type { status.m_id, last_machine_info_t { attention_t::none, status.m_engine_temperature } } ).first; handle_new_status( status, it->second ); } void handle_new_status( const machine_status & status, last_machine_info_t & last_info ) const { const auto fresh_info = last_machine_info_t { detect_attention( status, last_info ), status.m_engine_temperature }; if( last_info.m_attention != fresh_info.m_attention ) // Machine needs some new attention. so_5::send< machine_needs_attention >( m_status_distrib_mbox, status.m_id, fresh_info.m_attention, status.m_engine_status, status.m_cooler_status ); last_info = fresh_info; } attention_t detect_attention( const machine_status & status, const last_machine_info_t & last ) const { if( last.m_engine_temperature < status.m_engine_temperature ) { // Engine is warming. if( status.m_engine_temperature > m_high_temperature ) { if( attention_t::engine_overheat_detected != last.m_attention ) return attention_t::engine_overheat_detected; } else if( status.m_engine_temperature > m_warn_temperature ) { if( attention_t::engine_cooling_needed != last.m_attention ) return attention_t::engine_cooling_needed; } } else { // Engine is cooling. if( status.m_engine_temperature < m_safe_temperature ) if( attention_t::none != last.m_attention && attention_t::engine_cooling_done != last.m_attention ) return attention_t::engine_cooling_done; } // Attention need not to be changed. return last.m_attention; } }; // A class for machine controllers. template< class Logic > class a_machine_controller_t final : public so_5::agent_t { public : a_machine_controller_t( context_t ctx, so_5::priority_t priority, so_5::mbox_t status_distrib_mbox, const machine_dictionary_t & machines ) : so_5::agent_t( ctx + priority ) , m_status_distrib_mbox( std::move( status_distrib_mbox ) ) , m_machines( machines ) , m_logic() {} void so_define_agent() override { so_set_delivery_filter( m_status_distrib_mbox, [this]( const machine_needs_attention & msg ) { return m_logic.filter( msg ); } ); so_subscribe( m_status_distrib_mbox ) .event( [this]( const machine_needs_attention & evt ) { m_logic.action( m_machines, evt ); } ); } private : const so_5::mbox_t m_status_distrib_mbox; const machine_dictionary_t & m_machines; const Logic m_logic; }; // // Implementation of engine stopper. // struct engine_stopper_t { bool filter( const machine_needs_attention & msg ) const { return msg.m_attention == attention_t::engine_overheat_detected; } void action( const machine_dictionary_t & machines, const machine_needs_attention & evt ) const { so_5::send< turn_engine_off >( machines.find_mbox( evt.m_id ) ); } }; // // Implementation of engine starter. // struct engine_starter_t { bool filter( const machine_needs_attention & msg ) const { return msg.m_attention == attention_t::engine_cooling_done && msg.m_engine_status == engine_state_t::off; } void action( const machine_dictionary_t & machines, const machine_needs_attention & evt ) const { so_5::send< turn_engine_on >( machines.find_mbox( evt.m_id ) ); } }; // // Implementation of cooler starter. // struct cooler_starter_t { bool filter( const machine_needs_attention & msg ) const { return (msg.m_attention == attention_t::engine_overheat_detected || msg.m_attention == attention_t::engine_cooling_needed) && msg.m_cooler_status == cooler_state_t::off; } void action( const machine_dictionary_t & machines, const machine_needs_attention & evt ) const { so_5::send< turn_cooler_on >( machines.find_mbox( evt.m_id ) ); } }; // // Implementation of cooler stopper. // struct cooler_stopper_t { bool filter( const machine_needs_attention & msg ) const { return msg.m_attention == attention_t::engine_cooling_done && msg.m_cooler_status == cooler_state_t::on; } void action( const machine_dictionary_t & machines, const machine_needs_attention & evt ) const { so_5::send< turn_cooler_off >( machines.find_mbox( evt.m_id ) ); } }; // Helper for creation of machine agents. const machine_dictionary_t & create_machines( so_5::coop_t & coop, const so_5::mbox_t & status_distrib_mbox ) { // Data for machine dictionary. machine_dictionary_t::dictionary_type_t dict_data; // All machines will work on dedicated working thread. auto machine_disp = so_5::disp::one_thread::make_dispatcher( coop.environment() ); // Helper for creation of machine agent and adding it info into // machine dictionary. auto make_machine = [&]( const std::string & name, float initial, float warming_step, float cooling_step ) { auto machine = coop.make_agent_with_binder< a_machine_t >( machine_disp.binder(), name, status_distrib_mbox, initial, warming_step, cooling_step ); dict_data[ name ] = machine->so_direct_mbox(); }; make_machine( "Mch01", 20.0f, 0.3f, 0.2f ); make_machine( "Mch02", 20.0f, 0.45f, 0.2f ); make_machine( "Mch03", 20.0f, 0.25f, 0.3f ); make_machine( "Mch04", 20.0f, 0.26f, 0.27f ); // Machine dictionary could be created at that point. return *( coop.take_under_control( std::make_unique< machine_dictionary_t >( std::move( dict_data ) ) ) ); } void create_machine_controllers( so_5::coop_t & coop, const so_5::mbox_t & status_distrib_mbox, const machine_dictionary_t & machines ) { // There must be a priority-respected dispatcher. auto disp = so_5::disp::prio_one_thread::strictly_ordered:: make_dispatcher( coop.environment() ); coop.make_agent_with_binder< a_machine_controller_t< engine_stopper_t > >( disp.binder(), so_5::prio::p4, status_distrib_mbox, machines ); coop.make_agent_with_binder< a_machine_controller_t< cooler_starter_t > >( disp.binder(), so_5::prio::p3, status_distrib_mbox, machines ); coop.make_agent_with_binder< a_machine_controller_t< engine_starter_t > >( disp.binder(), so_5::prio::p2, status_distrib_mbox, machines ); coop.make_agent_with_binder< a_machine_controller_t< cooler_stopper_t > >( disp.binder(), so_5::prio::p1, status_distrib_mbox, machines ); } void create_starter_agent( so_5::coop_t & coop, const machine_dictionary_t & dict ) { // A very simple agent will be used as starter. // It will work on the default dispatcher. class starter_t final : public so_5::agent_t { const machine_dictionary_t & m_dict; public : starter_t( context_t ctx, const machine_dictionary_t & dict ) : so_5::agent_t{ std::move(ctx) } , m_dict{ dict } {} void so_evt_start() override { m_dict.for_each( []( const std::string &, const so_5::mbox_t & mbox ) { so_5::send< turn_engine_on >( mbox ); } ); } }; coop.make_agent< starter_t >( std::ref(dict) ); } void fill_coop( so_5::coop_t & coop ) { // Common mbox for information distribution. auto status_distrib_mbox = coop.environment().create_mbox(); // Create machines and form machines dictionary. const auto & machine_dict = create_machines( coop, status_distrib_mbox ); // Machine dashboard will work on its own dedicated thread. coop.make_agent_with_binder< a_total_status_dashboard_t >( so_5::disp::one_thread::make_dispatcher( coop.environment() ).binder(), status_distrib_mbox ); // Status analyzer will work on its own dedicated thread. coop.make_agent_with_binder< a_statuses_analyzer_t >( so_5::disp::one_thread::make_dispatcher( coop.environment() ).binder(), status_distrib_mbox, 50.0f, // Safe temperature. 70.0f, // Warn temperature (cooler must be turned on) 95.0f // Critical temperature (engine must be turned off). ); // Create machine controllers. create_machine_controllers( coop, status_distrib_mbox, machine_dict ); // Special agent which will start machines. create_starter_agent( coop, machine_dict ); } int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( fill_coop ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/make_agent_ref/000077500000000000000000000000001523065210100217035ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/make_agent_ref/CMakeLists.txt000066400000000000000000000010001523065210100244320ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.make_agent_ref) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.make_agent_ref_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/make_agent_ref/main.cpp000066400000000000000000000127011523065210100233340ustar00rootroot00000000000000/* * A sample for make_agent_ref function. */ #include // Main SObjectizer header files. #include using namespace std::chrono_literals; enum class status_t { inprogress, completed, aborted }; // An agent that will play role of the performer of long-lasting // asynchronous operation. template< typename Completion_Handler > class operation_performer_t final : public so_5::agent_t { public: operation_performer_t( context_t ctx, std::chrono::milliseconds duration, Completion_Handler && completion_handler ) : so_5::agent_t{ std::move(ctx) } , m_duration{ duration } , m_completion_handler{ std::move(completion_handler) } {} void so_define_agent() override { // Subscription to a signal that indicates the completion // of async operation. so_subscribe_self().event( [this]( mhood_t ) { m_status = status_t::completed; m_completion_handler( m_status ); } ); } void so_evt_start() override { // Delay completion signal to the specified amount of time. so_5::send_delayed< completed_t >( *this, m_duration ); } void so_evt_finish() override { // At the end of work we should abort the operation // if it isn't completed yet. if( status_t::inprogress == m_status ) { // Suspend the current thread for some time to // allow the deregistration of cooperation with the issuer. std::this_thread::sleep_for( 15ms ); // Now we can call completion handler. m_completion_handler( status_t::aborted ); } } private: struct completed_t final : public so_5::signal_t {}; status_t m_status{ status_t::inprogress }; const std::chrono::milliseconds m_duration; Completion_Handler m_completion_handler; }; // A class that plays the role of "async operation handle" and allows // to cancel the operation if it isn't completed. class async_operation_t final { public: // This class is not Copyable, nor Moveable. async_operation_t( const async_operation_t & ) = delete; async_operation_t( async_operation_t && ) = delete; async_operation_t( so_5::environment_t & env ) : m_env{ env } {} ~async_operation_t() { cancel(); } template< typename Completion_Handler > void start( std::chrono::milliseconds duration, Completion_Handler && completion_handler ) { // Don't care about previous calls of start() for simplicity. m_performer_coop = m_env.register_agent_as_coop( m_env.make_agent< operation_performer_t< Completion_Handler > >( duration, std::move(completion_handler) ) ); } void cancel() { if( m_performer_coop ) m_env.deregister_coop( std::move(m_performer_coop), so_5::dereg_reason::normal ); } private: so_5::environment_t & m_env; so_5::coop_handle_t m_performer_coop; }; // An agent that issues an async operation and waits its completion. class async_operation_issuer_t final : public so_5::agent_t { public: async_operation_issuer_t( context_t ctx, std::string name, std::chrono::milliseconds operation_duration, std::chrono::milliseconds lifetime ) : agent_t{ std::move(ctx) } , m_name{ std::move(name) } , m_operation_duration{ operation_duration } , m_lifetime{ lifetime } {} ~async_operation_issuer_t() override { // Debug print to see when issuer will be destroyed. std::cout << m_name << " destroyed" << std::endl; } // Definition of the agent for SObjectizer. void so_define_agent() override { // Work should be finished on no_more_time arrival. so_subscribe_self().event( [this]( mhood_t< no_more_time > ) { so_deregister_agent_coop_normally(); } ); } // A reaction to start of work in SObjectizer. void so_evt_start() override { // Limit our lifetime. so_5::send_delayed< no_more_time >( *this, m_lifetime ); // Initiate an asynchronous operation. m_async_op.start( m_operation_duration, // Don't capture 'this' because 'this' can be invalid // at the moment of callback invocation. // Use make_agent_ref() instead. [self = so_5::make_agent_ref(this)]( status_t status ) { std::cout << self->m_name << " -> " << (status_t::aborted == status ? "aborted" : "completed") << std::endl; } ); } // A reaction to finish of work in SObjectizer. void so_evt_finish() override { // Async operation should be aborted if it isn't completed yet. m_async_op.cancel(); } private: struct no_more_time final : public so_5::signal_t {}; const std::string m_name; const std::chrono::milliseconds m_operation_duration; const std::chrono::milliseconds m_lifetime; async_operation_t m_async_op{ so_environment() }; }; int main() { try { // Starting SObjectizer. so_5::launch( // A function for SO Environment initialization. []( so_5::environment_t & env ) { // Creating and registering several agents with different // lifetimes. Every agent will be a separate cooperation. env.register_agent_as_coop( env.make_agent< async_operation_issuer_t >( "The First Issuer (with rather long name)", 125ms, 50ms ) ); env.register_agent_as_coop( env.make_agent< async_operation_issuer_t >( "The Second Issuer (with yet more long name)", 125ms, 100ms ) ); env.register_agent_as_coop( env.make_agent< async_operation_issuer_t >( "The Third Issuer", 125ms, 150ms ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/make_new_direct_mbox/000077500000000000000000000000001523065210100231215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/make_new_direct_mbox/CMakeLists.txt000066400000000000000000000010141523065210100256550ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.make_new_direct_mbox) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.make_new_direct_mbox_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/make_new_direct_mbox/main.cpp000066400000000000000000000030501523065210100245470ustar00rootroot00000000000000/* * A sample of usage of so_make_new_direct_mbox() method. */ #include // Main SObjectizer header files. #include // Definition of an agent for SObjectizer. class a_demo_t final : public so_5::agent_t { // Signals to be used by this agent. struct msg_hello final : public so_5::signal_t {}; struct msg_bye final : public so_5::signal_t {}; public: using so_5::agent_t::agent_t; void so_evt_start() override { // Make subscriptions for the default direct mbox. so_subscribe_self() .event( [](mhood_t) { std::cout << "hello from the default mbox" << std::endl; } ) .event( [this](mhood_t) { // Work should be finished. so_deregister_agent_coop_normally(); } ); // Create another direct mbox and make subscription for it. const auto another = so_make_new_direct_mbox(); so_subscribe( another ) .event( [](mhood_t) { std::cout << "hello from a new direct mbox" << std::endl; } ); // Send signals. so_5::send< msg_hello >( *this ); so_5::send< msg_hello >( another ); so_5::send< msg_bye >( *this ); } }; int main() { try { // Starting SObjectizer. so_5::launch( // A function for SO Environment initialization. []( so_5::environment_t & env ) { // Creating and registering single agent as a cooperation. env.register_agent_as_coop( env.make_agent< a_demo_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/make_pipeline/000077500000000000000000000000001523065210100215565ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/make_pipeline/CMakeLists.txt000066400000000000000000000005441523065210100243210ustar00rootroot00000000000000set(SAMPLE sample.so_5.make_pipeline) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.make_pipeline_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/make_pipeline/main.cpp000066400000000000000000000376161523065210100232230ustar00rootroot00000000000000/* * An example of creating message processing pipeline from * a declarative description. * * This example consists from three parts: * * - the first one is a preparation of necessary infrastructure; * - the second one is a declaration of messages to be processed * and all message's processing stuff; * - the third one is a declaration of pipeline and initiation of * message processing. * * A task to be solved with the help of processing pipeline is * a processing of data samples from imaginary temperature sensor. * * A pipeline receives raw data sample on the input and does several * actions: * * - validation of raw data; * - transformation of raw data to a temperature in Celsius degrees; * - archivation and distribution of converted value to outside world; * - checking value for allowed range; * - detection of dangerous situations when temperature is too high; * - initiation of alarm in presence of dangerous situation; * - distribution of the alarm. * * The main target of this example is showing how this processing stages could * be represented without manual description and creation of dedicated agents. * * It is possible. But some C++11 template magic must be used here. */ #include #include #include using namespace std; using namespace so_5; /* * The first part. * * Definition of low-level pipeline implementation details. */ // // All messages will be passed as SObjectizer-messages. // It means that they will be created as dynamically allocated objects. // The name stage_result_t will be used as alias for smart-pointer to // dynamically created message. // // The name stage_result_t means that messages to be passed will be // returned as stage processing result. // template< typename M > using stage_result_t = message_holder_t< M >; // // Just a helper function for creating new message instance. // Something like std::make_shared or std::make_unique. // template< typename M, typename... Args > stage_result_t< M > make_result( Args &&... args ) { return stage_result_t< M >::make(std::forward< Args >(args)...); } // // Just a helper function for the case when there is no processing result // (stage has to return nothing). // template< typename M > stage_result_t< M > make_empty() { return stage_result_t< M >(); } // // We have to deal with two types of stage handlers: // - intermediate handlers which will return some result (e.g. some new // message); // - terminal handlers which can return nothing (e.g. void instead of // stage_result_t); // // This template with specialization defines `input` and `output` // aliases for both cases. // template< typename In, typename Out > struct handler_traits_t { using input = In; using output = stage_result_t< Out >; }; template< typename In > struct handler_traits_t< In, void > { using input = In; using output = void; }; // // A template for repesentation of one stage handler. // // The handler could be specified as free function pointer, as stateful // object with operator() or as lambda function. The actual handler will // be stored as instance of std::function. // template< typename In, typename Out > class stage_handler_t { public : using traits = handler_traits_t< In, Out >; using func_type = function< typename traits::output(const typename traits::input &) >; stage_handler_t( func_type handler ) : m_handler( std::move(handler) ) {} template< typename Callable > stage_handler_t( Callable handler ) : m_handler( handler ) {} typename traits::output operator()( const typename traits::input & a ) const { return m_handler( a ); } private : func_type m_handler; }; // // An agent which will be used as intermediate or terminal pipeline stage. // It will receive input message, call the stage handler and pass // handler result to the next stage (if any). // template< typename In, typename Out > class a_stage_point_t final : public agent_t { public : a_stage_point_t( context_t ctx, stage_handler_t< In, Out > handler, mbox_t next_stage ) : agent_t{ ctx } , m_handler{ std::move( handler ) } , m_next{ std::move(next_stage) } {} void so_define_agent() override { if( m_next ) // Because there is the next stage the appropriate // message handler will be used. so_subscribe_self().event( [this]( const In & evt ) { auto r = m_handler( evt ); if( r ) so_5::send( m_next, r ); } ); else // There is no next stage. A very simple message handler // will be used for that case. so_subscribe_self().event( [this]( const In & evt ) { m_handler( evt ); } ); } private : const stage_handler_t< In, Out > m_handler; const mbox_t m_next; }; // // A specialization of a_stage_point_t for the case of terminal stage of // a pipeline. This type will be used for stage handlers with void // return type. // template< typename In > class a_stage_point_t< In, void > final : public agent_t { public : a_stage_point_t( context_t ctx, stage_handler_t< In, void > handler, mbox_t next_stage ) : agent_t{ ctx } , m_handler{ std::move( handler ) } { if( next_stage ) throw std::runtime_error( "sink point cannot have next stage" ); } void so_define_agent() override { so_subscribe_self().event( [this]( const In & evt ) { m_handler( evt ); } ); } private : const stage_handler_t< In, void > m_handler; }; // // An agent type for such special case as broadcasting of a message // to several parallel and independent pipelines. An agent receives // an input messages and resends it to every pipeline specified. // // An agent of such type should be used only for terminal stages. // template< typename In > class a_broadcaster_t final : public agent_t { public : a_broadcaster_t( context_t ctx, vector< mbox_t > && next_stages ) : agent_t{ ctx } , m_next_stages( std::move(next_stages) ) {} void so_define_agent() override { so_subscribe_self().event( &a_broadcaster_t::evt_broadcast ); } private : const vector< mbox_t > m_next_stages; void evt_broadcast( mhood_t< In > evt ) { // The same message instance will be redirected to subsequent stages. for( const auto & mbox : m_next_stages ) so_5::send( mbox, evt ); } }; // // An alias for functional object to build necessary agent for a // pipeline stage. // using stage_builder_t = function< mbox_t(coop_t &, mbox_t) >; // // Description of one pipeline stage. // template< typename In, typename Out > struct stage_t { stage_builder_t m_builder; }; // // A bunch of definitions related to detection of handler argument // type and return value. // // // Helper type for `arg_type` and `result_type` alises definition. // template< typename R, typename A > struct callable_traits_typedefs_t { using arg_type = A; using result_type = R; }; // // Helper type for dealing with statefull objects with operator() // (they could be usef-defined objects or generated by compiler // like lambdas). // template< typename T > struct lambda_traits_t; template< typename M, typename A, typename T > struct lambda_traits_t< stage_result_t< M >(T::*)(const A &) const > : public callable_traits_typedefs_t< M, A > {}; template< typename A, typename T > struct lambda_traits_t< void (T::*)(const A &) const > : public callable_traits_typedefs_t< void, A > {}; template< typename M, typename A, typename T > struct lambda_traits_t< stage_result_t< M >(T::*)(const A &) > : public callable_traits_typedefs_t< M, A > {}; template< typename A, typename T > struct lambda_traits_t< void (T::*)(const A &) > : public callable_traits_typedefs_t< void, A > {}; // // Main type for definition of `arg_type` and `result_type` aliases. // With specialization for various cases. // template< typename T > struct callable_traits_t : public lambda_traits_t< decltype(&T::operator()) > {}; template< typename M, typename A > struct callable_traits_t< stage_result_t< M >(*)(const A &) > : public callable_traits_typedefs_t< M, A > {}; template< typename A > struct callable_traits_t< void(*)(const A &) > : public callable_traits_typedefs_t< void, A > {}; // // Main function for defining intermediate of terminal stage of a pipeline. // template< typename Callable, typename In = typename callable_traits_t< Callable >::arg_type, typename Out = typename callable_traits_t< Callable >::result_type > stage_t< In, Out > stage( Callable handler ) { stage_builder_t builder{ [h = std::move(handler)]( coop_t & coop, mbox_t next_stage) -> mbox_t { return coop.make_agent< a_stage_point_t >( std::move(h), std::move(next_stage) ) ->so_direct_mbox(); } }; return { std::move(builder) }; } // // Serie of helper functions for building description for // `broadcast` stage. // // Those functions are used for collecting // `builders` functions for every child pipeline. // template< typename In, typename Out, typename... Rest > void move_sink_builder_to( vector< stage_builder_t > & receiver, stage_t< In, Out > && first, Rest &&... rest ) { receiver.emplace_back( std::move( first.m_builder ) ); if constexpr( 0u != sizeof...(rest) ) move_sink_builder_to( receiver, std::forward< Rest >(rest)... ); } template< typename In, typename Out, typename... Rest > vector< stage_builder_t > collect_sink_builders( stage_t< In, Out > && first, Rest &&... stages ) { vector< stage_builder_t > receiver; receiver.reserve( 1 + sizeof...(stages) ); move_sink_builder_to( receiver, std::move(first), std::forward(stages)... ); return receiver; } // // Main helper function for building `broadcast` stage. // template< typename In, typename Out, typename... Rest > stage_t< In, void > broadcast( stage_t< In, Out > && first, Rest &&... stages ) { stage_builder_t builder{ [broadcasts = collect_sink_builders( std::move(first), std::forward< Rest >(stages)...)] ( coop_t & coop, mbox_t ) -> mbox_t { vector< mbox_t > mboxes; mboxes.reserve( broadcasts.size() ); for( const auto & b : broadcasts ) mboxes.emplace_back( b( coop, mbox_t{} ) ); return coop.make_agent< a_broadcaster_t >( std::move(mboxes) ) ->so_direct_mbox(); } }; return { std::move(builder) }; } // // Helper `operator|` for continuation of a pipeline definition. // template< typename In, typename Out1, typename Out2 > stage_t< In, Out2 > operator|( stage_t< In, Out1 > && prev, stage_t< Out1, Out2 > && next ) { return { stage_builder_t{ [prev, next]( coop_t & coop, mbox_t next_stage ) -> mbox_t { auto m = next.m_builder( coop, std::move(next_stage) ); return prev.m_builder( coop, std::move(m) ); } } }; } // // Main function for creation of all pipeline-related stuff. // template< typename In, typename Out, typename... Args > mbox_t make_pipeline( // Agent who will own a cooperation with pipeline-related agent. agent_t & owner, // Definition of a pipeline. stage_t< In, Out > && sink, // Optional args to be passed to so_5::create_child_coop function. Args &&... args ) { auto coop = create_child_coop( owner, std::forward< Args >(args)... ); auto mbox = sink.m_builder( *coop, mbox_t{} ); owner.so_environment().register_coop( std::move(coop) ); return mbox; } /* * The second part. * * Definition of messages to be processed by a pipeline and * the message processing code. */ // Raw data from a sensor. struct raw_measure { int m_meter_id; uint8_t m_high_bits; uint8_t m_low_bits; }; // Type of input for validation stage with raw data from a sensor. struct raw_value { raw_measure m_data; }; // Type of input for conversion stage with valid raw data from a sensor. struct valid_raw_value { raw_measure m_data; }; // Data from a sensor after conversion to Celsius degrees. struct calculated_measure { int m_meter_id; float m_measure; }; // The type for result of conversion stage with converted data from a sensor. struct sensor_value { calculated_measure m_data; }; // Type with value which could mean a dangerous level of temperature. struct suspicional_value { calculated_measure m_data; }; // Type with information about detected dangerous situation. struct alarm_detected { int m_meter_id; }; // // The first stage of a pipeline. Validation of raw data from a sensor. // // Returns valid_raw_value or nothing if value is invalid. // stage_result_t< valid_raw_value > validation( const raw_value & v ) { if( 0x7 >= v.m_data.m_high_bits ) return make_result< valid_raw_value >( v.m_data ); else return make_empty< valid_raw_value >(); } // // The second stage of a pipeline. Conversion from raw data to a value // in Celsius degrees. // stage_result_t< sensor_value > conversion( const valid_raw_value & v ) { return make_result< sensor_value >( calculated_measure{ v.m_data.m_meter_id, 0.5f * ((static_cast< uint16_t >( v.m_data.m_high_bits ) << 8) + v.m_data.m_low_bits) } ); } // // Simulation of the data archivation. // void archivation( const sensor_value & v ) { clog << "archiving (" << v.m_data.m_meter_id << "," << v.m_data.m_measure << ")" << endl; } // // Simulation of the data distribution. // void distribution( const sensor_value & v ) { clog << "distributing (" << v.m_data.m_meter_id << "," << v.m_data.m_measure << ")" << endl; } // // The first stage of a child pipeline at third level of the main pipeline. // // Checking for to high value of the temperature. // // Returns suspicional_value message or nothing. // stage_result_t< suspicional_value > range_checking( const sensor_value & v ) { if( v.m_data.m_measure >= 45.0f ) return make_result< suspicional_value >( v.m_data ); else return make_empty< suspicional_value >(); } // // The next stage of a child pipeline. // // Checks for two suspicional_value-es in 25ms time window. // class alarm_detector { using clock = chrono::steady_clock; public : stage_result_t< alarm_detected > operator()( const suspicional_value & v ) { if( m_previous ) if( *m_previous + chrono::milliseconds(25) > clock::now() ) { m_previous = std::nullopt; return make_result< alarm_detected >( v.m_data.m_meter_id ); } m_previous = clock::now(); return make_empty< alarm_detected >(); } private : optional< clock::time_point > m_previous; }; // // One of last stages of a child pipeline. // Imitates beginning of the alarm processing. // void alarm_initiator( const alarm_detected & v ) { clog << "=== alarm (" << v.m_meter_id << ") ===" << endl; } // // Another of last stages of a child pipeline. // Imitates distribution of the alarm. // void alarm_distribution( ostream & to, const alarm_detected & v ) { to << "alarm_distribution (" << v.m_meter_id << ")" << endl; } /* * The third part. * * Definition of message processing pipeline and imitation of * several measures from a sensor. */ class a_parent_t final : public agent_t { // Signal for finishing the imitation. struct shutdown : public signal_t {}; public : a_parent_t( context_t ctx ) : agent_t( ctx ) {} void so_define_agent() override { // On shutdown the coop and its children must be deregistered. so_subscribe_self().event( [this](mhood_t< shutdown >) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { // Construction of a pipeline. auto pipeline = make_pipeline( *this, stage(validation) | stage(conversion) | broadcast( stage(archivation), stage(distribution), stage(range_checking) | stage(alarm_detector{}) | broadcast( stage(alarm_initiator), stage( []( const alarm_detected & v ) { alarm_distribution( cerr, v ); } ) ) ) ); // One second for imitation then shutdown. send_delayed< shutdown >( *this, chrono::seconds(1) ); // Imitation of several samples from a sensor. // One sample for each 10ms. for( uint8_t i = 0; i < static_cast< uint8_t >(250); i += 10 ) send_delayed< raw_value >( pipeline, chrono::milliseconds( i ), raw_measure{ 0, 0, i } ); } }; int main() { try { so_5::launch( []( environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_parent_t >() ); } ); } catch( const exception & x ) { cerr << "Exception: " << x.what() << endl; } } sobjectizer-5.8.6/dev/sample/so_5/many_timers/000077500000000000000000000000001523065210100213035ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/many_timers/CMakeLists.txt000066400000000000000000000005371523065210100240500ustar00rootroot00000000000000set(SAMPLE sample.so_5.many_timers) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.many_timers_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/many_timers/main.cpp000066400000000000000000000130561523065210100227400ustar00rootroot00000000000000/* * Sample of sending big amount of delayed messages. * This sample can also be used as stress test for * SObjectizer timers implementation. */ #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif // Configuration for the sample. struct cfg_t { // Count of messages to be sent. unsigned long long m_messages = 5000000; // Initial delay for every message. std::chrono::milliseconds m_delay = std::chrono::milliseconds{ 100 }; // Type of timer. enum class timer_type_t { wheel, list, heap } m_timer_type = { timer_type_t::wheel }; }; // Integer argument parsing helper. template< class T > T str_to_value( const char * s ) { std::stringstream stream; stream << s; stream.seekg( 0 ); T r; stream >> r; if( !stream || !stream.eof() ) throw std::invalid_argument( std::string( "unable to parse value '" ) + s + "'" ); return r; } // Parse command-line arguments and prepare sample configuration. cfg_t parse_args( int argc, char ** argv ) { cfg_t result; for( char ** current = &argv[1], ** last = &argv[argc]; current != last; ++current ) { if( 0 == std::strcmp( *current, "-h" ) ) { std::cout << "Usage:\n\n" "sample.so_5.many_timers [options]\n\n" "Where options are:\n" "-m count of delayed messages to be sent\n" "-d pause for delayed messages\n" "-t timer type (wheel, list, heap)\n" "-h show this help\n" << std::flush; std::exit( 1 ); } else if( 0 == std::strcmp( *current, "-d" ) ) { ++current; if( current == last ) throw std::invalid_argument( "-d requires value (milliseconds)" ); result.m_delay = std::chrono::milliseconds( str_to_value< unsigned int >( *current ) ); } else if( 0 == std::strcmp( *current, "-m" ) ) { ++current; if( current == last ) throw std::invalid_argument( "-m requires value (message count)" ); result.m_messages = str_to_value< unsigned long long >( *current ); } else if( 0 == std::strcmp( *current, "-t" ) ) { ++current; if( current == last ) throw std::invalid_argument( "-t requires value (timer type)" ); if( 0 == std::strcmp( *current, "wheel" ) ) result.m_timer_type = cfg_t::timer_type_t::wheel; else if( 0 == std::strcmp( *current, "list" ) ) result.m_timer_type = cfg_t::timer_type_t::list; else if( 0 == std::strcmp( *current, "heap" ) ) result.m_timer_type = cfg_t::timer_type_t::heap; else throw std::invalid_argument( "unknown type of timer" ); } else throw std::invalid_argument( std::string( "unknown argument: " ) + *current ); } return result; } void show_cfg( const cfg_t & cfg ) { std::string timer_type = "wheel"; if( cfg.m_timer_type == cfg_t::timer_type_t::list ) timer_type = "list"; else if( cfg.m_timer_type == cfg_t::timer_type_t::heap ) timer_type = "heap"; std::cout << "timer: " << timer_type << ", messages: " << cfg.m_messages << ", delay: " << cfg.m_delay.count() << "ms" << std::endl; } // Timer message. struct msg_timer final : public so_5::signal_t {}; // Agent-receiver. class a_receiver_t final : public so_5::agent_t { public : a_receiver_t( context_t ctx, unsigned long long messages ) : so_5::agent_t( ctx ) , m_messages_to_receive( messages ) {} void so_define_agent() override { so_subscribe_self().event( &a_receiver_t::evt_timer ); } void evt_timer(mhood_t< msg_timer >) { ++m_messages_received; if( m_messages_received == m_messages_to_receive ) so_deregister_agent_coop_normally(); } private : const unsigned long long m_messages_to_receive; unsigned long long m_messages_received = { 0 }; }; // Agent-sender. class a_sender_t final : public so_5::agent_t { public : a_sender_t( context_t ctx, so_5::mbox_t dest_mbox, unsigned long long messages_to_send, std::chrono::milliseconds delay ) : so_5::agent_t( ctx ) , m_dest_mbox( std::move( dest_mbox ) ) , m_messages_to_send( messages_to_send ) , m_delay( delay ) {} void so_evt_start() override { for( unsigned long long i = 0; i != m_messages_to_send; ++i ) so_5::send_delayed< msg_timer >( m_dest_mbox, m_delay ); } private : const so_5::mbox_t m_dest_mbox; const unsigned long long m_messages_to_send; const std::chrono::milliseconds m_delay; }; void run_sobjectizer( const cfg_t & cfg ) { so_5::launch( // Initialization actions. [&cfg]( so_5::environment_t & env ) { // Active object dispatcher is necessary. env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), [&cfg]( so_5::coop_t & coop ) { auto a_receiver = coop.make_agent< a_receiver_t >( cfg.m_messages ); coop.make_agent< a_sender_t >( a_receiver->so_direct_mbox(), cfg.m_messages, cfg.m_delay ); }); }, // Parameter tuning actions. [&cfg]( so_5::environment_params_t & params ) { // Appropriate timer thread must be used. so_5::timer_thread_factory_t timer = so_5::timer_wheel_factory(); if( cfg.m_timer_type == cfg_t::timer_type_t::list ) timer = so_5::timer_list_factory(); else if( cfg.m_timer_type == cfg_t::timer_type_t::heap ) timer = so_5::timer_heap_factory(); params.timer_thread( timer ); } ); } int main( int argc, char ** argv ) { try { const auto cfg = parse_args( argc, argv ); show_cfg( cfg ); run_sobjectizer( cfg ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/mchain_empty_notificator/000077500000000000000000000000001523065210100240325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/mchain_empty_notificator/CMakeLists.txt000066400000000000000000000005721523065210100265760ustar00rootroot00000000000000set(SAMPLE sample.so_5.mchain_empty_notificator) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.mchain_empty_notificator_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/mchain_empty_notificator/main.cpp000066400000000000000000000133411523065210100254640ustar00rootroot00000000000000/* * An example of using mchain with empty_notificator. * * There is a producer that tries to send a message to a mchain, but * only if the mchain is empty. The emptyness of the mchain is detected * by a empty_notificator. */ #include #include #include // // Stuff for logging. // using log_msg = std::string; so_5::mbox_t make_logger( so_5::coop_t & coop ) { class logger_actor final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( [](mhood_t cmd ) { std::cout << *cmd << std::endl; } ); } }; // Logger will work on its own working thread. auto logger = coop.make_agent_with_binder< logger_actor >( so_5::disp::one_thread::make_dispatcher( coop.environment() ).binder() ); return logger->so_direct_mbox(); } template< typename A > inline void operator<<=( const so_5::mbox_t & to, A && a ) { so_5::send< log_msg >( to, std::forward< A >(a) ); } struct msg_maker { std::ostringstream m_os; template< typename A > msg_maker & operator<<( A && a ) { m_os << std::forward< A >(a); return *this; } }; inline void operator<<=( const so_5::mbox_t & to, msg_maker & maker ) { to <<= maker.m_os.str(); } // // Implementation of producers. // // A request to be sent for processing. struct request { std::string m_payload; }; /* * Producer agent will send N requests and then closes mchain. */ class producer final : public so_5::agent_t { // This signal initiates next send attempt. struct send_next final : public so_5::signal_t {}; // This signal indicates that the mchain is empty. struct mchain_is_empty final : public so_5::signal_t {}; public : producer( context_t ctx, std::string name, so_5::mbox_t logger_mbox, unsigned int requests ) : so_5::agent_t{ ctx } , m_name( std::move(name) ) , m_logger_mbox{ std::move(logger_mbox) } , m_attempts_left{ requests } { // Create the target mchain. m_target_mchain = so_environment().create_mchain( so_5::make_limited_without_waiting_mchain_params( // Just one message inside, there is no need for more. 1, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::throw_exception) .empty_notificator( [m = so_direct_mbox()]() { // Tell the agent that the mchain is empty now. so_5::send< mchain_is_empty >( m ); } ) ); } // Get the target mchain. [[nodiscard]] so_5::mchain_t target_mchain() const { return m_target_mchain; } void so_define_agent() override { so_subscribe_self() .event( &producer::evt_send_next ) .event( &producer::evt_mchain_is_empty ) ; } void so_evt_start() override { // Initiate request sending loop. so_5::send< send_next >( *this ); } private : const std::string m_name; const so_5::mbox_t m_logger_mbox; // How many attempts remains. unsigned int m_attempts_left; // The target mchain to be used. so_5::mchain_t m_target_mchain; // Indicator of mchain's emptiness. // The mchain is empty at the beginning. bool m_target_is_empty = true; // An event for next attempt to send another requests. void evt_send_next(mhood_t< send_next >) { if( m_target_is_empty ) { m_logger_mbox <<= ( msg_maker() << m_name << ": sending a message..." ); // Assume that mchain won't be empty after the sent. m_target_is_empty = false; so_5::send< request >( m_target_mchain, m_name + "_request_" + std::to_string( m_attempts_left ) ); } else { m_logger_mbox <<= ( msg_maker{} << m_name << ": message is not sent because mchain is full" ); } --m_attempts_left; if( m_attempts_left ) { // Next try after a timeout. so_5::send_delayed< send_next >( *this, std::chrono::milliseconds{ 50 } ); } else { // Nothing to do, it's time to close the target mchain. so_5::close_retain_content( so_5::exceptions_enabled, m_target_mchain ); m_logger_mbox <<= ( msg_maker{} << m_name << ": target mchain is closed" ); } } void evt_mchain_is_empty( mhood_t ) { m_logger_mbox <<= ( msg_maker{} << m_name << ": mchain_is_empty received" ); m_target_is_empty = true; } }; void run_example() { so_5::mbox_t logger_mbox; so_5::mchain_t chain_to_use; // Launch SObjectizer without blocking the current thread. so_5::wrapped_env_t sobjectizer{ so_5::wrapped_env_t::wait_init_completion, [&logger_mbox, &chain_to_use]( so_5::environment_t & env ) { chain_to_use = env.introduce_coop( [&logger_mbox]( so_5::coop_t & coop ) { // Logger will work on its own context. logger_mbox = make_logger( coop ); auto * a_producer = coop.make_agent< producer >( "Alice", logger_mbox, 5 ); return a_producer->target_mchain(); }); } }; // Loop for reading messages from chain_to_use. auto status = so_5::mchain_props::extraction_status_t::no_messages; while( so_5::mchain_props::extraction_status_t::chain_closed != status ) { // Take a pause. std::this_thread::sleep_for( std::chrono::milliseconds{ 75 } ); // Try to get a message from the chain. status = so_5::receive( so_5::from( chain_to_use ) .handle_all() .no_wait_on_empty(), [&logger_mbox]( const request & req ) { logger_mbox <<= "Bob: start handling of received request"; logger_mbox <<= ( msg_maker{} << "Bob: request payload: " << req.m_payload ); logger_mbox <<= "Bob: finish handling of received request"; } ).status(); } // SObjectizer will be shutdown automatically. } int main() { try { run_example(); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/mchain_empty_notificator_2/000077500000000000000000000000001523065210100242535ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/mchain_empty_notificator_2/CMakeLists.txt000066400000000000000000000005761523065210100270230ustar00rootroot00000000000000set(SAMPLE sample.so_5.mchain_empty_notificator_2) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.mchain_empty_notificator_2_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/mchain_empty_notificator_2/main.cpp000066400000000000000000000153251523065210100257110ustar00rootroot00000000000000/* * An example of using mchain with empty_notificator. * * There is a producer that tries to send a message to a mchain, but * only if the mchain is empty. The emptyness of the mchain is detected * by a empty_notificator. * * The producer sends messages to the target mchain until it becomes full * and then wait for the emptyness of the mchain to resume sending. */ #include #include #include // // Stuff for logging. // using log_msg = std::string; so_5::mbox_t make_logger( so_5::coop_t & coop ) { class logger_actor final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( [](mhood_t cmd ) { std::cout << *cmd << std::endl; } ); } }; // Logger will work on its own working thread. auto logger = coop.make_agent_with_binder< logger_actor >( so_5::disp::one_thread::make_dispatcher( coop.environment() ).binder() ); return logger->so_direct_mbox(); } template< typename A > inline void operator<<=( const so_5::mbox_t & to, A && a ) { so_5::send< log_msg >( to, std::forward< A >(a) ); } struct msg_maker { std::ostringstream m_os; template< typename A > msg_maker & operator<<( A && a ) { m_os << std::forward< A >(a); return *this; } }; inline void operator<<=( const so_5::mbox_t & to, msg_maker & maker ) { to <<= maker.m_os.str(); } // // Implementation of producers. // // A request to be sent for processing. struct request { std::string m_payload; }; /* * Producer agent will send N requests and then closes mchain. */ class producer final : public so_5::agent_t { // This signal initiates next send attempt. struct send_next final : public so_5::signal_t {}; // This signal indicates that the mchain is empty. struct mchain_is_empty final : public so_5::signal_t {}; public : producer( context_t ctx, std::string name, so_5::mbox_t logger_mbox, unsigned int requests ) : so_5::agent_t{ ctx } , m_name( std::move(name) ) , m_logger_mbox{ std::move(logger_mbox) } , m_attempts_left{ requests } { // Create the target mchain. m_target_mchain = so_environment().create_mchain( so_5::make_limited_without_waiting_mchain_params( // Use mchain as a buffer of fixed capacity. 5, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::throw_exception) .empty_notificator( [m = so_direct_mbox()]() { // Tell the agent that the mchain is empty now. so_5::send< mchain_is_empty >( m ); } ) ); } // Get the target mchain. [[nodiscard]] so_5::mchain_t target_mchain() const { return m_target_mchain; } void so_define_agent() override { this >>= st_sending_enabled; st_sending_enabled .event( &producer::evt_send_next ) ; st_sending_blocked .event( &producer::evt_send_next_when_blocked ) .event( &producer::evt_mchain_is_empty ) ; } void so_evt_start() override { // Initiate request sending loop. m_send_timer = so_5::send_periodic< send_next >( *this, // No pause before the first sent. std::chrono::milliseconds::zero(), // Pause before next send. std::chrono::milliseconds{ 20 }); } private : // State in that agent can try to send messages. state_t st_sending_enabled{ this, "sending_enabled" }; // State in that agent can't send messages because mchain // isn't empty yet. state_t st_sending_blocked{ this, "sending_blocked" }; const std::string m_name; const so_5::mbox_t m_logger_mbox; // How many attempts remains. unsigned int m_attempts_left; // The target mchain to be used. so_5::mchain_t m_target_mchain; // Timer ID for periodic send_next signal. so_5::timer_id_t m_send_timer; // An event for the next attempt to send another request. void evt_send_next(mhood_t< send_next >) { const auto result = so_5::select( so_5::from_all().handle_n( 1 ).no_wait_on_empty(), so_5::send_case( m_target_mchain, so_5::message_holder_t< request >::make( m_name + "_request_" + std::to_string( m_attempts_left ) ), [this]() { m_logger_mbox <<= ( msg_maker() << m_name << ": message stored to target mbox" ); --m_attempts_left; } ) ); if( !result.was_sent() ) { // Message wasn't sent. m_logger_mbox <<= ( msg_maker{} << m_name << ": message is not sent because mchain is full" ); // The sending has to be paused. this >>= st_sending_blocked; } else { if( !m_attempts_left ) { // It's time to close the target mchain. so_5::close_retain_content( so_5::exceptions_enabled, m_target_mchain ); // Periodic message has to be cancelled because all // messages have been sent. m_send_timer.release(); } } } // An event for next attempt to send another requests. void evt_send_next_when_blocked(mhood_t< send_next >) { m_logger_mbox <<= ( msg_maker{} << m_name << ": message can't be sent because mchain is full" ); } // Reaction to notification about emptiness of the target mbox. void evt_mchain_is_empty( mhood_t ) { m_logger_mbox <<= ( msg_maker{} << m_name << ": mchain_is_empty received" ); this >>= st_sending_enabled; } }; void run_example() { so_5::mbox_t logger_mbox; so_5::mchain_t chain_to_use; // Launch SObjectizer without blocking the current thread. so_5::wrapped_env_t sobjectizer{ so_5::wrapped_env_t::wait_init_completion, [&logger_mbox, &chain_to_use]( so_5::environment_t & env ) { chain_to_use = env.introduce_coop( [&logger_mbox]( so_5::coop_t & coop ) { // Logger will work on its own context. logger_mbox = make_logger( coop ); auto * a_producer = coop.make_agent< producer >( "Alice", logger_mbox, 20 ); return a_producer->target_mchain(); }); } }; // Loop for reading messages from chain_to_use. auto status = so_5::mchain_props::extraction_status_t::no_messages; while( so_5::mchain_props::extraction_status_t::chain_closed != status ) { // Try to get a message from the chain. status = so_5::receive( so_5::from( chain_to_use ) .handle_all() .no_wait_on_empty(), [&logger_mbox]( const request & req ) { logger_mbox <<= "Bob: start handling of received request"; logger_mbox <<= ( msg_maker{} << "Bob: request payload: " << req.m_payload ); // Take a pause. std::this_thread::sleep_for( std::chrono::milliseconds{ 75 } ); logger_mbox <<= "Bob: finish handling of received request"; } ).status(); } // SObjectizer will be shutdown automatically. } int main() { try { run_example(); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/mchain_fibonacci/000077500000000000000000000000001523065210100222105ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/mchain_fibonacci/CMakeLists.txt000066400000000000000000000010041523065210100247430ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.mchain_fibonacci) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.mchain_fibonacci_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/mchain_fibonacci/main.cpp000066400000000000000000000030731523065210100236430ustar00rootroot00000000000000/* * Usage of select() with send_case() for calculation of Fibonacci numbers. */ #include #include using namespace std; using namespace std::chrono_literals; using namespace so_5; struct quit {}; void fibonacci( mchain_t values_ch, mchain_t quit_ch ) { int x = 0, y = 1; mchain_select_result_t r; do { r = select( from_all().handle_n(1), // Sends a new message of type 'int' with value 'x' inside // when values_ch is ready for a new outgoing message. send_case( values_ch, message_holder_t::make(x), [&x, &y] { // This block of code will be called after the send(). auto old_x = x; x = y; y = old_x + y; } ), // Receive a 'quit' message from quit_ch if it is here. receive_case( quit_ch, [](quit){} ) ); } // Continue the loop while we send something and receive nothing. while( r.was_sent() && !r.was_handled() ); } int main() { wrapped_env_t sobj; thread fibonacci_thr; auto thr_joiner = auto_join( fibonacci_thr ); // The chain for Fibonacci number will have limited capacity. auto values_ch = create_mchain( sobj, 1s, 1, mchain_props::memory_usage_t::preallocated, mchain_props::overflow_reaction_t::abort_app ); auto quit_ch = create_mchain( sobj ); auto ch_closer = auto_close_drop_content( values_ch, quit_ch ); fibonacci_thr = thread{ fibonacci, values_ch, quit_ch }; // Read the first 10 numbers from values_ch. receive( from( values_ch ).handle_n( 10 ), // And show every number to the standard output. []( int v ) { cout << v << endl; } ); send< quit >( quit_ch ); } sobjectizer-5.8.6/dev/sample/so_5/mchain_handler_formats/000077500000000000000000000000001523065210100234435ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/mchain_handler_formats/CMakeLists.txt000066400000000000000000000010211523065210100261750ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.mchain_handler_formats) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.mchain_handler_formats_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/mchain_handler_formats/main.cpp000066400000000000000000000062141523065210100250760ustar00rootroot00000000000000/* * An example for demonstration of different format of message handlers for mchain. */ #include #include // Main SObjectizer header file. #include // Types of messages and signals to be placed into mchain. struct two_ints { int a, b; }; struct delayed_two_ints { int a, b; }; struct just_signal final : public so_5::signal_t {}; struct periodic_signal final : public so_5::signal_t {}; void demo() { // A SObjectizer instance. so_5::wrapped_env_t sobj; // Message chain to be used. auto ch = so_5::create_mchain( sobj, // Wait on overloaded mchain for 5min. std::chrono::minutes{5}, // No more than 3 messages in chain. 3u, // Space for mchain will be preallocated. so_5::mchain_props::memory_usage_t::preallocated, // What to do on overflow. // This value has no sence because we use too large time limit. // Because of that use hardest case. so_5::mchain_props::overflow_reaction_t::abort_app ); // We must store timer_id for periodic signal somewhere. // Otherwise periodic signal will be canceled automaticallly right after // exit from send_periodic function. so_5::timer_id_t periodic_signal_timer; // Spawn another thread to fill mchain. std::thread worker{ [&]{ // Send a string as message. so_5::send< std::string >( ch, "Hello!" ); // Send int as message. so_5::send< int >( ch, 42 ); // Send struct instance as message. so_5::send< two_ints >( ch, 0, 1 ); // Send delayed messages. so_5::send_delayed< delayed_two_ints >( ch, std::chrono::milliseconds{150}, 1, 2 ); // Send signal. so_5::send< just_signal >( ch ); // Send periodic signal. periodic_signal_timer = so_5::send_periodic< periodic_signal >( ch, std::chrono::milliseconds{20}, std::chrono::milliseconds{150} ); } }; // Read content of mchain. // We expect exactly 8 messages/signals. receive( from( ch ).handle_n( 8 ), // Message instance by const reference. []( const std::string & v ) { std::cout << "str: " << v << std::endl; }, // Message instance by value (efficient for small types like int). []( int v ) { std::cout << "int: " << v << std::endl; }, // Message instance via mhood_t value. []( so_5::mhood_t< two_ints > v ) { std::cout << "two_ints: " << v->a << ", " << v->b << std::endl; }, // Message instance via const reference to mhood_t. []( const so_5::mhood_t< delayed_two_ints > & v ) { std::cout << "delayed_two_ints: " << v->a << ", " << v->b << std::endl; }, // Signal handler via mhood_t value. []( so_5::mhood_t< just_signal > ) { std::cout << "just signal" << std::endl; }, // Signal handler via const reference to mhood_t. []( const so_5::mhood_t< periodic_signal > & ) { std::cout << "periodic signal" << std::endl; } ); // Close channel with dropping all its content. // It has sence because timer thread can send periodic signals to // mchain while we wait on worker.join(). close_drop_content( so_5::exceptions_enabled, ch ); worker.join(); // SObjectizer will be stopped automatically. } int main() { try { demo(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/mchain_multi_consumers/000077500000000000000000000000001523065210100235235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/mchain_multi_consumers/CMakeLists.txt000066400000000000000000000010211523065210100262550ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.mchain_multi_consumers) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.mchain_multi_consumers_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/mchain_multi_consumers/main.cpp000066400000000000000000000055241523065210100251610ustar00rootroot00000000000000/* * An example for demonstration of using receive from several threads. */ #include #include // Main SObjectizer header file. #include using namespace std; size_t get_workers_count() { auto c = thread::hardware_concurrency(); if( c > 2 ) // Decrement count of thread because one thread will be producer. c -= 1; else if( 1 == c ) // We must have at least two consumer threads. c = 2; return c; } void demo() { // Type of message to be received from every consumer thread as a result. struct consumer_result { thread::id m_id; size_t m_values_received; uint64_t m_sum; }; // A SObjectizer instance. so_5::wrapped_env_t sobj; // Message chain to be used for values speading between worker threads. auto values_ch = so_5::create_mchain( sobj, // Chain is size-limited with blocking of sender on overload. // Wait on overloaded mchain for 5min. chrono::minutes{5}, // No more than 300 messages in chain. 300u, // Space for mchain will be preallocated. so_5::mchain_props::memory_usage_t::preallocated, // What to do on overflow. // This value has no sence because we use too large time limit. // Because of that use hardest case. so_5::mchain_props::overflow_reaction_t::abort_app ); // Message chain to be used for results from consumers. // A very simple chain will be created for that. auto results_ch = create_mchain( sobj ); // Create workers. const auto workers_count = get_workers_count(); vector< thread > workers; workers.reserve( workers_count ); for( size_t i = 0; i != workers_count; ++i ) workers.emplace_back( thread{ [&values_ch, &results_ch] { // Receive all data from input chain. size_t received = 0u; uint64_t sum = 0u; receive( from( values_ch ).handle_all(), [&sum, &received]( unsigned int v ) { ++received; sum += v; } ); // Send result back. so_5::send< consumer_result >( results_ch, this_thread::get_id(), received, sum ); } } ); cout << "Workers created: " << workers_count << endl; // Send a bunch of values for consumers. for( unsigned int i = 0; i != 10000; ++i ) so_5::send< unsigned int >( values_ch, i ); // No more values will be sent. close_retain_content( so_5::exceptions_enabled, values_ch ); // Receive responses from consumers. receive( // Exactly workers_count results expected. from( results_ch ).handle_n( workers_count ), []( const consumer_result & r ) { cout << "Thread: " << r.m_id << ", values: " << r.m_values_received << ", sum: " << r.m_sum << endl; } ); // All consumer threads must be finished. for_each( begin(workers), end(workers), []( thread & t ) { t.join(); } ); // SObjectizer will be stopped automatically. } int main() { try { demo(); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/mchain_select/000077500000000000000000000000001523065210100215525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/mchain_select/CMakeLists.txt000066400000000000000000000007761523065210100243240ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.mchain_select) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.mchain_select_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/mchain_select/main.cpp000066400000000000000000000075211523065210100232070ustar00rootroot00000000000000/* * Usage of select() for listening several mchains. */ #include #include #include // Main SObjectizer header file. #include using namespace std; void reverse_worker( so_5::mchain_t command_ch, so_5::mchain_t reply_ch ) { receive( from( command_ch ).handle_all(), [&]( string str ) { this_thread::sleep_for( chrono::milliseconds( 10 * str.size() ) ); reverse( str.begin(), str.end() ); so_5::send< string >( reply_ch, str ); } ); close_retain_content( so_5::exceptions_enabled, reply_ch ); } void doubler_worker( so_5::mchain_t command_ch, so_5::mchain_t reply_ch ) { receive( from( command_ch ).handle_all(), [&]( string str ) { this_thread::sleep_for( chrono::milliseconds( 5 * str.size() * 2 ) ); so_5::send< string >( reply_ch, str + str ); } ); close_retain_content( so_5::exceptions_enabled, reply_ch ); } void demo() { // A SObjectizer instance. so_5::wrapped_env_t sobj; // Strings to be sent to workers. vector< string > strings{ "one", "two", "three", "four" }; // Thread objects for workers. thread reverse_thread; thread doubler_thread; // Threads must be automatically joined (either normal return from // demo() or in case of an exception). auto thread_joiner = so_5::auto_join( reverse_thread, doubler_thread ); // Create workers. // The first worker and its stuff. // Chains for the first worker. auto reverse_cmd_ch = create_mchain( sobj ); auto reverse_reply_ch = create_mchain( sobj ); // Command and reply chains must be closed automatically in case // of unexpected exit (as result of exception for example). // Content of mchain must be dropped in case of unxpected exit. auto reverse_chain_closer = so_5::auto_close_drop_content( reverse_cmd_ch, reverse_reply_ch ); // Launching thread for the first worker. reverse_thread = thread{ reverse_worker, reverse_cmd_ch, reverse_reply_ch }; // Interator for reverse_worker tasks. auto reverse_it = strings.begin(); // The second worker and its stuff. auto doubler_cmd_ch = create_mchain( sobj ); auto doubler_reply_ch = create_mchain( sobj ); // Command and reply chains must be closed automatically in case // of unexpected exit (as result of exception for example). // Content of mchain must be dropped in case of unxpected exit. auto doubler_chain_closer = so_5::auto_close_drop_content( doubler_cmd_ch, doubler_reply_ch ); // Launching thread for the second worker. doubler_thread = thread{ doubler_worker, doubler_cmd_ch, doubler_reply_ch }; // Interator for doubler_worker tasks. auto doubler_it = strings.begin(); // Send initial messages. so_5::send< string >( reverse_cmd_ch, *(reverse_it++) ); so_5::send< string >( doubler_cmd_ch, *(doubler_it++) ); // Handle results and initiate new tasks. select( so_5::from_all().handle_all(), receive_case( reverse_reply_ch, [&]( const string & v ) { cout << "reverse_result: " << v << endl; if( reverse_it != strings.end() ) so_5::send< string >( reverse_cmd_ch, *(reverse_it++) ); else // No more work for reverse worker. Close command chain and // worker will finish its work. close_drop_content( so_5::exceptions_enabled, reverse_cmd_ch ); } ), receive_case( doubler_reply_ch, [&]( const string & v ) { cout << "doubler_result: " << v << endl; if( doubler_it != strings.end() ) so_5::send< string >( doubler_cmd_ch, *(doubler_it++) ); else // No more work for doubler worker. Close command chain and // worker will finish its work. close_drop_content( so_5::exceptions_enabled, doubler_cmd_ch ); } ) ); // There is no need to call join() for reverse_worker and doubler_worker // because of auto_joiner objects. // SObjectizer will be stopped automatically. } int main() { try { demo(); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/modify_resend_as_immutable/000077500000000000000000000000001523065210100243255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/modify_resend_as_immutable/CMakeLists.txt000066400000000000000000000010311523065210100270600ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.modify_resend_as_immutable) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.modify_resend_as_immutable_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/modify_resend_as_immutable/main.cpp000066400000000000000000000050331523065210100257560ustar00rootroot00000000000000/* * Example of usage of mutable_msg with resending it as an immutable_msg. */ #include #include // A message which will be modified. struct envelope final : public so_5::message_t { // Payload to be modified. // Please note: it is not a const object. std::string m_payload; envelope(std::string payload) : m_payload(std::move(payload)) {} }; // An agent which will mutate message. class modificator_agent final : public so_5::agent_t { public : modificator_agent(context_t ctx, std::string prefix, std::string suffix, so_5::mbox_t receiver) : so_5::agent_t(std::move(ctx)) , m_prefix(std::move(prefix)) , m_suffix(std::move(suffix)) , m_receiver(std::move(receiver)) { so_subscribe_self().event( &modificator_agent::on_envelope ); } private : const std::string m_prefix; const std::string m_suffix; const so_5::mbox_t m_receiver; void on_envelope(mutable_mhood_t cmd) { // Show message address and its old content. std::cout << "modificator, msg_addr=" << cmd.get() << ", old_content=" << cmd->m_payload; // Modify message's payload. cmd->m_payload.insert(begin(cmd->m_payload), begin(m_prefix), end(m_prefix)); cmd->m_payload.insert(end(cmd->m_payload), begin(m_suffix), end(m_suffix)); std::cout << ", new_content=" << cmd->m_payload << std::endl; // Resend the message to receiver as an immutable message. so_5::send(m_receiver, to_immutable(std::move(cmd))); } }; // Type of agent which will receive modified message. class receiver_agent final : public so_5::agent_t { public : receiver_agent(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &receiver_agent::on_envelope ); } private : void on_envelope(mhood_t cmd) { // Show message address and its resulting content. std::cout << "receiver, msg_addr=" << cmd.get() << ", content=" << cmd->m_payload; so_deregister_agent_coop_normally(); } }; int main() { try { so_5::launch( [](so_5::environment_t & env) { // Create a coop with agents auto modificator_mbox = env.introduce_coop( [](so_5::coop_t & coop) { auto receiver = coop.make_agent(); auto modificator = coop.make_agent( "<{(", ")}>", receiver->so_direct_mbox() ); return modificator->so_direct_mbox(); } ); // Send mutable message with initial content. so_5::send>(modificator_mbox, "hello"); } ); } catch(const std::exception & x) { std::cerr << "Oops! Exception: " << x.what() << std::endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/mutable_msg_agents/000077500000000000000000000000001523065210100226145ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/mutable_msg_agents/CMakeLists.txt000066400000000000000000000010111523065210100253450ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.mutable_msg_agents) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.mutable_msg_agents_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/mutable_msg_agents/main.cpp000066400000000000000000000055411523065210100242510ustar00rootroot00000000000000/* * Example of usage of mutable_msg in agents chain. */ #include #include // A message which will be sent from one agent to another. struct envelope final : public so_5::message_t { // Payload to be modified by every agent in the chain. // Please note: it is not a const object. std::string m_payload; envelope(std::string payload) : m_payload(std::move(payload)) {} }; // An agent which will be part of a chain. // This agent will mutate a data from received message. class chain_agent final : public so_5::agent_t { public : chain_agent(context_t ctx, std::string name, std::string prefix, std::string suffix, so_5::mbox_t next) : so_5::agent_t(std::move(ctx)) , m_name(std::move(name)) , m_prefix(std::move(prefix)) , m_suffix(std::move(suffix)) , m_next(std::move(next)) { so_subscribe_self().event( &chain_agent::on_envelope ); } private : const std::string m_name; const std::string m_prefix; const std::string m_suffix; const so_5::mbox_t m_next; void on_envelope(mutable_mhood_t cmd) { // Show message address and its old content. std::cout << m_name << ", msg_addr=" << cmd.get() << ", old_content=" << cmd->m_payload; // Modify message's payload. cmd->m_payload.insert(begin(cmd->m_payload), begin(m_prefix), end(m_prefix)); cmd->m_payload.insert(end(cmd->m_payload), begin(m_suffix), end(m_suffix)); std::cout << ", new_content=" << cmd->m_payload << std::endl; // Resend the message to next agent in chain. so_5::send(m_next, std::move(cmd)); } }; // Type of agent which will be the last agent in chain. class last_agent final : public so_5::agent_t { public : last_agent(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &last_agent::on_envelope ); } private : void on_envelope(mutable_mhood_t cmd) { // Show message address and its resulting content. std::cout << "last, msg_addr=" << cmd.get() << ", content=" << cmd->m_payload; so_deregister_agent_coop_normally(); } }; int main() { try { so_5::launch( [](so_5::environment_t & env) { // Create a coop with agents chain. auto chain_head = env.introduce_coop( [](so_5::coop_t & coop) { // Agents will be created from the last to the first. auto last = coop.make_agent(); auto third = coop.make_agent( "third", " [", "] ", last->so_direct_mbox() ); auto second = coop.make_agent( "second", " {", "} ", third->so_direct_mbox() ); auto first = coop.make_agent( "first", " (", ") ", second->so_direct_mbox() ); return first->so_direct_mbox(); } ); // Send mutable message with initial content. so_5::send>(chain_head, "hello"); } ); } catch(const std::exception & x) { std::cerr << "Oops! Exception: " << x.what() << std::endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/named_dispatchers_layer/000077500000000000000000000000001523065210100236255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/named_dispatchers_layer/CMakeLists.txt000066400000000000000000000010221523065210100263600ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.named_dispatchers_layer) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.named_dispatchers_layer_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/named_dispatchers_layer/main.cpp000066400000000000000000000110161523065210100252540ustar00rootroot00000000000000/* * An example of usage of SObjectizer's layer for holding a dictionary of named * dispatchers. */ #include #include // Implemetation of SObjectizer's layer that plays a role of // a dictionary of named one_thread dispatchers. class disp_binder_dictionary_layer_t : public so_5::layer_t { // Type of dictionary for holding dispatcher binders. using map_t = std::map>; // Because layer can be used from different worker threads it should // be protected. std::mutex m_lock; // The dictionary. map_t m_dict; public: // There is no need to override start()/shutdown()/wait() methods // because the basic implementation does nothing. // An interface of dictionary. // Add a new binder if there is no such name in the dictionary. void add( const std::string & name, so_5::disp_binder_shptr_t binder) { std::lock_guard l{m_lock}; auto [it, inserted] = m_dict.emplace(name, std::move(binder)); (void)it; // To suppress warnings about unused variables. if(!inserted) throw std::runtime_error(name + ": is not unique name"); } // Try find binder in the dictionary. // Absence of a binder is not an error. std::optional try_get(std::string_view name) noexcept { std::lock_guard l{m_lock}; if(const auto it = m_dict.find(name); it != m_dict.end()) return { it->second }; else return std::nullopt; } // Try get binder from the dictinary. // Absence of a binder is reported by an exception. so_5::disp_binder_shptr_t get(std::string_view name) { auto r = try_get(name); if(!r) throw std::runtime_error("binder is not found"); return std::move(*r); } }; // Type of demo agent to be used in the example. // // This agent tells the parent about its start and then deregisters itself. // class a_child_t final : public so_5::agent_t { public: // Type of signal to be sent to the parent. struct i_am_completed final : public so_5::signal_t {}; a_child_t(context_t ctx, so_5::mbox_t parent) : so_5::agent_t{std::move(ctx)} , m_parent{std::move(parent)} {} void so_evt_start() override { so_5::send(m_parent); so_deregister_agent_coop_normally(); } private: const so_5::mbox_t m_parent; }; // Another type of demo agent to be used in the example. class a_parent_t final : public so_5::agent_t { public: a_parent_t(context_t ctx, std::string disp_name) : so_5::agent_t{std::move(ctx)} , m_disp_name{std::move(disp_name)} {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t) { // We can finish our work too. std::cout << "child from '" << m_disp_name << "' completed" << std::endl; so_deregister_agent_coop_normally(); }); } void so_evt_start() override { // Create a new child on the specified dispatcher. so_5::introduce_child_coop( *this, [this](so_5::coop_t & coop) { auto binder = so_environment().query_layer< disp_binder_dictionary_layer_t >()->get(m_disp_name); coop.make_agent_with_binder(binder, so_direct_mbox()); }); } private: const std::string m_disp_name; }; int main() { so_5::launch( [](so_5::environment_t & env) { // Create several dispatchers and give them names. auto * layer = env.query_layer(); layer->add("first", so_5::disp::one_thread::make_dispatcher(env).binder()); layer->add("second", so_5::disp::one_thread::make_dispatcher(env).binder()); layer->add("third", so_5::disp::one_thread::make_dispatcher(env).binder()); // Create several parent agents. All of them will work on // the default dispatcher. env.register_agent_as_coop(env.make_agent("first")); env.register_agent_as_coop(env.make_agent("second")); env.register_agent_as_coop(env.make_agent("third")); env.register_agent_as_coop(env.make_agent("second")); env.register_agent_as_coop(env.make_agent("first")); env.register_agent_as_coop(env.make_agent("third")); env.register_agent_as_coop(env.make_agent("third")); env.register_agent_as_coop(env.make_agent("third")); env.register_agent_as_coop(env.make_agent("third")); // Wait while all parents finish their work. }, [](so_5::environment_params_t & params) { // Our layer should be created and stored in params. params.add_layer(std::make_unique()); }); return 0; } sobjectizer-5.8.6/dev/sample/so_5/news_board/000077500000000000000000000000001523065210100210775ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/news_board/CMakeLists.txt000066400000000000000000000005361523065210100236430ustar00rootroot00000000000000set(SAMPLE sample.so_5.news_board) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.news_board_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/news_board/main.cpp000066400000000000000000000501741523065210100225360ustar00rootroot00000000000000/* * An example for imitation of news board which handles requests from * different types of clients: news-writers and news-readers. */ #if defined( _MSC_VER ) #if defined( __clang__ ) #pragma clang diagnostic ignored "-Wreserved-id-macro" #endif #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include #include #include #include #include #include // // Auxilary tools. // // Helper function to generate a random integer in the specified range. unsigned int random_value( unsigned int left, unsigned int right ) { std::random_device rd; std::mt19937 gen{ rd() }; return std::uniform_int_distribution< unsigned int >{left, right}(gen); } // Imitation of some hard-working. // Blocks the current thread for random amount of time. void imitate_hard_work() { std::this_thread::sleep_for( std::chrono::milliseconds{ random_value( 25, 125 ) } ); } // Type of clock to work with time values. using clock_type = std::chrono::system_clock; // A helper function to calculate difference between to time points. // The result is converted to string. std::string ms_from_time( const clock_type::time_point & previous_point ) { using namespace std::chrono; const auto t = clock_type::now(); if( t > previous_point ) return std::to_string( duration_cast< milliseconds >( clock_type::now() - previous_point ).count() ) + "ms"; else return "0ms"; } // A message for logging something. struct msg_log { std::string m_who; std::string m_what; }; // A helper for logging simplification. void log( const so_5::mbox_t & logger_mbox, std::string who, std::string what ) { so_5::send< msg_log >( logger_mbox, std::move(who), std::move(what) ); } // Builder of logger agent. so_5::mbox_t create_logger_coop( so_5::environment_t & env ) { class logger_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( []( const msg_log & evt ) { // String representation for date/time. char local_time_sz[ 32 ]; auto t = clock_type::to_time_t( clock_type::now() ); std::strftime( local_time_sz, sizeof local_time_sz, "%Y.%m.%d %H:%M:%S", std::localtime( &t ) ); // Simplest form of logging. std::cout << "[" << local_time_sz << "] {" << evt.m_who << "}: " << evt.m_what << std::endl; } ); } }; return env.introduce_coop( [&]( so_5::coop_t & coop ) { // Logger agent. auto a = coop.make_agent< logger_t >(); // Direct mbox of logger agent will be returned. return a->so_direct_mbox(); } ); } // // Messages for interaction with news board. // // Type of story ID. using story_id_type = unsigned long; // Base class for all messages. It stores timestamp. struct news_board_message_base : public so_5::message_t { // Time at which an operation was started. // This time will be used for calculation of operation duration. const clock_type::time_point m_timestamp; news_board_message_base( clock_type::time_point timestamp ) : m_timestamp( std::move(timestamp) ) {} }; // Base class for all request messages. It stores reply_to value. struct news_board_request_base : public news_board_message_base { // Mbox of request initiator. // Response will be sent to that mbox. const so_5::mbox_t m_reply_to; news_board_request_base( clock_type::time_point timestamp, so_5::mbox_t reply_to ) : news_board_message_base( std::move(timestamp) ) , m_reply_to( std::move(reply_to) ) {} }; // Request for publishing new story. struct msg_publish_story_req final : public news_board_request_base { const std::string m_title; const std::string m_content; msg_publish_story_req( clock_type::time_point timestamp, so_5::mbox_t reply_to, std::string title, std::string content ) : news_board_request_base( std::move(timestamp), std::move(reply_to) ) , m_title( std::move(title) ) , m_content( std::move(content) ) {} }; // Reply for publishing new story. struct msg_publish_story_resp final : public news_board_message_base { story_id_type m_id; msg_publish_story_resp( clock_type::time_point timestamp, story_id_type id ) : news_board_message_base( std::move(timestamp) ) , m_id( id ) {} }; // Request for updates from news board. struct msg_updates_req final : public news_board_request_base { // Last known story ID. story_id_type m_last_id; msg_updates_req( clock_type::time_point timestamp, so_5::mbox_t reply_to, story_id_type last_id ) : news_board_request_base( std::move(timestamp), std::move(reply_to) ) , m_last_id( last_id ) {} }; // Reply for request for updates. struct msg_updates_resp final : public news_board_message_base { // Type of new stories list. using story_list = std::list< std::tuple< story_id_type, std::string > >; // List of short info about new storis. const story_list m_updates; msg_updates_resp( clock_type::time_point timestamp, story_list updates ) : news_board_message_base( std::move(timestamp) ) , m_updates( std::move(updates) ) {} }; // Request for content of the story. struct msg_story_content_req final : public news_board_request_base { // Story ID. const story_id_type m_id; msg_story_content_req( clock_type::time_point timestamp, so_5::mbox_t reply_to, story_id_type id ) : news_board_request_base( std::move(timestamp), std::move(reply_to) ) , m_id( id ) {} }; // Positive response to a request for story content. struct msg_story_content_resp_ack final : public news_board_message_base { // Story content. const std::string m_content; msg_story_content_resp_ack( clock_type::time_point timestamp, std::string content ) : news_board_message_base( std::move(timestamp) ) , m_content( std::move(content) ) {} }; // Negative response to a request for story content. // This message is used when story was removed from the board. struct msg_story_content_resp_nack final : public news_board_message_base { msg_story_content_resp_nack( clock_type::time_point timestamp ) : news_board_message_base( std::move(timestamp) ) {} }; // // News board data. // struct news_board_data { // Information about one story. struct story_info { std::string m_title; std::string m_content; }; // Type of map from story ID to story data. using story_map = std::map< story_id_type, story_info >; // Published stories. story_map m_stories; // ID counter. story_id_type m_last_id = 0; }; // // Agents to work with news board data. // // Helper class for agents with just one event. class one_event_handler_t final : public so_5::agent_t { public : one_event_handler_t( context_t ctx, so_5::priority_t prio ) : so_5::agent_t{ ctx + prio } {} template< typename Lambda > void subscribe_event( const so_5::mbox_t & mbox, Lambda && handler ) { so_subscribe( mbox ).event( std::forward(handler) ); } }; // Agent for receiving and storing new stories to news board. void define_news_receiver_agent( so_5::coop_t & coop, news_board_data & board_data, const so_5::mbox_t & board_mbox, const so_5::mbox_t & logger_mbox ) { auto receiver = coop.make_agent< one_event_handler_t >( // This agent should have lowest priority among // board-related agents. so_5::prio::p1 ); // It handles just one message. receiver->subscribe_event( board_mbox, [&board_data, logger_mbox]( so_5::mhood_t cmd ) { // Store new story to board. auto story_id = ++(board_data.m_last_id); board_data.m_stories.emplace( story_id, news_board_data::story_info{ cmd->m_title, cmd->m_content } ); // Log this fact. log( logger_mbox, "board.receiver", "new story published, id=" + std::to_string( story_id ) + ", title=" + cmd->m_title ); // Take some time for processing. imitate_hard_work(); // Send reply to story-sender. so_5::send< msg_publish_story_resp >( cmd->m_reply_to, cmd->m_timestamp, story_id ); // Remove oldest story if there are too much stories. if( 40 < board_data.m_stories.size() ) { auto removed_id = board_data.m_stories.begin()->first; board_data.m_stories.erase( board_data.m_stories.begin() ); log( logger_mbox, "board.receiver", "old story removed, id=" + std::to_string( removed_id ) ); } } ); } // Agent for handling requests about updates on news board. void define_news_directory_agent( so_5::coop_t & coop, news_board_data & board_data, const so_5::mbox_t & board_mbox, const so_5::mbox_t & logger_mbox ) { auto directory = coop.make_agent< one_event_handler_t >( // This agent should have priority higher than news_receiver. so_5::prio::p2 ); // It handles just one message. directory->subscribe_event( board_mbox, [&board_data, logger_mbox]( so_5::mhood_t cmd ) { log( logger_mbox, "board.directory", "request for updates received, last_id=" + std::to_string( cmd->m_last_id ) ); // Take some time for processing. imitate_hard_work(); // Searching for new stories for that request // and building result list. msg_updates_resp::story_list new_stories; std::transform( board_data.m_stories.upper_bound( cmd->m_last_id ), std::end( board_data.m_stories ), std::back_inserter( new_stories ), []( const news_board_data::story_map::value_type & v ) { return std::make_tuple( v.first, v.second.m_title ); } ); log( logger_mbox, "board.directory", std::to_string( new_stories.size() ) + " new stories found" ); // Sending response. so_5::send< msg_updates_resp >( cmd->m_reply_to, cmd->m_timestamp, std::move(new_stories) ); } ); } // Agent for handling requests for story content. void define_story_extractor_agent( so_5::coop_t & coop, news_board_data & board_data, const so_5::mbox_t & board_mbox, const so_5::mbox_t & logger_mbox ) { auto extractor = coop.make_agent< one_event_handler_t >( // This agent should have priority higher that news_directory. so_5::prio::p3 ); // It handles just one message. extractor->subscribe_event( board_mbox, [&board_data, logger_mbox]( so_5::mhood_t< msg_story_content_req > cmd ) { log( logger_mbox, "board.extractor", "request for story content received, id=" + std::to_string( cmd->m_id ) ); // Take some time for processing. imitate_hard_work(); auto it = board_data.m_stories.find( cmd->m_id ); if( it != board_data.m_stories.end() ) { log( logger_mbox, "board.extractor", "story {" + std::to_string( cmd->m_id ) + "} found" ); so_5::send< msg_story_content_resp_ack >( cmd->m_reply_to, cmd->m_timestamp, it->second.m_content ); } else { log( logger_mbox, "board.extractor", "story {" + std::to_string( cmd->m_id ) + "} NOT found" ); so_5::send< msg_story_content_resp_nack >( cmd->m_reply_to, cmd->m_timestamp ); } } ); } so_5::mbox_t create_board_coop( so_5::environment_t & env, const so_5::mbox_t & logger_mbox ) { auto board_mbox = env.create_mbox(); using namespace so_5::disp::prio_one_thread::quoted_round_robin; using namespace so_5::prio; // Board cooperation will use quoted_round_robin dispatcher // with different quotes for agents. env.introduce_coop( make_dispatcher( env, quotes_t{ 1 } .set( p1, 10 ) // 10 events for news_receiver. .set( p2, 20 ) // 20 events for news_directory. .set( p3, 30 ) // 30 events for story_extractor. ).binder(), [&]( so_5::coop_t & coop ) { // Lifetime of news board data will be controlled by cooperation. auto board_data = coop.take_under_control( std::make_unique< news_board_data >() ); define_news_receiver_agent( coop, *board_data, board_mbox, logger_mbox ); define_news_directory_agent( coop, *board_data, board_mbox, logger_mbox ); define_story_extractor_agent( coop, *board_data, board_mbox, logger_mbox ); } ); return board_mbox; } // // Story publishers. // class story_publisher final : public so_5::agent_t { struct msg_time_for_new_story final : public so_5::signal_t {}; public : story_publisher( context_t ctx, std::string publisher_name, so_5::mbox_t board_mbox, so_5::mbox_t logger_mbox ) : so_5::agent_t( ctx ) , m_name( std::move(publisher_name) ) , m_board_mbox( std::move(board_mbox) ) , m_logger_mbox( std::move(logger_mbox) ) {} void so_define_agent() override { this >>= st_await_new_story; st_await_new_story.event( &story_publisher::evt_time_for_new_story ); st_await_publish_response.event( &story_publisher::evt_publish_response ); } void so_evt_start() override { initiate_time_for_new_story_signal(); } private : // The agent will wait 'msg_time_for_new_story' signal in this state. const state_t st_await_new_story{ this }; // The agent will wait a response to publising request in this state. const state_t st_await_publish_response{ this }; const std::string m_name; const so_5::mbox_t m_board_mbox; const so_5::mbox_t m_logger_mbox; // This counter will be used in store generation procedure. unsigned int m_stories_counter = 0; void initiate_time_for_new_story_signal() { so_5::send_delayed< msg_time_for_new_story >( *this, std::chrono::milliseconds{ random_value( 100, 1500 ) } ); } void evt_time_for_new_story(mhood_t< msg_time_for_new_story >) { // Create new story. auto story_number = ++m_stories_counter; std::string title = "A story from " + m_name + " #" + std::to_string( story_number ); std::string content = "This is a content from a story '" + title + "' provided by " + m_name; log( m_logger_mbox, m_name, "Publish new story: " + title ); // Publishing the story. so_5::send< msg_publish_story_req >( m_board_mbox, clock_type::now(), so_direct_mbox(), std::move(title), std::move(content) ); // Waiting a response. this >>= st_await_publish_response; } void evt_publish_response( const msg_publish_story_resp & resp ) { log( m_logger_mbox, m_name, "Publish finished, id=" + std::to_string( resp.m_id ) + ", publish took " + ms_from_time( resp.m_timestamp ) ); // Waiting for a time for next story. this >>= st_await_new_story; initiate_time_for_new_story_signal(); } }; void create_publisher_coop( so_5::environment_t & env, const so_5::mbox_t & board_mbox, const so_5::mbox_t & logger_mbox ) { // All publishers will work on the same working thread. env.introduce_coop( so_5::disp::one_thread::make_dispatcher( env ).binder(), [&]( so_5::coop_t & coop ) { for( int i = 0; i != 5; ++i ) coop.make_agent< story_publisher >( "publisher" + std::to_string(i+1), board_mbox, logger_mbox ); } ); } // // News readers. // class news_reader final : public so_5::agent_t { struct msg_time_for_updates final : public so_5::signal_t {}; public : news_reader( context_t ctx, std::string reader_name, so_5::mbox_t board_mbox, so_5::mbox_t logger_mbox ) : so_5::agent_t( ctx ) , m_name( std::move(reader_name) ) , m_board_mbox( std::move(board_mbox) ) , m_logger_mbox( std::move(logger_mbox) ) {} void so_define_agent() override { this >>= st_sleeping; st_sleeping.event( &news_reader::evt_time_for_updates ); st_await_updates.event( &news_reader::evt_updates_received ); st_await_story_content.event( &news_reader::evt_story_content ); st_await_story_content.event( &news_reader::evt_story_not_found ); } void so_evt_start() override { initiate_time_for_updates_signal(); } private : // The agent will wait 'msg_time_for_updates' signal in this state. const state_t st_sleeping{ this }; // The agent will wait updates from news board in this state. const state_t st_await_updates{ this }; // The agent will wait story content in this state. const state_t st_await_story_content{ this }; const std::string m_name; const so_5::mbox_t m_board_mbox; const so_5::mbox_t m_logger_mbox; // ID of last received story from news board. story_id_type m_last_id = 0; // List a stories to be requested from news board. msg_updates_resp::story_list m_stories_to_read; void initiate_time_for_updates_signal() { so_5::send_delayed< msg_time_for_updates >( *this, std::chrono::milliseconds{ random_value( 500, 2500 ) } ); } void evt_time_for_updates(mhood_t< msg_time_for_updates >) { request_updates(); } void evt_updates_received( const msg_updates_resp & resp ) { log( m_logger_mbox, m_name, std::to_string( resp.m_updates.size() ) + " updates received, took " + ms_from_time( resp.m_timestamp ) ); if( resp.m_updates.empty() ) { // Nothing new. We should sleep. this >>= st_sleeping; initiate_time_for_updates_signal(); } else { this >>= st_await_story_content; // Read no more than 3 latest stories. unsigned int c = 0; for( auto it = resp.m_updates.rbegin(); it != resp.m_updates.rend() && c != 3; ++it, ++c ) m_stories_to_read.push_front( *it ); request_story_content(); } } void evt_story_content( const msg_story_content_resp_ack & resp ) { const auto & id = std::get<0>( *std::begin(m_stories_to_read) ); const auto & title = std::get<1>( *std::begin(m_stories_to_read) ); log( m_logger_mbox, m_name, "read story {" + std::to_string( id ) + "} '" + title + "': \"" + resp.m_content + "\", took " + ms_from_time( resp.m_timestamp ) ); remove_current_story_and_read_next(); } void evt_story_not_found( const msg_story_content_resp_nack & resp ) { const auto & id = std::get<0>( *std::begin(m_stories_to_read) ); const auto & title = std::get<1>( *std::begin(m_stories_to_read) ); log( m_logger_mbox, m_name, "unable to read story {" + std::to_string( id ) + "} '" + title + "', took " + ms_from_time( resp.m_timestamp ) ); remove_current_story_and_read_next(); } void request_updates() { log( m_logger_mbox, m_name, "requesting updates, last_id=" + std::to_string( m_last_id ) ); so_5::send< msg_updates_req >( m_board_mbox, clock_type::now(), so_direct_mbox(), m_last_id ); this >>= st_await_updates; } void request_story_content() { auto id = std::get<0>( *std::begin(m_stories_to_read) ); log( m_logger_mbox, m_name, "requesting story {" + std::to_string( id ) + "}" ); so_5::send< msg_story_content_req >( m_board_mbox, clock_type::now(), so_direct_mbox(), id ); } void remove_current_story_and_read_next() { m_last_id = std::get<0>( *std::begin(m_stories_to_read) ); m_stories_to_read.pop_front(); if( !m_stories_to_read.empty() ) request_story_content(); else request_updates(); } }; void create_reader_coop( so_5::environment_t & env, const so_5::mbox_t & board_mbox, const so_5::mbox_t & logger_mbox ) { // All readers will work on the same working thread. env.introduce_coop( so_5::disp::one_thread::make_dispatcher( env ).binder(), [&]( so_5::coop_t & coop ) { for( int i = 0; i != 50; ++i ) coop.make_agent< news_reader >( "reader" + std::to_string(i+1), board_mbox, logger_mbox ); } ); } void init( so_5::environment_t & env ) { auto logger_mbox = create_logger_coop( env ); auto board_mbox = create_board_coop( env, logger_mbox ); create_publisher_coop( env, board_mbox, logger_mbox ); create_reader_coop( env, board_mbox, logger_mbox ); } int main() { try { so_5::launch( init ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/nohandler_msg_tracing/000077500000000000000000000000001523065210100233035ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/nohandler_msg_tracing/CMakeLists.txt000066400000000000000000000010161523065210100260410ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.nohandler_msg_tracing) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.nohandler_msg_tracing_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/nohandler_msg_tracing/main.cpp000066400000000000000000000043331523065210100247360ustar00rootroot00000000000000/* * An example to show custom msg_tracing filter. * Only trace message for cases when no event handler found will be printed. */ #include #include // Main SObjectizer header file. #include // Main example agent. class a_example_t final : public so_5::agent_t { // Several signals for demonstration. struct first final : public so_5::signal_t {}; struct second final : public so_5::signal_t {}; struct third final : public so_5::signal_t {}; // Signal for finishing the example. struct finish final : public so_5::signal_t {}; // Several agent's states. const state_t st_first{ this, "first" }; const state_t st_second{ this, "second" }; const state_t st_third{ this, "third" }; public : a_example_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { st_first.event( &a_example_t::on_first ); st_first.event( &a_example_t::on_finish ); st_second.event( &a_example_t::on_second ); st_third.event( &a_example_t::on_third ); } void so_evt_start() override { // Change the state of the agent. this >>= st_first; // Send a serie of messages. so_5::send< first >( *this ); so_5::send< second >( *this ); so_5::send< third >( *this ); so_5::send< finish >( *this ); } private : void on_first( mhood_t ) {} void on_second( mhood_t ) {} void on_third( mhood_t ) {} void on_finish( mhood_t ) { so_deregister_agent_coop_normally(); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_example_t >(); } ); }, []( so_5::environment_params_t & params ) { // Turn message delivery tracing on. params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); // Setup filter which enables only messages with // null event_handler_data_ptr. params.message_delivery_tracer_filter( so_5::msg_tracing::make_filter( [](const so_5::msg_tracing::trace_data_t & td) { const auto h = td.event_handler_data_ptr(); return h && (nullptr == *h); } ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/parent_coop/000077500000000000000000000000001523065210100212655ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/parent_coop/CMakeLists.txt000066400000000000000000000005371523065210100240320ustar00rootroot00000000000000set(SAMPLE sample.so_5.parent_coop) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.parent_coop_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/parent_coop/main.cpp000066400000000000000000000100021523065210100227060ustar00rootroot00000000000000/* * Parent-child cooperations sample. * * The sample shows: * - how to create a child-cooperation (cooperation, which has some parent * cooperation). * - auto-deregistration of the child-cooperation if the parent cooperation is * deregistering. * * Parent agent creates a child agent, which do some task. * If parent cooperation is deregistering, the child cooperation will be * deregistered automatically. */ #include #include // Main SObjectizer header file. #include // Result of task which will be send to a parent agent. struct task_result { unsigned int m_id; }; // Child finished to do his task. class task_completed final : public so_5::signal_t {}; // Child agent. /* This agent will be created in his own cooperation. Agent does some task, sends a result to the parent agent and closes down. */ class a_child_t final : public so_5::agent_t { public: a_child_t( context_t ctx, so_5::mbox_t result_mbox, unsigned int task_id ) : so_5::agent_t( ctx ) , m_result_mbox( std::move(result_mbox) ) , m_task_id( task_id ) {} ~a_child_t() override { std::cout << "Child: agent of the task " << m_task_id << " has destroyed." << std::endl; } // Definition of the agent for SObjectizer. void so_define_agent() override { so_subscribe_self().event( &a_child_t::evt_task_completed ); } // A reaction to start of work in SObjectizer. void so_evt_start() override { std::cout << "Child: has started to do task " << m_task_id << std::endl; so_5::send_delayed< task_completed >( *this, std::chrono::seconds( 1 ) ); } void so_evt_finish() override { std::cout << "Child: has finished, task " << m_task_id << std::endl; } //! Child has completed the task. void evt_task_completed( mhood_t< task_completed > ) { std::cout << "Child: has completed his task " << m_task_id << std::endl; // Send information about result to the parent agent. so_5::send< task_result >( m_result_mbox, m_task_id ); // Deregister child cooperation and close // down activity of this child instance. so_deregister_agent_coop_normally(); } private: // Result mbox. const so_5::mbox_t m_result_mbox; // Task ID. const unsigned int m_task_id; }; // Parent agent in his parent cooperation. class a_parent_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; ~a_parent_t() override { std::cout << "Parent: agent has destroyed." << std::endl; } // Definition of the agent for SObjectizer. void so_define_agent() override { so_subscribe_self().event( &a_parent_t::evt_task_result ); } // A reaction to start of work in SObjectizer. void so_evt_start() override { std::cout << "Parent: agent has started." << std::endl; // Our cooperation has created. We may start a child agent work. start_child( 0 ); } void so_evt_finish() override { std::cout << "Parent: agent has finished." << std::endl; } // Task result received from child-agent. void evt_task_result( const task_result & evt ) { std::cout << "Parent: task result " << evt.m_id << " is received." << std::endl; // We will start to do a next task. start_child( evt.m_id + 1 ); } private: // Starts child to solve a task number ID. void start_child( unsigned int id ) { std::cout << "Parent: starting a child to do task " << id << std::endl; // Creating a child cooperation. so_5::introduce_child_coop( *this, [&]( so_5::coop_t & coop ) { // Adding agents to the cooperation. coop.make_agent< a_child_t >( so_direct_mbox(), id ); } ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { // Registering the parent cooperation. env.register_agent_as_coop( env.make_agent< a_parent_t >() ); // Give some time to the agents. std::this_thread::sleep_for( std::chrono::seconds( 3 ) ); env.stop(); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/ping_pong/000077500000000000000000000000001523065210100207345ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/ping_pong/CMakeLists.txt000066400000000000000000000005331523065210100234750ustar00rootroot00000000000000set(SAMPLE sample.so_5.ping_pong) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.ping_ping_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/ping_pong/main.cpp000066400000000000000000000074331523065210100223730ustar00rootroot00000000000000#include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif struct cfg_t { unsigned int m_request_count; bool m_active_objects; cfg_t() : m_request_count( 1000 ) , m_active_objects( false ) {} }; cfg_t try_parse_cmdline( int argc, char ** argv ) { if( 1 == argc ) { std::cout << "usage:\n" "sample.so_5.ping_pong \n" "\noptions:\n" "-a, --active-objects agents should be active objects\n" "-r, --requests count of requests to send\n" << std::endl; throw std::runtime_error( "No command-line errors" ); } auto is_arg = []( const char * value, const char * v1, const char * v2 ) { return 0 == std::strcmp( value, v1 ) || 0 == std::strcmp( value, v2 ); }; cfg_t result; char ** current = argv + 1; char ** last = argv + argc; while( current != last ) { if( is_arg( *current, "-a", "--active-objects" ) ) { result.m_active_objects = true; } else if( is_arg( *current, "-r", "--requests" ) ) { ++current; if( current == last ) throw std::runtime_error( "-r requires argument" ); result.m_request_count = static_cast< unsigned int >( std::atoi( *current ) ); } else { throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } ++current; } return result; } void show_cfg( const cfg_t & cfg ) { std::cout << "Configuration: " << "active objects: " << ( cfg.m_active_objects ? "yes" : "no" ) << ", requests: " << cfg.m_request_count << std::endl; } void run_sample( const cfg_t & cfg ) { // Types of signals for the agents. struct msg_ping final : public so_5::signal_t {}; struct msg_pong final : public so_5::signal_t {}; // Type of pinger agent. class pinger_t final : public so_5::agent_t { const so_5::mbox_t m_mbox; unsigned int m_pings_left; public : pinger_t( context_t ctx, so_5::mbox_t mbox, unsigned int pings_left ) : so_5::agent_t{ std::move(ctx) } , m_mbox{ std::move(mbox) } , m_pings_left{ pings_left } { so_subscribe( m_mbox ).event( [this]( mhood_t ) { if( m_pings_left ) --m_pings_left; if( m_pings_left ) so_5::send< msg_ping >( m_mbox ); else so_environment().stop(); } ); } void so_evt_start() override { so_5::send< msg_ping >( m_mbox ); } }; // Type of ponger agent. class ponger_t final : public so_5::agent_t { public : ponger_t( context_t ctx, const so_5::mbox_t & mbox ) : so_5::agent_t{ std::move(ctx) } { so_subscribe( mbox ).event( [mbox]( mhood_t ) { so_5::send< msg_pong >( mbox ); } ); } }; so_5::launch( [&cfg]( so_5::environment_t & env ) { env.introduce_coop( // Agents will be active or passive. // It depends on sample arguments. cfg.m_active_objects ? so_5::disp::active_obj::make_dispatcher( env ).binder() : so_5::make_default_disp_binder( env ), [&]( so_5::coop_t & coop ) { auto mbox = env.create_mbox(); // Pinger agent. coop.make_agent< pinger_t >( mbox, cfg.m_request_count ); // Ponger agent. coop.make_agent< ponger_t >( std::cref(mbox) ); } ); } ); } int main( int argc, char ** argv ) { try { cfg_t cfg = try_parse_cmdline( argc, argv ); show_cfg( cfg ); run_sample( cfg ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/ping_pong_minimal/000077500000000000000000000000001523065210100224425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/ping_pong_minimal/CMakeLists.txt000066400000000000000000000010061523065210100251770ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.ping_pong_minimal) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.ping_pong_minimal_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/ping_pong_minimal/main.cpp000066400000000000000000000032431523065210100240740ustar00rootroot00000000000000#include #include // Types of signals for the agents. struct msg_ping final : public so_5::signal_t {}; struct msg_pong final : public so_5::signal_t {}; // Class of pinger agent. class a_pinger_t final : public so_5::agent_t { public : a_pinger_t( context_t ctx, so_5::mbox_t mbox, int pings_to_send ) : so_5::agent_t{ ctx } , m_mbox{ std::move(mbox) } , m_pings_left{ pings_to_send } {} void so_define_agent() override { so_subscribe( m_mbox ).event( &a_pinger_t::evt_pong ); } void so_evt_start() override { send_ping(); } private : const so_5::mbox_t m_mbox; int m_pings_left; void evt_pong(mhood_t< msg_pong >) { if( m_pings_left > 0 ) send_ping(); else so_environment().stop(); } void send_ping() { so_5::send< msg_ping >( m_mbox ); --m_pings_left; } }; class a_ponger_t final : public so_5::agent_t { public : a_ponger_t( context_t ctx, const so_5::mbox_t & mbox ) : so_5::agent_t( std::move(ctx) ) { so_subscribe( mbox ).event( [mbox]( mhood_t ) { so_5::send< msg_pong >( mbox ); } ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( [&env]( so_5::coop_t & coop ) { // Mbox for agent's interaction. auto mbox = env.create_mbox(); // Pinger. coop.make_agent< a_pinger_t >( mbox, 100000 ); // Ponger agent. coop.make_agent< a_ponger_t >( std::cref(mbox) ); }); }); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/ping_pong_with_owner/000077500000000000000000000000001523065210100232015ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/ping_pong_with_owner/CMakeLists.txt000066400000000000000000000005611523065210100257430ustar00rootroot00000000000000set(SAMPLE sample.so_5.ping_pong_with_owner) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.ping_pong_with_owner_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/ping_pong_with_owner/main.cpp000066400000000000000000000140521523065210100246330ustar00rootroot00000000000000#include #include // Main SObjectizer header file. #include // Ping signal. // Signal is a special type of message which has no actual data. // Sending of signals is like sending only atoms in Erlang. struct ping final : public so_5::signal_t {}; // Pong signal. struct pong final : public so_5::signal_t {}; // Message with result of pinger/ponger run. // Unlike signal message it must have actual data. struct run_result { std::string m_result; }; // Pinger agent. class pinger final : public so_5::agent_t { public : pinger( // Working context for agent. context_t ctx, // Parent's mbox for result sending. so_5::mbox_t parent ) : so_5::agent_t( ctx ) , m_parent( std::move(parent) ) {} // Ponger mbox will be available only after // creation of pinger/ponger pair. void set_ponger_mbox( const so_5::mbox_t & mbox ) { m_ponger = mbox; } // This method is automatically called by SObjectizer // during agent's registration procedure. void so_define_agent() override { // Subscription for only one signal. so_default_state().event( [this](mhood_t< pong >) { ++m_pongs; so_5::send< ping >( m_ponger ); } ); } // This method is automatically called by SObjectizer // just after successful registration. // Pinger uses this method to initiate message exchange. void so_evt_start() override { // Sending signal by corresponding function. so_5::send< ping >( m_ponger ); } // This method is automatically called by SObjectizer // just after successful agent's deregistration. void so_evt_finish() override { // Sending result message by corresponding function. so_5::send< run_result >( // Receiver of the message. m_parent, // This string will be forwarded to run_result's constructor. "pongs: " + std::to_string( m_pongs ) ); } private : const so_5::mbox_t m_parent; so_5::mbox_t m_ponger; unsigned int m_pongs = 0; }; // Ponger agent is very similar to pinger. // But it hasn't so_evt_start method because it will // wait the first ping signal from the pinger. class ponger final : public so_5::agent_t { public : ponger( context_t ctx, so_5::mbox_t parent ) : so_5::agent_t( ctx ) , m_parent( std::move(parent) ) {} void set_pinger_mbox( const so_5::mbox_t & mbox ) { m_pinger = mbox; } void so_define_agent() override { so_default_state().event( [this](mhood_t< ping >) { ++m_pings; so_5::send< pong >( m_pinger ); } ); } void so_evt_finish() override { so_5::send< run_result >( m_parent, "pings: " + std::to_string( m_pings ) ); } private : const so_5::mbox_t m_parent; so_5::mbox_t m_pinger; unsigned int m_pings = 0; }; // Parent agent. // Creates pair of pinger/ponger agents, then limits their working time, // then handles their run results. class parent final : public so_5::agent_t { public : parent( context_t ctx ) : so_5::agent_t( ctx ) {} void so_define_agent() override { // Arriving of time limit signal means that // child cooperation must be deregistered. so_default_state().event( [this](mhood_t< stop >) { so_environment().deregister_coop( // Cooperation name for deregistration. m_child, // The reason of deregistration. // This value means that deregistration is caused // by application logic. so_5::dereg_reason::normal ); } ); // NOTE: message type is automatically deduced from // event handlers signatures. // The first result will be received in default state. so_default_state().event( &parent::evt_first_result ); // But the second one will be received in the next state. st_first_result_got.event( &parent::evt_second_result ); } void so_evt_start() override { // Creation of child cooperation with pinger and ponger. auto coop = so_5::create_child_coop( // Parent of the new cooperation. *this, // Child cooperation will use active_obj dispatcher. // So pinger and ponger will work on the different // working threads. // active_obj dispatcher will be used as a primary // dispatcher for that cooperation. so_5::disp::active_obj::make_dispatcher( so_environment() ).binder() ); // Filling the child cooperation. auto a_pinger = coop->make_agent< pinger >( so_direct_mbox() ); auto a_ponger = coop->make_agent< ponger >( so_direct_mbox() ); a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() ); a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() ); // Registration of the child coop. m_child = so_environment().register_coop( std::move(coop) ); // Limit the pinger/ponger exchange time. so_5::send_delayed< stop >( *this, std::chrono::seconds( 1 ) ); } private : // Time limit signal. struct stop : public so_5::signal_t {}; // Additional state for the agent. // This state means that the first result from children agents // has been received and that the parent expects the last one. const state_t st_first_result_got{ this }; // Result's accumulator. std::string m_results; // Handle of child coop. // Will receive actual value when child will be registered. so_5::coop_handle_t m_child; // Event handler for the first result. void evt_first_result( const run_result & evt ) { m_results = evt.m_result + "; "; // Agent's state must be changed. this >>= st_first_result_got; } // Event handler for the next (and last) result. void evt_second_result( const run_result & evt ) { m_results += evt.m_result; // Show the results and finish work. std::cout << m_results << std::endl; // This is deregistration of the last live cooperation. // SO Environment will finish its work. so_deregister_agent_coop_normally(); } }; int main() { try { // Create SO Environment objects and run SO Run-Time inside it. so_5::launch( // SO Environment initialization routine. []( so_5::environment_t & env ) { // We must have the cooperation with just one // agent inside it. env.register_agent_as_coop( // The single agent in the cooperation. env.make_agent< parent >() ); } ); } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } } sobjectizer-5.8.6/dev/sample/so_5/prio_work_stealing/000077500000000000000000000000001523065210100226555ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/prio_work_stealing/CMakeLists.txt000066400000000000000000000005561523065210100254230ustar00rootroot00000000000000set(SAMPLE sample.so_5.prio_work_stealing) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.prio_work_stealing_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/prio_work_stealing/main.cpp000066400000000000000000000412341523065210100243110ustar00rootroot00000000000000/* * An example for demonstration of prio_dedicated_threads::one_per_prio * dispatcher. * * There is a request generator agent. It randomly sends request for * creating images. Every request contains ID, dimension of the image * to be generated and some metadata. It is supposed that time for * image creation depends from image dimension -- big dimension leads * to big amount of time for image creation. * * Requests are processed by several agents. * * First of all there are two managers. One of them (request_acceptor) * receives new requests, calculates priorities for them and stores * requests in queues (one queue for each priority). * * Second manager (request_scheduler) schedules requests from queues to * actual processor agents. * * Request_acceptor and request_scheduler works on the same * prio_one_thread::strictly_ordered dispatcher. Request_acceptor agent * has lower priority than request_scheduler agent. It means that * messages for scheduling requests to processor agents are processed before * messages about new requests. * * There are several processor agents: one for each priority. All processor * agents are bound to prio_dedicated_threads::one_per_prio dispatcher. * It means that every processor works on separate thread. * * Request_scheduler implements work stealing mechanism. * When a processor with priority P tells that it is free and ready for * processing new image then request_scheduler tries to find next request of * the same priority P. If there is no such requests then request_scheduler * triest to find a request for lower priority (P-1) and so on. */ #include #include #include #include #include #include #include // // Auxilary tools. // // Helper function to generate a random integer in the specified range. unsigned int random_value( unsigned int left, unsigned int right ) { std::random_device rd; std::mt19937 gen{ rd() }; return std::uniform_int_distribution< unsigned int >{left, right}(gen); } // Imitation of some hard-working. // Blocks the current thread for random amount of time. void imitate_hard_work( unsigned int pause ) { std::this_thread::sleep_for( std::chrono::milliseconds{ pause } ); } // Type of clock to work with time values. using clock_type = std::chrono::steady_clock; // // Messages for interaction between requests generator and // requests scheduler. // // Maximum dimension allowed. const unsigned int max_dimension = 10000; // Request metadata. Contains information related to request processing. struct request_metadata { // When request was generated. clock_type::time_point m_generated_at; // When request was queued. clock_type::time_point m_queued_at; // When processing of request was started. clock_type::time_point m_processing_started_at; // When processing of request was finished. clock_type::time_point m_processing_finished_at; // Priority initially assigned to request. so_5::priority_t m_queue_prio; // Priority at which request was processed. so_5::priority_t m_processor_prio; }; // Alias for shared_ptr to request_metadata. using request_metadata_shptr = std::shared_ptr< request_metadata >; // Request for image generation. struct generation_request final : public so_5::message_t { const unsigned int m_id; const unsigned int m_dimension; request_metadata_shptr m_metadata; generation_request( unsigned int id, unsigned int dimension, request_metadata_shptr metadata ) : m_id( id ) , m_dimension( dimension ) , m_metadata( std::move(metadata) ) {} }; // Alias for smart pointer to generation_request. It will be used // for storing and resending pending requests. using generation_request_holder = so_5::message_holder_t< generation_request >; // Positive response for image generation request. struct generation_result final : public so_5::message_t { const unsigned int m_id; request_metadata_shptr m_metadata; generation_result( unsigned int id, request_metadata_shptr metadata ) : m_id( id ) , m_metadata( metadata ) {} }; // Negative response for image generation request. struct generation_rejected final : public so_5::message_t { const unsigned int m_id; generation_rejected( unsigned int id ) : m_id( id ) {} }; // // Request generator agent. // class request_generator final : public so_5::agent_t { // Signal which agent sends to itself with random delays. // A new request will be produced for every occurrence of that signal. struct produce_next final : public so_5::signal_t {}; public : request_generator( context_t ctx, so_5::mbox_t interaction_mbox ) : so_5::agent_t( ctx ) , m_interaction_mbox( std::move( interaction_mbox ) ) {} void so_define_agent() override { so_subscribe_self() .event( &request_generator::evt_produce_next ); so_subscribe( m_interaction_mbox ) .event( &request_generator::evt_generation_result ) .event( &request_generator::evt_generation_rejected ); } void so_evt_start() override { // Will start requests generation immediately. so_5::send< produce_next >( *this ); } private : // Mbox for sending new requests and receiving responses. const so_5::mbox_t m_interaction_mbox; // Counter for request ID generation. unsigned int m_last_id = 0; // Values for make distribution in the proportion 60%/30%/10%. unsigned int m_x = 0; unsigned int m_y = 0; unsigned int m_z = 0; void evt_produce_next(mhood_t< produce_next >) { auto id = ++m_last_id; auto dimension = generate_next_dimension(); auto metadata = std::make_shared< request_metadata >(); metadata->m_generated_at = clock_type::now(); so_5::send< generation_request >( m_interaction_mbox, id, dimension, std::move( metadata ) ); std::cout << "generated {" << id << "}, dimension: " << dimension << std::endl; so_5::send_delayed< produce_next >( *this, std::chrono::milliseconds( random_value( 0, 100 ) ) ); } void evt_generation_result( const generation_result & evt ) { auto ms = []( const clock_type::time_point a, const clock_type::time_point b ) { return std::to_string( std::chrono::duration_cast< std::chrono::milliseconds >( b - a ).count() ) + "ms"; }; const auto & meta = *evt.m_metadata; const auto & d1 = ms( meta.m_generated_at, meta.m_queued_at ); const auto & d2 = ms( meta.m_queued_at, meta.m_processing_started_at ); const auto & d3 = ms( meta.m_processing_started_at, meta.m_processing_finished_at ); std::cout << "result {" << evt.m_id << "}: " << "in route: " << d1 << ", waiting(p" << so_5::to_size_t( meta.m_queue_prio ) << "): " << d2 << ", processing(p" << so_5::to_size_t( meta.m_processor_prio ) << "): " << d3 << std::endl; } void evt_generation_rejected( const generation_rejected & evt ) { std::cout << "*** REJECTION: " << evt.m_id << std::endl; } /* * Generation of new dimension. * * Implements algorithm for the following value distrubution: * 60% of values in range [100,3000) * 30% of values in range [3000, 8000) * 10% of values in range [8000, 10000] */ unsigned int generate_next_dimension() { if( 0 == m_x + m_y + m_z ) { // Distribution params must be reinitialized. m_x = 60; m_y = 30; m_z = 10; } auto make_result = []( unsigned int & param, unsigned int l, unsigned int r ) { param -= 1; return random_value( l, r ); }; const auto v = random_value( 0, m_x + m_y + m_z ); if( v < m_x ) return make_result( m_x, 100, 2999 ); else if( v < m_x + m_y ) return make_result( m_y, 3000, 7999 ); else return make_result( m_z, 8000, 10000 ); } }; // // Data for requests scheduling. // struct request_scheduling_data { // Type of queue for storing pending requests. using request_queue = std::queue< generation_request_holder >; struct priority_data { // Mbox of request processor for that priority. so_5::mbox_t m_processor; // List of pending requests for that priority. request_queue m_requests; // Is processor free or busy. bool m_processor_is_free = true; }; // Processors and queues for them. std::array< priority_data, so_5::prio::total_priorities_count > m_processors; // Helper for access information for the specified priority. priority_data & info_at( so_5::priority_t p ) { return m_processors[ so_5::to_size_t(p) ]; } }; // // Messages and signals for interaction between request acceptor and processor. // // An information about a possibility to schedule request to // a free processor. struct processor_can_be_loaded final : public so_5::message_t { // Priority of the processor. so_5::priority_t m_priority; processor_can_be_loaded( so_5::priority_t priority ) : m_priority( priority ) {} }; // Ask for next request to be processed. struct ask_for_work final : public so_5::message_t { // Priority of the processor. so_5::priority_t m_priority; ask_for_work( so_5::priority_t priority ) : m_priority( priority ) {} }; // Request acceptor. // Accepts and stores requests to queue of appropriate priority. class request_acceptor final : public so_5::agent_t { public : request_acceptor( context_t ctx, so_5::mbox_t interaction_mbox, request_scheduling_data & data ) : so_5::agent_t( ctx // This agent has minimal priority. + so_5::prio::p0 // If there are to many pending requests then // new requests must be rejected. + limit_then_transform( 10, [this]( const generation_request & req ) { return make_transformed< generation_rejected >( m_interaction_mbox, req.m_id ); } ) ) , m_interaction_mbox( std::move( interaction_mbox ) ) , m_data( data ) {} void so_define_agent() override { so_subscribe( m_interaction_mbox ) .event( &request_acceptor::evt_request ); } private : const so_5::mbox_t m_interaction_mbox; request_scheduling_data & m_data; // The event handler has that prototype for ability to // store the original message object in request queue. void evt_request( mhood_t< generation_request > evt ) { using namespace so_5::prio; // Detecting priority for that request. auto step = double{ max_dimension + 1 } / total_priorities_count; // Requests with lowest dimensions must have highest priority. auto pos = so_5::to_size_t( so_5::priority_t::p_max ) - static_cast< std::size_t >( evt->m_dimension / step ); // Store request to the queue. auto & info = m_data.m_processors[ pos ]; // Defense for overloading. if( info.m_requests.size() < 100 ) { if( info.m_requests.empty() && info.m_processor_is_free ) // A work for that processor can be scheduled. so_5::send< processor_can_be_loaded >( m_interaction_mbox, so_5::to_priority_t( pos ) ); info.m_requests.push( evt.make_holder() ); // Update request information. evt->m_metadata->m_queued_at = clock_type::now(); evt->m_metadata->m_queue_prio = so_5::to_priority_t( pos ); } else // Request cannot be processed. so_5::send< generation_rejected >( m_interaction_mbox, evt->m_id ); } }; // Request scheduler. // Creates child coop with processors at the start. // Processes ask_for_work requests from processors. class request_scheduler final : public so_5::agent_t { public : request_scheduler( context_t ctx, so_5::mbox_t interaction_mbox, request_scheduling_data & data ) : so_5::agent_t( ctx // This agent must have more high priority than acceptor. + so_5::prio::p1 ) , m_interaction_mbox( std::move( interaction_mbox ) ) , m_data( data ) {} void so_define_agent() override { so_subscribe( m_interaction_mbox ) .event( &request_scheduler::evt_processor_can_be_loaded ) .event( &request_scheduler::evt_ask_for_work ); } void so_evt_start() override { // Child cooperation with actual processors must be created. // It will use prio_dedicated_threads::one_per_prio dispacther. so_5::introduce_child_coop( *this, so_5::disp::prio_dedicated_threads::one_per_prio::make_dispatcher( so_environment() ).binder(), [this]( so_5::coop_t & coop ) { so_5::prio::for_each_priority( [&]( so_5::priority_t p ) { create_processor_agent( coop, p ); } ); } ); } private : const so_5::mbox_t m_interaction_mbox; request_scheduling_data & m_data; void evt_processor_can_be_loaded( const processor_can_be_loaded & evt ) { auto & info = m_data.info_at( evt.m_priority ); // Processor was free when message was sent. // But its state could have been changed since then. // So we need to check it and do scheduling only if // processor is still free. if( info.m_processor_is_free ) try_schedule_work_to( evt.m_priority ); } void evt_ask_for_work( const ask_for_work & evt ) { // Processor must be marked as free. m_data.info_at( evt.m_priority ).m_processor_is_free = true; try_schedule_work_to( evt.m_priority ); } void create_processor_agent( so_5::coop_t & coop, so_5::priority_t priority ) { class processor_t final : public so_5::agent_t { public : processor_t( context_t ctx, so_5::priority_t priority, const so_5::mbox_t & interaction_mbox ) : so_5::agent_t{ ctx + priority + limit_then_abort< generation_request >( 1 ) } { so_subscribe_self().event( [priority, interaction_mbox] ( mhood_t cmd ) { cmd->m_metadata->m_processing_started_at = clock_type::now(); cmd->m_metadata->m_processor_prio = priority; // Some processing. // Time of processing is proportional to // dimension of the image to be generated. imitate_hard_work( cmd->m_dimension / 10 ); cmd->m_metadata->m_processing_finished_at = clock_type::now(); so_5::send< generation_result >( interaction_mbox, cmd->m_id, cmd->m_metadata ); // Processor is free to get next request for // processing. so_5::send< ask_for_work >( interaction_mbox, priority ); } ); } }; auto processor = coop.make_agent< processor_t >( priority, std::cref(m_interaction_mbox) ); // Mbox of processor must be stored to be used later. m_data.info_at( priority ).m_processor = processor->so_direct_mbox(); } void try_schedule_work_to( so_5::priority_t priority ) { auto & free_processor_info = m_data.info_at( priority ); // Should dive no more than several levels deep; const auto max_deep = 5; auto deep = 0; do { auto & info = m_data.info_at( priority ); if( !info.m_requests.empty() ) { // There is a work for processor. auto req = info.m_requests.front(); info.m_requests.pop(); // Message must be delivered to processor who asks // for new work. so_5::send( free_processor_info.m_processor, req ); free_processor_info.m_processor_is_free = false; break; } else { // There is no more work. Try to stole it from // lower priority. if( so_5::prio::has_prev( priority ) ) { priority = so_5::prio::prev( priority ); ++deep; } else // There is no more priorities to look. break; } } while( deep < max_deep ); } }; void init( so_5::environment_t & env ) { // All top-level agents belong to the same coop, // but work on different dispacthers. env.introduce_coop( []( so_5::coop_t & coop ) { auto mbox = coop.environment().create_mbox(); // Request scheduler and accepter stuff. // A special dispatcher. namespace disp_ns = so_5::disp::prio_one_thread::strictly_ordered; auto prio_disp = disp_ns::make_dispatcher( coop.environment() ); // Common data for both agents. Will be controlled by the coop. auto data = coop.take_under_control( std::make_unique< request_scheduling_data >() ); coop.make_agent_with_binder< request_scheduler >( prio_disp.binder(), mbox, *data ); coop.make_agent_with_binder< request_acceptor >( prio_disp.binder(), mbox, *data ); // Requests generator. coop.make_agent< request_generator >( mbox ); } ); } int main() { try { so_5::launch( init ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/producer_consumer_mchain/000077500000000000000000000000001523065210100240315ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/producer_consumer_mchain/CMakeLists.txt000066400000000000000000000005721523065210100265750ustar00rootroot00000000000000set(SAMPLE sample.so_5.producer_consumer_mchain) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.producer_consumer_mchain_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/producer_consumer_mchain/main.cpp000066400000000000000000000206501523065210100254640ustar00rootroot00000000000000/* * An example of using mchain for solving producer/consumer problem. * * Several producers will send requests to the single consumer. All requests * will be sent to size-limited mchain with a timeout on overflow. * This timeout will slowdown producers. * * The consumer will periodically process requests from mchain and sends * replies back. A very simple not-empty notificator will be used for * mchain to inform the consumer about presense of new requests. */ #include #include #include #include using steady_clock = std::chrono::steady_clock; // // Stuff for logging. // using log_msg = std::string; so_5::mbox_t make_logger( so_5::coop_t & coop ) { class logger_actor final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( [](mhood_t cmd ) { std::cout << *cmd << std::endl; } ); } }; // Logger will work on its own working thread. auto logger = coop.make_agent_with_binder< logger_actor >( so_5::disp::one_thread::make_dispatcher( coop.environment() ).binder() ); return logger->so_direct_mbox(); } template< typename A > inline void operator<<=( const so_5::mbox_t & to, A && a ) { so_5::send< log_msg >( to, std::forward< A >(a) ); } struct msg_maker { std::ostringstream m_os; template< typename A > msg_maker & operator<<( A && a ) { m_os << std::forward< A >(a); return *this; } }; inline void operator<<=( const so_5::mbox_t & to, msg_maker & maker ) { to <<= maker.m_os.str(); } // // Implementation of example shutdowner. // /* * Shutdowner will shutdown the SObjectizer Environment when all * producers send finish signals. */ class shutdowner final : public so_5::agent_t { struct another_producer_finished final : public so_5::signal_t {}; public : shutdowner( context_t ctx, unsigned int producers_count ) : so_5::agent_t{ ctx } , m_producers_left{ producers_count } { so_subscribe( so_environment().create_mbox( "shutdowner" ) ) .event( [this](mhood_t< another_producer_finished >) { --m_producers_left; if( !m_producers_left ) so_environment().stop(); } ); } static void producer_finished( const so_5::agent_t & p ) { so_5::send< another_producer_finished >( p.so_environment().create_mbox( "shutdowner" ) ); } private : unsigned int m_producers_left; }; // // Implementation of producers. // // A request to be sent for processing. struct request { so_5::mbox_t m_who; std::string m_payload; }; // A response from consumer. struct reply { std::string m_payload; }; /* * Producer agent will send N requests and then initiate finish signal. */ class producer final : public so_5::agent_t { // This signal allows to send next request for consumer. struct send_next final : public so_5::signal_t {}; public : producer( context_t ctx, std::string name, so_5::mbox_t logger_mbox, so_5::mbox_t consumer_mbox, unsigned int requests ) : so_5::agent_t{ ctx } , m_name( std::move(name) ) , m_logger_mbox{ std::move(logger_mbox) } , m_consumer_mbox{ std::move(consumer_mbox) } , m_requests_left{ requests } { so_subscribe_self() .event( &producer::evt_reply ) .event( &producer::evt_send_next ); } void so_evt_start() override { // Initiate request sending loop. so_5::send< send_next >( *this ); } private : const std::string m_name; const so_5::mbox_t m_logger_mbox; const so_5::mbox_t m_consumer_mbox; unsigned int m_requests_left; // An event for next attempt to send another requests. void evt_send_next(mhood_t< send_next >) { if( m_requests_left ) { // Send can wait on full mchain. Mark the start time to // calculate send call duration later. const auto started_at = steady_clock::now(); try { // Send another request. // Note: this call can wait on full mchain. so_5::send< request >( m_consumer_mbox, so_direct_mbox(), m_name + "_request_" + std::to_string( m_requests_left ) ); // How much time the send take? const auto ms = std::chrono::duration_cast< std::chrono::milliseconds >( steady_clock::now() - started_at ).count(); m_logger_mbox <<= msg_maker() << m_name << ": request sent in " << ms << "ms"; } catch( const so_5::exception_t & ex ) { // Log the reason of send request failure. m_logger_mbox <<= msg_maker() << m_name << ": request NOT SENT, " << ex.what(); // Initiate next send attempt. so_5::send< send_next >( *this ); } } else // No more requests to send. Shutdowner must known about it. shutdowner::producer_finished( *this ); } void evt_reply( const reply & msg ) { m_logger_mbox <<= msg_maker() << m_name << ": reply received, " << msg.m_payload; --m_requests_left; so_5::send< send_next >( *this ); } }; // // Implementation of consumer. // class consumer final : public so_5::agent_t { // This signal will be sent by not_empty_notificator when // the first message is stored in the empty mchain. struct chain_has_requests final : public so_5::signal_t {}; public : consumer( context_t ctx, so_5::mbox_t logger_mbox ) : so_5::agent_t{ ctx + limit_then_drop< chain_has_requests >(1) } , m_logger_mbox{ std::move(logger_mbox) } { // Appropriate mchain must be created. m_chain = so_environment().create_mchain( so_5::make_limited_with_waiting_mchain_params( // No more than 10 requests in the chain. 10, // Preallocated storage for the chain. so_5::mchain_props::memory_usage_t::preallocated, // Throw an exception on overload. so_5::mchain_props::overflow_reaction_t::throw_exception, // Wait no more than 0.5s on overflow. std::chrono::milliseconds(150) ) // A custom notificator must be used for chain. .not_empty_notificator( [this] { so_5::send< chain_has_requests >( *this ); } ) ); so_subscribe_self().event( &consumer::process_requests ); } // A mchain will look like an ordinary mbox from outside of consumer. so_5::mbox_t consumer_mbox() const { return m_chain->as_mbox(); } private : const so_5::mbox_t m_logger_mbox; so_5::mchain_t m_chain; void process_requests(mhood_t< chain_has_requests >) { auto r = receive( // Handle no more than 5 requests at once. // No wait if queue is empty. from( m_chain ).handle_n( 5 ).no_wait_on_empty(), []( const request & req ) { // Imitation of some hardwork before sending a reply back. std::this_thread::sleep_for( random_pause() ); so_5::send< reply >( req.m_who, req.m_payload + "#handled" ); } ); m_logger_mbox <<= msg_maker() << "=== " << r.handled() << " request(s) handled"; if( !m_chain->empty() ) // Not all messages from chain have been processed. // Initiate new processing by sending the signal to itself. so_5::send< chain_has_requests >( *this ); } static std::chrono::milliseconds random_pause() { std::random_device rd; std::mt19937 gen{ rd() }; return std::chrono::milliseconds( std::uniform_int_distribution< unsigned int >{2u, 25u}(gen) ); } }; void fill_demo_coop( so_5::coop_t & coop ) { const unsigned int producers = 40; // Shutdowner will work on the default dispatcher. coop.make_agent_with_binder< shutdowner >( so_5::make_default_disp_binder( coop.environment() ), producers ); // Logger will work on its own context. const auto logger_mbox = make_logger( coop ); // Consumer agent must be created first. // It will work on its own working thread. auto consumer_mbox = coop.make_agent_with_binder< consumer >( so_5::disp::one_thread::make_dispatcher( coop.environment() ).binder(), logger_mbox )->consumer_mbox(); // All producers will work on thread pool dispatcher. namespace tp_disp = so_5::disp::thread_pool; auto disp = tp_disp::make_dispatcher( coop.environment() ); // All agents on this dispatcher will have independent event queues. const auto bind_params = tp_disp::bind_params_t{} .fifo( tp_disp::fifo_t::individual ) .max_demands_at_once( 1 ); // All producers can be created now. for( unsigned int i = 0; i != producers; ++i ) coop.make_agent_with_binder< producer >( disp.binder( bind_params ), "producer-" + std::to_string( i + 1 ), logger_mbox, consumer_mbox, 10u ); } int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( fill_demo_coop ); } ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/queue_size_stats/000077500000000000000000000000001523065210100223505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/queue_size_stats/CMakeLists.txt000066400000000000000000000005521523065210100251120ustar00rootroot00000000000000set(SAMPLE sample.so_5.queue_size_stats) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.queue_size_stats_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/queue_size_stats/main.cpp000066400000000000000000000155451523065210100240120ustar00rootroot00000000000000/* * An example of using SObjectizer-5.5.4 features for receiving * run-time monitoring information. */ #include #include #include #include #include #include #include #include #include // A signal to worker agent to do something. struct msg_start_thinking : public so_5::signal_t {}; // Message for logger. struct log_message { // Text to be logged. std::string m_what; }; // Logger agent. class a_logger_t final : public so_5::agent_t { public : a_logger_t( context_t ctx ) : so_5::agent_t( ctx // Limit the count of messages. // Because we can't lost log messages the overlimit // must lead to application crash. + limit_then_abort< log_message >( 100 ) ) , m_started_at( std::chrono::steady_clock::now() ) {} void so_define_agent() override { so_default_state().event( [this]( const log_message & evt ) { std::cout << "[+" << time_delta() << "] -- " << evt.m_what << std::endl; } ); } private : const std::chrono::steady_clock::time_point m_started_at; std::string time_delta() const { auto now = std::chrono::steady_clock::now(); std::ostringstream ss; ss << double( std::chrono::duration_cast< std::chrono::milliseconds >( now - m_started_at ).count() ) / 1000.0 << "ms"; return ss.str(); } }; // Agent for receiving run-time monitoring information. class a_stats_listener_t final : public so_5::agent_t { public : a_stats_listener_t( // Environment to work in. context_t ctx, // Address of logger. so_5::mbox_t logger ) : so_5::agent_t( ctx ) , m_logger( std::move( logger ) ) {} void so_define_agent() override { using namespace so_5::stats; auto & controller = so_environment().stats_controller(); // Set up a filter for messages with run-time monitoring information. so_set_delivery_filter( // Message box to which delivery filter must be set. controller.mbox(), // Delivery predicate. []( const messages::quantity< std::size_t > & msg ) { // Process only messages related to dispatcher's queue sizes. return suffixes::work_thread_queue_size() == msg.m_suffix; } ); // We must receive messages from run-time monitor. so_default_state() .event( // This is mbox to that run-time statistic will be sent. controller.mbox(), &a_stats_listener_t::evt_quantity ) .event( controller.mbox(), [this]( const messages::distribution_started & ) { so_5::send< log_message >( m_logger, "--- DISTRIBUTION STARTED ---" ); } ) .event( controller.mbox(), [this]( const messages::distribution_finished & ) { so_5::send< log_message >( m_logger, "--- DISTRIBUTION FINISHED ---" ); } ); } void so_evt_start() override { // Change the speed of run-time monitor updates. so_environment().stats_controller().set_distribution_period( std::chrono::milliseconds( 330 ) ); // Turn the run-timer monitoring on. so_environment().stats_controller().turn_on(); } private : const so_5::mbox_t m_logger; void evt_quantity( const so_5::stats::messages::quantity< std::size_t > & evt ) { std::ostringstream ss; ss << "stats: '" << evt.m_prefix << evt.m_suffix << "': " << evt.m_value; so_5::send< log_message >( m_logger, ss.str() ); } }; // Load generation agent. class a_generator_t final : public so_5::agent_t { public : a_generator_t( // Environment to work in. context_t ctx, // Address of logger. so_5::mbox_t logger, // Addresses of worker agents. std::vector< so_5::mbox_t > workers ) : so_5::agent_t( ctx ) , m_logger( std::move( logger ) ) , m_workers( std::move( workers ) ) , m_turn_pause( 600 ) {} void so_define_agent() override { so_default_state() .event( &a_generator_t::evt_next_turn ); } void so_evt_start() override { // Start work cycle. so_5::send< msg_next_turn >( *this ); } private : // Signal about start of the next turn. struct msg_next_turn : public so_5::signal_t {}; // Logger. const so_5::mbox_t m_logger; // Workers. const std::vector< so_5::mbox_t > m_workers; // Pause between working turns. const std::chrono::milliseconds m_turn_pause; void evt_next_turn(mhood_t< msg_next_turn >) { // Create and send new requests. generate_new_requests( random( 100, 200 ) ); // Wait for next turn and process replies. so_5::send_delayed< msg_next_turn >( *this, m_turn_pause ); } void generate_new_requests( unsigned int requests ) { const auto size = m_workers.size(); for( unsigned int i = 0; i != requests; ++i ) so_5::send< msg_start_thinking >( m_workers[ i % size ] ); so_5::send< log_message >( m_logger, std::to_string( requests ) + " requests are sent" ); } static unsigned int random( unsigned int left, unsigned int right ) { std::random_device rd; std::mt19937 gen{ rd() }; return std::uniform_int_distribution< unsigned int >{left, right}(gen); } }; // Worker agent. class a_worker_t final : public so_5::agent_t { public : a_worker_t( context_t ctx ) : so_5::agent_t( ctx // Limit the maximum count of messages. + limit_then_drop< msg_start_thinking >( 50 ) ) {} void so_define_agent() override { so_default_state().event( [](mhood_t< msg_start_thinking >) { std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); } ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( [&env]( so_5::coop_t & coop ) { // Logger will work on the default dispatcher. auto logger = coop.make_agent< a_logger_t >(); // Run-time stats listener will work on a dedicated // one-thread dispatcher. coop.make_agent_with_binder< a_stats_listener_t >( so_5::disp::one_thread::make_dispatcher( env, "stats_listener" ).binder(), logger->so_direct_mbox() ); // Bunch of workers. // Must work on dedicated thread_pool dispatcher. auto worker_disp = so_5::disp::thread_pool::make_dispatcher( env, "workers", // Name of dispatcher (for convience of monitoring). 3 ); // Count of working threads. const auto worker_binding_params = so_5::disp::thread_pool::bind_params_t{} .fifo( so_5::disp::thread_pool::fifo_t::individual ); std::vector< so_5::mbox_t > workers; for( int i = 0; i != 5; ++i ) { auto w = coop.make_agent_with_binder< a_worker_t >( worker_disp.binder( worker_binding_params ) ); workers.push_back( w->so_direct_mbox() ); } // Generators will work on dedicated active_obj dispatcher. auto generator_disp = so_5::disp::active_obj::make_dispatcher( env, "generator" ); coop.make_agent_with_binder< a_generator_t >( generator_disp.binder(), logger->so_direct_mbox(), std::move( workers ) ); }); // Take some time to work. std::this_thread::sleep_for( std::chrono::seconds(50) ); env.stop(); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/redirect_and_transform/000077500000000000000000000000001523065210100234725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/redirect_and_transform/CMakeLists.txt000066400000000000000000000005661523065210100262410ustar00rootroot00000000000000set(SAMPLE sample.so_5.redirect_and_transfrom) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.redirect_and_transfrom_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/redirect_and_transform/main.cpp000066400000000000000000000207331523065210100251270ustar00rootroot00000000000000/* * An example of using message limits with redirection and * transformation for processing of message peaks. */ #include #include #include #include #include #include #include #include #include // A request to be processed. struct request { // Return address. so_5::mbox_t m_reply_to; // Request ID. int m_id; // Some payload. int m_payload; }; // A reply to processed request. struct reply { // Request ID. int m_id; // Was request processed successfully? bool m_processed; }; // Message for logger. struct log_message { // Text to be logged. std::string m_what; }; // Logger agent. class a_logger_t final : public so_5::agent_t { public : a_logger_t( context_t ctx ) : so_5::agent_t( ctx // Limit the count of messages. // Because we can't lost log messages the overlimit // must lead to application crash. + limit_then_abort< log_message >( 100 ) ) , m_started_at( std::chrono::steady_clock::now() ) {} void so_define_agent() override { so_default_state().event( [this]( const log_message & evt ) { std::cout << "[+" << time_delta() << "] -- " << evt.m_what << std::endl; } ); } private : const std::chrono::steady_clock::time_point m_started_at; std::string time_delta() const { auto now = std::chrono::steady_clock::now(); std::ostringstream ss; ss << double( std::chrono::duration_cast< std::chrono::milliseconds >( now - m_started_at ).count() ) / 1000.0 << "ms"; return ss.str(); } }; // Load generation agent. class a_generator_t final : public so_5::agent_t { public : a_generator_t( // Environment to work in. context_t ctx, // Name of generator. std::string name, // Starting value for request ID generation. int id_starting_point, // Address of message processor. so_5::mbox_t performer, // Address of logger. so_5::mbox_t logger ) : so_5::agent_t( ctx // Expect no more than just one next_turn signal. + limit_then_drop< msg_next_turn >( 1 ) // Limit the quantity of non-processed replies in the queue. + limit_then_transform( 10, // All replies which do not fit to message queue // will be transformed to log_messages and sent // to logger. [this]( const reply & msg ) { return make_transformed< log_message >( m_logger, m_name + ": unable to process reply(" + std::to_string( msg.m_id ) + ")" ); } ) ) , m_name( std::move( name ) ) , m_performer( std::move( performer ) ) , m_logger( std::move( logger ) ) , m_turn_pause( 250 ) , m_last_id( id_starting_point ) {} void so_define_agent() override { so_default_state() .event( &a_generator_t::evt_next_turn ) .event( &a_generator_t::evt_reply ); } void so_evt_start() override { // Start work cycle. so_5::send< msg_next_turn >( *this ); } private : // Signal about start of the next turn. struct msg_next_turn : public so_5::signal_t {}; // Generator name. const std::string m_name; // Performer for the requests processing. const so_5::mbox_t m_performer; // Logger. const so_5::mbox_t m_logger; // Pause between working turns. const std::chrono::milliseconds m_turn_pause; // Last generated ID for request. int m_last_id; void evt_next_turn(mhood_t< msg_next_turn >) { // Create and send new requests. generate_new_requests( random( 5, 8 ) ); // Wait for next turn and process replies. so_5::send_delayed< msg_next_turn >( *this, m_turn_pause ); } void evt_reply( const reply & evt ) { so_5::send< log_message >( m_logger, m_name + ": reply received(" + std::to_string( evt.m_id ) + "), processed:" + std::to_string( evt.m_processed ) ); } void generate_new_requests( int requests ) { for(; requests > 0; --requests ) { auto id = ++m_last_id; so_5::send< log_message >( m_logger, m_name + ": sending request(" + std::to_string( id ) + ")" ); so_5::send< request >( m_performer, so_direct_mbox(), id, random( 30, 100 ) ); } } static int random( int l, int h ) { std::random_device rd; std::mt19937 gen{ rd() }; return std::uniform_int_distribution< int >{l, h}(gen); } }; // Performer agent. class a_performer_t final : public so_5::agent_t { public : // A special indicator that agent must work with anotner performer. struct next_performer { so_5::mbox_t m_target; }; // A special indicator that agent is last in the chain. struct last_performer {}; // Constructor for the case when worker is last in the chain. a_performer_t( context_t ctx, std::string name, float slowdown, last_performer, so_5::mbox_t logger ) : so_5::agent_t( ctx // Limit count of requests in the queue. // If queue is full then request must be transformed // to negative reply. + limit_then_transform( 3, []( const request & evt ) { return make_transformed< reply >( evt.m_reply_to, evt.m_id, false ); } ) ) , m_name( std::move( name ) ) , m_slowdown( slowdown ) , m_logger( std::move( logger ) ) {} // Constructor for the case when worker has the next performer in chain. a_performer_t( context_t ctx, std::string name, float slowdown, next_performer next, so_5::mbox_t logger ) : so_5::agent_t( ctx // Limit count of requests in the queue. // If queue is full then request must be redirected to the // next performer in the chain. + limit_then_redirect< request >( 3, [next] { return next.m_target; } ) ) , m_name( std::move( name ) ) , m_slowdown( slowdown ) , m_logger( std::move( logger ) ) {} void so_define_agent() override { so_default_state().event( &a_performer_t::evt_request ); } private : const std::string m_name; const float m_slowdown; const so_5::mbox_t m_logger; void evt_request( const request & evt ) { // Processing time is depend on speed of the performer. auto processing_time = static_cast< int >( m_slowdown * float(evt.m_payload) ); so_5::send< log_message >( m_logger, m_name + ": processing request(" + std::to_string( evt.m_id ) + ") for " + std::to_string( processing_time ) + "ms" ); // Imitation of some intensive processing. std::this_thread::sleep_for( std::chrono::milliseconds( processing_time ) ); // Generator must receive a reply for the request. so_5::send< reply >( evt.m_reply_to, evt.m_id, true ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( [&env]( so_5::coop_t & coop ) { // Logger will work on the default dispatcher. auto logger = coop.make_agent< a_logger_t >(); // Chain of performers. // Must work on dedicated thread_pool dispatcher. auto performer_disp = so_5::disp::thread_pool::make_dispatcher( env, 3 ); auto performer_binding_params = so_5::disp::thread_pool::bind_params_t{} .fifo( so_5::disp::thread_pool::fifo_t::individual ); // Start chain from the last agent. auto p3 = coop.make_agent_with_binder< a_performer_t >( performer_disp.binder( performer_binding_params ), "p3", 1.4f, // Each performer in chain is slower then previous. a_performer_t::last_performer{}, logger->so_direct_mbox() ); auto p2 = coop.make_agent_with_binder< a_performer_t >( performer_disp.binder( performer_binding_params ), "p2", 1.2f, // Each performer in chain is slower then previous. a_performer_t::next_performer{ p3->so_direct_mbox() }, logger->so_direct_mbox() ); auto p1 = coop.make_agent_with_binder< a_performer_t >( performer_disp.binder( performer_binding_params ), "p1", 1.0f, // The first performer is the fastest one. a_performer_t::next_performer{ p2->so_direct_mbox() }, logger->so_direct_mbox() ); // Generators will work on dedicated thread_pool dispatcher. auto generator_disp = so_5::disp::thread_pool::make_dispatcher( env, 2 ); auto generator_binding_params = so_5::disp::thread_pool::bind_params_t{} .fifo( so_5::disp::thread_pool::fifo_t::individual ); coop.make_agent_with_binder< a_generator_t >( generator_disp.binder( generator_binding_params ), "g1", 0, p1->so_direct_mbox(), logger->so_direct_mbox() ); coop.make_agent_with_binder< a_generator_t >( generator_disp.binder( generator_binding_params ), "g2", 1000000, p1->so_direct_mbox(), logger->so_direct_mbox() ); }); // Take some time to work. std::this_thread::sleep_for( std::chrono::seconds(5) ); env.stop(); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/selective_msg_tracing/000077500000000000000000000000001523065210100233145ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/selective_msg_tracing/CMakeLists.txt000066400000000000000000000010161523065210100260520ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.selective_msg_tracing) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.selective_msg_tracing_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/selective_msg_tracing/main.cpp000066400000000000000000000065041523065210100247510ustar00rootroot00000000000000/* * An example to show usage of environment::change_message_delivery_tracer_filter() method. * * A message delivery tracing is enabled. Trace is going to std::cout. */ // Main SObjectizer header file. #include using namespace std::chrono_literals; // Signals for ping-pong. struct ping final : public so_5::signal_t {}; struct pong final : public so_5::signal_t {}; // A class for implementation of pinger and ponger agents. template< typename Signal_To_Send, typename Signal_To_Receive > class a_pinger_ponger_t final : public so_5::agent_t { const so_5::mbox_t m_mbox; public : a_pinger_ponger_t( context_t ctx, so_5::mbox_t mbox ) : so_5::agent_t( std::move(ctx) ) , m_mbox( std::move(mbox) ) {} void so_define_agent() override { so_subscribe( m_mbox ).event( [this](mhood_t) { so_5::send_delayed< Signal_To_Send >( m_mbox, 25ms ); } ); } }; // Main example agent. class a_example_t final : public so_5::agent_t { // Signal for finishing the example. struct finish final : public so_5::signal_t {}; public : a_example_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( &a_example_t::on_finish ); } virtual void so_evt_start() override { // Limit the work time. so_5::send_delayed< finish >( *this, std::chrono::milliseconds(500) ); // Setup a msg_tracing's filter which will filter only // trace messages related to this work thread. const auto thr_id = so_5::query_current_thread_id(); so_environment().change_message_delivery_tracer_filter( so_5::msg_tracing::make_filter( [thr_id]( const so_5::msg_tracing::trace_data_t & td ) { const auto tid = td.tid(); // Enable trace message only if tid is defined and this tid // is equal to thr_id. return tid && thr_id == *tid; } ) ); // Create a ping-pong pair which will work on separate thread. make_ping_pong_pair( so_5::disp::one_thread::make_dispatcher( so_environment() ).binder() ); // Create a ping-pong pair which will work on this work thread. make_ping_pong_pair( so_5::make_default_disp_binder( so_environment() ) ); } private : void on_finish( mhood_t ) { so_deregister_agent_coop_normally(); } void make_ping_pong_pair( so_5::disp_binder_shptr_t binder ) { // A mbox to be used by pinger and ponger agents. const auto mbox = so_environment().create_mbox(); // Create a new coop with two agents inside. so_5::introduce_child_coop( *this, std::move(binder), [mbox]( so_5::coop_t & coop ) { coop.make_agent< a_pinger_ponger_t >( mbox ); coop.make_agent< a_pinger_ponger_t >( mbox ); } ); // Initiate ping-pong exchange. so_5::send_delayed< ping >( mbox, 25ms ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_example_t >(); } ); }, []( so_5::environment_params_t & params ) { // Turn message delivery tracing on. params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); // Setup filter which disables all messages. params.message_delivery_tracer_filter( so_5::msg_tracing::make_disable_all_filter() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/simple_message_deadline/000077500000000000000000000000001523065210100235765ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/simple_message_deadline/CMakeLists.txt000066400000000000000000000005671523065210100263460ustar00rootroot00000000000000set(SAMPLE sample.so_5.simple_message_deadline) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.simple_message_deadline_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/simple_message_deadline/main.cpp000066400000000000000000000177031523065210100252360ustar00rootroot00000000000000/* * Demonstration of very simple implementation of message deadlines * by using collector+performer idiom. */ #if defined( _MSC_VER ) #if defined( __clang__ ) #pragma clang diagnostic ignored "-Wreserved-id-macro" #endif #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include #include // A request to be processed. struct msg_request final : public so_5::message_t { std::string m_id; std::time_t m_deadline; const so_5::mbox_t m_reply_to; msg_request( std::string id, std::time_t deadline, const so_5::mbox_t & reply_to ) : m_id( std::move( id ) ) , m_deadline( deadline ) , m_reply_to( reply_to ) {} }; // Just a useful alias. using msg_request_holder_t = so_5::message_holder_t< msg_request >; // A successful reply to request. struct msg_positive_reply final : public so_5::message_t { std::string m_id; std::string m_result; std::time_t m_started_at; msg_positive_reply( std::string id, std::string result, std::time_t started_at ) : m_id( std::move( id ) ) , m_result( std::move( result ) ) , m_started_at( started_at ) {} }; // A negative reply to request. struct msg_negative_reply final : public so_5::message_t { std::string m_id; std::time_t m_deadline; msg_negative_reply( std::string id, std::time_t deadline ) : m_id( std::move( id ) ) , m_deadline( deadline ) {} }; // Simple helper function for time formatting. std::string time_to_string( std::time_t t ) { char r[ 32 ]; std::strftime( r, sizeof(r) - 1, "%H:%M:%S", std::localtime(&t) ); return r; } // Agent for generation of serie of requests. class a_generator_t final : public so_5::agent_t { public : a_generator_t( context_t ctx, so_5::mbox_t processor_mbox ) : so_5::agent_t( ctx ) , m_processor_mbox( std::move(processor_mbox) ) {} void so_define_agent() override { so_default_state() .event( &a_generator_t::evt_positive_reply ) .event( &a_generator_t::evt_negative_reply ); } void so_evt_start() override { unsigned int delays[] = { 1, 4, 5, 3, 9, 15, 12 }; const std::time_t now = std::time(nullptr); int i = 0; for( auto d : delays ) { std::ostringstream idstream; idstream << "i=" << i << ";d=" << d; const std::string id = idstream.str(); const std::time_t deadline = now + d; so_5::send< msg_request >( m_processor_mbox, id, deadline, so_direct_mbox() ); std::cout << "sent: [" << id << "], deadline: " << time_to_string( deadline ) << std::endl; ++m_expected_replies; ++i; } } private : const so_5::mbox_t m_processor_mbox; unsigned int m_expected_replies = 0; void evt_positive_reply( const msg_positive_reply & evt ) { std::cout << time_to_string( std::time(nullptr) ) << " - OK: [" << evt.m_id << "], started_at: " << time_to_string( evt.m_started_at ) << ", result: " << evt.m_result << std::endl; count_reply(); } void evt_negative_reply( const msg_negative_reply & evt ) { std::cout << time_to_string( std::time(nullptr) ) << " - FAIL: [" << evt.m_id << "], deadline: " << time_to_string( evt.m_deadline ) << std::endl; count_reply(); } void count_reply() { --m_expected_replies; if( !m_expected_replies ) so_deregister_agent_coop_normally(); } }; // Agent-collector for handling message deadlines. class a_collector_t final : public so_5::agent_t { public : struct msg_select_next_job : public so_5::signal_t {}; a_collector_t( context_t ctx ) : so_5::agent_t( ctx ) {} void set_performer_mbox( const so_5::mbox_t & mbox ) { m_performer_mbox = mbox; } void so_define_agent() override { this >>= st_performer_is_free; st_performer_is_free .event( &a_collector_t::evt_first_request ); st_performer_is_busy .event( &a_collector_t::evt_yet_another_request ) .event( &a_collector_t::evt_select_next_job ) .event( &a_collector_t::evt_check_deadline ); } private : struct msg_check_deadline : public so_5::signal_t {}; // Comparator for priority queue of stored requests. struct request_comparator_t { bool operator()( const msg_request_holder_t & a, const msg_request_holder_t & b ) const { // Request with earlier deadline has greater priority. return a->m_deadline > b->m_deadline; } }; const state_t st_performer_is_free{ this }; const state_t st_performer_is_busy{ this }; so_5::mbox_t m_performer_mbox; // Queue of pending requests. std::priority_queue< msg_request_holder_t, std::vector< msg_request_holder_t >, request_comparator_t > m_pending_requests; void evt_first_request( mhood_t< msg_request > evt ) { // Performer is waiting for a request. // So the request can be sent for processing right now. this >>= st_performer_is_busy; so_5::send( m_performer_mbox, evt ); } void evt_yet_another_request( mhood_t< msg_request > evt ) { // Performer is busy. So the request must be stored in the queue. // And deadline for it must be controlled. const std::time_t now = std::time(nullptr); if( now < evt->m_deadline ) { m_pending_requests.push( evt.make_holder() ); // Just use delayed signal for every pending request. so_5::send_delayed< msg_check_deadline >( *this, std::chrono::seconds( evt->m_deadline - now ) ); } else { // Deadline is already passed. // Negative reply must be sent right now. send_negative_reply( *evt ); } } // Reaction for request for next job from performer. void evt_select_next_job(mhood_t< msg_select_next_job >) { if( m_pending_requests.empty() ) // Because there are no more pending jobs // we should wait them in performer_is_free state. this >>= st_performer_is_free; else { // We are still in performer_is_busy state and // next job must be sent to the performer. auto & request = m_pending_requests.top(); so_5::send( m_performer_mbox, request ); m_pending_requests.pop(); } } // Reaction for check deadline timer signal. void evt_check_deadline(mhood_t< msg_check_deadline >) { const std::time_t now = std::time(nullptr); // Just remove all jobs for which deadline is already passed. while( !m_pending_requests.empty() ) { auto & request = m_pending_requests.top(); if( now >= request->m_deadline ) { send_negative_reply( *request ); m_pending_requests.pop(); } else break; } } void send_negative_reply( const msg_request & request ) { so_5::send< msg_negative_reply >( request.m_reply_to, request.m_id, request.m_deadline ); } }; // Agent for handling requests. class a_performer_t final : public so_5::agent_t { public : a_performer_t( context_t ctx, so_5::mbox_t collector_mbox ) : so_5::agent_t( ctx ) , m_collector_mbox( std::move(collector_mbox) ) {} void so_define_agent() override { so_default_state().event( &a_performer_t::evt_request ); } private : const so_5::mbox_t m_collector_mbox; void evt_request( const msg_request & evt ) { const std::time_t started_at = std::time(nullptr); // Imitation of some work. std::this_thread::sleep_for( std::chrono::seconds(4) ); // Reply must be sent to request generator. so_5::send< msg_positive_reply >( evt.m_reply_to, evt.m_id, "-=<" + evt.m_id + ">=-", started_at ); // New job must be requested. so_5::send< a_collector_t::msg_select_next_job >( m_collector_mbox ); } }; void init( so_5::environment_t & env ) { using namespace so_5::disp::thread_pool; env.introduce_coop( make_dispatcher( env, 3 ).binder( bind_params_t{}.fifo( fifo_t::individual ) ), []( so_5::coop_t & c ) { auto collector = c.make_agent< a_collector_t >(); auto performer = c.make_agent< a_performer_t >( collector->so_direct_mbox() ); collector->set_performer_mbox( performer->so_direct_mbox() ); c.make_agent< a_generator_t >( collector->so_direct_mbox() ); }); } int main() { try { so_5::launch( &init ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/single_sink_binding/000077500000000000000000000000001523065210100227535ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/single_sink_binding/CMakeLists.txt000066400000000000000000000010121523065210100255050ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.single_sink_binding) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.single_sink_binding_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/single_sink_binding/main.cpp000066400000000000000000000060161523065210100244060ustar00rootroot00000000000000/* * A sample of the simple case for single_sink_binding. */ #include // Main SObjectizer header files. #include // Message that carries some data. struct msg_data final : public so_5::message_t { int m_value; msg_data( int v ) : m_value{ v } {} }; // Agent that creates MPMC mbox and sends new msg_data to it periodically. class data_generator final : public so_5::agent_t { // Signal to be used for new data generation. struct msg_generate_next final : public so_5::signal_t {}; // Mbox to be used for data distribution. const so_5::mbox_t m_dest; // Counter to be used for data generation. int m_value_counter{}; public: data_generator( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_dest{ so_environment().create_mbox() } {} // Accessor for mbox used for data distribution. [[nodiscard]] so_5::mbox_t distribution_mbox() const { return m_dest; } void so_define_agent() override { so_subscribe_self().event( &data_generator::evt_generate_next ); } void so_evt_start() override { so_5::send< msg_generate_next >( *this ); } private: void evt_generate_next( mhood_t< msg_generate_next > ) { // New message has to be sent to the destination mbox. so_5::send< msg_data >( m_dest, ++m_value_counter ); // Next iteration after some delay. so_5::send_delayed< msg_generate_next >( *this, std::chrono::milliseconds{ 25 } ); } }; // Agent that waits msg_data messages to be sent to its direct_mbox. class data_consumer final : public so_5::agent_t { // Number of consumed messages. int m_messages_consumed{}; public: data_consumer( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe_self() .event( &data_consumer::evt_data ); } private: void evt_data( mhood_t< msg_data > cmd ) { std::cout << "data_consumer: new data: " << cmd->m_value << std::endl; ++m_messages_consumed; if( m_messages_consumed > 3 ) // It's time to finish the demo. so_deregister_agent_coop_normally(); } }; int main() { try { // Starting SObjectizer. so_5::launch( []( so_5::environment_t & env ) { // Create a new coop with generator and consumer agents. env.introduce_coop( []( so_5::coop_t & coop ) { auto * generator = coop.make_agent< data_generator >(); auto * consumer = coop.make_agent< data_consumer >(); // Make a binding for msg_data message. // Binding should live as long as consumer agent, // so place it under the control of the coop. auto * binding = coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() ); // Message msg_data from generator.distribution_mbox // should go into consumer.direct_mbox. binding->bind< msg_data >( generator->distribution_mbox(), so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); } ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/state_deep_history/000077500000000000000000000000001523065210100226525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/state_deep_history/CMakeLists.txt000066400000000000000000000005561523065210100254200ustar00rootroot00000000000000set(SAMPLE sample.so_5.state_deep_history) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.state_deep_history_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/state_deep_history/main.cpp000066400000000000000000000240521523065210100243050ustar00rootroot00000000000000/* * An example of usage of agent's state with deep history. */ #include #include #include // Main SObjectizer header file. #include // Messages to be used for interaction with console agent. struct key_cancel final : public so_5::signal_t {}; struct key_bell final : public so_5::signal_t {}; struct key_grid final : public so_5::signal_t {}; struct key_digit { char m_value; }; // Main agent. class console final : public so_5::agent_t { // States of agent's statechart. state_t dialog{ this, "dialog", deep_history }, wait_activity{ initial_substate_of{ dialog }, "wait_activity" }, number_selection{ substate_of{ dialog }, "number_selection" }, special_code_selection{ substate_of{ dialog }, "special_code_selection" }, special_code_selection_0{ initial_substate_of{ special_code_selection }, "special_code_selection_0" }, user_code_selection{ substate_of{ special_code_selection }, "user_code_selection" }, user_code_apartment_number{ initial_substate_of{ user_code_selection }, "apartment_number" }, user_code_secret{ substate_of{ user_code_selection }, "secret_code" }, service_code_selection{ substate_of{ special_code_selection }, "service_code" }, operation_completed{ substate_of{ dialog }, "op_completed" }, show_error{ this, "error" } ; public : console( context_t ctx ) : so_5::agent_t{ ctx } { // Setting up statechart. dialog .event( &console::dialog_on_grid ) .event( &console::dialog_on_cancel ); wait_activity .on_enter( &console::wait_activity_on_enter ) .transfer_to_state< key_digit >( number_selection ); number_selection .on_enter( &console::apartment_number_on_enter ) .event( &console::apartment_number_on_digit ) .event( &console::apartment_number_on_bell ) .event( &console::apartment_number_on_grid ); special_code_selection_0 .transfer_to_state< key_digit >( user_code_selection ) .just_switch_to< key_grid >( service_code_selection ); user_code_apartment_number .on_enter( &console::user_code_apartment_number_on_enter ) .event( &console::apartment_number_on_digit ) .event( &console::user_code_apartment_number_on_bell ) .event( &console::user_code_apartment_number_on_grid ); user_code_secret .on_enter( &console::user_code_secret_on_enter ) .event( &console::user_code_secret_on_digit ) .event( &console::user_code_secret_on_bell ) .event( &console::user_code_secret_on_grid ); service_code_selection .on_enter( &console::service_code_on_enter ) .event( &console::service_code_on_digit ) .event( &console::service_code_on_bell ) .event( &console::service_code_on_grid ); operation_completed .on_enter( &console::op_completed_on_enter ) .time_limit( std::chrono::seconds{3}, wait_activity ); show_error .on_enter( &console::show_error_on_enter ) .on_exit( &console::show_error_on_exit ) .time_limit( std::chrono::seconds{2}, dialog ); } void so_evt_start() override { // Agent starts in the default state. Switch to the appropriate one. this >>= dialog; } private : // Helper class for imitiation of console's display. class display { public : void show( const std::string & what ) { std::cout << "display, msg: '" << what << "'" << std::endl; } void show_error( const std::string & what ) { std::cout << "display, ERR: '" << what << "'" << std::endl; } void clear() { std::cout << "display cleared" << std::endl; } }; // Limitations for various pieces of user input. static const std::size_t apartment_number_size = 3u; static const std::size_t secret_code_size = 4u; static const std::size_t service_code_size = 5u; // Accumutator for apartment number. std::string m_apartment_number; // Accumutator for user's secret code. std::string m_user_secret_code; // Accumutator for service code. std::string m_service_code; // Message to be displayed in show_error state. std::string m_error_message; // Message to be displayed in operation_completed state. std::string m_op_result_message; // Display imitator. display m_display; void dialog_on_cancel( mhood_t< key_cancel > ) { // key_cancel always leads to wait_activity state. // We must switch to wait_activity but not to dialog state // because dialog has history and switch to dialog state will // lead to restoration of last active dialog's substate. this >>= wait_activity; } void dialog_on_grid( mhood_t< key_grid > ) { this >>= special_code_selection; } void wait_activity_on_enter() { // We can enter in that state only on the very first enter into dialog // state (because wait_activity is the initial substate). Or after // completion of some operator. Or after cancelation of any operation. // Because of that all accumulators can be cleared here. m_apartment_number.clear(); m_user_secret_code.clear(); m_service_code.clear(); m_display.clear(); } void apartment_number_on_enter() { // Show the current value of accumulator on reentry into this state. if( !m_apartment_number.empty() ) m_display.show( m_apartment_number ); } void apartment_number_on_digit( const key_digit & msg ) { if( m_apartment_number.size() < apartment_number_size ) { m_apartment_number += msg.m_value; m_display.show( m_apartment_number ); } else initiate_error( "apartment number must be " + std::to_string( apartment_number_size ) + " digits long" ); } void apartment_number_on_bell( mhood_t< key_bell > ) { if( m_apartment_number.size() == apartment_number_size ) { complete_operation( "dial to apartment #" + m_apartment_number ); } else initiate_error( "apartment number must be " + std::to_string( apartment_number_size ) + " digits long" ); } void apartment_number_on_grid( mhood_t< key_grid > ) { initiate_error( "enter apartment number, then 'b'" ); } void user_code_apartment_number_on_enter() { // Show the current value of accumulator on reentry into this state. if( !m_apartment_number.empty() ) m_display.show( m_apartment_number ); } void user_code_apartment_number_on_bell( mhood_t< key_bell > ) { initiate_error( "enter apartment number, then '#', then secret code, " "then 'b'" ); } void user_code_apartment_number_on_grid( mhood_t< key_grid > ) { if( m_apartment_number.size() == apartment_number_size ) { this >>= user_code_secret; } else initiate_error( "apartment number must be " + std::to_string( apartment_number_size ) + " digits long" ); } void user_code_secret_on_enter() { // Show the current value of accumulator on reentry into this state. if( !m_user_secret_code.empty() ) m_display.show( std::string( m_user_secret_code.size(), '*' ) ); } void user_code_secret_on_digit( const key_digit & msg ) { if( m_user_secret_code.size() < secret_code_size ) { m_user_secret_code += msg.m_value; m_display.show( std::string( m_user_secret_code.size(), '*' ) ); } else initiate_error( "secret code must be " + std::to_string( secret_code_size ) + " digits long" ); } void user_code_secret_on_bell( mhood_t< key_bell > ) { if( m_user_secret_code.size() == secret_code_size ) { complete_operation( "open the door via user secret code: " + m_apartment_number + "#" + m_user_secret_code ); } else initiate_error( "secret code be " + std::to_string( secret_code_size ) + " digits long" ); } void user_code_secret_on_grid( mhood_t< key_grid > ) { initiate_error( "enter user secret code, then 'b'" ); } void service_code_on_enter() { // Show the current value of accumulator on reentry into this state. if( !m_service_code.empty() ) m_display.show( std::string( m_service_code.size(), '#' ) ); } void service_code_on_digit( const key_digit & msg ) { if( m_service_code.size() < service_code_size ) { m_service_code += msg.m_value; m_display.show( std::string( m_service_code.size(), '#' ) ); } else initiate_error( "service code be " + std::to_string( service_code_size ) + " digits long" ); } void service_code_on_bell( mhood_t< key_bell > ) { initiate_error( "enter service code, then '#'" ); } void service_code_on_grid( mhood_t< key_grid > ) { if( m_service_code.size() == service_code_size ) { complete_operation( "open the door via service code: " + m_service_code ); } else initiate_error( "service code must be " + std::to_string( service_code_size ) + " digits long" ); } void op_completed_on_enter() { m_display.show( m_op_result_message ); } void show_error_on_enter() { m_display.show_error( m_error_message ); } void show_error_on_exit() { m_display.clear(); } void initiate_error( const std::string & what ) { m_error_message = what; this >>= show_error; } void complete_operation( const std::string & what ) { m_op_result_message = what; this >>= operation_completed; } }; // Helper for creation of coop with console agent. so_5::mbox_t create_console( so_5::environment_t & env ) { return env.introduce_coop( []( so_5::coop_t & coop ) { return coop.make_agent< console >()->so_direct_mbox(); } ); } void demo() { // A SObjectizer instance. so_5::wrapped_env_t sobj/*{ []( so_5::environment_t & ) {}, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cerr_tracer() ); } }*/; auto console_mbox = create_console( sobj.environment() ); while( true ) { std::cout << "enter digit or 'c' or 'b' or '#' (or 'exit' to stop): " << std::flush; std::string choice; std::cin >> choice; if( "c" == choice ) so_5::send< key_cancel >( console_mbox ); else if( "b" == choice ) so_5::send< key_bell >( console_mbox ); else if( "#" == choice ) so_5::send< key_grid >( console_mbox ); else if( "exit" == choice ) break; else if( 1 == choice.size() && std::isdigit( choice[ 0 ] ) ) so_5::send< key_digit >( console_mbox, choice[ 0 ] ); } // SObjectizer will be stopped automatically. } int main() { try { demo(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/state_deep_history/statechart.jpg000066400000000000000000003535031523065210100255270ustar00rootroot00000000000000JFIF``CC "  z  !1AX"289QRUWYx67aqrvw(5y#%'B$&HVb )34CEFGSghsIcf ?ss@xpAig'e"k5ptE[q6e6|淶T{:ݧܲkܶm\QiQIސQ*ߥo7.P : !Ǐ#lYۙ[;2N7w·sͰӽYx5uYӖ.MNX^!ɠih49j!_zs0LT8ϻްya:$0h<N2JϮ7#0" !$M(4Ҏ!x1e#0i[M@exM^Y*Өd kV)59eOBZZZ&j7HQ$TTzL=GLjVjUV#F:^))ԩB4kKN-@`steYu D@5 5 DQI&$#׀1Fc64e7`İ֖zR cNjM$M0z QgZ Q&MWXyg4\ъh3V^9gNYلt聽:͠20iD27VQ4:k20j2@y! (#J=iQjpĵͮ(,oC{f􋨑8\ 4\Qf,bTMS jOHjP1B%*jѫ,N_`kI/^:koKY<VG@`2MMB`YaG@ D@DHJՌfSHzuTRz2D9'b+PN)kJui<{PRꚣBאT+RT(TMPy*nZe北NIVyMGP]@4MDFa7@GQAt7`<w^0"Cs!!!!y%?@: GMtym7~;-[oWf/cmQeiRC7 tf<ٕUdeudZQ-T$x0q/ e 4a`                  |+G59IdofrWSUeYmY)%rTł~qHZLQH̲UDZZbܧ;=1* ;=1*rب_ 'T" ب_!s۳bP+s۳b)nl튅"uB )nl튅";=1* ;z٫%6n!jAkM%QZZ^dE AD ;ngV= @CP`!i:mY s܄A#S:KEdP%bn'Qd4gI BNk.k8=n3/)қb=FE l]Ƨc(o*$)DrI'ZvuֽpP:ܞK!k]lfs<ץ+0k9>FjR T/ٵͶlӞ'LdO(-髊bcYEMZON'Tpu>m㰓mJ/$Y@-#P߃N[Ri'O\ \\̒VjLBϵic[S̓i2YˠC,UJ$[C; 7͕Vo0B# |ܲMS(oիio^f%\~ثCchI^]VB,[;t\Co4 KŠ8[r& 5fdMzB^m됉0ӛ06VE!Q5N"<4Dбxt%bZC uDQ x"7.~RRlŋCq>=٪m.~=ZB"\1N}5+zT]p4Id;Qrb;,qq(mz8vm:aKcIC'^S,;-+=bXI#`}3֠~{lrb۱L az[g,Ģ:bꒊao+`.2ԓh>m)U.mKE T:cu}ت^q"nD'2tyt:@mK7{s깜{;HCFo qNʹOX`>qDo]J9%G%^^\3ZlrYd{?"lc0ՏgtMvWx~?f0R/9rlbШL: g+g=us" >qS!Bm5i(#gDtCN403"jdbڻ FO{dK*6qqӻY{f\'RK-)>s?^w-n s4(NjMeXS}nNK\+[bVolnL8[#R*K,5N^^dSODANw5sieUn,ӄVCHiI^)XLtJV%ѼvjUTk9Ig3# nYكd)7ħ*J xFUTJ iCfxmZu܆*Յdv0LxGIjI0( Kuep FA Q׼u nay䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?.|VK%>䞆_j7$2P,uuaf>+wB?!ZnJ4媞i&ҞYg%Ts'H4<ЯRQygݘC"ܓC䞆_jq-I&Yit@|˽(M" xMuܓ]@u uaNpyh͕FIeK,K$y}BI%Ye Д%4"bTyI^_x !!!! @tn?A4 ƪzJKѴpR S-n2ѭ]n׮fbBTӊ͇2rEo)nhCPPlUwLSǻ2i DEm42۔;Q :!.˨nh,DD48e-vF%kXޮx(XP9cUM uUJ -Vi) Jӌhթ&Xҵ8>O-)؏ FOrW.3uQFRĩWMNOKXp[!r23$*_R }&%ٹ{NZr6vgڴg2KY-bb(Sr/g`\edɊvڬ}/Zϵ6r-V붃k$p/Mq-EUtu[mLvii>҇՛Kv ԋݵ8&I֡MKI.19B9=5P1f+..렀Ø@PMeV1bᲩ%Af.t5tj8#Dk(&+: dSS\Wc4fZNO$Ku6㷨XMeV1b -ێޠv[c(u6㷨X7L 4Sn;znՌoێޠv[c(0MeV1bSn;znՌotKu6㷨XMeV1b -ێޠv[c(u6㷨X7L 4Sn;znՌoێޠv[c(0MeV1bSn;znՌotKu6㷨XMeV1b -ێޠv[c(u6㷨X7L 4Sn;znՌoێޠv[c(0MeV1bSn;znՌotKu6㷨XMeV1b -ێޠv[c(u6㷨X7L 4Sn;znՌo&q-{i2@^me-qt6NEwtY;!"lwa\JYQ:Rl:%MqXc/OfWL2 <Ccnebm׶[EsTD*k9J OI1NZu+QjSF_>|4ҿq՞a8K4ҁ]U 6 @fs >@'@7% .<8h"]xW^ʏ)#_ )ײHeX!!!!!JzHk7I`4!rf-+Z-$I-U6 H<<lwM'o͓ l[ѝdQ졦i駝ˎ{nc5q ێ y~ا+Ij7!m&]wdq^XuE1"1t~v3?uGV1.K(_\%`N'ic>#Z<>!g~[P'ic>#Z<>!g~[P'ic>#Z<>!g~[P'ic>#Z<>!g~[P"6{ώ` ݤ=kt>nB'd ݤ=kt>nB'd ݤ=kt>nB'd ݤ=kt>nB'd ݤ=kt>nB'd ݤ=kt>nB'd ݤ=kt>nB'd ݤ=kt>nB'd ݤ=kt>nB'd ݤ=kt>nB'd@Qǀ@A?H{}T#Ͻ>$pCL2sˊ >}CψַCO|GUx}CψַCO|GUx}CψַC85Py^? x}CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַCH{}T"vB }CψַC 13Y)jĚEŴɖw:y ɤi&%eiBc%ִ3~`C/e m,3ͅ25%a3=y ֘ZFU ƕ1HQVQ +T/JiYlO"D@u"u+g@^gQ:x5:k֋K2ײHeŊEu1?+e)BBBBBB5|;/\A/`1{|NM,0!(s<lzmp#`JTkMt+T)X\6E>jj!,4äpyp a\/79(T596] ѩ Qfz LԐgZiBM4-)v,ʓ!/1鶎\3efkㅑHUPV̡t;KWk;wmgݰuHj? KՋ& .IN ׬jjҔgJ1"~Ys5~p[V|SA1LcE2Djlz9s T)qr;-Uh$[jdn{i f֛Mwux(BLF: QFG cuIA1Xf;+^WԮK:jQ UOdg@j:-J6P'lm1Dt'D7QfQ 5FWt`p >pG<{:{H"!]{:{H!7J,B7J:S<9Q C5bQH[Km^=2|{{e#.捗8s͐])8~OM`ߩۆظn3 䴊*S[x7%e 9D5 TkFtsk.}8#^uAd6`]l5vzUˀIm\N8(QKD9L7%I$Oo;i2O֏?ȳyr 6"ݛf>iX2ʹ+rZB+sm&8iHxH\e'PG{WӺ׉ RطsdMTBAD{lַ8mU;%\L=n I s \%u) dI?yu?>8p;(6s❹6ts(h9f Gy*ϛlaEX{u.{tMGjju4 gI/,[%[nFyAMFQ~rL$/cHc-rZb<ԩ0Z̍.3Wn㍢ZDNU_5\E (URMR!)T'2J#4?g6=JvA֫55_"k 9Za4ͽofs9e\mw5ɢ֒c{$‹Z8j\\D3`%A$U5 >F]̽e.'O.։# 7JXDrC6ϵhRi(21nI#͊VإnLA }wMR?s $Ξ+sg?@W\&u2❻QnwqC(ɤ2I&n8՟9Λԥcڮz噹吓#F`]E/ki P NAyRtjFyW%jEģZIeZU TRIy*I04҈͛+Z O"9RH!0JY!EM0 (Xu)M0n՗I 90y̡9O䃖*r_ *(#[/V~V5Dw)*\,zkgHR @"<@|wMR?s $Ξ+sg?@W$Ξ+sg?Mg,M(╻Qt#(N/k4D:ج~pKe L5™+ 0ջJԷ wS,"4$M)3Ok'GsJQAS"":g@88%9(6sv)۞q 52Ppu(^c6kr:C [v/z6ɘz,|JB6 ٴ9.YQ0.ۖݦv z;6[ɹ}k$%kKG;K~բ3\md9fZHԝI!(bt ΍@@&uJ Sjk{c;] +x 60#%Œ#n@j^".>01ŴmrM wJA'>K9t=,܊Ŵ=UR-vz24EVPOQTO= :h:N:xԧnu DC5 5<0jCO Fa|Y+yͥ >-=fsN.a8̴jEF- GlUYPlu]IdWI1^fvMq9^R%nN cP\g(R:"<ڈ,l:%+uwt4W!x0[dfe[uU9Y3&2L"6j 3BԪ9gRw֣!2QNܻHX6@2$X޷jJ*o 6\:#V%OVӒ4,v9G_n8gG43J?d@e01m{)Vwΰ6qѤkUpit7o MVp99i*]UX}Fg]jl~^cY-=W.˝%SFoMeyT ==ۤ\uTChh ɨ&Uo%g=ⳮn])/;q ;4Y',  ;fi3 8l:|B'JsG\B)T\zQJITYeŶT:XXlN:ZfJq.lzr֠dz.9/^TZR$Nyhn]dW&Ͻ1%A@h`Zpb_G&E#=97nyjNɊͫMR`Sچs$%Xޞggӑ$>%KC$QNVݚCOp)=pw݁Hp,T-WgnT;Ҵjͧ]f*88Kxu^JO eKAp<jYܻ'vtO(Ln>: TΆvI)f& ?r gsNmLm#"kߧFbQWj}кL&l~>+f\6᪉;vۆtf)v29FQu]w W$Ξ+sg?rMR?s vOkgTn :z48@'xK0馡6GCfV03kVT59䡢bH&xg{e{5怄9X=b" "6sJ{g E%p@vQ$b~V2y}"TyI^_x !!!!!!icW L{r'|rwZ꺟6H"eH FU\Cʳ(lz-ZJ9fy'#..^1yiT*]8ͷNwՒD9WFlhӬʝ`e 癖\dmz!ͯ!>"\3\ Fw`5.E{rQւݻѯ%Ec%Ep&HN` 2{+Yp2ckg؎ۜytAh|JfC]}n!06 4՛ڎZ%kjU2G/o#|}Iyn)UvjƷWA ApZ%e\DO4BݤOy $\;am#vU,6?[W+KZd7sj[계)4֦(U%M,7KzX~Fb1^)Jf4q+aI_?RĭZ¡'Of B9TLڳRI gW6%5 tDz!9[md-a.Zۄ] ׊;תf(qmĿ]Xy: _gnlğ|x#*Y%묡^ihzE҈9vkhzJO6ה'A-Ieeȹ68T8EԔGn8ۍOj 9 t8^m^9s%3C7vs=Jz4|Vϕ|9þ$/? G;Ƈ%%YBYeeMYg.TK4lHYQi&ђy,"3o?fMl^ v6Xv|*ߔ<*1;9l.2 ȉ州&Dj;H8QP:rcLc-]mTN5 du3Je)4ҜhʤӗJI*4N *]qf֊M^r胄ij3$W+DKRUiFO<7e[gFVhGMN9(u`yBVGg6Uuxxġ @7v :4+o3Ό =|ɄD4눆\^'B3~݆5'u+E&%ڙP (+!5JRjud AW?SX<;bOGsh]VwʟlIêSm?X]VwʟlIêSm?X]VwʟlIƼO~O;P(j R2AU $fQ<ȔiwSGIm㭲WWn[;s=r_ː6 KrL]>IuU9VBMJ A$͕?ؓ]fxvsV4ā^ "@#4o[^{XW5VQe1kJJ/d6i҇{ W-;W"ԧVnˬbPn͐6ˏD;=~[7B:oΥ9x,5zܛQm+RldfUi%K4o:ɭ>8U;HQBV!'z]gfˋ(nMX|d`nTVTo8fqllʩDwu_(!GنNqso:S)չ=`Ul%5Ƚu Zk.tuNȤ: X2NP{b8(M(ͤ./& fC+ӕ㷀<@WZrbd1}jȑyO1J&m VDI&^ΕtNG6ޚ*BmTP6¶cbžI-[NȉOmGBBCޜJV!O\n>p$Vf=–M1d}>ޤguӹe/3Uqi]q-ҕ yB2X,cqJCڋH'rv b͕MCYs,⛁tղ&96'B[WRŚ-RTc;4aEBTU s =xe3@ )BEܴ3e6~fh;&gmSHu?.Ø~ev* E!6U3@fX5KIjlh ک&S]im6Mj]$r/{vW .vU1 Ȧ: 82z\%XIJd̔yPL!ci+i.4 [4헊Ʉ儥zL&mXdZhfQI]v2/ĭW3fYժL0s)"/\6H1o,.@꺁6UUQ##?k00뮠:⥿9ƏٔonmZt5;rݺʽﳎfmwЩ7LF{S:!;,kw#Æ U]jwiꙺ , )aJ FI@@j#JYujeͺ2I6#VQYEja,6/Mͻb]RUTIW%Qa 3& 2[[>Bf\WQ=VOmkl_S  *&vr9HDI%:EdSݸA1ep,kȊ#aISqYڶu- u`Z%nk5U'Uz;\َN^FSA$i1Fa<&5z1kʑxW4kK ۷Z)51MⱮKumͽ>bY"JuRG4nT96a*bzӶZn zէhTi BFcna YY,+9J6zcXy wa6V8zY/Kfjeۛ=cZFBTjcM^nSE ,-q6͝g\[j۔*n5Kime"^ mQUd*PJS&)Ӛ;#lr/3[yn oLFIW.h  ہiht0b垤bq̵>~f1n԰ݮV:[$,ҩ=фJ[!Pe9-}xjAV: VgEX)L[}&Gi]ۉnGMRUXϏ -jJmbX(+CD:1 P>ul]758uug{z{vfn uEx>[io훜sTv.ZC.v ɳM[U?xE9Ny6@dN)$ U@t9MF]C{oHrv ٵa  ֒pӀS7xLG wds\ (Yv5Y[XաqOZ+H5O+ջe4F7~M.JԮW>ɭt*1\ƷamګSҕeĢJkQWbRl]7Yip6ڇڗ 4瓂udȶfNQiO{غ/9y},gԤ*֧c%Uk9'^kvܹN /Ib4Q&Lo 2%IϡyF]C{oH]S,żjߗfb3T\n/-@I-PӍCKACJ"ZJO(+JYtҀE7 o/D\ۻ![o-[rIHCUFLUrtfe)>ڶ}BL}WK{D%TJ/"" 1um)azx%kzii<2[zpS˟q5M oB4i*8xը&tʅP-BkK2ͮ|k:+tmjt-MQG=' uP:kO=Qf}yiأg/WYuSl4Z. oO&۷.!n0*9u7&(frjmu֣8yٰ$)өl[u3x@VuKIh-IToezJDD/sj`Cԙœ^q +5BoWY'vj露znf&{@]Cs5S32g=)N_+XDh$Zw|Sպ ƥ,띠4'b}Jy 5X 2[vXi/Dvɷ|( xR<جzIL%Y(\AVaN zgj!>8g S"2!(:h1W/NMĸMwS\lH;YMXW9 t!(渄R0r\(7 Tz ,vǍȰx{\ϽF&s޻*M79{dN>[fuǷۖ®lTT;ꮠHUP/ZDVP@E>pY5xMD@5;ɷOrh Ԏ9U,wM\KFOMt₉3%ET2`) SdhЫp¥Jx?+vט}겲oj;ZR$t6+rZ jHmي%lemsnM>\і5dV3)"LuJJ 甂]k .,RF\:QtHOjA CAAcddR799zqq,˵hܦ êFwݶq!^LpړIIe~7Use-Nj- 62"ZNb[*Uݖ;%+2ҥ¼LM;z\+$& =6^@]e;-A3O4הC@<& 2JDBaf r<ݢv{ٷv=:R3C%T6u=̟%r*6in1JST'P(K,-ư=T-+ljkd#\gyVQppͩ7t,p3Exm5dY{]3Ѥݿv)cMՈԼ!m29kVnԔ)UIʋ$x؈fÎ5RMn]4uNZ0F̫r<,ǔC[˄鐒qRXʱ6"ՠ6ٳV[/ FB֭дej)Mn˭E镍k&YMe2dZErmr7+仗&xe1Xm&ܼqr*T-NSU@]J(qwsun 'V]toO,Ȉ kfDZ Rޫw"{Yz[% q4( zMԣcL׺*OKzNHHVX́Wsźl]UAYp%l%6Dc LjyxpKi M .޾+k'' 5ʔNiu 'P@uǖ_Y=?xP鬞|f<(VPӈh9YM0uIA>]umDN;zyYF}Д70,N꘨f9QIܖiG ~]+M5( *c)'eU+=xR,O!/W^ʏ)#_ )ײHeX!!!!!! L{r'|Ag|;     :TItYe׈xt%e+RlVEnQ4Ws$C ԩrWjz^*-֙dW_i$1esû:L7 @C8XC]@@B+|˼s6غ ) ^uȸ]4 =oVˆw׭JU7Ul;Xy>LR;sI'lfv8(K~Zr=9qw+fŴWt1_/Y%[Ws7YUt7͞IIQ s۔-,f >PpO)i٤ّح#(u9[~fќRjHF^S*֩BI% +ל ZJEiӡh]ȭ[hJ6:[t.Wty)p]WVYGN5[cpSݖyޥwlZc]]NPm),*qTZ"A"{Tc3]ʅJv[ +7'[nyc$+lfvt$jW!ITi+܏&&2qӚSS5E]o/˗wݶ{=MiӼ8@UUr\kg]RK>v=.f9H=X5%gFL-DfM rqWX˃tJh]Vv2*Z5{MuVz%v&\si+,Lzl˶nG UȻzu\ɺTnK.-ھojǝwogK[fo2))9 6\u44}Nӣ?C, JB51QFYG^}@8!k.]RVjEt9ePEt^7afms1nz…ôp{-2  .V`g]?"ձ'ӜtttI Pz?Whj2^ZL#f!?-ַ۱x?lO/ eWir ԦAx Ȧ)Sq H5O87e:(!ovXً }5]Wv]gZX7n|ӥ.손mkyd[nKjn6*03mW'%YheK8n+xܴfk=HVݪ[VSuXs掩6$HO>xҢ` V&-uA]g-5)Z2ݷTV-us4U1 "s1HjuDjԒ)fBI@6Ps?7_]^:>| :KJJ x`<QQFYDyMC]:8"nI1&IT)\фfɃI-z%k]EhlzUL[}],W!'9耸vtە8B:!T$rMZYp *?4l(娶_o\Aw*Ҿ PM%n׎T Eu2W zlW c6c)G# KdR 2k,ƹSN-ilRںSWfs:h :p!ըvMUjʹMOȚQ>aIH{Ĝq d^6mCs-5f$ o _x#5Ե,;U)]XFkeuq(˩LaRKpS,"Q(X͹psQ9u*Te7DC"4o4/±c%!>CNIʗlBu:(u뎁Ljkކm;!@#zW1ke{QAU+=xR,O!/+g?V{XB_hRF'c,,R+eGX/HBBBBBBA/`1{|N _4{+w!@!@!@!@!@!@!@#85>}w]D鮓Kc{'[Kkp: #nC(o]:jp!&*Ԙf% D`!͚D1sըfʬżeW*ɛQ%z,ZR TvY)ޒDJIez1S[铣8hcSv%^6:+F,ՁԮ5Y]M0H!@'UڕhdB8 D ̭8 ])4RˠPt {*k$/E+:kZvUE-Rl_n8 Nt$)jP0Yw4ptc"K;D)zYҀq"Ohr Wo(>}]ʋH!"Ohr q/ͦ`]x6oy6gԵ%[GS4PCt_hJ*tMɍ(t4_'OKQwxChY L)&V_,M9}4҄|s4_#N;=!K6`~4dNϛ8TYKkj"o*+1GEs$&`ԐS$v=QӔُ('b\|ؠ RƬR*mnPؤ$dZiN}BuW;C6:*-sC⡠mt%z{MD& E|dmDuhK.J9ü7#VmLҽ,rݱoN_UBJJ9TAut]Q]*&]mv4D%O4߫~'Peef+v5וg*hsZ4Ko22ӅUޜJ[,)A8 WT<60 2 :ͨ+Y'r/f}ɹ Zvr) VK㥞mVwX[29 EE2uU+6_KVv]gqB_G*՝D ۡxn|Fܑ7\p4LWa kDէ25a󘖾H{:r$71io(jq3sP[EH-@ЫRfR>y9lMt q8  ƑaQaT"L)N@TO):% 0njCZ:4ӦoGn}^Z>ijw]&5Uژu! >OMA5@vgj"9RJn*:emj3cp{UlѰd%b$;,̱JzgݨiYݹI\Z;8ޝb[ *:kEhbp]5xm

c\m֩w"#ƊŴv%NTz:`a;6OK6vx;#Wᕏ\1I%B/dSJF8i M%Yh]kP()'*סG=cALӆgu㮚umٵוî\}uZ"s[WqߠPCF+S**|$')%ΔRњm!!!о^MƭײTS&yWR6\t&N}X7#swɷ$#nJ) +* ]_nY п6w+Smt] B&9ZAz3D *CrԞB'>ip!]5`~^<u \a'R+ جU\­VjK{ #1]B)HKpmƝbh63ݸy3]I M6m9PKK\@^H:i1]%L )L6ZciϑI4ü042BQ{j$u;D%_\8pb"-QSqʞٿn3՞L̳OoߵC&SAvɪQ (b*I^y)ՠbUZZaZjUdijQLBjudFa 1B4/^z& N+H2U^Ou(Ԥ3SJpU$ {z:byW- $71]o:STmcQJJqI`Q,6 g5oS8j]n$Y:ap}n=z;M*r,!--r*)j(|ZjОY.@#zW1ke{QAU+=xR,O!/+g?V{XB_hRF'c,,R+eGX/HBBBBBBA/`1{|N _4{+w!@!@!@!@!@!@!@"9_gK0Iw!(X|3GtB,}51%$ZJu^4P/afyKӫ4 >-{z[dڮ[i.]UԄ]>( {|ԢV4YHcf9VQfi7G>%.Z!r-:4(PZTҥ$ӣNR(INIePƿ-xU:nʹ D'TI'.f*iUr'T|AHib#$ӤljrP !!!@"/˽*p %k8Py4 RMKj=50xr0 rf?NNM\Q0$ _S7Ѥqrʷ2L7.k_+WYݔO.{oB';%*C,B{1_<.5[oB7;ղ[Ko*WEWzݫs"<%U~,.sJt2pи 65o#]iݷ}'O \o3HA_Ѫ7*M  RD{Hsk +̺d[n=+Hrç\vYJtiT1C7Ev'O JIX!%$o;m[R8m`uZq\: odg2Tr۵ *i/V!L'&"bh`Z,2C;gl`K7^YW=9vX׽vd|.ؗws9ۍYM"@zas qm77F^;n풽IVdvؕ%s;n"kjD[5v5u[U7jckݗJb5D'}!^eȸʺXʔ9RYB45v)["WQc"nVɧw3;Jk|I!5ku i$]G#Yj)Gy]ej 0IN.rN˼&GrixOJfB\RӘiSXsXP+Ԑm$NItڛTRmFqvg|Vn+DTQ$wv*( .#wQ%e#z^4/$r5'nê^jw.>A;]I%rG4KNӢS zGšJML=6CR$(kT䖂ܷRs.[FPk&( 5)2QB% j܎mY =ySq L 7 uܬFiw~Hu)vhIF=)G &'Sw+7q^۴w(%&TbjLyIVU͉f",++/ȴEK9x6*4q&.KNj\;bDv¸TCM"|eH>UTcCMZI j2e߾Wvcߋ*O2|( m60be$Ӛm~6=C`VvޭtIWi%K`-!֏O,6vVEeJ:Dw)a©P=3srmXyV\$sw*<5#- e FTe)%uKY=2hᵬi\$ +u@{&ܚ Or6OL~u$*D|Ζ(Q=*E IŔHnCA[ºW7V㢮RRlN,]:ʬ+/:^H.L<`٢$KSM]Ԭ׻mt Ov(\w? av͛f."\b-Rjd=:KS4]zEd-"3g<&MV+4vaX+GvTe) JFS[Jn*QClN@p2tl凖޾x8.չ}< 亮ݯncL8mݪ.F5cT %k(7+iiND(JMnBqho,:5o]`M7JC&.{Aꘑ ElcT Vi7_HbŐ!m^WIUh]KOy|@rUjbL|']H@cGT*}#XhOZ8p%ɱWmV׫PǮ:ɖ(QUP:z_ֵETN03DɧCgSB9kI_l.>\ \YwqғZ4aQxυTbONbŃz2A` \׻]V񀑚S&XU|0.AˈEouV MЋl,2"fkS.M2zUn 78kvYv=iǫfWrSM@Knń5c'( O'T GUe\,V9vke(ٜV_n|:OR˔{M\h ),x!ըA&]3eN&M=B"V.&#2|R.j{\ku8L.h LHTmqN,D% iV*'\ZwYu=-^굨p\{_|٫&A¼r.uzbh/v%-B fj;IW poK]tmDri_f[7o4!3u|a^PMZ,m#eF忶ӵ\GRRkFݶ}Ml꠰`7 -S"4*լ$L֔=X+kK[ >MwU@NT{*Mq&=^i>6ݶ(;m *2XZi`rF\o'A[pu3Ufv.SwxZ?Rs8gX@<ʀx&a&iÀyx Ą$6Qm&Ko" ӿ6艄cs`kR TZ ԩ<31`aaBBB Xtm"۷ȲL Xc(MQ!\*mph HH7C`G0i0ÀpC pnCo olty,Pi(IK.q$jkq|[W%$IQXΦ"2ͮyMGA%5]%]t aZ7̠I*=WN:N׏PFZKm1gVkUh_Xnջe\zAݭe!!eTTԩJ}*5-:D4@:QfZRr >NJiI@de͠x|hoQm8K0L2 pA]c3J3p˺8N-_|TJXCTmNIId.^EIE:)wB dq @fj}UBʹi:8?t;rӼtN.zw7?CkޝuwgPrZw*]\kޝus~CDYT+ܫʳWCW-;Ds~CD:FߧPyVp w*xrUN8FߧPpCUtem 4+#xޘB]BrUNYtB0u*妣>GNi@fBot`5vytֹ%Dt2!(H28ȣsڀKJq _f;h 0BzKl#YVX_yVN#U GR7V[vt$0,ݻnSJՖdiSq;-%zڨ-#w$Q2oαmՠ`RE++V6;t)&Km/\QCmvEOFP*H Z=9Ba5Xe=iiAJTm (4iL5dnjҘ:l+t ƿC*aOzqh0j-=Gp%eKДtZ4@$@8iysMD' G6 Bi& e P& 20K瀁SxL~?|J/i<   :Y3ss(PNa MD& ?ޔF]]w^G%[ːl:^jHvFm=qNVL_% 2n߳+.u au tSOfNUF &uZfE;AfA:EVu B ^|r*t}maW?[X %{C1˸^|r*t}m`'\" x.G%{C1˸qInK{@raMu/C+FZku1EJ(HonFQNigE6βqnu֙>%{C1˸mG ]aCAsV+O{-΢iٖա([Ucۡwԧ{o0 K՘xiKW%sSGiu=_T(3q/ƙR"KQVգ藘j]18pf BXu}̴Zo,`^C5 2D=X δEƃAFt7X7高ܙ`fG & ]Mz)*ڼAmV~lpiY 3/]w@fe!aCA.Ku:xZkuRΚ[ZI}YV JknNb;tΪq3»~u&vR'Ebȓ2dT סSpB#!⢃6h[{r +{.xGqL봛ĺmJ%HBCFa6Jn*2s|+jr9bC^*7kfdTP!L>y[KPe[(|ɐT~Ф 5YeOCk\\!v㜷/ŦYʚh+;#NRk-JzTk֡J&$fmPߑX%IS%R>Y-- S˖( !!P6d*NK!`0P4(c2J:;zPQӆX @9z1' UD0g16'@`#:?bleY^n2h^aŕF!)V{H9^ag(VAOóS!óS;9{./?,2^|;9{./?óS!óS;9{./?,29Xvr]Z_*5Cg/eեbT(xa]{tm+%w7{8,i $:a-89B4S/'TL"MR7޼ L H̸Mn> Ǣstu)Dyj֒"t$ *&ZshNFbɑq.f5X$̚HEpc9;a̢*͗NkW65KʼvKݢ`K31]F) t3\G<25(VѲ_cznXr/XZӕ6fti!óS;9{./?,2^|;9{./?óS!óS;9{./?,2^|;9{./?óS!óS;9{./?,2^|;9{./?óS!0ph>VVPauiXaauiXVVP>VVPauiXaauiXVVP>VVPauiXa@_]~~>VVPauiXaauiXVVP>VVPauiXaЊauiXVVP>VVPauiXaauiXVVP>VVPauiXa[9ݤ:2K -fJ(F15Lvv?䳭y\_S9imO{ϫAlyʽZ"XM!A<6pWUP 5 4AXgYzW.øVa,Q}UN8R h&dT:ݶ^VUZ-IUn'$'9^t1TDlvq=)|뚪\HӕQiסm+J\'Mk-So!R,-lzK g`_vZk&N cM "fæ^p;ExnKe%aI,3aN=9k*d/5rmDo]mR&Iu!4+Tb\j((NIS1U% Ѫ!0Rܶ*]x&Wi0ԝ^nTs' u<}0i,KTفeuqY[5l8!使|nv2o8׸c>ݤ\4iN7>鬮[Zu-z @A>h!WY;}ռ;:,t07 X\VKXT5-E=ND[j`%GmYTeuDw$\K%%4s*Mh*'TJC sՒC4) c~W֕?SYl}QɆ[~n͢gKjmLy()qO\njYi P;1Ik*/ŗ%+fmc$Rl+%EQzF!lņEMGc%p 4En2O t]Kh]h5 t\ ,ӥoI-Jm4ue5 RONj 3JYz@:e=,z gwZ wSLr޿Bf9^RfIp0HiӪ<ץxl9o֑2gВ7ҙp*nw"R2eYLl7%wbU̯9p3EHl-v:UdͶͤg&oj.{)mv۹̻Ċ^5X6#%v{6ɿwjS)E)+f]RQM7"*ԊAL:RW|#nٹ h6ӹٷ-;鼦Yr-o"56:rH?BT0j 9^7Pd.d!S3]lknV+aոƣL=ok.km .bje1AJҔwSu.>12;+r>lpbf΁utҿMK*qqtzrSs$U;zҰq@3&N #VʒUsUQ-+?fn-ƹ@Y]u#-6.fj'Ad[VܿVEF8[ լrZT[nK7$]|jvΎ0LyPt+ӨެP+QYTPlLH2 ;vmd_W^Ff=KhMĬa9֑k_óYs:)Yڧݾu+=iL;UFZc/˶xw909]tT Q6yx5t׭⩦EHtR hUO+Z@JjJ"jt cHEΕEH*VJ%˨WjuP)F5NbJTikR<$VBBBBBBBӛk?8k玡, ;!% "L x@W%Kmc6],I `y#h > Ƶb7!rޖl)D!zŪyh$OEuO%JJ%(ɦ[M8i<\@v[yݞ40ws 鸈`MڳVF%$bd[;4ܔP@|]$2m!d97Q}{;Mhtk/y),F}{;Mhtk/y),F}{;Mhtk/y),F}{;Mhtk/y),F}{;Mhtk/y),F}{;Mhtk/y),B`@D5D8}xqٸj˫g1Y4-ەoʍ7b]).dvBf0\E֮5zOgwieCo%;FE.ϻ\?ͩ]92Iӌ0_%@#'ժg͎<tk/y)'ꇳJwzwSӇ'ժg͎<6A[;Gdnvgho%<=]6yOjYc)v]:jgoNkkLJW.Skß_NmG Тc[;y~y)8ÜyI6sxA œ~} u22;--P2)nF~䳅H 0pS.QD fZjU$<}&ꃳIt86>? go 7:^#猌6~q9&S7ACHkњk\Osk# gXAۨj>#Y(㼗wn~#3"b@f;;DC]lJj鮟ۯ~`bc95^))n,zb-TW.ʥP=AATCG~ځ9 3ɔeOQ D54 Ckghhk%4G@m8 4`Ekp6viLJIۘC_;9pt rK-eV 6 Ar-ֺEU[RE'&Njj +^O.` |re?]N}N!6IC@[;䦁Y}PwvNђRތ ?2S<9 tb&22L&ZoC)]JZ󠳓U Cu(-i(=-)4A='ꇳJwI7$:њɔ"m>}N}^hzwSӀƺOgwieCo%;FKjYc)ÓzwSӀƺOgwieCo%;FJ;>X6lɔڏ nDL϶ўDlΆrE5#twQ'cΉZi4 NVL2@|neCo%;CY}PwvNѕZôYS9v?;s@MuVmٗVY*#Ѩ5n$ewrR^t d @4[NM:k>WI7}{;Mh@WwI7}{;Mh@WwI7}{;Mh@WwI7}{;Mh@SsQRї&WyHpTmT,d o)?Q̝2}"5VH+LVr  s@DuD:r%7[D̨GEGN'Lj>X 6j*iI4*Jg _[k-w., d5|LRam6K&.Nf *M(m'6/ [`XmO|G+-r$F9+xeM(̽-u#%̞GF0cGWVAVK\ASQD]EQ&prJqg OD4LJԌ3Jz$NY?$0~ptIn駎~s>jzmuﵣ+x6[d 餹-KoH]+͒nDD Jm\⢵"'Bt+C=K0ʙ8 l 5Rzգ^:Zޖju<3ԩ<1RI[IsZ]iL;ъ cW(l}jf ɇ8][mŒ4oѸU0uJa'͞=-JFC- Ui^B-9+ˠnW%']Ȅ!!!\Sgn%pZ[m5n,et !E-e5g"+ﱷ!i*}>*6O/;o M(} PII)mSmL\|v[vW:ϱn5l+z]Ur5Qxn 5hJ%MKQNqSch}.pk0ڗAys-0?Mْ 6#,9o8ʣ/Z%n5MO947Q(j;œI0o&& 7`O:F؛A-3l`E:ĐPJbJ`Zq KKzUSΥͬU;r2\HVS [Pv g,< t`-Ӳ{jRRj4*yjZ%;`<4"ٗJ5U++ҩBM?EU TW{zN(Szܜ?Ae 5 Au&@Ct%ӈ~cPsW&[щlkh{g\{h)a}4\1t485ڱi)N2ȔٍQe;W+~~Kv;Sl͍Ds[fgs$[,Vf vۚHNrtt^㭺]밿n] `?H RZK#4/p@uGͦ>xe׈u5%8x2;-됻9ߴϕ6St5eU\'mxHT*FYƪpjEUݘo =K%7˦TǷ毊%Um;.דʹ4MEoQ/TpӶ*n+8??6<GSr2\+ A"SJzFTLY'5! siԘxn0u wdGQuߛx, m.lJo WvllE}Finڥݣyth^<>*QNK o#[U!0BO.o|ƃ|wN~ht-b];kq-V2ZUɮvyvp&od٫pTqns4TQcLG0t 4&]:47&enJ@uf|7dC$SGsf^F]dyRff8˜ j 13c<,2kۑ8qu2^uNev(p5];F^*m׹LF}7[+2 Mɳ*ٙO c&6 þAy[|SQ˶'J߮.7Zy/,J#%dL1f[mIn$42Q- JNxܔi64(tzА'meޓxC!O"ĩML3u%JіC7IFz!0$9B>x!>h""FYt,hӛoX-BoiϿ4G@|#'gfg錱?g錱?'!?!=b^O4C!C`'c/_oqF2 v, 76sJ{g E%pE~l`{K2ײHeŊEu1?+e)BBBBBB5|;/\A/`1{|N!!!n^?1~;: 9?560)6]j1&d:Δo#ޛeYXˑ1QriHWV)K,+DVͱr瑷,^bNGռfR]l22%vy-0j*Dp82喌O @xqC!<|$* zuCxd IP@Yu;^n #Om)Vs&7VA(ӖZTFQ}ɵYea@7Q ]4DG]up<~zݗ]we]u5ώaDx89>GLxy|$"2yt%4U5#8>V5IMV@RI'vif`ЀըvZ+SVږ#Fڰ-m;i\ћnu,RF6b5Zg>Yp P{8#,0QpӚYwCQ 45$RJud*O%Zu% 䞜Ҍ<pa7}Cc~֠|i@9`) ZzkP>pa7}Cc~֠|i@9`)yeFV7Kq]vѓ ݚs * 劗Ԟn7_Û2M51E&Gt*ԥ,h O$k8Dh6Ӧu: ǏOPQ(m^eۻm6XNޓg, jPӷʵ x-69]T5O^Jg,/b tM3Ϻ*YPi@'yM,0K4f t>ӛoX-BoiϿ4G@|#'gfg錱?g錱?'!?!=b^O4C!C`'c/_oqF2 v, 76sJ{g E%pE~l`{K2ײHeŊEu1?+e)BBBBBB5|;/\A/`1{|N!!!n^?1~;: 9?BBBBBYf 5Q 5P>y;ܱnb5Fc3OVTWa ֓-iԦL[&x jM$KCx)L2>Ya 9q GQkAS;=ڕIbYRq1*TFIxܟsP`zqnq]~OD$t᠀(D& e":k`n:-f!#\w:,{:*p:̩leKuݷQ4kSTUkUSRZѣV0ܟoC؝ vo]7|ӟN<Ít CMC j:?̜à< !ߌϼ1,x2eN͗-Vȴ+טe-Z.qM' b{RS}=` (&H^,Q9/E?nTc2$WX MlY T"Lk&Kr6ۋ<$ôk Mgƪ5D ;\xrmMRcuM^Viɹ]F^,3 h 8T hQ2!"",dzf.E6k1U(dk9Vq&*:k xr9pVpCsM~= |O9VsH\xs;̐ÞPg>d= |O:!"k`," K,($=aHjs }-;~xnf;Y{9lZW._Ҋ/%JSҫS>k+fIU@Gإ6X;:#ͻ _"3  0GC‹kRr`Y\[AՇѭX K)Sv9zSV`T@:L ii48!0#4>w;AYϙ!= |:T(먀uᦚÜx0i`P<8:\,0Ui9vW {~3J&M \ƓQNUb,gq&TImdw9 8TQ= p<=`[:x'PP5:<YÞI"]=m:V ˈOyjG1SkV bb)J(ez6"P5Ot'S P%ևAyZ H5F_w&.+YO$ zE& qiXT*Qe5l]tҔ8BsjTYfvJ7nI0U-Ut\hy.s3\ڋ0FkG2lcJhԪBjs 6QN%',E< H H0L鄹"(K% )zRI:rKvaSQ+(#R`IMi9{BBBBߚsm >Om3v2l2#LA9z1'@9z1' UD0g16'@`#:?bleY^n2h^aŕF!)V{H9ִ?NZ 78]")Lk!J-#K8PDo{o6h-"JQq'>{P yC@@$%k#.3:+8z6% sS0vxYPjma7^!ZaIbqe.QRj7ɔtg Smzה潌өZe'uV0`5FLWSz$kj~iuxj#iDGhaӈ;#(2GBBBBB1h{,&6NY;Jz吖\͕$5SVz JYdnFYy䖜y'1\ҲJ9/CSoUUIviVa|+(HڣKDw@%xw 3@D+O^ lr==( zk`qx8Z^%#5Wc_+珶&⠬eH8VYp i~uq K(NcQDvۢi֙5g V$˨Hcjvyr4e"lф{ UWr-]!Xo;uN8YD)&^cmxS^#5 hR "!:st C]P0qyzZ~-Gĺth0hg Y@$X9U0Ҫ6b2\)ZO"2qk%QCx̷͖% Zs^ Q&QFiDy䀫6Ͷ;q=n޽} Xa]6p%52{\TE76D֑͉TH7JneXp̹6vk.[7xD~]xiJa%RE-Ҽ +t8pK(isp͠ypFPMC PJ;LD-3ݎWdh[KN.s,vўť@Jz(p\!m袅\:vc;RqVr `s&Jn"5ٷ-lwL6}o!m,`QI!((Z`489:1UQœfU7s [ nux7w%U]!}akkozrFu%Ç W3S- '/[z7ǻPf$2 VMːJNWqÄӡZ(GM6At"KVtёB <~h!upK(ט9u`BBBBB~iͷGc,O!?7@gߚsm >Om3khwtXpkhwtXWFt؞'!?!1ke{QɣzW9X=b" "6sJ{g E%p@vQ$b~V2y}"TyI^_x !!!!!!icW L{r'|_ίfOn^!!!! fhv??c*D1# 0@8spwG085P9^?9QOwXI}r\[DFFPitnYLBMxE}qM),K~Sg\7]`JKW=8ZAJ|^iƙBE- 璣gObDgtB+gObFgt9)t!f@X$")t!fCgObFgtB+gObFgt9)t!f@X$")t!fCgObFgtB+㒯g>vt]4#ZCPqY5!%fmD:, zDh%D(qDCMټ Qe8J ')4Vy!'U*lQJKXje&g O$+йvi ̝"ͽ)\ ai nȶ.9{ וT][4~ˢ]>ldS+ qIuREC6ƹ2]1N4i,XSN=D))y̔7JZL"en`K:#P5:DkQMO >:`+6su!:6(qiX<"eFΑ;;3?J}=vw J}=vw؇gQ`؇gQJ}=vw J}=vw؇gQ`DxsX+m;xbJ='::oL) *Ki k, 1<.>-;ݣ*? usT=l]v}7Yr(inZ@t2VՊ R_핀Dt P𛆺>w^;Fw핶m9NZ0*Ko\(Pi(=VKVyh¥P& ]4G@|#'gf 39ed'F5s;:c,O85s;:c,O@`#:?blOXWFt 5(dь~]+B R1Y{‘`ry __9X=b" ;Eu1?+eb]{*JPa5KTxW ;q,J!mK./[hm;;(^2Ď ת>R;mW|'(cZ]P,Eec2x̄4&Xx ;oK3x4@9\nz0GkL k*)3;mTQUsriiY9h=%rKeRpi}Z:u@ϚmUtUl̈Ayq+vnR WeT^^D߸:7O<2qZ8]HYRLIo"*DHd򦈞٥~<^S^,76PjMNs_+NyZsўY'mG\u^ѹ6TvLV;IyjN^s)еyO<=ЧP (RTX22fj[]_-c3C5n pk&][Oel]z55: ܾ01c-W-Wo!d32>vy .7 SfdM!kQ6(Y&ܒ+tGjK̃7&a;Mu&QޛxXrs(uN0Mu Bp FmBxC", vk :*b]kn\3uYƆꪺv۝K(DF3%V?YMݨaB&mo\Dy#%VE"+KIk$8eCKvb᛹3M? ٻ !aκ8K FM|#:yuwYBP1gf*kkc~v̀~夙6r#\E..^%؄mN(\YUw_[ekܞa[|B#[<ɦpCk;5lͺW1Vg3-^p0J"IiL!zNe 0q7d26@CB#1,xqQ2&#ӆ4_#/M0i@iH eQ̂?F(窋)=m̂чX( =TYIndn;C)=m̂чX( =TYIndn;C)=m̂чX( =TYIndn;C)=m̂чX( Y'j9(ONvԳ04& & !˪)=m̂чX(:8r)Vw @MMj3ר0 kM9ɳtjXI4wFzR$8ofݞ^:-95P پ3M5<ԪSjkT/^i0KPZŧZLT^jkV3Pi'sӫR`+v2l2#L}6>2>[?̄0v{?Lev{?Ley< gG M*y3Ώ=czW1ke{QAU+=xR,O!/+g?V{XB_hRF'c,,R+eGX/HBBBBBBA/`1{|N _4{+w!@!@!@@ph(O7QBBBB_Ȇ ʘ1# 0 L_aȬks[b]S-g"?$MKp_*n F_GCm9iC n[tp:ЬWG\u9iNr\.$f~{7oj60_)-Ӓٴ48`4'yut5 SBr_`=Z-ι e6Ej7 &u]o,}'AghM4kB?(|q6i>zӆA$JD@җ0O/ g7$ t55xˊԸf& x]Tʴ36IIT@e8Q=Xd)@F LoWj՚MA 'u QQcԋ"2ѤiDy$ RnvWםK YNsu¼Ywuק $J+Q$\gVriK%j2IJ^7Y=Q@  zz (NjT ,ە*I%97&^: 1^xmpoWFYն&=ۛp0QVr^& mi/(}RqH!BMYp);abk4P-'wZ Fff cw_i-쫴(k#B F;eY/я\F 6ChǮ#m{}#Oas^Y`WV[ax]/QI W4)DK) IujkP)DÚ9;x=oVű7zrwR Y%dY+XtR%2\'1BZK I']s[я\F 6ChǮ#m{}" }Z"Vԭ$ \"ظoˆviM<#ɷTb6/w%{MQ4QJ}$wnD;5 l UڋqS.f`Z)fs~3w ڢd2QH-ghǮ#m{}!c vd*NWNЪ2ehl5g<<(5K$ U[S07{m#֒;3K$W@X%[{߂D]{TD`rt.-o;'g6zk{Jx(ng͓]ԈshǮ#m{}!c R_ju-1jUٚe5PEWw'<4i #7 P8{ɪS"IgE'm6"ypc{fm{S&qdoOnUhdԭ 1+XUmя\F 6El~Z|kٙX!*[ 7eУ#7y *UhHөXDB@8KU=g~lEέ'{TP or_m7䂋&¤ԜOv"U+52Y`?mя\F 6Eq7e]jǶ[x3NcZSZ\ŻRҿ}/uJү8-YM2j)f֑=R30)r[[m/#L6 Ut#-;X/5Dڣ=*UU$=Ub`XNmя\F 6G,Й.=9#z]qۍ{˹S6ԍ("VbE6bE\SD35Ё-WأXi\gcCv\ (F)ײ#BSni+-]=i%1WN1^xmpoVz׌x-6 1d1Wʹm7 )RmOg=J'Z{nNDe TIxcH/o:_}vȳ/kb\ƙkM˅A^jȷι@-XX*Y9p-3hǮ#m{}!c ]Vm5Ѽwr%1Z޼uA'g6] {d2 ㍪mcs 눕YIXo.'RT=3}dMeZfSKMfxO-Or0LO&@ƁedT)ԩ' g RIiM.avVa7`ݦ#`Xy܄Yӽdm۴\d3aL'rbh+naZ`Z[нvmkC((ї T%*E՛NgmER H 2뮝p|ӛoX-BoiϿ4G@|#'gfg錱?g錱?'!?!=b^O4C!C`'c/_oqF2v,}7g?V{XB_Wq |:V{P󴷥BoB <6h*s28s!8q+eGX/Q$b~V2y},Rjw=^_ W3^bBBB?1~;: 9? ~bvu{4 r~' 5Ds!'@k/oC?L!Z` EcdO+je߄V6@OI_RX_vjNTw-Req:u;Pox,T3K&OokX; U;a}-mVbjsc= 5V:ԋID%e1\IS!SԚJjܛr6rϳι%#QEmX)(P0ҝ9'6tNN.iA@Rl 4³o_Ό)q9ㅐ/YL[p[Ma>ݰHQEHjsdjY\OP7#l&8åe^d։WO>v)q,MU"wX8*^P?I4bt+W4ԩK,DG31&О2MVIjOVpeJJC6Jk!4i[g}MAN6 򅎝*w:P4XO)TBi-W$!6dY ԡsh z%SIKgUt-]rPq٘gN+;يt-%5ddej_^ !!㷥 7utRCp4CQwe@DCzje&[3 zWs yl1EDA]2]  ̳%*jyD1:JZ4d2z#l|Du=qD%2Xlh[ji\WzÉ^ݦ8ܙCqHNW;ضn78tT5Ron-$JM\KJ'.ugH׺׭M:jlVHi zp19=4EÆkse#u%q1ݩg1$syZ. QYlS] *WK6N"XC5l1-lSwe05Bwe2(Sz"me$$SRdƪ|O#v|{;BNC128- E"BGj%IZL5'V]>d e%,t+ifj%TKd!'252Y@,n@K א* 35٬+<6wrlz"n[ҽgC]4arF5ZҋVJ|lƻ,yy4Kk#mU3R@Ah0(|ćIYE\*TŠy (;.nJaC)ٵvܤݻXn\Tz: Ki;o;K+rەkۊhƗG g/R,7ᆻ Q+o6:$n Q'qEjc5o#3qEDiPSI6PN(`a om.\UzQyD .<ųN;JגiZf -s7S[!M&JRie66 ܓْw.wKu}<{4nBJTfƹ:e8R"`UtziզxL}:_{6tJ2f.kTʩSXU Q a9D3d܄[sf̱M4C aAtyT U %O-1r C2R*I AeKXMԜ.~ 2\#h̻JU jX.!uZ7.w8Ah PVtr oRy6cvKNwkhZPjKlok[}n^\*EQMO:jMTkt:-J|;v`I7]WEQŧ=WPQUk] J L:F&VᨣRi'֋Amt*nLEǗ3C/lw(-:b׬%.=EkΨ\7+jyE)NL OB1RZ" =l+\}u ԃKIA"eg7kK%YuY꤄c@hql&ղUS8JP2=ږ&bHR+DR$% e⧱6AH^4QؽW*-p:5n-?T*h)V@vڍR }B!iVn,u"CmEUkUmFMh:ۯiYncYɺ Hǧu ԁg-Ȯ$Js-eYFsSMē^`g$HQB@ blsp\+}vniRuUxc͸G@d#ʅE֢rYBt"hPGj&Tgf[٧hd[VuL+bZmDi֕ϸ&K@DsK!2*VN Z >GxYQWD a40DxFYD| MeoU|Wz8aOm|)ʩYF6"ʬFvHP8ij4Epw ŭ6>mYRwP\Lh'_IINQC)*e0Ddj3e%]r4qo**ɍM%eTe(1ul]#2уl0k{DqrK, iK1-L2>[?̄0v{?Lev{?Ley< gG M*y3Ώ=cxSEJM77˽醥]8騆qӛdq^?!@Dy5&#=mzvu)z1kLjwC^歭<~Ѩ.lf+q!xmѽnI.+GU߭2CŒX n$z!p.<ϥƅ=EXb5wCUZIV;8+s7J,NKRYAw%=&[wR]1l8gti2WXdVtIYTyHF)2f&\(Vh˘JaBONZj k%QdQ+ר#PC'8Bb]krQBGk h#)΢uy:!7n RLbsS]kTsJU&ϓ܌n:ܰVv慂-l$"uI)un=Cę:+\gk&HtvF(GQXG9˷hh@OBբuխ ųkS-2< lw+58B&irE!M*AT,=Uz5_$1/"&g4T@f#ݻ®A-rNs6+*]J36"?7Yzp8xP!*D8ucm]#ܕ~H/+&YfdƦ۫6׻4n-u.*?nL8h/fMIW/B51u.izG!kVtT6*_ 9ם.nB$V 3YHO ѧF|.p*4?K,]JOz fgޫ!9LONKLgZZsɼf@] zTrV;]+HM֑E5N:h2Nz q.8S κ|N\) \.(_E헮 u9 nS$jgQ^@ۚIW/*ʦ&}ymRȻq& RR+2.2jɉgk:2:S:b y0?\"5Y( 1vxe?sc}^f&-i6,ɏmn!u`XuLɑ G`CY#WI=!Z`s=bݻ9,*{ 9\`ѤB0&b VFd&J\cr6 [i*إZ}_$Me欨JZb*&Vr?" -!KWRXK3LGc9^m(PpV(ʒSPMPnz*ji̛ZJR%jLq`E&<(,(ɓM*G:L#uam:= 1> X9*_Yf! 1> X9*_Yf `¢vqJ0{iqaPNm|d;b| b>ӛoX-Boi?_g;C_2_g;C_2D h B3&y< gG M~]+M5( *c)'eU+=xR,O!/!!!!!!! L{r'|Ag|;   ~bvu{4 r~'@ph(O!!,BQR J\5&Lt7tbkRE qr"2W3IUJ)IH>#ѥxLi(#P @fxe]Bm9:q"" kkp $#M?:L0L zI@wu4g&N4c(RR9E QILKr,:1sfhUT5AO!!`xդj 5Ds!' 7+&UpKO\ԕv/]}ՓRVN#C%n@IDq5vL'jګ0MYF.n})MgњķSgV;<בLӲ hwnkθ755NR2R\nj (K rT>LE׃eZ}?]mF5z*iiHt8֔*HDBGK(qU]P邩 4f$CŦuPަӱ,7j}d%)q[ )(}R(t/zb%3V] rP/Y:Fʌ$Jz7VGͼkj }?;Mn؆n#PrqQaI t~D@u`eM#!;6 r9n>'tWmU`< M[bZpO[{t*f1BYtLaоaD9䫨sR0XT" L|Aà(t: h!cQ<~OO}'\" rW~|-Q<~OO}'\" rW~|-Q<~OO}< EFQo.ѤĂ^+#5T+Jbٸe"7Rʲb:[qymi\T*HaKl8zf3ZٹVPKkukn&,*d -V%&I5p-KŖ\)Śn"ºp r/Yl\V^+|j)5ŴN|jxйET;hI"xf\TnJO-Kgٕfr׷f^Ίr`F1h123j9Vq*+QQTUKV]c\VMU# 5wbz`A^ir#RY\!q34eԩTJ{\}rTnVKf?ݼSqڤvdT՗_iRNVʺkiP-%]oknMFk=VFv} 1!0R5ں־QN$UZ.3^8J&'%5k0-EѦfY(e$5Ki;oYoqT !r6[M.Zfk=MsVQk1yxDoʻ1ugJ\@io _'@On^Lv8lu%7\ȠJTn6/4UPQRDK345cJPtn3ܼA9FʹKjgWJe*X̺YnOcU+-qҎU[%d(ѻo,gfQwrND;A_&PF45;XIIaH DwZc)f r9t:bۯh13bob ಽw{5nVmnTrLkz+.66:FA­GC}Y$-'Ic(䩜Zr<" -W(0D>𦙀8=eu8]~R2:XTt>ӛoX-BoiϿ4G@|#'gfg錱?g錱?'!?!=b^O4C!C`'c/_oqF2 v, 76sJ{g E%pE~l`{K2BBBBBBB5|;/\A/`1{|N!!!n^?1~;: 9?BF9K)pg)B]ABaujlδ5D\*( ң%;5 5W ٤p ϯ[D9! @xɼ\ll_dRYʼnR-\Km2^=N%+ۆP}#P_ ĴZ"Fc,f\r.Cs I8onm$D2:|5Mrj,FenؒxGe]OoiYiFQjԒ`Qi'i&:O 8p`4mڢKmWFVP䯻W+0w@vsIT%QQO .ey9 _:[؇wSkĝfe*-?m2;5}ƞdsʸVU%P47 ˊw)LOmLV KNf̩ɻ"䂴Re7\BXӶW2&9) Q4Slz\@Ml(Á8zǛƗ8 $z*+)WV,@3uöU5Sg11.VŨ.Xe(QB*4)SݧF*r%9i(I$.ųq25;Ǘ? _[A"-wsaV 6[FU;|UQDHu')ZyUC_Ǝ*8jC1>ID4 wj#<4Nii,! >T k=7?~X?@!@!@!@!@!@!@WjF.[\)Jbɘ"o$ -X3ݧ*2*R#km$YD)rS$UVg%iBQyg7&FigypY if`ט&$ϗ4LRI-XfHd†iDSEk FiVVRzs4\lKvb5y"DA m}/$%Ǚg }sW*KRIp1]ķVC غ"՚diFni旀k@D7iu2yjZJr֥V=*K$MN,$JzH@x˜󀆠 |n6w N(i0ihkzg4G]4::ǒGCAXx @C}#@xFk< !4 @#,f C2vQpLFCU8(c-ŅEz` +v2l2#L}6>2>[?̄0v{?Lev{?Ley< gG M*y3Ώ=czW1ke{QAU+=xR,O!/+g?V{XB_hBBBBBBBA/`1{|N _4{+w!@!@!@@ph(O7QBBBB_Ȇ ʘ1# 0 k=7?~X?@!@!mDNt?x`9?Yl]n\ǖ+'znRY7ia̻9^Tr^+arSTۋZ [t7 6OlBcX~CO|IZS|֮CZ)7*bZPٻ4c2̩DwZdE)JZ\-3[qlvPש(u @1姲gmNaV7Uǔ*ӞYV0Ju)גiTRnZK8MJi f)ZۂngնcR%rG턥 V=ZUi,zZ5)֭$f& >]5tZٜ4AgMɞ`BZT Ц0),N J8[kg.W^8\̜̹HI/FC]۸)hN2t^VLQ>w\hQHĂ;K(ZL"eAm4Ɩ"z.> :ieB9<@@:[Ct\q fxGPXEu :u567|4a@A:J"iDt AgSKgP<4\ptAx-wA`,V[6XN,|"u(T ҕN(&xO)r7' tGOz]< q!M " ^@X"y'6y46jn7(rNlhloP%nxI͞} ڛ<`bcfC.X*bBn!jJU%"nJVE3]+`zEU 'W ޗΌ}4\8VȇKLs@Am~R2:XTW!|i&댵M(׬C]@xG^<IfLj<`+v2l2#L}\$mEeM186dyVTI$Xr/**k)Nq$YJ#$LF0qHc*1Nc[ﭭM.3nB>6iFm5ݛI@Tg錱?w;o|1\oFS=.UÈg&T2DJMc馍E8? r '!?!=b^O4C!C`'c/_oqF2 v, 76sJ{g E%pE~l`{K2BBBBBBB5|;/\A/`1{|N!!!n^?1~;: 9?BBBB9S 5Ds!|D]S-g"'@52{o+ '!!! @f5θʉڣơYvUx2u@ٓfkOJVPFIlZ(gFH]&ZdKIr_,p^[#nZGi/m)Sth9BU9\&T67٘1K3;aKzݙ0꿼޷fb/an#Z謍.ɶq[m3^wq\MҢt [ZM)˪%(;hHk"2z9⩵꿼޷fb/x(;s`2'/ joC|9jzW/NE2GԲZ%JSjSNif&ߗ{8׌2Gv\ P C^x|p(qԞfHLfi!KZ_ F$R %(A eI"dZ@#2l)w&MRyT ȺU]Ѿ2oMkI<8|nm~R2:XT,m|NAk+8vrnDbKEAWUUYlQ;Js5iLm!H!Uv)599[-1tH49K(4!v}6>2>[?̄0zYtɒLO,3xy2䉖 խSӧBEVi i&TFja@`#:?blOXWFt 5(dь~]+B R1Y{‘`ry __9X=b" ;BBBBBBB _4{+wjw=^_BBB/ۇgW@G'xD_ίfOBBBBDhvD0c~T e`0 W|YlwL BBG:M}DTl%w 惢Գt:Z6D˵w%H*Ah$F/:%UK'קb֢;+{Mtw {0$qѫ^;:ݲ D-S|8E;g.M98eC[i[is\+LnF[el\2AzǥkqXRZkO+r#iz׮aW5}\W;Rn :uHH[JB^PMȗzW:ȶ 9 iFc–[87X|hq*dSI$4.E%7CPM"ڂ\˹v 68 6kXˀk?# S!SmTOc MXYwVߥ+vޤI&$$Ëksu"m)Hn]">U{vqK͞=;1ļؠ&E Iymy\b5m\7m粁l$qqh0&ANF2YtZLJyۀ,AEPDۓwmyr mvuJr0`lDET+=2 ]x7b:kۉ8raٴ1ҩdN2JGK)}+<~*/8bZ=}[u|3]w7K"\)o)+J r4a<91~.+em.:3Jv{vmr mjQm4$ }]Y&\QN^&aI(-[̄qroԸ^V3uϺvW]`miUL' jYƝlݔ-Zm~"\RU+i,6aݠ7kĹv6!IVTEE*N!\V2BO~($SK c*"IiVA$pS)( kX:JM4xѸEm*REe7U,3sfB[]Ԓ [ ,*DJ_D(yk䓅ȨS]qiAa,*\OHr&jD9UuFCKRS-,z5;b Yxj-;e ݖm<.olѸ ݘmn,mcez+fVPaw{{bk!a\v1-RR>d)-vBq B՛I/@ӖOD\Ui ]tq9%,>A꿯㘵E5E{2AN[`{9ifmfSA*KHk5ލ[e̐K6AX%"Ge-(,ĹnWKh-ͬyf^ۘ^Di.vci?3̻O&̒ :aCM&BQ8a&!kUD=.{u-q]Mun7SS 8|ɵE"i"Jc+YeFZN#( Oh~:瞝IxRIy7Z9 42/na Bi@D"Q%ZdSTULKQNBdPRp 9)@2d Jb@+0Ͻ^uʜs~/3vqJ0{iqaQ^8=eu8 }6>2>[?̄1~iͷGc,O!?7ބ!y< gG M*y3Ώ=czW1ke{QAU+=xR,O!/+g?V{XB_hBBBBBBBA/`1{|N _4{+w!@!@!@@ph(O7QBBBB_Ȇ ʘ1# 0 k=7?~X?@!@#@@AtN8}^;Wmb2[%g:YDBfuHd'9dRiT+<'3>5oVpܚi ɇx%2,Z2D9%Ye?:qshDŽ8VCC qcC @GQ GGO;}|1䞆_jzq?a7h|P~oX·$2P'ڇ^xOC98🰇V4>(= >(nIe@W'jƇN<'!Ս,+rOC/z}|P>'7C5xڶ/02L%׀B/( ~oXDŽ8ڱEnIeCrOC/y8🰇V4>(rq?a7h|Qa[z}|Q\ETRpQJQ UB*-cuRV$*Sj`% J JcRYq?a7h|P~oX$  4$n;tf 7^3(ADCYi? ƌ(M/>47@f 798🰇V4>(ZX/SݪCP*I6Wr(+*aX1& u%ƌ3WPi\j!*iZZ?E1 CK2|I(9j(Nz)7DqUZ5Jʄt#iʄj"pQ(/^*4e%CT9^zI< RIG @f%@(K(hI%PYBYePB?w †K*UJq" +jjIE:z KHz:>e@53w"֗v[_܀xݿboc }*_Yf `¢vqJ0{iqaPNm|d;b| b>ӛoX-BoiB*y3Ώ UD0g16z2 v, 74c/_opg?V{XB_W)V{H9bY4G;"1?*`'!@52{o+"D^S-_"]<75 BBBBBBBB׳?Rvr,Bp@uL  2ɽ4 VQ-h 谙m;voR ii%9\+ԠL'&>n"JOަ^]rL9뜼B+:sr~4}Suz/.h9bQOy`:[.zvǫ`~kl [2(4X,aQYiv%'*\Ý^˹?>C:sr~4} 72o+vo ^1i,7,R*lp!F3_TRJK%=Oew (ȶq}#-C>n/+DzIVUIn6$("aeKW{R򊸖I^J9ӈynֶ\)Nǫ115:Plmci^YjJ%֒Ԋԧ5buMIuSfCsm$lnh3iF6QP;vſ2J(@ $$HBӚ\5G\&[GuxVcgյЖg(6\vx_*kaDVu y«j%i*V[d]x ù 7'e{)d ڋb]܃Ztv%7fܘ{ Vv5,b&EFs/%Pm|AfqIo&I iӏViD$ G3̥89UekRmvˑ+t\D]A4ցN&á~ʥNכ]JB Mgn($,Fbt-۬:68#[Sren7.#q ȫoں S%+2'iiN,]GiI1Yy %<06H vaί\ܟG ,J+/izGeL1gPq䝍!/@Ϥe2d˻!#ҵ]e;kki{;HoWWWRWtNs' M49K͛я~\ߋ?Ìݦ"26:ͮØ8n? Ū_Y3@A]~R2:XTW?)F?ɖ=o:ӛoX-BoiB*y3Ώ UD0g16z2 v, 74c/_opg?V{XB_W)V{H9FGd?y#C얟hoS c{m0~C{!omCxw]G:g =]7:` -7=xd~èo{k=S c{m0~C{!omCxw]G:g =]7:` -7=xd~èo{k=S c{m0~C{!omCxw]G:g =]7:` -7=xd~èo{k=S c{m0~C{!omCxw]G:g =]7:` -7=xd~èo{k=S c{m0~C{!omCxw]G:g =]7:` -7=xd~èo{k=S c{m0~C{!omCxw]G:g =]7:` -7=xd~èo{k=S c{m0~C{!omCxw]G:g =]7:` -7=xd~èo{k=S c{m0~C{!omCxw]G:g 7,BXkEp\vUq'K& LEؔ%@]51fXeX f 3J:hH>I@Q 5ÑEje.j6BiFYJ R F=&%w7`0m͹m)NmldEr d,``XR*afdSFJT&b1$&T 5ІI5:4M- 0R Yw4f:eNm|d;b| bMV^nO(k ϮQφ1)u9P4:h23(: t#6?yt{ڀkxpxk:p@vy< gG MˬS$ܜ5R1Gm`IЃZ*uYI4APg*s8+wKf$-Sϕ|j.MP{ M&+➔Q`ʍZ)G YK5(drN$ ]uYޔ@D&wFiu@@FYtYqǯke{QAU+=xR,O!/+g?V{XB_hBBBBBBBA/`1{|N _4{+w!@!@!@@ph(O7QBBBBBrѪ\Ĕ1NzFK֥-Z&(էQN}eFpҘMN}ću]S~&U9]Yߊj5bcHZԗZ鄌1zIpY4mucL"}GJ  Q4p !\41,U2{suޒD؜ݢ: nkjqE]ӆej7J{de0 &ttyƭfFn!ەDy5`2\2Z&9Պ]I4͛%RI_KF_Ur[O:oo?ʎЀ| P>oo?ʎЀ| P>oo?ʎЀ| P>oo?ʎЀ| P>oo?ʎЀ| P>oo?ʎЀ| կk_;^n7Ct<*> ̀DTsWR YpU K=9*QVS2H 6f)jN3H0eGjz5릶s yѬ'}VWU6 \ 5iܚz垌 I'PMn7D?Еv]2@z S~"2 bMOcLugq`6Eg bMuti5 EmPZ]sW!SˬvƩxBJ! |'GYӏQ5xj!^-ڴﭟXGԖtȼ5RmUAnBS t[d~5!,O۔X ҒJT*tN0 )ӧ I$$(,GidQyϞ:#u sܬv 8dk _"$/Q[ ʙ s(-2G$KTJԎk4k@Mc2w&f) _7RyW :G[ʴ@ͭ["&'k tv[qDl(i0LxЈi~j>xS:(R+w6Yi[fٺ4RC7IFiK@tfcTR^zWVۑ2ўJH@dfHSP^4dRjk ) tl20㛺`[[yo׼vv^.e6w5;OKNdd$SUPu9K7mu)@:l\c@ ,]Mfvb[IFk}!CtdKXLK#F]DBR%(PN@AN{%Yԏ|}q\Ŀ LjŴn i\) Zʒ\3ě:mqQ%pG_@׬ <۩zs7V|ڳ:קE1 1%gKl.:65¼6&-&_RMZbrkqHܬYV)٦ ۘaxD_I eT{~"#wM8.!%nS *%ԓ{qlsD< !>!ě#mˁ8#]?03dќ͇,sm78ЎkiawnW#\Ozc1{{v~ڣRD,iz.-ج%^ECV Ͱk-cID>O[G&.Fo\XBM$_rjsihq=H9%Z"T6d-kgٍiMh ٛj^kl!'ցNS&TrPSi^^S?Jt6#iPY?*[~TbvruM?ܸ1xżit96& -'ERtop_REr7ľ:::Q+8s8q<AY%th5E\_՞H˟ټKG>\_՞H˟ټKG>\_՞H˟ټKG>\_՞:͈t+@"<ı 5:jtן?;I:$9f,޸j"1kDR&1͇B T3^drI*{1m.Qk.F9W:mwJ)1y[;THUzKlߕm1{9B&G]' NcaT Pmb<$I+0MF`cfc8 T& LԁLlRiaq/ LMLQ{חgh·C#9ٛ{5)iaTcqDʴ' z^i&z3X 8k҄>kt/6u6.48^:R{DѺ\b覻9a.h~Tp5LVXS6SI7fIgYTm1gѨ,q916UGuQhL(Ң13{!eb:d4퍼w KZ|<4܎{„fi$+Ee|q )aĎtP ]&Zcw%iѽ S Y5 ]AVPr%"LF&jBr*R&Κwտj02iR1W[:u $#(h")hRJQjѐң[ [7KvpT +Vn8.d fSn#1M.f9\q"!sMl}Uܛ{3m[1Ѧ$M6 uRZCe>r-snvK *lbT el.j[rmuįZyPQHDH#P1Fu*W(2˩[݇t+imO\hH#Jv]{- gCiu _PztWU^3wSO&g07UfuXŢq6kk/Z ' S#Z\l|B7h&Q][r8* d)8)xi ˢMVz5ӫ!Did捨wim~7ۖQxj\ӔN&~ȤF$3)S cĥ'B"(2y 8x͠< "=}C^a|9 f̸vV5Fy`zǸGv5W+:i.IJ$|4)BIBm`Pt tk׀B~iͷGc,O!?7@gߚsm >Om3@!@#zW1ke{QAU+=xR,O!/+g?V{XB_hBBBBBBBA/`1{|N _4{+w!@!@!@@ph(O7QyNj{z5Zr:(j=`CQJDj2zS $ƫ59]e[\& PCA8@AjڙqW&TU*t=瞍imxQkL6n! 2ͻ8 XxvO3Q>_m:b~$vQz^жZ-F!qΕ|RTL uU:( X@0kq.eDW&CnCKȶ6weem%QʀU hald!$ KH0>V=Opcm8,ej.)ٴTSˮPEP]~Z m UOH&j!eIm4+Dbz=rŤv<ԋ&;з|M]$EdU(P/F:d*mIHNj>Z7b mǘ/Ur qr+)uzsU*hJCT>ᨇӀ`+8vlϙIS -}GJ..tQn;IL %e4ZTP&JP*&jPtHm;^a׶5ꙶjZi[WȩRԨԭ^Z4d 4h0J!kBDDbh,Z9(ѧ-9%@Ww+w'8rg~}wrg~}+w'8@Ww+w'8rg~}wrg~}+w'8@Ww+w'88Ñv6ju`XTÏnUnD{Lr>A0Ғ6T)$4s7zw^;y1{\v'N?[mzB &=^+gne( 4TJ骡|[g|Zye^It)OExہmSJn6 (Չj/*'g݂:yw?*U zHG3g6fd>ڔP (|uh\3Vx$}1Mfmi8iwD4/T :{٥!4y!; Q;E{aJZg=s W|F^⺮+,]H&|(+#s+*N>p )Kҭ=1za ue&3L:fݜ`Pt4I'aF`֠ !JJ|&YgDd'j5Ij=R$$a 2>[?̄1~iͷGc,O!?7ބ!!eY^n2h^aŕF!)V{H9 lBTԛ%fun4/0J4Hz? pa]̼=rOxj-z2j;8¿l.X,k0TB*e)Y5]50eKu݄F% 5җNe1$٪ISK՗ihh`ӛu@xBBBBBBBBBBBf/N1L6`p NLh<0sM0 7*Zĺb6*8o CeFmU֎"VE^Z5Z(RT UB_R7o.%ˬ+u،bIWn0luKa+XKV8/vRCy %ƋS J Q )=z|8kx>[ ?4aaBBBB~iͷGc,O!?7@gߚsm >Om3@!@#zW1ke{QAU+=xR,O!/+g?V{XB_hBBBBBBBA/`1{|N _4{+w!@!@!@@ph(O7Q8L27 @a!:rfCX{7|48ͧw-: =MLUZ&I '[ ѡ@ʢ%r'R*ZP2JI(VwtjT`aSIFP((MS y@D"u DL ;5לGX .k%/.k%/e]^3?#a2Ʌ]?J_Ʌ]?J_ʺZ"_-?ZX%McdX%Mcd,e!2Ʌ]?J_Ʌ]?J_ʺZ"_-?ZX%McdX%Mcd,e!2Ʌ]?J_Ʌ]?J_ʺZ"_-?ZX%McdX%Mcd,e!2Ʌ]?J_^nNKLzf]wch~go 6gX& 3S,sj}ƌ#!jkWZ"+"dP[ ?4BBBBNm|d;b| b>ӛoX-BoiBB5(dь~]+B R1Y{‘`ry __9X=b" ;BBBBBBB _4{+wjw=^_BBB/ۇgW@G'xD_ίfOBBBBBBBBBEuWww6=mXC 7|{jJȀ6tۋӢu,5kQa!Nb"@ZEq>]NAr.c NAr.c a B,= B,=,F]NAr.c NAr.c a B,= B,=,F]NAr.c NAr.c a B,= B,=4^}>n-ny0h ĬNwj兴G2gFe-ȏU4r'IU<QZk|iOK5/d]lv{γ(YV˲:-g 8ϑȶ+ ݆d\_ )۹.TVrCLNAr.c >:3m]IYB7)J) aB]%ID'\jd3-tBSP2D  ѩl%onƺĵIZvX7V9e* %̷"Qt`ot#Tr. V] )a [$vrMbFvћ%W ߓ:m\zWy֕mn0m+r@$O&vuenv"T6MmC%*܇ZpZ&8aU9ӕT+*NfcJVv(g-Lfm`)j(+=qMuƍísN,UP@/Pj$TYj :@"G0ig8fнV;Kcۜx,C#UdܵVyr n/Fb ׅFۆp.-rlQe ئ9^,T$Tr[uBr^H&qIW^p]Q.̼.۱uuRp'6Qק] tFQԢT+OV2tyqh=IDGh @0Kq tQw7ĥ,pM8si駝+h?@Yl{GB5C@»b"!a?\u QciO 1'D[Wu;z&)K}/l J׷ bGo4ՏU]X:}NāPNBBBBߚsm >Om3v2l2#L}BB^aŕF&eY^nl`{K2*c)'ejw=^_ W3^bBBB?1d{;N- Y=*)SopZseQ( JK$3<@@5 <Öax s|۳=},Y8g6]搩W$|6eE*曔\.ஏI CF_b̫LJA}\thnOQ1crB(7lo Œ J]@ђał~/J!B[A!ł~/l-c0b?Us6Sw+ +T_MZX'fn3nV,Q3e7yhr`)@X"XOE͔ł~/l-c0b?Us6Sw+ +T_MZX'fn3nV,Q3e7yhr`)@X"XOE͔ł~/l-c0b?Us6Sw+ +T_MZX'fn3nV,Q3e7yhr`)@X"XOE͔ł~/l-c0b?Us6Sw+ +T_MZX'fn3nV,Q3e7yhr`)@X"XOE͔ł~/l-c0b?Us6Sw+ +T_MZX'fn3nV,Q3e7yhr`)@X"XOE͔ł~/l-c0b?Us6Sw+ +T_MZX'fn3nV,Q3e7yhr`)@Xp3j:o}G@#;XLQ/z:zG5WL]k!id[vR SPɔo.D $|j$TJCVFJ{Ԓӻ}-9q- %zLGJ_{N$jnHC1?C9 EkWeطg[-V̬puw*]j'p73-VfIq EgjQN<Qx& ]0v1ke{QɣzW9X=b" "6sJ{g E%p@v!!!!!!!icW L{r'|0 0j!@CzgILhҤh9q%B'AN,ӚiTm4>CA@a!ź/[~ GNjkMuqeN~CFe(ìnm|z}|PoI:U/zuS*3-= >(nIe@a&^T>T:LOҨ|̷$2P'ڇ{ӪPP2U?J2ܓC䞆_jeN~CCޝT*ʌrOC/z}|PoI:U/zuS*3-= >(nIe@a&^T>T:LOҨ|̷$2P'ڇ{ӪPP2U?J2ܓC䞆_jeN~CCޝT*ʌrOC/z}|PoI:U/zuS*3-= >(nIe@a&^T>T:LOҨ|̷$2P'ڇ{ӪPP2U?J2ܓC䞆_jeN~CCޝT*ʌrOC/z}|PoI:U/zuS*3-= >(nIe@a&^T>T:LOҨ|̷$2P'ڇ{ӪPP2U?J2ܓC䞆_jeN~CCޝT*ʌrOC/z}|PoI:U/zuS*3-= >(nIe@a&^T>T:LOҨ|̷$2P'ڇ{ӪPP2U?J2ܓC䞆_jeN~CCޝT*ʌrOC/z}|PoI:U/zuS*3-= >(nIe@a&^T>T:LOҨ|̷$2P'ڇ{ӪPP2U?J2ܓC䞆_jeA:p!9VVARif GQe 4m'ڇ = >( Fqٰ 24\FiB]j5dy8O8O50)Q nmB(ӣ$, rJGtxGmG@`hw#:y9!!eY^n2h^aŕF!)V{H9V&'^MlO:չϡɭW5Ó[NgunkwE`kbn0S[`,X<؟u{;s[95?wV >V&'^MlO:չϡɭW5Ó[NgunkwE`kbn0S[`,X<؟u{;s[95?wV >V&'^MlO:չϡɭW5Ó[NgunkwE`kbn0S[`,X<؟u{;s[95?wV >X纑L?] ,k\~!!!!!!!uy7>>wӆBn8t~ nIc :Ia>o'ɎЀ>o'Ɇ94&;B94&||nIc :Ia>o'ɎЀ>o'Ɇ94&;B94&||nIc :Ia>o'ɎЀ>o'Ɇ94&;B94&||nIc :Ia>o'ɎЀ>o'Ɏk(jO0z@ 䈇H^sm'Ⱥx;ʮd$EtaRb*[H[#=^JK^@x 8D7CY mueӴ,ЖC|X<Ժ6罹&">Dn̗ZUDwʋ^ ԙi!-r& +ݔdvѓwU"^!wd|g 1=bUnrh;VC[{w.`8nU wq\+{oS̶+w)h Otnyo.;f9~D8O@SFjp{.;0폷S\ϴӬT;L!o9n۴R:4&RWS"Ȱ*PЗ\PnIb6Xq–O,?qGb0wG0G7h@uG7t}ߚOt}ߚO s~i>Lv]s~i>L7G1wG0G7h@uG7t}ߚOt}ߚO s~i>Lv]s~i>L7G1wG0G7h@uG7t}ߚOt}ߚO s~i>Lv]s~i>L7G1wG0G7h@uG7t}ߚOt}ߚO s~i>Lv]s~i>L7fN: D5!!]9s6        5p5a@<BBBBBBBBBBBBBBO#m5{d&Pz!-] uTҊJ:zDqJR[HxT/";8P5C~y Y]ǫrFGfs%-)JPpQu,SiQF Z$5i\oc6̶k\zVMEY= d<~e$LL&jD GQӮ>ߎ4 u5@N5 ^:C xqd<97ۛybQ^{-֡UUV[C5:RP/IDd` YD80L,2BBBBBBBBBBBBBBBG y:9u tifm` aBBBBBBB^9G!Y8mRaV}PSn oӚGJw"3eY9RgS<챕r׮n޷}^RzR)RH =4@Ț;ipgiK^eTե?Vjf鈏5Ә9y/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~[,J/orKG~\ܳ%M͠4??>5` GqL熂Og~ɚLݐv.!oSu%equ5FcҢWNڶuUq^ZZyL쾗(e62WQ'6ou$Z;W8z vdYċZi-I&7\e]B`9CQxu82Κw]g9,fTo3Sv_lww%: 1i6Sy } ٘^ꅿ=l(xBvho zQex.u]ĹQxsmjiiW&_bſ'2YS\%Nd 7gP+djR/-C38T9j~-JZ§2K$I4($Volټ#RE]4*sܘCcϴ}0O(0iYC)ZeÏ0@]5KHvwIUTIZf))ij[\ǹNf*?zd*J{Yj9"YFSzaTf䌭JJs[,nܓ{7@`xw#qY&`P$z]Bi#.D|ѦlX*՚Ia#Qj@fF*rYDB74R?nJnf9 :n\}Fd詅Ը USz:QQJyeO0(ҐB"nYfa%FN]t+~DEkaVWǓݱ ܊{6!}c8sr4k&*9kqo.]x0-$[Xs-ݵWbf]QAt++"Z dﵲMw]9;pms>R/7KEqrلCPQFq.ftt$fVWǓݱ ܊{6!}cPP;ooZk~VfAǫR2x4¢l 5T(D,8呕2n!ƍ&B ]*7ДTJ'ki*pU2Qת"n<퍈_XuVWǓݱ  +U|xk]ebXۊ(uoˀ÷5y!㛊*Tkצ|:2AT*bB96i0*+#rk.$YL-*Ȇ>z i$EJ8jJLp\3MuVWǓݱ ܊{6!}cMgFHTZɋ6[qq2ߵ\XY~T&ۑm.:/z5z$x8{i,L{d ֙۬"V\+V/\)g\|Ek%f\V} 5.H+q覆"n<퍈_XuVWǓݱ RD[uyՙ5-›5H=W w\iU b1e`SW”斴K$۝}r+؅UnEzy=;!r+؅UnEzy=;!r+؅UnEzy=;!r+؅UnEzy=;!r+؅UnEzy=;!r+؅UnEzy=;!r+؅+2(@@vqcb_:'l wU>DzܙA䷇F0Kie6DluH!DQ3L șC@ɔmuuGh: f5:TL%ZUpdZuZu.&J:VJRJڒ$<6` G †ZXAUuzMҠx*rBaoU jHHwBl\șDG'DzQ@4#;eG@G]t㮀%` wU>Dpɔ =Q mD%C<9r^x"=p,, ȠǓlAGqVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XuVWǓݱ O"n<퍈_XYWg )OgH6SԱ:Se9Z(i\t|SVM,jMN8CAo!!!!!!!ٛ4A?F\a^e&Y(h&P=jrL!5Y@s2X5SL|NYzއ3A1r&Z{x*[ l.FnP][MsV&@3~/kk~Y}m+cd]h-V32O֓#+-7N54]-WjIn![j֨Ip.V37N)et *KRMKc.: I0 e )uih4xitʩ\酧efMu煩)dt+2^͹՛.[ZDaQ,upNJV8i”QTu[ 5vx]2Ѱvl}55IHl绉aF4y hdUt8O.*|u+>ώr]n{r%nL+wRղYSi;IiQvmyo:,6횔6C1=uF;ָ<`+[VČw67kO!nIM>:X:;ڏ5˵gC͒qQS712J 5h>;!vϳkq">9"$R*.[47EȺגZׅqz8̸N4n0@K{:\i I!J.D_Nܻ,Z6۪WBQ0/eY!M6K>U2 DdBӒCnSEn#ռ>;!vϳkt5Uؾ71>$S[k|X5IK$q+j>) fml"Ik7%[T"5GKec\FW6HN9"7[FAsՕ*D3qgd9.79Y|vCswnFcŸ i8|#\&]v)ig(깗fWs>R[۵ W6کU+Vi:맹Fc36㕛r̻y.EȌ},A7E[£DbBˑ`JNfRuJI5+>ώr]n{r%h#n3zaqwUH򣊡%MoP'ԦLOCR!VC MfQ|Mȟ_G7=rMٝyͺ(O(Yk) >݉ی;8u\*!QrOV}\gK%Zçc<ڛ x;jצęВ̛t⢍DzL'&t@ԩN*JZ}QDG;^@Etr%+>ώr]n{ WG+>ώr]n{r%xEtr%+>ώr]n{ WG+>ώr]n{r%xEtr%+>ώr]n{ WG+>ώr]n{r%xEtr%+>ώr]n{ WG+>ώr]n{r%xEtr%+>ώr]n{ WG+>ώr]n{r%xEudzO%.J|A!qR0=9Y|vCswX"9Y|vCswÕgd9.7+gd9.79Y|vCswX"9Y|vCswÕgd9.7+gd9.79Y|vCswX"9Y|vCswÕgd9.7+gd9.79Y|vCswX"9Y|vCswÕgd9.7+gd9.79Y|vCswX"9Y|vCswÕgd9.7+gd9.79Y|vCswX"9Y|vCswÕgd9.7+gd9.79Y|vCswX"9Y|vCswÕgd9.7+gd9.79Y|vCswX"9Y|vCswÕgd9.7Dd׫=ZrRY3ԚyeIe aCM4j!ѳߘ2 t;swꙨ#֢VaO›A-S쭺ɖ$_an63v)~znr!}֥u.Uѷ3-ܷ$Kv] hiԤܷԪ5:BJI'@RII垝Ps0(5v^bY$ZVQ`*U %45N2S P6,j!!@9gG!t(qzl>Q3QQӀp׆~xy䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_j҇҇5ܓC䞆_jwY^o;`JîM 3M8CaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ CaZPCaZQIeCrOC/ 3]zo7\<isǭ;aROT !WSSUwAiM68,nOT)FZLrOC/4`>225$j ZշBQ4m=zPFa !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!sobjectizer-5.8.6/dev/sample/so_5/stop_guard/000077500000000000000000000000001523065210100211235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/stop_guard/CMakeLists.txt000066400000000000000000000005351523065210100236660ustar00rootroot00000000000000set(SAMPLE sample.so_5.stop_guard) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.stop_guard_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/stop_guard/main.cpp000066400000000000000000000065651523065210100225670ustar00rootroot00000000000000/* * A sample of using stop_guard. */ #include // Main SObjectizer header files. #include // Definition of stop_guard for the example. // This guard sends a shutdown_started signal to the specified mbox. class example_guard final : public so_5::stop_guard_t , public std::enable_shared_from_this< example_guard > { public : // A signal to be sent when shutdown is started. struct shutdown_started final : public so_5::signal_t {}; example_guard( // Mbox to which shutdown_started must be sent. so_5::mbox_t dest ) : m_dest( std::move(dest) ) {} void stop() noexcept override { so_5::send< shutdown_started >( m_dest ); } private : const so_5::mbox_t m_dest; }; // Definition of an agent for SObjectizer. class worker final : public so_5::agent_t { // Worker will have two states: state_t st_normal{ this }, st_shutdown{ this }; // These signals will be used by worker. struct timer final : public so_5::signal_t {}; struct terminate_work final : public so_5::signal_t {}; public: worker( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { this >>= st_normal; st_normal .event( &worker::on_timer_normal ) .event( &worker::on_shutdown_started ); st_shutdown .event( &worker::on_timer_shutdown ) .event( &worker::on_terminate ); } // A reaction to start of work in SObjectizer. void so_evt_start() override { // Stop_guard must be created and installed. m_guard = std::make_shared< example_guard >( so_direct_mbox() ); so_environment().setup_stop_guard( m_guard ); // Periodic timer message must be sent. m_timer = so_5::send_periodic< timer >( *this, std::chrono::milliseconds(125), std::chrono::milliseconds(125) ); } private : // Stop_guard to be used by this agent. so_5::stop_guard_shptr_t m_guard; // Timer ID for periodic message. so_5::timer_id_t m_timer; void on_timer_normal( mhood_t ) { std::cout << "working in normal mode..." << std::endl; } void on_timer_shutdown( mhood_t ) { std::cout << "working in shutdown mode..." << std::endl; } void on_shutdown_started( mhood_t ) { std::cout << "shutdown is in progress!" << std::endl; this >>= st_shutdown; so_5::send_delayed< terminate_work >( *this, std::chrono::milliseconds(300) ); } void on_terminate( mhood_t ) { so_environment().remove_stop_guard( m_guard ); std::cout << "terminate work." << std::endl; } }; // This agent will initiate the stop operation. class work_stopper final : public so_5::agent_t { struct stop_work final : public so_5::signal_t {}; public : work_stopper( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( [this]( mhood_t ) { // Initiate shutdown of SObjectizer Environment. so_environment().stop(); } ); } void so_evt_start() override { so_5::send_delayed< stop_work >( *this, std::chrono::milliseconds(400) ); } }; int main() { try { // Starting SObjectizer. so_5::launch( // A function for SO Environment initialization. []( so_5::environment_t & env ) { // Create coop with example's agents. env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< worker >(); coop.make_agent< work_stopper >(); } ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/subscriptions/000077500000000000000000000000001523065210100216635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/subscriptions/CMakeLists.txt000066400000000000000000000007761523065210100244350ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.subscriptions) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.subscriptions_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/subscriptions/main.cpp000066400000000000000000000101631523065210100233140ustar00rootroot00000000000000/* * A sample of using mbox for subscribing messages. */ #include #include // Main SObjectizer header file. #include // State sequence for sample agent. enum sample_state_t { DEFAULT_STATE, FIRST_STATE, SECOND_STATE, }; // Sample message. struct my_message { // Some data. int m_x; }; // Another sample message. struct my_another_message { // Some data. std::string m_s; }; // Sample message for the subscription demonstrtion. class my_agent_t final : public so_5::agent_t { public: my_agent_t( context_t ctx ); // Definition of an agent for SObjectizer. void so_define_agent() override; // A reaction to start of work in SObjectizer. void so_evt_start() override; // Handle change state. void change_state_event_handler( sample_state_t next_state ); // Handle my_message. void my_event_handler( const my_message & message ); // Handle my_another_message. void my_another_event_handler( const my_another_message & message ); private: // Agent states. const state_t st_first{ this, "first" }; const state_t st_second{ this, "second" }; }; my_agent_t::my_agent_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void my_agent_t::so_define_agent() { std::cout << "so_define_agent()" << std::endl; st_first.event( &my_agent_t::change_state_event_handler ); st_second.event( &my_agent_t::change_state_event_handler ); so_default_state().event( &my_agent_t::change_state_event_handler ); std::cout << "\tsubscribe my_event_handler in " << st_first.query_name() << std::endl; st_first.event( &my_agent_t::my_event_handler ); std::cout << "\tsubscribe my_another_event_handler in " << st_first.query_name() << std::endl; st_first.event( &my_agent_t::my_another_event_handler ); std::cout << "\tsubscribe my_event_handler in " << st_second.query_name() << std::endl; st_second.event( &my_agent_t::my_event_handler ); } void my_agent_t::so_evt_start() { std::cout << "so_evt_start()" << std::endl; std::cout << "\tsend the first sample_state_t for state changes" << std::endl; // Switch to first state and handle messages. so_5::send< sample_state_t >( *this, FIRST_STATE ); } void my_agent_t::change_state_event_handler( sample_state_t next_state ) { std::cout << "change_state_event_handler()" << std::endl; if( DEFAULT_STATE == next_state ) { this >>= so_default_state(); std::cout << "\tswitched to default state and shutdown..." << std::endl; so_environment().stop(); } else { if( FIRST_STATE == next_state ) { this >>= st_first; std::cout << "\tswitched to " << so_current_state().query_name() << std::endl; // Send serie of messages... so_5::send< my_message >( *this, 42 ); so_5::send< my_another_message >( *this, "SObjectizer" ); std::cout << "\tmessages sent" << std::endl; // Switch to second. so_5::send< sample_state_t >( *this, SECOND_STATE ); } else if( SECOND_STATE == next_state ) { this >>= st_second; std::cout << "\tswitched to " << so_current_state().query_name() << std::endl; // Send serie of messages... so_5::send< my_message >( *this, -42 ); // Message should not be received. so_5::send< my_another_message >( *this, "rezitcejbOS" ); std::cout << "\tmessages sent" << std::endl; // Switch to default. so_5::send< sample_state_t >( *this, DEFAULT_STATE ); } } } void my_agent_t::my_event_handler( const my_message & message ) { std::cout << "my_event_handler()" << std::endl; std::cout << "\tcurrent state is " << so_current_state().query_name() << std::endl << "\tmessage.x = " << message.m_x << std::endl; } void my_agent_t::my_another_event_handler( const my_another_message & message ) { std::cout << "my_another_event_handler()" << std::endl; std::cout << "\tcurrent state is " << so_current_state().query_name() << std::endl << "\tmessage.s = " << message.m_s << std::endl; } int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< my_agent_t >() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/two_handlers/000077500000000000000000000000001523065210100214455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/two_handlers/CMakeLists.txt000066400000000000000000000007741523065210100242150ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.two_handlers) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.two_handlers_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/two_handlers/main.cpp000066400000000000000000000025321523065210100230770ustar00rootroot00000000000000/* * An example of receiving message of type M as immutable and as mutable ones. */ // Main SObjectizer header files. #include // Definition of an agent for SObjectizer. class two_handlers final : public so_5::agent_t { // Message to be sent as mutable and immutable. struct M final {}; // Helper signal to stop the example. struct stop final : public so_5::signal_t {}; public : two_handlers(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self() .event(&two_handlers::on_immutable_M) .event(&two_handlers::on_mutable_M) .event(&two_handlers::on_stop); } void so_evt_start() override { so_5::send(*this); // Immutable message is sent. so_5::send>(*this); // Mutable message is sent. so_5::send(*this); // Finish the work. } private : void on_immutable_M(mhood_t) { std::cout << "on immutable" << std::endl; } void on_mutable_M(mhood_t>) { std::cout << "on mutable" << std::endl; } void on_stop(mhood_t) { so_deregister_agent_coop_normally(); } }; int main() { // Starting SObjectizer. so_5::launch( // A function for SO Environment initialization. [](so_5::environment_t & env) { // Creating and registering single agent as a cooperation. env.register_agent_as_coop( env.make_agent() ); } ); return 0; } sobjectizer-5.8.6/dev/sample/so_5/unique_subscribers_mbox/000077500000000000000000000000001523065210100237155ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/unique_subscribers_mbox/CMakeLists.txt000066400000000000000000000010221523065210100264500ustar00rootroot00000000000000include(${CMAKE_SOURCE_DIR}/cmake/sample_as_unittest.cmake) set(SAMPLE sample.so_5.unique_subscribers_mbox) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) add_sample_with_shared_lib_as_test(${SAMPLE}) set(SAMPLE_S sample.so_5.unique_subscribers_mbox_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) add_sample_with_static_lib_as_test(${SAMPLE_S}) sobjectizer-5.8.6/dev/sample/so_5/unique_subscribers_mbox/main.cpp000066400000000000000000000143701523065210100253520ustar00rootroot00000000000000/* * Simple usage of unique_subscribers mbox. */ #include #include #include // Data type to be passed between agents. struct data_t { std::string m_stage{ "initial" }; std::string m_prefix; std::string m_payload; std::string m_suffix; }; std::ostream & operator<<( std::ostream & to, const data_t & what ) { return (to << "(<" << what.m_stage << ">:[" << what.m_prefix << "]= '" << what.m_payload << "' =[" << what.m_suffix << "])"); } // Messages to be used for interaction between agents. // Because all messages are similar we use class templates for them. struct preprocess_tag {}; struct process_tag {}; struct postprocess_tag {}; template< typename Tag > struct msg_handle_data final : public so_5::message_t { std::unique_ptr< data_t > m_data; const so_5::mbox_t m_reply_to; msg_handle_data( std::unique_ptr< data_t > data, so_5::mbox_t reply_to ) : m_data{ std::move(data) } , m_reply_to{ std::move(reply_to) } {} }; template< typename Tag > struct msg_handling_finished final : public so_5::message_t { std::unique_ptr< data_t > m_data; explicit msg_handling_finished( std::unique_ptr< data_t > data ) : m_data{ std::move(data) } {} }; using msg_preprocess_data = msg_handle_data< preprocess_tag >; using msg_preprocess_finished = msg_handling_finished< preprocess_tag >; using msg_process_data = msg_handle_data< process_tag >; using msg_process_finished = msg_handling_finished< process_tag >; using msg_postprocess_data = msg_handle_data< postprocess_tag >; using msg_postprocess_finished = msg_handling_finished< postprocess_tag >; // Type of agent for coordination of data processing. class processing_manager_t final : public so_5::agent_t { const so_5::mbox_t m_processing_mbox; public: processing_manager_t( context_t ctx, so_5::mbox_t processing_mbox ) : so_5::agent_t{ std::move(ctx) } , m_processing_mbox{ std::move(processing_mbox) } {} void so_define_agent() override { so_subscribe_self() .event( &processing_manager_t::evt_preprocess_finished ) .event( &processing_manager_t::evt_process_finished ) .event( &processing_manager_t::evt_postprocess_finished ) ; } void so_evt_start() override { // Data to be processed by several workers. auto data = std::make_unique< data_t >(); data->m_payload = "Hello, World"; std::cout << "data to be processed: " << *data << std::endl; // Initiate processing. so_5::send< so_5::mutable_msg >( m_processing_mbox, std::move(data), so_direct_mbox() ); } private: void evt_preprocess_finished( mutable_mhood_t< msg_preprocess_finished > cmd ) { std::cout << "preprocessed data: " << *(cmd->m_data) << std::endl; // Initiate the next stage. so_5::send< so_5::mutable_msg >( m_processing_mbox, std::move(cmd->m_data), so_direct_mbox() ); } void evt_process_finished( mutable_mhood_t< msg_process_finished > cmd ) { std::cout << "processed data: " << *(cmd->m_data) << std::endl; // Initiate the next stage. so_5::send< so_5::mutable_msg >( m_processing_mbox, std::move(cmd->m_data), so_direct_mbox() ); } void evt_postprocess_finished( mutable_mhood_t< msg_postprocess_finished > cmd ) { std::cout << "postprocessed data: " << *(cmd->m_data) << std::endl; // Finish the example. so_deregister_agent_coop_normally(); } }; // Type of agent for preprocessing of the data. class preprocessor_t final : public so_5::agent_t { const so_5::mbox_t m_processing_mbox; public: preprocessor_t( context_t ctx, so_5::mbox_t processing_mbox ) : so_5::agent_t{ std::move(ctx) } , m_processing_mbox{ std::move(processing_mbox) } {} void so_define_agent() override { so_subscribe( m_processing_mbox ) .event( []( mutable_mhood_t cmd ) { cmd->m_data->m_stage = "preprocessed"; cmd->m_data->m_prefix = "-=#"; cmd->m_data->m_suffix = "#=-"; so_5::send< so_5::mutable_msg >( cmd->m_reply_to, std::move(cmd->m_data) ); } ); } }; // Type of agent for processing of the data. class processor_t final : public so_5::agent_t { const so_5::mbox_t m_processing_mbox; public: processor_t( context_t ctx, so_5::mbox_t processing_mbox ) : so_5::agent_t{ std::move(ctx) } , m_processing_mbox{ std::move(processing_mbox) } {} void so_define_agent() override { so_subscribe( m_processing_mbox ) .event( []( mutable_mhood_t cmd ) { cmd->m_data->m_stage = "processed"; // Reverse the content of the data. cmd->m_data->m_payload = std::string{ cmd->m_data->m_payload.rbegin(), cmd->m_data->m_payload.rend() }; so_5::send< so_5::mutable_msg >( cmd->m_reply_to, std::move(cmd->m_data) ); } ); } }; // Type of agent for processing of the data. class postprocessor_t final : public so_5::agent_t { const so_5::mbox_t m_processing_mbox; public: postprocessor_t( context_t ctx, so_5::mbox_t processing_mbox ) : so_5::agent_t{ std::move(ctx) } , m_processing_mbox{ std::move(processing_mbox) } {} void so_define_agent() override { so_subscribe( m_processing_mbox ) .event( []( mutable_mhood_t cmd ) { cmd->m_data->m_stage = "postprocessed"; // Add prefix and suffix to the payload. cmd->m_data->m_payload = cmd->m_data->m_prefix + " " + cmd->m_data->m_payload + " " + cmd->m_data->m_suffix; so_5::send< so_5::mutable_msg >( cmd->m_reply_to, std::move(cmd->m_data) ); } ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { // Mbox to be used for exchanging the data. auto processing_mbox = so_5::make_unique_subscribers_mbox( coop.environment() ); // Fill the coop with manager and workers. coop.make_agent< processing_manager_t >( processing_mbox ); coop.make_agent< preprocessor_t >( processing_mbox ); coop.make_agent< processor_t >( processing_mbox ); coop.make_agent< postprocessor_t >( processing_mbox ); } ); } ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/sample/so_5/wrapped_env_demo_2/000077500000000000000000000000001523065210100225135ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/wrapped_env_demo_2/CMakeLists.txt000066400000000000000000000005561523065210100252610ustar00rootroot00000000000000set(SAMPLE sample.so_5.wrapped_env_demo_2) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.wrapped_env_demo_2_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/wrapped_env_demo_2/main.cpp000066400000000000000000000154171523065210100241530ustar00rootroot00000000000000/* * A very simple demo of wrapped_env_t and mchains. */ #include #include // Main SObjectizer header file. #include using namespace std; using namespace std::chrono; // A supervisor agent which will interact with main thread. class a_supervisor final : public so_5::agent_t { const state_t st_idle{ this }; const state_t st_started{ this }; const state_t st_finished{ this }; using clock = steady_clock; public : // A message for asking current state of the supervisor. struct ask_status {}; // Messages about status of the supervisor. struct status_idle {}; struct status_in_progress {}; struct status_finished { unsigned long long m_ms; }; // A message to start ping-pong. struct start_ping_pong { unsigned int m_pings; }; // A message to indicate that pinger or ponger stopped its work. struct ping_pong_stopped {}; a_supervisor( // Working context for the agent. context_t ctx, // Mbox for requests from not-SO-part. so_5::mbox_t req_mbox, // Chain for responses to not-SO-part. so_5::mchain_t chain ) : so_5::agent_t{ ctx } , m_req_mbox{ std::move(req_mbox) } , m_chain{ std::move(chain) } {} void so_define_agent() override { // Will start in idle state. this >>= st_idle; // Event handlers for idle state. st_idle // On status request will sent status_idle to chain as a reply. .event( m_req_mbox, [this]( ask_status ) { so_5::send< status_idle >( m_chain ); } ) // Ping-pong start requires more actions so it is implemented // as separate method. .event( m_req_mbox, &a_supervisor::evt_start_ping_pong ); st_started // On status request will reply by sending // status_in_progress to the chain. .event( m_req_mbox, [this]( ask_status ) { so_5::send< status_in_progress >( m_chain ); } ) // Reaction to the end of ping-pong. .event( [this]( ping_pong_stopped ) { // Storing time for the future ask_status requests. m_last_duration_ms = static_cast< unsigned long long >( duration_cast< milliseconds >( clock::now() - m_started_at ).count() ); // Reflect the end of ping-pong by changing state of supervisor. st_finished.activate(); // Send the result of ping-pong to the main thread. so_5::send< status_finished >( m_chain, m_last_duration_ms ); } ); st_finished // On status request will reply by sending // status_finished to the chain. .event( m_req_mbox, [this]( ask_status ) { so_5::send< status_finished >( m_chain, m_last_duration_ms ); } ) // Star of ping-pong will be handled exactly same way // as in st_idle state. .event( m_req_mbox, &a_supervisor::evt_start_ping_pong ); } private : const so_5::mbox_t m_req_mbox; const so_5::mchain_t m_chain; clock::time_point m_started_at; unsigned long long m_last_duration_ms; // Stuff for pinger and ponger agents. // Types of messages to be used by pinger and ponger. struct ping{ unsigned int m_v; }; struct pong{ unsigned int m_v; }; // Type of pinger agent. class a_pinger final : public so_5::agent_t { const so_5::mbox_t m_parent_mbox; so_5::mbox_t m_ponger; unsigned int m_pings; public : a_pinger( context_t ctx, so_5::mbox_t parent_mbox, unsigned int pings ) : so_5::agent_t{ std::move(ctx) } , m_parent_mbox{ std::move(parent_mbox) } , m_pings( pings ) { so_subscribe_self().event( [this]( mhood_t reply ) { if( reply->m_v ) so_5::send< ping >( m_ponger, reply->m_v - 1 ); else { so_5::send< ping_pong_stopped >( m_parent_mbox ); so_deregister_agent_coop_normally(); } } ); } void set_ponger( const so_5::mbox_t & mbox ) { m_ponger = mbox; } void so_evt_start() override { so_5::send< ping >( m_ponger, m_pings ); } }; // Type of ponger agent. class a_ponger final : public so_5::agent_t { so_5::mbox_t m_pinger; public : a_ponger( context_t ctx) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [this]( mhood_t req ) { so_5::send< pong >( m_pinger, req->m_v ); } ); } void set_pinger( const so_5::mbox_t & mbox ) { m_pinger = mbox; } }; void evt_start_ping_pong( start_ping_pong evt ) { this >>= st_started; m_started_at = clock::now(); // Pinger and ponger will be started inside a new coop. // They will work on different threads (management of those // threads will be done by active_obj dispatcher). so_5::introduce_child_coop( *this, so_5::disp::active_obj::make_dispatcher( so_environment() ).binder(), [this, evt]( so_5::coop_t & coop ) { // Pinger and ponger itself. auto pinger = coop.make_agent< a_pinger >( so_direct_mbox(), evt.m_pings ); auto ponger = coop.make_agent< a_ponger >(); // Bind them together. pinger->set_ponger( ponger->so_direct_mbox() ); ponger->set_pinger( pinger->so_direct_mbox() ); } ); } }; // Main demo loop. void demo() { // SO Environment in a special wrapper object. // Environment will be started automatically. so_5::wrapped_env_t sobj; // Mbox for sending requests to SO-part of the app. auto req_mbox = sobj.environment().create_mbox(); // Chain for receiving replies from SO-part of the app. auto chain = create_mchain( sobj ); // Start SO-part of the app. sobj.environment().introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_supervisor >( req_mbox, chain ); } ); // Main loop. The demo will be finished when user enter // 'exit' or 'quit' command. while( true ) { cout << "Enter command (status,start,exit): " << flush; string choice; cin >> choice; if( "status" == choice ) { // Ask status request must be sent. so_5::send< a_supervisor::ask_status >( req_mbox ); // Now we can receive and handle responses. // Wait for messages no more than 200ms. auto r = receive( from( chain ).handle_all().empty_timeout( milliseconds(200) ), []( a_supervisor::status_idle ) { cout << "status: IDLE" << endl; }, []( a_supervisor::status_in_progress ) { cout << "status: IN PROGRESS" << endl; }, []( a_supervisor::status_finished v ) { cout << "status: finished in " << v.m_ms << "ms" << endl; } ); // Have some messages been handled? if( !r.handled() ) cout << "--- no response from supervisor ---" << endl; } else if( "start" == choice ) { cout << "Enter pings count: " << flush; unsigned int pings = 0; cin >> pings; // Initiate ping-pong. so_5::send< a_supervisor::start_ping_pong >( req_mbox, pings ); } else if( "exit" == choice || "quit" == choice ) break; } // SO Environment will be automatically stopped. } int main() { try { demo(); } catch( const exception & x ) { cerr << "Exception: " << x.what() << endl; return 2; } return 0; } sobjectizer-5.8.6/dev/sample/so_5/wrapped_env_demo_3/000077500000000000000000000000001523065210100225145ustar00rootroot00000000000000sobjectizer-5.8.6/dev/sample/so_5/wrapped_env_demo_3/CMakeLists.txt000066400000000000000000000005551523065210100252610ustar00rootroot00000000000000set(SAMPLE sample.so_5.wrapped_env_demo_3) add_executable(${SAMPLE} main.cpp) target_link_libraries(${SAMPLE} sobjectizer::SharedLib) install(TARGETS ${SAMPLE} DESTINATION bin) set(SAMPLE_S sample.so_5.wrapped_env_demo_3_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} sobjectizer::StaticLib) install(TARGETS ${SAMPLE_S} DESTINATION bin) sobjectizer-5.8.6/dev/sample/so_5/wrapped_env_demo_3/main.cpp000066400000000000000000000047671523065210100241620ustar00rootroot00000000000000/* * An example of use wrapped_env with simple_mtsafe environment * and two working threads with mchains and delayed messages. */ #include // Messages for exchanges between threads. struct tick final : public so_5::signal_t {}; struct tack final : public so_5::signal_t {}; // Special signal to stop the exchange. struct stop final : public so_5::signal_t {}; // Helper class for removing code duplication in handling the state of one // working thread. class thread_state final { std::chrono::milliseconds pause_{ 750 }; bool must_stop_{ false }; public : bool must_stop() const { return must_stop_; } template void reply_or_stop( const so_5::mchain_t & to ) { if( pause_ > std::chrono::milliseconds(5) ) { pause_ = std::chrono::milliseconds( static_cast< decltype(pause_.count()) >( double(pause_.count()) / 1.5) ); so_5::send_delayed< Reply >( to, pause_ ); } else { so_5::send< stop >( to ); must_stop_ = true; } } }; // Body for thread. void thread_body( so_5::mchain_t recv_chain, so_5::mchain_t write_chain ) { thread_state state; receive( from(recv_chain).handle_all().stop_on( [&state]{ return state.must_stop(); } ), [&]( so_5::mhood_t ) { std::cout << "Tick!" << std::endl; state.reply_or_stop< tack >( write_chain ); }, [&]( so_5::mhood_t ) { std::cout << "Tack!" << std::endl; state.reply_or_stop< tick >( write_chain ); } ); } // Just a helper function for preparation of environment params. // It is needed just for shortening of main() function code. so_5::environment_params_t make_env_params() { so_5::environment_params_t env_params; env_params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); return env_params; } int main() { so_5::wrapped_env_t sobj( make_env_params() ); // An instance for second working thread. // The thread itself will be started later. std::thread second_thread; // This thread must be joined later. const auto thread_joiner = so_5::auto_join( second_thread ); // We need two simple mchains for exchange. auto ch1 = create_mchain( sobj ); auto ch2 = create_mchain( sobj ); // The chains must be closed automatically. const auto ch_closer = so_5::auto_close_drop_content( ch1, ch2 ); // Launch another thread. second_thread = std::thread( thread_body, ch2, ch1 ); // The first message must be initiated. so_5::send< tick >( ch1 ); // Launch the interchange on the context of main thread. thread_body( ch1, ch2 ); return 0; } sobjectizer-5.8.6/dev/so_5/000077500000000000000000000000001523065210100154735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/3rd_party/000077500000000000000000000000001523065210100174025ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/3rd_party/timertt/000077500000000000000000000000001523065210100210725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/3rd_party/timertt/all.hpp000066400000000000000000003121451523065210100223610ustar00rootroot00000000000000/* * TimerThreadTemplate */ /*! * \file timertt/all.hpp * \brief All project's stuff. */ #if !defined( SO_5_TIMERTT_ALL_HPP ) #define SO_5_TIMERTT_ALL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_MSC_VER) && (_MSC_VER < 1900) // Under VS++12 the alignment will always be 8 bytes. #define TIMERTT_ALIGNAS_WORKAROUND(T) __declspec(align(8)) // VS++12 doesn't support noexcept keyword. #define TIMERTT_NOEXCEPT #define TIMERTT_HAS_NOEXCEPT 0 #else #define TIMERTT_ALIGNAS_WORKAROUND(T) alignas(T) #define TIMERTT_NOEXCEPT noexcept #define TIMERTT_HAS_NOEXCEPT 1 #endif /*! * \brief The current version of timertt. * * Please note that this macro was added only in version 1.2.1. * It means that it is better to check presense of TIMERTT_VERSION and * only then try to check its value: * \code * #include * * #if defined(TIMERTT_VERSION) * #if TIMERTT_VERSION > 1002002 * #endif * #endif * \endcode * * The value of TIMERTT_VERSION has YXXXZZZ format in decimal. For * example: 1002001 or 1003014. 'Y' means the major version number * (e.g. 1 for 1.2.1), 'XXX' means minor version number * (e.g. 002 for 1.2.1) and 'ZZZ' means patch number * (e.g. 001 for 1.2.1). It means that version 1.2.1 will be * represented as 1002001 and 1.3.14 will be represented as 1003014. * * \since * v.1.2.1 */ #define TIMERTT_VERSION 1002004u /*! * \brief Top-level project's namespace. */ namespace timertt { /*! * \brief Container for thread safety flags. * * \since * v.1.1.0 */ struct thread_safety { /*! * \brief Indicator for not-thread-safe implementation. * * \since * v.1.1.0 */ struct unsafe {}; /*! * \brief Indicator for thread-safe implemetation. * * \since * v.1.1.0 */ struct safe {}; }; namespace details { //! Status of timer. enum class timer_status : unsigned int { //! Timer is deactivated. /*! It can be activated or destroyed safely. */ deactivated, //! Timer is activated. /*! It can be safely deactivated and destroyed. */ active, //! Timer is in execution list and is waiting for execution. /*! * It cannot be deactivated and destroyed right now. * Status of timer can only be changed to wait_for_deactivation. * And actual deactivation will be performed later, after * processing of execution list. */ wait_for_execution, //! Timer must be deactivated after processing of execution list. /*! * The only possible switch for the timer is to deactivated status. */ wait_for_deactivation }; } /* namespace details */ /*! * \brief Container for thread-safety-specific type declarations. * * \note Will be specialized for every thread-safety case. * * \tparam Thread_Safety must be thread_safety::unsafe or thread_safety::safe. * * \since * v.1.1.0 */ template< typename Thread_Safety > struct threading_traits {}; /*! * \brief Specialization for not-thread-safe case. * * \since * v.1.1.0 */ template<> struct threading_traits< thread_safety::unsafe > { //! Type for reference counters. typedef unsigned int reference_counter_type; //! Type for holding timer status inside a timer object. typedef details::timer_status status_holder_type; }; /*! * \brief Specialization for thread-safe case. * * \since * v.1.1.0 */ template<> struct threading_traits< thread_safety::safe > { //! Type for reference counters. typedef std::atomic_uint reference_counter_type; //! Type for holding timer status inside a timer object. typedef std::atomic< details::timer_status > status_holder_type; }; // // timer_object // /*! * \brief Base type for timer demands. * * \tparam Thread_Safety Thread-safety indicator. Must be thread_safety::unsafe * or thread_safety::safe type. */ template< typename Thread_Safety > struct timer_object { //! Reference counter for the demand. typename threading_traits< Thread_Safety >::reference_counter_type m_references; //! Deafault constructor. inline timer_object() { m_references = 0; } inline virtual ~timer_object() {} //! Increment reference counter for the demand. static inline void increment_references( timer_object * t ) { ++(t->m_references); } //! Decrement reference counter for the demand and destroy //! demand if there is no more references. static inline void decrement_references( timer_object * t ) { if( 0 == --(t->m_references) ) delete t; } }; // // scoped_timer_object_holder // /*! * \brief A special wrapper to be used to hold an actual timer object which * is not allocated dynamically. * * This class is a part of support for scoped timer objects. Every timer * engine will define its own `scoped_timer_object` by using this * template class. Something like: * \code * class some_engine * { * struct timer_type { ... }; * public : * using scoped_timer_object = scoped_timer_object_holder; * ... * }; * \endcode * * \par Some implementation details. * Version 1.2.0 doesn't change way of working with actual timer objects. * They are still reference countable. It means that when a scoped timer object * is passed to engine's `activate` method a reference count will be * incremented. When this object is passed to `deactivate` method then * reference counter will be decremented. If reference counter becomes zero * then the timer object will be deallocated by calling `delete`. * To prevent this scoped_timer_object_holder automatically incremented * reference counter by 1 in the constructor. It means that the reference * counter will not be zero (in normal scenarios). * * \note * This type is not Copyable nor Moveable. * * \since * v.1.2.0 */ template< typename Actual_Object > class scoped_timer_object_holder { Actual_Object m_object; public : scoped_timer_object_holder() { // Actual object must have yet another reference to prevent its deletion. Actual_Object::increment_references( &m_object ); } scoped_timer_object_holder( const scoped_timer_object_holder & ) = delete; scoped_timer_object_holder( scoped_timer_object_holder && ) = delete; Actual_Object * ptr() { return &m_object; } }; // // timer_object_holder // /*! * \brief An intrusive smart pointer to timer demand. * * \tparam Thread_Safety Thread-safety indicator. Must be thread_safety::unsafe * or thread_safety::safe type. */ template< typename Thread_Safety > class timer_object_holder { public : //! Default constructor. /*! * Constructs a null pointer. */ inline timer_object_holder() : m_timer( nullptr ) {} //! Constructor for a raw pointer. inline timer_object_holder( timer_object< Thread_Safety > * t ) : m_timer( t ) { take_object(); } //! Copy constructor. inline timer_object_holder( const timer_object_holder & o ) : m_timer( o.m_timer ) { take_object(); } //! Move constructor. inline timer_object_holder( timer_object_holder && o ) : m_timer( o.m_timer ) { o.m_timer = nullptr; } //! Constructor for the case when timer object is a scoped timer. template< typename Actual_Object > inline timer_object_holder( scoped_timer_object_holder & scoped ) : timer_object_holder( scoped.ptr() ) {} //! Destructor. inline ~timer_object_holder() { dismiss_object(); } //! Copy operator. inline timer_object_holder & operator=( const timer_object_holder & o ) { timer_object_holder t( o ); swap( t ); return *this; } //! Move operator. inline timer_object_holder & operator=( timer_object_holder && o ) { timer_object_holder t( std::move( o ) ); swap( t ); return *this; } //! Swap values. inline void swap( timer_object_holder & o ) { auto t = m_timer; m_timer = o.m_timer; o.m_timer = t; } /*! * \brief Drop controlled object. */ inline void reset() { dismiss_object(); } //! Is this a null pointer? /*! i.e. whether get() != 0. \retval true if *this manages an object. \retval false otherwise. */ inline operator bool() const { return nullptr != m_timer; } /*! * \name Access to object. * \{ */ inline timer_object< Thread_Safety > * get() const { return m_timer; } template< class O > O * cast_to() { if( !m_timer ) throw std::runtime_error( "timer is nullptr" ); return static_cast< O * >(m_timer); } /*! * \} */ private : //! Timer controlled by a smart pointer. timer_object< Thread_Safety > * m_timer; //! Increment reference count to object if it's not null. inline void take_object() { if( m_timer ) timer_object< Thread_Safety >::increment_references( m_timer ); } //! Decrement reference count to object and delete it if needed. inline void dismiss_object() { if( m_timer ) { timer_object< Thread_Safety >::decrement_references( m_timer ); m_timer = nullptr; } } }; // // default_error_logger // /*! * \brief Class of default error logger. * * This class uses std::cerr as the stream for logging errors. */ struct default_error_logger { //! Logs error message to std::cerr. inline void operator()( //! The text of error message. const std::string & what ) { std::cerr << what << std::endl; } }; // // default_actor_exception_handler // /*! * \brief Class of default handler for exceptions thrown from timer actors. * * Calls std::abort() to terminate application execution. */ struct default_actor_exception_handler { //! Handles exception. inline void operator()( //! An exception from timer actor. const std::exception & ) { std::abort(); } }; // // default_timer_action_type // /*! * \brief Defaulf type of timer action. * * \since * v.1.2.0 */ typedef std::function< void() > default_timer_action_type; // // monotonic_clock // /*! * \brief Type of clock used by all threads. */ typedef std::chrono::steady_clock monotonic_clock; // // timer_quantities // /*! * \brief Information about quantities of various timer types. * * \since * v.1.1.1 */ struct timer_quantities { //! Quantity of single-shot timers. std::size_t m_single_shot_count = { 0 }; //! Quantity of periodic timers. std::size_t m_periodic_count = { 0 }; }; /*! * \brief An internal namespace with implementation details. */ namespace details { // NOTE: invoke_noexcept_code_block was added because some compilers // do not have support for noexcept (for example VC++2013). So it's impossible // to write code like that: // // [&]() noexcept { // some_code(); // }(); // // Instead we have to write: // // invoke_noexcept_code_block([&] { // some_code(); // }); // #if TIMERTT_HAS_NOEXCEPT template< typename Lambda > void invoke_noexcept_code_block(Lambda && lambda) noexcept { lambda(); } #else template< typename Lambda > void invoke_noexcept_code_block(Lambda && lambda) { try { lambda(); } catch(...) { std::abort(); } } #endif // // buffer_allocated_object // /*! * \brief A special storage to be used for holding non-default constructible * objects which are created by demand. * * \note * In C++17 std::optional can be used instead of this class. * * \since * v.1.2.0 */ template class buffer_allocated_object { TIMERTT_ALIGNAS_WORKAROUND(T) std::array buffer_; bool allocated_{ false }; void destroy_if_allocated() { if(allocated_) { get()->~T(); allocated_ = false; } } public : using pointer = T*; using element_type = T; using reference = typename std::add_lvalue_reference::type; buffer_allocated_object() TIMERTT_NOEXCEPT = default; buffer_allocated_object(const buffer_allocated_object &) = delete; buffer_allocated_object(buffer_allocated_object &&) = delete; ~buffer_allocated_object() { destroy_if_allocated(); } template void allocate(Args &&...args) { destroy_if_allocated(); new(buffer_.data()) T(std::forward(args)...); allocated_ = true; } void destroy() { destroy_if_allocated(); } operator bool() const TIMERTT_NOEXCEPT { return allocated_; } pointer get() const TIMERTT_NOEXCEPT { return reinterpret_cast(const_cast(buffer_.data())); } pointer operator->() const TIMERTT_NOEXCEPT { return get(); } reference operator*() const TIMERTT_NOEXCEPT { return *get(); } }; // // timer_action_holder // /*! * \brief A special storage for holding timer actions. * * If a timer action is represented by std::function then * a simple std::function can be used for holding this timer action. * But if a timer action is represented by some user type then * we must use a internal buffer and should construct timer action * instance inplace only when it is necessary. * * \since * v.1.2.0 */ template< typename Action_Type > class timer_action_holder { buffer_allocated_object m_action; public: timer_action_holder() = default; timer_action_holder( const timer_action_holder & ) = delete; timer_action_holder( timer_action_holder && ) = delete; void assign( Action_Type && action ) { m_action.allocate( std::move(action) ); } void exec() const { (*m_action)(); } }; template<> class timer_action_holder< default_timer_action_type > { default_timer_action_type m_action; public : timer_action_holder() = default; timer_action_holder( const timer_action_holder & ) = delete; timer_action_holder( timer_action_holder && ) = delete; void assign( default_timer_action_type && action ) { m_action = std::move(action); } void exec() const { m_action(); } }; // // timer_kind // /*! * \brief Type of the timer (single-shot or periodic). * * \since * v.1.1.1 */ enum class timer_kind { //! Timer is a single-shot timer. single_shot, //! Timer is a periodic timer. periodic }; // // engine_common // /*! * \brief A common part for all timer engines. * * Will be used by concrete engines for storing instances of * Error_Logger and Actor_Exception_Handler. * * Also defines type \a thread_safety to be used later. * * \tparam Thread_Safety Thread-safety indicator. * Must be timertt::thread_safety::unsafe or timertt::thread_safety::safe. * * \tparam Timer_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Error_Logger type of logger for errors detected during * timer handling. Interface for error logger is defined * by default_error_logger class. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. * * \since * v.1.1.0 */ template< typename Thread_Safety, typename Timer_Action, typename Error_Logger, typename Actor_Exception_Handler > class engine_common { public : //! Indicator of thread-safety. using thread_safety = Thread_Safety; //! Alias for Timer_Action. using timer_action = Timer_Action; //! Initializing constructor. engine_common( Error_Logger error_logger, Actor_Exception_Handler exception_handler ) : m_error_logger( error_logger ) , m_exception_handler( exception_handler ) {} /*! * \brief Get the quantities of timers of various types. * * \since * v.1.1.1 */ timer_quantities get_timer_quantities() const { return this->m_timer_quantities; } protected : //! Error logger. Error_Logger m_error_logger; //! Exception handler. Actor_Exception_Handler m_exception_handler; /*! * \brief Quantities of timers of various types. * * \since * v.1.1.1 */ timer_quantities m_timer_quantities; /*! * \brief Helper method for increment the count of timers of * the specific type. * * \since * v.1.1.1 */ void inc_timer_count( timer_kind kind ) { if( timer_kind::single_shot == kind ) ++m_timer_quantities.m_single_shot_count; else ++m_timer_quantities.m_periodic_count; } /*! * \brief Helper method for decrement the count of timers of * the specific type. * * \since * v.1.1.1 */ void dec_timer_count( timer_kind kind ) { if( timer_kind::single_shot == kind ) --m_timer_quantities.m_single_shot_count; else --m_timer_quantities.m_periodic_count; } /*! * \brief Helper method for reseting quantities of timers to zero. * * \since * v.1.1.1 */ void reset_timer_count() { m_timer_quantities = timer_quantities{}; } }; // // timer_wheel_engine_defaults // /*! * \brief Container for static method with default values for * timer_wheel engine. * * \since * v.1.1.0 */ struct timer_wheel_engine_defaults { //! Default wheel size. inline static unsigned int default_wheel_size() { return 1000; } //! Default tick duration. inline static monotonic_clock::duration default_granularity() { return std::chrono::milliseconds( 10 ); } }; // // timer_wheel_engine // /*! * \brief A engine for timer wheel mechanism. * * This class uses timer_wheel * mechanism to work with timers. * This mechanism is efficient for working with big amount of timers. But this * mechanism can consume some more resources when there is a small number of * waiting timers. It's because there is a wakeup at every time step of the * wheel, even if there is no elapsed timers for that step. Another price for * timer_wheel is the granularity of timer steps. * * Timer wheel data structure consists from one fixed size vector * (the wheel) and several double-linked list (one list for every wheel * slot). * * At the start of next time step thread switches to next wheel position * and handles timers from this position list. After processing * all elapsed single-shot timers will be removed and deactivated, and * all elapsed periodic timers will be rescheduled for the new time steps. * * \note At the beginnig of time step thread detects elapsed timers, then * unblocks object mutex and calls timer actors for those timers. It means * that actors can call timer thread object. And there won't be frequent * mutex locking/unlocking operations for building and processing * list of elapsed timers. This allows to process millions of timer actor * per second. * * \tparam Thread_Safety Thread-safety indicator. * Must be timertt::thread_safety::unsafe or timertt::thread_safety::safe. * * \tparam Timer_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Error_Logger type of logger for errors detected during * timer thread execution. Interface for error logger is defined * by default_error_logger class. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. */ template< typename Thread_Safety, typename Timer_Action, typename Error_Logger, typename Actor_Exception_Handler > class timer_wheel_engine : public engine_common< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler > { //! An alias for base class. using base_type = engine_common< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler >; struct timer_type; public : //! Type with default parameters for this engine. using defaults_type = timer_wheel_engine_defaults; //! Alias for timer_action type. using timer_action = typename base_type::timer_action; //! Alias for scoped timer object. using scoped_timer_object = scoped_timer_object_holder< timer_type >; //! Constructor with all parameters. timer_wheel_engine( //! Size of the wheel. unsigned int wheel_size, //! Size of time step for the timer_wheel. monotonic_clock::duration granularity, //! An error logger for timer thread. Error_Logger error_logger, //! An actor exception handler for timer thread. Actor_Exception_Handler exception_handler ) : base_type( error_logger, exception_handler ) , m_wheel_size( wheel_size ) , m_granularity( granularity ) { m_wheel.resize( wheel_size ); m_current_tick_border = monotonic_clock::now() + m_granularity; } //! Destructor. ~timer_wheel_engine() { clear_all(); } //! Create timer to be activated later. timer_object_holder< Thread_Safety > allocate() { return timer_object_holder< Thread_Safety >( new timer_type() ); } //! Activate timer and schedule it for execution. /*! * \return Value \a true is returned only when the first timer is added to * the empty wheel. * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is already activated. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. */ template< class Duration_1, class Duration_2 > bool activate( //! Timer to be activated. timer_object_holder< Thread_Safety > timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { auto * wheel_timer = timer.template cast_to< timer_type >(); ensure_timer_deactivated( wheel_timer ); wheel_timer->m_action.assign( std::move(action) ); // Timer must be taken under control. timer_object< Thread_Safety >::increment_references( wheel_timer ); // It is an active timer now. wheel_timer->m_status = timer_status::active; perform_insertion_info_wheel( wheel_timer, pause, period ); // If wheel was empty and this is the first timer added // the value of timer_count must be exactly 1. return 1 == this->m_timer_quantities.m_single_shot_count + this->m_timer_quantities.m_periodic_count; } /*! * \brief Perform an attempt to reschedule a timer. * * Before v.1.2.1 there was just one way to reschedule a timer: * method deactivate() must be called first and then method * activate() must be called for the same timer. This approach is * not fast because in the case of thread-safe engines it requires * two operations on a mutex. * * Since v.1.2.1 there is a reschedule() method which does deactivation * of a timer (if it is active) and then new activation of this timer. * All actions are performed by using just one operation on a mutex. * * \note * This operation can fail if the timer to be rescheduled is in processing. * Because of that it is recommended to use such operation for * timer_managers only. But even with timer_managers this operation * should be used with care. * * \attention * It move operator for a timer_action throws then timer will be * deactivated. The state for a timer_action itself will be unknown. * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is in processing right now. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. * * \since * v.1.2.1 */ template< class Duration_1, class Duration_2 > bool reschedule( //! Timer to be rescheduled. Must be in activated or deactivated state. timer_object_holder< Thread_Safety > timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { auto * wheel_timer = timer.template cast_to< timer_type >(); // If timer is deactivated the usual activation logic can be used. if( timer_status::deactivated == wheel_timer->m_status ) return this->activate( std::move(timer), pause, period, std::move(action) ); else if( timer_status::active != wheel_timer->m_status ) { // Timer which is in processing now can't be reactivated. throw std::runtime_error( "timer is in processing now, " "it can't be rescheduled" ); } // Timer must be removed from the wheel first. this->remove_timer_from_wheel( wheel_timer ); this->dec_timer_count( wheel_timer->kind() ); // If this assigment throws then we must deactivate the timer. try { wheel_timer->m_action.assign( std::move(action) ); } catch(...) { wheel_timer->m_status = timer_status::deactivated; timer_object< Thread_Safety >::decrement_references( wheel_timer ); // Exception must be rethrown; throw; } this->perform_insertion_info_wheel( wheel_timer, pause, period ); return false; } //! Deactivate timer and remove it from the wheel. void deactivate( timer_object_holder< Thread_Safety > timer ) { auto wheel_timer = timer.template cast_to< timer_type >(); if( timer_status::active == wheel_timer->m_status ) { // This is normal active timer. It can be safely // deactivated and destroyed. remove_timer_from_wheel( wheel_timer ); wheel_timer->m_status = timer_status::deactivated; // Release timer object. this->dec_timer_count( wheel_timer->kind() ); timer_object< Thread_Safety >::decrement_references( wheel_timer ); } else if( timer_status::wait_for_execution == wheel_timer->m_status ) { // This timer is in execution list right now. // We can only changed its status. // Final deactivation will be done after execution of // timers actions. wheel_timer->m_status = timer_status::wait_for_deactivation; } } /*! * \brief Build sublist of elapsed timers and process them all. */ template< typename Unique_Lock > void process_expired_timers( //! Object's lock. Unique_Lock & lock ) { /* * NOTE: It is possible that period between consequtive * calls to process_expired_timers will be longer than * m_granuality (it is possible when engine is used inside * timer_manager). In that case it is necessary to process * several wheel positions at once. */ const auto now = monotonic_clock::now(); for(;;) { if( !m_current_tick_processed ) { process_current_wheel_position( lock ); // After processing all current demands and rescheduling // all periodic demands the current_position must be // advanced. m_current_position += 1; if( m_current_position >= m_wheel_size ) m_current_position = 0; m_current_tick_processed = true; } if( now >= m_current_tick_border ) { // A switch to next tick is necessary. m_current_tick_border += m_granularity; m_current_tick_processed = false; } else break; } } /*! * \brief Is empty timer list? */ bool empty() const { return 0 == this->m_timer_quantities.m_single_shot_count && 0 == this->m_timer_quantities.m_periodic_count; } /*! * \brief Get time point of the next timer. * * \attention Must be called only when \a !empty(). */ monotonic_clock::time_point nearest_time_point() const { if( !m_current_tick_processed ) return monotonic_clock::now(); else return m_current_tick_border; } /*! * \brief Deactivate all timers and cleanup internal data structures. */ void clear_all() { for( auto & item : m_wheel ) { timer_type * timer = item.m_head; item = wheel_item(); while( timer ) { timer_type * t = timer; timer = timer->m_next; t->m_status = timer_status::deactivated; timer_object< Thread_Safety >::decrement_references( t ); } } // For the case of timer_engine restart. this->reset_timer_count(); this->m_current_tick_border = monotonic_clock::now() + m_granularity; this->m_current_position = 0; } private : //! Type of wheel timer. struct timer_type : public timer_object< Thread_Safety > { //! Status of the timer. typename threading_traits< Thread_Safety >::status_holder_type m_status; //! Position in the wheel. unsigned int m_position = 0; //! Full rolls of wheel before execution of demand. unsigned int m_full_rolls_left = 0; //! Period in ticks. /*! * Zero means that demand is single shot. */ unsigned int m_period = 0; //! Timer action. timer_action_holder< timer_action > m_action; //! Previous demand in the list. timer_type * m_prev = nullptr; //! Next demand in the list. timer_type * m_next = nullptr; timer_type() { m_status = timer_status::deactivated; } /*! * \brief Detect type of the timer (single-shot or periodic). * * \since * v.1.1.1 */ timer_kind kind() const { return !m_period ? timer_kind::single_shot : timer_kind::periodic; } }; //! Type of wheel's item. struct wheel_item { //! Head of the demand's list. timer_type * m_head = nullptr; //! Tail of the demand's list. timer_type * m_tail = nullptr; }; /*! * \name Object's attributes. * \{ */ //! Size of the wheel. const unsigned int m_wheel_size; //! Granularity of one time step. const monotonic_clock::duration m_granularity; //! Index of the current position in the wheel. unsigned int m_current_position = 0; //! Right border of the current tick. /*! * This is the time point at which new tick must be started. */ monotonic_clock::time_point m_current_tick_border; //! Has the current tick been processed? bool m_current_tick_processed = false; //! The wheel data. std::vector< wheel_item > m_wheel; /*! * \} */ /*! * \brief Hard check for deactivation state of the timer. * * \throw std::runtimer_error if timer is not deactivated. */ static void ensure_timer_deactivated( const timer_type * timer ) { if( timer_status::deactivated != timer->m_status ) throw std::runtime_error( "timer is not in 'deactivated' state" ); } /*! * \brief Perform insertion of a timer into wheel data structure. * * This method added to remove the duplication of code in * activate() and reschedule() methods. * * \note * This method doesn't change reference count to timer object. * * \since * v.1.2.1 */ template< class Duration_1, class Duration_2 > void perform_insertion_info_wheel( //! Timer to be inserted. timer_type * wheel_timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period ) { // Calculate the demand position in the wheel. this->set_position_in_the_wheel( wheel_timer, duration_to_ticks( pause ) ); // Special calculations for the periodic demand. if( monotonic_clock::duration::zero() != period ) wheel_timer->m_period = duration_to_ticks( period ); else wheel_timer->m_period = 0; // Timer now can be reinserted into the wheel. this->insert_demand_to_wheel( wheel_timer ); // Count of timers changed. this->inc_timer_count( wheel_timer->kind() ); } /*! * \brief Converion of duration to number of time steps. * * \note This implementation performs rounding up for duration * values. For example if granularity is 10ms and duration is * 15ms then result will be 2 time steps. * * \note Never return 0. If duration is less then granularity (even * after rounding up) the value 1 will be returned. E.g. timer * will be scheduled for the next time step. * * \tparam Duration actual type for duration representation. */ template< class Duration > unsigned int duration_to_ticks( //! Time duration to be converted in time steps count. Duration d ) const { auto d_units = std::chrono::duration_cast< monotonic_clock::duration >( d ) .count(); auto g_units = m_granularity.count(); unsigned int r = static_cast< unsigned int >( /* * Add g_units/2 for rounding up. * For example, if d is 24ms and granularity is 10 * it will be (24+5)=29, and result will be 2. * But if d is 25ms then (25+5)=30 and result will be 3. */ (d_units + g_units/2) / g_units ); if( !r ) r = 1; return r; } /*! * \brief Calculate and fill up wheel position for the timer. * * timer_type::m_position and timer_type::m_full_rolls_left * will be set for \a wheel_timer. */ void set_position_in_the_wheel( //! Timer to modify. timer_type * wheel_timer, //! Timeout for the timer is time steps. unsigned int pause_in_ticks ) const { wheel_timer->m_position = ( m_current_position + pause_in_ticks ) % m_wheel_size; wheel_timer->m_full_rolls_left = pause_in_ticks / m_wheel_size; } /*! * \brief Insert timer to the wheel. * * If there is a non-empty timer list for the timer wheel position * the \a wheel_timer will be added to the end of that list. */ void insert_demand_to_wheel( timer_type * wheel_timer ) { wheel_item & item = m_wheel[ wheel_timer->m_position ]; if( item.m_head ) { // There is a list of demands for the wheel position. // New demand must be added to the end of that list. wheel_timer->m_prev = item.m_tail; wheel_timer->m_next = nullptr; item.m_tail->m_next = wheel_timer; item.m_tail = wheel_timer; } else { // There is no list of demands for this wheel position yet. // New list must be started. wheel_timer->m_prev = wheel_timer->m_next = nullptr; item.m_head = wheel_timer; item.m_tail = wheel_timer; } } /*! * \brief Remove timer from the timer_wheel. */ void remove_timer_from_wheel( timer_type * wheel_timer ) { if( wheel_timer->m_prev ) wheel_timer->m_prev->m_next = wheel_timer->m_next; else m_wheel[ wheel_timer->m_position ].m_head = wheel_timer->m_next; if( wheel_timer->m_next ) wheel_timer->m_next->m_prev = wheel_timer->m_prev; else m_wheel[ wheel_timer->m_position ].m_tail = wheel_timer->m_prev; } /*! * \brief Detect elapsed timers for the current time step and * process them all. * * Object \a lock will be unlocked and then locked back. */ template< class Unique_Lock > void process_current_wheel_position( Unique_Lock & lock ) { timer_type * exec_list_head = make_exec_list(); if( exec_list_head ) { exec_actions( lock, exec_list_head ); utilize_exec_list( exec_list_head ); } } /*! * \brief Make list of elapsed timers to be executed. */ timer_type * make_exec_list() { timer_type * head = nullptr; timer_type * tail = nullptr; timer_type * timer = m_wheel[ m_current_position ].m_head; while( timer ) { if( timer->m_full_rolls_left ) { timer->m_full_rolls_left -= 1; timer = timer->m_next; } else { timer_type * t = timer; timer = timer->m_next; remove_timer_from_wheel( t ); t->m_status = timer_status::wait_for_execution; if( head ) { tail->m_next = t; t->m_prev = tail; t->m_next = nullptr; tail = t; } else { head = tail = t; t->m_prev = t->m_next = nullptr; } } } return head; } /*! * \brief Execute all active timers from the list. */ template< class Unique_Lock > void exec_actions( //! Object lock. //! This lock will be unlocked before execution of actions //! and locked back after. Unique_Lock & lock, //! Head of execution list. //! Cannot be nullptr. timer_type * head ) TIMERTT_NOEXCEPT { lock.unlock(); while( head ) { try { // Status of timer can be changed. So it must be checked // just before execution. If timer is waiting for // deregistration it must not be executed. if( timer_status::wait_for_execution == head->m_status ) head->m_action.exec(); } catch( const std::exception & x ) { // Note this invoke_noexcept_code_block() is not needed // if compiler supports noexcept. invoke_noexcept_code_block( [this, &x] { this->m_exception_handler( x ); } ); } catch( ... ) { // Logging should not throw exceptions. invoke_noexcept_code_block( [this] { std::ostringstream ss; ss << __FILE__ << "(" << __LINE__ << "): an unknown exception from timer action"; this->m_error_logger( ss.str() ); } ); std::abort(); } head = head->m_next; } lock.lock(); } /*! * \brief Process list of elapsed timers after execution of * its actions. * * Active periodic timers will be rescheduled. All other timers * will be deactivated and removed. */ void utilize_exec_list( //! Head of execution list. //! Cannot be null. timer_type * head ) { while( head ) { timer_type * t = head; head = head->m_next; // Actual periodic timer must be rescheduled. if( timer_status::wait_for_execution == t->m_status && t->m_period ) { // Timer is active again. t->m_status = timer_status::active; set_position_in_the_wheel( t, t->m_period ); insert_demand_to_wheel( t ); } else { // Timer must be utilized. t->m_status = timer_status::deactivated; this->dec_timer_count( t->kind() ); timer_object< Thread_Safety >::decrement_references( t ); } } } }; // // timer_list_engine_defaults // /*! * \brief Container for static method with default values for * timer_list engine. * * \note At this moment timer_list engine doesn't not need any * default parameter. Because of that this type is empty. * But it must be declared because of definition of * timer_list_engine::defaults_type. * * \since * v.1.1.0 */ struct timer_list_engine_defaults { }; // // timer_list_engine // /*! * \brief An engine for timer list mechanism. * * This engine uses double-linked list of timers as timer mechanism. * This list is ordered. The head of the list is the timer with the * minimum time point. * * When used with timer thread then the thread sleeps until the first timer in * the list elapsed. Then thread build sublist of elapsed timers and process * them. Single-shot timers are removed after processing. Periodic timers * rescheduled (inserted into appropriate places in the list). * * \note After building sublist of elapsed timers thread * unblocks object mutex and calls timer actors for timers from the sublist. * And locks object back right after processing. It means * that actors call call timer thread object. And there won't be frequent * mutex locking/unlocking operations for building and processing * sublist of elapsed timers. This allows to process millions of timer actor * per second. * * \attention This type of timer thread is good for situations * where there are many timers with equal pauses and repetition periods. * In that cases almost all timers will be added to the end of the * list. But if there are many timers with very different pauses then * operation of activating and rescheduling of timers will be too * expensive. Timer thread based on timer_wheel or timer_heap is * more appropriate for that scenario. * * \tparam Thread_Safety Thread-safety indicator. * Must be timertt::thread_safety::unsafe or timertt::thread_safety::safe. * * \tparam Time_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Error_Logger type of logger for errors detected during * timer thread execution. Interface for error logger is defined * by default_error_logger class. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. */ template< typename Thread_Safety, typename Time_Action, typename Error_Logger, typename Actor_Exception_Handler > class timer_list_engine : public engine_common< Thread_Safety, Time_Action, Error_Logger, Actor_Exception_Handler > { //! An alias for base class. using base_type = engine_common< Thread_Safety, Time_Action, Error_Logger, Actor_Exception_Handler >; // Forward declaration. struct timer_type; public : //! Type with default parameters for this engine. typedef timer_list_engine_defaults defaults_type; //! Alias for timer_action type. using timer_action = typename base_type::timer_action; //! Alias for scoped timer object. using scoped_timer_object = scoped_timer_object_holder< timer_type >; //! Default constructor. timer_list_engine() : timer_list_engine( Error_Logger(), Actor_Exception_Handler() ) {} //! Constructor with all parameters. timer_list_engine( //! An error logger for timer thread. Error_Logger error_logger, //! An actor exception handler for timer thread. Actor_Exception_Handler exception_handler ) : base_type( error_logger, exception_handler ) { } ~timer_list_engine() { clear_all(); } //! Create timer to be activated later. timer_object_holder< Thread_Safety > allocate() { return timer_object_holder< Thread_Safety >( new timer_type() ); } //! Activate timer and schedule it for execution. /*! * * \return true if the new timer is the first timer in the list. * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is already activated. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. */ template< class Duration_1, class Duration_2 > bool activate( //! Timer to be activated. timer_object_holder< Thread_Safety > timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { auto list_timer = timer.template cast_to< timer_type >(); ensure_timer_deactivated( list_timer ); // Timer object must be correctly (re)initialized. list_timer->m_action.assign( std::move( action ) ); list_timer->m_when = monotonic_clock::now() + pause; list_timer->m_period = std::chrono::duration_cast< monotonic_clock::duration >( period ); // Timer must be taken under control. timer_object< Thread_Safety >::increment_references( list_timer ); // It is an active timer now. list_timer->m_status = timer_status::active; insert_timer_to_list( list_timer ); // Count of timers in the list changed. this->inc_timer_count( list_timer->kind() ); return list_timer == m_head; } /*! * \brief Perform an attempt to reschedule a timer. * * Before v.1.2.1 there was just one way to reschedule a timer: * method deactivate() must be called first and then method * activate() must be called for the same timer. This approach is * not fast because in the case of thread-safe engines it requires * two operations on a mutex. * * Since v.1.2.1 there is a reschedule() method which does deactivation * of a timer (if it is active) and then new activation of this timer. * All actions are performed by using just one operation on a mutex. * * \note * This operation can fail if the timer to be rescheduled is in processing. * Because of that it is recommended to use such operation for * timer_managers only. But even with timer_managers this operation * should be used with care. * * \attention * It move operator for a timer_action throws then timer will be * deactivated. The state for a timer_action itself will be unknown. * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is in processing right now. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. * * \since * v.1.2.1 */ template< class Duration_1, class Duration_2 > bool reschedule( //! Timer to be rescheduled. timer_object_holder< Thread_Safety > timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { auto list_timer = timer.template cast_to< timer_type >(); // If timer is deactivated the usual activation logic can be used. if( timer_status::deactivated == list_timer->m_status ) return this->activate( std::move(timer), pause, period, std::move(action) ); else if( timer_status::active != list_timer->m_status ) { // Timer which is in processing now can't be reactivated. throw std::runtime_error( "timer is in processing now, " "it can't be rescheduled" ); } // Timer must be removed from the list first. this->remove_timer_from_list( list_timer ); this->dec_timer_count( list_timer->kind() ); // Timer object must be correctly (re)initialized. // If this assigment throws then we must deactivate the timer. try { list_timer->m_action.assign( std::move(action) ); } catch(...) { list_timer->m_status = timer_status::deactivated; timer_object< Thread_Safety >::decrement_references( list_timer ); // Exception must be rethrown; throw; } list_timer->m_when = monotonic_clock::now() + pause; list_timer->m_period = std::chrono::duration_cast< monotonic_clock::duration >( period ); // Updated timer must be placed into the list. this->insert_timer_to_list( list_timer ); // Count of timers in the list changed. this->inc_timer_count( list_timer->kind() ); return list_timer == m_head; } //! Deactivate timer and remove it from the list. void deactivate( //! Timer to be deactivated. timer_object_holder< Thread_Safety > timer ) { auto list_timer = timer.template cast_to< timer_type >(); if( timer_status::active == list_timer->m_status ) { // This is normal active timer. It can be safely // deactivated and destroyed. remove_timer_from_list( list_timer ); // Count of timers in the list changed. this->dec_timer_count( list_timer->kind() ); list_timer->m_status = timer_status::deactivated; // Release timer object. timer_object< Thread_Safety >::decrement_references( list_timer ); } else if( timer_status::wait_for_execution == list_timer->m_status ) { // This timer is in execution list right now. // We can only change its status. // Final deactivation will be done after execution of // timers actions. list_timer->m_status = timer_status::wait_for_deactivation; } } /*! * \brief Build sublist of elapsed timers and process them all. * * Object is unlocked and then locked back. */ template< typename Unique_Lock > void process_expired_timers( //! Object's lock. Unique_Lock & lock ) { timer_type * exec_list_head = make_exec_list(); if( exec_list_head ) { exec_actions( lock, exec_list_head ); utilize_exec_list( exec_list_head ); } } /*! * \brief Is empty timer list? * * \return true if there is no more timers. */ bool empty() const { return nullptr == m_head; } /*! * \brief Get time point of the next timer. * * \attention Must be called only when \a !empty(). */ monotonic_clock::time_point nearest_time_point() const { return m_head->m_when; } /*! * \brief Deactivate all timers and cleanup internal data structures. */ void clear_all() { while( m_head ) { auto t = m_head; m_head = m_head->m_next; t->m_status = timer_status::deactivated; timer_object< Thread_Safety >::decrement_references( t ); } // There are no more timers in the list. this->reset_timer_count(); m_tail = nullptr; } private : //! Type of list timer. struct timer_type : public timer_object< Thread_Safety > { //! Status of the timer. typename threading_traits< Thread_Safety >::status_holder_type m_status; //! Time of execution for this timer. monotonic_clock::time_point m_when; //! Period in ticks. /*! * Zero means that demand is single shot. */ monotonic_clock::duration m_period; //! Timer action. timer_action_holder< timer_action > m_action; //! Previous demand in the list. timer_type * m_prev = nullptr; //! Next demand in the list. timer_type * m_next = nullptr; timer_type() { m_status = timer_status::deactivated; } /*! * \brief Detect type of timer (single-shot or periodic). * * \since * v.1.1.1 */ timer_kind kind() const { return monotonic_clock::duration::zero() == m_period ? timer_kind::single_shot : timer_kind::periodic; } }; /*! * \name Object's attributes. * \{ */ //! Head of the list of timers. timer_type * m_head = nullptr; //! Tail of the list of timers. timer_type * m_tail = nullptr; /*! * \} */ /*! * \brief Hard check for deactivation state of the timer. * * \throw std::runtimer_error if timer is not deactivated. */ static void ensure_timer_deactivated( const timer_type * timer ) { if( timer_status::deactivated != timer->m_status ) throw std::runtime_error( "timer is not in 'deactivated' state" ); } //! Insert timer to the list. /*! * Insertion starts from the tail of the list. And if \a timer * has lower timer_type::m_when value then the last list item * there is an loop of searching appropriate place by going to * the head of the list. * * Doesn't increment reference count for \a timer. */ void insert_timer_to_list( //! Timer to be inserted. timer_type * timer ) { timer_type * point = m_tail; while( point ) { if( point->m_when > timer->m_when ) point = point->m_prev; else { // This is a point to insertion (new timer must be // next to 'point' item). timer->m_next = point->m_next; if( point->m_next ) point->m_next->m_prev = timer; point->m_next = timer; timer->m_prev = point; if( point == m_tail ) // timer must become a new tail for the list. m_tail = timer; return; } } // Timer must go to the head of the list. timer->m_prev = nullptr; timer->m_next = m_head; if( m_head ) m_head->m_prev = timer; m_head = timer; if( !m_tail ) // List was empty. So there must be new tail of the list. m_tail = timer; } //! Remove the timer from the list. /*! * Doesn't decrement reference count for \a timer. */ void remove_timer_from_list( timer_type * timer ) { if( timer->m_prev ) timer->m_prev->m_next = timer->m_next; else m_head = timer->m_next; if( timer->m_next ) timer->m_next->m_prev = timer->m_prev; else m_tail = timer->m_prev; } /*! * \brief Build sublist of elapsed timers. * * All timers in the sublist receive timer_status::wait_for_execution * status. */ timer_type * make_exec_list() { // If there is no timer return empty list immidiately. if( !m_head ) return nullptr; auto tail = m_head; const auto now = monotonic_clock::now(); // Search the first not-elapsed-yet timer. while( tail && now >= tail->m_when ) { tail->m_status = timer_status::wait_for_execution; tail = tail->m_next; } if( tail == m_head ) // There is no elapsed timers. return nullptr; auto exec_list_head = m_head; if( tail ) { // This item must be the new head of the list. m_head = tail; tail->m_prev->m_next = nullptr; tail->m_prev = nullptr; } else { // Whole timer list is the execution list. m_head = m_tail = nullptr; } return exec_list_head; } /*! * \brief Execute all active timers in the sublist. * * Object is unlocked and locked back after sublist processing. */ template< class Unique_Lock > void exec_actions( //! Object lock. //! This lock will be unlocked before execution of actions //! and locked back after. Unique_Lock & lock, //! Head of execution list. //! Cannot be nullptr. timer_type * head ) TIMERTT_NOEXCEPT { lock.unlock(); while( head ) { try { // Status of timer can be changed. So it must be checked // just before execution. If timer is waiting for // deregistration it must not be executed. if( timer_status::wait_for_execution == head->m_status ) head->m_action.exec(); } catch( const std::exception & x ) { // Note this invoke_noexcept_code_block() is not needed // if compiler supports noexcept. invoke_noexcept_code_block( [this, &x] { this->m_exception_handler( x ); } ); } catch( ... ) { // Logging should not throw exceptions. invoke_noexcept_code_block( [this] { std::ostringstream ss; ss << __FILE__ << "(" << __LINE__ << "): an unknown exception from timer action"; this->m_error_logger( ss.str() ); } ); std::abort(); } head = head->m_next; } lock.lock(); } /*! * \brief Process list of elapsed timers after execution of * its actions. * * Active periodic timers will be rescheduled. All other timers * will be deactivated and removed. */ void utilize_exec_list( //! Head of execution list. //! Cannot be null. timer_type * head ) { while( head ) { auto t = head; head = head->m_next; // Actual periodic timer must be rescheduled. if( timer_status::wait_for_execution == t->m_status && monotonic_clock::duration::zero() != t->m_period ) { t->m_when += t->m_period; t->m_status = timer_status::active; insert_timer_to_list( t ); } else { // Timer must be utilized. this->dec_timer_count( t->kind() ); t->m_status = timer_status::deactivated; timer_object< Thread_Safety >::decrement_references( t ); } } } }; // // timer_heap_engine_defaults // /*! * \brief Container for static method with default values for * timer_heap engine. * * \since * v.1.1.0 */ struct timer_heap_engine_defaults { //! Default initial capacity of heap-array. inline static std::size_t default_initial_heap_capacity() { return 64; } }; // // timer_heap_engine // /*! * \brief An engine for timer heap mechanism. * * This timer engine uses timer mechanism based on * heap data structure. * The timer with the earlier time point is on the top of * the heap. When this timer elapsed and removed next timer with the * eralier time point is going to the top of the heap. * * This implementation uses array-based binary heap. * The array is growing as necessary to hold all the timers. The initial size * of that array can be specified in the constructor. * * \note Unlike timer_wheel and timer_list threads this thread unlock and lock * its mutex for processing every timers. It means that processing speed of * this thread will be slower then for timer_wheel or timer_list threads. And * has very efficient activation and deactivation procedures (unlike timer_list * thread). * * \tparam Thread_Safety Thread-safety indicator. * Must be timertt::thread_safety::unsafe or timertt::thread_safety::safe. * * \tparam Timer_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Error_Logger type of logger for errors detected during * timer thread execution. Interface for error logger is defined * by default_error_logger class. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. */ template< typename Thread_Safety, typename Timer_Action, typename Error_Logger, typename Actor_Exception_Handler > class timer_heap_engine : public engine_common< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler > { //! An alias for base class. using base_type = engine_common< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler >; // Forward declaration. struct timer_type; public : //! Type with default parameters for this engine. typedef timer_heap_engine_defaults defaults_type; //! Alias for timer_action type. using timer_action = typename base_type::timer_action; //! Alias for scoped timer object. using scoped_timer_object = scoped_timer_object_holder< timer_type >; //! Constructor with all parameters. timer_heap_engine( //! An initial size for heap array. std::size_t initial_heap_capacity, //! An error logger for timer thread. Error_Logger error_logger, //! An actor exception handler for timer thread. Actor_Exception_Handler exception_handler ) : base_type( error_logger, exception_handler ) { m_heap.reserve( initial_heap_capacity ); } ~timer_heap_engine() { clear_all(); } //! Create timer to be activated later. timer_object_holder< Thread_Safety > allocate() { return timer_object_holder< Thread_Safety >( new timer_type() ); } //! Activate timer and schedule it for execution. /*! * \return true is new timer is a timer on the top of the heap * (has earlier expiration time). * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is already activated. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. */ template< class Duration_1, class Duration_2 > bool activate( //! Timer to be activated. timer_object_holder< Thread_Safety > timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { auto heap_timer = timer.template cast_to< timer_type >(); ensure_timer_deactivated( heap_timer ); // Timer object must be correctly (re)initialized. heap_timer->m_action.assign( std::move( action ) ); heap_timer->m_when = monotonic_clock::now() + pause; heap_timer->m_period = std::chrono::duration_cast< monotonic_clock::duration >( period ); // Timer must be taken under control. timer_object< Thread_Safety >::increment_references( heap_timer ); // Timer will be marked as active during insertion into // heap structure. heap_add( heap_timer ); // Count of timers must be incremented. this->inc_timer_count( heap_timer->kind() ); return heap_timer == heap_head(); } /*! * \brief Perform an attempt to reschedule a timer. * * Before v.1.2.1 there was just one way to reschedule a timer: * method deactivate() must be called first and then method * activate() must be called for the same timer. This approach is * not fast because in the case of thread-safe engines it requires * two operations on a mutex. * * Since v.1.2.1 there is a reschedule() method which does deactivation * of a timer (if it is active) and then new activation of this timer. * All actions are performed by using just one operation on a mutex. * * \note * This operation can fail if the timer to be rescheduled is in processing. * Because of that it is recommended to use such operation for * timer_managers only. But even with timer_managers this operation * should be used with care. * * \attention * It move operator for a timer_action throws then timer will be * deactivated. The state for a timer_action itself will be unknown. * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is in processing right now. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. * * \since * v.1.2.1 */ template< class Duration_1, class Duration_2 > bool reschedule( //! Timer to be rescheduled. timer_object_holder< Thread_Safety > timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { auto heap_timer = timer.template cast_to< timer_type >(); // If timer is deactivated the usual activation logic can be used. if( heap_timer->deactivated() ) return this->activate( std::move(timer), pause, period, std::move(action) ); else if( heap_timer == m_timer_in_processing ) { // Timer which is in processing now can't be reactivated. throw std::runtime_error( "timer is in processing now, " "it can't be rescheduled" ); } // Timer must be removed from heap first. heap_remove( heap_timer ); // Count of timers changed. this->dec_timer_count( heap_timer->kind() ); // Timer object must be correctly (re)initialized. // If this assigment throws then we must deactivate the timer. try { heap_timer->m_action.assign( std::move(action) ); } catch(...) { heap_timer->deactivate(); timer_object< Thread_Safety >::decrement_references( heap_timer ); // Exception must be rethrown; throw; } heap_timer->m_when = monotonic_clock::now() + pause; heap_timer->m_period = std::chrono::duration_cast< monotonic_clock::duration >( period ); // Timer will be marked as active during insertion into // heap structure. heap_add( heap_timer ); // Count of timers must be incremented. this->inc_timer_count( heap_timer->kind() ); return heap_timer == heap_head(); } //! Deactivate timer and remove it from the list. void deactivate( //! Timer to be deactivated. timer_object_holder< Thread_Safety > timer ) { auto heap_timer = timer.template cast_to< timer_type >(); if( !heap_timer->deactivated() ) { // If this timer is not in processing now it can // be safely destroyed. if( heap_timer != m_timer_in_processing ) { heap_remove( heap_timer ); // Count of timers changed. this->dec_timer_count( heap_timer->kind() ); // We can deactivate timer only after removing. // Because deactivation drops actual timer position. heap_timer->deactivate(); // Release timer object. timer_object< Thread_Safety >::decrement_references( heap_timer ); } else { // Otherwise m_timer_in_processing will be destroyed // after end of timer action processing. // But it must be deactivated right now. heap_timer->deactivate(); } } } /*! * \brief Process all expired timers from the heap. * * \note time_point for timers expiration detection is got * only once at the begining on the method. * * \note \a lock unlocked and then locked back for every * timer action execution. */ template< typename Unique_Lock > void process_expired_timers( //! Object's lock. Unique_Lock & lock ) { // Process timers in loop until there are elapsed timers. const auto now = monotonic_clock::now(); while( !heap_empty() && now > heap_head()->m_when ) { m_timer_in_processing = heap_head(); heap_remove( m_timer_in_processing ); execute_timer_in_processing( lock ); // If timer has become deactive it must be removed even // it is periodic timer. if( m_timer_in_processing->deactivated() || m_timer_in_processing->single_shot() ) { // Count of timers changed. this->dec_timer_count( m_timer_in_processing->kind() ); m_timer_in_processing->deactivate(); timer_object< Thread_Safety >::decrement_references( m_timer_in_processing ); } else { // This is active periodic timer and it must be resheduled. m_timer_in_processing->m_when += m_timer_in_processing->m_period; heap_add( m_timer_in_processing ); } m_timer_in_processing = nullptr; } } /*! * \brief Is empty timer list? * * \return true if there is no more timers. */ bool empty() const { return heap_empty(); } /*! * \brief Get time point of the next timer. * * \attention Must be called only when \a !empty(). */ monotonic_clock::time_point nearest_time_point() const { return heap_head()->m_when; } //! Clear all timer demands. void clear_all() { for( auto t : m_heap ) { t->deactivate(); timer_object< Thread_Safety >::decrement_references( t ); } this->reset_timer_count(); m_heap.clear(); } private : //! Type of heap timer. struct timer_type : public timer_object< Thread_Safety > { //! A special value which means that timer is deactivated. /*! * This value is illegal index in heap-array because * position numbers in heap-array are started from 1, not from 0. */ static const std::size_t deactivation_indicator = 0; //! Time of execution for this timer. monotonic_clock::time_point m_when; //! Period in ticks. /*! * Zero means that demand is single shot. */ monotonic_clock::duration m_period; //! Timer action. timer_action_holder< timer_action > m_action; //! Position in the heap-array. std::size_t m_position = deactivation_indicator; //! Is timer deactivated. bool deactivated() const { return deactivation_indicator == m_position; } //! Set deactivation indicator on. void deactivate() { m_position = deactivation_indicator; } //! Is this is single shot timer? bool single_shot() const { return monotonic_clock::duration::zero() == m_period; } /*! * \brief Detect type of timer (single-shot or periodic). * * \since * v.1.1.1 */ timer_kind kind() const { return single_shot() ? timer_kind::single_shot : timer_kind::periodic; } }; /*! * \name Object's attributes. * \{ */ //! Array for holding heap data structure. std::vector< timer_type * > m_heap; //! Timer which is currently in processing. timer_type * m_timer_in_processing = nullptr; /*! * \} */ /*! * \brief Hard check for deactivation state of the timer. * * \throw std::runtimer_error if timer is not deactivated. */ static void ensure_timer_deactivated( const timer_type * timer ) { if( !timer->deactivated() ) throw std::runtime_error( "timer is not in 'deactivated' state" ); } //! Execute the current timer. template< class Unique_Lock > void execute_timer_in_processing( //! Object lock. //! This lock will be unlocked before execution of actions //! and locked back after. Unique_Lock & lock ) TIMERTT_NOEXCEPT { lock.unlock(); try { m_timer_in_processing->m_action.exec(); } catch( const std::exception & x ) { // Note this invoke_noexcept_code_block() is not needed // if compiler supports noexcept. invoke_noexcept_code_block( [this, &x] { this->m_exception_handler( x ); } ); } catch( ... ) { // Logging should not throw exceptions. invoke_noexcept_code_block( [this] { std::ostringstream ss; ss << __FILE__ << "(" << __LINE__ << "): an unknown exception from timer action"; this->m_error_logger( ss.str() ); } ); std::abort(); } lock.lock(); } /*! * \name Methods for work with heap data structure. * \{ */ //! Is heap data structure empty? bool heap_empty() const { return m_heap.empty(); } //! Get the minimal timer. /*! * \attention This method must be called only on non-empty heap. */ timer_type * heap_head() const { return m_heap.front(); } //! Add new timer to the heap data structure. void heap_add( timer_type * timer ) { timer->m_position = m_heap.size() + 1; m_heap.push_back( timer ); while( 1 != timer->m_position ) { auto parent = heap_item( timer->m_position / 2 ); if( parent->m_when > timer->m_when ) { // timer must be heap-up on the place of the parent node. heap_swap( timer, parent ); } else // There is no need to modify heap structure anymore. break; } } //! Remove timer from the heap data structure. void heap_remove( timer_type * timer ) { if( timer->m_position == m_heap.size() ) { // A special case: timer to remove is a last added item // in the heap. It could be simply removed from heap // without any other actions. m_heap.pop_back(); } else { auto last_item = m_heap.back(); heap_swap( timer, last_item ); m_heap.pop_back(); // If timer->m_position is not 1 then we can violate // binary heap order. The last_item can now: // // - be less than its new parent; // - be greater than any of its new children. // // If the last_item is less than new parent then we // have to heap it up. If we move the last_item up then there // is no need to check order down of it. // // But if the last_item wasn't moved up then we have to // check the order down of it. bool should_be_checked_down = true; // NOTE: there is no need to check upward direction // if removed timer was the head of the heap. while( last_item->m_position > std::size_t{ 1 } ) { auto parent = heap_item( last_item->m_position / std::size_t{ 2 } ); if( parent->m_when > last_item->m_when ) { // Timer must be heap-up on the place of the parent node. heap_swap( parent, last_item ); // There is no need to do additional check in downward // order (because parent is guaranteed to be less then // children noded). should_be_checked_down = false; } else { // There is no need to modify heap structure anymore. break; } } if( should_be_checked_down ) heap_down_item( last_item ); } } //! Swap two heap nodes. void heap_swap( timer_type * a, timer_type * b ) { m_heap[ a->m_position - 1 ] = b; m_heap[ b->m_position - 1 ] = a; std::swap( a->m_position, b->m_position ); } //! Helper method to ensure that children of the specified //! item are greater. //! //! If this isn't true then \a subtree_head will be moved down //! the heap until the appropriate place will be found. void heap_down_item( timer_type * subtree_head ) { do { auto left_index = subtree_head->m_position * std::size_t{ 2 }; auto right_index = left_index + std::size_t{ 1 }; auto min_index = subtree_head->m_position; if( left_index <= m_heap.size() && heap_item( left_index )->m_when <= heap_item( min_index )->m_when ) min_index = left_index; if( right_index <= m_heap.size() && heap_item( right_index )->m_when <= heap_item( min_index )->m_when ) min_index = right_index; if( min_index != subtree_head->m_position ) { heap_swap( subtree_head, heap_item( min_index ) ); } else // Heap structure is correct. break; } while( true ); } //! Get timer by it index. /*! * This accessor work with respect that positions are started from 1. */ timer_type * heap_item( std::size_t position ) const { return m_heap[ position - 1 ]; } /*! * \} */ }; // // thread_unsafe_manager_mixin // /*! * \brief A mixin which must be used as base class for not-thread-safe * timer managers. * * \since * v.1.1.0 */ struct thread_unsafe_manager_mixin { //! A empty class for an object's lock emulation. /*! * Instance of that class will be used in not-thread-safe * code in places where object's lock is necessary. * * Because this class is empty its usage will be removed by * optimized compiler. */ class lock_guard { public : lock_guard( thread_unsafe_manager_mixin & ) {} void lock() {} void unlock() {} }; void ensure_started() {} void notify() {} }; // // thread_safe_manager_mixin // /*! * \brief A mixin which must be used as base class for thread-safe * timer managers. * * \since * v.1.1.0 */ struct thread_safe_manager_mixin { //! Timer manager's lock. std::mutex m_lock; //! A special wrapper around actual std::unique_lock. class lock_guard { std::unique_lock< std::mutex > m_lock; public : lock_guard( thread_safe_manager_mixin & self ) : m_lock( self.m_lock ) {} void lock() { m_lock.lock(); } void unlock() { m_lock.unlock(); } }; void ensure_started() {} void notify() {} }; // // thread_mixin // /*! * \brief A mixin which must be used as base class for timer threads. * * \since * v.1.1.0 */ struct thread_mixin { //! Timer thread's lock. std::mutex m_lock; //! Condition variable for waiting for next event. std::condition_variable m_condition; //! Underlying thread object. /*! * \note Will be created during timer thread start and * destroyed after timer thread shutdown. */ std::shared_ptr< std::thread > m_thread; //! A special wrapper around actual std::unique_lock. class lock_guard { std::unique_lock< std::mutex > m_lock; public : lock_guard( thread_mixin & self ) : m_lock( self.m_lock ) {} void lock() { m_lock.lock(); } void unlock() { m_lock.unlock(); } //! Accessor for actual std::unique_lock object. std::unique_lock< std::mutex > & actual_lock() { return m_lock; } }; //! Checks that timer thread is really started. /*! * \throw std::exception if thread is not started. */ void ensure_started() { if( !m_thread ) throw std::runtime_error( "timer thread is not started" ); } //! Sends notification to timer thread. void notify() { m_condition.notify_one(); } }; /*! * \brief A type-container for types of engine-consumers. * * \since * v.1.1.0 */ struct consumer_type { /*! * \brief Indicator that an engine will be owned by timer manager. * * \since * v.1.1.0 */ struct manager {}; /*! * \brief Indicator that an engine will be owned by timer thread. * * \since * v.1.1.0 */ struct thread {}; }; /*! * \brief A selector of actual mixin type for timer manager or timer thread. * * \attention This type is empty because all actual content will be * defined in specializations. * * \since * v.1.1.0 */ template< typename Thread_Safety, typename Consumer > struct mixin_selector { }; /*! * \brief A selector of actual mixin type for not-thread-safe timer manager. * * \since * v.1.1.0 */ template<> struct mixin_selector< thread_safety::unsafe, consumer_type::manager > { //! Actual type of the mixin. using type = thread_unsafe_manager_mixin; }; /*! * \brief A selector of actual mixin type for thread-safe timer manager. * * \since * v.1.1.0 */ template<> struct mixin_selector< thread_safety::safe, consumer_type::manager > { //! Actual type of the mixin. using type = thread_safe_manager_mixin; }; /*! * \brief A selector of actual mixin type for timer thread. * * \since * v.1.1.0 */ template<> struct mixin_selector< thread_safety::safe, consumer_type::thread > { //! Actual type of the mixin. using type = thread_mixin; }; // // basic_methods_impl_mixin // /*! * \brief A implementation of basic methods for timer managers and * timer threads. * * \tparam Engine actual type of engine to be used. * \tparam Consumer type of engine consumer (e.g. consumer_type::manager or * consumer_type::thread). * * \since * v.1.1.0 */ template< typename Engine, typename Consumer > class basic_methods_impl_mixin : protected mixin_selector< typename Engine::thread_safety, Consumer >::type , public Engine::defaults_type { //! Shorthand for actual mixin type. using mixin_type = typename mixin_selector< typename Engine::thread_safety, Consumer >::type; public : /*! * \brief A typedef for thread safety type from Engine. * * \since * v.1.1.2 */ using thread_safety = typename Engine::thread_safety; //! An alias for timer_action type. using timer_action = typename Engine::timer_action; //! An alias for scoped timer objects. using scoped_timer_object = typename Engine::scoped_timer_object; //! Shorthand for timer objects' smart pointer. /*! * \note * Since v.1.2.1 it is a public type name. */ using timer_holder = timer_object_holder< typename Engine::thread_safety >; //! Constructor with all parameters. template< typename... Args > basic_methods_impl_mixin( Args && ... args ) : m_engine( std::forward< Args >(args)... ) { } /*! * \brief Allocate of new timer object. * * \note This object must be activated by activate() methods. */ timer_holder allocate() { return m_engine.allocate(); } //! Activate timer and schedule it for execution. /*! * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is already activated. * * \tparam Duration_1 actual type which represents time duration. */ template< class Duration_1 > void activate( //! Timer to be activated. timer_holder timer, //! Pause for timer execution. Duration_1 pause, //! Action for the timer. timer_action action ) { activate( std::move( timer ), pause, monotonic_clock::duration::zero(), std::move( action ) ); } /*! * \brief Perform an attempt to reschedule a timer. * * Before v.1.2.1 there was just one way to reschedule a timer: * method deactivate() must be called first and then method * activate() must be called for the same timer. This approach is * not fast because in the case of thread-safe engines it requires * two operations on a mutex. * * Since v.1.2.1 there is a reschedule() method which does deactivation * of a timer (if it is active) and then new activation of this timer. * All actions are performed by using just one operation on a mutex. * * \note * This operation can fail if the timer to be rescheduled is in processing. * Because of that it is recommended to use such operation for * timer_managers only. But even with timer_managers this operation * should be used with care. * * \attention * It move operator for a timer_action throws then timer will be * deactivated. The state for a timer_action itself will be unknown. * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is in processing right now. * * \tparam Duration_1 actual type which represents time duration. * * \since * v.1.2.1 */ template< class Duration_1 > void reschedule( //! Timer to be rescheduled. timer_holder timer, //! Pause for timer execution. Duration_1 pause, //! Action for the timer. timer_action action ) { reschedule( std::move( timer ), pause, monotonic_clock::duration::zero(), std::move( action ) ); } //! Activate a scoped timer and schedule it for execution. /*! * * \note * A proper lifetime of this timer must be controlled by user. * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is already activated. * * \tparam Duration_1 actual type which represents time duration. * * \since * v.1.2.0 */ template< class Duration_1 > void activate( //! Timer to be activated. scoped_timer_object & timer, //! Pause for timer execution. Duration_1 pause, //! Action for the timer. timer_action action ) { this->activate( timer_holder{timer}, pause, action ); } //! Activate timer and schedule it for execution. /*! * There is no need to preallocate timer object. It will * be allocated automatically, but not be shown to user. * * \throw std::exception If timer thread is not started. * * \tparam Duration_1 actual type which represents time duration. */ template< class Duration_1 > void activate( //! Pause for timer execution. Duration_1 pause, //! Action for the timer. timer_action action ) { activate( allocate(), pause, monotonic_clock::duration::zero(), std::move( action ) ); } //! Activate timer and schedule it for execution. /*! * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is already activated. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. */ template< class Duration_1, class Duration_2 > void activate( //! Timer to be activated. timer_holder timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { typename mixin_type::lock_guard locker{ *this }; this->ensure_started(); if( m_engine.activate( std::move( timer ), pause, period, std::move( action ) ) ) this->notify(); } /*! * \brief Perform an attempt to reschedule a timer. * * Before v.1.2.1 there was just one way to reschedule a timer: * method deactivate() must be called first and then method * activate() must be called for the same timer. This approach is * not fast because in the case of thread-safe engines it requires * two operations on a mutex. * * Since v.1.2.1 there is a reschedule() method which does deactivation * of a timer (if it is active) and then new activation of this timer. * All actions are performed by using just one operation on a mutex. * * \note * This operation can fail if the timer to be rescheduled is in processing. * Because of that it is recommended to use such operation for * timer_managers only. But even with timer_managers this operation * should be used with care. * * \attention * It move operator for a timer_action throws then timer will be * deactivated. The state for a timer_action itself will be unknown. * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is in processing right now. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. * * \since * v.1.2.1 */ template< class Duration_1, class Duration_2 > void reschedule( //! Timer to be activated. timer_holder timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { typename mixin_type::lock_guard locker{ *this }; this->ensure_started(); if( m_engine.reschedule( std::move( timer ), pause, period, std::move( action ) ) ) this->notify(); } //! Activate a scoped timer and schedule it for execution. /*! * * \note * A proper lifetime of this timer must be controlled by user. * * \throw std::exception If timer thread is not started. * \throw std::exception If \a timer is already activated. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. * * \since * v.1.2.0 */ template< class Duration_1, class Duration_2 > void activate( //! Timer to be activated. scoped_timer_object & timer, //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { this->activate( timer_holder{timer}, pause, period, std::move(action) ); } //! Activate timer and schedule it for execution. /*! * There is no need to preallocate timer object. It will * be allocated automatically, but not be shown to user. * * \throw std::exception If timer thread is not started. * * \tparam Duration_1 actual type which represents time duration. * \tparam Duration_2 actual type which represents time duration. */ template< class Duration_1, class Duration_2 > void activate( //! Pause for timer execution. Duration_1 pause, //! Repetition period. //! If Duration_2::zero() == period then timer will be //! single-shot. Duration_2 period, //! Action for the timer. timer_action action ) { activate( allocate(), pause, period, std::move( action ) ); } //! Deactivate timer and remove it from the list. void deactivate( //! Timer to be deactivated. timer_holder timer ) { typename mixin_type::lock_guard locker{ *this }; m_engine.deactivate( timer ); } //! Deactivate timer and remove it from the list. /*! * \since * v.1.2.0 */ void deactivate( //! Timer to be deactivated. scoped_timer_object & timer ) { this->deactivate( timer_holder{timer} ); } /*! * \brief Count of timers of various types. * * \note This are quantities of all timers known to manager/thread. * Some of them can be already deactivated but not removed yet. * * \since * v.1.1.1 */ timer_quantities get_timer_quantities() { typename mixin_type::lock_guard locker{ *this }; return m_engine.get_timer_quantities(); } /*! * \brief Check for emptiness. * * \since * v.1.1.3 */ bool empty() { typename mixin_type::lock_guard locker{ *this }; return m_engine.empty(); } protected : //! Actual timer engine instance. Engine m_engine; }; // // manager_impl_template // /*! * \brief Template-based implementation of timer manager. * * \tparam Engine actual type of engine to be used. * * \since * v.1.1.0 */ template< typename Engine > class manager_impl_template : public basic_methods_impl_mixin< Engine, consumer_type::manager > { //! Shorthand for base type. using base_type = basic_methods_impl_mixin< Engine, consumer_type::manager >; public : //! Constructor with all parameters. template< typename... Args > manager_impl_template( Args && ... args ) : base_type( std::forward< Args >(args)... ) { } //! Reset all timers and return manager to the initial state. void reset() { typename manager_impl_template::lock_guard locker{ *this }; this->m_engine.clear_all(); } //! Perform processing of expired timers. void process_expired_timers() { typename manager_impl_template::lock_guard locker{ *this }; this->m_engine.process_expired_timers( locker ); } //! Get the time for next process_expired_timers invocation. /*! * \return tuple if there is a timer to process. Or * tuple if there is no timers to be processed. */ std::tuple< bool, monotonic_clock::time_point > nearest_time_point() { typename manager_impl_template::lock_guard locker{ *this }; auto e = this->m_engine.empty(); if( !e ) return std::make_tuple( true, this->m_engine.nearest_time_point() ); else return std::make_tuple( false, monotonic_clock::time_point() ); } //! Get the sleeping time before the earlist timer expiration. /*! * \return actual sleeping time if there is at least one timer. * Or \a default_timeout if there is no any timers. * * \tparam Duration type for \a default_timeout */ template< typename Duration > monotonic_clock::duration timeout_before_nearest_timer( //! Default timeout value which will be used if there is no any timers. Duration default_timeout ) { auto r = this->nearest_time_point(); if( std::get<0>( r ) ) { auto now = monotonic_clock::now(); const auto & f = std::get<1>( r ); if( now > f ) return monotonic_clock::duration( 0 ); else return (f - now); } else return default_timeout; } }; // // thread_impl_template // /*! * \brief Template-based implementation of timer thread. * * \tparam Engine actual type of engine to be used. * * \since * v.1.1.0 */ template< typename Engine > class thread_impl_template : public basic_methods_impl_mixin< Engine, consumer_type::thread > { //! Shorthand for base type. using base_type = basic_methods_impl_mixin< Engine, consumer_type::thread >; public : //! Constructor with all parameters. template< typename... Args > thread_impl_template( Args && ... args ) : base_type( std::forward< Args >(args)... ) { } //! Destructor. /*! * Calls shutdown_and_join() */ ~thread_impl_template() { shutdown_and_join(); } //! Start timer thread. /*! * \throw std::runtime_error if thread is already started. */ void start() { typename base_type::lock_guard locker{ *this }; if( this->m_thread ) throw std::runtime_error( "timer thread is already started" ); else this->m_shutdown = false; this->m_thread = std::make_shared< std::thread >( std::bind( &thread_impl_template::body, this ) ); } //! Initiate shutdown for the timer thread without waiting for completion. void shutdown() { typename base_type::lock_guard locker{ *this }; if( this->m_thread && !this->m_shutdown ) { this->m_shutdown = true; this->notify(); } } //! Wait for completion of timer thread. /*! * Method shutdown() must be called somewhere else. */ void join() { std::shared_ptr< std::thread > t; { typename base_type::lock_guard locker{ *this }; t = this->m_thread; } if( t ) { t->join(); typename base_type::lock_guard locker{ *this }; this->m_thread.reset(); } } //! Initiate shutdown and wait for completion. void shutdown_and_join() { shutdown(); join(); } protected : /*! * \name Object's attributes. * \{ */ //! Shutdown flag. bool m_shutdown = false; /*! * \} */ //! Thread body. void body() { typename base_type::lock_guard locker{ *this }; while( !this->m_shutdown ) { this->m_engine.process_expired_timers( locker ); sleep_for_next_event( locker ); } this->m_engine.clear_all(); } /*! * \brief Waiting for next event to process. * * If the list is not emply the thread will sleep until * time point of the first timer in the list. */ void sleep_for_next_event( //! Object's lock. //! The lock is necessary for waiting on condition variable. typename base_type::lock_guard & lock ) { if( !this->m_shutdown ) { if( !this->m_engine.empty() ) { auto time_point = this->m_engine.nearest_time_point(); this->m_condition.wait_until( lock.actual_lock(), time_point ); } else this->m_condition.wait( lock.actual_lock() ); } } }; } /* namespace details */ // // timer_wheel_thread_template // /*! * \brief A timer wheel thread template. * * Please see description of details::timer_wheel_engine for the details * of the timer wheel mechanism. * * \tparam Timer_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Error_Logger type of logger for errors detected during * timer thread execution. Interface for error logger is defined * by default_error_logger class. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. */ template< typename Timer_Action, typename Error_Logger, typename Actor_Exception_Handler > class timer_wheel_thread_template : public details::thread_impl_template< details::timer_wheel_engine< ::timertt::thread_safety::safe, Timer_Action, Error_Logger, Actor_Exception_Handler > > { using base_type = details::thread_impl_template< details::timer_wheel_engine< ::timertt::thread_safety::safe, Timer_Action, Error_Logger, Actor_Exception_Handler > >; public : //! Default constructor. timer_wheel_thread_template() : timer_wheel_thread_template( base_type::default_wheel_size(), base_type::default_granularity(), Error_Logger(), Actor_Exception_Handler() ) {} //! Constructor with wheel size and granularity parameters. timer_wheel_thread_template( //! Size of the wheel. unsigned int wheel_size, //! Size of time step for the timer_wheel. monotonic_clock::duration granularity ) : timer_wheel_thread_template( wheel_size, granularity, Error_Logger(), Actor_Exception_Handler() ) {} //! Constructor with all parameters. timer_wheel_thread_template( //! Size of the wheel. unsigned int wheel_size, //! Size of time step for the timer_wheel. monotonic_clock::duration granularity, //! An error logger for timer thread. Error_Logger error_logger, //! An actor exception handler for timer thread. Actor_Exception_Handler exception_handler ) : base_type( wheel_size, granularity, error_logger, exception_handler ) {} }; // // timer_wheel_manager_template // /*! * \brief A timer wheel manager template. * * \note Please see description of details::timer_wheel_engine for the details * of the timer wheel mechanism. * * \tparam Thread_Safety Thread-safety indicator. * Must be timertt::thread_safety::unsafe or timertt::thread_safety::safe. * * \tparam Timer_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Error_Logger type of logger for errors detected during * timer handling. Interface for error logger is defined * by default_error_logger class. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. * * \since * v.1.1.0 */ template< typename Thread_Safety, typename Timer_Action = default_timer_action_type, typename Error_Logger = default_error_logger, typename Actor_Exception_Handler = default_actor_exception_handler > class timer_wheel_manager_template : public details::manager_impl_template< details::timer_wheel_engine< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler > > { //! Shorthand for base type. using base_type = details::manager_impl_template< details::timer_wheel_engine< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler > >; public : //! Default constructor. timer_wheel_manager_template() : timer_wheel_manager_template( base_type::default_wheel_size(), base_type::default_granularity(), Error_Logger(), Actor_Exception_Handler() ) {} //! Constructor with wheel size and granularity parameters. timer_wheel_manager_template( //! Size of the wheel. unsigned int wheel_size, //! Size of time step for the timer_wheel. monotonic_clock::duration granularity ) : timer_wheel_manager_template( wheel_size, granularity, Error_Logger(), Actor_Exception_Handler() ) {} //! Constructor with all parameters. timer_wheel_manager_template( //! Size of the wheel. unsigned int wheel_size, //! Size of time step for the timer_wheel. monotonic_clock::duration granularity, //! An error logger for timer thread. Error_Logger error_logger, //! An actor exception handler for timer thread. Actor_Exception_Handler exception_handler ) : base_type( wheel_size, granularity, error_logger, exception_handler ) {} }; // // default_timer_wheel_thread // /*! * \brief Alias for timer_wheel_thread_template with the default * parameters. * * \since * v.1.2.0 */ using default_timer_wheel_thread = timer_wheel_thread_template< default_timer_action_type, default_error_logger, default_actor_exception_handler >; // // timer_list_thread_template // /*! * \brief A timer list thread template. * * \note Please see description of details::timer_list_engine for the * details of this timer mechanism. * * \tparam Timer_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Error_Logger type of logger for errors detected during * timer thread execution. Interface for error logger is defined * by default_error_logger class. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. */ template< typename Timer_Action, typename Error_Logger, typename Actor_Exception_Handler > class timer_list_thread_template : public details::thread_impl_template< details::timer_list_engine< ::timertt::thread_safety::safe, Timer_Action, Error_Logger, Actor_Exception_Handler > > { using base_type = details::thread_impl_template< details::timer_list_engine< ::timertt::thread_safety::safe, Timer_Action, Error_Logger, Actor_Exception_Handler > >; public : //! Default constructor. timer_list_thread_template() {} //! Constructor with all parameters. timer_list_thread_template( Error_Logger error_logger, Actor_Exception_Handler actor_exception_handler ) : base_type( error_logger, actor_exception_handler ) { } }; // // default_timer_list_thread // /*! * \brief Alias for timer_list_thread_template with the default * parameters. * * \since * v.1.2.0 */ using default_timer_list_thread = timer_list_thread_template< default_timer_action_type, default_error_logger, default_actor_exception_handler >; // // timer_list_manager_template // /*! * \brief A timer list thread template. * * \note Please see description of details::timer_list_engine for the * details of this timer mechanism. * * \tparam Thread_Safety Thread-safety indicator. * Must be timertt::thread_safety::unsafe or timertt::thread_safety::safe. * * \tparam Timer_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Error_Logger type of logger for errors detected during * timer handling. Interface for error logger is defined * by default_error_logger class. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. * * \since * v.1.1.0 */ template< typename Thread_Safety, typename Timer_Action = default_timer_action_type, typename Error_Logger = default_error_logger, typename Actor_Exception_Handler = default_actor_exception_handler > class timer_list_manager_template : public details::manager_impl_template< details::timer_list_engine< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler > > { using base_type = details::manager_impl_template< details::timer_list_engine< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler > >; public : //! Default constructor. timer_list_manager_template() {} //! Constructor with all parameters. timer_list_manager_template( Error_Logger error_logger, Actor_Exception_Handler actor_exception_handler ) : base_type( error_logger, actor_exception_handler ) { } }; // // timer_heap_thread_template // /*! * \brief A timer heap thread template. * * \note Please see description of details::timer_heap_engine for the * details about this timer mechanism. * * \tparam Error_Logger type of logger for errors detected during * timer thread execution. Interface for error logger is defined * by default_error_logger class. * * \tparam Timer_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. */ template< typename Timer_Action, typename Error_Logger, typename Actor_Exception_Handler > class timer_heap_thread_template : public details::thread_impl_template< details::timer_heap_engine< ::timertt::thread_safety::safe, Timer_Action, Error_Logger, Actor_Exception_Handler > > { //! Shorthand for base type. using base_type = details::thread_impl_template< details::timer_heap_engine< ::timertt::thread_safety::safe, Timer_Action, Error_Logger, Actor_Exception_Handler > >; public : //! Default constructor. /*! * Value default_initial_heap_capacity() is used as initial * heap array size. */ timer_heap_thread_template() : timer_heap_thread_template( base_type::default_initial_heap_capacity(), Error_Logger(), Actor_Exception_Handler() ) {} //! Constructor to specify initial capacity of heap-array. timer_heap_thread_template( //! An initial size for heap array. std::size_t initial_heap_capacity ) : timer_heap_thread_template( initial_heap_capacity, Error_Logger(), Actor_Exception_Handler() ) {} //! Constructor with all parameters. timer_heap_thread_template( //! An initial size for heap array. std::size_t initial_heap_capacity, //! An error logger for timer thread. Error_Logger error_logger, //! An actor exception handler for timer thread. Actor_Exception_Handler exception_handler ) : base_type( initial_heap_capacity, error_logger, exception_handler ) {} }; // // default_timer_heap_thread // /*! * \brief Alias for timer_heap_thread_template with the default * parameters. * * \since * v.1.2.0 */ using default_timer_heap_thread = timer_heap_thread_template< default_timer_action_type, default_error_logger, default_actor_exception_handler >; // // timer_heap_manager_template // /*! * \brief A timer heap manager template. * * \note Please see description of details::timer_heap_engine for the * details about this timer mechanism. * * \tparam Thread_Safety Thread-safety indicator. * Must be timertt::thread_safety::unsafe or timertt::thread_safety::safe. * * \tparam Timer_Action type of functor to perform an user-defined * action when timer expires. This must be Moveable and MoveConstructible * type. * * \tparam Error_Logger type of logger for errors detected during * timer handling. Interface for error logger is defined * by default_error_logger class. * * \tparam Actor_Exception_Handler type of handler for dealing with * exceptions thrown from timer actors. Interface for exception handler * is defined by default_actor_exception_handler. */ template< typename Thread_Safety, typename Timer_Action = default_timer_action_type, typename Error_Logger = default_error_logger, typename Actor_Exception_Handler = default_actor_exception_handler > class timer_heap_manager_template : public details::manager_impl_template< details::timer_heap_engine< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler > > { //! Shorthand for base type. using base_type = details::manager_impl_template< details::timer_heap_engine< Thread_Safety, Timer_Action, Error_Logger, Actor_Exception_Handler > >; public : //! Default constructor. /*! * Value default_initial_heap_capacity() is used as initial * heap array size. */ timer_heap_manager_template() : timer_heap_manager_template( base_type::default_initial_heap_capacity(), Error_Logger(), Actor_Exception_Handler() ) {} //! Constructor to specify initial capacity of heap-array. timer_heap_manager_template( //! An initial size for heap array. std::size_t initial_heap_capacity ) : timer_heap_manager_template( initial_heap_capacity, Error_Logger(), Actor_Exception_Handler() ) {} //! Constructor with all parameters. timer_heap_manager_template( //! An initial size for heap array. std::size_t initial_heap_capacity, //! An error logger for timer thread. Error_Logger error_logger, //! An actor exception handler for timer thread. Actor_Exception_Handler exception_handler ) : base_type( initial_heap_capacity, error_logger, exception_handler ) {} }; } /* namespace timertt */ #endif sobjectizer-5.8.6/dev/so_5/CMakeLists.txt000066400000000000000000000137351523065210100202440ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.14) if(NOT SOBJECTIZER_MASTER_PROJECT) set(SOBJECTIZER_MASTER_PROJECT OFF) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) set(SOBJECTIZER_MASTER_PROJECT ON) endif() endif() if((NOT SOBJECTIZER_BUILD_STATIC) AND (NOT SOBJECTIZER_BUILD_SHARED)) set(SOBJECTIZER_BUILD_STATIC ON) set(SOBJECTIZER_BUILD_SHARED ON) endif() if(NOT SOBJECTIZER_INSTALL) set(SOBJECTIZER_INSTALL ${SOBJECTIZER_MASTER_PROJECT}) endif() include(cmake/target.cmake) project(sobjectizer VERSION ${SO_5_VERSION} LANGUAGES CXX) set(PTHREDS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) set(SO_5_DEFS "-DSO_5_PRJ") if(WIN32) list(APPEND SO_5_DEFS "-DSO_5__PLATFORM_REQUIRES_CDECL") endif() get_filename_component(CURRENT_FILE_DIR ${CMAKE_CURRENT_LIST_FILE} DIRECTORY) get_filename_component(CURRENT_FILE_DIR ${CURRENT_FILE_DIR} DIRECTORY) set(SO_5_INCLUDE_PATH ${CURRENT_FILE_DIR}) unset(CURRENT_FILE_DIR) set(SO_5_SRC exception.cpp exception.cpp error_logger.cpp timers.cpp msg_tracing.cpp msg_tracing_individual.cpp wrapped_env.cpp message.cpp enveloped_msg.cpp handler_makers.cpp message_limit.cpp event_queue_hook.cpp mbox.cpp mchain.cpp event_exception_logger.cpp agent.cpp coop.cpp queue_locks_defaults_manager.cpp environment.cpp so_layer.cpp impl/msg_tracing_helpers.cpp impl/subscription_storage_iface.cpp impl/subscr_storage_vector_based.cpp impl/subscr_storage_flat_set_based.cpp impl/subscr_storage_map_based.cpp impl/subscr_storage_hash_table_based.cpp impl/subscr_storage_adaptive.cpp impl/process_unhandled_exception.cpp impl/named_local_mbox.cpp impl/mbox_core.cpp impl/coop_repository_basis.cpp impl/layer_core.cpp impl/mt_env_infrastructure.cpp impl/simple_mtsafe_st_env_infrastructure.cpp impl/simple_not_mtsafe_st_env_infrastructure.cpp stats/repository.cpp stats/std_names.cpp stats/impl/std_controller.cpp stats/impl/ds_agent_core_stats.cpp stats/impl/ds_mbox_core_stats.cpp stats/impl/ds_timer_thread_stats.cpp disp/abstract_work_thread.cpp disp/mpsc_queue_traits/pub.cpp disp/mpmc_queue_traits/pub.cpp disp/one_thread/pub.cpp disp/nef_one_thread/pub.cpp disp/active_obj/pub.cpp disp/active_group/pub.cpp disp/thread_pool/pub.cpp disp/adv_thread_pool/pub.cpp disp/nef_thread_pool/pub.cpp disp/prio_one_thread/strictly_ordered/pub.cpp disp/prio_one_thread/quoted_round_robin/pub.cpp disp/prio_dedicated_threads/one_per_prio/pub.cpp experimental/testing/v1/all.cpp ) function(setup_target_cxx_std TARGET_NAME) if(${CMAKE_VERSION} VERSION_LESS "3.8.0") set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS NO ) else() target_compile_features(${TARGET_NAME} PUBLIC cxx_std_17) endif() endfunction() set(SO_5_TARGETS_TO_INSTALL) if(SOBJECTIZER_BUILD_SHARED) add_library(${SO_5_SHARED_LIB} SHARED ${SO_5_SRC}) add_library(sobjectizer::SharedLib ALIAS ${SO_5_SHARED_LIB}) set_property(TARGET ${SO_5_SHARED_LIB} PROPERTY EXPORT_NAME SharedLib) setup_target_cxx_std(${SO_5_SHARED_LIB}) if(NOT WIN32) target_compile_options(${SO_5_SHARED_LIB} PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden ) endif() target_compile_definitions(${SO_5_SHARED_LIB} PRIVATE ${SO_5_DEFS} ) target_include_directories(${SO_5_SHARED_LIB} PUBLIC $ $ ) target_link_libraries(${SO_5_SHARED_LIB} ${CMAKE_THREAD_LIBS_INIT}) set(SO_5_EXT_LIBS ) if( ANDROID ) list(APPEND SO_5_EXT_LIBS ${ANDROID_LIBCRYSTAX_FILE}) list(APPEND SO_5_EXT_LIBS ${ANDROID_LIBSTDCXX_FILE}) endif() foreach(__extLibrary ${SO_5_EXT_LIBS}) add_custom_command(TARGET ${SO_5_SHARED_LIB} PRE_LINK COMMAND ${CMAKE_COMMAND} -E copy_if_different ${__extLibrary} $) endforeach() list(APPEND SO_5_TARGETS_TO_INSTALL ${SO_5_SHARED_LIB}) endif() if(SOBJECTIZER_BUILD_STATIC) add_library(${SO_5_STATIC_LIB} STATIC ${SO_5_SRC}) add_library(sobjectizer::StaticLib ALIAS ${SO_5_STATIC_LIB}) set_property(TARGET ${SO_5_STATIC_LIB} PROPERTY EXPORT_NAME StaticLib) setup_target_cxx_std(${SO_5_STATIC_LIB}) target_compile_definitions(${SO_5_STATIC_LIB} PRIVATE ${SO_5_DEFS} ) target_compile_definitions(${SO_5_STATIC_LIB} PUBLIC -DSO_5_STATIC_LIB ) target_include_directories(${SO_5_STATIC_LIB} PUBLIC $ $ ) target_link_libraries(${SO_5_STATIC_LIB} ${CMAKE_THREAD_LIBS_INIT}) list(APPEND SO_5_TARGETS_TO_INSTALL ${SO_5_STATIC_LIB}) endif() if(SOBJECTIZER_INSTALL) set(SO_5_CMAKE_FILES_DEST "lib/cmake/sobjectizer") install( TARGETS ${SO_5_TARGETS_TO_INSTALL} EXPORT SO_5_ALL_TARGETS LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin ) install( EXPORT SO_5_ALL_TARGETS FILE sobjectizer-targets.cmake NAMESPACE sobjectizer:: DESTINATION ${SO_5_CMAKE_FILES_DEST} ) if(SOBJECTIZER_BUILD_SHARED) foreach(__extLibrary ${SO_5_EXT_LIBS}) install(FILES ${__extLibrary} DESTINATION lib) endforeach() endif() file(GLOB_RECURSE ALL_SO_5_HEADERS RELATIVE ${CMAKE_CURRENT_LIST_DIR} *.hpp) foreach( HEADER_FILE ${ALL_SO_5_HEADERS} ) # Since v.5.7.2.3 install even files from impl subfolders # because it could be necessary for projects like so5extra. get_filename_component( DIR ${HEADER_FILE} PATH ) install( FILES ${HEADER_FILE} DESTINATION include/so_5/${DIR} ) endforeach() set(SO_5_CONFIG_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/sobjectizer-config-version.cmake") set(SO_5_CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/sobjectizer-config.cmake") include(CMakePackageConfigHelpers) write_basic_package_version_file( ${SO_5_CONFIG_VERSION_FILE} VERSION ${SO_5_VERSION} COMPATIBILITY ExactVersion ) configure_package_config_file( "cmake/sobjectizer-config.cmake.in" ${SO_5_CONFIG_FILE} INSTALL_DESTINATION ${SO_5_CMAKE_FILES_DEST} PATH_VARS SO_5_VERSION ) install( FILES ${SO_5_CONFIG_FILE} ${SO_5_CONFIG_VERSION_FILE} DESTINATION ${SO_5_CMAKE_FILES_DEST} ) endif() sobjectizer-5.8.6/dev/so_5/agent.cpp000066400000000000000000001373311523065210100173050ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { // // agent_identity_t::pointer_only_t // std::array agent_identity_t::pointer_only_t::make_c_string() const noexcept { static constexpr std::array hex_symbols{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; std::array result; auto it = std::copy( c_string_prefix.begin(), c_string_prefix.end(), result.begin() ); // NOTE: this code won't compile if there is no std::uintptr_t type, // but we don't care about such platforms at the moment. std::uintptr_t ptr_as_uint = reinterpret_cast(m_pointer_value); // Handle by 4 bits portions from the most significant bit. // Leading zeros are not skipped (for the simplicity of implementation). constexpr unsigned int half_octet = 4u; unsigned bits_to_process = sizeof(void *) * (half_octet * 2u); while( bits_to_process >= half_octet ) { const auto index = static_cast( (ptr_as_uint >> (bits_to_process - half_octet)) & 0x0Fu ); *(it++) = hex_symbols[ index ]; bits_to_process -= half_octet; } it = std::copy( c_string_suffix.begin(), c_string_suffix.end(), it ); *it = 0; return result; } namespace { [[nodiscard]] unsigned int ensure_valid_agent_name_length( std::size_t length ) { if( 0u == length ) SO_5_THROW_EXCEPTION( rc_empty_agent_name, "Name of an agent can't be empty" ); constexpr std::size_t max_unit_value = std::numeric_limits::max(); if( max_unit_value < length ) SO_5_THROW_EXCEPTION( rc_empty_agent_name, "Name of an agent is too long (length should fit " "into unsigned int)" ); return static_cast( length ); } } /* namespace anonymous */ // // name_for_agent_t // name_for_agent_t::name_for_agent_t() : m_length{} {} name_for_agent_t::name_for_agent_t( std::string_view value ) : m_length{ ensure_valid_agent_name_length( value.size() ) } { m_value.reset( new char[ value.size() ] ); std::copy( std::begin(value), std::end(value), m_value.get() ); } name_for_agent_t::name_for_agent_t( const name_for_agent_t & other ) : m_length{ other.m_length } { if( other.has_value() ) { m_value.reset( new char[ m_length ] ); std::copy( other.m_value.get(), other.m_value.get() + m_length, m_value.get() ); } } name_for_agent_t & name_for_agent_t::operator=( const name_for_agent_t & other ) { name_for_agent_t tmp{ other }; swap( *this, tmp ); return *this; } name_for_agent_t::name_for_agent_t( name_for_agent_t && other ) noexcept : m_value{ std::exchange( other.m_value, std::unique_ptr< char[] >{} ) } , m_length{ std::exchange( other.m_length, 0u ) } {} name_for_agent_t & name_for_agent_t::operator=( name_for_agent_t && other ) noexcept { name_for_agent_t tmp{ std::move(other) }; swap( *this, tmp ); return *this; } SO_5_FUNC void swap( name_for_agent_t & a, name_for_agent_t & b ) noexcept { using std::swap; swap( a.m_value, b.m_value ); swap( a.m_length, b.m_length ); } name_for_agent_t::~name_for_agent_t() = default; std::string_view name_for_agent_t::as_string_view() const { std::string_view result; if( has_value() ) result = std::string_view{ m_value.get(), m_length }; return result; } bool name_for_agent_t::has_value() const noexcept { return 0u != m_length; } namespace { /*! * \since * v.5.4.0 * */ std::string create_anonymous_state_name( const agent_t * agent, const state_t * st ) { std::ostringstream ss; ss << ""; return ss.str(); } } /* namespace anonymous */ // NOTE: Implementation of state_t is moved to that file in v.5.4.0. // // state_t::time_limit_t // /// Information of time_limit for a state. class state_t::time_limit_t { public: /// Type of signal to be used for controlling timeouts. using msg_timeout = so_5::details::msg_state_timeout; /// Type of clock to be used. using steady_clock = std::chrono::steady_clock; /// Initializing constructor. time_limit_t( /// Value of the time limit. duration_t limit, /// The target state to switch to after the time limit has been exceeded. const state_t & state_to_switch ) noexcept : m_limit{ limit } , m_state_to_switch{ state_to_switch } {} /// Change parameters of time limit. /// /// \note /// This method only changes m_limit and m_state_to_switch but /// doesn't change m_activation_data. If time_limit is activated /// it has to be deactivated explicitly via deactivate() method. void change( duration_t limit, const state_t & state_to_switch ) noexcept { m_limit = limit; m_state_to_switch = state_to_switch; } /// React to activation of the state for that time_limit is defined. /// /// Sends delayed msg_timeout message and sets the m_activation_data. /// /// \attention /// This method is marked as noexcept because it has to be called /// in a noexcept context. But it calls methods those can throw /// (like so_5::send_periodic). This is a consequence of the current /// design of SObjectizer-5 (there is no non-throwing ways to send /// a message yet). void on_state_activation( const agent_t::state_time_limit_handling_data_t & info ) noexcept { activate( info ); } /// React to deactivation of the state for that time_limit is defined. /// /// Resets the value of m_activation_data and cancels the delayed /// msg_timeout message. void on_state_deactivation() noexcept { // We call reset() always even if time_limit is not activated, // just for simplicity of the implementation. m_activation_data.reset(); } /// Sends the delayed msg_timeout signal. /// /// Updates the value of m_activation_data. /// /// It's assumed that this method will be called only if /// time_limit is not active yet. void activate( const agent_t::state_time_limit_handling_data_t & info ) { const auto limit_exceeded_at = steady_clock::now() + m_limit; m_activation_data.emplace( so_5::send_periodic< msg_timeout >( info.timeout_mbox(), m_limit, steady_clock::duration::zero() ), limit_exceeded_at ); } /// Deactivates the time limit. /// /// This method can be called even if the time limit is not active. /// /// Resets the value of m_activation_data and cancels the delayed /// msg_timeout signal. void deactivate() { // We call reset() always even if time_limit is not activated, // just for simplicity of the implementation. m_activation_data.reset(); } /// \retval true if timeout has been exceeded and the agent has to /// be switched to a new state. /// \retval false if timeout has not been exceeded yet (or if time limit /// isn't activated). [[nodiscard]] bool is_limit_exceeded( const steady_clock::time_point current_time ) const noexcept { if( m_activation_data.has_value() ) { return m_activation_data->m_expiration_point <= current_time; } return false; } /// \return Reference to the state to switch after expiration of the timeout. [[nodiscard]] const state_t & state_to_switch() const noexcept { return m_state_to_switch.get(); } private: /// Information for active timeout. /// /// \note /// Destruction of an instance of activation_data_t will lead /// to destruction of m_timer and this will lead to cancelling /// of the delayed message. /// /// \since v.5.8.5 struct activation_data_t { /// ID of delayed timeout signal. timer_id_t m_timer; /// Timeout of timeout expiration. steady_clock::time_point m_expiration_point; activation_data_t( timer_id_t timer, steady_clock::time_point expiration_point ) : m_timer{ std::move(timer) } , m_expiration_point{ expiration_point } {} }; /// The current duration of the timeout. /// /// Will be changed on the next call to state_t::time_limit(). duration_t m_limit; /// The target state to switch after the timeout. std::reference_wrapper< const state_t > m_state_to_switch; /// Information required to serve timeout when it's activated. /// /// Empty value means that the timeout isn't activated. std::optional< activation_data_t > m_activation_data; }; // // state_t // state_t::state_t( agent_t * target_agent, std::string state_name, state_t * parent_state, std::size_t nested_level, history_t state_history ) : m_target_agent{ target_agent } , m_state_name( std::move(state_name) ) , m_parent_state{ parent_state } , m_initial_substate{ nullptr } , m_state_history{ state_history } , m_last_active_substate{ nullptr } , m_nested_level{ nested_level } , m_substate_count{ 0 } { if( parent_state ) { // We should check the deep of nested states. if( m_nested_level >= max_deep ) SO_5_THROW_EXCEPTION( rc_state_nesting_is_too_deep, "max nesting deep for agent states is " + std::to_string( max_deep ) ); // Now we can safely mark parent state as composite. parent_state->m_substate_count += 1; } } state_t::state_t( agent_t * agent ) : state_t{ agent, history_t::none } { } state_t::state_t( agent_t * agent, history_t state_history ) : state_t{ agent, std::string(), nullptr, 0, state_history } { } state_t::state_t( agent_t * agent, std::string state_name ) : state_t{ agent, std::move(state_name), history_t::none } {} state_t::state_t( agent_t * agent, std::string state_name, history_t state_history ) : state_t{ agent, std::move(state_name), nullptr, 0, state_history } {} state_t::state_t( initial_substate_of parent ) : state_t{ parent, std::string(), history_t::none } {} state_t::state_t( initial_substate_of parent, std::string state_name ) : state_t{ parent, std::move(state_name), history_t::none } {} state_t::state_t( initial_substate_of parent, std::string state_name, history_t state_history ) : state_t{ parent.m_parent_state->m_target_agent, std::move(state_name), parent.m_parent_state, parent.m_parent_state->m_nested_level + 1, state_history } { if( m_parent_state->m_initial_substate ) SO_5_THROW_EXCEPTION( rc_initial_substate_already_defined, "initial substate for state " + m_parent_state->query_name() + " is already defined: " + m_parent_state->m_initial_substate->query_name() ); m_parent_state->m_initial_substate = this; } state_t::state_t( substate_of parent ) : state_t{ parent, std::string(), history_t::none } {} state_t::state_t( substate_of parent, std::string state_name ) : state_t{ parent, std::move(state_name), history_t::none } {} state_t::state_t( substate_of parent, std::string state_name, history_t state_history ) : state_t{ parent.m_parent_state->m_target_agent, std::move(state_name), parent.m_parent_state, parent.m_parent_state->m_nested_level + 1, state_history } {} state_t::state_t( state_t && other ) : m_target_agent( other.m_target_agent ) , m_state_name( std::move( other.m_state_name ) ) , m_parent_state{ other.m_parent_state } , m_initial_substate{ other.m_initial_substate } , m_state_history{ other.m_state_history } , m_last_active_substate{ other.m_last_active_substate } , m_nested_level{ other.m_nested_level } , m_substate_count{ other.m_substate_count } , m_on_enter{ std::move(other.m_on_enter) } , m_on_exit{ std::move(other.m_on_exit) } { if( m_parent_state && m_parent_state->m_initial_substate == &other ) m_parent_state->m_initial_substate = this; } state_t::~state_t() { } bool state_t::operator == ( const state_t & state ) const noexcept { return &state == this; } std::string state_t::query_name() const { auto getter = [this]() -> std::string { if( m_state_name.empty() ) return create_anonymous_state_name( m_target_agent, this ); else return m_state_name; }; if( m_parent_state ) return m_parent_state->query_name() + "." + getter(); else return getter(); } namespace { #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wexit-time-destructors" #pragma clang diagnostic ignored "-Wglobal-constructors" #endif /*! * \since * v.5.4.0 * * \brief A special object for the state in which agent is awaiting * for deregistration after unhandled exception. * * This object will be shared between all agents. */ const state_t awaiting_deregistration_state( nullptr, "" ); /*! * \since * v.5.5.21 * * \brief A special object to be used as state for make subscriptions * for deadletter handlers. * * This object will be shared between all agents. */ const state_t deadletter_state( nullptr, "" ); #if defined(__clang__) #pragma clang diagnostic pop #endif } /* namespace anonymous */ bool state_t::is_target( const agent_t * agent ) const noexcept { if( m_target_agent ) return m_target_agent == agent; else if( this == &awaiting_deregistration_state ) return true; else return false; } void state_t::activate() const { m_target_agent->so_change_state( *this ); } state_t & state_t::time_limit( duration_t timeout, const state_t & state_to_switch ) { if( duration_t::zero() == timeout ) SO_5_THROW_EXCEPTION( rc_invalid_time_limit_for_state, "zero can't be used as time limit for state: " + query_name() ); m_target_agent->define_state_time_limit_handling_data_if_needed(); // Existing time_limit_t object will be reused, but if there is no // such object yet it has to be created. // // NOTE: by changing m_time_limit we can provide only basic // exception guarantee. if( !m_time_limit ) { m_time_limit = std::make_unique< time_limit_t >( timeout, state_to_switch ); } else { // NOTE: if the state is active and there was old time_limit // (with already sent instance of msg_timeout) then the old // delayed msg_timeout signal will be automatically cancelled. m_time_limit->deactivate(); m_time_limit->change( timeout, state_to_switch ); } // If this state is active then new time limit must be activated. if( is_active() ) { // If this action fails then m_time_limit should be reset // to nullptr. so_5::details::do_with_rollback_on_exception( [this] { m_time_limit->activate( m_target_agent->m_state_time_limit_handling_data ); }, [this] { m_time_limit.reset(); } ); } return *this; } state_t & state_t::drop_time_limit() { m_time_limit.reset(); return *this; } const state_t * state_t::actual_state_to_enter() const { const state_t * s = this; while( 0 != s->m_substate_count ) { if( s->m_last_active_substate ) // Note: for states with shallow history m_last_active_substate // can point to composite substate. This substate must be // processed usual way with checking for substate count, presence // of initial substate and so on... s = s->m_last_active_substate; else if( !s->m_initial_substate ) SO_5_THROW_EXCEPTION( rc_no_initial_substate, "there is no initial substate for composite state: " + query_name() ); else s = s->m_initial_substate; } return s; } void state_t::update_history_in_parent_states() const { auto p = m_parent_state; // This pointer will be used for update states with shallow history. // This pointer will be changed on every iteration. auto c = this; while( p ) { if( history_t::shallow == p->m_state_history ) p->m_last_active_substate = c; else if( history_t::deep == p->m_state_history ) p->m_last_active_substate = this; c = p; p = p->m_parent_state; } } void state_t::handle_time_limit_on_enter() const { m_time_limit->on_state_activation( m_target_agent->m_state_time_limit_handling_data ); } void state_t::handle_time_limit_on_exit() const { m_time_limit->on_state_deactivation(); } // // agent_t::state_time_limit_handling_data_t // agent_t::state_time_limit_handling_data_t::state_time_limit_handling_data_t() : m_timeout_mbox{} {} agent_t::state_time_limit_handling_data_t::~state_time_limit_handling_data_t() = default; bool agent_t::state_time_limit_handling_data_t::is_defined() const noexcept { return static_cast(m_timeout_mbox); } void agent_t::state_time_limit_handling_data_t::make_defined( mbox_t timeout_mbox ) { m_timeout_mbox = std::move(timeout_mbox); } mbox_t agent_t::state_time_limit_handling_data_t::timeout_mbox() const noexcept { return m_timeout_mbox; } namespace { /*! * \brief Helper for creation of the direct mbox for an agent. * * If there is a custom direct mbox factory in \a tuning_options * then the return value of that factory is used. Otherwise the * \a standard_mbox is returned. */ [[nodiscard]] mbox_t make_direct_mbox_with_respect_to_custom_factory( partially_constructed_agent_ptr_t agent_ptr, const agent_tuning_options_t & tuning_options, mbox_t standard_mbox ) { mbox_t result{ std::move(standard_mbox) }; const auto & factory = tuning_options.query_custom_direct_mbox_factory(); if( factory ) { result = factory( agent_ptr, std::move(result) ); if( mbox_type_t::multi_producer_single_consumer != result->type() ) { SO_5_THROW_EXCEPTION( rc_mpsc_mbox_expected, "MPSC mbox is expected as the direct mbox " "for an agent" ); } } return result; } /*! * \brief Helper for selection of subscription storage factory. * * If a factory is specified in @a tuning_options explicitly then it's * used. Otherwise the default subscription storage factory from * SObjectizer Environment will be used. * * \since v.5.8.2 */ [[nodiscard]] subscription_storage_factory_t detect_subscription_storage_factory_to_use( environment_t & env, const agent_tuning_options_t & tuning_options ) { if( tuning_options.is_user_provided_subscription_storage_factory() ) return tuning_options.query_subscription_storage_factory(); else return impl::internal_env_iface_t{ env }.default_subscription_storage_factory(); } } /* namespace anonymous */ // // agent_t // agent_t::agent_t( environment_t & env ) : agent_t( env, tuning_options() ) { } agent_t::agent_t( environment_t & env, agent_tuning_options_t options ) : agent_t( context_t( env, std::move( options ) ) ) { } agent_t::agent_t( context_t ctx ) : m_current_state_ptr( &st_default ) , m_current_status( agent_status_t::not_defined_yet ) , m_handler_finder( // Actual handler finder is dependent on msg_tracing status. impl::internal_env_iface_t( ctx.env() ).is_msg_tracing_enabled() ? &agent_t::handler_finder_msg_tracing_enabled : &agent_t::handler_finder_msg_tracing_disabled ) , m_subscriptions( detect_subscription_storage_factory_to_use( ctx.env(), ctx.options() )() ) , m_message_sinks( impl::create_sinks_storage_if_necessary( partially_constructed_agent_ptr_t( self_ptr() ), ctx.options().giveout_message_limits() ) ) , m_env( ctx.env() ) , m_event_queue( nullptr ) , m_direct_mbox( make_direct_mbox_with_respect_to_custom_factory( partially_constructed_agent_ptr_t( self_ptr() ), ctx.options(), impl::internal_env_iface_t( ctx.env() ).create_ordinary_mpsc_mbox( *self_ptr() ) ) ) // It is necessary to enable agent subscription in the // constructor of derived class. , m_working_thread_id( so_5::query_current_thread_id() ) , m_agent_coop( nullptr ) , m_priority( ctx.options().query_priority() ) , m_name( ctx.options().giveout_agent_name() ) , m_demands_handling_on_dereg( ctx.options().demands_handling_on_dereg() ) { } agent_t::~agent_t() { // Sometimes it is possible that agent is destroyed without // correct deregistration from SO Environment. destroy_all_subscriptions_and_filters(); } void agent_t::so_evt_start() { // Default implementation do nothing. } void agent_t::so_evt_finish() { // Default implementation do nothing. } bool agent_t::so_is_active_state( const state_t & state_to_check ) const noexcept { state_t::state_path_t current_path{ *m_current_state_ptr }; return current_path.end() != std::find( current_path.begin(), current_path.end(), &state_to_check ); } void agent_t::so_add_nondestroyable_listener( agent_state_listener_t & state_listener ) { m_state_listener_controller.add( impl::state_listener_controller_t::wrap_nondestroyable( state_listener ) ); } void agent_t::so_add_destroyable_listener( agent_state_listener_unique_ptr_t state_listener ) { m_state_listener_controller.add( impl::state_listener_controller_t::wrap_destroyable( std::move( state_listener ) ) ); } exception_reaction_t agent_t::so_exception_reaction() const noexcept { if( m_agent_coop ) return m_agent_coop->exception_reaction(); else // This is very strange case. So it would be better to abort. return abort_on_exception; } void agent_t::so_switch_to_awaiting_deregistration_state() { so_deactivate_agent(); } const mbox_t & agent_t::so_direct_mbox() const { return m_direct_mbox; } mbox_t agent_t::so_make_new_direct_mbox() { return impl::internal_env_iface_t{ so_environment() }.create_ordinary_mpsc_mbox( *self_ptr() ); } const state_t & agent_t::so_default_state() const { return st_default; } namespace impl { class state_switch_guard_t { agent_t & m_agent; agent_t::agent_status_t m_previous_status; public : state_switch_guard_t( agent_t & agent ) : m_agent( agent ) , m_previous_status( agent.m_current_status ) { if( agent_t::agent_status_t::state_switch_in_progress == agent.m_current_status ) SO_5_THROW_EXCEPTION( rc_another_state_switch_in_progress, "an attempt to switch agent state when another state " "switch operation is in progress for the same agent" ); agent.m_current_status = agent_t::agent_status_t::state_switch_in_progress; } ~state_switch_guard_t() { m_agent.m_current_status = m_previous_status; } }; } /* namespace impl */ void agent_t::so_change_state( const state_t & new_state ) { ensure_operation_is_on_working_thread( "so_change_state" ); do_change_agent_state( new_state ); } void agent_t::so_deactivate_agent() { ensure_operation_is_on_working_thread( "so_deactivate_agent" ); do_change_agent_state( awaiting_deregistration_state ); destroy_all_subscriptions_and_filters(); } void agent_t::so_drop_all_subscriptions_and_filters() { ensure_operation_is_on_working_thread( "so_drop_all_subscriptions_and_filters" ); destroy_all_subscriptions_and_filters(); } void agent_t::so_initiate_agent_definition() { impl::agent_impl::working_thread_id_sentinel_t sentinel( m_working_thread_id, so_5::query_current_thread_id() ); so_define_agent(); m_current_status = agent_status_t::defined; } void agent_t::so_define_agent() { // Default implementation do nothing. } bool agent_t::so_was_defined() const { return agent_status_t::not_defined_yet != m_current_status; } environment_t & agent_t::so_environment() const noexcept { return m_env; } [[nodiscard]] coop_handle_t agent_t::so_coop() const { if( !m_agent_coop ) SO_5_THROW_EXCEPTION( rc_agent_has_no_cooperation, "agent_t::so_coop() can be completed because agent is not bound " "to any cooperation" ); return m_agent_coop->handle(); } void agent_t::so_bind_to_dispatcher( event_queue_t & queue ) noexcept { // Since v.5.5.24 we should use event_queue_hook to get an // actual event_queue. auto * actual_queue = impl::internal_env_iface_t{ m_env } .event_queue_on_bind( this, &queue ); std::lock_guard< default_rw_spinlock_t > queue_lock{ m_event_queue_lock }; // Cooperation usage counter should be incremented. // It will be decremented during final agent event execution. impl::coop_private_iface_t::increment_usage_count( *m_agent_coop ); // A starting demand must be sent first. actual_queue->push_evt_start( execution_demand_t( this, message_limit::control_block_t::none(), 0, typeid(void), message_ref_t(), &agent_t::demand_handler_on_start ) ); // Only then pointer to the queue could be stored. m_event_queue = actual_queue; } execution_hint_t agent_t::so_create_execution_hint( execution_demand_t & d ) { enum class demand_type_t { message, enveloped_msg, other }; // We can't use message_kind_t here because there are special // demands like demands for so_evt_start/so_evt_finish. // Because of that a pointer to demand handler will be analyzed. const auto demand_type = (d.m_demand_handler == &agent_t::demand_handler_on_message ? demand_type_t::message : (d.m_demand_handler == &agent_t::demand_handler_on_enveloped_msg ? demand_type_t::enveloped_msg : demand_type_t::other)); if( demand_type_t::other != demand_type ) { // Try to find handler for the demand. auto handler = d.m_receiver->m_handler_finder( d, "create_execution_hint" ); if( handler ) { if( demand_type_t::message == demand_type ) // Ordinary message. return execution_hint_t( d, [handler]( execution_demand_t & demand, current_thread_id_t thread_id ) { process_message( thread_id, demand, handler->m_thread_safety, handler->m_method ); }, handler->m_thread_safety ); else // Enveloped message. return execution_hint_t( d, [handler]( execution_demand_t & demand, current_thread_id_t thread_id ) { process_enveloped_msg( thread_id, demand, *handler ); }, handler->m_thread_safety ); } else // Handler not found. // // NOTE: there is no difference between ordinary and // enveloped messages. return execution_hint_t::create_empty_execution_hint( d ); } else // This is demand_handler_on_start or demand_handler_on_finish. return execution_hint_t( d, []( execution_demand_t & demand, current_thread_id_t thread_id ) { demand.call_handler( thread_id ); }, not_thread_safe ); } void agent_t::so_deregister_agent_coop( int dereg_reason ) { so_environment().deregister_coop( m_agent_coop->handle(), dereg_reason ); } void agent_t::so_deregister_agent_coop_normally() { so_deregister_agent_coop( dereg_reason::normal ); } disp_binder_shptr_t agent_t::so_this_coop_disp_binder() const { if( !m_agent_coop ) SO_5_THROW_EXCEPTION( rc_agent_has_no_cooperation, "agent_t::so_this_coop_disp_binder() can be completed " "because agent is not bound to any cooperation" ); return m_agent_coop->coop_disp_binder(); } agent_identity_t agent_t::so_agent_name() const noexcept { if( m_name.has_value() ) return { m_name.as_string_view() }; else return { this }; } void agent_t::destroy_all_subscriptions_and_filters() noexcept { drop_all_delivery_filters(); m_subscriptions->drop_all_subscriptions(); } agent_ref_t agent_t::create_ref() { agent_ref_t agent_ref( this ); return agent_ref; } void agent_t::bind_to_coop( coop_t & coop ) { m_agent_coop = &coop; } void agent_t::shutdown_agent() noexcept { event_queue_t * actual_queue = nullptr; { std::lock_guard< default_rw_spinlock_t > queue_lock{ m_event_queue_lock }; // Since v.5.5.8 shutdown is done by two simple step: // - remove actual value from m_event_queue; // - pushing final demand to actual event queue. // // No new demands will be sent to the agent, but all the subscriptions // remains. They will be destroyed at the very end of agent's lifetime. if( m_event_queue ) { // This pointer will be used later. actual_queue = m_event_queue; // Final event must be pushed to queue. so_5::details::invoke_noexcept_code( [&] { m_event_queue->push_evt_finish( execution_demand_t( this, message_limit::control_block_t::none(), 0, typeid(void), message_ref_t(), &agent_t::demand_handler_on_finish ) ); // No more events will be stored to the queue. m_event_queue = nullptr; } ); } else so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( so_environment(), log_stream ) { log_stream << "Unexpected error: m_event_queue contains " "nullptr. Unable to push demand_handler_on_finish for " "the agent (" << this << "). Application will be aborted" << std::endl; } } ); } // Since v.5.8.5 pending demands can be skipped. if( demands_handling_on_dereg_t::skip == m_demands_handling_on_dereg ) m_current_status = agent_status_t::shutdown_with_skipping_pending_demands; if( actual_queue ) // Since v.5.5.24 we should utilize event_queue via // event_queue_hook. impl::internal_env_iface_t{ m_env } .event_queue_on_unbind( this, actual_queue ); } void agent_t::so_create_event_subscription( const mbox_t & mbox_ref, std::type_index msg_type, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) { // Since v.5.4.0 there is no need for locking agent's mutex // because this operation can be performed only on agent's // working thread. ensure_operation_is_on_working_thread( "so_create_event_subscription" ); // A new subscription can't be made if agent is already deactivated. if( is_agent_deactivated() ) SO_5_THROW_EXCEPTION( so_5::rc_agent_deactivated, "new subscription can't made for deactivated agent" ); m_subscriptions->create_event_subscription( mbox_ref, msg_type, detect_sink_for_message_type( msg_type ), target_state, method, thread_safety, handler_kind ); } void agent_t::so_create_deadletter_subscription( const mbox_t & mbox, const std::type_index & msg_type, const event_handler_method_t & method, thread_safety_t thread_safety ) { ensure_operation_is_on_working_thread( "so_create_deadletter_subscription" ); // A new deadletter handler can't be made if agent is already deactivated. if( is_agent_deactivated() ) SO_5_THROW_EXCEPTION( so_5::rc_agent_deactivated, "new deadletter handler can't be set for deactivated agent" ); m_subscriptions->create_event_subscription( mbox, msg_type, detect_sink_for_message_type( msg_type ), deadletter_state, method, thread_safety, event_handler_kind_t::final_handler ); } void agent_t::so_destroy_deadletter_subscription( const mbox_t & mbox, const std::type_index & msg_type ) { // Since v.5.4.0 there is no need for locking agent's mutex // because this operation can be performed only on agent's // working thread. ensure_operation_is_on_working_thread( "do_drop_deadletter_handler" ); m_subscriptions->drop_subscription( mbox, msg_type, deadletter_state ); } abstract_message_sink_t & agent_t::detect_sink_for_message_type( const std::type_index & msg_type ) { auto * result = m_message_sinks->find_or_create( msg_type ); if( !result ) SO_5_THROW_EXCEPTION( so_5::rc_message_has_no_limit_defined, std::string( "message type without " "predefined limit for that type, type: " ) + msg_type.name() ); return *result; } void agent_t::do_drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) { // Since v.5.4.0 there is no need for locking agent's mutex // because this operation can be performed only on agent's // working thread. ensure_operation_is_on_working_thread( "do_drop_subscription" ); m_subscriptions->drop_subscription( mbox, msg_type, target_state ); } void agent_t::do_drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) { // Since v.5.4.0 there is no need for locking agent's mutex // because this operation can be performed only on agent's // working thread. ensure_operation_is_on_working_thread( "do_drop_subscription_for_all_states" ); m_subscriptions->drop_subscription_for_all_states( mbox, msg_type ); } bool agent_t::do_check_subscription_presence( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) const noexcept { return nullptr != m_subscriptions->find_handler( mbox->id(), msg_type, target_state ); } bool agent_t::do_check_deadletter_presence( const mbox_t & mbox, const std::type_index & msg_type ) const noexcept { return nullptr != m_subscriptions->find_handler( mbox->id(), msg_type, deadletter_state ); } namespace { /*! * \brief A helper function to select actual demand handler in * dependency of message kind. * * \since * v.5.5.23 */ inline demand_handler_pfn_t select_demand_handler_for_message( const agent_t & agent, const message_ref_t & msg ) { demand_handler_pfn_t result = &agent_t::demand_handler_on_message; if( msg ) { switch( message_kind( *msg ) ) { case message_t::kind_t::classical_message : // Already has value. break; case message_t::kind_t::user_type_message : // Already has value. break; case message_t::kind_t::enveloped_msg : result = &agent_t::demand_handler_on_enveloped_msg; break; case message_t::kind_t::signal : so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( agent.so_environment(), log_stream ) { log_stream << "message that has data and message_kind_t::signal!" "Signals can't have data. Application will be aborted!" << std::endl; } } ); break; } } return result; } } /* namespace anonymous */ void agent_t::push_event( const message_limit::control_block_t * limit, mbox_id_t mbox_id, const std::type_index & msg_type, const message_ref_t & message ) { const auto handler = select_demand_handler_for_message( *this, message ); read_lock_guard_t< default_rw_spinlock_t > queue_lock{ m_event_queue_lock }; if( m_event_queue ) m_event_queue->push( execution_demand_t( this, limit, mbox_id, msg_type, message, handler ) ); } void agent_t::demand_handler_on_start( current_thread_id_t working_thread_id, execution_demand_t & d ) { d.m_receiver->ensure_binding_finished(); impl::agent_impl::working_thread_id_sentinel_t sentinel( d.m_receiver->m_working_thread_id, working_thread_id ); try { d.m_receiver->so_evt_start(); } catch( const std::exception & x ) { impl::process_unhandled_exception( working_thread_id, x, *(d.m_receiver) ); } catch( ... ) // Since v.5.5.24.3 { impl::process_unhandled_unknown_exception( working_thread_id, *(d.m_receiver) ); } } void agent_t::ensure_binding_finished() { // Nothing more to do. // Just lock coop's binding_lock. If cooperation is not finished yet // it would stop the current thread. std::lock_guard< std::mutex > binding_lock{ m_agent_coop->m_lock }; } demand_handler_pfn_t agent_t::get_demand_handler_on_start_ptr() noexcept { return &agent_t::demand_handler_on_start; } void agent_t::demand_handler_on_finish( current_thread_id_t working_thread_id, execution_demand_t & d ) { { // Sentinel must finish its work before decrementing // reference count to cooperation. impl::agent_impl::working_thread_id_sentinel_t sentinel( d.m_receiver->m_working_thread_id, working_thread_id ); try { d.m_receiver->so_evt_finish(); } catch( const std::exception & x ) { impl::process_unhandled_exception( working_thread_id, x, *(d.m_receiver) ); } catch( ... ) // Since v.5.5.24.3 { impl::process_unhandled_unknown_exception( working_thread_id, *(d.m_receiver) ); } // Since v.5.5.15 agent should be returned in default state. d.m_receiver->return_to_default_state_if_possible(); } // Cooperation should receive notification about agent deregistration. impl::coop_private_iface_t::decrement_usage_count( *(d.m_receiver->m_agent_coop) ); } demand_handler_pfn_t agent_t::get_demand_handler_on_finish_ptr() noexcept { return &agent_t::demand_handler_on_finish; } void agent_t::demand_handler_on_message( current_thread_id_t working_thread_id, execution_demand_t & d ) { message_limit::control_block_t::decrement( d.m_limit ); auto handler = d.m_receiver->m_handler_finder( d, "demand_handler_on_message" ); if( handler ) process_message( working_thread_id, d, handler->m_thread_safety, handler->m_method ); } demand_handler_pfn_t agent_t::get_demand_handler_on_message_ptr() noexcept { return &agent_t::demand_handler_on_message; } void agent_t::demand_handler_on_enveloped_msg( current_thread_id_t working_thread_id, execution_demand_t & d ) { message_limit::control_block_t::decrement( d.m_limit ); auto handler = d.m_receiver->m_handler_finder( d, "demand_handler_on_enveloped_msg" ); if( handler ) process_enveloped_msg( working_thread_id, d, *handler ); } demand_handler_pfn_t agent_t::get_demand_handler_on_enveloped_msg_ptr() noexcept { return &agent_t::demand_handler_on_enveloped_msg; } void agent_t::process_message( current_thread_id_t working_thread_id, execution_demand_t & d, thread_safety_t thread_safety, event_handler_method_t method ) { // Since v.5.8.5 pending demands may be skipped after dereg. if( agent_status_t::shutdown_with_skipping_pending_demands == d.m_receiver->m_current_status ) { // Demand has to be ignored. return; } impl::agent_impl::working_thread_id_sentinel_t sentinel{ d.m_receiver->m_working_thread_id, // v.5.7.3 // If event_handler is thread_safe-handler then null_thread_id // has to be used instead of the actual working_thread_id. so_5::thread_safe == thread_safety ? null_current_thread_id() : working_thread_id }; try { method( d.m_message_ref ); } catch( const std::exception & x ) { impl::process_unhandled_exception( working_thread_id, x, *(d.m_receiver) ); } catch( ... ) // Since v.5.5.24.3 { impl::process_unhandled_unknown_exception( working_thread_id, *(d.m_receiver) ); } } void agent_t::process_enveloped_msg( current_thread_id_t working_thread_id, execution_demand_t & d, const impl::event_handler_data_t & handler_data ) { // Since v.5.8.5 pending demands may be skipped after dereg. if( agent_status_t::shutdown_with_skipping_pending_demands == d.m_receiver->m_current_status ) { // Demand has to be ignored. return; } using namespace enveloped_msg::impl; // If this is intermediate_handler then we should pass the // whole envelope to it. if( event_handler_kind_t::intermediate_handler == handler_data.m_kind ) // Just call process_message() in that case because // process_message() already does what we need (including // setting working_thread_id and handling of exceptions). process_message( working_thread_id, d, handler_data.m_thread_safety, handler_data.m_method ); else // For a final_handler the payload should be extracted // from the envelope and the extracted payload should go // to the handler. // We don't expect exceptions here and can't restore after them. so_5::details::invoke_noexcept_code( [&] { auto & envelope = message_to_envelope( d.m_message_ref ); agent_demand_handler_invoker_t invoker{ working_thread_id, d, handler_data }; envelope.access_hook( so_5::enveloped_msg::access_context_t::handler_found, invoker ); } ); } void agent_t::ensure_operation_is_on_working_thread( const char * operation_name ) const { if( so_5::query_current_thread_id() != m_working_thread_id ) { std::ostringstream s; s << operation_name << ": operation is enabled only on agent's working thread; " << "working_thread_id: "; if( m_working_thread_id == null_current_thread_id() ) s << ""; else s << m_working_thread_id; s << ", current_thread_id: " << so_5::query_current_thread_id(); SO_5_THROW_EXCEPTION( so_5::rc_operation_enabled_only_on_agent_working_thread, s.str() ); } } void agent_t::drop_all_delivery_filters() noexcept { if( m_delivery_filters ) { m_delivery_filters->drop_all(); m_delivery_filters.reset(); } } void agent_t::do_set_delivery_filter( const mbox_t & mbox, const std::type_index & msg_type, delivery_filter_unique_ptr_t filter ) { ensure_operation_is_on_working_thread( "set_delivery_filter" ); // A new delivery filter can't be set if agent is already deactivated. if( is_agent_deactivated() ) SO_5_THROW_EXCEPTION( so_5::rc_agent_deactivated, "new delivery filter can't be set for deactivated agent" ); // Message sink for that message type have to be obtained with // the respect to message limits. auto & target_sink = detect_sink_for_message_type( msg_type ); if( !m_delivery_filters ) m_delivery_filters.reset( new impl::delivery_filter_storage_t() ); m_delivery_filters->set_delivery_filter( mbox, msg_type, std::move(filter), outliving_mutable( target_sink ) ); } void agent_t::do_drop_delivery_filter( const mbox_t & mbox, const std::type_index & msg_type ) noexcept { ensure_operation_is_on_working_thread( "set_delivery_filter" ); if( m_delivery_filters ) m_delivery_filters->drop_delivery_filter( mbox, msg_type ); } const impl::event_handler_data_t * agent_t::handler_finder_msg_tracing_disabled( execution_demand_t & d, const char * /*context_marker*/ ) { auto search_result = find_event_handler_for_current_state( d ); if( !search_result ) // Since v.5.5.21 we should check for deadletter handler for that demand. search_result = find_deadletter_handler( d ); return search_result; } const impl::event_handler_data_t * agent_t::handler_finder_msg_tracing_enabled( execution_demand_t & d, const char * context_marker ) { auto search_result = find_event_handler_for_current_state( d ); if( !search_result ) { // Since v.5.5.21 we should check for deadletter handler for that demand. search_result = find_deadletter_handler( d ); if( search_result ) { // Deadletter handler found. This must be reflected in trace. impl::msg_tracing_helpers::trace_deadletter_handler_search_result( d, context_marker, search_result ); return search_result; } } // This trace will be made if an event_handler is found for the // current state or not found at all (including deadletter handlers). impl::msg_tracing_helpers::trace_event_handler_search_result( d, context_marker, search_result ); return search_result; } const impl::event_handler_data_t * agent_t::find_event_handler_for_current_state( execution_demand_t & d ) { const impl::event_handler_data_t * search_result = nullptr; const state_t * s = &d.m_receiver->so_current_state(); do { search_result = d.m_receiver->m_subscriptions->find_handler( d.m_mbox_id, d.m_msg_type, *s ); if( !search_result ) s = s->parent_state(); } while( search_result == nullptr && s != nullptr ); return search_result; } const impl::event_handler_data_t * agent_t::find_deadletter_handler( execution_demand_t & demand ) { return demand.m_receiver->m_subscriptions->find_handler( demand.m_mbox_id, demand.m_msg_type, deadletter_state ); } void agent_t::do_change_agent_state( const state_t & state_to_be_set ) { // The agent can't leave awaiting_deregistration_state if it's // in that state already. if( is_agent_deactivated() && state_to_be_set != awaiting_deregistration_state ) { SO_5_THROW_EXCEPTION( rc_agent_deactivated, "unable to switch agent to another state because the " "agent is already deactivated" ); } if( state_to_be_set.is_target( this ) ) { // Since v.5.5.18 we must check nested state switch operations. // This object will drop pointer to the current state. impl::state_switch_guard_t switch_op_guard( *this ); auto actual_new_state = state_to_be_set.actual_state_to_enter(); if( !( *actual_new_state == *m_current_state_ptr ) ) { // New state differs from the current one. // Actual state switch must be performed. do_state_switch( *actual_new_state ); // State listener should be informed. m_state_listener_controller.changed( *this, *m_current_state_ptr ); } } else SO_5_THROW_EXCEPTION( rc_agent_unknown_state, "unable to switch agent to alien state " "(the state that doesn't belong to this agent)" ); } void agent_t::do_state_switch( const state_t & state_to_be_set ) noexcept { state_t::path_t old_path; state_t::path_t new_path; // Since v.5.5.22 we will change the value of m_current_state_ptr // during state change procedure. auto current_st = m_current_state_ptr; current_st->fill_path( old_path ); state_to_be_set.fill_path( new_path ); // Find the first item which is different in the paths. std::size_t first_diff = 0; for(; first_diff < std::min( current_st->nested_level(), state_to_be_set.nested_level() ); ++first_diff ) if( old_path[ first_diff ] != new_path[ first_diff ] ) break; // Do call for on_exit and on_enter for states. // on_exit and on_enter should not throw exceptions. so_5::details::invoke_noexcept_code( [&] { impl::msg_tracing_helpers::safe_trace_state_leaving( *this, *current_st ); for( std::size_t i = current_st->nested_level(); i >= first_diff; ) { // Modify current state before calling on_exit handler. m_current_state_ptr = old_path[ i ]; // Perform on_exit actions. old_path[ i ]->call_on_exit(); if( i ) --i; else break; } impl::msg_tracing_helpers::safe_trace_state_entering( *this, state_to_be_set ); for( std::size_t i = first_diff; i <= state_to_be_set.nested_level(); ++i ) { // Modify current state before calling on_exit handler. m_current_state_ptr = new_path[ i ]; // Perform on_enter actions. new_path[ i ]->call_on_enter(); } } ); // Now the current state for the agent can be changed. m_current_state_ptr = &state_to_be_set; m_current_state_ptr->update_history_in_parent_states(); } void agent_t::return_to_default_state_if_possible() noexcept { if( !( st_default == so_current_state() || is_agent_deactivated() ) ) { // The agent must be returned to the default state. // All on_exit handlers must be called at this point. so_change_state( st_default ); } } bool agent_t::is_agent_deactivated() const noexcept { return awaiting_deregistration_state == so_current_state(); } void agent_t::define_state_time_limit_handling_data_if_needed() { ensure_operation_is_on_working_thread( "state_t::time_limit" ); // NOTE: the agent can be in awaiting_deregistration_state, but we don't // check it because an attempt to make a deadletter subscription will // fail in that case. if( !m_state_time_limit_handling_data.is_defined() ) { // We need a new special mbox for msg_timeout message. auto unique_mbox = impl::internal_env_iface_t{ so_environment() } // A new MPSC mbox will be used for that. .create_limitless_mpsc_mbox( // New MPSC mbox will be directly connected // to this agent. *this ); // We need a special deadletter handler. so_subscribe_deadletter_handler( unique_mbox, &agent_t::evt_state_time_limit ); // Now we can store time_limit_handling_data in the agent. m_state_time_limit_handling_data.make_defined( std::move(unique_mbox) ); } } void agent_t::evt_state_time_limit( mhood_t< so_5::details::msg_state_timeout > ) { const auto now = state_t::time_limit_t::steady_clock::now(); // We should have all active states in a row. state_t::state_path_t current_state_path{ *m_current_state_ptr }; // NOTE: we're going from the outer-most to the inner-most state. // It means that if A is the parent for B and A's timeout exceeded // then the timeout for B is ignored (even if it's exceeded too). for( const state_t * st : current_state_path ) { if( st->m_time_limit ) { if( st->m_time_limit->is_limit_exceeded( now ) ) { // We have to switch the agent to a new state. so_change_state( st->m_time_limit->state_to_switch() ); break; } } } // NOTE: it's possible that we haven't find an exceeded timeout. // This could happen if msg_timeout waited too long in the event_queue and // the agent changed its state during that time. } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/agent.hpp000066400000000000000000003350761523065210100173200ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A base class for agents. */ #if !defined( SO_5_AGENT_HPP ) #define SO_5_AGENT_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { // // exception_reaction_t // /*! * \brief A reaction of SObjectizer to an exception from agent event. * * \since v.5.2.3 */ enum exception_reaction_t { //! Execution of application must be aborted immediatelly. abort_on_exception = 1, //! Agent must be switched to special state and SObjectizer //! Environment will be stopped. shutdown_sobjectizer_on_exception = 2, //! Agent must be switched to special state and agent's cooperation //! must be deregistered. deregister_coop_on_exception = 3, //! Exception should be ignored and agent should continue its work. ignore_exception = 4, /*! * \brief Exception reaction should be inherited from SO Environment. * * \since v.5.3.0 */ inherit_exception_reaction = 5 }; // // subscription_bind_t // /*! * \brief A class for creating a subscription to messages from the mbox. * * This type provides one of the ways to subscribe an agent's event handlers. * There are two way to do that. The first one uses so_5::state_t::event() * methods: * \code * class subscribe_demo : public so_5::agent_t * { * // Some states for the agent. * state_t st_first{this}, st_second{this}, st_third{this}; * ... * virtual void so_define_agent() override { * // Subscribe just one event handler for st_first. * st_first.event(some_mbox, &subscribe_demo::event_handler_1); * * // Subscribe two event handlers for st_second. * st_second * .event(some_mbox, &subscribe_demo::event_handler_1) * .event(some_mbox, &subscribe_demo::event_handler_2); * * // Subscribe two event handlers for st_third. * st_third * .event(some_mbox, &subscribe_demo::event_handler_1) * .event(some_mbox, &subscribe_demo::event_handler_3) * } * }; * \endcode * But this way do not allow to subscribe the same event handler for * several states in the compact way. * * This can be done via agent_t::so_subscribe(), agent_t::so_subscribe_self() * and subscription_bind_t object: * \code * class subscribe_demo : public so_5::agent_t * { * // Some states for the agent. * state_t st_first{this}, st_second{this}, st_third{this}; * ... * virtual void so_define_agent() override { * // Subscribe event_handler_1 for all three states * so_subscribe(some_mbox) * .in(st_first) * .in(st_second) * .in(st_third) * .event(&subscribe_demo::event_handler_1); * * // Subscribe just one event handler for st_second and st_third. * so_subscribe(some_mbox) * .in(st_second) * .event(&subscribe_demo::event_handler_2); * * // Subscribe two event handlers for st_third. * so_subscribe(some_mbox) * .in(st_third) * .event(&subscribe_demo::event_handler_3) * } * }; * \endcode * * \par Some words about binder logic... * An object of type subscription_bind_t collects list of states * enumerated by calls to subscription_bind_t::in() method. * Every call to in() method add a state to that list. It means: * \code * so_subscribe(some_mbox) // list is: {} * .in(st_first) // list is: {st_first} * .in(st_second) // list is: {st_first, st_second} * .in(st_third) // list is: {st_first, st_second, st_third} * ... * \endcode * A call to event() or suppress() or just_switch_to() applies subscription * to all states which are currently in the list. But these calls do not * remove the content of that list. It means: * \code * so_subscribe(some_mbox) // list is: {} * .in(st_first) // list is: {st_first} * .event(handler_1) // subscribe for state st_first only. * .in(st_second) // list is: {st_first, st_second} * .event(handler_2) // subscribe for state st_first and for st_second. * .in(st_third) // list is: {st_first, st_second, st_third} * .event(handler_3) // subscribe for state st_first, st_second and st_third. * ... * \endcode */ class subscription_bind_t { public: inline subscription_bind_t( //! Agent to subscribe. agent_t & agent, //! Mbox for messages to be subscribed. const mbox_t & mbox_ref ); //! Set up a state in which events are allowed be processed. inline subscription_bind_t & in( //! State in which events are allowed. const state_t & state ); //! Make subscription to the message. /*! * \note Can be used for message and signal handlers. * * \par Usage example * \code struct engine_control : public so_5::message_t { ... }; struct check_status : public so_5::signal_t {}; class engine_controller : public so_5::agent_t { public : virtual void so_define_agent() override { so_subscribe_self() .event( &engine_controller::control ) .event( &engine_controller::check_status ); .event( &engine_controller::accelerate ); ... } ... private : void control( so_5::mhood_t< engine_control > & cmd ) { ... } void check_status( so_5::mhood_t< check_status > & cmd ) { ... } void accelerate( so_5::mhood_t< int > & cmd ) { ... } }; * \endcode * * \since v.5.5.14 */ template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>::value, subscription_bind_t & >::type event( //! Event handling method. Method_Pointer pfn, //! Thread safety of the event handler. thread_safety_t thread_safety = not_thread_safe ); /*! * \brief Make subscription to the message by lambda-function. * * \attention Only lambda-function in the forms: * \code Result (const Message &) Result (Message) Result (so_5::mhood_t) Result (const so_5::mhood_t &) * \endcode * are supported. * * \par Usage example. * \code enum class engine_control { turn_on, turn_off, slow_down }; struct setup_params : public so_5::message_t { ... }; struct update_settings { ... }; class engine_controller : public so_5::agent_t { public : virtual void so_define_agent() override { so_subscribe_self() .event( [this]( engine_control evt ) {...} ) .event( [this]( const setup_params & evt ) {...} ) .event( [this]( const update_settings & evt ) {...} ) ... } ... }; * \endcode * * \since v.5.3.0 */ template< class Lambda > typename std::enable_if< details::lambda_traits::is_lambda::value, subscription_bind_t & >::type event( //! Event handler code. Lambda && lambda, //! Thread safety of the event handler. thread_safety_t thread_safety = not_thread_safe ); /*! * \brief An instruction for switching agent to the specified * state and transfering event proceessing to new state. * * \par Usage example: * \code class device : public so_5::agent_t { state_t off{ this, "off" }; state_t on{ this, "on" }; public : virtual void so_define_agent() override { so_subscribe_self().in( off ) .transfer_to_state< key_on >( on ) .transfer_to_state< key_info >( on ); } ... }; * \endcode * * \note Event will not be postponed or returned to event queue. * A search for a handler for this event will be performed immediately * after switching to the new state. * * \note New state can use transfer_to_state for that event too: * \code class device : public so_5::agent_t { state_t off{ this, "off" }; state_t on{ this, "on" }; state_t status_dialog{ this, "status" }; public : virtual void so_define_agent() override { so_subscribe_self().in( off ) .transfer_to_state< key_on >( on ) .transfer_to_state< key_info >( on ); so_subscribe_self().in( on ) .transfer_to_state< key_info >( status_dialog ) ...; } ... }; * \endcode * * \note Since v.5.5.22.1 actual execution of transfer_to_state operation * can raise so_5::exception_t with so_5::rc_transfer_to_state_loop * error code if a loop in transfer_to_state is detected. * * \since v.5.5.15 */ template< typename Msg > subscription_bind_t & transfer_to_state( const state_t & target_state ); /*! * \brief Suppress processing of event in this state. * * \note This method is useful because the event is not passed to * event handlers from parent states. For example: * \code class demo : public so_5::agent_t { state_t S1{ this, "1" }; state_t S2{ initial_substate_of{ S1 }, "2" }; state_t S3{ initial_substate_of{ S2 }, "3" }; public : virtual void so_define_agent() override { so_subscribe_self().in( S1 ) // Default event handler which will be inherited by states S2 and S3. .event< msg1 >(...) .event< msg2 >(...) .event< msg3 >(...); so_subscribe_self().in( S2 ) // A special handler for msg1. // For msg2 and msg3 event handlers from state S1 will be used. .event< msg1 >(...); so_subscribe_self().in( S3 ) // Message msg1 will be suppressed. It will be simply ignored. // No events from states S1 and S2 will be called. .suppress< msg1 >() // The same for msg2. .suppress< msg2 >() // A special handler for msg3. Overrides handler from state S1. .event< msg3 >(...); } }; * \endcode * * \since v.5.5.15 */ template< typename Msg > subscription_bind_t & suppress(); /*! * \brief Define handler which only switches agent to the specified * state. * * \note This method differes from transfer_to_state() method: * just_switch_to() changes state of the agent, but there will not be a * look for event handler for message/signal in the new state. It means * that just_switch_to() is just a shorthand for: * \code virtual void demo::so_define_agent() override { so_subscribe_self().in( S1 ) .event< some_signal >( [this]{ this >>= S2; } ); } * \endcode * With just_switch_to() this code can looks like: * \code virtual void demo::so_define_agent() override { so_subscribe_self().in( S1 ) .just_switch_to< some_signal >( S2 ); } * \endcode * * \since v.5.5.15 */ template< typename Msg > subscription_bind_t & just_switch_to( const state_t & target_state ); private: //! Agent to which we are subscribing. agent_t * m_agent; //! Mbox for messages to subscribe. mbox_t m_mbox_ref; /*! * \brief Type of vector of states. * * \since v.5.3.0 */ typedef std::vector< const state_t * > state_vector_t; /*! * \brief States of agents the event to be subscribed in. * * \since v.5.3.0 */ state_vector_t m_states; /*! * \brief Create subscription of event for all states. * * \since v.5.3.0 */ void create_subscription_for_states( const std::type_index & msg_type, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) const; /*! * \brief Additional check for subscription to a mutable message * from MPMC mbox. * * Such attempt must be disabled because delivery of mutable * messages via MPMC mboxes is prohibited. * * \throw so_5::exception_t if m_mbox_ref is a MPMC mbox and * \a handler is for mutable message. * * \since v.5.5.19 */ void ensure_handler_can_be_used_with_mbox( const so_5::details::msg_type_and_handler_pair_t & handler ) const; }; /*! * \brief Internal namespace with details of agent_t implementation. * * \attention * Nothing from that namespace can be used in user code. All of this is an * implementation detail and is subject to change without any prior notice. * * \since v.5.8.0 */ namespace impl::agent_impl { /*! * \brief A helper class for temporary setting and then dropping * the ID of the current working thread. * * \note New working thread_id is set only if it is not an * null thread_id. * * \since v.5.4.0 */ struct working_thread_id_sentinel_t { so_5::current_thread_id_t & m_id; working_thread_id_sentinel_t( so_5::current_thread_id_t & id_var, so_5::current_thread_id_t value_to_set ) : m_id( id_var ) { if( value_to_set != null_current_thread_id() ) m_id = value_to_set; } ~working_thread_id_sentinel_t() { if( m_id != null_current_thread_id() ) m_id = null_current_thread_id(); } }; } /* namespace impl::agent_impl */ // // agent_t // //! A base class for agents. /*! An agent in SObjctizer must be derived from the agent_t. The base class provides various methods whose can be splitted into the following groups: \li methods for the interaction with SObjectizer; \li predefined hook-methods which are called during: cooperation registration, starting and stopping of an agent; \li methods for the message subscription and unsubscription; \li methods for working with an agent state; Methods for the interaction with SObjectizer Method so_5::agent_t::so_environment() serves for the access to the SObjectizer Environment (and, therefore, to all methods of the SObjectizer Environment). This method could be called immediatelly after the agent creation. This is because agent is bound to the SObjectizer Environment during the creation process. Hook methods The base class defines several hook-methods. Its default implementation do nothing. The method agent_t::so_define_agent() is called just before agent will started by SObjectizer as a part of the agent registration process. It should be reimplemented for the initial subscription of the agent to messages. There are two hook-methods related to important agent's lifetime events: agent_t::so_evt_start() and agent_t::so_evt_finish(). They are called by SObjectizer in next circumstances: - method so_evt_start() is called when the agent is starting its work inside of SObjectizer. At that moment all agents are defined (all their methods agent_t::so_define_agent() have executed); - method so_evt_finish() is called during the agent's cooperation deregistration just after agent processed the last pending event. Methods so_evt_start() and so_evt_finish() are called by SObjectizer and user can just reimplement them to implement the agent-specific logic. Message subscription and unsubscription methods Any method with one of the following prototypes can be used as an event handler: \code return_type evt_handler( mhood_t< Message > msg ); return_type evt_handler( const mhood_t< Message > & msg ); return_type evt_handler( const Message & msg ); return_type evt_handler( Message msg ); // Since v.5.5.20: return_type evt_handler( mhood_t< Message > msg ) const; return_type evt_handler( const mhood_t< Message > & msg ) const; return_type evt_handler( const Message & msg ) const; return_type evt_handler( Message msg ) const; \endcode Where \c evt_handler is a name of the event handler, \c Message is a message type. The class mhood_t is a wrapper on pointer to an instance of the \c Message. It is very similar to std::unique_ptr. The pointer to \c Message can be a nullptr. It happens in case when the message has no actual data and servers just a signal about something. Please note that handlers with the following prototypes can be used only for messages, not signals: \code return_type evt_handler( const Message & msg ); return_type evt_handler( Message msg ); // Since v.5.5.20: return_type evt_handler( const Message & msg ) const; return_type evt_handler( Message msg ) const; \endcode A subscription to the message is performed by the methods so_subscribe() and so_subscribe_self(). This method returns an instance of the so_5::subscription_bind_t which does all actual actions of the subscription process. This instance already knows agents and message mbox and uses the default agent state for the event subscription (binding to different state is also possible). The presence of a subscription can be checked by so_has_subscription() method. A subscription can be dropped (removed) by so_drop_subscription() and so_drop_subscription_for_all_states() methods. Deadletter handlers subscription and unsubscription Since v.5.5.21 SObjectizer supports deadletter handlers. Such handlers are called if there is no any ordinary event handler for a specific messages from a specific mbox. Deadletter handler can be implemented by an agent method or by lambda function. Deadletter handler can have one of the following formats: \code void evt_handler( mhood_t< Message > msg ); void return_type evt_handler( mhood_t< Message > msg ) const; void return_type evt_handler( const mhood_t< Message > & msg ); void return_type evt_handler( const mhood_t< Message > & msg ) const; void return_type evt_handler( const Message & msg ); void return_type evt_handler( const Message & msg ) const; void return_type evt_handler( Message msg ); void return_type evt_handler( Message msg ) const; \endcode Subscription for a deadletter handler can be created by so_subscribe_deadletter_handler() method. The presence of a deadletter handler can be checked by so_has_deadletter_handler() method. A deadletter can be dropped (removed) by so_drop_deadletter_handler() and so_drop_subscription_for_all_states() methods. Methods for working with an agent state The agent can change its state by his so_change_state() method. An attempt to switch an agent to the state which belongs to the another agent is an error. If state is belong to the same agent there are no possibility to any run-time errors. In this case changing agent state is a very safe operation. In some cases it is necessary to detect agent state switching. For example for application monitoring purposes. This can be done by "state listeners". Any count of state listeners can be set for an agent. There are two methods for that: - so_add_nondestroyable_listener() is for listeners whose lifetime are controlled by a programmer, not by SObjectizer; - so_add_destroyable_listener() is for listeners whose lifetime must be controlled by agent itself. Work thread identification Since v.5.4.0 some operations for agent are enabled only on agent's work thread. They are: - subscription management operations (creation or dropping); - changing agent's state. Work thread for an agent is defined as follows: - before invocation of so_define_agent() the work thread is a thread on which agent is created (id of that thread is detected in agent's constructor); - during cooperation registration the working thread is a thread on which so_environment::register_coop() is working; - after successful agent registration the work thread for it is specified by the dispatcher. \note Some dispatchers could provide several work threads for an agent. In such case there would not be work thread id. And operations like changing agent state or creation of subscription would be prohibited after agent registration. Accessing dispatcher binders Since v.5.8.1 there are two methods that allow to get a dispatcher binder related to the agent or agent's coop: - so_this_agent_disp_binder(). It returns the dispatcher binder that is used for binding the agent itself; - so_this_coop_disp_binder(). It returns the dispatcher binder that is the default disp binder for the agent's coop. Please note that binders returned by so_this_agent_disp_binder() and so_this_coop_disp_binder() may be different binders. */ class SO_5_TYPE agent_t : private atomic_refcounted_t , public message_limit::message_limit_methods_mixin_t , public name_for_agent_methods_mixin_t { friend class subscription_bind_t; friend class state_t; friend class so_5::impl::mpsc_mbox_t; friend class so_5::impl::state_switch_guard_t; friend class so_5::impl::internal_agent_iface_t; friend class so_5::enveloped_msg::impl::agent_demand_handler_invoker_t; template< typename T > friend class intrusive_ptr_t; public: /*! * \brief Short alias for agent_context. * * \since v.5.5.4 */ using context_t = so_5::agent_context_t; /*! * \brief Short alias for %so_5::state_t. * * \since v.5.5.13 */ using state_t = so_5::state_t; /*! * \brief Short alias for %so_5::mhood_t. * * \since v.5.5.14 */ template< typename T > using mhood_t = so_5::mhood_t< T >; /*! * \brief Short alias for %so_5::mutable_mhood_t. * * \since v.5.5.19 */ template< typename T > using mutable_mhood_t = so_5::mutable_mhood_t< T >; /*! * \brief Short alias for %so_5::initial_substate_of. * * \since v.5.5.15 */ using initial_substate_of = so_5::initial_substate_of; /*! * \brief Short alias for %so_5::substate_of. * * \since v.5.5.15 */ using substate_of = so_5::substate_of; /*! * \brief Short alias for %so_5::state_t::history_t::shallow. * * \since v.5.5.15 */ static constexpr const state_t::history_t shallow_history = state_t::history_t::shallow; /*! * \brief Short alias for %so_5::state_t::history_t::deep. * * \since v.5.5.15 */ static constexpr const state_t::history_t deep_history = state_t::history_t::deep; //! Constructor. /*! Agent must be bound to the SObjectizer Environment during its creation. And that binding cannot be changed anymore. */ explicit agent_t( //! The Environment for this agent must exist. environment_t & env ); /*! * \brief Constructor which allows specification of * agent's tuning options. * * \par Usage sample: \code using namespace so_5; class my_agent : public agent_t { public : my_agent( environment_t & env ) : agent_t( env, agent_t::tuning_options() .subscription_storage_factory( vector_based_subscription_storage_factory() ) ) {...} } \endcode * * \since v.5.5.3 */ agent_t( environment_t & env, agent_tuning_options_t tuning_options ); /*! * \brief Constructor which simplifies agent construction with * or without agent's tuning options. * * \par Usage sample: * \code class my_agent : public so_5::agent_t { public : my_agent( context_t ctx ) : so_5::agent( ctx + limit_then_drop< get_status >(1) ) {} ... }; class my_more_specific_agent : public my_agent { public : my_more_specific_agent( context_t ctx ) : my_agent( ctx + limit_then_drop< reconfigure >(1) ) {} }; // Then somewhere in the code: auto coop = env.make_coop(); auto a = coop->make_agent< my_agent >(); auto b = coop->make_agent< my_more_specific_agent >(); * \endcode * * \since v.5.5.4 */ explicit agent_t( context_t ctx ); virtual ~agent_t(); //! Get the raw pointer of itself. /*! This method is intended for use in the member initialization list instead 'this' to suppres compiler warnings. For example for an agent state initialization: \code class a_sample_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; // Agent state. const so_5::state_t m_sample_state; public: a_sample_t( so_5::environment_t & env ) : base_type_t( env ), m_sample_state( self_ptr() ) { // ... } // ... }; \endcode */ inline const agent_t * self_ptr() const { return this; } inline agent_t * self_ptr() { return this; } //! Hook on agent start inside SObjectizer. /*! It is guaranteed that this method will be called first just after end of the cooperation registration process. During cooperation registration agent is bound to some working thread. And the first method which is called for the agent on that working thread context is this method. \code class a_sample_t : public so_5::agent_t { // ... virtual void so_evt_start(); // ... }; a_sample_t::so_evt_start() { std::cout << "first agent action on bound dispatcher" << std::endl; ... // Some application logic actions. } \endcode */ virtual void so_evt_start(); //! Hook of agent finish in SObjectizer. /*! It is guaranteed that this method will be called last just before deattaching agent from it's working thread. This method should be used to perform some cleanup actions on it's working thread. \code class a_sample_t : public so_5::agent_t { // ... virtual void so_evt_finish(); // ... }; a_sample_t::so_evt_finish() { std::cout << "last agent activity"; if( so_current_state() == m_db_error_happened ) { // Delete the DB connection on the same thread where // connection was established and where some // error happened. m_db.release(); } } \endcode */ virtual void so_evt_finish(); //! Access to the current agent state. /*! * Returns a reference to the current agent's state. * * If composite states are used then so_current_state() returns * a reference to the inner-most state. For example: * \code * class demo final : public so_5::agent_t * { * state_t st_top_1{ this, "top_1" }; * state_t st_child_1_1{ initial_substate_of{ st_top_1 }, "child_1" }; * state_t st_child_1_2{ substate_of{ st_top_1 }, "child_2" }; * * state_t st_child_1_1_1{ initial_substate_of{ st_child_1_1 }, "1" }; * state_t st_child_1_1_2{ substate_of{ st_child_1_1 }, "2" }; * state_t st_child_1_1_3{ substate_of{ st_child_1_1 }, "3" }; * ... * void so_define_agent() override * { * // Change the agent state. * this >>= st_top_1; * // Now the agent has several active states: * // * // st_top_1 (because this state has been explicitly activated) * // st_child_1_1 (because it's initial substate of st_top_1) * // st_child_1_1_1 (because it's initial substate of st_child_1_1). * // * // And so_current_state() will return reference to st_child_1_1_1. * assert(st_child_1_1_1 == so_current_state()); * * ... * } * }; * \endcode * * \note * There is a change in behaviour of this methon in v.5.5.22. * If some on_enter/on_exit handler calls this method during * the state change procedure this method will return the state * for which this on_enter/on_exit handler is called. For example: * \code * class demo final : public so_5::agent_t { * state_t st_1{ this }; * state_t st_1_1{ initial_substate_of{st_1} }; * state_t st_1_2{ substate_of{st_1}}; * ... * virtual void so_define_agent() override { * st_1.on_enter([this]{ * assert(st_1 == so_current_state()); * ... * }); * st_1_1.on_enter([this]{ * assert(st_1_1 == so_current_state()); * ... * }); * ... * } * }; * \endcode */ inline const state_t & so_current_state() const { return *m_current_state_ptr; } /*! * \brief Is a state activated? * * \note Since v.5.5.15 a state can have substates. For example * state A can have substates B and C. If B is the current state * then so_current_state() will return a reference to B. But * state A is active too because it is a superstate for B. * Method so_is_active_state(A) will return \a true in that case: * \code class demo : public so_5::agent_t { state_t A{ this, "A" }; state_t B{ initial_substate_of{ A }, "B" }; state_t C{ substate_of{ A }, "C" }; ... void some_event() { this >>= C; assert( C == so_current_state() ); assert( !( A == so_current_state() ) ); assert( so_is_active_state(A) ); ... } }; * \endcode * * \attention This method is not thread safe. Be careful calling * this method from outside of agent's working thread. * * \return \a true if state \a state_to_check is the current state * or if the current state is a substate of \a state_to_check. * * \since v.5.5.15 */ bool so_is_active_state( const state_t & state_to_check ) const noexcept; //! Add a state listener to the agent. /*! * A programmer should guarantee that the lifetime of * \a state_listener is exceeds lifetime of the agent. */ void so_add_nondestroyable_listener( agent_state_listener_t & state_listener ); //! Add a state listener to the agent. /*! * Agent takes care of the \a state_listener destruction. */ void so_add_destroyable_listener( agent_state_listener_unique_ptr_t state_listener ); /*! * \brief A reaction from SObjectizer to an exception from * agent's event. * * If an exception is going out from agent's event it will be * caught by SObjectizer. Then SObjectizer will call this method * and perform some actions in dependence of return value. * * \note Since v.5.3.0 default implementation calls * coop_t::exception_reaction() for agent's cooperation * object. * * \note * This method is noexcept since v.5.8.0. * * \since v.5.2.3 */ virtual exception_reaction_t so_exception_reaction() const noexcept; /*! * \brief Switching agent to special state in case of unhandled * exception. * * \note * Since v.5.7.3 it's implemented via so_deactivate_agent(). * * \attention * The method is not noexcept, it can throw an exception. So additional * care has to be taken when it's called in catch-block and/or in * noexcept contexts. * * \since 5.2.3 */ void so_switch_to_awaiting_deregistration_state(); //! Push an event to the agent's event queue. /*! This method is used by SObjectizer for the agent's event scheduling. */ static inline void call_push_event( agent_t & agent, const message_limit::control_block_t * limit, mbox_id_t mbox_id, const std::type_index & msg_type, const message_ref_t & message ) { agent.push_event( limit, mbox_id, msg_type, message ); } /*! * \brief Get the agent's direct mbox. * * \since v.5.4.0 */ const mbox_t & so_direct_mbox() const; /*! * \brief Create a new direct mbox for that agent. * * This method creates a new MPSC mbox which is connected * with that agent. Only agent for that so_make_new_direct_mbox() * has been called can make subscriptions for a new mbox. * * Note. The new mbox doesn't replaces the standard direct mbox * for the agent. Old direct mbox is still here and can still be used * for sending messages to the agent. But new mbox is not related * to the old direct mbox: they are different mboxes and can be used * for different subscriptions. * For example: * \code * class my_agent final : public so_5::agent_t { * ... * void so_evt_start() override { * so_subscribe_self().event( [](mhood_t) { * std::cout << "hello from the direct mbox" << std::endl; * } ); * * const auto new_mbox = so_make_new_direct_mbox(); * so_subscribe( new_mbox ).event( [](mhood_t(*this); * so_5::send(new_mbox); * } * }; * \endcode * The output will be: \verbatim hello from the direct mbox hello from a new mbox \endverbatim * * \since v.5.6.0 */ mbox_t so_make_new_direct_mbox(); /*! * \brief Create tuning options object with default values. * * \since v.5.5.3 */ inline static agent_tuning_options_t tuning_options() { return agent_tuning_options_t(); } /*! * \brief Helper for creation a custom direct mbox factory. * * Usage example: * \code * class my_agent : public so_5::agent_t { * ... * public: * my_agent( context_t ctx ) * : so_5::agent_t{ ctx + custom_direct_mbox_factory( * []( so_5::partially_constructed_agent_ptr_t agent_ptr, * so_5::mbox_t actual_mbox ) * { * return so_5::mbox_t{ new my_custom_mbox{ agent_ptr.ptr(), std::move(actual_mbox) } }; * } ) * } * {...} * * ... * }; * \endcode * * \since v.5.7.4 */ template< typename Lambda > [[nodiscard]] static custom_direct_mbox_factory_t custom_direct_mbox_factory( Lambda && lambda ) { return { std::forward(lambda) }; } protected: /*! * \name Accessing the default state. * \{ */ //! Access to the agent's default state. const state_t & so_default_state() const; /*! * \} */ public : /* Note: since v.5.5.1 method so_change_state() is public */ /*! * \name Changing agent's state. * \{ */ //! Change the current state of the agent. /*! Usage sample: \code void a_sample_t::evt_smth( mhood_t< message_one_t > msg ) { // If something wrong with the message then we should // switch to the error_state. if( error_in_data( *msg ) ) so_change_state( m_error_state ); } \endcode \attention This method has to be called from a worker thread assigned to the agent by the dispatcher. This method can't be called from thread_safe event-handlers because so_change_state() modifies the state of the agent. */ void so_change_state( //! New agent state. const state_t & new_state ); /*! * \brief Deactivate the agent. * * This method deactivates the agent: * * - drops all agent's subscriptions (including deadletter handlers) and * delivery filters; * - switches the agent to a special state in that the agent does nothing * and just waits the deregistration. * * Sometimes it is necessary to mark an agent as 'failed'. Such an agent * shouldn't process anything and the only thing that is allowed * is waiting for the deregistration. For example: * * \code * class some_agent final : public so_5::agent_t * { * state_t st_working{ this, "working" }; * state_t st_failed{ this, "failed" }; * ... * void on_enter_st_failed() * { * // Notify some supervisor about the failure. * // It will deregister the whole cooperation with failed agent. * so_5::send( supervisor_mbox(), ... ); * } * ... * void so_define_agent() override * { * this >>= st_working; * * st_failed.on_enter( &some_agent::on_enter_st_failed ); * * ... * } * * void evt_some_event(mhood_t cmd) * { * try * { * do_some_processing_of(*cmd); * } * catch(...) * { * // Processing failed, agent can't continue work normally. * // Have to switch it to the failed state and wait for * // the deregistration. * this >>= st_failed; * } * } * ... * }; * \endcode * * This approach works but has a couple of drawbacks: * * - it's necessary to define a separate state for an agent (like * st_failed shown above); * - agent still has all its subscriptions. It means that messages will * be delivered to the agent's event queue and dispatched by the * agent's dispatcher. They won't be handled because there are no * subscriptions in a state like st_failed, but message dispatching * will consume some resources, although the agent is a special * 'failed' state. * * To cope with those drawbacks so_deactivate_agent was introduced in * v.5.7.3. That method drops all agent's subscriptions (including * deadletter handlers and delivery filters) and switches the agent to a * special hidden state in that the agent doesn't handle anything. * * The example above now can be rewritten that way: * \code * class some_agent final : public so_5::agent_t * { * state_t st_working{ this, "working" }; * ... * void switch_to_failed_state() * { * // Notify some supervisor about the failure. * // It will deregister the whole cooperation with failed agent. * so_5::send( supervisor_mbox(), ... ); * // Deactivate the agent. * so_deactivate_agent(); * } * ... * void so_define_agent() override * { * this >>= st_working; * ... * } * * void evt_some_event(mhood_t cmd) * { * try * { * do_some_processing_of(*cmd); * } * catch(...) * { * // Processing failed, agent can't continue work normally. * // Have to switch it to the failed state and wait for * // the deregistration. * switch_to_failed_state(); * } * } * ... * }; * \endcode * * \note * The method uses so_change_state(), so it has all requirements of * so_change_state(). Because the agent state will be changed, * so_deactivate_state() has to be called on the working thread * assigned to the agent by the dispatcher, and * so_deactivate_state() can't be invoked from a thread_safe event * handler. * * \attention * The method is not noexcept, it can throw an exception. So additional * care has to be taken when it's called in catch-block and/or in * noexcept contexts. * * \since v.5.7.3 */ void so_deactivate_agent(); /*! * \brief Dropping all agents subscriptions and filters. * * This method is similar to so_deactivate_agent(), but it leaves * the agent in the current state. The agent can change its state * again or make new subscriptions later. * * \since v.5.8.5 */ void so_drop_all_subscriptions_and_filters(); /*! * \} */ public : /* Note: since v.5.2.3.2 subscription-related method are made public. */ /*! * \name Subscription methods. * \{ */ //! Initiate subscription. /*! This method starts a subscription procedure by returning an instance of subscription_bind_t. The subscription details and the completion of a subscription is controlled by this subscription_bind_t object. Usage sample: \code void a_sample_t::so_define_agent() { // Subscription for state `state_one` so_subscribe( mbox_target ) .in( state_one ) .event( &a_sample_t::evt_sample_handler ); // Subscription for the default state. so_subscribe( another_mbox ) .event( &a_sample_t::evt_another_handler ); // Subscription for several event handlers in the default state. so_subscribe( yet_another_mbox ) .event( &a_sample_t::evt_yet_another_handler ) // Lambda-function can be used as event handler too. .event( [this](mhood_t cmd) {...} ); // Subscription for several event handlers. // All of them will be subscribed for states first_state and second_state. so_subscribe( some_mbox ) .in( first_state ) .in( second_state ) .event( &a_sample_t::evt_some_handler_1 ) .event( &a_sample_t::evt_some_handler_2 ) .event( &a_sample_t::evt_some_handler_3 ); } \endcode */ inline subscription_bind_t so_subscribe( //! Mbox for messages to subscribe. const mbox_t & mbox_ref ) { return subscription_bind_t( *this, mbox_ref ); } /*! * \brief Initiate subscription to agent's direct mbox. * * Note that is just a short form of: * \code * void a_sample_t::so_define_agent() * { * so_subscribe( so_direct_mbox() ) * .in( some_state ) * .in( another_state ) * .event( some_event_handler ) * .event( some_another_handler ); * } * \endcode * Instead of writting `so_subscribe(so_direct_mbox())` it is possible * to write just `so_subscribe_self()`. * * \par Usage sample: \code void a_sample_t::so_define_agent() { // Subscription for state `state_one` so_subscribe_self() .in( state_one ) .event( &a_sample_t::evt_sample_handler ); // Subscription for the default state. so_subscribe_self() .event( &a_sample_t::evt_another_handler ); // Subscription for several event handlers in the default state. so_subscribe_self() .event( &a_sample_t::evt_yet_another_handler ) // Lambda-function can be used as event handler too. .event( [this](mhood_t cmd) {...} ); // Subscription for several event handlers. // All of them will be subscribed for states first_state and second_state. so_subscribe_self() .in( first_state ) .in( second_state ) .event( &a_sample_t::evt_some_handler_1 ) .event( &a_sample_t::evt_some_handler_2 ) .event( &a_sample_t::evt_some_handler_3 ); } \endcode * * \since v.5.5.1 */ inline subscription_bind_t so_subscribe_self() { return so_subscribe( so_direct_mbox() ); } /*! * \brief Create a subscription for an event. * * \note * Before v.5.5.21 it was a private method. Since v.5.5.21 * it is a public method with a standard so_-prefix. * It was made public to allow creation of subscriptions * to agent from outside of agent. * * \note * Parameter \a handler_kind was introduced in v.5.7.0. */ void so_create_event_subscription( //! Message's mbox. const mbox_t & mbox_ref, //! Message type. std::type_index type_index, //! State for event. const state_t & target_state, //! Event handler caller. const event_handler_method_t & method, //! Thread safety of the event handler. thread_safety_t thread_safety, //! Kind of that event handler. event_handler_kind_t handler_kind ); /*! * \brief Destroy event subscription. * * \note * This method was introduced in v.5.5.21 to allow manipulation * of agent's subscriptions from outside of an agent. * * \note * It is safe to try to destroy nonexistent subscription. * * \since v.5.5.21 */ void so_destroy_event_subscription( //! Message's mbox. const mbox_t & mbox, //! Message's type. const std::type_index & subscription_type, //! Target state of a subscription. const state_t & target_state ) { do_drop_subscription( mbox, subscription_type, target_state ); } /*! * \brief Drop subscription for the state specified. * * This overload is indended to be used when there is an event-handler in * the form of agent's method. And there is a need to unsubscribe this * event handler. * For example: * \code * class demo : public so_5::agent_t { * void on_some_event(mhood_t cmd) { * if(cmd->some_condition) * // New subscription must be created. * so_subscribe(some_mbox).in(some_state) * .event(&demo::one_shot_message_handler); * ... * } * * void one_shot_message_handler(mhood_t cmd) { * ... // Some actions. * // Subscription is no more needed. * so_drop_subscription(some_mbox, some_state, * &demo::one_shot_message_handler); * } * }; * \endcode * * \note Doesn't throw if there is no such subscription. * * \note Subscription is removed even if agent was subscribed * for this message type with different method pointer. * The pointer to event routine is necessary only to * detect MSG type. * * \since v.5.2.3 */ template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>::value, void >::type so_drop_subscription( const mbox_t & mbox, const state_t & target_state, Method_Pointer /*pfn*/ ) { using pfn_traits = details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>; using message_type = typename details::message_handler_format_detector< typename pfn_traits::argument_type >::type; do_drop_subscription( mbox, message_payload_type< message_type >::subscription_type_index(), target_state ); } /*! * \brief Drop subscription for the state specified. * * Usage example: * \code * class demo : public so_5::agent_t { * void on_turn_listening_on(mhood_t cmd) { * // New subscription must be created. * so_subscribe(cmd->listeting_mbox()).in(some_state) * .event([this](mhood_t cmd) {...}); * ... * } * * void on_turn_listening_off(mhood_t cmd) { * // Subscription is no more needed. * so_drop_subscription(cmd->listening_mbox(), some_state); * ... * } * }; * \endcode * * \note Doesn't throw if there is no such subscription. * * \since v.5.5.3 */ template< class Message > inline void so_drop_subscription( const mbox_t & mbox, const state_t & target_state ) { do_drop_subscription( mbox, message_payload_type< Message >::subscription_type_index(), target_state ); } /*! * \brief Drop subscription for the default agent state. * * This overload is indended to be used when there is an event-handler in * the form of agent's method. And there is a need to unsubscribe this * event handler. * For example: * \code * class demo : public so_5::agent_t { * void on_some_event(mhood_t cmd) { * if(cmd->some_condition) * // New subscription must be created. * so_subscribe(some_mbox) * .event(&demo::one_shot_message_handler); * ... * } * * void one_shot_message_handler(mhood_t cmd) { * ... // Some actions. * // Subscription is no more needed. * so_drop_subscription(some_mbox, * &demo::one_shot_message_handler); * } * }; * \endcode * * \note Doesn't throw if there is no such subscription. * * \note Subscription is removed even if agent was subscribed * for this message type with different method pointer. * The pointer to event routine is necessary only to * detect Msg type. * * \since v.5.2.3 */ template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>::value, void >::type so_drop_subscription( const mbox_t & mbox, Method_Pointer /*pfn*/ ) { using pfn_traits = details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>; using message_type = typename details::message_handler_format_detector< typename pfn_traits::argument_type >::type; do_drop_subscription( mbox, message_payload_type< message_type >::subscription_type_index(), so_default_state() ); } /*! * \brief Drop subscription for the default agent state. * * Usage example: * \code * class demo : public so_5::agent_t { * void on_turn_listening_on(mhood_t cmd) { * // New subscription must be created. * so_subscribe(cmd->listening_mbox()) * .event([this](mhood_t cmd) {...}); * ... * } * * void on_turn_listening_off(mhood_t cmd) { * // Subscription is no more needed. * so_drop_subscription(cmd->listening_mbox()); * ... * } * }; * \endcode * * \note Doesn't throw if there is no such subscription. * * \since v.5.5.3 */ template< class Message > inline void so_drop_subscription( const mbox_t & mbox ) { do_drop_subscription( mbox, message_payload_type< Message >::subscription_type_index(), so_default_state() ); } /*! * \brief Drop subscription for all states. * * Usage example: * \code * class demo : public so_5::agent_t { * state_t st_working{this}, st_waiting{this}, st_stopping{this}; * ... * void on_turn_listening_on(mhood_t cmd) { * // Make subscriptions for message of type state_change_notify. * st_working.event(cmd->listening_mbox(), * &demo::on_state_notify_when_working); * st_waiting.event(cmd->listening_mbox(), * &demo::on_state_notify_when_waiting); * st_waiting.event(cmd->listening_mbox(), * &demo::on_state_notify_when_stopping); * ... * } * void on_turn_listening_off(mhood_t cmd) { * // Subscriptions are no more needed. * // All three event handlers for state_change_notify * // will be unsubscribed. * so_drop_subscription_for_all_states(cmd->listening_mbox(), * &demo::on_state_notify_when_working); * } * ... * void on_state_notify_when_working(mhood_t cmd) {...} * void on_state_notify_when_waiting(mhood_t cmd) {...} * void on_state_notify_when_stopping(mhood_t cmd) {...} * }; * \endcode * * \note Doesn't throw if there is no any subscription for * that mbox and message type. * * \note Subscription is removed even if agent was subscribed * for this message type with different method pointer. * The pointer to event routine is necessary only to * detect Msg type. * * \note * Since v.5.5.21 this method also drops the subscription * for a deadletter handler for that type of message/signal. * * \since v.5.2.3 */ template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>::value, void >::type so_drop_subscription_for_all_states( const mbox_t & mbox, Method_Pointer /*pfn*/ ) { using pfn_traits = details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>; using message_type = typename details::message_handler_format_detector< typename pfn_traits::argument_type >::type; do_drop_subscription_for_all_states( mbox, message_payload_type< message_type >::subscription_type_index() ); } /*! * \brief Drop subscription for all states. * * Usage example: * \code * class demo : public so_5::agent_t { * state_t st_working{this}, st_waiting{this}, st_stopping{this}; * ... * void on_turn_listening_on(mhood_t cmd) { * // Make subscriptions for message of type state_change_notify. * st_working.event(cmd->listening_mbox(), * [this](mhood_t cmd) {...}); * st_waiting.event(cmd->listening_mbox(), * [this](mhood_t cmd) {...}); * st_waiting.event(cmd->listening_mbox(), * [this](mhood_t cmd) {...}); * ... * } * void on_turn_listening_off(mhood_t cmd) { * // Subscriptions are no more needed. * // All three event handlers for state_change_notify * // will be unsubscribed. * so_drop_subscription_for_all_states(cmd->listening_mbox()); * } * ... * }; * \endcode * \note Doesn't throw if there is no any subscription for * that mbox and message type. * * \note * Since v.5.5.21 this method also drops the subscription * for a deadletter handler for that type of message/signal. * * \since v.5.5.3 */ template< class Message > inline void so_drop_subscription_for_all_states( const mbox_t & mbox ) { do_drop_subscription_for_all_states( mbox, message_payload_type< Message >::subscription_type_index() ); } /*! * \brief Check the presence of a subscription. * * This method can be used to avoid an exception from so_subscribe() * in the case if the subscription is already present. For example: * \code * void my_agent::evt_create_new_subscription(mhood_t cmd) * { * // cmd can contain mbox we have already subscribed to. * // If we just call so_subscribe() then an exception can be thrown. * // Because of that check the presence of subscription first. * if(!so_has_subscription(cmd->mbox(), so_default_state())) * { * // There is no subscription yet. New subscription can be * // created. * so_subscribe(cmd->mbox()).event(...); * } * } * \endcode * * \note * Please do not call this method from outside of working context * of the agent. * * \return true if subscription is present for \a target_state. * * \tparam Message a type of message/signal subscription to which * must be checked. * * \since v.5.5.19.5 */ template< class Message > bool so_has_subscription( //! A mbox from which message/signal of type \a Message is expected. const mbox_t & mbox, //! A target state for the subscription. const state_t & target_state ) const noexcept { return do_check_subscription_presence( mbox, message_payload_type< Message >::subscription_type_index(), target_state ); } /*! * \brief Check the presence of a subscription in the default_state. * * This method can be used to avoid an exception from so_subscribe() * in the case if the subscription is already present. For example: * \code * void my_agent::evt_create_new_subscription(mhood_t cmd) * { * // cmd can contain mbox we have already subscribed to. * // If we just call so_subscribe() then an exception can be thrown. * // Because of that check the presence of subscription first. * if(!so_has_subscription(cmd->mbox())) * { * // There is no subscription yet. New subscription can be * // created. * so_subscribe(cmd->mbox()).event(...); * } * } * \endcode * * \note * Please do not call this method from outside of working context * of the agent. * * \return true if subscription is present for the default_state. * * \tparam Message a type of message/signal subscription to which * must be checked. * * \since v.5.5.19.5 */ template< class Message > bool so_has_subscription( //! A mbox from which message/signal of type \a Message is expected. const mbox_t & mbox ) const noexcept { return do_check_subscription_presence( mbox, message_payload_type< Message >::subscription_type_index(), so_default_state() ); } /*! * \brief Check the presence of a subscription. * * Type of message is deducted from event-handler signature. * * Usage example: * \code * void my_agent::evt_create_new_subscription(mhood_t cmd) * { * // cmd can contain mbox we have already subscribed to. * // If we just call so_subscribe() then an exception can be thrown. * // Because of that check the presence of subscription first. * if(!so_has_subscription(cmd->mbox(), my_state, &my_agent::my_event)) * { * // There is no subscription yet. New subscription can be * // created. * so_subscribe(cmd->mbox()).event(...); * } * } * \endcode * * \note * Please do not call this method from outside of working context * of the agent. * * \return true if subscription is present for \a target_state. * * \since v.5.5.19.5 */ template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>::value, bool >::type so_has_subscription( //! A mbox from which message/signal is expected. const mbox_t & mbox, //! A target state for the subscription. const state_t & target_state, Method_Pointer /*pfn*/ ) const noexcept { using pfn_traits = details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>; using message_type = typename details::message_handler_format_detector< typename pfn_traits::argument_type>::type; return this->so_has_subscription( mbox, target_state ); } /*! * \brief Check the presence of a subscription. * * Subscription is checked for the default agent state. * * Type of message is deducted from event-handler signature. * * Usage example: * \code * void my_agent::evt_create_new_subscription(mhood_t cmd) * { * // cmd can contain mbox we have already subscribed to. * // If we just call so_subscribe() then an exception can be thrown. * // Because of that check the presence of subscription first. * if(!so_has_subscription(cmd->mbox(), &my_agent::my_event)) * { * // There is no subscription yet. New subscription can be * // created. * so_subscribe(cmd->mbox()).event(...); * } * } * \endcode * * \note * Please do not call this method from outside of working context * of the agent. * * \return true if subscription is present for the default state. * * \since v.5.5.19.5 */ template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>::value, bool >::type so_has_subscription( //! A mbox from which message/signal is expected. const mbox_t & mbox, Method_Pointer /*pfn*/ ) const noexcept { using pfn_traits = details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>; using message_type = typename details::message_handler_format_detector< typename pfn_traits::argument_type>::type; return this->so_has_subscription( mbox, so_default_state() ); } /*! * \} */ /*! * \name Methods for dealing with deadletter subscriptions. * \{ */ /*! * \brief Create a subscription for a deadletter handler. * * \note * This is low-level method intended to be used by libraries writters. * Do not call it directly if you don't understand its purpose and * what its arguments mean. Use so_subscribe_deadletter_handler() * instead. * * This method actually creates a subscription to deadletter handler * for messages/signal of type \a msg_type from mbox \a mbox. * * \throw so_5::exception_t in the case when the subscription * of a deadletter handler for type \a msg_type from \a mbox is * already exists. * * \since v.5.5.21 */ void so_create_deadletter_subscription( //! Message's mbox. const mbox_t & mbox, //! Message type. const std::type_index & msg_type, //! Event handler caller. const event_handler_method_t & method, //! Thread safety of the event handler. thread_safety_t thread_safety ); /*! * \brief Destroy a subscription for a deadletter handler. * * \note * This is low-level method intended to be used by libraries writters. * Do not call it directly if you don't understand its purpose and * what its arguments mean. Use so_drop_deadletter_handler() instead. * * This method actually destroys a subscription to deadletter handler * for messages/signal of type \a msg_type from mbox \a mbox. * * \note * It is safe to call this method if there is no such * deadletter handler. It will do nothing in such case. * * \since v.5.5.21 */ void so_destroy_deadletter_subscription( //! Message's mbox. const mbox_t & mbox, //! Message type. const std::type_index & msg_type ); /*! * \brief Create a subscription for deadletter handler for * a specific message from a specific mbox. * * Type of a message for deadletter handler will be detected * automatically from the signature of the \a handler. * * A deadletter handler can be a pointer to method of agent or * lambda-function. The handler should have one of the following * format: * \code * void deadletter_handler(message_type); * void deadletter_handler(const message_type &); * void deadletter_handler(mhood_t); * \endcode * * Usage example: * \code * class demo : public so_5::agent_t { * void on_some_message(mhood_t cmd) {...} * ... * virtual void so_define_agent() override { * // Create deadletter handler via pointer to method. * // Event handler will be not-thread-safe. * so_subscribe_deadletter_handler( * so_direct_mbox(), * &demo::on_some_message ); * * // Create deadletter handler via lambda-function. * so_subscribe_deadletter_handler( * status_mbox(), // Any mbox can be used, not only agent's direct mbox. * [](mhood_t cmd) { * so_5::send(cmd->reply_mbox, "workind"); * }, * // This handler will be thread-safe one. * so_5::thread_safe ); * } * }; * \endcode * * \throw so_5::exception_t in the case when the subscription * of a deadletter handler for type \a msg_type from \a mbox is * already exists. * * \since v.5.5.21 */ template< typename Event_Handler > void so_subscribe_deadletter_handler( const so_5::mbox_t & mbox, Event_Handler && handler, thread_safety_t thread_safety = thread_safety_t::unsafe ) { using namespace details::event_subscription_helpers; const auto ev = preprocess_agent_event_handler( mbox, *this, std::forward(handler) ); so_create_deadletter_subscription( mbox, ev.m_msg_type, ev.m_handler, thread_safety ); } /*! * \brief Drops the subscription for deadletter handler. * * A message type must be specified explicitely via template * parameter. * * Usage example: * \code * class demo : public so_5::agent_t { * void some_deadletter_handler(mhood_t cmd) { * ... // Do some stuff. * // There is no need for deadletter handler. * so_drop_deadletter_handler(some_mbox); * } * ... * }; * \endcode * * \note * Is is safe to call this method if there is no a deadletter * handler for message of type \a Message from message box * \a mbox. * * \tparam Message Type of a message or signal for deadletter * handler. * * \since v.5.5.21 */ template< typename Message > void so_drop_deadletter_handler( //! A mbox from which the message is expected. const so_5::mbox_t & mbox ) { so_destroy_deadletter_subscription( mbox, message_payload_type< Message >::subscription_type_index() ); } /*! * \brief Checks the presence of deadletter handler for a message of * a specific type from a specific mbox. * * Message type must be specified explicitely via template * parameter \a Message. * * \return true if a deadletter for a message/signal of type * \a Message from message mbox \a mbox exists. * * Usage example: * \code * class demo : public so_5::agent_t { * void on_some_request(mhood_t cmd) { * if(!so_has_deadletter_handler(some_mbox)) * // There is no deadletter handler yet. * // It should be created now. * so_subscribe_deadletter_handler( * some_mbox, * [this](mhood_t cmd) {...}); * ... * } * }; * \endcode * * \tparam Message Type of a message or signal for deadletter * handler. * * \since v.5.5.21 */ template< typename Message > bool so_has_deadletter_handler( //! A mbox from which the message is expected. const so_5::mbox_t & mbox ) const noexcept { return do_check_deadletter_presence( mbox, message_payload_type< Message >::subscription_type_index() ); } /*! * \} */ protected : /*! * \name Agent initialization methods. * \{ */ /*! * \brief A correct initiation of so_define_agent method call. * * Before the actual so_define_agent() method it is necessary * to temporary set working thread id. And then drop this id * to non-actual value after so_define_agent() return. * * Because of that this method must be called during cooperation * registration procedure instead of direct call of so_define_agent(). * * \since v.5.4.0 */ void so_initiate_agent_definition(); //! Hook on define agent for SObjectizer. /*! This method is called by SObjectizer during the cooperation registration process before agent will be bound to its working thread. Should be used by the agent to make necessary message subscriptions. Usage sample; \code class a_sample_t : public so_5::agent_t { // ... virtual void so_define_agent(); void evt_handler_1( mhood_t< message1 > msg ); // ... void evt_handler_N( mhood_t< messageN > & msg ); }; void a_sample_t::so_define_agent() { // Make subscriptions... so_subscribe( m_mbox1 ) .in( m_state_1 ) .event( &a_sample_t::evt_handler_1 ); // ... so_subscribe( m_mboxN ) .in( m_state_N ) .event( &a_sample_t::evt_handler_N ); } \endcode */ virtual void so_define_agent(); //! Is method define_agent already called? /*! Usage sample: \code class a_sample_t : public so_5::agent_t { // ... public: void set_target_mbox( const so_5::mbox_t & mbox ) { // mbox cannot be changed after agent registration. if( !so_was_defined() ) { m_target_mbox = mbox; } } private: so_5::mbox_t m_target_mbox; }; \endcode */ bool so_was_defined() const; /*! * \} */ public: //! Access to the SObjectizer Environment which this agent is belong. /*! Usage sample for other cooperation registration: \code void a_sample_t::evt_on_smth( mhood_t< some_message_t > msg ) { so_5::coop_unique_holder_t coop = so_environment().make_coop(); // Filling the cooperation... coop->make_agent< a_another_t >( ... ); // ... // Registering cooperation. so_environment().register_coop( std::move(coop) ); } \endcode Usage sample for the SObjectizer shutting down: \code void a_sample_t::evt_last_event( mhood_t< message_one_t > msg ) { ... so_environment().stop(); } \endcode */ environment_t & so_environment() const noexcept; /*! * \brief Get a handle of agent's coop. * * \note * This method is a replacement for so_coop_name() method * from previous versions of SObjectizer-5. * * \attention * If this method is called when agent is not registered (e.g. * there is no coop for agent) then this method will throw. * * Usage example: * \code * class parent final : public so_5::agent_t { * ... * void so_evt_start() override { * // Create a child coop. * auto coop = so_environment().make_coop( * // We as a parent coop. * so_coop() ); * ...; // Fill the coop. * so_environment().register_coop( std::move(coop) ); * } * }; * \endcode * * \since v.5.6.0 */ [[nodiscard]] coop_handle_t so_coop() const; /*! * \brief Binding agent to the dispatcher. * * This is an actual start of agent's work in SObjectizer. * * \note * This method was a de-facto noexcept in previous versions of * SObjectizer. But didn't marked as noexcept because of need of * support old C++ compilers. Since v.5.6.0 it is officially noexcept. * * \since v.5.4.0 */ void so_bind_to_dispatcher( //! Actual event queue for an agent. event_queue_t & queue ) noexcept; /*! * \brief Create execution hint for the specified demand. * * The hint returned is intendent for the immediately usage. * It must not be stored for the long time and used sometime in * the future. It is because internal state of the agent * can be changed and some references from hint object to * agent's internals become invalid. * * \since v.5.4.0 */ static execution_hint_t so_create_execution_hint( //! Demand for execution of event handler. execution_demand_t & demand ); /*! * \brief A helper method for deregistering agent's coop. * * Usage example: * \code * class demo : public so_5::agent_t { * ... * void on_some_event(mhood_t cmd) { * try { * ... // Some processing. * if(no_more_work_left()) * // Normal deregistration of the coop. * so_deregister_agent_coop_normally(); * } * catch(...) { * // Some error. * // Deregister the coop with special 'exit code'. * so_deregister_agent_coop(so_5::dereg_reason::user_defined_reason+10); * } * } * }; * \endcode * * \since v.5.4.0 */ void so_deregister_agent_coop( int dereg_reason ); /*! * \brief A helper method for deregistering agent's coop * in case of normal deregistration. * * \note It is just a shorthand for: \code so_deregister_agent_coop( so_5::dereg_reason::normal ); \endcode * * \since v.5.4.0 */ void so_deregister_agent_coop_normally(); /*! * \name Methods for dealing with message delivery filters. * \{ */ /*! * \brief Set a delivery filter. * * \note * Since v.5.7.4 it can be used for mutable messages too (if mbox is MPSC mbox). * In that case \a Message should be in form `so_5::mutable_msg`. * * \tparam Message type of message to be filtered. * * \since v.5.5.5 */ template< typename Message > void so_set_delivery_filter( //! Message box from which message is expected. //! This must be MPMC-mbox. const mbox_t & mbox, //! Delivery filter instance. delivery_filter_unique_ptr_t filter ) { ensure_not_signal< Message >(); do_set_delivery_filter( mbox, message_payload_type< Message >::subscription_type_index(), std::move(filter) ); } /*! * \brief Set a delivery filter. * * \tparam Lambda type of lambda-function or functional object which * must be used as message filter. * * \par Usage sample: \code void my_agent::so_define_agent() { so_set_delivery_filter( temp_sensor, []( const current_temperature & msg ) { return !is_normal_temperature( msg ); } ); ... } \endcode * * \since v.5.5.5 */ template< typename Lambda > void so_set_delivery_filter( //! Message box from which message is expected. //! This must be MPMC-mbox. const mbox_t & mbox, //! Delivery filter as lambda-function or functional object. Lambda && lambda ); /*! * \brief Set a delivery filter for a mutable message. * * \tparam Lambda type of lambda-function or functional object which * must be used as message filter. * * \note * The content of the message will be passed to delivery-filter * lambda-function by a const reference. * * \par Usage sample: \code void my_agent::so_define_agent() { so_set_delivery_filter_for_mutable_msg( temp_sensor, []( const current_temperature & msg ) { return !is_normal_temperature( msg ); } ); ... } \endcode * * \since v.5.7.4 */ template< typename Lambda > void so_set_delivery_filter_for_mutable_msg( //! Message box from which message is expected. //! This must be MPMC-mbox. const mbox_t & mbox, //! Delivery filter as lambda-function or functional object. Lambda && lambda ); /*! * \brief Drop a delivery filter. * * Usage example: * \code * // For a case of an immutable message. * void some_agent::some_event(mhood_t cmd) { * ... // Some actions. * // Now we want to drop the subscription and the delivery * // filter for this message. * so_drop_subscription_for_all_states(source_mbox); * so_drop_delivery_filter(source_mbox); * } * * // For a case of a mutable message. * void some_agent::some_event(mutable_mhood_t cmd) { * ... // Some actions. * // Now we want to drop the subscription and the delivery * // filter for this message. * so_drop_subscription_for_all_states>(source_mbox); * so_drop_delivery_filter>(source_mbox); * } * \endcode * * \tparam Message type of message filtered. * * \since v.5.5.5 */ template< typename Message > void so_drop_delivery_filter( //! Message box to which delivery filter was set. //! This must be MPMC-mbox. const mbox_t & mbox ) noexcept { do_drop_delivery_filter( mbox, message_payload_type< Message >::subscription_type_index() ); } /*! * \} */ /*! * \name Dealing with priority. * \{ */ /*! * \brief Get the priority of the agent. * * \since v.5.5.8 */ [[nodiscard]] priority_t so_priority() const noexcept { return m_priority; } /*! * \} */ /*! * \brief Helper method that allows to run a block of code as * non-thread-safe event handler. * * \attention * This is a low-level method. Using it may destroy all thread-safety * guarantees provided by SObjectizer. Please use it only when you know * what your are doing. All responsibility rests with the user. * * Use of this method may be necessary when an agent is bound to a * special dispatcher that runs not only the agent's event-handlers, but * also other callbacks on the same worker thread. * * A good example of such a dispatcher is so5extra's asio_one_thread * dispatcher. It guarantees that an IO completion handler is called on * the same worker thread as agent's event-handlers. For example: * \code * class agent_that_uses_asio : public so_5::agent_t * { * state_t st_not_ready{this}; * state_t st_ready{this}; * * asio::io_context & io_ctx_; * * public: * agent_that_uses_asio(context_t ctx, asio::io_context & io_ctx) * : so_5::agent_t{std::move(ctx)} * , io_ctx_{io_ctx} * {} * ... * void so_define_agent() override * { * st_not_ready.activate(); * ... * } * * void so_evt_start() override * { * auto resolver = std::make_shared(io_ctx_); * resolver->async_resolve("some.host.name", "", * asio::ip::tcp::numeric_service | asio::ip::tcp::address_configured, * // IO completion handler to be run on agent's worker thread. * [resolver, this](auto ec, auto results) { * // It's necessary to wrap the block of code, otherwise * // modification of the agent's state (or managing of subscriptions) * // will be prohibited because SObjectizer doesn't see * // the IO completion handler as event handler. * so_low_level_exec_as_event_handler( [&]() { * ... * st_ready.activate(); * }); * }); * } * } * \endcode * * \attention * Using this method inside a running event-handler (non-thread-safe and * especially thread-safe) is undefined behavior. SObjectizer can't check * such a case without a significant performance penalty, so there won't * be any warnings or errors from SObjectizer's side, anything can * happen. * * \since v.5.8.0 */ template< typename Lambda > decltype(auto) so_low_level_exec_as_event_handler( Lambda && lambda ) noexcept( noexcept(lambda()) ) { impl::agent_impl::working_thread_id_sentinel_t sentinel{ m_working_thread_id, query_current_thread_id() }; return lambda(); } /*! * \brief Returns the dispatcher binder that is used for binding this * agent. * * \attention * It's safe to use this method only while the agent is registered * in a SObjectizer Environment -- from the start of so_evt_start() until * the completion so_evt_finish(). The calling of this method when agent * it not registered (e.g. before the invocation of so_evt_start() or * after the completion of so_evt_finish()) may lead to undefined behavior. * * This method is intended to simplify creation of children cooperations: * \code * void parent_agent::evt_some_command(mhood_t cmd) { * ... * // A new child coop has to be created and bound to the same * // dispatcher as the parent agent. * so_5::introduce_child_coop( *this, * // Get the binder of the parent. * so_this_agent_disp_binder(), * [&](so_5::coop_t & coop) { * ... // Creation of children agents. * } ); * } * \endcode * * \since v.5.8.1 */ [[nodiscard]] disp_binder_shptr_t so_this_agent_disp_binder() const { return m_disp_binder; } /*! * \brief Returns the dispatcher binder that is used as the default * binder for the agent's coop. * * \attention * It's safe to use this method only while the agent is registered * in a SObjectizer Environment -- from the start of so_evt_start() until * the completion so_evt_finish(). The calling of this method when agent * it not registered (e.g. before the invocation of so_evt_start() or * after the completion of so_evt_finish()) may lead to undefined behavior. * * This method is intended to simplify creation of children cooperations: * \code * void parent_agent::evt_some_command(mhood_t cmd) { * ... * // A new child coop has to be created and bound to the same * // dispatcher as the parent agent. * so_5::introduce_child_coop( *this, * // Get the binder of the parent's coop. * so_this_coop_disp_binder(), * [&](so_5::coop_t & coop) { * ... // Creation of children agents. * } ); * } * \endcode * * \note * This method may return a different binder that so_this_agent_disp_binder() * in a case when the agent was bound by a separate dispatcher. For example: * \code * // The parent coop will use thread_pool dispatcher as * // the default dispatcher. * env.introduce_coop( * so_5::disp::thread_pool::make_dispatcher( env, 8u ) * .binder( []( auto & params ) { * // Every agent will have a separate event queue. * params.fifo( so_5::disp::thread_pool::fifo_t::individual ); * } ), * [&]( so_5::coop_t & coop ) { * // The parent agent itself will use a separate dispatcher. * coop.make_agent_with_binder< parent_agent >( * so_5::disp::one_thread::make_dispatcher( env ).binder(), * ... ); * * ... // Creation of other agents. * } ); * \endcode * In that case use of so_this_agent_disp_binder() instead of * so_this_coop_disp_binder() will bind children agents to the * parent's one_thread dispatcher instead of coop's thread_pool * dispatcher. * * \since v.5.8.1 */ [[nodiscard]] disp_binder_shptr_t so_this_coop_disp_binder() const; /*! * \brief Get an optional name of the agent. * * If agent has the name then a reference to this name will be returned. * Otherwise a small object with a pointer to agent will be returned. * * The result can be printed to std::ostream or converted into a string: * \code * class my_agent final : public so_5::agent_t * { * ... * void so_evt_start() override * { * std::cout << so_agent_name() << ": started" << std::endl; * ... * so_5::send(some_mbox, so_agent_name().to_string()); * } * void so_evt_finished() override * { * std::cout << so_agent_name() << ": stopped" << std::endl; * } * ... * } * \endcode * * \attention * This method returns a lightweight object that just holds a reference * to the agent's name (or a pointer to the agent). This object should * not be stored for the long time, because the references/pointers it * holds may become invalid. If you have to store the agent name for * a long time please convert the returned value into std::string and * store the resulting std::string object. * * \since v.5.8.2 */ [[nodiscard]] agent_identity_t so_agent_name() const noexcept; private: const state_t st_default{ self_ptr(), "" }; //! Current agent state. /*! * If composite states are used then m_current_state_ptr points * to the inner-most state. See so_current_state() for an example. */ const state_t * m_current_state_ptr; /*! * \brief Enumeration of possible agent statuses. * * \since v.5.5.18 */ enum class agent_status_t : char { //! Agent is not defined yet. //! This is an initial agent status. not_defined_yet, //! Agent is defined. defined, //! State switch operation is in progress. state_switch_in_progress, //! Agent was shutdown and all pending demands have to be skipped. shutdown_with_skipping_pending_demands }; /*! * \brief Current agent status. * * \since v.5.5.18 */ agent_status_t m_current_status; //! State listeners controller. impl::state_listener_controller_t m_state_listener_controller; /*! * \brief Type of function for searching event handler. * * \since v.5.5.9 */ using handler_finder_t = const impl::event_handler_data_t *(*)( execution_demand_t & /* demand */, const char * /* context_marker */ ); /*! * \brief Function for searching event handler. * * \note The value is set only once in the constructor and * doesn't changed anymore. * * \since v.5.5.9 */ handler_finder_t m_handler_finder; /*! * \brief All agent's subscriptions. * * \since v.5.4.0 */ impl::subscription_storage_unique_ptr_t m_subscriptions; /*! * \brief Holder of message sinks for that agent. * * If message limits are defined for the agent it will be an actual * storage with separate sinks for every (message_type, message_limit). * * If message limits are not defined then it will be a special storage * with just one message sink (that sink will be used for all subscriptions). * * \since v.5.8.0 */ std::unique_ptr< impl::sinks_storage_t > m_message_sinks; //! SObjectizer Environment for which the agent is belong. environment_t & m_env; /*! * \brief Event queue operation protector. * * Initially m_event_queue is NULL. It is changed to actual value * in so_bind_to_dispatcher() method. And reset to nullptr again * in shutdown_agent(). * * nullptr in m_event_queue means that methods push_event() will throw * away any new demand. * * It is necessary to provide guarantee that m_event_queue will be reset * to nullptr in shutdown_agent() only if there is no working * push_event() methods. To do that default_rw_spinlock_t is used. Method * push_event() acquire it in read-mode and shutdown_agent() acquires it * in write-mode. It means that shutdown_agent() cannot get access to * m_event_queue until there is working push_event(). * * \since v.5.5.8 */ default_rw_spinlock_t m_event_queue_lock; /*! * \brief A pointer to event_queue. * * After binding to the dispatcher is it pointed to the actual * event queue. * * After shutdown it is set to nullptr. * * \attention Access to m_event_queue value must be done only * under acquired m_event_queue_lock. * * \since v.5.5.8 */ event_queue_t * m_event_queue; /*! * \brief A direct mbox for the agent. * * \since v.5.4.0 */ const mbox_t m_direct_mbox; /*! * \brief Working thread id. * * Some actions like managing subscriptions and changing states * are enabled only on working thread id. * * \since v.5.4.0 */ so_5::current_thread_id_t m_working_thread_id; //! Agent is belong to this cooperation. coop_t * m_agent_coop; /*! * \brief Delivery filters for that agents. * * \note Storage is created only when necessary. * * \since v.5.5.5 */ std::unique_ptr< impl::delivery_filter_storage_t > m_delivery_filters; /*! * \brief Priority of the agent. * * \since v.5.5.8 */ const priority_t m_priority; /*! * \brief Binder for this agent. * * Since v.5.7.5 disp_binder for the agent is stored inside the agent. * It guarantees that disp_binder will be deleted after destruction * of the agent (if there is no circular references between the agent * and the disp_binder). * * This value will be set by coop_t when agent is being add to the * coop. * * \note * Access to that field provided by so_5::impl::internal_agent_iface_t. * * \since v.5.7.5 */ disp_binder_shptr_t m_disp_binder; /*! * \brief Optional name for the agent. * * This value can be set in the constructor only and can't be changed * later. * * Empty value means that the name for the agent wasn't specified. * * \since v.5.8.2 */ const name_for_agent_t m_name; /*! * \brief Type for holding information necessary for handling * time limits for agent states. * * \note * Instance of this type can be in one of the following states: * * - empty. There is no data for handling of time limits. This * is the default and initial state; * - defained. In this case m_timeout_mbox has an actual value. * The instance has to be switched to this state explicitly. * * \note * This class in not Copyable, nor Moveable. * * \since v.5.8.5 */ class SO_5_TYPE state_time_limit_handling_data_t { /// Message box to be used for state_t::time_limit_t::msg_timeout /// messages. /// /// \note /// If this field is nullptr then the whole instance of /// state_time_limit_handling_data_t is in empty state. mbox_t m_timeout_mbox; public: /// Default constructor. /// /// Creates an empty instance. state_time_limit_handling_data_t(); ~state_time_limit_handling_data_t(); /// Is the data for handling time limits defined? [[nodiscard]] bool is_defined() const noexcept; /// Define the data for handling time limits. /// /// \attention /// The actual value of \a timeout_mbox is not checked. /// It's just assumed that \a timeout_mbox is not nullptr. void make_defined( /// Message box to be used for msg_timeout signals. mbox_t timeout_mbox ); /// Get the mbox for msg_timeout signals. /// /// \attention /// This method doesn't check the actual value of /// m_timeout_mbox. So it can be called even if /// (is_defined() != true). [[nodiscard]] mbox_t timeout_mbox() const noexcept; }; /*! * \brief Data that is necessary for handling time limits * of agent's states. * * \note * It is not defined by default. The actual value is assigned * when state_t::time_limit is called for the first time. * * \attention * The current implementation never drops this value after * the first call of state_t::time_limit, even if all time limits * are dropped after then. * * \since v.5.8.5 */ state_time_limit_handling_data_t m_state_time_limit_handling_data; /// \brief What to do with pending demands on deregistration. /// /// This value is extracted from agent_tuning_options in the constructor /// and never changes later. /// /// \since v.5.8.5 const demands_handling_on_dereg_t m_demands_handling_on_dereg; //! Destroy all agent's subscriptions. /*! * \note * This method is intended to be used in the destructor and * methods like so_deactivate_agent(). * * \attention * It's noexcept method because there is no way to recover in case * when deletion of subscriptions throws. * * \since v.5.7.3 */ void destroy_all_subscriptions_and_filters() noexcept; //! Make an agent reference. /*! * This is an internal SObjectizer method. It is called when * it is guaranteed that the agent is still necessary and something * has reference to it. */ agent_ref_t create_ref(); /*! * \name Embedding agent into the SObjectizer Run-time. * \{ */ //! Bind agent to the cooperation. /*! * Initializes an internal cooperation pointer. */ void bind_to_coop( //! Cooperation for that agent. coop_t & coop ); //! Agent shutdown deriver. /*! * Method destroys all agent subscriptions. * * \since v.5.2.3 */ void shutdown_agent() noexcept; /*! * \} */ /*! * \name Subscription/unsubscription implementation details. * \{ */ /*! * \brief Helper function that returns a message sink to be used * for subscriptions for specified message type. * * \since v.5.8.0 */ [[nodiscard]] abstract_message_sink_t & detect_sink_for_message_type( const std::type_index & msg_type ); /*! * \brief Remove subscription for the state specified. * * \since v.5.2.3 */ void do_drop_subscription( //! Message's mbox. const mbox_t & mbox, //! Message type. const std::type_index & msg_type, //! State for event. const state_t & target_state ); /*! * \brief Remove subscription for all states. * * \since v.5.2.3 */ void do_drop_subscription_for_all_states( //! Message's mbox. const mbox_t & mbox, //! Message type. const std::type_index & msg_type ); /*! * \brief Check the presence of a subscription. * * \since v.5.5.19.5 */ bool do_check_subscription_presence( //! Message's mbox. const mbox_t & mbox, //! Message type. const std::type_index & msg_type, //! State for the subscription. const state_t & target_state ) const noexcept; /*! * \brief Check the presence of a deadletter handler. * * \since v.5.5.21 */ bool do_check_deadletter_presence( //! Message's mbox. const mbox_t & mbox, //! Message type. const std::type_index & msg_type ) const noexcept; /*! * \} */ /*! * \name Event handling implementation details. * \{ */ //! Push event into the event queue. void push_event( //! Optional message limit. const message_limit::control_block_t * limit, //! ID of mbox for this event. mbox_id_t mbox_id, //! Message type for event. const std::type_index & msg_type, //! Event message. const message_ref_t & message ); /*! * \} */ // NOTE: demand handlers declared as public to allow // access this handlers from unit-tests. public : /*! * \name Demand handlers. * \{ */ /*! * \brief Calls so_evt_start method for agent. * * \since v.5.2.0 */ static void demand_handler_on_start( current_thread_id_t working_thread_id, execution_demand_t & d ); /*! * \brief Ensures that all agents from cooperation are * bound to dispatchers. * * \since v.5.5.8 */ void ensure_binding_finished(); /*! * \note This method is necessary for GCC on Cygwin. * * \since v.5.4.0 */ static demand_handler_pfn_t get_demand_handler_on_start_ptr() noexcept; /*! * \brief Calls so_evt_finish method for agent. * * \since v.5.2.0 */ static void demand_handler_on_finish( current_thread_id_t working_thread_id, execution_demand_t & d ); /*! * \note This method is necessary for GCC on Cygwin. * * \since v.5.4.0 */ static demand_handler_pfn_t get_demand_handler_on_finish_ptr() noexcept; /*! * \brief Calls event handler for message. * * \since v.5.2.0 */ static void demand_handler_on_message( current_thread_id_t working_thread_id, execution_demand_t & d ); /*! * \note This method is necessary for GCC on Cygwin. * * \since v.5.4.0 */ static demand_handler_pfn_t get_demand_handler_on_message_ptr() noexcept; /*! * \brief Handles the enveloped message. * * \since v.5.5.23 */ static void demand_handler_on_enveloped_msg( current_thread_id_t working_thread_id, execution_demand_t & d ); /*! * \since v.5.5.24 */ static demand_handler_pfn_t get_demand_handler_on_enveloped_msg_ptr() noexcept; /*! * \} */ private : /*! * \brief Actual implementation of message handling. * * \note Since v.5.5.17.1 argument \a method is passed as copy. * It prevents deallocation of event_handler_method in the following * case: * \code auto mbox = so_environment().create_mbox(); so_subscribe( mbox ).event< some_signal >( [this, mbox] { so_drop_subscription< some_signal >( mbox ); ... // Some other actions. } ); * \endcode * * \attention * Implementation notes: it's important that \a method is passed * by value. It's because subscription can be deleted during * the work of process_message (due to unsubscription inside the * event handler) and if pass \a method by a reference then * that reference can become invalid. * * \since v.5.4.0 */ static void process_message( current_thread_id_t working_thread_id, execution_demand_t & d, thread_safety_t thread_safety, event_handler_method_t method ); /*! * \brief Actual implementation of enveloped message handling. * * \note * This method has to be called only if event-handler for the mesage * is found. * * \since v.5.5.23 */ static void process_enveloped_msg( current_thread_id_t working_thread_id, execution_demand_t & d, const impl::event_handler_data_t & handler_data ); /*! * \brief Enables operation only if it is performed on agent's * working thread. * * \since v.5.4.0 */ void ensure_operation_is_on_working_thread( const char * operation_name ) const; /*! * \brief Drops all delivery filters. * * \since v.5.5.0 */ void drop_all_delivery_filters() noexcept; /*! * \brief Set a delivery filter. * * \since v.5.5.5 */ void do_set_delivery_filter( const mbox_t & mbox, const std::type_index & msg_type, delivery_filter_unique_ptr_t filter ); /*! * \brief Drop a delivery filter. * * \since v.5.5.5 */ void do_drop_delivery_filter( const mbox_t & mbox, const std::type_index & msg_type ) noexcept; /*! * \brief Handler finder for the case when message delivery * tracing is disabled. * * \since v.5.5.9 */ static const impl::event_handler_data_t * handler_finder_msg_tracing_disabled( execution_demand_t & demand, const char * context_marker ); /*! * \brief Handler finder for the case when message delivery * tracing is enabled. * * \since v.5.5.9 */ static const impl::event_handler_data_t * handler_finder_msg_tracing_enabled( execution_demand_t & demand, const char * context_marker ); /*! * \brief Actual search for event handler with respect * to parent-child relationship between agent states. * * \since v.5.5.15 */ static const impl::event_handler_data_t * find_event_handler_for_current_state( execution_demand_t & demand ); /*! * \brief Search for event handler between deadletter handlers. * * \return nullptr if event handler is not found. * * \since v.5.5.21 */ static const impl::event_handler_data_t * find_deadletter_handler( execution_demand_t & demand ); /*! * \brief Perform actual operations related to state switch. * * It throws if the agent in awaiting_deregistration_state and * \a state_to_be_set isn't awaiting_deregistration_state. * * \note * This method doesn't check the working context. It's assumed * that this check has already been performed by caller. * * \since v.5.7.3 */ void do_change_agent_state( //! New state to be set as the current state. const state_t & state_to_be_set ); /*! * \brief Actual action for switching agent state. * * \since v.5.5.15 */ void do_state_switch( //! New state to be set as the current state. const state_t & state_to_be_set ) noexcept; /*! * \brief Return agent to the default state. * * \note This method is called just before invocation of * so_evt_finish() to return agent to the default state. * This return will initiate invocation of on_exit handlers * for all active states of the agent. * * \attention State switch is not performed if agent is already * in default state or if it waits deregistration after unhandled * exception. * * \since v.5.5.15 */ void return_to_default_state_if_possible() noexcept; /*! * \brief Is agent already deactivated. * * Deactivated agent is in awaiting_deregistration_state. * This method checks that the current state of the agent * is awaiting_deregistration_state. * * \attention * This method isn't thread safe and should be used with care. * A caller should guarantee that it's called from the right * working thread. * * \since v.5.7.3 */ bool is_agent_deactivated() const noexcept; /*! * \brief Initialize data for handling time limit of * agent's states. * * Initializes data for handling time limit if it is not * defined yet. * * \since v.5.8.5 */ void define_state_time_limit_handling_data_if_needed(); /*! * \brief Special event handler to process state time limits. * * This event handler is used as deadletter handler for * state_t::time_limit_t::msg_timeout. * * \note * Subscription for this deadletter handler will be created on * the first call to state_t::time_limit. If no one calls * state_t::time_limit then will event handler won't be used * at all. * * \since v.5.8.5 */ void evt_state_time_limit( mhood_t< so_5::details::msg_state_timeout > ); }; /*! * \brief Helper function template for the creation of smart pointer * to an agent. * * This function can be useful if a pointer to an agent should be passed * somewhere with the guarantee that this pointer will remain valid even * if the agent will be deregistered. * * This could be necessary, for example, if a pointer to an agent is * passed to some callback (like it is done in Asio): * \code * void my_agent::on_some_event(mhood_t cmd) { * connection_.async_read_some(input_buffer_, * [self = so_5::make_agent_ref(this)]( * const asio::error_code & ec, * std::size_t bytes_transferred ) * { * if(!ec) * self->handle_incoming_data(bytes_transferred); * } * ); * } * \endcode * * \since v.5.7.1 */ template< typename Derived > [[nodiscard]] intrusive_ptr_t< Derived > make_agent_ref( Derived * agent ) { static_assert( std::is_base_of_v< agent_t, Derived >, "type should be derived from so_5::agent_t" ); return { agent }; } template< typename Lambda > void agent_t::so_set_delivery_filter( const mbox_t & mbox, Lambda && lambda ) { using namespace so_5::details::lambda_traits; using lambda_type = std::remove_reference_t; using argument_type = typename argument_type_if_lambda< lambda_type >::type; ensure_not_signal< argument_type >(); do_set_delivery_filter( mbox, message_payload_type< argument_type >::subscription_type_index(), delivery_filter_unique_ptr_t{ new low_level_api::lambda_as_filter_t< lambda_type, argument_type >( std::move( lambda ) ) } ); } template< typename Lambda > void agent_t::so_set_delivery_filter_for_mutable_msg( const mbox_t & mbox, Lambda && lambda ) { using namespace so_5::details::lambda_traits; using lambda_type = std::remove_reference_t; using argument_type = typename argument_type_if_lambda< lambda_type >::type; ensure_not_signal< argument_type >(); using subscription_type = so_5::mutable_msg< argument_type >; do_set_delivery_filter( mbox, message_payload_type< subscription_type >::subscription_type_index(), delivery_filter_unique_ptr_t{ new low_level_api::lambda_as_filter_t< lambda_type, argument_type >( std::move( lambda ) ) } ); } // // subscription_bind_t implementation // inline subscription_bind_t::subscription_bind_t( agent_t & agent, const mbox_t & mbox_ref ) : m_agent( &agent ) , m_mbox_ref( mbox_ref ) { } inline subscription_bind_t & subscription_bind_t::in( const state_t & state ) { if( !state.is_target( m_agent ) ) { SO_5_THROW_EXCEPTION( rc_agent_is_not_the_state_owner, "agent doesn't own the state" ); } m_states.push_back( &state ); return *this; } template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>::value, subscription_bind_t & >::type subscription_bind_t::event( Method_Pointer pfn, thread_safety_t thread_safety ) { using namespace details::event_subscription_helpers; const auto ev = preprocess_agent_event_handler( m_mbox_ref, *m_agent, pfn ); create_subscription_for_states( ev.m_msg_type, ev.m_handler, thread_safety, event_handler_kind_t::final_handler ); return *this; } template typename std::enable_if< details::lambda_traits::is_lambda::value, subscription_bind_t & >::type subscription_bind_t::event( Lambda && lambda, thread_safety_t thread_safety ) { using namespace details::event_subscription_helpers; const auto ev = preprocess_agent_event_handler( m_mbox_ref, *m_agent, std::forward(lambda) ); create_subscription_for_states( ev.m_msg_type, ev.m_handler, thread_safety, event_handler_kind_t::final_handler ); return *this; } template< typename Msg > subscription_bind_t & subscription_bind_t::transfer_to_state( const state_t & target_state ) { /* * Note. Since v.5.5.22.1 there is a new implementation of transfer_to_state. * New implementation protects from loops in transfer_to_state calls. * For example in the following cases: * * \code class a_simple_case_t final : public so_5::agent_t { state_t st_base{ this, "base" }; state_t st_disconnected{ initial_substate_of{st_base}, "disconnected" }; state_t st_connected{ substate_of{st_base}, "connected" }; struct message {}; public : a_simple_case_t(context_t ctx) : so_5::agent_t{ctx} { this >>= st_base; st_base.transfer_to_state(st_disconnected); } virtual void so_evt_start() override { so_5::send(*this); } }; class a_two_state_loop_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; struct message {}; public : a_two_state_loop_t(context_t ctx) : so_5::agent_t{ctx} { this >>= st_one; st_one.transfer_to_state(st_two); st_two.transfer_to_state(st_one); } virtual void so_evt_start() override { so_5::send(*this); } }; * \endcode * * For such protection an additional objects with the current state * of transfer_to_state operation is necessary. There will be a boolean * flag in that state. When transfer_to_state will be started this * flag will should be 'false'. But if it is already 'true' then there is * a loop in transfer_to_state calls. */ // This is the state of transfer_to_state operation. struct transfer_op_state_t { agent_t * m_agent; mbox_id_t m_mbox_id; const state_t & m_target_state; bool m_in_progress; transfer_op_state_t( agent_t * agent, mbox_id_t mbox_id, outliving_reference_t tgt_state) : m_agent( agent ) , m_mbox_id( mbox_id ) , m_target_state( tgt_state.get() ) , m_in_progress( false ) {} }; //NOTE: shared_ptr is used because capture of unique_ptr //makes std::function non-copyable, but we need to copy //resulting 'method' object. // auto op_state = std::make_shared< transfer_op_state_t >( m_agent, m_mbox_ref->id(), outliving_const(target_state) ); auto method = [op_state]( message_ref_t & msg ) { // The current transfer_to_state operation should be inactive. if( op_state->m_in_progress ) SO_5_THROW_EXCEPTION( rc_transfer_to_state_loop, "transfer_to_state loop detected. target_state: " + op_state->m_target_state.query_name() + ", current_state: " + op_state->m_agent->so_current_state().query_name() ); // Activate transfer_to_state operation and make sure that it // will be deactivated on return automatically. op_state->m_in_progress = true; auto in_progress_reset = details::at_scope_exit( [&op_state] { op_state->m_in_progress = false; } ); // // The main logic of transfer_to_state operation. // op_state->m_agent->so_change_state( op_state->m_target_state ); execution_demand_t demand{ op_state->m_agent, nullptr, // Message limit is not actual here. op_state->m_mbox_id, typeid( Msg ), msg, // We have very simple choice here: message is an enveloped // message or just classical message/signal. // So we should select an appropriate demand handler. message_kind_t::enveloped_msg == message_kind( msg ) ? agent_t::get_demand_handler_on_enveloped_msg_ptr() : agent_t::get_demand_handler_on_message_ptr() }; demand.call_handler( query_current_thread_id() ); }; create_subscription_for_states( typeid( Msg ), method, thread_safety_t::unsafe, event_handler_kind_t::intermediate_handler ); return *this; } template< typename Msg > subscription_bind_t & subscription_bind_t::suppress() { // A method with nothing inside. auto method = []( message_ref_t & ) {}; create_subscription_for_states( typeid( Msg ), method, thread_safety_t::safe, // Suppression of a message is a kind of ignoring of the message. // In the case of enveloped message intermediate_handler receives // the whole envelope (not the payload) and the whole envelope // should be ignored. We can't specify final_handler here because // in that case the payload of an enveloped message will be // extracted from the envelope and envelope will be informed about // the handling of message. But message won't be handled, it will // be ignored. event_handler_kind_t::intermediate_handler ); return *this; } template< typename Msg > subscription_bind_t & subscription_bind_t::just_switch_to( const state_t & target_state ) { agent_t * agent_ptr = m_agent; auto method = [agent_ptr, &target_state]( message_ref_t & ) { agent_ptr->so_change_state( target_state ); }; create_subscription_for_states( typeid( Msg ), method, thread_safety_t::unsafe, // Switching to some state is a kind of message processing. // So if there is an enveloped message then the envelope will be // informed about the processing of the payload. event_handler_kind_t::final_handler ); return *this; } inline void subscription_bind_t::create_subscription_for_states( const std::type_index & msg_type, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) const { if( m_states.empty() ) // Agent should be subscribed only in default state. m_agent->so_create_event_subscription( m_mbox_ref, msg_type, m_agent->so_default_state(), method, thread_safety, handler_kind ); else for( auto s : m_states ) m_agent->so_create_event_subscription( m_mbox_ref, msg_type, *s, method, thread_safety, handler_kind ); } inline void subscription_bind_t::ensure_handler_can_be_used_with_mbox( const so_5::details::msg_type_and_handler_pair_t & handler ) const { ::so_5::details::event_subscription_helpers::ensure_handler_can_be_used_with_mbox( handler, m_mbox_ref ); } /* * Implementation of template methods of state_t class. */ inline bool state_t::is_active() const noexcept { return m_target_agent->so_is_active_state( *this ); } template< typename... Args > const state_t & state_t::event( Args&&... args ) const { return this->subscribe_message_handler( m_target_agent->so_direct_mbox(), std::forward< Args >(args)... ); } template< typename... Args > const state_t & state_t::event( mbox_t from, Args&&... args ) const { return this->subscribe_message_handler( from, std::forward< Args >(args)... ); } template< typename Msg > bool state_t::has_subscription( const mbox_t & from ) const { return m_target_agent->so_has_subscription< Msg >( from, *this ); } template< typename Method_Pointer > bool state_t::has_subscription( const mbox_t & from, Method_Pointer && pfn ) const { return m_target_agent->so_has_subscription( from, *this, std::forward(pfn) ); } template< typename Msg > void state_t::drop_subscription( const mbox_t & from ) const { m_target_agent->so_drop_subscription< Msg >( from, *this ); } template< typename Method_Pointer > void state_t::drop_subscription( const mbox_t & from, Method_Pointer && pfn ) const { m_target_agent->so_drop_subscription( from, *this, std::forward(pfn) ); } template< typename Msg > const state_t & state_t::transfer_to_state( mbox_t from, const state_t & target_state ) const { m_target_agent->so_subscribe( from ) .in( *this ) .transfer_to_state< Msg >( target_state ); return *this; } template< typename Msg > const state_t & state_t::transfer_to_state( const state_t & target_state ) const { return this->transfer_to_state< Msg >( m_target_agent->so_direct_mbox(), target_state ); } template< typename Msg > const state_t & state_t::just_switch_to( mbox_t from, const state_t & target_state ) const { m_target_agent->so_subscribe( from ) .in( *this ) .just_switch_to< Msg >( target_state ); return *this; } template< typename Msg > const state_t & state_t::just_switch_to( const state_t & target_state ) const { return this->just_switch_to< Msg >( m_target_agent->so_direct_mbox(), target_state ); } template< typename Msg > const state_t & state_t::suppress() const { return this->suppress< Msg >( m_target_agent->so_direct_mbox() ); } template< typename Msg > const state_t & state_t::suppress( mbox_t from ) const { m_target_agent->so_subscribe( from ) .in( *this ) .suppress< Msg >(); return *this; } template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::nullary, Method_Pointer>::value, state_t & >::type state_t::on_enter( Method_Pointer pfn ) { using namespace details::event_subscription_helpers; using pfn_traits = details::is_agent_method_pointer< details::method_arity::nullary, Method_Pointer>; // Agent must have right type. auto cast_result = get_actual_agent_pointer< typename pfn_traits::agent_type >( *m_target_agent ); return this->on_enter( [cast_result, pfn]() { (cast_result->*pfn)(); } ); } template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::nullary, Method_Pointer>::value, state_t & >::type state_t::on_exit( Method_Pointer pfn ) { using namespace details::event_subscription_helpers; using pfn_traits = details::is_agent_method_pointer< details::method_arity::nullary, Method_Pointer>; // Agent must have right type. auto cast_result = get_actual_agent_pointer< typename pfn_traits::agent_type >( *m_target_agent ); return this->on_exit( [cast_result, pfn]() { (cast_result->*pfn)(); } ); } template< typename... Args > const state_t & state_t::subscribe_message_handler( const mbox_t & from, Args&&... args ) const { m_target_agent->so_subscribe( from ).in( *this ) .event( std::forward< Args >(args)... ); return *this; } /*! * \brief A shortcat for switching the agent state. * * \par Usage example. \code class my_agent : public so_5::agent_t { const so_5::state_t st_normal = so_make_state(); const so_5::state_t st_error = so_make_state(); ... public : virtual void so_define_agent() override { this >>= st_normal; st_normal.handle( [=]( const msg_failure & evt ) { this >>= st_error; ... }); ... }; ... }; \endcode * * \since v.5.5.1 */ inline void operator>>=( agent_t * agent, const state_t & new_state ) { agent->so_change_state( new_state ); } } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/agent_context.hpp000066400000000000000000000100721523065210100210460ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A context for agent construction and tuning. * * \since v.5.5.4 */ #if !defined( SO_5_AGENT_CONTEXT_HPP ) #define SO_5_AGENT_CONTEXT_HPP #include #include namespace so_5 { // // agent_context_t // /*! * \brief A context for agent construction and tuning. * * \since v.5.5.4 */ class agent_context_t { public : //! Constructor for the case when only environment available. agent_context_t( //! SObjectizer Environment an agent will work in. environment_t & env ) : m_env( &env ) {} //! Constructor for the case when environment and already created //! tuning option object are avaliable. agent_context_t( //! SObjectizer Environment an agent will work in. environment_t & env, //! Options to be used for agent tuning. agent_tuning_options_t options ) : m_env( &env ) , m_options( std::move( options ) ) {} //! Swap operation. friend inline void swap( so_5::agent_context_t & a, so_5::agent_context_t & b ) noexcept { using std::swap; swap( a.m_env, b.m_env ); swap( a.m_options, b.m_options ); } //! Access to SObjectizer Environment. environment_t & env() const { return *m_env; } //! Access to SObjectizer Environment. /*! * \since v.5.5.9 */ environment_t & environment() const { return *m_env; } //! Read-Write access to agent options. agent_tuning_options_t & options() { return m_options; } //! Read-only access to agent options. const agent_tuning_options_t & options() const { return m_options; } private : //! SObjectizer Environment to work in. environment_t * m_env; //! Options for agent tuning. agent_tuning_options_t m_options; }; //FIXME: should the following operator+ be marked as [[nodiscard]]? /*! * \brief A plus operator for creating agent_context object from * a reference to Environment and single agent tuning option. * * \since v.5.5.4 */ template< typename Option > agent_context_t operator+( environment_t & env, Option arg ) { agent_context_t result{ env }; return std::move(result) + std::move(arg); } /*! * \name Operators for tuning agent_context. * \{ */ inline agent_context_t operator+( agent_context_t ctx, subscription_storage_factory_t factory ) { ctx.options().subscription_storage_factory( std::move( factory ) ); return ctx; } template< class M > agent_context_t operator+( agent_context_t ctx, message_limit::drop_indicator_t< M > limit ) { ctx.options().message_limits( limit ); return ctx; } template< class M > agent_context_t operator+( agent_context_t ctx, message_limit::abort_app_indicator_t< M > limit ) { ctx.options().message_limits( limit ); return ctx; } template< class M, class L > agent_context_t operator+( agent_context_t ctx, message_limit::log_then_abort_app_indicator_t< M, L > limit ) { ctx.options().message_limits( limit ); return ctx; } template< class M, class L > agent_context_t operator+( agent_context_t ctx, message_limit::redirect_indicator_t< M, L > limit ) { ctx.options().message_limits( std::move( limit ) ); return ctx; } template< class M > agent_context_t operator+( agent_context_t ctx, message_limit::transform_indicator_t< M > limit ) { ctx.options().message_limits( std::move( limit ) ); return ctx; } inline agent_context_t operator+( agent_context_t ctx, so_5::priority_t agent_priority ) { ctx.options().priority( agent_priority ); return ctx; } inline agent_context_t operator+( agent_context_t ctx, custom_direct_mbox_factory_t factory ) { ctx.options().custom_direct_mbox_factory( std::move(factory) ); return ctx; } inline agent_context_t operator+( agent_context_t ctx, name_for_agent_t agent_name ) { ctx.options().agent_name( std::move(agent_name) ); return ctx; } inline agent_context_t operator+( agent_context_t ctx, demands_handling_on_dereg_t handling_mode ) { ctx.options().demands_handling_on_dereg( handling_mode ); return ctx; } /*! * \} */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/agent_coop_notifications.hpp000066400000000000000000000076231523065210100232630ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \since v.5.2.3 \file \brief Agent cooperation notifications definition. */ #if !defined( SO_5_AGENT_COOP_NOTIFICATIONS_HPP ) #define SO_5_AGENT_COOP_NOTIFICATIONS_HPP #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { // // msg_coop_registered // /*! * \since * v.5.2.3 * * \brief Message about cooperation registration completion. */ struct SO_5_TYPE msg_coop_registered : public message_t { // Note: clang-3.9 requires this on Windows platform. msg_coop_registered( const msg_coop_registered & ) = delete; msg_coop_registered( msg_coop_registered && ) = delete; msg_coop_registered & operator=( const msg_coop_registered & ) = delete; msg_coop_registered & operator=( msg_coop_registered && ) = delete; msg_coop_registered( coop_handle_t coop ) noexcept : m_coop( std::move(coop) ) {} const coop_handle_t m_coop; }; // // msg_coop_deregistered // /*! * \since * v.5.2.3 * * \brief Message about cooperation deregistration completion. */ struct SO_5_TYPE msg_coop_deregistered : public message_t { // Note: clang-3.9 requires this on Windows platform. msg_coop_deregistered( const msg_coop_deregistered & ) = delete; msg_coop_deregistered( msg_coop_deregistered && ) = delete; msg_coop_deregistered & operator=( const msg_coop_deregistered & ) = delete; msg_coop_deregistered & operator=( msg_coop_deregistered && ) = delete; msg_coop_deregistered( coop_handle_t coop, coop_dereg_reason_t reason ) noexcept : m_coop( std::move(coop) ) , m_reason( reason ) {} const coop_handle_t m_coop; const coop_dereg_reason_t m_reason; }; // // make_coop_reg_notificator // /*! * \since * v.5.2.3 * * \brief Create notificator about cooperation registration completion. * * \attention * Since v.5.6.0 reg_notificators should be noexcept functions/functors. * But this notificator uses so_5::send() function inside. This function * can throw (for example std::bad_alloc can be raised if there is no * enough memory available). It means that if so_5::send() throws inside * this notificator the whole application will be aborted via * std::terminate(). */ inline auto make_coop_reg_notificator( //! A mbox to which msg_coop_registered will be sent. mbox_t target ) noexcept { class actual_notificator_t final { mbox_t m_target; public : actual_notificator_t( mbox_t target ) noexcept : m_target{ std::move(target) } {} void operator()( environment_t & /*env*/, const coop_handle_t & handle ) const noexcept { so_5::send< msg_coop_registered >( m_target, handle ); } }; return actual_notificator_t{ std::move(target) }; } // // make_coop_dereg_notificator // /*! * \since * v.5.2.3 * * \brief Create notificator about cooperation deregistration completion. * * \attention * Since v.5.6.0 dereg_notificators should be noexcept functions/functors. * But this notificator uses so_5::send() function inside. This function * can throw (for example std::bad_alloc can be raised if there is no * enough memory available). It means that if so_5::send() throws inside * this notificator the whole application will be aborted via * std::terminate(). */ inline auto make_coop_dereg_notificator( //! A mbox to which msg_coop_deregistered will be sent. mbox_t target ) noexcept { class actual_notificator_t final { mbox_t m_target; public : actual_notificator_t( mbox_t target ) noexcept : m_target{ std::move(target) } {} void operator()( environment_t & /*env*/, const coop_handle_t & handle, const coop_dereg_reason_t & reason ) const noexcept { so_5::send< msg_coop_deregistered >( m_target, handle, reason ); } }; return actual_notificator_t{ std::move(target) }; } } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/agent_identity.hpp000066400000000000000000000112731523065210100212170ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Types related to agents identity (name). */ #if !defined( SO_5_AGENT_IDENTITY_HPP ) #define SO_5_AGENT_IDENTITY_HPP #include #include #include #include #include #include #include namespace so_5 { /*! * \brief Helper class for holding agent's identity (name or pointer). * * Since v.5.8.2 agents may have an optional name. If a name is set for agent * a reference to this name can be returned by agent_t::so_agent_name(). But * if a name is not specified then only a pointer to agent (agent_t's `this` value) * can be used as agent's identity. * * This type is intended to be used as a thin wrapper that covers cases described * above. It holds a reference to agent's name if the name is specified or * just pointer to an agent if there is no name. This type can be seen as * a more convenient version of: * \code * std::variant; * \endcode * * \attention * Objects of this type hold references, not values. So it's not safe to store * them for a long time. Please consider to store `std::string` objects * returned by to_string() methods instead of agent_identity_t objects. * * \since v.5.8.2 */ class agent_identity_t { friend class agent_t; //! Type for case when agent has no user-provided name. struct pointer_only_t { //! Prefix to be used for string representation. static constexpr std::string_view c_string_prefix{ "" }; //! Capacity of c-string for holding string representation. //! //! Size of hex representation of a pointer + "".length() + 1. static constexpr std::size_t c_string_size = (sizeof(void*) * 2u) + c_string_prefix.size() + c_string_suffix.size() + 1u /* terminating 0-symbol */; //! Value. const agent_t * m_pointer_value; // NOTE: this method is implemented in agent.cpp source file. //! Make a c-string with text representation of a value. [[nodiscard]] SO_5_FUNC std::array make_c_string() const noexcept; }; //! Type for case when agent has user-provided name. struct actual_name_t { //! Value. std::string_view m_name; }; /*! * Helper type to be used with std::visit when agent_identity has to * be converted into a string. */ struct to_string_visitor_t { [[nodiscard]] std::string operator()( const pointer_only_t & v ) const { const auto c_str = v.make_c_string(); return std::string( c_str.data(), pointer_only_t::c_string_size - 1u ); } [[nodiscard]] std::string operator()( const actual_name_t & v ) const { return std::string( v.m_name ); } }; /*! * Helper type to be used with std::visit when agent_identity has to * be printed into a std::ostream. */ struct to_ostream_visitor_t { std::ostream & m_to; to_ostream_visitor_t( std::ostream & to ) : m_to{ to } {} void operator()( const pointer_only_t & v ) const { const auto c_str = v.make_c_string(); m_to << c_str.data(); } void operator()( const actual_name_t & v ) const { m_to << v.m_name; } }; //! Type of identity holder. using value_t = std::variant< pointer_only_t, actual_name_t >; //! Agent's identity. value_t m_value; //! Initializing constructor for case when agent has no user specified name. agent_identity_t( const agent_t * pointer ) noexcept : m_value{ pointer_only_t{ pointer } } {} //! Initializing constructor for case when agent has a user specified name. agent_identity_t( std::string_view name ) noexcept : m_value{ actual_name_t{ name } } {} public: //! Does agent have an actual name? [[nodiscard]] bool has_actual_name() const noexcept { return std::holds_alternative< actual_name_t >( m_value ); } //! Attempt to get an agent name. /*! * \return empty std::string_view if agent has no an actual name. */ [[nodiscard]] std::string_view actual_name() const noexcept { if( const auto * an = std::get_if< actual_name_t >( &m_value ) ) return an->m_name; else return std::string_view{}; } //! Transform identity into a string. [[nodiscard]] std::string to_string() const { return std::visit( to_string_visitor_t{}, m_value ); } friend std::ostream & operator<<( std::ostream & to, const agent_identity_t & what ) { std::visit( to_ostream_visitor_t{ to }, what.m_value ); return to; } }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/agent_ref_fwd.hpp000066400000000000000000000006721523065210100210030ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \since v.5.2.0 \brief A forward declaration of agent_ref_t. */ #if !defined( SO_5_AGENT_REF_FWD_HPP ) #define SO_5_AGENT_REF_FWD_HPP #include #include namespace so_5 { // // agent_ref_t // //! A smart reference to an agent. /*! * \note Defined as typedef since v.5.2.0 */ using agent_ref_t = intrusive_ptr_t< agent_t >; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/agent_state_listener.hpp000066400000000000000000000070711523065210100224140ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Agent state listener definition. */ #if !defined( SO_5_AGENT_STATE_LISTENER_HPP ) #define SO_5_AGENT_STATE_LISTENER_HPP #include #include #include #include namespace so_5 { // // agent_state_listener_t // //! Interface of the agent state listener. /*! * This interface is intended for cases when the agent state switches * should be observed or monitored. A "state listener" is an object * which can be attached to an agent and the agent will inform * the listener about state changes. Such state listeners should implement * this interface. * * A unlimited count of state listeners may be attached to the agent. * Agent will call changed() method inside of * so_5::agent_t::so_change_state() for each of them. * * \attention It is important not to change the agent state inside of changed(). * Because this could lead to the infinite recursion. */ class SO_5_TYPE agent_state_listener_t { agent_state_listener_t( const agent_state_listener_t & ) = delete; agent_state_listener_t & operator=( const agent_state_listener_t & ) = delete; public: agent_state_listener_t() = default; virtual ~agent_state_listener_t() noexcept = default; //! Hook method for state changes. /*! * Agent calls this method after successful change of the state. * * \note * Since v.5.6.0 this method is marked as noexcept. */ virtual void changed( //! Agent which state has been changed. agent_t & agent, //! New agent state. const state_t & state ) noexcept = 0; }; //! Typedef for the agent_state_listener autopointer. using agent_state_listener_unique_ptr_t = std::unique_ptr< agent_state_listener_t >; namespace impl { // // internal_state_listener_unique_ptr_t // //! Name of unique-ptr to be used inside SObjectizer. /*! * Internal unique-ptr to agent_state_listener_t should have a custom * deleter. It allows to use the same unique_ptr for destroyable and * non-destroyable listeners. * * \since * v.5.6.0 */ using internal_state_listener_unique_ptr_t = std::unique_ptr< agent_state_listener_t, void (*)(agent_state_listener_t *) >; // // state_listener_controller_t // class state_listener_controller_t final { using container_t = std::vector< internal_state_listener_unique_ptr_t >; //! All listeners. container_t m_listeners; static void actual_deleter( agent_state_listener_t * listener ) { delete listener; } static void noop_deleter( agent_state_listener_t * ) {} public : static auto wrap_nondestroyable( agent_state_listener_t & listener ) { return internal_state_listener_unique_ptr_t{ &listener, &state_listener_controller_t::noop_deleter }; } static auto wrap_destroyable( agent_state_listener_unique_ptr_t listener ) { return internal_state_listener_unique_ptr_t{ listener.release(), &state_listener_controller_t::actual_deleter }; } //! Add a new listener. /*! * Actual listener should be wrapped into * internal_state_listener_unique_ptr_t by using * wrap_nondestroyable() or wrap_destroyable(). */ void add( internal_state_listener_unique_ptr_t listener ) { m_listeners.push_back( std::move(listener) ); } //! Handle state change. void changed( //! Agent which state has been changed. agent_t & agent, //! The current agent state. const state_t & state ) noexcept { for( auto & l : m_listeners ) l->changed( agent, state ); } }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/agent_tuning_options.hpp000066400000000000000000000257061523065210100224530ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.3 * * \file * \brief A collector for agent tuning options. */ #if !defined( SO_5_AGENT_TUNING_OPTIONS_HPP ) #define SO_5_AGENT_TUNING_OPTIONS_HPP #include #include #include #include namespace so_5 { /*! * \brief Wrapper around a pointer to partially constructed agent. * * This type is used as indicator that a pointer to agent can be * transferred or stored somewhere, but shouldn't be used because * the agent isn't fully constructed yet. * * \since v.5.7.4 */ class partially_constructed_agent_ptr_t { agent_t * m_ptr; public: explicit partially_constructed_agent_ptr_t( agent_t * ptr ) : m_ptr{ ptr } {} [[nodiscard]] agent_t * ptr() const noexcept { return m_ptr; } }; /*! * \brief Type of functor to be used as a factory for custom direct mbox. * * \attention * The factory should return a valid mbox or should throw an exception. * The behavior isn't defined if the factory return empty so_5::mbox_t * object (the application may crash in the better case). * * \since v.5.7.4 */ using custom_direct_mbox_factory_t = std::function< so_5::mbox_t(partially_constructed_agent_ptr_t, so_5::mbox_t) >; #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif // NOTE: the implementation of name_for_agent_t is in agent.cpp /*! * \brief Type for holding agent name. * * Unlike std::string it has no small-string optimization, but its size is just * a (sizeof(char *)+sizeof(unsigned int)), so it is smaller than std::string in * cases when agent's name is empty. * * \note * This type holds a copy of name (without the terminal 0-symbol). If name is * not set (or if the content of the object is moved out) then a nullptr is * held and the object is treated as empty or null-object. * * \attention * The size of a name has to be fit into `unsigned int`, not into `std::size_t`, * so name of agent can't exceed 4GiB if `unsigned int` is 32-bit. * * \since v.5.8.2 */ class SO_5_TYPE name_for_agent_t { static_assert( sizeof(unsigned int) <= sizeof(std::size_t), "it's expected that std::size_t is bigger or the same as unsigned int" ); //! Name storage. /*! * \note * It doesn't have a terminating 0-symbol. */ std::unique_ptr< char[] > m_value; //! Name length. unsigned int m_length; public: //! Default constructor makes an null value. name_for_agent_t(); //! Initializing constructor. /*! * \attention * It throws is \a value has zero length. * * \attention * It throws is the length of \a value can't fit into unsigned int. */ explicit name_for_agent_t( std::string_view value ); // Copy operation. name_for_agent_t( const name_for_agent_t & ); name_for_agent_t & operator=( const name_for_agent_t & ); // Move operation. name_for_agent_t( name_for_agent_t && other ) noexcept; name_for_agent_t & operator=( name_for_agent_t && other ) noexcept; ~name_for_agent_t(); friend SO_5_FUNC void swap( name_for_agent_t & a, name_for_agent_t & b ) noexcept; //! Get the value as a string_view. /*! * \note * If the object is null, then it produces an empty string_view. */ [[nodiscard]] std::string_view as_string_view() const; //! Does this object have a value? [[nodiscard]] bool has_value() const noexcept; //! Does this object have a value? explicit operator bool() const noexcept { return this->has_value(); } }; /*! * \brief Helper type with method to be mixed into agent class. * * \since v.5.8.2 */ struct name_for_agent_methods_mixin_t { /*! * \brief A helper factory for making %name_for_agent_t instance. * * Usage example: * \code * class my_agent final : public so_5::agent_t * { * public: * my_agent(context_t ctx, std::string_view name) * : so_5::agent_t{ ctx + name_for_agent(name) } * {} * ... * } * \endcode * * \since v.5.8.2 */ [[nodiscard]] static name_for_agent_t name_for_agent( std::string_view name ) { return name_for_agent_t{ name }; } }; #if defined( SO_5_MSVC ) #pragma warning(pop) #endif // // demands_handling_on_dereg_t // /// \brief How pending demands should be handled on deregistration. /// /// \since v.5.8.5 enum class demands_handling_on_dereg_t { /// Pending demands have to be handled as usual. normal, /// Pending demands have to be skipped. skip }; /// \brief Marker that tells that pending demands have to be skipped /// on deregistration. /// /// Usage example: /// \code /// class my_agent : public so_5::agent_t /// { /// public: /// my_agent(context_t ctx) : so_5::agent_t{ctx + so_5::skip_demands_on_dereg} {} /// ... /// }; /// \endcode /// /// \since v.5.8.5 inline constexpr demands_handling_on_dereg_t skip_demands_on_dereg = demands_handling_on_dereg_t::skip; // // agent_tuning_options_t // /*! * \brief A collector for agent tuning options. * * \attention * This class isn't thread-safe. * * \since v.5.5.3 */ class agent_tuning_options_t { public : agent_tuning_options_t() = default; friend inline void swap( so_5::agent_tuning_options_t & a, so_5::agent_tuning_options_t & b ) noexcept { using std::swap; swap( a.m_subscription_storage_factory, b.m_subscription_storage_factory ); swap( a.m_message_limits, b.m_message_limits ); swap( a.m_priority, b.m_priority ); swap( a.m_custom_direct_mbox_factory, b.m_custom_direct_mbox_factory ); swap( a.m_is_user_provided_subscription_storage_factory, b.m_is_user_provided_subscription_storage_factory ); swap( a.m_agent_name, b.m_agent_name ); swap( a.m_demands_handling_on_dereg, b.m_demands_handling_on_dereg ); } //! Set factory for subscription storage creation. agent_tuning_options_t & subscription_storage_factory( subscription_storage_factory_t factory ) noexcept( noexcept(std::declval() = std::move(factory)) ) { m_subscription_storage_factory = std::move( factory ); m_is_user_provided_subscription_storage_factory = true; return *this; } //FIXME(v.5.9.0): this method has to be changed to this one: // // std::optional // query_subscription_storage_factory() const. // [[nodiscard]] const subscription_storage_factory_t & query_subscription_storage_factory() const noexcept { return m_subscription_storage_factory; } /*! * \brief Does a user provide a specific subscription_storage_factory? * * \retval false If subscription_storage_factory wasn't specified by a user. * \retval true If the subscription_storage_factory was specified by a user. * * \since v.5.8.2 */ [[nodiscard]] bool is_user_provided_subscription_storage_factory() const noexcept { return m_is_user_provided_subscription_storage_factory; } //FIXME(v.5.9.0): should this method be removed? //! Default subscription storage factory. static subscription_storage_factory_t default_subscription_storage_factory() { return so_5::default_subscription_storage_factory(); } [[nodiscard]] message_limit::description_container_t giveout_message_limits() { return std::move( m_message_limits ); } template< typename... Args > agent_tuning_options_t & message_limits( Args &&... args ) { message_limit::accept_indicators( m_message_limits, std::forward< Args >( args )... ); return *this; } //! Set priority for agent. /*! * \since v.5.5.8 */ agent_tuning_options_t & priority( so_5::priority_t v ) { m_priority = v; return *this; } //! Get priority value. [[nodiscard]] so_5::priority_t query_priority() const noexcept { return m_priority; } /*! * \brief Set custom direct mbox factory. * * \since v.5.7.4 */ agent_tuning_options_t & custom_direct_mbox_factory( custom_direct_mbox_factory_t factory ) { m_custom_direct_mbox_factory = factory; return *this; } /*! * \brief Get a reference to custom direct mbox factory. * * \note * If the factory isn't set then a reference to empty * std::function object is returned. * * \since v.5.7.4 */ [[nodiscard]] const custom_direct_mbox_factory_t & query_custom_direct_mbox_factory() const noexcept { return m_custom_direct_mbox_factory; } /*! * \brief Set a name for agent. * * \note * The name should have an actual name, otherwise an exception * will be thrown. * * \since v.5.8.2 */ agent_tuning_options_t & agent_name( name_for_agent_t name ) { if( !name.has_value() ) SO_5_THROW_EXCEPTION( rc_empty_agent_name, "empty name can't be used for an agent" ); m_agent_name = std::move(name); return *this; } /*! * \brief Does a name specified for an agent? * * \since v.5.8.2 */ [[nodiscard]] bool has_agent_name() const noexcept { return m_agent_name.has_value(); } /*! * \brief Gives away the name for an agent. * * If the name wasn't set an empty value is returned. * * \since v.5.8.2 */ [[nodiscard]] name_for_agent_t giveout_agent_name() noexcept { return name_for_agent_t{ std::move(m_agent_name) }; } /// \brief Set mode of handling pending demands on deregistration. /// /// \since v.5.8.5 agent_tuning_options_t & demands_handling_on_dereg( demands_handling_on_dereg_t handling_mode ) { m_demands_handling_on_dereg = handling_mode; return *this; } /// \breif Get mode of handling pending demands on deregistration. /// /// \since v.5.8.5 [[nodiscard]] demands_handling_on_dereg_t demands_handling_on_dereg() const noexcept { return m_demands_handling_on_dereg; } private : //FIXME(v.5.9.0): this member has to be changed to: // // std::optional m_subscription_storage_factory; // subscription_storage_factory_t m_subscription_storage_factory = default_subscription_storage_factory(); message_limit::description_container_t m_message_limits; //! Priority for agent. /*! * \since v.5.5.8 */ so_5::priority_t m_priority = so_5::prio::default_priority; /*! * \brief Optional factory for custom direct mboxes. * * It can be an empty std::function object. * * \since v.5.7.4 */ custom_direct_mbox_factory_t m_custom_direct_mbox_factory; //FIXME(v.5.9.0): this member has to be removed. /*! * \brief Does a user provide a specific subscription_storage_factory? * * This member is set to `true` in subscription_storage_factory() setter. * * \since v.5.8.2 */ bool m_is_user_provided_subscription_storage_factory{ false }; /*! * \brief Optional name for an agent. * * \since v.5.8.2 */ name_for_agent_t m_agent_name; /// \brief What to do with pending demands on deregistration. /// /// \since v.5.8.5 demands_handling_on_dereg_t m_demands_handling_on_dereg{ demands_handling_on_dereg_t::normal }; }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/all.hpp000066400000000000000000000022271523065210100167570ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A helper header file for including all public SObjectizer stuff. * * \since v.5.4.0 */ #if !defined( SO_5_ALL_HPP ) #define SO_5_ALL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif sobjectizer-5.8.6/dev/so_5/api.hpp000066400000000000000000000103011523065210100167500ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Function for the SObjectizer starting. */ #if !defined( SO_5_API_HPP ) #define SO_5_API_HPP #include #include #include #include namespace so_5 { namespace impl { //! Auxiliary class for the SObjectizer launching. /*! * It is used as wrapper on various types of initialization routines. */ template< class Init > class so_quick_environment_t : public so_5::environment_t { using base_type_t = so_5::environment_t; public: so_quick_environment_t( //! Initialization routine. Init init, //! SObjectizer Environment parameters. so_5::environment_params_t && env_params ) : base_type_t( std::move(env_params) ), m_init( std::move(init) ) {} void init() override { m_init( *this ); } private: //! Initialization routine. Init m_init; }; } /* namespace impl */ /*! * \since * v.5.3.0 * * \brief Generic type for a simple SObjectizer-initialization function. */ using generic_simple_init_t = std::function< void(so_5::environment_t &) >; /*! * \since * v.5.3.0 * * \brief Generic type for a simple SO Environment paramenters tuning function. */ using generic_simple_so_env_params_tuner_t = std::function< void(so_5::environment_params_t &) >; // // launch // //! Launch a SObjectizer Environment with default parameters. /*! Example with free function as initializer: \code void init( so_5::environment_t & env ) { env.register_agent_as_coop( "main_coop", new my_agent_t( env ) ); } int main() { so_5::launch( &init ); return 0; } \endcode Example with lambda-function as initializer: \code int main() { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( "main_coop", new my_agent_t( env ) ); } ); return 0; } \endcode Example with object method as initializer: \code class application_t { public : void init( so_5::environment_t & env ) { env.register_agent_as_coop( "main_coop", new my_agent_t( env ) ); } ... }; int main() { using namespace std; using namespace std::placeholders; application_t app; so_5::launch( bind( &application_t::init, &app, _1 ) ); return 0; } \endcode \tparam Init_Routine Type of initialization routine. It can be a pointer to function or functional object that accepts a reference to so_5::environment_t. */ template< typename Init_Routine > void launch( //! Initialization routine. Init_Routine && init_routine ) { impl::so_quick_environment_t env{ std::forward(init_routine), environment_params_t() }; env.run(); } //! Launch a SObjectizer Environment with explicitely specified parameters. /*! Example with free functions as initializers: \code void init( so_5::environment_t & env ) { env.register_agent_as_coop( "main_coop", new my_agent_t( env ), so_5::disp::active_obj::create_disp_binder( "active_obj" ) ); } void params_setter( so_5::environment_params_t & params ) { params.disable_autoshutdown(); } int main() { so_5::launch( &init, ¶ms_setter ); return 0; } \endcode Example with lambda-functions as initializers: \code int main() { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( "main_coop", new my_agent_t( env ), so_5::disp::active_obj::create_disp_binder( "active_obj" ) ); }, []( so_5::environment_params_t & params ) { params.disable_autoshutdown(); } ); return 0; } \endcode \tparam Init_Routine Type of initialization routine. It can be a pointer to function or functional object that accepts a reference to so_5::environment_t. \tparam Params_Tuner Type of routine for tuning environment's parameters. It can be a pointer to function or functional object that accepts a reference to so_5::environment_params_t. */ template< typename Init_Routine, typename Params_Tuner > inline void launch( //! Initialization routine. Init_Routine && init_routine, //! Parameters setting routine. Params_Tuner && params_tuner ) { so_5::environment_params_t params; params_tuner( params ); so_5::impl::so_quick_environment_t env{ std::forward(init_routine), std::move(params) }; env.run(); } } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/atomic_refcounted.hpp000066400000000000000000000125471523065210100217070ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief The base class for the object with a reference counting definition. */ #if !defined( SO_5_ATOMIC_REFCOUNTED_HPP ) #define SO_5_ATOMIC_REFCOUNTED_HPP #include #include #include #include #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { //! The base class for the object with a reference counting. /*! * Should be used as a base class. The smart reference for such objects * should be defined for derived classes (for example agent_ref_t). */ class SO_5_TYPE atomic_refcounted_t { public: /*! Disabled. */ atomic_refcounted_t( const atomic_refcounted_t & ) = delete; /*! Disabled. */ atomic_refcounted_t & operator = ( const atomic_refcounted_t & ) = delete; //! Default constructor. /*! * Sets reference counter to 0. */ atomic_refcounted_t() noexcept : m_ref_counter(0) {} //! Destructor. /*! * Do nothing. */ ~atomic_refcounted_t() noexcept = default; //! Increments reference count. inline void inc_ref_count() noexcept { ++m_ref_counter; } //! Decrement reference count. /*! * \return Value of reference counter *after* decrement. */ inline unsigned long dec_ref_count() noexcept { return --m_ref_counter; } private: //! Object reference count. atomic_counter_t m_ref_counter; }; // // intrusive_ptr_t // /*! * \since * v.5.2.0 * * \brief Template class for smart reference wrapper on the atomic_refcounted_t. * * \tparam T class which must be derived from the atomic_refcounted_t. */ template< class T > class intrusive_ptr_t { static void ensure_right_T() { static_assert( std::is_base_of< atomic_refcounted_t, T >::value, "T must be derived from atomic_refcounted_t" ); } public : //! Default constructor. /*! * Constructs a null reference. */ intrusive_ptr_t() noexcept : m_obj( nullptr ) { ensure_right_T(); } //! Constructor for a raw pointer. intrusive_ptr_t( T * obj ) noexcept : m_obj( obj ) { ensure_right_T(); take_object(); } //! Copy constructor. intrusive_ptr_t( const intrusive_ptr_t & o ) noexcept : m_obj( o.m_obj ) { ensure_right_T(); take_object(); } //! Move constructor. intrusive_ptr_t( intrusive_ptr_t && o ) noexcept : m_obj( o.m_obj ) { ensure_right_T(); o.m_obj = nullptr; } /*! * \since * v.5.2.2 * * \brief Constructor from another smart reference. */ template< class Y > intrusive_ptr_t( const intrusive_ptr_t< Y > & o ) noexcept : m_obj( o.get() ) { ensure_right_T(); take_object(); } /*! * \since * v.5.5.23 * * \brief Constructor from unique_ptr instance. */ template< class Y > intrusive_ptr_t( std::unique_ptr< Y > o ) noexcept : m_obj( o.release() ) { ensure_right_T(); take_object(); } //! Destructor. ~intrusive_ptr_t() noexcept { dismiss_object(); } //! Swap values. friend void swap( intrusive_ptr_t & a, intrusive_ptr_t & b ) noexcept { using std::swap; swap( a.m_obj, b.m_obj ); } //! Copy operator. intrusive_ptr_t & operator=( const intrusive_ptr_t & o ) noexcept { intrusive_ptr_t tmp( o ); swap( tmp, *this ); return *this; } //! Move operator. intrusive_ptr_t & operator=( intrusive_ptr_t && o ) noexcept { intrusive_ptr_t tmp( std::move(o) ); swap( tmp, *this ); return *this; } /*! * \since * v.5.2.2 * * \brief Drop controlled object. */ void reset() noexcept { dismiss_object(); } /*! * \since * v.5.2.2 * * \brief Make reference with casing to different type. */ template< class Y > intrusive_ptr_t< Y > make_reference() const noexcept { return intrusive_ptr_t< Y >( *this ); } //! Is this a null reference? /*! i.e. whether get() != 0. \retval true if *this manages an object. \retval false otherwise. */ [[nodiscard]] explicit operator bool() const noexcept { return nullptr != m_obj; } /*! * \name Access to object. * \{ */ [[nodiscard]] T * get() const noexcept { return m_obj; } [[nodiscard]] T * operator->() const noexcept { return m_obj; } [[nodiscard]] T & operator*() const noexcept { return *m_obj; } /*! * \} */ /*! * \name Comparision * \{ */ [[nodiscard]] bool operator==( const intrusive_ptr_t & o ) const { return get() == o.get(); } [[nodiscard]] bool operator<( const intrusive_ptr_t & o ) const { return std::less{}( get(), o.get() ); } /*! * \} */ private : //! Object controlled by a smart reference. T * m_obj; //! Increment reference count to object if it's not null. void take_object() noexcept { if( m_obj ) m_obj->inc_ref_count(); } //! Decrement reference count to object and delete it if needed. void dismiss_object() noexcept { if( m_obj ) { if( 0 == m_obj->dec_ref_count() ) { delete m_obj; } m_obj = nullptr; } } }; /*! * Deduction rule for C++17 compiler. * * \since * v.5.6.0 */ template intrusive_ptr_t(std::unique_ptr) -> intrusive_ptr_t; } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/bind_transformer_helpers.hpp000066400000000000000000000320361523065210100232700ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helpers to simplify usage of transform_then_redirect_sink. * * \since v.5.8.1 */ #if !defined( SO_5_BIND_TRANSFORMER_HELPERS_HPP ) #define SO_5_BIND_TRANSFORMER_HELPERS_HPP #include #include #include namespace so_5 { namespace bind_then_redirect_impl { } /* namespace bind_then_redirect_impl */ /*! * \brief Helper function to add transform_then_redirect msink to a binding * object. * * Type of source message is automatically deduced from the type of * \a transformer argument. * * Usage example: * \code * struct part { ... }; * struct compound { * part m_first; * part m_second; * }; * * ... * so_5::mbox_t src_mbox = ...; * so_5::mbox_t dest_mbox = ...; * * so_5::single_sink_binding_t binding; * so_5::bind_transformer(binding, src_mbox, * [dest_mbox](const compound & msg) { * return so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * msg.m_first // Initializer for the new `part` instance. * ); * }); * \endcode * * The \a transformer is expected to return a so_5::transformed_message_t * or std::optional: * \code * so_5::bind_transformer(binding, src_mbox, * [dest_mbox](const compound & msg) * -> std::optional> * { * if(should_message_be_transformed(msg)) { * return { * so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * msg.m_first // Initializer for the new `part` instance. * ) }; * } * else * return std::nullopt; // No transformation. * }); * \endcode * * \note * Because the type of the source message is deduced from \a transformer * argument this helper can't be used for transforming mutable messages * and signals. * * \attention * The \a transformer can't have an argument in the form of `const auto &`: * \code * // Compilation error is expected here because the type of * // the transformer argument can't be deduced. * so_5::bind_transformer(binding, src, [](const auto & msg) {...}); * \endcode * * \tparam Binding type of binding object. It's expected to be * so_5::single_sink_binding_t, so_5::multi_sink_binding_t or a similar * type with the same interface. * * \tparam Transformer type of transformer functor (a lambda or free function). * * \since v.5.8.1 */ template< typename Binding, typename Transformer > void bind_transformer( //! Binding object to be used. Binding & binding, //! Source mbox. It must not be nullptr. const so_5::mbox_t & src_mbox, //! Transformer that produced so_5::transformed_message_t instance or //! std::optional instance. Transformer && transformer ) { using transformer_t = std::decay_t< Transformer >; using lambda_traits_t = so_5::details::lambda_traits::traits< transformer_t >; using transformer_arg_t = std::remove_cv_t< typename lambda_traits_t::argument_type >; binding.template bind< transformer_arg_t >( src_mbox, so_5::msinks::transform_then_redirect( src_mbox->environment(), std::forward(transformer) ) ); } /*! * \brief Helper function to add transform_then_redirect msink to a binding * object (with a delivery filter). * * Type of source message is automatically deduced from the type of * \a transformer argument. * * The \a filter is called first and only if it return `true` the * \a transformed is called for message transformation. * * Usage example: * \code * struct part { ... }; * struct compound { * part m_first; * part m_second; * }; * * ... * so_5::mbox_t src_mbox = ...; * so_5::mbox_t dest_mbox = ...; * * so_5::single_sink_binding_t binding; * so_5::bind_transformer(binding, src_mbox, * [dest_mbox](const compound & msg) { * return so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * msg.m_first // Initializer for the new `part` instance. * ); * }, * [](const compound & msg) { // or [](const auto & msg) {...} * return ...; // Some predicate. * }); * \endcode * * The \a transformer is expected to return a so_5::transformed_message_t * or std::optional. * * \note * Because the type of the source message is deduced from \a transformer * argument this helper can't be used for transforming mutable messages * and signals. * * \attention * The \a transformer can't have an argument in the form of `const auto &`: * \code * // Compilation error is expected here because the type of * // the transformer argument can't be deduced. * so_5::bind_transformer(binding, src, * // This would lead to compilation error. * [](const auto & msg) {...}, * // `const auto &` can be used for a delivery filter. * [](const auto & msg) {...}); * \endcode * * \tparam Binding type of binding object. It's expected to be * so_5::single_sink_binding_t, so_5::multi_sink_binding_t or a similar * type with the same interface. * * \tparam Transformer type of transformer functor (a lambda or free function). * * \tparam Delivery_Filter type of delivery filter functor (a lambda or free * function). * * \since v.5.8.1 */ template< typename Binding, typename Transformer, typename Delivery_Filter > void bind_transformer( //! Binding object to be used. Binding & binding, //! Source mbox. It must not be nullptr. const so_5::mbox_t & src_mbox, //! Transformer that produced so_5::transformed_message_t instance or //! std::optional instance. Transformer && transformer, //! Delivery filter for the source message. Delivery_Filter && filter ) { using transformer_t = std::decay_t< Transformer >; using lambda_traits_t = so_5::details::lambda_traits::traits< transformer_t >; using transformer_arg_t = std::remove_cv_t< typename lambda_traits_t::argument_type >; binding.template bind< transformer_arg_t >( src_mbox, so_5::msinks::transform_then_redirect( src_mbox->environment(), std::forward(transformer) ), std::forward(filter) ); } /*! * \brief Helper function to add transform_then_redirect msink to a binding * object. * * The type of the source message is specified explicitly. * * \attention * Because the source message is specified explicitly, this helper * can be used for immutable, mutable messages, and signals. * * Usage example: * \code * struct part { ... }; * struct compound { * part m_first; * part m_second; * }; * * ... * so_5::mbox_t src_mbox = ...; * so_5::mbox_t dest_mbox = ...; * * so_5::single_sink_binding_t binding; * so_5::bind_transformer< so_5::mutable_msg >(binding, src_mbox, * [dest_mbox](compound & msg) { // or [dest_mbox](auto & msg) * return so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * std::move(msg.m_first) // Initializer for the new `part` instance. * ); * }); * \endcode * * If this helper is used for signals then \a transformer should be a * function without arguments: * \code * struct first_signal final : public so_5::signal_t {}; * struct second_signal final : public so_5::signal_t {}; * * ... * so_5::mbox_t src_mbox = ...; * so_5::mbox_t dest_mbox = ...; * * so_5::single_sink_binding_t binding; * so_5::bind_transformer< first_signal >(binding, src_mbox, * [dest_mbox]() { * return so_5::make_transformed( * dest_mbox // Destination for the transformed message. * ); * }); * \endcode * * The \a transformer is expected to return a so_5::transformed_message_t * or std::optional: * \code * // so_5::immutable_msg is supported as well as so_5::mutable_msg. * so_5::bind_transformer< so_5::immutable_msg >(binding, src_mbox, * [dest_mbox](const auto & msg) * -> std::optional> * { * if(should_message_be_transformed(msg)) { * return { * so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * msg.m_first // Initializer for the new `part` instance. * ) }; * } * else * return std::nullopt; // No transformation. * }); * \endcode * * \note * The \a transformer can have an argument in the form of `const auto &`: * \code * so_5::bind_transformer< compound >(binding, src, [](const auto & msg) {...}); * \endcode * * \tparam Expected_Msg type of the source message or signal. If `Msg` is the * message type, then `Msg`, `so_5::immutable_msg` or * `so_5::mutable_msg` can be used. If `Msg` is the signal type then * `Msg` or `so_5::immutable_msg` is allowed. * * \tparam Binding type of binding object. It's expected to be * so_5::single_sink_binding_t, so_5::multi_sink_binding_t or a similar * type with the same interface. * * \tparam Transformer type of transformer functor (a lambda or free function). * * \since v.5.8.1 */ template< typename Expected_Msg, typename Binding, typename Transformer > void bind_transformer( //! Binding object to be used. Binding & binding, //! Source mbox. It must not be nullptr. const so_5::mbox_t & src_mbox, //! Transformer that produced so_5::transformed_message_t instance or //! std::optional instance. Transformer && transformer ) { binding.template bind< Expected_Msg >( src_mbox, so_5::msinks::transform_then_redirect< Expected_Msg >( src_mbox->environment(), std::forward(transformer) ) ); } /*! * \brief Helper function to add transform_then_redirect msink to a binding * object (with a delivery filter). * * The type of the source message is specified explicitly. * * \attention * Because the source message is specified explicitly, this helper * can be used for immutable and mutable messages. * * \attention * This helper can't be used for signals because delivery filters are * not applicable to signals. * * Usage example: * \code * struct part { ... }; * struct compound { * part m_first; * part m_second; * }; * * ... * so_5::mbox_t src_mbox = ...; * so_5::mbox_t dest_mbox = ...; * * so_5::single_sink_binding_t binding; * so_5::bind_transformer< so_5::mutable_msg >(binding, src_mbox, * [dest_mbox](compound & msg) { // or [dest_mbox](auto & msg) * return so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * std::move(msg.m_first) // Initializer for the new `part` instance. * ); * }, * // Note the use of const, delivery filter always receives a const * // reference for a message to be filtered. * [](const compound & msg) { // or [](const auto & msg) * return ...; // Some predicate. * }); * \endcode * * The \a transformer is expected to return a so_5::transformed_message_t * or std::optional: * \code * so_5::bind_transformer< so_5::mutable_msg >(binding, src_mbox, * [dest_mbox](auto & msg) * -> std::optional> * { * if(should_message_be_transformed(msg)) { * return { * so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * std::move(msg.m_first) // Initializer for the new `part` instance. * ) }; * } * else * return std::nullopt; // No transformation. * }, * // Note the use of const, delivery filter always receives a const * // reference for a message to be filtered. * [](const compound & msg) { // or [](const auto & msg) * return ...; // Some predicate. * }); * \endcode * * \note * The \a transformer can have an argument in the form of `const auto &`: * \code * so_5::bind_transformer< compound >(binding, src, * // Transformer. * [](const auto & msg) {...}, * // Delivery filter * [](const auto & msg) {...}); * \endcode * * \tparam Expected_Msg type of the source message or signal. When `Msg` is the * expected message type, then `Msg`, `so_5::immutable_msg` or * `so_5::mutable_msg` can be used. * * \tparam Binding type of binding object. It's expected to be * so_5::single_sink_binding_t, so_5::multi_sink_binding_t or a similar * type with the same interface. * * \tparam Transformer type of transformer functor (a lambda or free function). * * \since v.5.8.1 */ template< typename Expected_Msg, typename Binding, typename Transformer, typename Delivery_Filter > void bind_transformer( //! Binding object to be used. Binding & binding, //! Source mbox. It must not be nullptr. const so_5::mbox_t & src_mbox, //! Transformer that produced so_5::transformed_message_t instance or //! std::optional instance. Transformer && transformer, //! Delivery filter for the source message. Delivery_Filter && delivery_filter ) { binding.template bind< Expected_Msg >( src_mbox, so_5::msinks::transform_then_redirect< Expected_Msg >( src_mbox->environment(), std::forward(transformer) ), std::forward(delivery_filter) ); } } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/cmake/000077500000000000000000000000001523065210100165535ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/cmake/sobjectizer-config.cmake.in000066400000000000000000000001151523065210100237450ustar00rootroot00000000000000@PACKAGE_INIT@ include(${CMAKE_CURRENT_LIST_DIR}/sobjectizer-targets.cmake) sobjectizer-5.8.6/dev/so_5/cmake/target.cmake000066400000000000000000000001541523065210100210430ustar00rootroot00000000000000set(SO_5_VERSION "5.8.6") set(SO_5_SHARED_LIB so.${SO_5_VERSION}) set(SO_5_STATIC_LIB so_s.${SO_5_VERSION}) sobjectizer-5.8.6/dev/so_5/compiler_features.hpp000066400000000000000000000005121523065210100217120ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \since * v.5.3.0 * * \file * \brief Detection of compiler version and absence of various features. */ #if !defined( SO_5_COMPILER_FEATURES_HPP ) #define SO_5_COMPILER_FEATURES_HPP #if defined( _MSC_VER ) #define SO_5_MSVC #endif #if defined( __clang__ ) #define SO_5_CLANG #endif #endif sobjectizer-5.8.6/dev/so_5/coop.cpp000066400000000000000000000356501523065210100171500ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include #include #include #include #include #include #include #include #include namespace so_5 { // // coop_reg_notificators_container_t // void coop_reg_notificators_container_t::call_all( environment_t & env, const coop_handle_t & coop ) const noexcept { for( auto & n : m_notificators ) n( env, coop ); } // // coop_dereg_notificators_container_t // void coop_dereg_notificators_container_t::call_all( environment_t & env, const coop_handle_t & coop, const coop_dereg_reason_t & reason ) const noexcept { for( auto & n : m_notificators ) n( env, coop, reason ); } namespace impl { // // coop_impl_t // void coop_impl_t::destroy_content( coop_t & coop ) noexcept { // Initiate deleting of agents by hand to guarantee that // agents will be destroyed before return from coop_t // destructor. // // NOTE: because agents are stored here by smart references // for some agents this operation will lead only to reference // counter descrement. Not to deletion of agent. coop.m_agent_array.clear(); // Don't expect exceptions here because all resource deleters have // to be noexcept. for( auto & d : coop.m_resource_deleters ) { static_assert( noexcept( d() ), "resource deleter is expected to be noexcept" ); d(); } coop.m_resource_deleters.clear(); } void coop_impl_t::do_add_agent( coop_t & coop, agent_ref_t agent_ref ) { internal_agent_iface_t agent_iface{ *agent_ref }; so_5::details::do_with_rollback_on_exception( [&]() { agent_iface.set_disp_binder( coop.m_coop_disp_binder ); coop.m_agent_array.emplace_back( std::move(agent_ref) ); }, [&agent_iface]() noexcept { agent_iface.drop_disp_binder(); } ); } void coop_impl_t::do_add_agent( coop_t & coop, agent_ref_t agent_ref, disp_binder_shptr_t disp_binder ) { internal_agent_iface_t agent_iface{ *agent_ref }; so_5::details::do_with_rollback_on_exception( [&]() { agent_iface.set_disp_binder( std::move(disp_binder) ); coop.m_agent_array.emplace_back( std::move(agent_ref) ); }, [&agent_iface]() noexcept { agent_iface.drop_disp_binder(); } ); } namespace { /*! * \since * v.5.2.3 * * \brief Helper function for notificator addition. */ template< class C, class N > inline void do_add_notificator_to( intrusive_ptr_t< C > & to, N notificator ) { if( !to ) { to = intrusive_ptr_t< C >( new C() ); } to->add( std::move(notificator) ); } } /* namespace anonymous */ void coop_impl_t::add_reg_notificator( coop_t & coop, coop_reg_notificator_t notificator ) { do_add_notificator_to( coop.m_reg_notificators, std::move(notificator) ); } void coop_impl_t::add_dereg_notificator( coop_t & coop, coop_dereg_notificator_t notificator ) { do_add_notificator_to( coop.m_dereg_notificators, std::move(notificator) ); } [[nodiscard]] exception_reaction_t coop_impl_t::exception_reaction( const coop_t & coop ) noexcept { if( inherit_exception_reaction == coop.m_exception_reaction ) { const auto parent = so_5::low_level_api::to_shptr_noexcept( coop.m_parent ); if( parent ) return parent->exception_reaction(); else return coop.environment().exception_reaction(); } return coop.m_exception_reaction; } void coop_impl_t::do_decrement_reference_count( coop_t & coop ) noexcept { // If it is the last working agent then Environment should be // informed that the cooperation is ready to be deregistered. if( 0 == --coop.m_reference_count ) { // NOTE: usage counter incremented and decremented during // registration process even if registration of cooperation failed. // So decrement_usage_count() could be called when cooperation // has coop_not_registered status. // // It is possible that reference counter become 0 several times. // For example when a child coop is being registered while // the parent coop is in deregistration process. // Because of that it is necessary to check the current status // of the coop. // // If the coop should be deregistered finally its status should // be changed to deregistration_in_final_stage. const auto should_finalize = [&] { so_5::details::tsan_friendly_lock_guard_t< std::mutex > lock{ coop.m_lock }; using status_t = coop_t::registration_status_t; if( status_t::coop_registered == coop.m_registration_status || status_t::coop_deregistering == coop.m_registration_status ) { coop.m_registration_status = status_t::deregistration_in_final_stage; return true; } else return false; }; if( should_finalize() ) { impl::internal_env_iface_t{ coop.m_env.get() } .ready_to_deregister_notify( coop.shared_from_this() ); } } } class coop_impl_t::registration_performer_t { coop_t & m_coop; void perform_actions_without_rollback_on_exception() { reorder_agents_with_respect_to_priorities(); bind_agents_to_coop(); preallocate_disp_resources(); } void perform_actions_with_rollback_on_exception() { so_5::details::do_with_rollback_on_exception( [this] { define_all_agents(); // Coop's lock should be acquired before notification // of the parent coop. so_5::details::tsan_friendly_lock_guard_t< std::mutex > lock{ m_coop.m_lock }; make_relation_with_parent_coop(); // These actions shouldn't throw. details::invoke_noexcept_code( [&] { // This operation shouldn't throw because dispatchers // allocated resources for agents. // // But it is possible that an exception will be throw // during an attempt to send evt_start message to agents. // In that case it is simpler to call std::terminate(). bind_agents_to_disp(); // Cooperation should assume that it is registered now. m_coop.m_registration_status = coop_t::registration_status_t::coop_registered; // Increment reference count to reflect that cooperation // is registered. This is necessary in v.5.5.12 to prevent // automatic deregistration of the cooperation right after // finish of registration process for empty cooperation. m_coop.increment_usage_count(); } ); }, [this] { // NOTE: we use the fact that actual binding of agents to // dispatchers can't throw. It means that exception was thrown // at earlier stages (in define_all_agents() or // make_relation_with_parent_coop()). deallocate_disp_resources(); } ); } void reorder_agents_with_respect_to_priorities() noexcept { std::sort( std::begin(m_coop.m_agent_array), std::end(m_coop.m_agent_array), []( const auto & a, const auto & b ) noexcept { return special_agent_ptr_compare( *a, *b ); } ); } void bind_agents_to_coop() { for( const auto & agent_ref : m_coop.m_agent_array ) { internal_agent_iface_t{ *agent_ref }.bind_to_coop( m_coop ); } } void preallocate_disp_resources() { // In case of an exception we should undo preallocation only for // those agents for which preallocation was successful. coop_t::agent_array_t::iterator it; try { for( it = m_coop.m_agent_array.begin(); it != m_coop.m_agent_array.end(); ++it ) { agent_t & agent = **it; internal_agent_iface_t agent_iface{ agent }; agent_iface.query_disp_binder() .preallocate_resources( agent ); } } catch( const std::exception & x ) { // All preallocated resources should be returned back. for( auto it2 = m_coop.m_agent_array.begin(); it2 != it; ++it2 ) { agent_t & agent = **it2; internal_agent_iface_t agent_iface{ agent }; agent_iface.query_disp_binder() .undo_preallocation( agent ); } SO_5_THROW_EXCEPTION( rc_agent_to_disp_binding_failed, std::string{ "an exception during the first stage of " "binding agent to the dispatcher, exception: " } + x.what() ); } } void define_all_agents() { try { for( const auto & agent_ref : m_coop.m_agent_array ) internal_agent_iface_t{ *agent_ref } .initiate_agent_definition(); } catch( const exception_t & ) { throw; } catch( const std::exception & ex ) { SO_5_THROW_EXCEPTION( rc_coop_define_agent_failed, ex.what() ); } catch( ... ) { SO_5_THROW_EXCEPTION( rc_coop_define_agent_failed, "exception of unknown type has been thrown in " "so_define_agent()" ); } } void make_relation_with_parent_coop() { so_5::low_level_api::to_shptr(m_coop.m_parent)->add_child( m_coop.shared_from_this() ); } void bind_agents_to_disp() noexcept { for( const auto & agent_ref : m_coop.m_agent_array ) { internal_agent_iface_t agent_iface{ *agent_ref }; agent_iface.query_disp_binder().bind( *agent_ref ); } } void deallocate_disp_resources() noexcept { for( const auto & agent_ref : m_coop.m_agent_array ) { internal_agent_iface_t agent_iface{ *agent_ref }; agent_iface.query_disp_binder() .undo_preallocation( *agent_ref ); } } public : explicit registration_performer_t( coop_t & coop ) noexcept : m_coop{ coop } {} void perform() { // On first phase we perform actions that don't require // any rollback on exception. perform_actions_without_rollback_on_exception(); // Then we should perform some actions that require some // rollback in the case of an exception. perform_actions_with_rollback_on_exception(); } }; void coop_impl_t::do_registration_specific_actions( coop_t & coop ) { registration_performer_t{ coop }.perform(); } // // deregistration_performer_t // //! A helper for coop's deregistration procedure. class coop_impl_t::deregistration_performer_t { coop_t & m_coop; const coop_dereg_reason_t m_reason; enum class phase1_result_t { dereg_initiated, dereg_already_in_progress }; phase1_result_t perform_phase1() noexcept { // The first phase should be performed on locked object. so_5::details::tsan_friendly_lock_guard_t< std::mutex > lock{ m_coop.m_lock }; if( coop_t::registration_status_t::coop_registered != m_coop.m_registration_status ) // Deregistration is already in progress. // Nothing to do. return phase1_result_t::dereg_already_in_progress; // Deregistration process should be started. m_coop.m_registration_status = coop_t::registration_status_t::coop_deregistering; m_coop.m_dereg_reason = m_reason; initiate_deregistration_for_children(); return phase1_result_t::dereg_initiated; } void shutdown_all_agents() noexcept { for( const auto & agent_ref : m_coop.m_agent_array ) { internal_agent_iface_t{ *agent_ref }.shutdown_agent(); } } void initiate_deregistration_for_children() noexcept { m_coop.for_each_child( []( coop_t & coop ) { coop.deregister( dereg_reason::parent_deregistration ); } ); } public : deregistration_performer_t( coop_t & coop, coop_dereg_reason_t reason ) noexcept : m_coop{ coop } , m_reason{ reason } {} void perform() noexcept { auto result = perform_phase1(); if( phase1_result_t::dereg_initiated == result ) { // Deregistration is initiated the first time. // All agents should be shut down. shutdown_all_agents(); // Reference count to this coop can be decremented. // If there is no more uses of that coop then the coop // will be deregistered completely. m_coop.decrement_usage_count(); } } }; void coop_impl_t::do_deregistration_specific_actions( coop_t & coop, coop_dereg_reason_t reason ) noexcept { deregistration_performer_t{ coop, reason }.perform(); } void coop_impl_t::do_final_deregistration_actions( coop_t & coop ) { // Agents should be unbound from their dispatchers. for( const auto & agent_ref : coop.m_agent_array ) { agent_t & agent = *agent_ref; internal_agent_iface_t agent_iface{ agent }; agent_iface.query_disp_binder().unbind( agent ); } // Now the coop can be removed from it's parent. // We don't expect an exception here because m_parent should // contain an actual value. // But if not then we have a serious problem and it is better // to terminate the application. so_5::low_level_api::to_shptr(coop.m_parent)->remove_child( coop ); } void coop_impl_t::do_add_child( coop_t & parent, coop_shptr_t child ) { // Count of users on this coop is incremented. parent.increment_usage_count(); // If an exception is throw below then usage count for the parent // coop should be decremented. so_5::details::do_with_rollback_on_exception( [&] { // Modification of parent-child relationship must be performed // on locked object. so_5::details::tsan_friendly_lock_guard_t< std::mutex > lock{ parent.m_lock }; // A new coop can't be added as a child if coop is being // deregistered. if( coop_t::registration_status_t::coop_registered != parent.m_registration_status ) { SO_5_THROW_EXCEPTION( rc_coop_is_not_in_registered_state, "add_child() can be processed only when coop " "is registered" ); } // New child will be inserted to the head of children list. if( parent.m_first_child ) parent.m_first_child->m_prev_sibling = child; child->m_next_sibling = std::move(parent.m_first_child); parent.m_first_child = std::move(child); }, [&parent] { // Something went wrong. Count of references should be // returned back. parent.decrement_usage_count(); } ); } void coop_impl_t::do_remove_child( coop_t & parent, coop_t & child ) noexcept { { // Modification of parent-child relationship must be performed // on locked object. so_5::details::tsan_friendly_lock_guard_t< std::mutex > lock{ parent.m_lock }; if( parent.m_first_child.get() == &child ) { // Child was a head of children chain. There is no prev-sibling // for the child to be removed. parent.m_first_child = child.m_next_sibling; if( parent.m_first_child ) parent.m_first_child->m_prev_sibling.reset(); } else { child.m_prev_sibling->m_next_sibling = child.m_next_sibling; if( child.m_next_sibling ) child.m_next_sibling->m_prev_sibling = child.m_prev_sibling; } } // Count of references to the parent coop can be decremented now. parent.decrement_usage_count(); } } /* namespace impl */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/coop.hpp000066400000000000000000001062041523065210100171470ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Cooperation of agents. * * \since v.5.6.0 */ #if !defined( SO_5_COOP_HPP ) #define SO_5_COOP_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { namespace dereg_reason { /*! * \name Cooperation deregistration reasons. * \{ */ //! Normal deregistration. const int normal = 0; //! Deregistration because SObjectizer Environment shutdown. const int shutdown = 1; //! Deregistration because parent cooperation deregistration. const int parent_deregistration = 2; //! Deregistration because of unhandled exception. const int unhandled_exception = 3; //! Deregistration because of unknown error. const int unknown_error = 4; //! Reason is not properly defined. const int undefined = -1; //! A starting point for user-defined reasons. const int user_defined_reason = 0x1000; /*! * \} */ } /* namespace dereg_reason */ // // coop_dereg_reason_t // /*! * \brief It's a kind of strong typedef for coop's deregistration reason. * * \since v.5.2.3 */ class coop_dereg_reason_t { public : coop_dereg_reason_t() noexcept : m_reason( dereg_reason::undefined ) {} explicit coop_dereg_reason_t( int reason ) noexcept : m_reason( reason ) {} int reason() const noexcept { return m_reason; } private : int m_reason; }; // // coop_reg_notificator_t // /*! * \brief Type of cooperation registration notificator. * * Cooperation notificator should be a function with the following * prototype: \code void notificator( // SObjectizer Environment for cooperation. so_5::environment_t & env, // Coop's handle. const coop_handle_t & coop ); \endcode * * \since v.5.2.3 */ using coop_reg_notificator_t = std::function< void(environment_t &, const coop_handle_t &) >; // // coop_reg_notificators_container_t // /*! * \brief Container for cooperation registration notificators. * * \since v.5.2.3 */ class SO_5_TYPE coop_reg_notificators_container_t : public atomic_refcounted_t { public : //! Add a notificator. void add( coop_reg_notificator_t notificator ) { m_notificators.push_back( std::move(notificator) ); } //! Call all notificators. void call_all( environment_t & env, const coop_handle_t & coop ) const noexcept; private : std::vector< coop_reg_notificator_t > m_notificators; }; // // coop_reg_notificators_container_ref_t // /*! * \brief Typedef for smart pointer to notificators_container. * * \since v.5.2.3 */ using coop_reg_notificators_container_ref_t = intrusive_ptr_t< coop_reg_notificators_container_t >; // // coop_dereg_notificator_t // /*! * \brief Type of cooperation deregistration notificator. * * Cooperation notificator should be a function with the following * prototype: \code void notificator( // SObjectizer Environment for cooperation. so_5::environment_t & env, // Coop's handle. const coop_handle_t & coop, // Reason of deregistration. const so_5::coop_dereg_reason_t & reason ); \endcode * * \since v.5.2.3 */ using coop_dereg_notificator_t = std::function< void( environment_t &, const coop_handle_t &, const coop_dereg_reason_t &) >; // // coop_dereg_notificators_container_t // /*! * \brief Container for cooperation deregistration notificators. * * \since v.5.2.3 */ class SO_5_TYPE coop_dereg_notificators_container_t : public atomic_refcounted_t { public : //! Add a notificator. void add( coop_dereg_notificator_t notificator ) { m_notificators.push_back( std::move(notificator) ); } //! Call all notificators. void call_all( environment_t & env, const coop_handle_t & coop, const coop_dereg_reason_t & reason ) const noexcept; private : std::vector< coop_dereg_notificator_t > m_notificators; }; // // coop_dereg_notificators_container_ref_t // /*! * \brief Typedef for smart pointer to notificators_container. * * \since v.5.2.3 */ using coop_dereg_notificators_container_ref_t = intrusive_ptr_t< coop_dereg_notificators_container_t >; namespace impl { class coop_private_iface_t; // // coop_impl_t // /*! * \brief An internal class with real implementation of coop's logic. * * Class coop_t is derived from std::enable_shared_from_this. But when * coop_t is exported from a DLL the VC++ compiler issue some warnings * about dll-linkage of std::enable_shared_from_this. To avoid these * warnings coop_t is just a colletion of data. All coop's logic is * implemented by coop_impl_t. * * \since v.5.6.0 */ class SO_5_TYPE coop_impl_t { friend class so_5::coop_t; friend class so_5::impl::coop_private_iface_t; //! Perform all necessary cleanup actions for coop. static void destroy_content( //! Target coop. coop_t & coop ) noexcept; //! Add agent to cooperation. /*! * Cooperation takes care about agent lifetime. * * Default dispatcher binding is used for the agent. */ static void do_add_agent( //! Target coop. coop_t & coop, //! Agent to be bound to the coop. agent_ref_t agent_ref ); //! Add agent to the cooperation with the dispatcher binding. /*! * Instead of the default dispatcher binding the \a disp_binder * is used for this agent during the cooperation registration. */ static void do_add_agent( //! Target coop. coop_t & coop, //! Agent. agent_ref_t agent_ref, //! Agent to dispatcher binder. disp_binder_shptr_t disp_binder ); //! Add notificator about cooperation registration event. static void add_reg_notificator( //! Target coop. coop_t & coop, //! Notificator to be added. coop_reg_notificator_t notificator ); //! Add notificator about cooperation deregistration event. static void add_dereg_notificator( //! Target coop. coop_t & coop, //! Notificator to be added. coop_dereg_notificator_t notificator ); //! Get exception reaction for coop. [[nodiscard]] static exception_reaction_t exception_reaction( //! Target coop. const coop_t & coop ) noexcept; //! Do decrement reference count for a coop. /*! * \note * This method is marked as noexcept because there is no way * to recover if any exception is raised here. */ static void do_decrement_reference_count( //! Target coop. coop_t & coop ) noexcept; //! Perform actions related to the registration of coop. static void do_registration_specific_actions( coop_t & coop ); class registration_performer_t; //! Perform actions related to the deregistration of coop. /*! * \note * This method is marked as noexcept because there is no way * to recover if any exception is raised here. */ static void do_deregistration_specific_actions( //! Coop to be deregistered. coop_t & coop, //! Reason of coop's deregistration. coop_dereg_reason_t reason ) noexcept; class deregistration_performer_t; //! Perform final deregistration actions for an coop. static void do_final_deregistration_actions( //! Target coop. coop_t & coop ); //! Perform addition of a new child coop. static void do_add_child( //! Parent coop. coop_t & parent, //! Child to be added. coop_shptr_t child ); //! Perform removement of a child coop. /*! * \note * This method is noexcept since v.5.8.0 */ static void do_remove_child( //! Parent coop. coop_t & parent, //! Child to be removed. coop_t & child ) noexcept; }; } /* namespace impl */ //! Agent cooperation. /*! * The main purpose of the cooperation is the introducing of several agents into * SObjectizer as a single unit. A cooperation should be registered. * * For the cooperation to be successfuly registered all of its agents must * successfuly pass registration steps (so-define, bind to the dispatcher). * If at least one agent out of this cooperation fails to pass any of * mentioned steps, the cooperation will not be registered and * all of agents will run procedures opposite to registration * steps (unbind from the dispatcher, so-undefine) which had been successfuly * taken for the particulary agent in the reverse order. * * Agents are added to the cooperation by the add_agent() method. * * After addition to the cooperation the cooperation takes care about * the agent lifetime. */ class coop_t : public std::enable_shared_from_this { private : friend class agent_t; friend class impl::coop_private_iface_t; friend class impl::coop_impl_t; friend class impl::coop_impl_t::registration_performer_t; friend class impl::coop_impl_t::deregistration_performer_t; coop_t( const coop_t & ) = delete; coop_t & operator=( const coop_t & ) = delete; coop_t( coop_t && ) = delete; coop_t & operator=( coop_t && ) = delete; protected : //! Constructor. coop_t( //! Cooperation ID. coop_id_t id, //! Parent coop. coop_handle_t parent, //! Default dispatcher binding. disp_binder_shptr_t coop_disp_binder, //! SObjectizer Environment. outliving_reference_t< environment_t > env ) : m_id{ id } , m_parent{ std::move(parent) } , m_coop_disp_binder{ std::move(coop_disp_binder) } , m_env{ env } {} public: virtual ~coop_t() { impl::coop_impl_t::destroy_content( *this ); } /*! * \brief Get handle for this coop. * * \since v.5.6.0 */ [[nodiscard]] coop_handle_t handle() noexcept { return coop_handle_t{ m_id, shared_from_this() }; } /*! * \brief Get the ID of coop. * * \since v.5.6.0 */ [[nodiscard]] coop_id_t id() const noexcept { return m_id; } /*! * \brief Access to SO Environment for which cooperation is bound. * * \since v.5.3.0 */ [[nodiscard]] environment_t & environment() const noexcept { return m_env.get(); } //! Add agent to cooperation. /*! * Cooperation takes care about agent lifetime. * * Default dispatcher binding is used for the agent. */ template< class Agent > inline Agent * add_agent( //! Agent. std::unique_ptr< Agent > agent ) { Agent * p = agent.get(); impl::coop_impl_t::do_add_agent( *this, agent_ref_t{ std::move(agent) } ); return p; } //! Add agent to the cooperation with the dispatcher binding. /*! * Instead of the default dispatcher binding the \a disp_binder * is used for this agent during the cooperation registration. * * Usage example: * \code * [[nodiscard]] std::unique_ptr make_agent(so_5::environment_t & env) { * ... // Some preparation. * return std::make_unique(...); * } * ... * so_5::environment_t & env = ...; * auto coop_obj = env.make_coop(); * // Add a new agent and bind it to a separate one_thread dispatcher. * coop_obj->add_agent( * make_agent(env), * so_5::disp::one_thread::make_dispatcher(env).binder()); * ... * env.register_coop(std::move(coop_obj)); * \endcode */ template< class Agent > inline Agent * add_agent( //! Agent. std::unique_ptr< Agent > agent, //! Agent to dispatcher binder. disp_binder_shptr_t disp_binder ) { Agent * p = agent.get(); impl::coop_impl_t::do_add_agent( *this, agent_ref_t{ std::move(agent) }, std::move(disp_binder) ); return p; } /*! * \name Method for working with notificators. * \{ */ /*! * \brief Add notificator about cooperation registration event. * * Notificator should be a function or functional object with the * following signature: * \code * void(so_5::environment_t & env, const so_5::coop_handle_t & handle) noexcept; * \endcode * * An example: * \code * class parent_agent final : public so_5::agent_t * { * // Map of registered cooperations. * std::map live_coops_; * ... * struct coop_is_alive final : public so_5::message_t * { * const std::string name_; * const so_5::coop_handle_t handle_; * * coop_is_alive(std::string name, so_5::coop_handle_t handle) * : m_name{std::move(name)} * , m_handle{std::move(handle)} * {} * }; * * void evt_coop_is_alive(mhood_t cmd) { * m_live_coops_[cmd->name_] = cmd->handle_; * } * * void evt_make_new_coop(mhood_t cmd) { * so_5::introduce_child_coop(*this, [&](so_5::coop_t & coop) { * ... // Fill the coop. * // Add reg-notificator. * coop.add_reg_notificator( * [this, name=cmd->source_name]( * so_5::environment_t &, * const so_5::coop_handle_t & handle) noexcept * { * // Inform the parent about the registration. * so_5::send(*this, name, handle); * } * } ); * } * * ... * } * \endcode * * \note * reg_notificator can (and most likely will) be called from some * different thread, not the thread where reg_notificator was added. So * please take an additional care if your notificator changes some shared * data -- it can break thread safety easily. * * \attention * Since v.5.6.0 reg_notificator should be a noexcept function or * functor. Because of that a check in performed during compile time. An * attempt to pass non-noexcept function or functor to * add_reg_notificator will lead to compilation error. * * \since v.5.2.3 */ template< typename Lambda > void add_reg_notificator( Lambda && notificator ) { static_assert( std::is_nothrow_invocable_v< Lambda, environment_t &, const coop_handle_t & >, "notificator should be noexcept function/functor" ); impl::coop_impl_t::add_reg_notificator( *this, coop_reg_notificator_t{ std::forward(notificator) } ); } /*! * \brief Add notificator about cooperation deregistration event. * * Notificator should be a function or functional object with the * following signature: * \code * void( * so_5::environment_t & env, * const so_5::coop_handle_t & handle, * const so_5::coop_dereg_reason_t & reason) noexcept; * \endcode * * An example: * \code * class parent_agent final : public so_5::agent_t * { * // Map of registered cooperations. * std::map live_coops_; * ... * struct coop_is_dead final : public so_5::message_t * { * const std::string name_; * * explicit coop_is_dead(std::string name) * : m_name{std::move(name)} * {} * }; * * void evt_coop_is_dead(mhood_t cmd) { * m_live_coops_.erase(cmd->name_); * } * * void evt_make_new_coop(mhood_t cmd) { * so_5::introduce_child_coop(*this, [&](so_5::coop_t & coop) { * ... // Fill the coop. * * // Add reg-notificator. * coop.add_reg_notificator(...); * * // Add deref-notificator. * coop.add_dereg_notificator( * [this, name=cmd->source_name]( * so_5::environment_t &, * const so_5::coop_handle_t & handle, * const so_5::coop_dereg_reason_t &) noexcept * { * // Inform the parent about the deregistration. * so_5::send(*this, name); * } * } ); * } * * ... * } * \endcode * * \note * dereg_notificator can (and most likely will) be called from some * different thread, not the thread where dereg_notificator was added. So * please take an additional care if your notificator changes some shared * data -- it can break thread safety easily. * * \attention * Since v.5.6.0 dereg_notificator should be a noexcept function or * functor. Because of that a check in performed during compile time. An * attempt to pass non-noexcept function or functor to * add_dereg_notificator will lead to compilation error. * * \since v.5.2.3 */ template< typename Lambda > void add_dereg_notificator( Lambda && notificator ) { static_assert( std::is_nothrow_invocable_v< Lambda, environment_t &, const coop_handle_t &, const coop_dereg_reason_t & >, "notificator should be noexcept function/functor" ); impl::coop_impl_t::add_dereg_notificator( *this, coop_dereg_notificator_t{ std::forward(notificator) } ); } /*! * \} */ /*! * \name Method for working with user resources. * \{ */ /*! * \brief Take a user resouce under cooperation control. * * \since v.5.2.3 */ template< class T > T * take_under_control( std::unique_ptr< T > resource ) { T * ret_value = resource.get(); m_resource_deleters.emplace_back( resource_deleter_t{ ret_value } ); resource.release(); return ret_value; } /*! * \} */ /*! * \name Exception reaction methods. * \{ */ /*! * \brief Set exception reaction for that cooperation. * * This value will be used by agents and children cooperation if * they use inherit_exception_reaction value. * * \since v.5.3.0 */ void set_exception_reaction( exception_reaction_t value ) noexcept { m_exception_reaction = value; } /*! * \brief Get the current exception rection flag for that cooperation. * * It uses the following logic: * - if own's exception_reaction flag value differs from * inherit_exception_reaction value than own's exception_reaction * flag value returned; * - otherwise if there is a parent cooperation than parent coop's * exception_reaction value returned; * - otherwise SO Environment's exception_reaction is returned. * * \since v.5.3.0 */ [[nodiscard]] exception_reaction_t exception_reaction() const noexcept { return impl::coop_impl_t::exception_reaction( *this ); } /*! * \} */ /*! * \brief Helper method for simplification of agents creation. * * \note Creates an instance of agent of type \a Agent and adds it to * the cooperation. * * \return pointer to the new agent. * * \tparam Agent type of agent to be created. * \tparam Args type of parameters list for agent constructor. * * \par Usage sample: \code auto coop = env.make_coop(); // For the case of constructor like my_agent(environmen_t&). coop->make_agent< my_agent >(); // For the case of constructor like your_agent(environment_t&, std::string). auto ya = coop->make_agent< your_agent >( "hello" ); // For the case of constructor like thier_agent(environment_t&, std::string, mbox_t). coop->make_agent< their_agent >( "bye", ya->so_direct_mbox() ); \endcode * * \since v.5.5.4 */ template< class Agent, typename... Args > Agent * make_agent( Args &&... args ) { return this->add_agent( std::make_unique( environment(), std::forward(args)...) ); } /*! * \brief Helper method for simplification of agents creation and * binding to the specified dispatcher. * * \note Creates an instance of agent of type \a Agent and adds it to * the cooperation with the specified binder. * * \return pointer to the new agent. * * \tparam Agent type of agent to be created. * \tparam Args type of parameters list for agent constructor. * * \par Usage sample: \code auto disp = so_5::disp::one_thread::make_dispatcher( env ); auto coop = env.make_coop(); // For the case of constructor like my_agent(environmen_t&). coop->make_agent_with_binder< my_agent >( disp.binder() ); // For the case of constructor like your_agent(environment_t&, std::string). auto ya = coop->make_agent_with_binder< your_agent >( disp.binder(), "hello" ); // For the case of constructor like thier_agent(environment_t&, std::string, mbox_t). coop->make_agent_with_binder< their_agent >( disp.binder(), "bye", ya->so_direct_mbox() ); \endcode * * \since v.5.5.4 */ template< class Agent, typename... Args > Agent * make_agent_with_binder( //! A dispatcher binder for the new agent. so_5::disp_binder_shptr_t binder, //! Arguments to be passed to the agent's constructor. Args &&... args ) { return this->add_agent( std::make_unique( environment(), std::forward(args)...), std::move(binder) ); } /*! * \brief Get agent count in the cooperation. * * \since v.5.5.4 */ std::size_t query_agent_count() const noexcept { return m_agent_array.size(); } /*! * \brief Get agent count in the cooperation. * \note Just an alias for query_agent_count(). * * \since v.5.5.16 */ std::size_t size() const noexcept { return query_agent_count(); } /*! * \brief Get the capacity of vector for holding agents list. * * \since v.5.5.16 */ std::size_t capacity() const noexcept { return m_agent_array.capacity(); } /*! * \brief Reserve a space for vector for holding agents list. * * This method can help avoid reallocations of agents list during * filling the coop: * \code env.introduce_coop( []( so_5::coop_t & coop ) { coop.reserve( agents_count ); for( size_t i = 0; i != agents_count; ++i ) coop.make_agent< some_type >( some_args ); } ); * \endcode * * \since v.5.5.16 */ void reserve( std::size_t v ) { m_agent_array.reserve( v ); } //FIXME: Should be moved to a protected/private part in v.5.9! /*! * \brief Deregister the cooperation with the specified reason. * * \attention * This method is intended for internal use by SObjectizer implementation. * It's a historical accident that this method remains public. It will be * made private in v.5.9. Do not use it! * * \par Usage example: \code coop_t & coop = ...; coop.deregister( so_4::dereg_reason::user_defined_reason + 100 ); \endcode * * \note * This method is marked as noexcept because there is no way * to recover if any exception is raised here. * * \since v.5.5.8 */ void deregister( //! Reason of cooperation deregistration. int reason ) noexcept { impl::coop_impl_t::do_deregistration_specific_actions( *this, coop_dereg_reason_t{ reason } ); } //FIXME: Should be removed in v.5.9! /*! * \deprecated This method is going to be removed in v.5.9. * * \brief Deregistr the cooperation normally. * * \attention * This method is intended for internal use by SObjectizer implementation. * It's a historical accident that this method remains public. It will be * removed in v.5.9. Do not use it! * * This method is just a shorthand for: \code so_5::coop_t & coop = ...; coop.deregister( so_5::dereg_reason::normal ); \endcode * * \note * This method is marked as noexcept because there is no way * to recover if any exception is raised here. * * \since v.5.5.8 */ [[deprecated]] void deregister_normally() noexcept { this->deregister( dereg_reason::normal ); } protected: //! Typedef for the agent information container. using agent_array_t = std::vector< agent_ref_t >; /*! * \brief Registration status. * * \since v.5.2.3 */ enum class registration_status_t { //! Cooperation is not registered yet. coop_not_registered, //! Cooperation is registered. /*! * Reference count for cooperation in that state should * be greater than zero. */ coop_registered, //! Cooperation is in deregistration process. /*! * Reference count for cooperation in that state should * be zero. */ coop_deregistering, //! Deregistration of the coop is in the final stage. deregistration_in_final_stage }; /*! * \brief Type of user resource deleter. * * Note. Before v.5.6.0 object of type std::function was used * as resource deleter. Since v.5.6.0 with functor is used as * lightweigt std::function alternative. */ struct resource_deleter_t { using deleter_pfn_t = void(*)(void *) noexcept; void * m_resource; deleter_pfn_t m_deleter; template< typename T > resource_deleter_t( T * resource ) : m_resource{ resource } , m_deleter{ [](void * raw_ptr) noexcept { auto r = reinterpret_cast(raw_ptr); delete r; } } {} void operator()() noexcept { (*m_deleter)( m_resource ); } }; /*! * \brief Type of container for user resource deleters. * * \since v.5.2.3 */ using resource_deleter_vector_t = std::vector< resource_deleter_t >; //! Cooperation ID. const coop_id_t m_id; //! Parent coop. /*! * \note * This handle will be empty only for special root-coop. * All other coops will have an actual parent (sometimes in the * form of unvisible to user root-coop). */ coop_handle_t m_parent; //! Default agent to the dispatcher binder. disp_binder_shptr_t m_coop_disp_binder; //! Cooperation agents. agent_array_t m_agent_array; //! SObjectizer Environment for which cooperation is created. outliving_reference_t< environment_t > m_env; //! Count for entities. /*! * Since v.5.2.3 this counter includes: * - count of agents from cooperation; * - count of direct child cooperations; * - usage of cooperation pointer in cooperation registration routine. * * \sa coop_t::increment_usage_count() */ atomic_counter_t m_reference_count{ 0l }; /*! * \brief Notificators for registration event. * * \since v.5.2.3 */ coop_reg_notificators_container_ref_t m_reg_notificators; /*! * \brief Notificators for deregistration event. * * \since v.5.2.3 */ coop_dereg_notificators_container_ref_t m_dereg_notificators; /*! * \brief A lock for synchonization of some operations on coop. * * A new way of handling coop registration was introduced * in v.5.5.8. Agents from the coop cannot start its work before * finish of main coop registration actions (expecialy before * end of such important step as binding agents to dispatchers). * But some agents could receive evt_start event before end of * agents binding step. Those agents will be stopped on this lock. * * Coop acquires this lock before agents binding step and releases * just after that. Every agent tries to acquire it during handling * of evt_start. If lock is belong to coop then an agent will be stopped * until the lock will be released. * * Since v.5.6.0 this lock is also used for protecting changes of * coop's status and sibling's chain. */ std::mutex m_lock; /*! * \brief The registration status of cooperation. * * By default cooperation has NOT_REGISTERED status. * It is changed to REGISTERED after successfull completion * of all registration-specific actions. * * And then changed to DEREGISTERING when m_reference_count * becames zero and final deregistration demand would be * put to deregistration thread. * * \since v.5.2.3 */ registration_status_t m_registration_status{ registration_status_t::coop_not_registered }; /*! * \brief Container of user resource deleters. * * \since v.5.2.3 * */ resource_deleter_vector_t m_resource_deleters; /*! * \brief Deregistration reason. * * Receives actual value only in deregister() method. * * \since v.5.2.3 */ coop_dereg_reason_t m_dereg_reason; /*! * \brief A reaction to non-handled exception. * * By default inherit_exception_reaction is used. It means that * actual exception reaction should be provided by parent coop * or by SO Environment. * * \since v.5.3.0 */ exception_reaction_t m_exception_reaction{ inherit_exception_reaction }; /*! * \brief The head of list of children coops. * * \since v.5.6.0 */ coop_shptr_t m_first_child; /*! * \brief The previous coop in sibling's chain. * * Can be nullptr if this coop is the first coop in the chain. * * \note * This field will be used only by parent coop. * * \since v.5.6.0 */ coop_shptr_t m_prev_sibling; /*! * \brief The next coop in sibling's chain. * * Can be nullptr if this coop is the last coop in the chain. * * \note * This field will be used only by parent coop. * * \since v.5.6.0 */ coop_shptr_t m_next_sibling; /*! * \brief The next coop in the chain for final deregistration actions. * * Since v.5.8.0 an intrusive list is used for holding the chain * of coops for the final deregistration. This field is used for * maintaining this intrusive list. * * This field is nullptr when the coop is registered (or not registered * yet). It may receive the actual value only during the deregistration * procedure (and this field may still be nullptr if there no more * coops that are ready for the final deregistration). * * \since v.5.8.0 */ coop_shptr_t m_next_in_final_dereg_chain; /*! * \brief Increment usage count for the coop. * * In v.5.2.3 the counter m_reference_count is used to * reflect count of references to the cooperation. There are * the following entities who can refer to cooperation: * - agents from that cooperation. When cooperation is successfully * registered the counter is incremented by count of agents. * During cooperation deregistration agents finish their work and * each agent decrement cooperation usage counter; * - children cooperations. Each child cooperation increments * reference counter on its registration and decrements counter * on its deregistration; * - cooperation registration routine. It increment reference counter * to prevent cooperation deregistration before the end of * registration process. It is possible if cooperation do its * work very quickly and initiates deregistration. When cooperation * has coop_notificators its registration process may be longer * then cooperation working time. And cooperation could be * deregistered and even destroyed before return from registration * routine. To prevent this cooperation registration routine * increments cooperation usage counter and the begin of process * and decrement it when registration process finished. */ void increment_usage_count() noexcept { ++m_reference_count; } /*! * \brief Decrement usage count for the coop. * * Note that is usage count is become 0 then final deregistration * actions will be initiated. * * Cooperation deregistration is a long process. All agents * process events out of their queues. When an agent detects that * no more events in its queue it informs the cooperation about this. * * When cooperation detects that all agents have finished their * work it initiates the agent's destruction. * * Since v.5.2.3 this method used not only for agents of cooperation * but and for children cooperations. Because final step of * cooperation deregistration could be initiated only when all * children cooperations are deregistered and destroyed. * * \note * This method is marked as noexcept because there is no way * to recover if any exception is raised here. */ void decrement_usage_count() noexcept { impl::coop_impl_t::do_decrement_reference_count( *this ); } /*! * \brief Add a new child to the parent coop. * * This method is called by child coop. * * \note * This method locks the parent coop object. But under that lock * fields m_prev_sibling and m_next_sibling of the child coop * are modified. * * \since v.5.6.0 */ void add_child( //! Child coop to be added. coop_shptr_t child ) { impl::coop_impl_t::do_add_child( *this, std::move(child) ); } /*! * \brief Remove a child from the parent coop. * * This method is called by child coop. * * \note * This method locks the parent coop object. But under that lock * fields m_prev_sibling and m_next_sibling of the child coop * are modified. * * \note * In contradiction to add_child() this method receives a reference * to child coop. This allows to avoid construction of temporary * shared_ptr object. * * \since v.5.6.0 */ void remove_child( //! Child coop to be removed. coop_t & child ) noexcept { impl::coop_impl_t::do_remove_child( *this, child ); } /*! * \brief A helper method for doing some actions with children coops. * * This method is intended to be used in derived classes where an * access to m_first_child, m_prev_sibling and m_next_sibling from * another coop object will be prohibited. * * \since v.5.6.0 */ template< typename Lambda > void for_each_child( Lambda && lambda ) const { auto * child = m_first_child.get(); while( child ) { lambda( *child ); child = child->m_next_sibling.get(); } } public: //NOTE: this method is defined after all protected stuff //to avoid compilation error with GCC-9. /*! * \brief Return the default dispatcher binder for the coop. * * \code * env.introduce_coop( * // (1) Make the default dispatcher for the coop. * so_5::disp::active_obj::make_dispatcher( env ).binder(), * []( so_5::coop_t & coop ) { * auto binder = coop_disp_binder(); // Will get binder created at (1). * ... * } ); * \endcode * * \since v.5.8.1 */ [[nodiscard]] disp_binder_shptr_t coop_disp_binder() const noexcept( noexcept( disp_binder_shptr_t{ this->m_coop_disp_binder } ) ) { return m_coop_disp_binder; } }; // // coop_unique_holder_t // /*! * \brief A special type that plays role of unique_ptr for coop. * * In previous versions of SObjectizer std::unique_ptr was used for * holding coop before the coop will be passed to register_coop. * But in v.5.6 shared_ptr should be used for holding a pointer * to a coop object (otherwise a call to coop_t::shared_from_this() * can throw std::bad_weak_ptr in some cases). * * Class coop_unique_holder_t is an replacement for coop_unique_ptr_t * from previous versions of SObjectizer, but it holds shared_ptr inside. * * \since v.5.6.0 */ class coop_unique_holder_t { friend class impl::coop_private_iface_t; //! A pointer to coop object. coop_shptr_t m_coop; coop_shptr_t release() noexcept { return std::move(m_coop); } public : coop_unique_holder_t() = default; coop_unique_holder_t( coop_shptr_t coop ) : m_coop{ std::move(coop) } {} coop_unique_holder_t( const coop_unique_holder_t & ) = delete; coop_unique_holder_t( coop_unique_holder_t && ) = default; coop_unique_holder_t & operator=( const coop_unique_holder_t & ) = delete; coop_unique_holder_t & operator=( coop_unique_holder_t && ) noexcept = default; friend void swap( coop_unique_holder_t & a, coop_unique_holder_t & b ) noexcept { using std::swap; swap( a.m_coop, b.m_coop ); } operator bool() const noexcept { return static_cast(m_coop); } bool operator!() const noexcept { return !m_coop; } coop_t * get() const noexcept { return m_coop.get(); } coop_t * operator->() const noexcept { return m_coop.get(); } coop_t & operator*() const noexcept { return *m_coop.get(); } }; } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/coop_handle.hpp000066400000000000000000000127131523065210100204630ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Type of smart handle to coop. * * \since * v.5.6.0 */ #if !defined( SO_5_COOP_HANDLE_HPP ) #define SO_5_COOP_HANDLE_HPP #include #include #include #include #include #include namespace so_5 { class coop_t; //! Typedef for the agent_coop smart pointer. using coop_shptr_t = std::shared_ptr< coop_t >; class coop_handle_t; namespace low_level_api { coop_shptr_t to_shptr( const coop_handle_t & ); coop_shptr_t to_shptr_noexcept( const coop_handle_t & ) noexcept; } /* namespace low_level_api */ // // coop_handle_t // /*! * \brief Type of smart handle for a cooperation. * * Type coop_handle_t is used for references to registered coops. * Before v.5.6 coops in SObjectizer-5 were identified via strings. * Every coop had its own name. * * Since v.5.6.0 names are no more used for identificators of coops. * SObjectizer's environment_t returns instances of coop_handle_t for * every registered coop. This handle can be used for deregistration * of the coop at the appropriate time. For example: * \code * class request_manager_t final : public so_5::agent_t { * std::map active_requests_; * ... * void on_new_request(mhood_t cmd) { * // Create a new coop for handling that request. * auto coop = so_5::create_child_coop( *this ); * ...; // Fill the coop with agents. * // Register coop and store its handle in the map of actual requests. * active_requests_[cmd->request_id()] = so_environment().register_coop(std::move(coop)); * } * ... * void on_cancel_request(mhood_t cmd) { * auto it = active_requests_.find(cmd->request_id()); * if(it != active_requests_.end()) * // Deregister coop for this request. * so_environment().deregister_coop(it->second); * } * }; * \endcode * * Note that coop_handle_t is somewhat like (smart)pointer. It can be empty, * e.g. do not point to any coop. Or it can be not-empty. In that case * coop_handle_t is very similar to std::weak_ptr. * * \since * v.5.6.0 */ class coop_handle_t { friend class so_5::coop_t; friend coop_shptr_t low_level_api::to_shptr( const coop_handle_t & ); friend coop_shptr_t low_level_api::to_shptr_noexcept( const coop_handle_t & ) noexcept; //! ID of cooperation. coop_id_t m_id; //! Pointer for cooperation. /*! * \attention * This is a weak pointer. It means that it can refer to already * destroyed cooperation. */ std::weak_ptr< coop_t > m_coop; //! Initializing constructor. coop_handle_t( coop_id_t id, std::shared_ptr< coop_t > coop ) : m_id{ id }, m_coop{ coop } {} public : static constexpr const coop_id_t invalid_coop_id = 0u; coop_handle_t() : m_id{ invalid_coop_id } {} //! Is this handle empty? /*! * Handle is empty if there is no underlying coop. * * \retval true if handle is not empty */ operator bool() const noexcept { return invalid_coop_id != m_id; } //! Is this non-empty handle? /*! * Handle is empty if there is no underlying coop. * * \retval true if handle is empty */ bool operator!() const noexcept { return invalid_coop_id == m_id; } //! Get the ID of the coop. auto id() const noexcept { return m_id; } //! A tool for dumping coop_handle to ostream. friend inline std::ostream & operator<<( std::ostream & to, const coop_handle_t & what ) { if( what ) return (to << "{coop:id=" << what.m_id << "}"); else return (to << "{empty-coop-handle}"); } friend inline bool operator==( const coop_handle_t & a, const coop_handle_t & b ) noexcept { return a.m_id == b.m_id; } friend inline bool operator!=( const coop_handle_t & a, const coop_handle_t & b ) noexcept { return a.m_id != b.m_id; } friend inline bool operator<( const coop_handle_t & a, const coop_handle_t & b ) noexcept { return a.m_id < b.m_id; } friend inline bool operator<=( const coop_handle_t & a, const coop_handle_t & b ) noexcept { return a.m_id <= b.m_id; } friend inline bool operator>( const coop_handle_t & a, const coop_handle_t & b ) noexcept { return a.m_id > b.m_id; } friend inline bool operator>=( const coop_handle_t & a, const coop_handle_t & b ) noexcept { return a.m_id >= b.m_id; } }; namespace low_level_api { /*! * \brief A helper function for safe extraction of shared_ptr to * coop from coop_handle instance. * * \note * This function throws if coop object is already destroyed. * * \attention * This is a part of low-level SObjectizer's API. * * \since * v.5.6.0 */ [[nodiscard]] inline coop_shptr_t to_shptr( const coop_handle_t & handle ) { auto result = handle.m_coop.lock(); if( !result ) SO_5_THROW_EXCEPTION( rc_coop_already_destroyed, "coop object already destroyed, coop_id=" + std::to_string(handle.m_id) ); return result; } /*! * \brief A helper function for unsafe extraction of shared_ptr to * coop from coop_handle instance. * * \note * This function don't throws if coop object is already destroyed. * An empty shared_ptr is returned instead. * * \attention * This is a part of low-level SObjectizer's API. * * \since * v.5.6.0 */ [[nodiscard]] inline coop_shptr_t to_shptr_noexcept( const coop_handle_t & handle ) noexcept { return handle.m_coop.lock(); } } /* namespace low_level_api */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/coop_listener.hpp000066400000000000000000000040301523065210100210460ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Interface for the cooperation listener definition. */ #if !defined( SO_5_COOP_LISTENER_HPP ) #define SO_5_COOP_LISTENER_HPP #include #include #include #include namespace so_5 { // // coop_listener_t // //! Interface for the cooperation listener. /*! * Cooperation listener is intended for observation moments of * cooperation registrations and deregistrations. * * \attention SObjectizer doesn't synchronize calls to the * on_registered() and on_deregistered(). If this is a problem * then programmer should take care about the object's thread safety. */ class SO_5_TYPE coop_listener_t { // Note: clang-3.9 requires this on Windows platform. coop_listener_t( const coop_listener_t & ) = delete; coop_listener_t( coop_listener_t && ) = delete; coop_listener_t & operator=( const coop_listener_t & ) = delete; coop_listener_t & operator=( coop_listener_t && ) = delete; public: coop_listener_t() = default; virtual ~coop_listener_t() noexcept = default; //! Hook for the cooperation registration event. /*! * Method will be called right after the successful * cooperation registration. * * \note * Since v.5.6.0 this method is noexcept! */ virtual void on_registered( //! SObjectizer Environment. environment_t & so_env, //! Cooperation which was registered. const coop_handle_t & coop ) noexcept = 0; //! Hook for the cooperation deregistration event. /*! * Method will be called right after full cooperation deregistration. * * \note * Since v.5.6.0 this method is noexcept! */ virtual void on_deregistered( //! SObjectizer Environment. environment_t & so_env, //! Cooperation which was registered. const coop_handle_t & coop, //! Reason of deregistration. const coop_dereg_reason_t & reason ) noexcept = 0; }; //! Typedef for the coop_listener autopointer. using coop_listener_unique_ptr_t = std::unique_ptr< coop_listener_t >; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/current_thread_id.hpp000066400000000000000000000020501523065210100216660ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.4.0 * * \file * \brief A workaround for very slow implementation of * std::this_thread::get_id() under some compilers. * * \note * This header file was actual when too old versions of VC++ compilers * were used. Now it is here for compatibility reasons. */ #if !defined( SO_5_CURRENT_THREAD_ID_HPP ) #define SO_5_CURRENT_THREAD_ID_HPP // For the normal implementations use the standard tools. #include namespace so_5 { //! Type of the current thread id. using current_thread_id_t = std::thread::id; //! Get the ID of the current thread. inline current_thread_id_t query_current_thread_id() { return std::this_thread::get_id(); } //! Get NULL thread id. inline current_thread_id_t null_current_thread_id() { return std::thread::id(); } /*! * \brief Get the raw thread id from current_thread_id. * * \since * v.5.5.18 */ inline std::thread::id raw_id_from_current_thread_id( const current_thread_id_t & w ) { return w; } } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/custom_mbox.hpp000066400000000000000000000045311523065210100205460ustar00rootroot00000000000000/* * SObjectizer 5 */ /*! * \file * \brief Various stuff required for custom mboxes. * * \since * v.5.5.19.2 */ #if !defined( SO_5_CUSTOM_MBOX_HPP ) #define SO_5_CUSTOM_MBOX_HPP #include #include #include namespace so_5 { // // mbox_creation_data_t // /*! * \brief An information which is necessary for creation of a new mbox. * * \since * v.5.5.19.2 */ struct mbox_creation_data_t { //! Environment for which the mbox is created. outliving_reference_t< environment_t > m_env; //! ID for a new mbox. mbox_id_t m_id; //! Stuff to be used for message delivery tracing. outliving_reference_t< msg_tracing::holder_t > m_tracer; //! Initializing constructor. mbox_creation_data_t( outliving_reference_t< environment_t > env, mbox_id_t id, outliving_reference_t< msg_tracing::holder_t > tracer ) : m_env(env) , m_id(id) , m_tracer(tracer) {} }; namespace custom_mbox_details { #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnon-virtual-dtor" #endif // // creator_iface_t // /*! * \brief Interface for creator of new mbox in OOP style. * * This interface is necessary to hide details of creation of custom mbox. * * \since * v.5.5.19.2 */ class creator_iface_t { protected : // Non-virtual destructor to prevent creation of dynamically // allocated objects of that type. ~creator_iface_t() = default; public : //! Creation of custom mbox. virtual mbox_t create( //! An environment-specific data to be used for new mbox. const mbox_creation_data_t & data ) = 0; }; // // creator_template_t // /*! * \brief Template-based implementation of creator interface. * * \tparam Lambda type of actual lambda with all creation actions. * * \since * v.5.5.19.2 */ template< typename Lambda > class creator_template_t : public creator_iface_t { //! Lambda or functional object to be used for custom mbox creation. Lambda m_lambda; public : creator_template_t( Lambda && lambda ) : m_lambda(std::move(lambda)) {} ~creator_template_t() = default; virtual mbox_t create( const mbox_creation_data_t & data ) override { return m_lambda( data ); } }; #if defined(__clang__) #pragma clang diagnostic pop #endif } /* namespace custom_mbox_details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/declspec.hpp000066400000000000000000000023371523065210100177730ustar00rootroot00000000000000/* SObjectizer 5. */ #if !defined( SO_5_DECLSPEC_HPP ) #define SO_5_DECLSPEC_HPP #if defined( _MSC_VER ) /* ** Visual C++ */ #define SO_5_EXPORT __declspec(dllexport) #define SO_5_IMPORT __declspec(dllimport) #endif /* _MSC_VER */ #if defined( __MINGW32__ ) || defined( __WINGW64__ ) /* ** MinGW C++ */ #define SO_5_EXPORT __declspec(dllexport) #define SO_5_IMPORT __declspec(dllimport) #endif /* __MINGW32__ */ #if !defined( _WIN32 ) #define SO_5_EXPORT __attribute__ ((visibility ("default"))) #define SO_5_IMPORT __attribute__ ((visibility ("default"))) #endif // For the cases when export/import not needed. #if !defined( SO_5_EXPORT ) #define SO_5_EXPORT #endif #if !defined( SO_5_IMPORT ) #define SO_5_IMPORT #endif #if defined( SO_5_PRJ ) #if defined( SO_5_STATIC_LIB ) // Compilation of SO-5 as static library. #define SO_5_TYPE #define SO_5_FUNC #else // Compilation of SO-5 as dynamic library. #define SO_5_TYPE SO_5_EXPORT #define SO_5_FUNC SO_5_EXPORT #endif #else #if defined( SO_5_STATIC_LIB ) // Use of SO-5 as static library. #define SO_5_TYPE #define SO_5_FUNC #else // Use of SO-5 as dynamic library. #define SO_5_TYPE SO_5_IMPORT #define SO_5_FUNC SO_5_IMPORT #endif #endif #endif sobjectizer-5.8.6/dev/so_5/details/000077500000000000000000000000001523065210100171205ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/details/abort_on_fatal_error.hpp000066400000000000000000000024061523065210100240160ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.6 * * \file * \brief Helpers for aborting the whole application in the * case of a fatal error. */ #if !defined( SO_5_DETAILS_ABORT_ON_FATAL_ERROR_HPP ) #define SO_5_DETAILS_ABORT_ON_FATAL_ERROR_HPP #include #include #include namespace so_5 { namespace details { /*! * \since * v.5.5.6 * * \file * \brief Helper for aborting the whole application in the * case of a fatal error. * * \tparam L type of lambda with logging action. Note that this * lambda must not throws exceptions. * * \par Usage example \code if( coop_t::COOP_NOT_REGISTERED == m_agent_coop->m_registration_status ) so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( so_environment(), log_stream ) { log_stream << "Unexpected error: agent_coop has status " "COOP_NOT_REGISTERED at demand_handler_on_start. " "Cooperation: " << m_agent_coop->query_coop_name() << ". Application will be aborted" << std::endl; } } ); \endcode * */ template< typename L > void abort_on_fatal_error( L logging_lambda ) noexcept { invoke_noexcept_code( std::move( logging_lambda ) ); std::abort(); } } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/always_false.hpp000066400000000000000000000010331523065210100223000ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A simple metafunction that always "returns" false. * * \since * v.5.6.0 */ #if !defined( SO_5_DETAILS_ALWAYS_FALSE_HPP ) #define SO_5_DETAILS_ALWAYS_FALSE_HPP namespace so_5 { namespace details { /*! * \brief A simple metafunction that always "returns" false. * * Can be used to trigger static_assert. * * \since * v.5.6.0 */ template struct always_false { static constexpr const bool value = false; }; } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/at_scope_exit.hpp000066400000000000000000000023171523065210100224620ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A simple implementation of %at_scope_exit concept. * * \since * v.5.5.16 */ #if !defined( SO_5_DETAILS_AT_SCOPED_EXIT_HPP ) #define SO_5_DETAILS_AT_SCOPED_EXIT_HPP #include namespace so_5 { namespace details { namespace scope_exit_details { /*! * \brief Helper class for scope exit implementation. * \since * v.5.5.16 */ template< typename L > class at_exit_t { L m_lambda; public : at_exit_t( L && l ) : m_lambda{ std::forward(l) } {} at_exit_t( at_exit_t && o ) : m_lambda{ std::move(o.m_lambda) } {} ~at_exit_t() { m_lambda(); } }; } /* namespace scope_exit_details */ /*! * \brief Helper function for creation action to be performed at scope exit. * * Usage example: * \code if( needs_wait ) { m_threads_to_wakeup += 1; auto decrement_threads = at_scope_exit( [&m_threads_to_wakeup] { --m_threads_to_wakeup; } ); m_sleep_cond.wait_for( some_time, some_predicate ); } * \endcode * * \since * v.5.5.16 */ template< typename L > scope_exit_details::at_exit_t< L > at_scope_exit( L && l ) { return scope_exit_details::at_exit_t{ std::forward(l) }; } } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/has_thread_sanitizer.hpp000066400000000000000000000004531523065210100240250ustar00rootroot00000000000000/* * SObjectizert-5 */ /// @file /// @brief Checking presence of thread sanitizer. /// /// @since v.5.8.6 #if !defined( SO_5_DETAILS_HAS_THREAD_SANITIZER_HPP ) #define SO_5_DETAILS_HAS_THREAD_SANITIZER_HPP #if defined( __SANITIZE_THREAD__ ) #define SO_5_HAS_THREAD_SANITIZER 1 #endif #endif sobjectizer-5.8.6/dev/so_5/details/invoke_noexcept_code.hpp000066400000000000000000000011651523065210100240260ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.6 * * \file * \brief Helpers for invoking code which must not throws. */ #if !defined( SO_5_DETAILS_INVOKE_NOEXCEPT_CODE_HPP ) #define SO_5_DETAILS_INVOKE_NOEXCEPT_CODE_HPP #include namespace so_5 { namespace details { /*! * \since * v.5.5.6 * * \file * \brief Helpers for invoking code which must not throws. * * \tparam L type of lambda with main code to be invoked. */ template< typename L > decltype(auto) invoke_noexcept_code( L lambda ) noexcept { return std::forward(lambda)(); } } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/ios_helpers.hpp000066400000000000000000000030711523065210100221460ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.12 * * \file * \brief Various helpers for working with C++ iostreams. */ #if !defined( SO_5_DETAILS_IOS_HELPERS_HPP ) #define SO_5_DETAILS_IOS_HELPERS_HPP #include #include #include namespace so_5 { namespace details { namespace ios_helpers { /*! * \since * v.5.5.4 * * \brief Helper for showing only part of long string. */ struct length_limited_string { const std::string_view m_what; const std::size_t m_limit; length_limited_string( const std::string_view what, std::size_t limit ) : m_what( what ) , m_limit( limit ) {} }; inline std::ostream & operator<<( std::ostream & to, const length_limited_string & v ) { if( v.m_what.size() > v.m_limit ) { const std::size_t median = v.m_limit / 2; to << v.m_what.substr( 0, median ) << "..." << v.m_what.substr( v.m_what.size() - median + 3 ); } else to << v.m_what; return to; } /*! * \since * v.5.5.4 * * \brief Helper for showing pointer value. * * \note * It's safe to pass a nullptr to `pointer` instance. */ struct pointer { const void * m_what; pointer( const void * what ) : m_what{ what } {} }; inline std::ostream & operator<<( std::ostream & to, const pointer & v ) { const auto oldf = to.setf( std::ios_base::hex, std::ios_base::basefield ); to << "0x" << reinterpret_cast(v.m_what); to.setf( oldf, std::ios_base::basefield ); return to; } } /* namespace ios_helpers */ } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/lambda_traits.hpp000066400000000000000000000123041523065210100224370ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.4 * * \brief Lambda-related type traits. */ #if !defined( SO_5_DETAILS_LAMBDA_TRAITS_HPP ) #define SO_5_DETAILS_LAMBDA_TRAITS_HPP #include namespace so_5 { namespace details { namespace lambda_traits { /*! * \brief Detector of plain type without const/volatile modifiers. */ template< typename M > struct plain_argument_type { using type = typename std::remove_cv< typename std::remove_reference< M >::type >::type; }; /*! * \brief Detector of lambda result and argument type. */ template< typename L > struct traits : public traits< decltype(&L::operator()) > { //! Type to be used to pass lambda as argument to another function. /*! * \since * v.5.5.22 */ using pass_by_type = L; }; /*! * \brief Specialization of lambda traits for const-lambda. */ template< class L, class R, class M > struct traits< R (L::*)(M) const > { //! Type to be used to pass lambda as argument to another function. /*! * \since * v.5.5.22 */ using pass_by_type = R(L::*)(M) const; //! Type of lambda result value. using result_type = R; //! Type of lambda argument. using argument_type = typename plain_argument_type< M >::type; //! Helper for calling a lambda. static R call_with_arg( L l, M m ) { return l(m); } }; /*! * \brief Specialization of lambda traits for mutable lambda. */ template< class L, class R, class M > struct traits< R (L::*)(M) > { //! Type to be used to pass lambda as argument to another function. /*! * \since * v.5.5.22 */ using pass_by_type = R(L::*)(M); //! Type of lambda result value. using result_type = R; //! Type of lambda argument. using argument_type = typename plain_argument_type< M >::type; //! Helper for calling a lambda. static R call_with_arg( L l, M m ) { return l(m); } }; /*! * \brief Specialization of lambda traits for const-lambda without argument. */ template< class L, class R > struct traits< R (L::*)() const > { //! Type to be used to pass lambda as argument to another function. /*! * \since * v.5.5.22 */ using pass_by_type = R(L::*)() const; //! Type of lambda result value. using result_type = R; //! Helper for calling a lambda. static R call_without_arg( L l ) { return l(); } }; /*! * \brief Specialization of lambda traits for mutable lambda without argument. */ template< class L, class R > struct traits< R (L::*)() > { //! Type to be used to pass lambda as argument to another function. /*! * \since * v.5.5.22 */ using pass_by_type = R(L::*)(); //! Type of lambda result value. using result_type = R; //! Helper for calling a lambda. static R call_without_arg( L l ) { return l(); } }; /*! * \brief Specialization of lambda traits for ordinary function pointer. */ template< class R, class M > struct traits< R(*)(M) > { //! Type to be used to pass lambda as argument to another function. /*! * \since * v.5.5.22 */ using pass_by_type = R(*)(M); //! Type of lambda result value. using result_type = R; //! Type of lambda argument. using argument_type = typename plain_argument_type< M >::type; //! Helper for calling a lambda. static R call_with_arg( R (*l)(M), M m ) { return l(m); } }; /*! * \brief Specialization of lambda traits for reference to ordinary function. */ template< class R, class M > struct traits< R(&)(M) > { //! Type to be used to pass lambda as argument to another function. /*! * \since * v.5.5.22 */ using pass_by_type = R(*)(M); //! Type of lambda result value. using result_type = R; //! Type of lambda argument. using argument_type = typename plain_argument_type< M >::type; //! Helper for calling a lambda. static R call_with_arg( R (*l)(M), M m ) { return l(m); } }; // Please note: there are no traits for R(*)() and R(&)(). // It is because argument-less handlers is going to be disabled in // the next major version of SObjectizer. namespace impl { /*! * \brief A checker for lambda likeness. */ template< typename L, typename = void > struct has_func_call_operator : public std::false_type {}; template< typename L > struct has_func_call_operator< L, std::void_t< decltype(&L::operator()) > > : public std::true_type {}; /*! * \brief A detector of lambda argument type if the checked type is lambda. */ template< bool is_lambda, class L > struct argument_if_lambda {}; /*! * \brief A specialization of lambda argument detector for the case * when the checked type is a lambda. */ template< class L > struct argument_if_lambda< true, L > { using type = typename traits< L >::argument_type; }; } /* namespace impl */ /*! * \brief A detector of lambda argument type if the checked type is lambda. */ template< class L > using argument_type_if_lambda = impl::argument_if_lambda< impl::has_func_call_operator< L >::value, L >; /*! * \brief A detector that type is a lambda or functional object. * * \since * v.5.5.20 */ template struct is_lambda { static constexpr const bool value = impl::has_func_call_operator::value; }; } /* namespace lambda_traits */ } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/remaining_time_counter.hpp000066400000000000000000000034771523065210100243720ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helper class for calculating remaining time. * * \since * v.5.5.16 */ #if !defined( SO_5_REMAINING_TIME_COUNTER_HPP ) #define SO_5_REMAINING_TIME_COUNTER_HPP #include namespace so_5 { namespace details { /*! * \brief Helper class for calculating remaining time. * * Usage example: * \code so_5::details::remaining_time_counter_t time_counter{ wait_time }; do { some_task( time_counter.remaining() ); time_counter.update(); } while( time_counter ); * \endcode * * \since * v.5.5.16 */ class remaining_time_counter_t { using time_point = std::chrono::steady_clock::time_point; using duration = std::chrono::steady_clock::duration; /*! * \note This member in not declared as const to allow reassignment * for remaining_time_counter_t object. Like in such cases: * \code remaining_time_counter_t counter{ some_initial_value }; do { do_something(); if( needs_restart ) counter = remaining_time_counter_t{ some_initial_value }; else counter.update(); } while( counter ); * \endcode */ time_point m_last_time; duration m_remaining; public : remaining_time_counter_t( duration remaining ) : m_last_time{ std::chrono::steady_clock::now() } , m_remaining{ remaining } {} void update() { const auto now = std::chrono::steady_clock::now(); const auto elapsed = now - m_last_time; m_last_time = now; if( elapsed < m_remaining ) m_remaining -= elapsed; else m_remaining = duration::zero(); } duration remaining() const { return m_remaining; } /*! * \return true if m_remaining is greater than duration::zero(). */ operator bool() const { return duration::zero() < m_remaining; } }; } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/rollback_on_exception.hpp000066400000000000000000000041161523065210100241760ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief Helpers for do rollback actions in the case of exception. */ #if !defined( SO_5_DETAILS_ROLLBACK_ON_EXCEPTION_HPP ) #define SO_5_DETAILS_ROLLBACK_ON_EXCEPTION_HPP namespace so_5 { namespace details { namespace rollback_on_exception_details { /*! * \since * v.5.5.4 * * \brief Helper template class for do rollback actions automatically * in the destructor. * * \tparam L type of lambda with rollback actions. */ template< typename L > class rollbacker_t { L & m_action; bool m_commited = false; public : inline rollbacker_t( L & action ) : m_action( action ) {} inline ~rollbacker_t() { if( !m_commited ) m_action(); } inline void commit() { m_commited = true; } }; template< typename Result, typename Main_Action, typename Rollback_Action > struct executor { static Result exec( Main_Action main_action, rollbacker_t< Rollback_Action > & rollback ) { auto r = main_action(); rollback.commit(); return r; } }; template< typename Main_Action, typename Rollback_Action > struct executor< void, Main_Action, Rollback_Action > { static void exec( Main_Action main_action, rollbacker_t< Rollback_Action > & rollback ) { main_action(); rollback.commit(); } }; } /* namespace rollback_on_exception_details */ /*! * \since * v.5.5.4 * * \brief Helper function for do some action with rollback in the case of * an exception. * * \tparam Main_Action type of lambda with main action. * \tparam Rollback_Action type of lambda with rollback action. */ template< typename Main_Action, typename Rollback_Action > auto do_with_rollback_on_exception( Main_Action main_action, Rollback_Action rollback_action ) -> decltype(main_action()) { using result_type = decltype(main_action()); using namespace rollback_on_exception_details; rollbacker_t< Rollback_Action > rollbacker{ rollback_action }; return executor< result_type, Main_Action, Rollback_Action >::exec( main_action, rollbacker ); } } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/safe_cv_wait_for.hpp000066400000000000000000000033331523065210100231330ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helpers for safe calls to condition_variable::wait_for with * big timeout intervals. * * \since * v.5.7.2 */ #if !defined( SO_5_DETAILS_SAFE_CV_WAIT_FOR_HPP ) #define SO_5_DETAILS_SAFE_CV_WAIT_FOR_HPP #include #include namespace so_5 { namespace details { /*! * \brief Helper function for safe call of condition_variable::wait_for * with possible big timeouts. * * In some places of SObjectizer's code big values of wait-time can * be used for std::condition_variable::wait_for. For example, * that values can be produced by so_5::infinite_wait_special_timevalue() * function. * * If such big value will be passed to std::condition_variable::wait_for * the wait_for can return immediately without any waiting. * * To avoid that behavior this helper function should be used instead * of the direct call to condition_variable::wait_for. * * \since * v.5.7.2 */ template< typename Predicate > bool wait_for_big_interval( std::unique_lock< std::mutex > & lock, std::condition_variable & cv, std::chrono::steady_clock::duration timeout, Predicate pred ) { constexpr std::chrono::hours border{24}; if( timeout <= border ) { return cv.wait_for( lock, timeout, pred ); } else { remaining_time_counter_t counter{ timeout }; while( counter ) { const auto r = counter.remaining(); const auto wait_time = (r <= border ? r : border); const auto wait_result = cv.wait_for( lock, wait_time, pred ); if( wait_result ) return wait_result; else counter.update(); } } return false; } } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/suppress_exceptions.hpp000066400000000000000000000013021523065210100237520ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.6.2 * * \file * \brief Helpers for invoking code with suppressing exceptions. */ #if !defined( SO_5_DETAILS_SUPPRESS_EXCEPTIONS_HPP ) #define SO_5_DETAILS_SUPPRESS_EXCEPTIONS_HPP namespace so_5 { namespace details { /*! * @brief Helper function for execution a block of code with * suppression of any exceptions raised inside that block. * * All exceptions are simply intercepted. Nothing is logged in the * case of an exception thrown. * * @since v.5.6.2 */ template< typename Lambda > void suppress_exceptions( Lambda && lambda ) noexcept { try { lambda(); } catch( ... ) {} } } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/sync_helpers.hpp000066400000000000000000000063071523065210100223350ustar00rootroot00000000000000/* * SObjectizert-5 */ /*! * \file * \brief Basic tools for simplify usage of std::mutex or null_mutex * * \since v.5.5.19 */ #if !defined( SO_5_DETAILS_SYNC_HELPERS_HPP ) #define SO_5_DETAILS_SYNC_HELPERS_HPP #include namespace so_5 { // // null_mutex_t // /*! * \brief A class which is like std::mutex but does not do any real actions. * * \note * Since v.5.8.3 it can also be used instead of std::shared_mutex because * empty method lock_shared() and unlock_shared() were added in v.5.8.3. * * \since v.5.5.19.2 */ class null_mutex_t { public : null_mutex_t() {} null_mutex_t( const null_mutex_t & ) = delete; null_mutex_t( null_mutex_t && ) = delete; void lock() {} void unlock() {} /*! * \since v.5.8.3 */ void lock_shared() {} /*! * \since v.5.8.3 */ void unlock_shared() {} }; namespace details { // // actual_lock_holder_t // /*! * \brief A class to be used as mixin with actual std::mutex instance inside. * * Usage example: * \code template< typename Lock_Holder > class coop_repo_t final : protected Lock_Holder { public : bool has_live_coop() { this->lock_and_perform([&]{ return !m_coops.empty(); }); } }; using mtsafe_coop_repo_t = coop_repo_t< so_5::details::actual_lock_holder_t >; * \endcode * * \tparam Lock_Type type of lock to be used for object protection. * Will be used with std::lock_guard. * * \since v.5.5.19 */ template< typename Lock_Type = std::mutex > class actual_lock_holder_t { //! Actual lock. /*! * \note * This is mutable attibute because locking can be necessary * even in const methods of derived classes. */ mutable Lock_Type m_lock; public : //! Do actual lock and perform necessary action. template< typename Lambda > auto lock_and_perform( Lambda && l ) const -> decltype(l()) { std::lock_guard< Lock_Type > lock{ m_lock }; return l(); } }; // // no_lock_holder_t // /*! * \brief A class to be used as mixin without any real mutex instance inside. * * Usage example: * \code template< typename Lock_Holder > class coop_repo_t final : protected Lock_Holder { public : bool has_live_coop() { this->lock_and_perform([&]{ return !m_coops.empty(); }); } }; using non_mtsafe_coop_repo_t = coop_repo_t< so_5::details::no_lock_holder_t >; * \endcode * * \since v.5.5.19 */ class no_lock_holder_t { public : //! Perform necessary action. template< typename Lambda > auto lock_and_perform( Lambda && l ) const -> decltype(l()) { return l(); } }; // // lock_holder_detector // /*! * \brief A selector of actual lock_holder type in dependency of lock type. * * Usage example: * \code * template< typename Lock_Type > * class my_thread_safe_class * : public so_5::details::lock_holder_detector::type * { * void some_method() { * this->lock_and_perform( [&] { * ... // Some actions. * } ); * } * }; * \endcode * * \since v.5.5.19.2 */ template< typename Lock_Type > struct lock_holder_detector { using type = actual_lock_holder_t; }; template<> struct lock_holder_detector { using type = no_lock_holder_t; }; } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/details/tsan_friendly_lock_guard.hpp000066400000000000000000000033131523065210100246640ustar00rootroot00000000000000/* * SObjectizer-5 */ /// @file /// @brief A special wrapper around std::lock_guard to make TSan happy. /// /// @since v.5.8.6 #if !defined( SO_5_DETAILS_TSAN_FRIENDLY_LOCK_GUARD_HPP ) #define SO_5_DETAILS_TSAN_FRIENDLY_LOCK_GUARD_HPP #include #include #if defined( SO_5_HAS_THREAD_SANITIZER ) #include #endif namespace so_5::details { #if defined( SO_5_HAS_THREAD_SANITIZER ) /// Workaround for a strange behaviour in thread sanitizer. /// /// Should be used instead of `std::lock_guard` in places where /// thread sanitizer report a possible deadlock because of lock /// order inversion. /// /// Uses a loop with mutex::try_lock and this_thread::yield instead of /// plain mutex::lock method. In such a case TSan doesn't report this /// warning. /// /// @sa https://github.com/llvm/llvm-project/issues/57955 template< typename M > class tsan_friendly_lock_guard_t { M & m_mutex; public: static void do_lock( M & mutex ) { // Repeat attempts until we successed. while( !mutex.try_lock() ) { std::this_thread::yield(); } } explicit tsan_friendly_lock_guard_t( M & mutex ) : m_mutex{ mutex } { do_lock( m_mutex ); } ~tsan_friendly_lock_guard_t() { m_mutex.unlock(); } }; #else /// Actual lock guard for normal cases. /// /// It's just a thin wrapper around std::lock_guard. /// /// @since v.5.8.6 template< typename M > class tsan_friendly_lock_guard_t { std::lock_guard< M > m_guard; public: static void do_lock( M & mutex ) { mutex.lock(); } explicit tsan_friendly_lock_guard_t( M & mutex ) : m_guard{ mutex } {} }; #endif } /* namespace so_5::details */ #endif sobjectizer-5.8.6/dev/so_5/detect_os.hpp000066400000000000000000000017751523065210100201670ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helper macros for detection of compiler/platform. * * \since v.5.8.2 */ #if !defined( SO_5_DETECT_OS_HPP ) #define SO_5_DETECT_OS_HPP /*! * \def SO_5_OS_WIN32 * \brief A marker for Win32 platform. * * \note This marker is also defined for Win64 platform. */ /*! * \def SO_5_OS_WIN64 * \brief A marker for Win64 platform. */ /*! * \def SO_5_OS_WINDOWS * \brief A marker for Windows platform. * * Defined if SO_5_OS_WIN32 or SO_5_OS_WIN64 are defined. */ /*! * \def SO_5_OS_UNIX * \brief A marker for Unix platforms, but not macOS/iOS. */ /*! * \def SO_5_OS_APPLE * \brief A marker for macOS/iOS. */ #if defined( _WIN64 ) #define SO_5_OS_WIN64 #endif #if defined( _WIN32 ) #define SO_5_OS_WIN32 #endif #if defined( SO_5_OS_WIN32 ) || defined( SO_5_OS_WIN64 ) #define SO_5_OS_WINDOWS #endif #if defined(unix) || defined(__unix__) || defined(__unix) #define SO_5_OS_UNIX #endif #if defined(__APPLE__) #define SO_5_OS_APPLE #endif #endif sobjectizer-5.8.6/dev/so_5/disp/000077500000000000000000000000001523065210100164325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/abstract_work_thread.cpp000066400000000000000000000055461523065210100233440ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Interfaces for work_thread and work_thread's factory. * \since v.5.7.3 */ #include #include #include namespace so_5::disp { // // abstract_work_thread_t // abstract_work_thread_t::abstract_work_thread_t() {} abstract_work_thread_t::~abstract_work_thread_t() {} // // abstract_work_thread_factory_t // abstract_work_thread_factory_t::abstract_work_thread_factory_t() {} abstract_work_thread_factory_t::~abstract_work_thread_factory_t() {} namespace std_work_thread_impl { // // std_work_thread_t // /*! * \brief The standard implementation of abstract_work_thread interface. * * It uses std::thread without any additiona tuning. * An actual instance of thread is created in start() and then joined only * in join(). * * \note * This implementation assumes that start() will be called before join(), and if * start() was called then someone must call join() before the destruction * of the object. * * \since v.5.7.3 */ class std_work_thread_t : public abstract_work_thread_t { //! Actual thread. std::thread m_thread; public: std_work_thread_t() = default; void start( body_func_t thread_body ) override { m_thread = std::thread{ [tb = std::move(thread_body)] { // All exceptions have to be intercepted and suppressed. so_5::details::suppress_exceptions( [&tb]() { tb(); } ); } }; } void join() override { // Do not check m_thread status because join() has to be called // only once and only if there was previous call to start(), // and that call was successful. m_thread.join(); } }; // // std_work_thread_factory_t // /*! * \brief The standard implementation of abstract_work_thread_factory interface. * * This implementation creates a new instance of std_work_thread_t dynamically * in every call to acquire(). An instance of std_work_thread_t is expected * in release() (but it's not checked) and the instance passed to * release() is just deleted by using ordinary `delete`. * * \since v.5.7.3 */ class std_work_thread_factory_t : public abstract_work_thread_factory_t { public: std_work_thread_factory_t() = default; [[nodiscard]] abstract_work_thread_t & acquire( so_5::environment_t & /*env*/ ) override { return *(new std_work_thread_t{}); } void release( abstract_work_thread_t & thread ) noexcept override { // Assume that 'thread' was created via acquire() method, // so we can safely delete it. delete (&thread); } }; } /* namespace std_work_thread_impl */ // // make_std_work_thread_factory // [[nodiscard]] SO_5_FUNC abstract_work_thread_factory_shptr_t make_std_work_thread_factory() { return std::make_shared< std_work_thread_impl::std_work_thread_factory_t >(); } } /* namespace so_5::disp */ sobjectizer-5.8.6/dev/so_5/disp/abstract_work_thread.hpp000066400000000000000000000242731523065210100233470ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Interfaces for work_thread and work_thread's factory. * \since v.5.7.3 */ #if !defined( SO_5_DISP_ABSTRACT_WORK_THREAD_HPP ) #define SO_5_DISP_ABSTRACT_WORK_THREAD_HPP #include #include #include #include #include namespace so_5::disp { // // abstract_work_thread_t // /*! * \brief An interface for one worker thread. * * All worker threads used by SObjectizer's dispatchers have to implement * that interface. * * A worker thread is used by SObjectizer that way: * * - an instance of worker thread is obtained from an appropriate work thread * factory by calling abstract_work_thread_factory_t::acquire(); * - method abstract_work_thread_t::start() is called for the obtained instance; * - some time later method abstract_work_thread_t::join() is called for the * obtained instance; * - after the call to join() the obtained instance is returned to the work * thread factory by calling abstract_work_thread_factory_t::release(). * * \since v.5.7.3 */ class SO_5_TYPE abstract_work_thread_t { public: /*! * \brief Type of functor to be passed to start method. * * abstract_work_thread_t::start() receives a function that * will be executed on the context of a new thread. Type of that * functor is described by this typedef. * * \note * A functor passed to abstract_work_thread_t::start() is not * noexcept. It can throw. However, see the description of * abstract_work_thread_t::start() for more details. */ using body_func_t = std::function< void() >; abstract_work_thread_t(); abstract_work_thread_t( const abstract_work_thread_t & ) = delete; abstract_work_thread_t & operator=( const abstract_work_thread_t & ) = delete; abstract_work_thread_t( abstract_work_thread_t && ) = delete; abstract_work_thread_t & operator=( abstract_work_thread_t && ) = delete; virtual ~abstract_work_thread_t(); /*! * \brief Start a new thread and execute specified functor on it. * * It is not specified should a new thread be launched or some existing * thread that is reused. The only demand is that this thread should not * execute some other task except the passed \a thread_body functor. * * The implementation should throw if a new thread can't be started. * * \note * It is allowed that \a thread_body can throw. All exceptions should * be intercepted and skipped. An implementation can use some form * of logging for intercepted exceptions, but it isn't required * (the standard impelementation doesn't log anything). */ virtual void start( body_func_t thread_body ) = 0; /*! * \brief Stops the current thread until worker thread completes * execution of thread_body passed to previous call to start. * * SObjectizer guarantees that join() is called only once, and if and * only if the previous call to start() is completed successfully * (without exceptions). The join() method won't be called if there * wasn't a previous call to start() method or if the previous call to * start() method threw. * * \attention * This method is not noexcept in the current version of SObjectizer. So * it can throw. However, SObjectizer has no defense from exceptions * thrown from join(). Moveover, most of the calls to join() are done in * noexcept destructors. It means that if join() throws then it will, * probably, lead to the termination of the whole application. It also * means that noexcept may be added to the requirements of join() * implementation in future versions of SObjectizer. * */ virtual void join() = 0; }; // // abstract_work_thread_factory_t // /*! * \brief An interface of factory for management of worker threads. * * A worker thread factory can implement different schemes of thread * management, for example: * * - a new instance of worker thread can be allocated dynamically in acquire() * method and destroyed in release() method; * - a pool of preallocated worker thread can be used, method acquire() will * take a thread from that pool and return the thread back to the pool in * release() method. * * SObjectizer doesn't care about the allocation scheme of worker threads. It * demands that a reference returned from acquire() method remains valid until * it is passed to release() method. * * SObjectizer guarantees that a thread is taken from acquire() method will be * returned back via release() method. * * \attention * An implementation of thread factory should be thread safe. Calls to * acquire() and release() can be performed from different threads. * For example, a call to acquire() can be made from thread A, but * the corresponding call to release() can be made from thread B. * * \since v.5.7.3 */ class SO_5_TYPE abstract_work_thread_factory_t { public: abstract_work_thread_factory_t(); abstract_work_thread_factory_t( const abstract_work_thread_factory_t & ) = delete; abstract_work_thread_factory_t & operator=( const abstract_work_thread_factory_t & ) = delete; abstract_work_thread_factory_t( abstract_work_thread_factory_t && ) = delete; abstract_work_thread_factory_t & operator=( abstract_work_thread_factory_t && ) = delete; virtual ~abstract_work_thread_factory_t(); /*! * \brief Get a new worker thread from factory. * * This method should throw an instance of std::exception (or any derived * from std::exception class) in the case when new worker thread can't * be obtained. * * The reference returned should remains valid until it will be * passed to release() method. */ [[nodiscard]] virtual abstract_work_thread_t & acquire( //! A reference to SObjectizer Environment in that acquire() is called. //! That parameter can be useful in case when a single factory //! used for several instances of SObjectizer Environment at the //! same time. so_5::environment_t & env ) = 0; /*! * \brief Return a worker thread back to the factory. * * SObjectizer guarantees that \a thread is a reference obtained * by a previous successful call to acquire(). * * SObjectizer guarantees that every successful call to acquire() * is paired with a call to release(). */ virtual void release( //! A reference to worker thread to be returned. //! This reference is obtained by a previous call to acquire(). abstract_work_thread_t & thread ) noexcept = 0; }; // // abstract_work_thread_factory_shptr_t // /*! * \brief An alias for shared_ptr to abstract_work_thread_factory. * * \since v.5.7.3 */ using abstract_work_thread_factory_shptr_t = std::shared_ptr< abstract_work_thread_factory_t >; // // work_thread_holder_t // /*! * \brief An analog of unique_ptr for abstract_work_thread. * * When an instance of abstract_work_thread is no more needed it has * to be returned to an appropriate abstract_work_thread_factory. * Helper class work_thread_holder_t simplifies that task, in that * sense it's an analog of std::unique_ptr. * * A fully initialized work_thread_holder_t holds a reference to * abstract_work_thread_t instance and shared_ptr to the corresponding * abstract_work_thread_factory_t. The instance of abstract_work_thread_t * will be returned to abstract_work_thread_factory_t in the destructor * of work_thread_holder_t. * * \note * This class doesn't call abstract_work_thread_t::start() nor * abstract_work_thread_t::join(). It only calls * abstract_work_thread_factory_t::release(). * * \attention * It is not thread safe. * * \since v.5.7.3 */ class [[nodiscard]] work_thread_holder_t { abstract_work_thread_t * m_thread{}; abstract_work_thread_factory_shptr_t m_factory{}; public: friend void swap( work_thread_holder_t & a, work_thread_holder_t & b ) noexcept { using std::swap; swap( a.m_thread, b.m_thread ); swap( a.m_factory, b.m_factory ); } work_thread_holder_t() noexcept = default; work_thread_holder_t( abstract_work_thread_t & thread, abstract_work_thread_factory_shptr_t factory ) noexcept : m_thread{ &thread } , m_factory{ std::move(factory) } {} work_thread_holder_t( const work_thread_holder_t & ) = delete; work_thread_holder_t & operator=( const work_thread_holder_t & ) = delete; work_thread_holder_t( work_thread_holder_t && o ) noexcept : m_thread{ std::exchange( o.m_thread, nullptr ) } , m_factory{ std::exchange( o.m_factory, abstract_work_thread_factory_shptr_t{} ) } {} work_thread_holder_t & operator=( work_thread_holder_t && o ) noexcept { work_thread_holder_t tmp{ std::move(o) }; swap( *this, tmp ); return *this; } ~work_thread_holder_t() noexcept { if( m_thread ) m_factory->release( *m_thread ); } /*! * \retval true if it doesn't hold an actual reference to * abstract_work_thread_t. */ [[nodiscard]] bool empty() const noexcept { return nullptr == m_thread; } /*! * \retval true if it holds an actual reference to * abstract_work_thread_t (e.g. holder is not empty). */ [[nodiscard]] explicit operator bool() const noexcept { return !empty(); } /*! * \retval true if it doesn't hold an actual reference to * abstract_work_thread_t. */ [[nodiscard]] bool operator!() const noexcept { return empty(); } /*! * \brief Unsafe method for getting a reference to holding * abstract_work_thread instance. * * No checks are performed inside that method. Use it at your own risk. * * \attention * Calling of that method for an empty holder is UB. */ [[nodiscard]] abstract_work_thread_t & unchecked_get() const noexcept { return *m_thread; } }; // // make_std_work_thread_factory // /*! * \brief Get a standard SObjectizer's work thread factory that is used by default. * * Usage example: * \code * so_5::launch( [](so_5::environment_t & env) { * ... * }, * [&]( so_5::environment_params_t & params ) { * params.work_thread_factory( some_condition ? * my_work_thread_factory() : so_5::disp::make_std_work_thread_factory() ); * } ); * \endcode * * \since v.5.7.3 */ [[nodiscard]] SO_5_FUNC abstract_work_thread_factory_shptr_t make_std_work_thread_factory(); } /* namespace so_5::disp */ #endif sobjectizer-5.8.6/dev/so_5/disp/active_group/000077500000000000000000000000001523065210100211215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/active_group/pub.cpp000066400000000000000000000264041523065210100224210ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace active_group { namespace impl { namespace work_thread = so_5::disp::reuse::work_thread; namespace stats = so_5::stats; namespace { /*! * \brief Just a helper function for consequetive call to shutdown and wait. * * \since * v.5.5.4 */ template< class T > void shutdown_and_wait( T & w ) { w.shutdown(); w.wait(); } void send_thread_activity_stats( const so_5::mbox_t &, const stats::prefix_t &, work_thread::work_thread_no_activity_tracking_t & ) { /* Nothing to do */ } void send_thread_activity_stats( const so_5::mbox_t & mbox, const stats::prefix_t & prefix, work_thread::work_thread_with_activity_tracking_t & wt ) { so_5::send< stats::messages::work_thread_activity >( mbox, prefix, stats::suffixes::work_thread_activity(), wt.thread_id(), wt.take_activity_stats() ); } } /* anonymous */ // // actual_dispatcher_iface_t // /*! * \brief An actual interface of active group dispatcher. * * \since * v.5.6.0 */ class actual_dispatcher_iface_t : public basic_dispatcher_iface_t { public : /*! * \brief Create a new thread for a group if it necessary. * * If name \a group_name is unknown then a new work * thread is started. This thread is marked as it has one * working agent on it. * * If there already is a thread for \a group_name then the * counter of working agents is incremented. */ virtual void allocate_thread_for_group( const std::string & group_name ) = 0; /*! * \brief Get the event_queue for the specified active group. * * It is expected that thread for the group is already * created by calling allocate_thread_for_group() method. */ virtual so_5::event_queue_t * query_thread_for_group( const std::string & group_name ) noexcept = 0; /*! * \brief Release the thread for the specified active group. * * Method decrements the working agent count for the thread of * \a group_name. If there no more working agents left then * the event_queue and working thread for that group will be * destroyed. */ virtual void release_thread_for_group( const std::string & group_name ) noexcept = 0; }; // // actual_dispatcher_iface_shptr_t // using actual_dispatcher_iface_shptr_t = std::shared_ptr< actual_dispatcher_iface_t >; // // actual_binder_t // /*! * \brief Implementation of binder interface for %active_group dispatcher. * * \since * v.5.6.0 */ class actual_binder_t final : public disp_binder_t { //! Dispatcher to be used. actual_dispatcher_iface_shptr_t m_disp; //! Name of group for new agents. const std::string m_group_name; public : actual_binder_t( actual_dispatcher_iface_shptr_t disp, nonempty_name_t group_name ) noexcept : m_disp{ std::move(disp) } , m_group_name{ group_name.giveout_value() } {} void preallocate_resources( agent_t & /*agent*/ ) override { m_disp->allocate_thread_for_group( m_group_name ); } void undo_preallocation( agent_t & /*agent*/ ) noexcept override { m_disp->release_thread_for_group( m_group_name ); } void bind( agent_t & agent ) noexcept override { auto queue = m_disp->query_thread_for_group( m_group_name ); agent.so_bind_to_dispatcher( *queue ); } void unbind( agent_t & /*agent*/ ) noexcept override { m_disp->release_thread_for_group( m_group_name ); } }; // // dispatcher_template_t // /*! * \brief Implementation of active object dispatcher in form of template class. */ template< typename Work_Thread > class dispatcher_template_t final : public actual_dispatcher_iface_t { public: dispatcher_template_t( //! SObjectizer Environment to work in. outliving_reference_t< environment_t > env, //! Base part of data sources names. const std::string_view name_base, //! Dispatcher's parameters. disp_params_t params ) : m_env{ env } , m_params{ std::move(params) } , m_data_source{ outliving_mutable(m_env.get().stats_repository()), name_base, outliving_mutable( *this ) } {} ~dispatcher_template_t() noexcept override { // All working threads should receive stop signal. for( auto & p: m_groups ) p.second.m_thread->shutdown(); // All working threads should be joined. for( auto & p: m_groups ) p.second.m_thread->wait(); } disp_binder_shptr_t binder( nonempty_name_t group_name ) override { return std::make_shared< actual_binder_t >( this->shared_from_this(), std::move(group_name) ); } void allocate_thread_for_group( const std::string & group_name ) override { std::lock_guard< std::mutex > lock{ m_lock }; auto it = m_groups.find( group_name ); if( m_groups.end() == it ) { // New thread should be created. auto thread = std::make_shared< Work_Thread >( acquire_work_thread( m_params, m_env.get() ), m_params.queue_params().lock_factory() ); thread->start(); so_5::details::do_with_rollback_on_exception( [&] { m_groups.emplace( group_name, thread_with_refcounter_t{ thread, 1u } ); }, [&thread] { shutdown_and_wait( *thread ); } ); } else { // Number of agents bound has to be incremented now. it->second.m_user_agent += 1u; } } so_5::event_queue_t * query_thread_for_group( const std::string & group_name ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; return m_groups.find( group_name )->second.m_thread-> get_agent_binding(); } void release_thread_for_group( const std::string & group_name ) noexcept override { auto thread = search_and_try_remove_group_from_map( group_name ); if( thread ) shutdown_and_wait( *thread ); } private: friend class disp_data_source_t; //! An alias for shared pointer to work thread. using work_thread_shptr_t = std::shared_ptr< Work_Thread >; //! Auxiliary class for the working agent counting. struct thread_with_refcounter_t { work_thread_shptr_t m_thread; std::size_t m_user_agent; }; //! Typedef for mapping from group names to a single thread //! dispatcher. using active_group_map_t = std::map< std::string, thread_with_refcounter_t >; /*! * \brief Data source for run-time monitoring of whole dispatcher. * * \since * v.5.5.4 */ class disp_data_source_t final : public stats::source_t { //! Dispatcher to work with. outliving_reference_t< dispatcher_template_t > m_dispatcher; //! Basic prefix for data sources. stats::prefix_t m_base_prefix; public : disp_data_source_t( const std::string_view name_base, outliving_reference_t< dispatcher_template_t > disp ) : m_dispatcher{ disp } { using namespace so_5::disp::reuse; m_base_prefix = make_disp_prefix( "ag", // ao -- active_groups name_base, &m_dispatcher ); } void distribute( const so_5::mbox_t & mbox ) override { auto & disp = m_dispatcher.get(); std::lock_guard< std::mutex > lock{ disp.m_lock }; so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::disp_active_group_count(), disp.m_groups.size() ); std::size_t agent_count = 0; for( const auto & p : disp.m_groups ) { distribute_value_for_work_thread( mbox, p.first, p.second ); agent_count += p.second.m_user_agent; } so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::agent_count(), agent_count ); } private: void distribute_value_for_work_thread( const so_5::mbox_t & mbox, const std::string & group_name, const thread_with_refcounter_t & wt ) { std::ostringstream ss; ss << m_base_prefix.c_str() << "/wt-" << group_name; const stats::prefix_t prefix{ ss.str() }; so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::agent_count(), wt.m_user_agent ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::work_thread_queue_size(), wt.m_thread->demands_count() ); send_thread_activity_stats( mbox, prefix, *(wt.m_thread) ); } }; /*! * \brief SObjectizer Environment to work in. * * It is necessary for calling work_thread_factory. * * \since v.5.7.3 */ outliving_reference_t< environment_t > m_env; //! Parameters for the dispatcher. const disp_params_t m_params; //! A map of dispatchers for active groups. active_group_map_t m_groups; //! This object lock. std::mutex m_lock; /*! * \brief Data source for run-time monitoring. * * \since * v.5.5.4, v.5.6.0 */ stats::auto_registered_source_holder_t< disp_data_source_t > m_data_source; /*! * \brief Helper function for searching and erasing agent's * thread from map of active threads. * * \since * v.5.5.4 * * \note Does all actions on locked object. * * \return nullptr if thread for the group is not found * or there are still some agents on it. */ work_thread_shptr_t search_and_try_remove_group_from_map( const std::string & group_name ) noexcept { work_thread_shptr_t result; std::lock_guard< std::mutex > lock{ m_lock }; auto it = m_groups.find( group_name ); if( m_groups.end() != it && 0u == --(it->second.m_user_agent) ) { result = it->second.m_thread; m_groups.erase( it ); } return result; } }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( actual_dispatcher_iface_shptr_t disp ) noexcept { return { std::move( disp ) }; } }; } /* namespace impl */ // // make_dispatcher // SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, disp_params_t params ) { using namespace so_5::disp::reuse; using dispatcher_no_activity_tracking_t = impl::dispatcher_template_t< work_thread::work_thread_no_activity_tracking_t >; using dispatcher_with_activity_tracking_t = impl::dispatcher_template_t< work_thread::work_thread_with_activity_tracking_t >; auto binder = so_5::disp::reuse::make_actual_dispatcher< impl::actual_dispatcher_iface_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params) ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace active_group */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/active_group/pub.hpp000066400000000000000000000152341523065210100224250ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Functions for creating and binding to the active group dispatcher. */ #if !defined( SO_5_DISP_ACTIVE_GROUP_HPP ) #define SO_5_DISP_ACTIVE_GROUP_HPP #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace active_group { /*! * \brief Alias for namespace with traits of event queue. * * \since * v.5.5.10 */ namespace queue_traits = so_5::disp::mpsc_queue_traits; // // disp_params_t // /*! * \brief Parameters for active group dispatcher. * * \since * v.5.5.10 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() = default; friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< work_thread_factory_mixin_t & >(a), static_cast< work_thread_factory_mixin_t & >(b) ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code so_5::disp::active_group::make_dispatcher( env, "my_active_group_disp", so_5::disp::active_group::disp_params_t{}.tune_queue_params( []( so_5::disp::active_group::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::active_group::queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const { return m_queue_params; } private : //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; namespace impl { class actual_dispatcher_iface_t; // // basic_dispatcher_iface_t // /*! * \brief The very basic interface of %active_group dispatcher. * * This class contains a minimum that is necessary for implementation * of dispatcher_handle class. * * \since * v.5.6.0 */ class basic_dispatcher_iface_t : public std::enable_shared_from_this { public : virtual ~basic_dispatcher_iface_t() noexcept = default; [[nodiscard]] virtual disp_binder_shptr_t binder( nonempty_name_t group_name ) = 0; }; using basic_dispatcher_iface_shptr_t = std::shared_ptr< basic_dispatcher_iface_t >; class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \since * v.5.6.0 * * \brief A handle for %active_group dispatcher. */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! A reference to actual implementation of a dispatcher. impl::basic_dispatcher_iface_shptr_t m_dispatcher; dispatcher_handle_t( impl::basic_dispatcher_iface_shptr_t dispatcher ) noexcept : m_dispatcher{ std::move(dispatcher) } {} //! Is this handle empty? bool empty() const noexcept { return !m_dispatcher; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. /*! * \attention * An attempt to call this method on empty handle is UB. */ [[nodiscard]] disp_binder_shptr_t binder( //! Name of group for a new agent. nonempty_name_t group_name ) const { return m_dispatcher->binder( std::move(group_name) ); } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_dispatcher.reset(); } }; /*! * \brief Create an instance of %active_group dispatcher. * * \par Usage sample \code auto disp = so_5::disp::active_group::make_dispatcher( env, "request_handler", // Additional params with specific options for queue's traits. so_5::disp::active_group::disp_params_t{}.tune_queue_params( []( so_5::disp::active_group::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::active_obj::queue_traits::simple_lock_factory() ); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of active_group dispatcher. disp.binder( "request_handler" ) ); \endcode * * \since * v.5.6.0 */ SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. so_5::environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Parameters for dispatcher. disp_params_t params ); /*! * \brief Create an instance of %active_group dispatcher. * * \par Usage sample \code auto disp = so_5::disp::active_group::make_dispatcher( env, "long_req_handlers" ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of active_group dispatcher. disp.binder( "passive_objects" ) ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. so_5::environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base ) { return make_dispatcher( env, data_sources_name_base, disp_params_t{} ); } /*! * \brief Create an instance of %active_group dispatcher. * * \par Usage sample \code auto disp = so_5::disp::active_group::make_dispatcher( env ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of active_group dispatcher. disp.binder( "passive_objects" ) ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. so_5::environment_t & env ) { return make_dispatcher( env, std::string_view{} ); } } /* namespace active_group */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/active_obj/000077500000000000000000000000001523065210100205375ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/active_obj/pub.cpp000066400000000000000000000173241523065210100220400ustar00rootroot00000000000000/* SObjectizer 5 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace active_obj { namespace impl { namespace work_thread = so_5::disp::reuse::work_thread; namespace stats = so_5::stats; namespace { /*! * \brief Just a helper function for consequetive call to shutdown and wait. * \since * v.5.5.4 */ template< class T > void shutdown_and_wait( T & w ) { w.shutdown(); w.wait(); } template< class Work_Thread > void send_demands_count_stats( const so_5::mbox_t & mbox, const stats::prefix_t & prefix, Work_Thread & wt ) { so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::work_thread_queue_size(), wt.demands_count() ); } void send_thread_activity_stats( const so_5::mbox_t &, const stats::prefix_t &, work_thread::work_thread_no_activity_tracking_t & ) { /* Nothing to do */ } void send_thread_activity_stats( const so_5::mbox_t & mbox, const stats::prefix_t & prefix, work_thread::work_thread_with_activity_tracking_t & wt ) { so_5::send< stats::messages::work_thread_activity >( mbox, prefix, stats::suffixes::work_thread_activity(), wt.thread_id(), wt.take_activity_stats() ); } } /* anonymous */ // // dispatcher_template_t // /*! * \brief Implementation of active object dispatcher in form of template class. */ template< typename Work_Thread > class dispatcher_template_t final : public disp_binder_t { public: dispatcher_template_t( //! SObjectizer Environment to work in. outliving_reference_t< environment_t > env, //! Base part of data sources names. const std::string_view name_base, //! Dispatcher's parameters. disp_params_t params ) : m_env{ env } , m_params{ std::move(params) } , m_data_source{ outliving_mutable(m_env.get().stats_repository()), name_base, outliving_mutable( *this ) } {} ~dispatcher_template_t() noexcept override { // All working threads should receive stop signal. for( auto & p: m_agent_threads ) p.second->shutdown(); // All working threads should be joined. for( auto & p: m_agent_threads ) p.second->wait(); } void preallocate_resources( agent_t & agent ) override { std::lock_guard< std::mutex > lock{ m_lock }; if( m_agent_threads.end() != m_agent_threads.find( &agent ) ) SO_5_THROW_EXCEPTION( rc_disp_create_failed, "thread for the agent is already exists" ); auto lock_factory = m_params.queue_params().lock_factory(); auto thread = std::make_shared< Work_Thread >( acquire_work_thread( m_params, m_env.get() ), std::move(lock_factory) ); thread->start(); so_5::details::do_with_rollback_on_exception( [&] { m_agent_threads[ &agent ] = thread; }, [&thread] { shutdown_and_wait( *thread ); } ); } void undo_preallocation( agent_t & agent ) noexcept override { const auto eject_thread = [&] { std::lock_guard< std::mutex > lock{ m_lock }; auto it = m_agent_threads.find( &agent ); auto thread = it->second; m_agent_threads.erase( it ); return thread; }; shutdown_and_wait( *eject_thread() ); } void bind( agent_t & agent ) noexcept override { const auto get_queue = [&] { std::lock_guard< std::mutex > lock{ m_lock }; return m_agent_threads.find( &agent )->second->get_agent_binding(); }; agent.so_bind_to_dispatcher( *get_queue() ); } void unbind( agent_t & agent ) noexcept override { // We should perform the same actions as for undo_preallocation. undo_preallocation( agent ); } private: friend class disp_data_source_t; //! An alias for shared pointer to work thread object. using work_thread_shptr_t = std::shared_ptr< Work_Thread >; //! Typedef for mapping from agents to their working threads. using agent_thread_map_t = std::map< const agent_t *, work_thread_shptr_t >; /*! * \brief Data source for run-time monitoring of whole dispatcher. * \since * v.5.5.4 */ class disp_data_source_t final : public stats::source_t { //! Dispatcher to work with. outliving_reference_t< dispatcher_template_t > m_dispatcher; //! Basic prefix for data source names. stats::prefix_t m_base_prefix; public : disp_data_source_t( const std::string_view name_base, outliving_reference_t< dispatcher_template_t > disp ) : m_dispatcher{ disp } { using namespace so_5::disp::reuse; m_base_prefix = make_disp_prefix( "ao", // ao -- active_objects name_base, &(m_dispatcher.get()) ); } void distribute( const mbox_t & mbox ) override { auto & disp = m_dispatcher.get(); std::lock_guard< std::mutex > lock{ disp.m_lock }; so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::agent_count(), disp.m_agent_threads.size() ); for( const auto & [a, wt] : disp.m_agent_threads ) distribute_value_for_work_thread( mbox, a, *wt ); } private: void distribute_value_for_work_thread( const mbox_t & mbox, const agent_t * agent, Work_Thread & wt ) { std::ostringstream ss; ss << m_base_prefix.c_str() << "/wt-" << so_5::disp::reuse::ios_helpers::pointer{ agent }; const stats::prefix_t wt_prefix{ ss.str() }; send_demands_count_stats( mbox, wt_prefix, wt ); send_thread_activity_stats( mbox, wt_prefix, wt ); } }; /*! * \brief SObjectizer Environment to work in. * * It is necessary for calling work_thread_factory. * * \since v.5.7.3 */ outliving_reference_t< environment_t > m_env; /*! * \brief Parameters for the dispatcher. * * \since * v.5.5.10 */ const disp_params_t m_params; //! This object lock. std::mutex m_lock; //! A map from agents to single thread dispatchers. agent_thread_map_t m_agent_threads; /*! * \brief Data source for run-time monitoring. * * \since * v.5.5.4, v.5.6.0 */ stats::auto_registered_source_holder_t< disp_data_source_t > m_data_source; }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( disp_binder_shptr_t binder ) noexcept { return { std::move( binder ) }; } }; } /* namespace impl */ // // make_dispatcher // [[nodiscard]] SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, disp_params_t params ) { using namespace so_5::disp::reuse; using dispatcher_no_activity_tracking_t = impl::dispatcher_template_t< work_thread::work_thread_no_activity_tracking_t >; using dispatcher_with_activity_tracking_t = impl::dispatcher_template_t< work_thread::work_thread_with_activity_tracking_t >; disp_binder_shptr_t binder = so_5::disp::reuse::make_actual_dispatcher< disp_binder_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params) ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace active_obj */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/active_obj/pub.hpp000066400000000000000000000131221523065210100220350ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Dispatcher creation and agent binding functions. */ #if !defined( SO_5_DISP_ACTIVE_OBJ_HPP ) #define SO_5_DISP_ACTIVE_OBJ_HPP #include #include #include #include #include #include namespace so_5 { namespace disp { namespace active_obj { /*! * \brief Alias for namespace with traits of event queue. * * \since * v.5.5.10 */ namespace queue_traits = so_5::disp::mpsc_queue_traits; // // disp_params_t // /*! * \brief Parameters for active object dispatcher. * * \since * v.5.5.10 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() = default; friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< work_thread_factory_mixin_t & >(a), static_cast< work_thread_factory_mixin_t & >(b) ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code so_5::disp::active_obj::make_dispatcher( env, "my_active_obj_disp", so_5::disp::active_obj::disp_params_t{}.tune_queue_params( []( so_5::disp::active_obj::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::active_obj::queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const { return m_queue_params; } private : //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; namespace impl { class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \since * v.5.6.0 * * \brief A handle for %active_obj dispatcher. */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! Binder for the dispatcher. disp_binder_shptr_t m_binder; dispatcher_handle_t( disp_binder_shptr_t binder ) noexcept : m_binder{ std::move(binder) } {} //! Is this handle empty? bool empty() const noexcept { return !m_binder; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. [[nodiscard]] disp_binder_shptr_t binder() const noexcept { return m_binder; } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_binder.reset(); } }; /*! * \brief Create an instance of %active_obj dispatcher. * * \par Usage sample \code auto disp = so_5::disp::active_obj::make_dispatcher( env, "db_handler", // Additional params with specific options for queue's traits. so_5::disp::active_obj::disp_params_t{}.tune_queue_params( []( so_5::disp::active_obj::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::active_obj::queue_traits::simple_lock_factory() ); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of active_obj dispatcher. disp->binder() ); \endcode * * \since * v.5.6.0 */ SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Parameters for dispatcher. disp_params_t params ); /*! * \brief Create an instance of %active_obj dispatcher. * * \par Usage sample \code auto disp = so_5::disp::active_obj::make_dispatcher( env, "db_handler" ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of active_obj dispatcher. disp.binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base ) { return make_dispatcher( env, data_sources_name_base, disp_params_t{} ); } /*! * \brief Create a private %active_obj dispatcher. * * \par Usage sample \code auto disp = so_5::disp::active_obj::make_dispatcher( env ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of active_obj dispatcher. disp.binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env ) { return make_dispatcher( env, std::string_view{} ); } } /* namespace active_obj */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/adv_thread_pool/000077500000000000000000000000001523065210100215645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/adv_thread_pool/impl/000077500000000000000000000000001523065210100225255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/adv_thread_pool/impl/disp.hpp000066400000000000000000000413631523065210100242040ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief An implementation of advanced thread pool dispatcher. * * \since * v.5.4.0 */ #if !defined( SO_5_DISP_ADV_THREAD_POOL_IMPL_DISP_HPP ) #define SO_5_DISP_ADV_THREAD_POOL_IMPL_DISP_HPP #include #include #include #include #include #include #include #include #if 0 #define SO_5_CHECK_INVARIANT_IMPL(what, data, file, line) \ if( !(what) ) { \ std::cerr << file << ":" << line << ": FAILED INVARIANT: " << #what << "; data: " << data << std::endl; \ std::abort(); \ } #define SO_5_CHECK_INVARIANT(what, data) SO_5_CHECK_INVARIANT_IMPL(what, data, __FILE__, __LINE__) #else #define SO_5_CHECK_INVARIANT(what, data) #endif namespace so_5 { namespace disp { namespace adv_thread_pool { namespace impl { using spinlock_t = so_5::default_spinlock_t; class agent_queue_t; namespace stats = so_5::stats; namespace tp_stats = so_5::disp::reuse::thread_pool_stats; // // dispatcher_queue_t // using dispatcher_queue_t = so_5::disp::reuse::queue_of_queues_t< agent_queue_t >; // // agent_queue_t // /*! * \brief Event queue for the agent (or cooperation). * * \since * v.5.4.0 */ class agent_queue_t final : public event_queue_t , private so_5::atomic_refcounted_t { friend class so_5::intrusive_ptr_t< agent_queue_t >; private : //! Actual demand in event queue. struct demand_t { //! Actual demand. execution_demand_t m_demand; //! Next item in queue. demand_t * m_next; demand_t() : m_next( nullptr ) {} demand_t( execution_demand_t && original ) : m_demand( std::move( original ) ) , m_next( nullptr ) {} }; public : static constexpr const unsigned int thread_safe_worker = 2; static constexpr const unsigned int not_thread_safe_worker = 1; //! Constructor. agent_queue_t( //! Dispatcher queue to work with. outliving_reference_t< dispatcher_queue_t > disp_queue, //! Dummy argument. It is necessary here because of //! common implementation for thread-pool and //! adv-thread-pool dispatchers. const bind_params_t & ) : m_disp_queue( disp_queue.get() ) , m_tail_demand( &m_head_demand ) , m_active( false ) , m_workers( 0 ) {} ~agent_queue_t() override { while( m_head_demand.m_next ) delete_head(); } //! Access to the queue's lock. [[nodiscard]] spinlock_t & lock() noexcept { return m_lock; } //! Push next demand to queue. void push( execution_demand_t demand ) override { bool need_schedule = false; { // Do memory allocation before spinlock locking. auto new_demand = new demand_t( std::move( demand ) ); std::lock_guard< spinlock_t > lock( m_lock ); m_tail_demand->m_next = new_demand; m_tail_demand = m_tail_demand->m_next; ++m_size; if( m_head_demand.m_next == m_tail_demand ) { // Queue was empty. Need to detect // necessity of queue activation. if( !m_active ) if( !is_there_not_thread_safe_worker() ) { need_schedule = true; m_active = true; } } SO_5_CHECK_INVARIANT( !empty(), this ) SO_5_CHECK_INVARIANT( m_active || is_there_any_worker(), this ) SO_5_CHECK_INVARIANT( !(need_schedule && !m_active), this ) } if( need_schedule ) m_disp_queue.schedule( this ); } /*! * \note * Delegates the work to the push() method. */ void push_evt_start( execution_demand_t demand ) override { this->push( std::move(demand) ); } /*! * \note * Delegates the work to the push() method. * * \attention * Terminates the whole application if the push() throws. */ void push_evt_finish( execution_demand_t demand ) noexcept override { this->push( std::move(demand) ); } //! Get the information about the front demand. /*! * \attention This method must be called only on non-empty queue. */ [[nodiscard]] execution_demand_t peek_front() { SO_5_CHECK_INVARIANT( !empty(), this ) SO_5_CHECK_INVARIANT( m_active, this ) m_active = false; return m_head_demand.m_next->m_demand; } //! Remove the front demand. /*! * \retval true queue must be activated. * \retval false queue must not be activated. */ [[nodiscard]] bool worker_started( //! Type of worker. //! Must be thread_safe_worker or not_thread_safe_worker. unsigned int type_of_worker ) { SO_5_CHECK_INVARIANT( !empty(), this ); SO_5_CHECK_INVARIANT( !m_active, this ); delete_head(); if( !m_head_demand.m_next ) m_tail_demand = &m_head_demand; m_workers += type_of_worker; // Queue must be activated only if queue is not empty // and current worker is a thread safe worker. m_active = ( !empty() && thread_safe_worker == type_of_worker ); return m_active; } //! Signal about finishing of worker of the specified type. /*! * \retval true queue must be activated. * \retval false queue must not be activated. */ [[nodiscard]] bool worker_finished( //! Type of worker. //! Must be thread_safe_worker or not_thread_safe_worker. unsigned int type_of_worker ) { m_workers -= type_of_worker; bool old_active = m_active; if( !m_active ) m_active = !empty(); SO_5_CHECK_INVARIANT( !(m_active && empty()), this ) SO_5_CHECK_INVARIANT( !old_active || m_active, this ); return old_active != m_active; } //! Check the presence of any worker at the moment. [[nodiscard]] bool is_there_any_worker() const { return 0 != m_workers; } //! Check the presence of thread unsafe worker. [[nodiscard]] bool is_there_not_thread_safe_worker() const { return 0 != (m_workers & not_thread_safe_worker ); } //! Is empty queue? [[nodiscard]] bool empty() const { return nullptr == m_head_demand.m_next; } //! Is active queue? [[nodiscard]] bool active() const { return m_active; } /*! * \brief Get the current size of the queue. * * \since * v.5.5.4 */ [[nodiscard]] std::size_t size() const noexcept { return m_size.load( std::memory_order_acquire ); } /*! * \brief Give away a pointer to the next agent_queue. * * \note * This method is a part of interface required by * so_5::disp::reuse::queue_of_queues_t. * * \since v.5.8.0 */ [[nodiscard]] agent_queue_t * intrusive_queue_giveout_next() noexcept { auto * r = m_intrusive_queue_next; m_intrusive_queue_next = nullptr; return r; } /*! * \brief Set a pointer to the next agent_queue. * * \note * This method is a part of interface required by * so_5::disp::reuse::queue_of_queues_t. * * \since v.5.8.0 */ void intrusive_queue_set_next( agent_queue_t * next ) noexcept { m_intrusive_queue_next = next; } private : //! Dispatcher queue for scheduling processing of events from //! this queue. dispatcher_queue_t & m_disp_queue; //! Object's lock. spinlock_t m_lock; //! Head of the demand's queue. /*! * Never contains actual demand. Only m_next field is used. */ demand_t m_head_demand; //! Tail of the demand's queue. /*! * Must point to m_head_demand if queue is empty or to the very * last queue item otherwise. */ demand_t * m_tail_demand; //! Is this queue activated? /*! * Queue is activated if it is scheduled to dispatcher queue. */ bool m_active; //! Count of active workers. unsigned int m_workers; /*! * \brief Current size of the queue. * * \since * v.5.5.4 */ std::atomic< std::size_t > m_size = { 0 }; /*! * \brief The next item in intrusive queue of agent_queues. * * This field is necessary to implement interface required by * so_5::disp::reuse::queue_of_queues_t. * * \since v.5.8.0 */ agent_queue_t * m_intrusive_queue_next{ nullptr }; //! Helper method for deleting queue's head object. inline void delete_head() noexcept { auto to_be_deleted = m_head_demand.m_next; m_head_demand.m_next = m_head_demand.m_next->m_next; --m_size; delete to_be_deleted; } }; // // agent_queue_ref_t // /*! * \brief A typedef of smart pointer for agent_queue. * * \since * v.5.4.0 */ using agent_queue_ref_t = so_5::intrusive_ptr_t< agent_queue_t >; namespace work_thread_details { /*! * \brief Main data for work_thread. * * \since * v.5.5.18 */ struct common_data_t { //! Dispatcher's queue. dispatcher_queue_t * m_disp_queue; //! ID of thread. /*! * Receives actual value inside body(). */ so_5::current_thread_id_t m_thread_id; //! Actual thread. work_thread_holder_t m_thread_holder; //! Waiting object for long wait. so_5::disp::mpmc_queue_traits::condition_unique_ptr_t m_condition; common_data_t( outliving_reference_t< dispatcher_queue_t > queue, work_thread_holder_t thread_holder ) : m_disp_queue( std::addressof( queue.get() ) ) , m_thread_holder{ std::move(thread_holder) } , m_condition{ queue.get().allocate_condition() } {} }; /*! * \brief Part of implementation of work thread without activity tracing. * * \since * v.5.5.18 */ class no_activity_tracking_impl_t : protected common_data_t { public : //! Initializing constructor. no_activity_tracking_impl_t( outliving_reference_t< dispatcher_queue_t > queue, work_thread_holder_t thread_holder ) : common_data_t( queue, std::move(thread_holder) ) {} template< typename L > void take_activity_stats( L ) { /* Nothing to do */ } protected : void work_started() {} void work_finished() {} void wait_started() {} void wait_finished() {} }; /*! * \brief Part of implementation of work thread with activity tracing. * * \since * v.5.5.18 */ class with_activity_tracking_impl_t : protected common_data_t { using activity_tracking_traits = so_5::stats::activity_tracking_stuff::traits; public : //! Initializing constructor. with_activity_tracking_impl_t( outliving_reference_t< dispatcher_queue_t > queue, work_thread_holder_t thread_holder ) : common_data_t( queue, std::move(thread_holder) ) {} template< typename L > void take_activity_stats( L lambda ) { so_5::stats::work_thread_activity_stats_t result; result.m_working_stats = m_work_activity_collector.take_stats(); result.m_waiting_stats = m_waiting_stats_collector.take_stats(); lambda( result ); } protected : //! Lock for activity statistics. activity_tracking_traits::lock_t m_stats_lock; //! A collector for work activity. so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::external_lock<> > m_work_activity_collector{ m_stats_lock }; //! A collector for waiting stats. so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::external_lock<> > m_waiting_stats_collector{ m_stats_lock }; void work_started() { m_work_activity_collector.start(); } void work_finished() { m_work_activity_collector.stop(); } void wait_started() { m_waiting_stats_collector.start(); } void wait_finished() { m_waiting_stats_collector.stop(); } }; // // work_thread_template_t // /*! * \brief Implementation of work_thread in form of template class. * * \tparam Impl no_activity_tracking_impl_t or with_activity_tracking_impl_t. * * \since * v.5.5.18 */ template< typename Impl > class work_thread_template_t final : public Impl { public : //! Initializing constructor. work_thread_template_t( outliving_reference_t< dispatcher_queue_t > queue, work_thread_holder_t thread_holder ) : Impl( queue, std::move(thread_holder) ) {} void join() { so_5::impl::ensure_join_from_different_thread( this->m_thread_id ); this->m_thread_holder.unchecked_get().join(); } //! Launch work thread. void start() { this->m_thread_holder.unchecked_get().start( [this]() { body(); } ); } /*! * \brief Get ID of work thread. * * \note This method returns correct value only after start * of the thread. * * \since * v.5.5.18 */ so_5::current_thread_id_t thread_id() const { return this->m_thread_id; } private : //! Thread body method. void body() { this->m_thread_id = so_5::query_current_thread_id(); agent_queue_t * agent_queue; while( nullptr != (agent_queue = this->pop_agent_queue()) ) { // This guard is necessary to ensure that queue // will exist until processing of queue finished. agent_queue_ref_t agent_queue_guard( agent_queue ); process_queue( *agent_queue ); } } /*! * \since * v.5.5.18 * * \brief An attempt of extraction of non-empty agent queue. * * \note This is noexcept method because its logic can't survive * an exception from m_disp_queue->pop. */ agent_queue_t * pop_agent_queue() noexcept { agent_queue_t * result = nullptr; this->wait_started(); result = this->m_disp_queue->pop( *(this->m_condition) ); this->wait_finished(); return result; } //! Processing of demands from agent queue. void process_queue( agent_queue_t & queue ) { std::unique_lock< spinlock_t > lock( queue.lock() ); auto demand = queue.peek_front(); if( queue.is_there_not_thread_safe_worker() ) // We can't process any demand until thread unsafe // worker is working. return; auto hint = demand.m_receiver->so_create_execution_hint( demand ); bool need_schedule = true; if( !hint.is_thread_safe() ) { if( queue.is_there_any_worker() ) // We can't process not thread safe demand until // there are some other workers. return; else need_schedule = queue.worker_started( agent_queue_t::not_thread_safe_worker ); } else // Threa-safe worker can be started. need_schedule = queue.worker_started( agent_queue_t::thread_safe_worker ); SO_5_CHECK_INVARIANT( !(need_schedule && queue.empty()), &queue ) SO_5_CHECK_INVARIANT( !need_schedule || hint.is_thread_safe(), &queue ); SO_5_CHECK_INVARIANT( !need_schedule || queue.active(), &queue ); // Next few actions must be done on unlocked queue. lock.unlock(); if( need_schedule ) this->m_disp_queue->schedule( &queue ); // For activity tracking if it is turned on. this->work_started(); // Processing of event. hint.exec( this->m_thread_id ); this->work_finished(); // Next actions must be done on locked queue. lock.lock(); need_schedule = queue.worker_finished( hint.is_thread_safe() ? agent_queue_t::thread_safe_worker : agent_queue_t::not_thread_safe_worker ); SO_5_CHECK_INVARIANT( !need_schedule || queue.active(), &queue ); lock.unlock(); if( need_schedule ) this->m_disp_queue->schedule( &queue ); } }; } /* namespace work_thread_details */ // // work_thread_no_activity_tracking_t // /*! * \brief Type of work thread without activity tracking. * * \since * v.5.5.18 */ using work_thread_no_activity_tracking_t = work_thread_details::work_thread_template_t< work_thread_details::no_activity_tracking_impl_t >; // // work_thread_with_activity_tracking_t // /*! * \brief Type of work thread without activity tracking. * * \since * v.5.5.18 */ using work_thread_with_activity_tracking_t = work_thread_details::work_thread_template_t< work_thread_details::with_activity_tracking_impl_t >; // // adaptation_t // /*! * \brief Adaptation of common implementation of thread-pool-like dispatcher * to the specific of this thread-pool dispatcher. * * \since * v.5.5.4 */ struct adaptation_t { [[nodiscard]] static constexpr std::string_view dispatcher_type_name() noexcept { return { "atp" }; // adv_thread_pool. } [[nodiscard]] static bool is_individual_fifo( const bind_params_t & params ) noexcept { return fifo_t::individual == params.query_fifo(); } static void wait_for_queue_emptyness( agent_queue_t & /*queue*/ ) noexcept { // This type of agent_queue doesn't require waiting for emptyness. } }; // // dispatcher_template_t // /*! * \brief Template for dispatcher. * * This template depends on work_thread type (with or without activity * tracking). * * \since * v.5.5.18 */ template< typename Work_Thread > using dispatcher_template_t = so_5::disp::thread_pool::common_implementation::dispatcher_t< Work_Thread, dispatcher_queue_t, bind_params_t, adaptation_t >; } /* namespace impl */ } /* namespace adv_thread_pool */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/adv_thread_pool/pub.cpp000066400000000000000000000126711523065210100230650ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Public interface of advanced thread pool dispatcher. * \since * v.5.4.0 */ #include #include #include #include #include #include namespace so_5 { namespace disp { namespace adv_thread_pool { namespace impl { // // actual_dispatcher_iface_t // /*! * \brief An actual interface of thread-pool dispatcher. * * This interface defines a set of methods necessary for binder. * * \since v.5.6.0 */ class actual_dispatcher_iface_t : public basic_dispatcher_iface_t { public : //! Preallocate all necessary resources for a new agent. virtual void preallocate_resources_for_agent( agent_t & agent, const bind_params_t & params ) = 0; //! Undo preallocation of resources for a new agent. virtual void undo_preallocation_for_agent( agent_t & agent ) noexcept = 0; //! Get resources allocated for an agent. [[nodiscard]] virtual event_queue_t * query_resources_for_agent( agent_t & agent ) noexcept = 0; //! Unbind agent from the dispatcher. virtual void unbind_agent( agent_t & agent ) noexcept = 0; }; // // actual_dispatcher_iface_shptr_t // using actual_dispatcher_iface_shptr_t = std::shared_ptr< actual_dispatcher_iface_t >; // // actual_binder_t // /*! * \brief Actual implementation of binder for %adv_thread_pool dispatcher. * * \since * v.5.6.0 */ class actual_binder_t final : public disp_binder_t { //! Dispatcher to be used. actual_dispatcher_iface_shptr_t m_disp; //! Binding parameters. const bind_params_t m_params; public : actual_binder_t( actual_dispatcher_iface_shptr_t disp, bind_params_t params ) noexcept : m_disp{ std::move(disp) } , m_params{ params } {} void preallocate_resources( agent_t & agent ) override { m_disp->preallocate_resources_for_agent( agent, m_params ); } void undo_preallocation( agent_t & agent ) noexcept override { m_disp->undo_preallocation_for_agent( agent ); } void bind( agent_t & agent ) noexcept override { auto queue = m_disp->query_resources_for_agent( agent ); agent.so_bind_to_dispatcher( *queue ); } void unbind( agent_t & agent ) noexcept override { m_disp->unbind_agent( agent ); } }; // // actual_dispatcher_implementation_t // /*! * \brief Actual implementation of %adv_thread_pool dispatcher. * * \since * v.5.6.0 */ template< typename Work_Thread > class actual_dispatcher_implementation_t final : public actual_dispatcher_iface_t { //! Real dispatcher. dispatcher_template_t< Work_Thread > m_impl; public : actual_dispatcher_implementation_t( //! SObjectizer Environment to work in. outliving_reference_t< environment_t > env, //! Base part of data sources names. const std::string_view name_base, //! Dispatcher's parameters. disp_params_t params ) : m_impl{ env.get(), params, name_base, params.thread_count(), params.queue_params() } { m_impl.start( env.get() ); } ~actual_dispatcher_implementation_t() noexcept override { m_impl.shutdown_then_wait(); } disp_binder_shptr_t binder( bind_params_t params ) override { return std::make_shared< actual_binder_t >( this->shared_from_this(), params ); } void preallocate_resources_for_agent( agent_t & agent, const bind_params_t & params ) override { m_impl.preallocate_resources_for_agent( agent, params ); } void undo_preallocation_for_agent( agent_t & agent ) noexcept override { m_impl.undo_preallocation_for_agent( agent ); } event_queue_t * query_resources_for_agent( agent_t & agent ) noexcept override { return m_impl.query_resources_for_agent( agent ); } void unbind_agent( agent_t & agent ) noexcept override { m_impl.unbind_agent( agent ); } }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( actual_dispatcher_iface_shptr_t disp ) noexcept { return { std::move( disp ) }; } }; } /* namespace impl */ namespace { /*! * \brief Sets the thread count to default value if used do not * specify actual thread count. * * \since * v.5.5.11 */ inline void adjust_thread_count( disp_params_t & params ) { if( !params.thread_count() ) params.thread_count( default_thread_pool_size() ); } } /* namespace anonymous */ // // make_dispatcher // SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, disp_params_t params ) { using namespace so_5::disp::reuse; adjust_thread_count( params ); using dispatcher_no_activity_tracking_t = impl::actual_dispatcher_implementation_t< impl::work_thread_no_activity_tracking_t >; using dispatcher_with_activity_tracking_t = impl::actual_dispatcher_implementation_t< impl::work_thread_with_activity_tracking_t >; auto binder = so_5::disp::reuse::make_actual_dispatcher< impl::actual_dispatcher_iface_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params) ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace adv_thread_pool */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/adv_thread_pool/pub.hpp000066400000000000000000000257311523065210100230730ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.4.0 * * \file * \brief Public interface of advanced thread pool dispatcher. */ #if !defined( SO_5_DISP_ADV_THREAD_POOL_HPP ) #define SO_5_DISP_ADV_THREAD_POOL_HPP #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace adv_thread_pool { /*! * \brief Alias for namespace with traits of event queue. * * \since * v.5.5.11 */ namespace queue_traits = so_5::disp::mpmc_queue_traits; // // disp_params_t // /*! * \brief Parameters for %adv_thread_pool dispatcher. * * \since * v.5.5.11 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() {} friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { using std::swap; swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< work_thread_factory_mixin_t & >(a), static_cast< work_thread_factory_mixin_t & >(b) ); swap( a.m_thread_count, b.m_thread_count ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for thread count. disp_params_t & thread_count( std::size_t count ) { m_thread_count = count; return *this; } //! Getter for thread count. std::size_t thread_count() const { return m_thread_count; } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code using namespace so_5::disp::thread_pool; auto disp = make_dispatcher( env, "workers_disp", disp_params_t{} .thread_count( 10 ) .tune_queue_params( []( queue_traits::queue_params_t & p ) { p.lock_factory( queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const { return m_queue_params; } private : //! Count of working threads. /*! * Value 0 means that actual thread will be detected automatically. */ std::size_t m_thread_count = { 0 }; //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; // // fifo_t // /*! * \brief Type of FIFO mechanism for agent's demands. * * \since * v.5.4.0 */ enum class fifo_t { //! A FIFO for demands for all agents from the same cooperation. /*! * It means that agents from the same cooperation for which this * FIFO mechanism is used will be worked on the same thread. * * If the same disp_binder with fifo_t::cooperation is used for * several cooperations then each coop will have a separate * event queue (thus agents from different coops may work on * different worker threads). */ cooperation, //! A FIFO for demands only for one agent. /*! * It means that FIFO is only supported for the concrete agent. * If several agents from a cooperation have this FIFO type they * will process demands independently and on different threads. */ individual }; // // bind_params_t // /*! * \brief Parameters for binding agents to %adv_thread_pool dispatcher. * * \since * v.5.5.11 */ class bind_params_t { public : //! Set FIFO type. bind_params_t & fifo( fifo_t v ) { m_fifo = v; return *this; } //! Get FIFO type. fifo_t query_fifo() const { return m_fifo; } private : //! FIFO type. fifo_t m_fifo = { fifo_t::cooperation }; }; // // default_thread_pool_size // using so_5::disp::reuse::default_thread_pool_size; namespace impl { class actual_dispatcher_iface_t; // // basic_dispatcher_iface_t // /*! * \brief The very basic interface of %adv_thread_pool dispatcher. * * This class contains a minimum that is necessary for implementation * of dispatcher_handle class. * * \since * v.5.6.0 */ class basic_dispatcher_iface_t : public std::enable_shared_from_this { public : virtual ~basic_dispatcher_iface_t() noexcept = default; [[nodiscard]] virtual disp_binder_shptr_t binder( bind_params_t params ) = 0; }; using basic_dispatcher_iface_shptr_t = std::shared_ptr< basic_dispatcher_iface_t >; class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \since * v.5.6.0 * * \brief A handle for %adv_thread_pool dispatcher. */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! A reference to actual implementation of a dispatcher. impl::basic_dispatcher_iface_shptr_t m_dispatcher; dispatcher_handle_t( impl::basic_dispatcher_iface_shptr_t dispatcher ) noexcept : m_dispatcher{ std::move(dispatcher) } {} //! Is this handle empty? bool empty() const noexcept { return !m_dispatcher; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. /*! * Usage example: * \code * using namespace so_5::disp::adv_thread_pool; * * so_5::environment_t & env = ...; * auto disp = make_dispatcher( env ); * bind_params_t params; * params.fifo( fifo_t::individual ); * * env.introduce_coop( [&]( so_5::coop_t & coop ) { * coop.make_agent_with_binder< some_agent_type >( * disp.binder( params ), * ... ); * * coop.make_agent_with_binder< another_agent_type >( * disp.binder( params ), * ... ); * * ... * } ); * \endcode * * \attention * An attempt to call this method on empty handle is UB. */ [[nodiscard]] disp_binder_shptr_t binder( bind_params_t params ) const { return m_dispatcher->binder( params ); } //! Create a binder for that dispatcher. /*! * This method allows parameters tuning via lambda-function * or other functional objects. * * Usage example: * \code * using namespace so_5::disp::adv_thread_pool; * * so_5::environment_t & env = ...; * env.introduce_coop( [&]( so_5::coop_t & coop ) { * coop.make_agent_with_binder< some_agent_type >( * // Create dispatcher instance. * make_dispatcher( env ) * // Make and tune binder for that dispatcher. * .binder( []( auto & params ) { * params.fifo( fifo_t::individual ); * } ), * ... ); * \endcode * * \attention * An attempt to call this method on empty handle is UB. */ template< typename Setter > [[nodiscard]] std::enable_if_t< std::is_invocable_v< Setter, bind_params_t& >, disp_binder_shptr_t > binder( //! Function for the parameters tuning. Setter && params_setter ) const { bind_params_t p; params_setter( p ); return this->binder( p ); } //! Get a binder for that dispatcher with default binding params. /*! * \attention * An attempt to call this method on empty handle is UB. */ [[nodiscard]] disp_binder_shptr_t binder() const { return this->binder( bind_params_t{} ); } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_dispatcher.reset(); } }; // // make_dispatcher // /*! * \brief Create an instance of %adv_thread_pool dispatcher. * * \par Usage sample \code using namespace so_5::disp::adv_thread_pool; auto disp = make_dispatcher( env, "db_workers_pool", disp_params_t{} .thread_count( 16 ) .tune_queue_params( []( queue_traits::queue_params_t & params ) { params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of adv_thread_pool dispatcher. disp.binder() ); \endcode * * \since * v.5.6.0 */ SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Parameters for the dispatcher. disp_params_t disp_params ); // // make_dispatcher // /*! * \brief Create an instance of %adv_thread_pool dispatcher. * * \par Usage sample \code auto disp = so_5::disp::adv_thread_pool::make_dispatcher( env, "req_processors", 16 ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of adv_thread_pool dispatcher. disp.binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Count of working threads. std::size_t thread_count ) { return make_dispatcher( env, data_sources_name_base, disp_params_t{}.thread_count( thread_count ) ); } /*! * \brief Create an instance of %adv_thread_pool dispatcher. * * \par Usage sample \code auto disp = so_5::disp::adv_thread_pool::make_dispatcher( env, 16 ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of adv_thread_pool dispatcher. disp.binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Count of working threads. std::size_t thread_count ) { return make_dispatcher( env, std::string_view{}, thread_count ); } // // make_dispatcher // /*! * \brief Create an instance of %adv_thread_pool dispatcher with the default * count of work threads. * * Count of work threads will be detected by default_thread_pool_size() * function. * * \par Usage sample \code auto disp = so_5::disp::adv_thread_pool::make_dispatcher( env ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of adv_thread_pool dispatcher. disp.binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env ) { return make_dispatcher( env, default_thread_pool_size() ); } } /* namespace adv_thread_pool */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/mpmc_queue_traits/000077500000000000000000000000001523065210100221605ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/mpmc_queue_traits/pub.cpp000066400000000000000000000136411523065210100234570ustar00rootroot00000000000000/* * SObjectizer 5 */ /*! * \since * v.5.5.11 * * \file * \brief Various traits for MPMC queues. */ #include #include #include #include namespace so_5 { namespace disp { namespace mpmc_queue_traits { namespace combined_lock { using spinlock_t = so_5::default_spinlock_t; // // actual_cond_t // /*! * \since * v.5.5.11 * * \brief Impementation of condition object for the case of combined lock. */ class actual_cond_t : public condition_t { //! Spinlock from parent lock object. spinlock_t & m_spinlock; //! Max waiting time for busy waiting stage. const std::chrono::high_resolution_clock::duration m_waiting_time; //! An indicator of notification for condition object. bool m_signaled = { false }; //! Personal mutex to be used with condition variable. std::mutex m_mutex; //! Condition variable for long-time waiting. std::condition_variable m_condition; public : //! Initializing constructor. actual_cond_t( //! Spinlock from parent lock object. spinlock_t & spinlock, //! Max waiting time for busy waiting stage. std::chrono::high_resolution_clock::duration waiting_time ) : m_spinlock( spinlock ) , m_waiting_time( std::move(waiting_time) ) {} virtual void wait() noexcept override { using hrc = std::chrono::high_resolution_clock; /* * NOTE: spinlock of the parent lock object is already * acquired by the current thread. */ m_signaled = false; // // Busy waiting stage. // // Limitation for busy waiting stage. const auto stop_point = hrc::now() + m_waiting_time; do { m_spinlock.unlock(); std::this_thread::yield(); m_spinlock.lock(); if( m_signaled ) return; } while( stop_point > hrc::now() ); // If we are here then busy waiting stage failed (condition // is not signaled yet) and we must go to long-time waiting. // // NOTE: spinlock of the parent lock object is acquired by // the current thread. // // Long-time waiting stage. // // Personal mutex object must be acquired. std::unique_lock< std::mutex > mutex_lock{ m_mutex }; // Spinlock of the parent lock can be released now. m_spinlock.unlock(); // Wait on condition_variable. m_condition.wait( mutex_lock, [this]{ return m_signaled; } ); // Spinlock must be reacquired to return the parent lock // in the state at the call to wait(). m_spinlock.lock(); } virtual void notify() noexcept override { std::lock_guard< std::mutex > mutex_lock{ m_mutex }; m_signaled = true; m_condition.notify_one(); } }; // // actual_lock_t // /*! * \since * v.5.5.11 * * \brief Actual implementation of combined lock object. */ class actual_lock_t : public lock_t { //! Common spinlock for locking of producers and consumers. spinlock_t m_spinlock; //! Max waiting time for busy waiting stage. const std::chrono::high_resolution_clock::duration m_waiting_time; public : //! Initializing constructor. actual_lock_t( //! Max waiting time for busy waiting stage. std::chrono::high_resolution_clock::duration waiting_time ) : m_waiting_time{ std::move(waiting_time) } {} virtual void lock() noexcept override { m_spinlock.lock(); } virtual void unlock() noexcept override { m_spinlock.unlock(); } virtual condition_unique_ptr_t allocate_condition() override { return condition_unique_ptr_t{ new actual_cond_t{ m_spinlock, m_waiting_time } }; } }; } /* namespace combined_lock */ namespace simple_lock { // // actual_cond_t // /*! * \since * v.5.5.11 * * \brief Actual implementation of condition object for the case * of simple locking on mutex and condition_variable. */ class actual_cond_t : public condition_t { //! An indicator of notification for condition object. bool m_signaled = { false }; //! Common mutex from the parent lock. std::mutex & m_mutex; //! Personal condition_variable object for condition object owner. std::condition_variable m_condition; public : //! Initializing constructor. actual_cond_t( //! Common mutex from the parent lock. std::mutex & mutex ) : m_mutex( mutex ) {} virtual void wait() noexcept override { m_signaled = false; // Common mutex is already acquired. So we can't reacquire it. std::unique_lock< std::mutex > mutex_lock{ m_mutex, std::adopt_lock }; m_condition.wait( mutex_lock, [this]{ return m_signaled; } ); // Common mutex must remain acquired. So we disable unique_lock // to release mutex in the destructor. mutex_lock.release(); } virtual void notify() noexcept override { m_signaled = true; m_condition.notify_one(); } }; // // actual_lock_t // /*! * \since * v.5.5.11 * * \brief Actual implementation of lock object for simple locking * on mutex and condition variables. */ class actual_lock_t : public lock_t { //! Common mutex for all producers and consumers. std::mutex m_mutex; public : actual_lock_t() {} virtual void lock() noexcept override { m_mutex.lock(); } virtual void unlock() noexcept override { m_mutex.unlock(); } virtual condition_unique_ptr_t allocate_condition() override { return condition_unique_ptr_t{ new actual_cond_t{ m_mutex } }; } }; } /* namespace simple_lock */ // // combined_lock_factory // SO_5_FUNC lock_factory_t combined_lock_factory( std::chrono::high_resolution_clock::duration waiting_time ) { return [waiting_time] { return lock_unique_ptr_t{ new combined_lock::actual_lock_t{ std::move(waiting_time) } }; }; } // // simple_lock_factory // SO_5_FUNC lock_factory_t simple_lock_factory() { return [] { return lock_unique_ptr_t{ new simple_lock::actual_lock_t{} }; }; } } /* namespace mpmc_queue_traits */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/mpmc_queue_traits/pub.hpp000066400000000000000000000171101523065210100234570ustar00rootroot00000000000000/* * SObjectizer 5 */ /*! * \file * \brief Various traits for MPMC queues. * \since * v.5.5.11 */ #if !defined( SO_5_DISP_MPMC_QUEUE_TRAITS_HPP ) #define SO_5_DISP_MPMC_QUEUE_TRAITS_HPP #include #include #include #include #include namespace so_5 { namespace disp { namespace mpmc_queue_traits { // // condition_t // /*! * \brief An interface for somethine like condition variable for * waiting on MPMC queue lock. * \since * v.5.5.11 */ class SO_5_TYPE condition_t { public : condition_t( const condition_t & ) = delete; condition_t( condition_t && ) = delete; condition_t & operator=( const condition_t & ) = delete; condition_t & operator=( condition_t && ) = delete; condition_t() = default; virtual ~condition_t() noexcept = default; /*! * \brief Waiting on condition. * * This method is intended to be used by queue's customers for * waiting on queue's pop operation. * * \attention This method will be called when parent lock object * is acquired by the current thread. */ virtual void wait() noexcept = 0; /*! * \brief Notification for waiting customer. * * \attention This method will be called when parent lock object * is acquired by the current thread. */ virtual void notify() noexcept = 0; }; // // condition_unique_ptr_t // /*! * \brief Alias of unique_ptr for condition. * \since * v.5.5.11 */ using condition_unique_ptr_t = std::unique_ptr< condition_t >; // // lock_t // /*! * \brief An interface for lock for MPMC queue. * \since * v.5.5.11 */ class SO_5_TYPE lock_t { public : lock_t( const lock_t & ) = delete; lock_t( lock_t && ) = delete; lock_t & operator=( const lock_t & ) = delete; lock_t & operator=( lock_t && ) = delete; lock_t() = default; virtual ~lock_t() noexcept = default; //! Lock object in exclusive mode. virtual void lock() noexcept = 0; //! Unlock object locked in exclusive mode. virtual void unlock() noexcept = 0; //! Create condition object for another MPMC queue's customer. virtual condition_unique_ptr_t allocate_condition() = 0; }; // // lock_unique_ptr_t // /*! * \brief An alias for unique_ptr for lock. * \since * v.5.5.11 */ using lock_unique_ptr_t = std::unique_ptr< lock_t >; // // lock_factory_t // /*! * \brief Type of lock factory. * \since * v.5.5.11 */ using lock_factory_t = std::function< lock_unique_ptr_t() >; // // default_combined_lock_waiting_time // /*! * \brief Default timeout used by combined_lock for waiting on spinlock * before switching to mutex-based locking scheme. * \since * v.5.5.11 */ inline std::chrono::high_resolution_clock::duration default_combined_lock_waiting_time() { return std::chrono::milliseconds(1); } /*! * \brief Factory for creation of combined queue lock with the specified * waiting time. * * \par Usage example: \code using namespace so_5::disp::thread_pool; auto disp = make_dispatcher( env, "db_workers_pool", disp_params_t{} .thread_count( 16 ) .tune_queue_params( []( queue_traits::queue_params_t & params ) { params.lock_factory( queue_traits::combined_lock_factory( // Switch to mutex after 125us of busy waiting. std::chrono::microseconds{125} ) ); } ) ); \endcode * * \since * v.5.5.11 */ SO_5_FUNC lock_factory_t combined_lock_factory( //! Max waiting time for waiting on spinlock before switching to mutex. std::chrono::high_resolution_clock::duration waiting_time ); // // combined_lock_factory // /*! * \brief Factory for creation of combined queue lock with default waiting time. * * \par Usage example: \code using namespace so_5::disp::thread_pool; auto disp = make_dispatcher( env, "db_workers_pool", disp_params_t{} .thread_count( 16 ) .tune_queue_params( []( queue_traits::queue_params_t & params ) { params.lock_factory( queue_traits::combined_lock_factory() ); } ) ); \endcode * * \since * v.5.5.11 */ inline lock_factory_t combined_lock_factory() { return combined_lock_factory( default_combined_lock_waiting_time() ); } /*! * \brief Factory for creation of very simple implementation based on * usage of mutex and condition_variable only. * * \par Usage example: \code using namespace so_5::disp::thread_pool; auto disp = make_dispatcher( env, "db_workers_pool", disp_params_t{} .thread_count( 16 ) .tune_queue_params( []( queue_traits::queue_params_t & params ) { params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); \endcode * * \since * v.5.5.11 */ SO_5_FUNC lock_factory_t simple_lock_factory(); // // queue_params_t // /*! * \brief Container for storing parameters for MPMC queue. * \since * v.5.5.11 */ class queue_params_t { public : //! Default constructor. queue_params_t() : m_lock_factory{} , m_next_thread_wakeup_threshold{ 0 } {} //! Copy constructor. queue_params_t( const queue_params_t & o ) : m_lock_factory{ o.m_lock_factory } , m_next_thread_wakeup_threshold{ o.m_next_thread_wakeup_threshold } {} //! Move constructor. queue_params_t( queue_params_t && o ) : m_lock_factory{ std::move(o.m_lock_factory) } , m_next_thread_wakeup_threshold{ std::move(o.m_next_thread_wakeup_threshold) } {} friend inline void swap( queue_params_t & a, queue_params_t & b ) noexcept { using std::swap; swap( a.m_lock_factory, b.m_lock_factory ); swap( a.m_next_thread_wakeup_threshold, b.m_next_thread_wakeup_threshold ); } //! Copy operator. queue_params_t & operator=( const queue_params_t & o ) { queue_params_t tmp{ o }; swap( *this, tmp ); return *this; } //! Move operator. queue_params_t & operator=( queue_params_t && o ) noexcept { queue_params_t tmp{ std::move(o) }; swap( *this, tmp ); return *this; } //! Setter for lock factory. queue_params_t & lock_factory( lock_factory_t factory ) { m_lock_factory = std::move( factory ); return *this; } //! Getter for lock factory. const lock_factory_t & lock_factory() const { return m_lock_factory; } /*! * \brief Setter for thread wakeup threshold. * * This parametr tells should the dispatcher wake up some * sleeping thread if there is no more than \a value items in queue. * It could be useful if some dispatcher serves agent with very * fast event handlers. Such agents can handle demands from queue * must faster than dispatcher will wake sleeping worker threads. * * Usage example: * \code using namespace so_5; using namespace so_5::disp::thread_pool; environment_t & env = ...; auto disp = make_dispatcher( env, "my-thread-pool", disp_params_t{} .thread_count( 16 ) .tune_queue_params( []( queue_traits::queue_params_t & qp ) { qp.next_thread_wakeup_threshold( 2 ); } ) ); * \endcode * * \since * v.5.5.16 */ queue_params_t & next_thread_wakeup_threshold( std::size_t value ) { m_next_thread_wakeup_threshold = value; return *this; } /*! * \brief Getter for thread wakeup threshold value. * \since * v.5.5.16 */ std::size_t next_thread_wakeup_threshold() const { return m_next_thread_wakeup_threshold; } private : //! Lock factory to be used during queue creation. lock_factory_t m_lock_factory; /*! * \brief Threshold for wake up next working thread if there are * non-empty queues. * * \since * v.5.5.16 */ std::size_t m_next_thread_wakeup_threshold; }; } /* namespace mpmc_queue_traits */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/mpsc_queue_traits/000077500000000000000000000000001523065210100221665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/mpsc_queue_traits/pub.cpp000066400000000000000000000104411523065210100234600ustar00rootroot00000000000000/* * SObjectizer 5 */ /*! * \since * v.5.5.10 * * \file * \brief Various traits for MPSC queues. */ #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace mpsc_queue_traits { namespace impl { // // combined_lock_t // /*! * \since * v.5.5.10 * * \brief A special combined lock for queue protection. * * This lock used spinlocks for efficiency and std::mutex and * std::condition_variable for signalization. * * \attention This lock can be used only for single-consumer queues! * It is because there is no way found to implement notify_all on * just two int variables (m_waiting and m_signaled). */ class combined_lock_t : public lock_t { public : inline combined_lock_t( //! Max waiting time for waiting on spinlock before switching to mutex. std::chrono::high_resolution_clock::duration waiting_time ) : m_waiting_time{ waiting_time } , m_waiting( false ) , m_signaled( false ) {} virtual void lock() noexcept override { m_spinlock.lock(); } virtual void unlock() noexcept override { m_spinlock.unlock(); } protected : virtual void wait_for_notify() noexcept override { using clock = std::chrono::high_resolution_clock; m_waiting = true; auto stop_point = clock::now() + m_waiting_time; do { m_spinlock.unlock(); std::this_thread::yield(); m_spinlock.lock(); if( m_signaled ) { m_waiting = false; m_signaled = false; return; } } while( stop_point > clock::now() ); // m_lock is locked now. // Must use heavy std::mutex and std::condition_variable // to allow OS to efficiently use the resources while // we are waiting for signal. std::unique_lock< std::mutex > mlock( m_mutex ); m_spinlock.unlock(); m_condition.wait( mlock, [this]{ return m_signaled; } ); // At this point m_signaled must be 'true'. m_spinlock.lock(); m_waiting = false; m_signaled = false; } //! Notify one waiting thread if it exists. /*! * \attention Must be called only when object is locked. */ virtual void notify_one() noexcept override { if( m_waiting ) { // There is a waiting thread. m_mutex.lock(); m_signaled = true; m_condition.notify_one(); m_mutex.unlock(); } } private : const std::chrono::high_resolution_clock::duration m_waiting_time; default_spinlock_t m_spinlock; std::mutex m_mutex; std::condition_variable m_condition; bool m_waiting; bool m_signaled; }; // // simple_lock_t // /*! * \since * v.5.5.10 * * \brief A very simple lock based on usage of std::mutex and * std::condition_variable. */ class simple_lock_t : public lock_t { public : virtual void lock() noexcept override { so_5::details::tsan_friendly_lock_guard_t< std::mutex > ::do_lock( m_mutex ); } virtual void unlock() noexcept override { m_mutex.unlock(); } protected : virtual void wait_for_notify() noexcept override { so_5::details::invoke_noexcept_code( [&] { // Mutex already locked. We must not try to reacquire it. std::unique_lock< std::mutex > mlock{ m_mutex, std::adopt_lock }; m_condition.wait( mlock, [this]{ return m_signaled; } ); mlock.release(); } ); // At this point m_signaled must be 'true'. m_signaled = false; } virtual void notify_one() noexcept override { m_signaled = true; m_condition.notify_one(); } private : std::mutex m_mutex; std::condition_variable m_condition; bool m_signaled = { false }; }; } /* namespace impl */ // // combined_lock_factory // SO_5_FUNC lock_factory_t combined_lock_factory( std::chrono::high_resolution_clock::duration waiting_time ) { return [waiting_time] { return lock_unique_ptr_t{ new impl::combined_lock_t{ waiting_time } }; }; } // // simple_lock_factory // SO_5_FUNC lock_factory_t simple_lock_factory() { return [] { return lock_unique_ptr_t{ new impl::simple_lock_t{} }; }; } } /* namespace mpsc_queue_traits */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/mpsc_queue_traits/pub.hpp000066400000000000000000000136671523065210100235020ustar00rootroot00000000000000/* * SObjectizer 5 */ /*! * \file * \brief Various traits for MPSC queues. * * \since v.5.5.10 */ #if !defined( SO_5_DISP_MPSC_QUEUE_TRAITS_HPP ) #define SO_5_DISP_MPSC_QUEUE_TRAITS_HPP #include #include #include #include #include namespace so_5 { namespace disp { namespace mpsc_queue_traits { // // lock_t // /*! * \brief An interface for lock for MPSC queue. * * \since v.5.5.10 */ class SO_5_TYPE lock_t { friend class unique_lock_t; friend class lock_guard_t; public : lock_t( const lock_t & ) = delete; lock_t( lock_t && ) = delete; lock_t & operator=( const lock_t & ) = delete; lock_t & operator=( lock_t && ) = delete; lock_t() = default; virtual ~lock_t() noexcept = default; //! Lock object in exclusive mode. virtual void lock() noexcept = 0; //! Unlock object locked in exclusive mode. virtual void unlock() noexcept = 0; protected : //! Waiting for nofication. /*! * \attention Must be called only when object is locked! */ virtual void wait_for_notify() noexcept = 0; //! Notify one waiting thread if it exists. /*! * \attention Must be called only when object is locked. */ virtual void notify_one() noexcept = 0; }; // // lock_unique_ptr_t // /*! * \brief An alias for unique_ptr for lock. * * \since v.5.5.10 */ using lock_unique_ptr_t = std::unique_ptr< lock_t >; // // lock_factory_t // /*! * \brief Type of lock factory. * * \since v.5.5.10 */ using lock_factory_t = std::function< lock_unique_ptr_t() >; // // default_combined_lock_waiting_time // /*! * \brief Default timeout used by combined_lock for waiting on spinlock * before switching to mutex-based locking scheme. * * \since v.5.5.10 */ inline std::chrono::high_resolution_clock::duration default_combined_lock_waiting_time() { return std::chrono::milliseconds(1); } /*! * \brief Factory for creation of combined queue lock with the specified * waiting time. * * \par Usage example: \code auto one_thread_disp = so_5::disp::one_thread::make_dispatcher( env, "file_handler", so_5::disp::one_thread::disp_params_t{}.tune_queue_params( []( so_5::disp::one_thread::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::one_thread::queue_traits::combined_lock_factory( // Switch to mutex after 125us of busy waiting. std::chrono::microseconds{125}) ); } ) ); \endcode * * \since v.5.5.10 */ SO_5_FUNC lock_factory_t combined_lock_factory( //! Max waiting time for waiting on spinlock before switching to mutex. std::chrono::high_resolution_clock::duration waiting_time ); // // combined_lock_factory // /*! * \brief Factory for creation of combined queue lock with default waiting time. * * \since v.5.5.10 */ inline lock_factory_t combined_lock_factory() { return combined_lock_factory( default_combined_lock_waiting_time() ); } /*! * \brief Factory for creation of very simple implementation based on * usage of mutex and condition_variable only. * * \since v.5.5.10 * * \par Usage example: \code auto one_thread_disp = so_5::disp::one_thread::make_dispatcher( env, "file_handler", so_5::disp::one_thread::disp_params_t{}.tune_queue_params( []( so_5::disp::one_thread::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::one_thread::queue_traits::simple_lock_factory() ); } ) ); \endcode */ SO_5_FUNC lock_factory_t simple_lock_factory(); // // unique_lock_t // /*! * \brief An analog of std::unique_lock for MPSC queue lock. * * \since v.5.5.10 */ class unique_lock_t { public : inline unique_lock_t( lock_t & lock ) : m_lock( lock ) { m_lock.lock(); } inline ~unique_lock_t() { m_lock.unlock(); } unique_lock_t( const unique_lock_t & ) = delete; unique_lock_t( unique_lock_t && ) = delete; inline void wait_for_notify() noexcept { static_assert( noexcept(m_lock.wait_for_notify()) ); m_lock.wait_for_notify(); } private : lock_t & m_lock; }; // // lock_guard_t // /*! * \brief An analog of std::lock_guard for MPSC queue lock. * * \since v.5.4.0 */ class lock_guard_t { public : inline lock_guard_t( lock_t & lock ) : m_lock( lock ) { m_lock.lock(); } inline ~lock_guard_t() { m_lock.unlock(); } lock_guard_t( const lock_guard_t & ) = delete; lock_guard_t( lock_guard_t && ) = delete; inline void notify_one() noexcept { static_assert( noexcept(m_lock.notify_one()) ); m_lock.notify_one(); } private : lock_t & m_lock; }; // // queue_params_t // /*! * \brief Container for storing parameters for MPSC queue. * * \since v.5.5.10 */ class queue_params_t { public : //! Default constructor. queue_params_t() : m_lock_factory{} {} //! Copy constructor. queue_params_t( const queue_params_t & o ) : m_lock_factory{ o.m_lock_factory } {} //! Move constructor. queue_params_t( queue_params_t && o ) noexcept : m_lock_factory{ std::move(o.m_lock_factory) } {} friend inline void swap( queue_params_t & a, queue_params_t & b ) noexcept { using namespace std; swap( a.m_lock_factory, b.m_lock_factory ); } //! Copy operator. queue_params_t & operator=( const queue_params_t & o ) { queue_params_t tmp{ o }; swap( *this, tmp ); return *this; } //! Move operator. queue_params_t & operator=( queue_params_t && o ) noexcept { queue_params_t tmp{ std::move(o) }; swap( *this, tmp ); return *this; } //! Setter for lock factory. queue_params_t & lock_factory( lock_factory_t factory ) { m_lock_factory = std::move( factory ); return *this; } //! Getter for lock factory. const lock_factory_t & lock_factory() const { return m_lock_factory; } private : //! Lock factory to be used during queue creation. lock_factory_t m_lock_factory; }; } /* namespace mpsc_queue_traits */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/nef_one_thread/000077500000000000000000000000001523065210100213725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/nef_one_thread/params.hpp000066400000000000000000000051301523065210100233650ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Parameters for %nef_one_thread dispatcher. * * \since v.5.8.0 */ #if !defined( SO_5_DISP_NEF_ONE_THREAD_PARAMS_HPP ) #define SO_5_DISP_NEF_ONE_THREAD_PARAMS_HPP #include #include #include #include namespace so_5 { namespace disp { namespace nef_one_thread { /*! * \brief Alias for namespace with traits of event queue. * * \since v.5.8.0 */ namespace queue_traits = so_5::disp::mpsc_queue_traits; // // disp_params_t // /*! * \brief Parameters for a dispatcher. * * \since v.5.8.0 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() = default; friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< work_thread_factory_mixin_t & >(a), static_cast< work_thread_factory_mixin_t & >(b) ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code namespace nef_disp = so_5::disp::nef_one_thread; auto disp = nef_disp::make_dispatcher( env, "my_nef_disp", nef_disp::disp_params_t{}.tune_queue_params( []( nef_disp::queue_traits::queue_params_t & p ) { p.lock_factory( nef_disp::queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const noexcept { return m_queue_params; } private : //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; } /* namespace nef_one_thread */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/nef_one_thread/pub.cpp000066400000000000000000000406351523065210100226740ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Functions for creating and binding of the single thread dispatcher * that provides noexcept guarantee for scheduling evt_finish demand. * * \since v.5.5.8 */ #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace nef_one_thread { namespace impl { namespace queue_traits = so_5::disp::mpsc_queue_traits; namespace stats = so_5::stats; // // demand_t // /*! * \brief A single execution demand. * * \since v.5.8.0 */ struct demand_t { //! Execution demand to be used. /*! * \note * It may be empty (if the default constructor was used for * preallocated evt_start/evt_finish demands). */ execution_demand_t m_execution_demand; //! Next demand in the queue. /*! * \note * It's a dynamically allocated object that has to be deallocated * manually during the destruction of the queue. */ demand_t * m_next = nullptr; /*! * \brief Default constructor. * * It's necessary for preallocation of evt_start and evt_finish * demands. */ demand_t() = default; //! Initializing constructor. demand_t( execution_demand_t && source ) : m_execution_demand{ std::move( source ) } {} }; // // demand_unique_ptr_t // /*! * \brief An alias for unique_ptr to demand. * * \since v.5.8.0 */ using demand_unique_ptr_t = std::unique_ptr< demand_t >; // // demand_queue_t // class demand_queue_t { //! Queue lock. queue_traits::lock_unique_ptr_t m_lock; //! Shutdown flag. bool m_shutdown = false; //! Head of the queue. /*! Null if queue is empty. */ demand_t * m_head = nullptr; //! Tail of the queue. /*! Null if queue is empty. */ demand_t * m_tail = nullptr; //! Current size of the queue. std::atomic< std::size_t > m_size = { 0 }; public: //! Initializing constructor. demand_queue_t( //! Lock to be used for queue protection. queue_traits::lock_unique_ptr_t lock ) : m_lock{ std::move(lock) } {} ~demand_queue_t() noexcept { while( m_head ) { remove_head(); } } //! Set the shutdown signal. void stop() { queue_traits::lock_guard_t lock{ *m_lock }; m_shutdown = true; // If the queue is empty then someone can wait a notification. if( !m_head ) { lock.notify_one(); } } void push( demand_unique_ptr_t tail_demand ) { queue_traits::lock_guard_t lock{ *m_lock }; ++m_size; if( nullptr == m_head ) { m_head = tail_demand.release(); m_tail = m_head; // Someone might wait for the first demand. lock.notify_one(); } else { m_tail->m_next = tail_demand.release(); m_tail = m_tail->m_next; } } // Returns nullptr if shutdown flag is set. demand_unique_ptr_t pop() { demand_unique_ptr_t result; queue_traits::unique_lock_t lock{ *m_lock }; while( !m_shutdown && (nullptr == m_head) ) { lock.wait_for_notify(); } if( !m_shutdown ) { // Assume that m_head != nullptr. result = remove_head(); } return result; } //! Get the current size of the queue. std::size_t size() const { return m_size.load( std::memory_order_acquire ); } private: //! Helper method for deleting queue's head object. demand_unique_ptr_t remove_head() noexcept { demand_unique_ptr_t to_be_deleted{ m_head }; m_head = m_head->m_next; to_be_deleted->m_next = nullptr; --m_size; return to_be_deleted; } }; // // agent_queue_t // /*! * * \note * This class is Moveable, but not Copyable. */ class agent_queue_t final : public event_queue_t { std::reference_wrapper< demand_queue_t > m_dest_queue; demand_unique_ptr_t m_evt_start_demand; demand_unique_ptr_t m_evt_finish_demand; public: agent_queue_t( demand_queue_t & dest_queue, demand_unique_ptr_t evt_start_demand, demand_unique_ptr_t evt_finish_demand ) : m_dest_queue{ dest_queue } , m_evt_start_demand{ std::move(evt_start_demand) } , m_evt_finish_demand{ std::move(evt_finish_demand) } {} agent_queue_t( const agent_queue_t & ) = delete; agent_queue_t & operator=( const agent_queue_t & ) = delete; agent_queue_t( agent_queue_t && o ) = delete; agent_queue_t & operator=( agent_queue_t && o ) = delete; void push( execution_demand_t demand ) override { m_dest_queue.get().push( std::make_unique< demand_t >( std::move(demand) ) ); } void push_evt_start( execution_demand_t demand ) override { // ATTENTION: assume that m_evt_start_demand is valid. // It's UB if that isn't the true. m_evt_start_demand->m_execution_demand = std::move(demand); m_dest_queue.get().push( std::move(m_evt_start_demand) ); } void push_evt_finish( execution_demand_t demand ) noexcept override { // ATTENTION: assume that m_evt_start_demand is valid. // It's UB if that isn't the true. // // Don't expect exceptions here. m_evt_finish_demand->m_execution_demand = std::move(demand); m_dest_queue.get().push( std::move(m_evt_finish_demand) ); } }; namespace work_thread_details { // // common_data_t // /*! * \brief A common data for all work thread implementations. * * \since v.5.8.0 */ struct common_data_t { //! Demands queue to work for. demand_queue_t m_queue; //! Thread object. work_thread_holder_t m_thread_holder; //! ID of the work thread. /*! * \note Receives actual value only after successful start * of the thread. */ so_5::current_thread_id_t m_thread_id; //! Initializing constructor. common_data_t( //! Lock to be used for queue protection. queue_traits::lock_unique_ptr_t lock, //! Worker thread to be used. work_thread_holder_t thread_holder ) : m_queue{ std::move(lock) } , m_thread_holder{ std::move(thread_holder) } {} }; // // no_activity_tracking_impl_t // /*! * \brief A part of implementation of work thread without activity tracking. * * \since v.5.8.0 */ class no_activity_tracking_impl_t : protected common_data_t { public : no_activity_tracking_impl_t( //! Lock to be used for queue protection. queue_traits::lock_unique_ptr_t lock, //! Worker thread to be used. work_thread_holder_t thread_holder ) : common_data_t{ std::move(lock), std::move(thread_holder) } {} protected : void work_started() { /* Nothing to do. */ } void work_finished() { /* Nothing to do. */ } void wait_started() { /* Nothing to do. */ } void wait_finished() { /* Nothing to do. */ } }; // // with_activity_tracking_impl_t // /*! * \brief A part of implementation of work thread with activity tracking. * * \since v.5.5.18 */ class with_activity_tracking_impl_t : protected common_data_t { public : with_activity_tracking_impl_t( //! Lock to be used for queue protection. queue_traits::lock_unique_ptr_t lock, //! Worker thread to be used. work_thread_holder_t thread_holder ) : common_data_t{ std::move(lock), std::move(thread_holder) } {} so_5::stats::work_thread_activity_stats_t take_activity_stats() { so_5::stats::work_thread_activity_stats_t result; result.m_working_stats = m_working_stats.take_stats(); result.m_waiting_stats = m_waiting_stats.take_stats(); return result; } protected : //! Statictics for work activity. so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::internal_lock > m_working_stats; //! Statictics for wait activity. so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::internal_lock > m_waiting_stats; void work_started() { m_working_stats.start(); } void work_finished() { m_working_stats.stop(); } void wait_started() { m_waiting_stats.start(); } void wait_finished() { m_waiting_stats.stop(); } }; } /* namespace work_thread_details */ // // work_thread_template_t // /*! * \brief A worker thread for nef_one_thread dispatcher. * * \since v.5.8.0 */ template< typename Work_Thread > class work_thread_template_t : public Work_Thread { using base_type_t = Work_Thread; public : //! Initializing constructor. work_thread_template_t( //! Lock to be used for queue protection. queue_traits::lock_unique_ptr_t lock, //! Worker thread to be used. work_thread_holder_t thread_holder ) : base_type_t{ std::move(lock), std::move(thread_holder) } {} void start() { this->m_thread_holder.unchecked_get().start( [this]() { body(); } ); } void stop() { this->m_queue.stop(); } void join() { so_5::impl::ensure_join_from_different_thread( this->m_thread_id ); this->m_thread_holder.unchecked_get().join(); } [[nodiscard]] so_5::current_thread_id_t thread_id() const { return this->m_thread_id; } [[nodiscard]] demand_queue_t & demand_queue() noexcept { return this->m_queue; } private : void body() { this->m_thread_id = so_5::query_current_thread_id(); bool demand_extracted = true; do { auto d = this->pop_demand(); if( d ) { this->call_handler( d->m_execution_demand ); } else // No more demands, it's time to break the loop. demand_extracted = false; } while( demand_extracted ); } [[nodiscard]] demand_unique_ptr_t pop_demand() { this->wait_started(); auto wait_meter_stopper = so_5::details::at_scope_exit( [this] { this->wait_finished(); } ); return this->m_queue.pop(); } void call_handler( so_5::execution_demand_t & demand ) { this->work_started(); auto work_meter_stopper = so_5::details::at_scope_exit( [this] { this->work_finished(); } ); demand.call_handler( this->m_thread_id ); } }; // // work_thread_no_activity_tracking_t // using work_thread_no_activity_tracking_t = work_thread_template_t< work_thread_details::no_activity_tracking_impl_t >; // // work_thread_with_activity_tracking_t // using work_thread_with_activity_tracking_t = work_thread_template_t< work_thread_details::with_activity_tracking_impl_t >; // // send_thread_activity_stats // void send_thread_activity_stats( const so_5::mbox_t &, const stats::prefix_t &, work_thread_no_activity_tracking_t & ) { /* Nothing to do */ } void send_thread_activity_stats( const so_5::mbox_t & mbox, const stats::prefix_t & prefix, work_thread_with_activity_tracking_t & wt ) { so_5::send< stats::messages::work_thread_activity >( mbox, prefix, stats::suffixes::work_thread_activity(), wt.thread_id(), wt.take_activity_stats() ); } // // dispatcher_template_t // /*! * \brief An implementation of dispatcher with one working * thread and guarantee that evt_finish demands will be added to * the queue without exceptions. * * \since v.5.8.0 */ template< typename Work_Thread > class dispatcher_template_t final : public disp_binder_t { friend class disp_data_source_t; public: dispatcher_template_t( outliving_reference_t< environment_t > env, const std::string_view name_base, disp_params_t params ) : m_work_thread{ params.queue_params().lock_factory()(), so_5::disp::reuse::acquire_work_thread( params, env.get() ), } , m_data_source{ outliving_mutable(env.get().stats_repository()), name_base, outliving_mutable(*this) } { m_work_thread.start(); } ~dispatcher_template_t() noexcept override { m_work_thread.stop(); m_work_thread.join(); } void preallocate_resources( agent_t & agent ) override { // Assume that there is no pointer to the agent in the map yet. auto evt_start_demand = std::make_unique< demand_t >(); auto evt_finish_demand = std::make_unique< demand_t >(); auto queue = std::make_unique< agent_queue_t >( m_work_thread.demand_queue(), std::move(evt_start_demand), std::move(evt_finish_demand) ); // All further operattions have to be performed under the lock. std::lock_guard< std::mutex > lock{ m_agent_map_lock }; m_agents.emplace( std::addressof(agent), std::move(queue) ); } void undo_preallocation( agent_t & agent ) noexcept override { std::lock_guard< std::mutex > lock{ m_agent_map_lock }; m_agents.erase( std::addressof(agent) ); } void bind( agent_t & agent ) noexcept override { event_queue_t & queue = [&]() -> event_queue_t & { std::lock_guard< std::mutex > lock{ m_agent_map_lock }; auto it = m_agents.find( std::addressof(agent) ); // Just in case, to simplify debugging if something // went very, very wrong. // This will lead to the termination of the application, // but it is better than accessing a random pointer. if( it == m_agents.end() ) SO_5_THROW_EXCEPTION( rc_no_preallocated_resources_for_agent, "nef_one_thread dispatcher has no info about an agent " "in bind() method" ); return *(it->second); }(); agent.so_bind_to_dispatcher( queue ); } void unbind( agent_t & agent ) noexcept override { // Just reuse existing implementation. this->undo_preallocation( agent ); } private: /*! * \brief Data source for run-time monitoring of whole dispatcher. * * \since v.5.8.0 */ class disp_data_source_t : public stats::source_t { //! Dispatcher to work with. outliving_reference_t< dispatcher_template_t > m_dispatcher; //! Basic prefix for data sources. stats::prefix_t m_base_prefix; public : disp_data_source_t( const std::string_view name_base, outliving_reference_t< dispatcher_template_t > disp ) : m_dispatcher{ disp } , m_base_prefix{ so_5::disp::reuse::make_disp_prefix( "nef-ot", name_base, &(disp.get()) ) } {} void distribute( const mbox_t & mbox ) override { auto & disp = m_dispatcher.get(); const std::size_t agents_count = [&disp]() { std::lock_guard< std::mutex > lock{ disp.m_agent_map_lock }; return disp.m_agents.size(); }(); so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::agent_count(), agents_count ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::work_thread_queue_size(), disp.m_work_thread.demand_queue().size() ); send_thread_activity_stats( mbox, m_base_prefix, disp.m_work_thread ); } }; //! Type of map from agent pointer to an individual event_queue. using agent_map_t = std::map< agent_t *, std::unique_ptr >; //! Worker thread for the dispatcher. Work_Thread m_work_thread; //! Data source for run-time monitoring. stats::auto_registered_source_holder_t< disp_data_source_t > m_data_source; //! Lock for agent_map protection. std::mutex m_agent_map_lock; //! Agents for those resources are allocated by the dispatcher. agent_map_t m_agents; }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( disp_binder_shptr_t binder ) noexcept { return { std::move( binder ) }; } }; } /* namespace impl */ // // make_dispatcher // SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, disp_params_t params ) { using namespace so_5::disp::reuse; using dispatcher_no_activity_tracking_t = impl::dispatcher_template_t< impl::work_thread_no_activity_tracking_t >; using dispatcher_with_activity_tracking_t = impl::dispatcher_template_t< impl::work_thread_with_activity_tracking_t >; disp_binder_shptr_t binder = so_5::disp::reuse::make_actual_dispatcher< disp_binder_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params) ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace nef_one_thread */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/nef_one_thread/pub.hpp000066400000000000000000000071351523065210100226770ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Functions for creating and binding of the single thread dispatcher * that provides noexcept guarantee for scheduling evt_finish demand. * * \since v.5.8.0 */ #if !defined( SO_5_DISP_NEF_ONE_THREAD_PUB_HPP ) #define SO_5_DISP_NEF_ONE_THREAD_PUB_HPP #include #include #include #include namespace so_5 { namespace disp { namespace nef_one_thread { namespace impl { class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \brief A handle for %nef_one_thread dispatcher. * * \since v.5.8.0 */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! Binder for the dispatcher. disp_binder_shptr_t m_binder; dispatcher_handle_t( disp_binder_shptr_t binder ) noexcept : m_binder{ std::move(binder) } {} //! Is this handle empty? bool empty() const noexcept { return !m_binder; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. [[nodiscard]] disp_binder_shptr_t binder() const noexcept { return m_binder; } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_binder.reset(); } }; // // make_dispatcher // /*! * \brief Create an instance of %nef_one_thread dispatcher. * * \par Usage sample \code auto my_disp = so_5::disp::nef_one_thread::make_dispatcher( env, "request_processor", so_5::disp::nef_one_thread::disp_params_t{}.tune_queue_params( []( so_5::disp::nef_one_thread::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::nef_one_thread::queue_traits::simple_lock_factory() ); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of nef_one_thread dispatcher. my_disp.binder() ); \endcode * * \since v.5.8.0 */ SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Parameters for the dispatcher. disp_params_t params ); // // make_dispatcher // /*! * \brief Create an instance of %nef_one_thread dispatcher. * * \par Usage sample \code auto my_disp = so_5::disp::nef_one_thread::make_dispatcher( env, "request_processor" ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of nef_one_thread dispatcher. my_disp.binder() ); \endcode * * \since v.5.8.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base ) { return make_dispatcher( env, data_sources_name_base, disp_params_t{} ); } // // make_dispatcher // /*! * \brief Create an instance of %nef_one_thread dispatcher. * * \par Usage sample \code auto my_disp = so_5::disp::nef_one_thread::make_dispatcher( env ); auto coop = env.make_coop( // The main dispatcher for that coop will be // private nef_one_thread dispatcher. my_disp.binder() ); \endcode * * \since v.5.8.0 */ inline dispatcher_handle_t make_dispatcher( environment_t & env ) { return make_dispatcher( env, std::string_view{} ); } } /* namespace nef_one_thread */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/nef_thread_pool/000077500000000000000000000000001523065210100215625ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/nef_thread_pool/pub.cpp000066400000000000000000000244341523065210100230630ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Public interface of thread pool dispatcher that * provides noexcept guarantee for scheduling evt_finish demand. * * \since v.5.8.0 */ #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace nef_thread_pool { namespace impl { using so_5::disp::thread_pool::impl::work_thread_no_activity_tracking_t; using so_5::disp::thread_pool::impl::work_thread_with_activity_tracking_t; class agent_queue_with_preallocated_finish_demand_t; // // dispatcher_queue_t // using dispatcher_queue_t = so_5::disp::reuse::queue_of_queues_t< agent_queue_with_preallocated_finish_demand_t >; // // agent_queue_with_preallocated_finish_demand_t // /*! * \brief Specail implementation of event queue for nef-thread-pool dispatcher. * * An instance of agent_queue_with_preallocated_finish_demand_t creates * a separate demand for evt_finish event in the constructor and uses it * then in push_evt_finish(). * * \attention * It's expected that an instance of agent_queue_with_preallocated_finish_demand_t * will be used for just one agent. It guarantees that push_evt_finish() * will be called at most once. */ class agent_queue_with_preallocated_finish_demand_t final : public so_5::disp::thread_pool::impl::basic_event_queue_t , private so_5::atomic_refcounted_t { friend class so_5::intrusive_ptr_t< agent_queue_with_preallocated_finish_demand_t >; //! Short alias for the main base type. using base_type_t = so_5::disp::thread_pool::impl::basic_event_queue_t; public: //! Initializing constructor. agent_queue_with_preallocated_finish_demand_t( //! Dispatcher queue to work with. outliving_reference_t< dispatcher_queue_t > disp_queue, //! Parameters for the queue. const bind_params_t & params ) : base_type_t{ params.query_max_demands_at_once() } , m_disp_queue{ disp_queue.get() } , m_finish_demand{ std::make_unique< base_type_t::demand_t >() } {} /*! * \note * Uses preallocated demand in m_finish_demand. Leaves m_finish_demand * empty after the completion. */ void push_evt_finish( execution_demand_t demand ) noexcept override { // It's assumed that m_finish_demand isn't empty. *(m_finish_demand) = std::move(demand); // Just delegate the work. this->push_preallocated( std::move(m_finish_demand) ); } /*! * \brief Give away a pointer to the next agent_queue. * * \note * This method is a part of interface required by * so_5::disp::reuse::queue_of_queues_t. * * \since v.5.8.0 */ [[nodiscard]] agent_queue_with_preallocated_finish_demand_t * intrusive_queue_giveout_next() noexcept { auto * r = m_intrusive_queue_next; m_intrusive_queue_next = nullptr; return r; } /*! * \brief Set a pointer to the next agent_queue. * * \note * This method is a part of interface required by * so_5::disp::reuse::queue_of_queues_t. * * \since v.5.8.0 */ void intrusive_queue_set_next( agent_queue_with_preallocated_finish_demand_t * next ) noexcept { m_intrusive_queue_next = next; } protected: void schedule_on_disp_queue() noexcept override { m_disp_queue.schedule( this ); } private : //! Dispatcher queue with that the agent queue has to be used. dispatcher_queue_t & m_disp_queue; //! A preallocated demand for evt_finish. /*! * It will be created empty in the agent queue's constructor. * The content will be set for it in push_evt_finish() method. */ std::unique_ptr< base_type_t::demand_t > m_finish_demand; /*! * \brief The next item in intrusive queue of agent_queues. * * This field is necessary to implement interface required by * so_5::disp::reuse::queue_of_queues_t * * \since v.5.8.0 */ agent_queue_with_preallocated_finish_demand_t * m_intrusive_queue_next{ nullptr }; }; // // adaptation_t // /*! * \brief Adaptation of common implementation of thread-pool-like dispatcher * to the specific of this thread-pool dispatcher. * * \since v.5.5.4 */ struct adaptation_t { [[nodiscard]] static constexpr std::string_view dispatcher_type_name() noexcept { return { "nef_tp" }; // nef_thread_pool. } [[nodiscard]] static bool is_individual_fifo( const bind_params_t & /*params*/ ) noexcept { // NOTE: all agents use individual fifo. return true; } static void wait_for_queue_emptyness( agent_queue_with_preallocated_finish_demand_t & queue ) noexcept { queue.wait_for_emptyness(); } }; // // dispatcher_template_t // /*! * \brief Template for dispatcher. * * This template depends on work_thread type (with or without activity * tracking). * * \since v.5.5.18 */ template< typename Work_Thread > using dispatcher_template_t = so_5::disp::thread_pool::common_implementation::dispatcher_t< Work_Thread, dispatcher_queue_t, bind_params_t, adaptation_t >; // // actual_dispatcher_iface_t // /*! * \brief An actual interface of nef-thread-pool dispatcher. * * This interface defines a set of methods necessary for binder. * * \since v.5.8.0 */ class actual_dispatcher_iface_t : public basic_dispatcher_iface_t { public : //! Preallocate all necessary resources for a new agent. virtual void preallocate_resources_for_agent( agent_t & agent, const bind_params_t & params ) = 0; //! Undo preallocation of resources for a new agent. virtual void undo_preallocation_for_agent( agent_t & agent ) noexcept = 0; //! Get resources allocated for an agent. [[nodiscard]] virtual event_queue_t * query_resources_for_agent( agent_t & agent ) noexcept = 0; //! Unbind agent from the dispatcher. virtual void unbind_agent( agent_t & agent ) noexcept = 0; }; // // actual_dispatcher_iface_shptr_t // using actual_dispatcher_iface_shptr_t = std::shared_ptr< actual_dispatcher_iface_t >; // // actual_binder_t // /*! * \brief Actual implementation of dispatcher binder for %nef_thread_pool dispatcher. * * \since v.5.8.0 */ class actual_binder_t final : public disp_binder_t { //! Dispatcher to be used. actual_dispatcher_iface_shptr_t m_disp; //! Binding parameters. const bind_params_t m_params; public : actual_binder_t( actual_dispatcher_iface_shptr_t disp, bind_params_t params ) noexcept : m_disp{ std::move(disp) } , m_params{ params } {} void preallocate_resources( agent_t & agent ) override { m_disp->preallocate_resources_for_agent( agent, m_params ); } void undo_preallocation( agent_t & agent ) noexcept override { m_disp->undo_preallocation_for_agent( agent ); } void bind( agent_t & agent ) noexcept override { auto queue = m_disp->query_resources_for_agent( agent ); agent.so_bind_to_dispatcher( *queue ); } void unbind( agent_t & agent ) noexcept override { m_disp->unbind_agent( agent ); } }; // // actual_dispatcher_implementation_t // /*! * \brief Actual implementation of binder for %nef_thread_pool dispatcher. * * \since v.5.8.0 */ template< typename Work_Thread > class actual_dispatcher_implementation_t final : public actual_dispatcher_iface_t { //! Real dispatcher. dispatcher_template_t< Work_Thread > m_impl; public : actual_dispatcher_implementation_t( //! SObjectizer Environment to work in. outliving_reference_t< environment_t > env, //! Base part of data sources names. const std::string_view name_base, //! Dispatcher's parameters. disp_params_t params ) : m_impl{ env.get(), params, name_base, params.thread_count(), params.queue_params() } { m_impl.start( env.get() ); } ~actual_dispatcher_implementation_t() noexcept override { m_impl.shutdown_then_wait(); } [[nodiscard]] disp_binder_shptr_t binder( bind_params_t params ) override { return std::make_shared< actual_binder_t >( this->shared_from_this(), params ); } void preallocate_resources_for_agent( agent_t & agent, const bind_params_t & params ) override { m_impl.preallocate_resources_for_agent( agent, params ); } void undo_preallocation_for_agent( agent_t & agent ) noexcept override { m_impl.undo_preallocation_for_agent( agent ); } event_queue_t * query_resources_for_agent( agent_t & agent ) noexcept override { return m_impl.query_resources_for_agent( agent ); } void unbind_agent( agent_t & agent ) noexcept override { m_impl.unbind_agent( agent ); } }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( actual_dispatcher_iface_shptr_t disp ) noexcept { return { std::move( disp ) }; } }; } /* namespace impl */ namespace { using namespace so_5::disp::nef_thread_pool::impl; /*! * \brief Sets the thread count to default value if used do not * specify actual thread count. * * \since v.5.8.0 */ inline void adjust_thread_count( disp_params_t & params ) { if( !params.thread_count() ) params.thread_count( default_thread_pool_size() ); } } /* namespace anonymous */ // // make_dispatcher // SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, disp_params_t params ) { using namespace so_5::disp::reuse; adjust_thread_count( params ); using dispatcher_no_activity_tracking_t = impl::actual_dispatcher_implementation_t< impl::work_thread_no_activity_tracking_t< impl::dispatcher_queue_t > >; using dispatcher_with_activity_tracking_t = impl::actual_dispatcher_implementation_t< impl::work_thread_with_activity_tracking_t< impl::dispatcher_queue_t > >; auto binder = so_5::disp::reuse::make_actual_dispatcher< impl::actual_dispatcher_iface_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params) ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace nef_thread_pool */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/nef_thread_pool/pub.hpp000066400000000000000000000247221523065210100230700ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Public interface of thread pool dispatcher that * provides noexcept guarantee for scheduling evt_finish demand. * * \since v.5.8.0 */ #if !defined( SO_5_DISP_NEF_THREAD_POOL_HPP ) #define SO_5_DISP_NEF_THREAD_POOL_HPP #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace nef_thread_pool { /*! * \brief Alias for namespace with traits of event queue. * * \since v.5.8.0 */ namespace queue_traits = so_5::disp::mpmc_queue_traits; // // disp_params_t // /*! * \brief Parameters for %nef_thread_pool dispatcher. * * \since v.5.8.0 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() = default; friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { using std::swap; swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< work_thread_factory_mixin_t & >(a), static_cast< work_thread_factory_mixin_t & >(b) ); swap( a.m_thread_count, b.m_thread_count ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for thread count. disp_params_t & thread_count( std::size_t count ) { m_thread_count = count; return *this; } //! Getter for thread count. std::size_t thread_count() const { return m_thread_count; } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code using namespace so_5::disp::nef_thread_pool; auto disp = make_dispatcher( env, "workers_disp", disp_params_t{} .thread_count( 10 ) .tune_queue_params( []( queue_traits::queue_params_t & p ) { p.lock_factory( queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const { return m_queue_params; } private : //! Count of working threads. /*! * Value 0 means that actual thread will be detected automatically. */ std::size_t m_thread_count = { 0 }; //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; // // bind_params_t // /*! * \brief Parameters for binding agents to %nef_thread_pool dispatcher. * * \since v.5.8.0 */ class bind_params_t { public : //! Set maximum count of demands to be processed at once. bind_params_t & max_demands_at_once( std::size_t v ) { m_max_demands_at_once = v; return *this; } //! Get maximum count of demands to do processed at once. [[nodiscard]] std::size_t query_max_demands_at_once() const { return m_max_demands_at_once; } private : //! Maximum count of demands to be processed at once. std::size_t m_max_demands_at_once = { 1 }; }; // // default_thread_pool_size // using so_5::disp::reuse::default_thread_pool_size; namespace impl { class actual_dispatcher_iface_t; // // basic_dispatcher_iface_t // /*! * \brief The very basic interface of %thread_pool dispatcher. * * This class contains a minimum that is necessary for implementation * of dispatcher_handle class. * * \since v.5.8.0 */ class basic_dispatcher_iface_t : public std::enable_shared_from_this { public : virtual ~basic_dispatcher_iface_t() noexcept = default; [[nodiscard]] virtual disp_binder_shptr_t binder( bind_params_t params ) = 0; }; using basic_dispatcher_iface_shptr_t = std::shared_ptr< basic_dispatcher_iface_t >; class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \brief A handle for %nef_thread_pool dispatcher. * * \since v.5.8.0 */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! A reference to actual implementation of a dispatcher. impl::basic_dispatcher_iface_shptr_t m_dispatcher; dispatcher_handle_t( impl::basic_dispatcher_iface_shptr_t dispatcher ) noexcept : m_dispatcher{ std::move(dispatcher) } {} //! Is this handle empty? bool empty() const noexcept { return !m_dispatcher; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. /*! * Usage example: * \code * using namespace so_5::disp::nef_thread_pool; * * so_5::environment_t & env = ...; * auto disp = make_dispatcher( env ); * bind_params_t params; * params.max_demands_at_once( 10u ); * * env.introduce_coop( [&]( so_5::coop_t & coop ) { * coop.make_agent_with_binder< some_agent_type >( * disp.binder( params ), * ... ); * * coop.make_agent_with_binder< another_agent_type >( * disp.binder( params ), * ... ); * * ... * } ); * \endcode * * \attention * An attempt to call this method on empty handle is UB. */ [[nodiscard]] disp_binder_shptr_t binder( bind_params_t params ) const { return m_dispatcher->binder( params ); } //! Create a binder for that dispatcher. /*! * This method allows parameters tuning via lambda-function * or other functional objects. * * Usage example: * \code * using namespace so_5::disp::nef_thread_pool; * * so_5::environment_t & env = ...; * env.introduce_coop( [&]( so_5::coop_t & coop ) { * coop.make_agent_with_binder< some_agent_type >( * // Create dispatcher instance. * make_dispatcher( env ) * // Make and tune binder for that dispatcher. * .binder( []( auto & params ) { * params.max_demands_at_once( 10u ); * } ), * ... ); * \endcode * * \attention * An attempt to call this method on empty handle is UB. */ template< typename Setter > [[nodiscard]] std::enable_if_t< std::is_invocable_v< Setter, bind_params_t& >, disp_binder_shptr_t > binder( //! Function for the parameters tuning. Setter && params_setter ) const { bind_params_t p; params_setter( p ); return this->binder( p ); } //! Get a binder for that dispatcher with default binding params. /*! * \attention * An attempt to call this method on empty handle is UB. */ [[nodiscard]] disp_binder_shptr_t binder() const { return this->binder( bind_params_t{} ); } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_dispatcher.reset(); } }; // // make_dispatcher // /*! * \brief Create an instance %nef_thread_pool dispatcher. * * \par Usage sample \code using namespace so_5::disp::nef_thread_pool; auto disp = make_dispatcher( env, "db_workers_pool", disp_params_t{} .thread_count( 16 ) .tune_queue_params( []( queue_traits::queue_params_t & params ) { params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of nef_thread_pool dispatcher. disp.binder() ); \endcode * * \since v.5.8.0 */ [[nodiscard]] SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Parameters for the dispatcher. disp_params_t disp_params ); // // make_dispatcher // /*! * \brief Create an instance of %nef_thread_pool dispatcher. * * \par Usage sample \code auto disp = so_5::disp::nef_thread_pool::make_dispatcher( env, "db_workers_pool", 16 ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of nef_thread_pool dispatcher. disp.binder() ); \endcode * * \since v.5.8.0 */ [[nodiscard]] inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Count of working threads. std::size_t thread_count ) { return make_dispatcher( env, data_sources_name_base, disp_params_t{}.thread_count( thread_count ) ); } /*! * \brief Create an instance of %nef_thread_pool dispatcher. * * \par Usage sample \code auto disp = so_5::disp::nef_thread_pool::make_dispatcher( env, 16 ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of nef_thread_pool dispatcher. disp.binder() ); \endcode * * \since v.5.8.0 */ [[nodiscard]] inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Count of working threads. std::size_t thread_count ) { return make_dispatcher( env, std::string_view{}, thread_count ); } // // make_dispatcher // /*! * \brief Create an instance of %nef_thread_pool dispatcher with the default * count of working threads. * * Count of work threads will be detected by default_thread_pool_size() * function. * * \par Usage sample \code auto disp = so_5::disp::nef_thread_pool::make_instance( env ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of nef_thread_pool dispatcher. disp.binder() ); \endcode * * \since v.5.8.0 */ [[nodiscard]] inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env ) { return make_dispatcher( env, std::string_view{}, default_thread_pool_size() ); } } /* namespace nef_thread_pool */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/one_thread/000077500000000000000000000000001523065210100205425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/one_thread/params.hpp000066400000000000000000000054171523065210100225450ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Parameters for %one_thread dispatcher. * * \note Parameters are described in separate header file to avoid * header file cycles (parameters needs for environment_params_t declared * in environment.hpp, which can be included in dispatcher definition file). * * \since v.5.5.10 */ #if !defined( SO_5_DISP_ONE_THREAD_PARAMS_HPP ) #define SO_5_DISP_ONE_THREAD_PARAMS_HPP #include #include #include namespace so_5 { namespace disp { namespace one_thread { /*! * \brief Alias for namespace with traits of event queue. * * \since v.5.5.10 */ namespace queue_traits = so_5::disp::mpsc_queue_traits; // // disp_params_t // /*! * \brief Parameters for one thread dispatcher. * * \since v.5.5.10 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() = default; friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { using std::swap; swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< thread_factory_mixin_t & >(a), static_cast< thread_factory_mixin_t & >(b) ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code so_5::disp::one_thread::make_dispatcher( env, "my_one_thread_disp", so_5::disp::one_thread::disp_params_t{}.tune_queue_params( []( so_5::disp::one_thread::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::one_thread::queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const { return m_queue_params; } private : //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; } /* namespace one_thread */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/one_thread/pub.cpp000066400000000000000000000142151523065210100220370ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace one_thread { namespace impl { namespace work_thread = so_5::disp::reuse::work_thread; namespace stats = so_5::stats; namespace data_source_details { template< typename Work_Thread > struct common_data_t { //! Prefix for dispatcher-related data. stats::prefix_t m_base_prefix; //! Prefix for working thread-related data. stats::prefix_t m_work_thread_prefix; //! Working thread of the dispatcher. Work_Thread & m_work_thread; //! Count of agents bound to the dispatcher. std::atomic< std::size_t > & m_agents_bound; common_data_t( Work_Thread & work_thread, std::atomic< std::size_t > & agents_bound ) : m_work_thread( work_thread ) , m_agents_bound( agents_bound ) {} }; inline void track_activity( const mbox_t &, const common_data_t< work_thread::work_thread_no_activity_tracking_t > & ) {} inline void track_activity( const mbox_t & mbox, const common_data_t< work_thread::work_thread_with_activity_tracking_t > & data ) { so_5::send< stats::messages::work_thread_activity >( mbox, data.m_base_prefix, stats::suffixes::work_thread_activity(), data.m_work_thread.thread_id(), data.m_work_thread.take_activity_stats() ); } } /* namespace data_source_details */ /*! * \brief Data source for one-thread dispatcher. */ template< typename Work_Thread > class data_source_t final : public stats::source_t , protected data_source_details::common_data_t< Work_Thread > { public : using actual_work_thread_type_t = Work_Thread; data_source_t( actual_work_thread_type_t & work_thread, std::atomic< std::size_t > & agents_bound, const std::string_view name_base, const void * pointer_to_disp ) : data_source_details::common_data_t< Work_Thread >{ work_thread, agents_bound } { // Names of data sources must be formed. using namespace so_5::disp::reuse; this->m_base_prefix = make_disp_prefix( "ot", // ot -- one_thread name_base, pointer_to_disp ); this->m_work_thread_prefix = make_disp_working_thread_prefix( this->m_base_prefix, 0 ); } void distribute( const mbox_t & mbox ) override { so_5::send< stats::messages::quantity< std::size_t > >( mbox, this->m_base_prefix, stats::suffixes::agent_count(), this->m_agents_bound.load( std::memory_order_acquire ) ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, this->m_work_thread_prefix, stats::suffixes::work_thread_queue_size(), this->m_work_thread.demands_count() ); data_source_details::track_activity( mbox, *this ); } }; // // actual_dispatcher_t // /*! * \brief A dispatcher with the single working thread and an event queue. * * \note * This class implements disp_binder_t. It makes possible to use instance * of that class as dispatcher binder (this avoids creation of small * binder objects). * * \since * v.5.6.0 */ template< typename Work_Thread > class actual_dispatcher_t final : public disp_binder_t { public: actual_dispatcher_t( outliving_reference_t< environment_t > env, const std::string_view name_base, disp_params_t params ) : m_work_thread{ acquire_work_thread( params, env.get() ), params.queue_params().lock_factory() } , m_data_source{ outliving_mutable(env.get().stats_repository()), m_work_thread, m_agents_bound, name_base, this } { m_work_thread.start(); } ~actual_dispatcher_t() noexcept override { m_work_thread.shutdown(); m_work_thread.wait(); } // Implementation of methods, inherited from disp_binder. void preallocate_resources( agent_t & /*agent*/ ) override { // Nothing to do. } void undo_preallocation( agent_t & /*agent*/ ) noexcept override { // Nothing to do. } virtual void bind( agent_t & agent ) noexcept override { agent.so_bind_to_dispatcher( *(m_work_thread.get_agent_binding()) ); ++m_agents_bound; } virtual void unbind( agent_t & /*agent*/ ) noexcept override { --m_agents_bound; } private: //! Working thread for the dispatcher. Work_Thread m_work_thread; /*! * \since * v.5.5.4 * * \brief Count of agents bound to this dispatcher. */ std::atomic< std::size_t > m_agents_bound = { 0 }; /*! * \brief Data source for run-time monitoring. * * \since * v.5.5.4, v.5.6.0 */ stats::auto_registered_source_holder_t< data_source_t< Work_Thread > > m_data_source; }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( disp_binder_shptr_t binder ) noexcept { return { std::move( binder ) }; } }; } /* namespace impl */ // // make_dispatcher // SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, disp_params_t params ) { using namespace so_5::disp::reuse; using dispatcher_no_activity_tracking_t = impl::actual_dispatcher_t< work_thread::work_thread_no_activity_tracking_t >; using dispatcher_with_activity_tracking_t = impl::actual_dispatcher_t< work_thread::work_thread_with_activity_tracking_t >; disp_binder_shptr_t binder = so_5::disp::reuse::make_actual_dispatcher< disp_binder_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params) ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace one_thread */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/one_thread/pub.hpp000066400000000000000000000067661523065210100220600ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Functions for creating and binding of the single thread dispatcher. */ #if !defined( SO_5_DISP_ONE_THREAD_PUB_HPP ) #define SO_5_DISP_ONE_THREAD_PUB_HPP #include #include #include #include #include namespace so_5 { namespace disp { namespace one_thread { namespace impl { class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \since * v.5.6.0 * * \brief A handle for %one_thread dispatcher. */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! Binder for the dispatcher. disp_binder_shptr_t m_binder; dispatcher_handle_t( disp_binder_shptr_t binder ) noexcept : m_binder{ std::move(binder) } {} //! Is this handle empty? bool empty() const noexcept { return !m_binder; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. [[nodiscard]] disp_binder_shptr_t binder() const noexcept { return m_binder; } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_binder.reset(); } }; // // make_dispatcher // /*! * \brief Create an instance of %one_thread dispatcher. * * \par Usage sample \code auto one_thread_disp = so_5::disp::one_thread::make_dispatcher( env, "file_handler", so_5::disp::one_thread::disp_params_t{}.tune_queue_params( []( so_5::disp::one_thread::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::one_thread::queue_traits::simple_lock_factory(); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of one_thread dispatcher. one_thread_disp.binder() ); \endcode * * \since * v.5.6.0 */ SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Parameters for the dispatcher. disp_params_t params ); /*! * \brief Create an instance of %one_thread dispatcher. * * \par Usage sample \code auto one_thread_disp = so_5::disp::one_thread::make_dispatcher( env, "file_handler" ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of one_thread dispatcher. one_thread_disp.binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base ) { return make_dispatcher( env, data_sources_name_base, disp_params_t{} ); } /*! * \brief Create an instance of %one_thread dispatcher. * * \par Usage sample \code auto one_thread_disp = so_5::disp::one_thread::make_dispatcher( env ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of one_thread dispatcher. one_thread_disp.binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( environment_t & env ) { return make_dispatcher( env, std::string_view{}, disp_params_t{} ); } } /* namespace one_thread */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/prio_dedicated_threads/000077500000000000000000000000001523065210100231035ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/prio_dedicated_threads/one_per_prio/000077500000000000000000000000001523065210100255635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/prio_dedicated_threads/one_per_prio/pub.cpp000066400000000000000000000177371523065210100270740ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Functions for creating and binding of the dispatcher with * dedicated threads per priority. * * \since * v.5.5.8 */ #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace prio_dedicated_threads { namespace one_per_prio { namespace impl { namespace stats = so_5::stats; namespace { void send_thread_activity_stats( const so_5::mbox_t &, const stats::prefix_t &, so_5::disp::reuse::work_thread::work_thread_no_activity_tracking_t & ) { /* Nothing to do */ } void send_thread_activity_stats( const so_5::mbox_t & mbox, const stats::prefix_t & prefix, so_5::disp::reuse::work_thread::work_thread_with_activity_tracking_t & wt ) { so_5::send< stats::messages::work_thread_activity >( mbox, prefix, stats::suffixes::work_thread_activity(), wt.thread_id(), wt.take_activity_stats() ); } } /* namespace anonymous */ // // dispatcher_template_t // /*! * \brief An actual implementation of dispatcher with dedicated thread * for every priority in form of a template class. * * \since * v.5.5.8, v.5.5.18, v.5.6.0 */ template< typename Work_Thread > class dispatcher_template_t final : public disp_binder_t { public: dispatcher_template_t( outliving_reference_t< environment_t > env, const std::string_view name_base, disp_params_t params ) : m_data_source{ outliving_mutable(env.get().stats_repository()), name_base, outliving_mutable(*this) } { allocate_work_threads( env.get(), params ); launch_work_threads(); } ~dispatcher_template_t() noexcept override { for( auto & t : m_threads ) t->shutdown(); for( auto & t : m_threads ) t->wait(); } void preallocate_resources( agent_t & /*agent*/ ) override { // Nothing to do. } void undo_preallocation( agent_t & /*agent*/ ) noexcept override { // Nothing to do. } void bind( agent_t & agent ) noexcept override { const auto priority = agent.so_priority(); agent.so_bind_to_dispatcher( *(m_threads[ to_size_t(priority) ]->get_agent_binding()) ); m_agents_per_priority[ to_size_t(priority) ] += 1; } void unbind( agent_t & agent ) noexcept override { const auto priority = agent.so_priority(); m_agents_per_priority[ to_size_t(priority) ] -= 1; } private: friend class disp_data_source_t; /*! * \brief Data source for run-time monitoring of whole dispatcher. * * \since * v.5.5.8 */ class disp_data_source_t final : public stats::source_t { //! Dispatcher to work with. outliving_reference_t< dispatcher_template_t > m_dispatcher; //! Basic prefix for data sources. stats::prefix_t m_base_prefix; public : disp_data_source_t( const std::string_view name_base, outliving_reference_t< dispatcher_template_t > disp ) : m_dispatcher{ disp } , m_base_prefix{ so_5::disp::reuse::make_disp_prefix( "pdt-opp", name_base, &(disp.get()) ) } {} void distribute( const mbox_t & mbox ) override { std::size_t agents_count = 0; auto & disp = m_dispatcher.get(); so_5::prio::for_each_priority( [&]( priority_t p ) { auto agents = disp.m_agents_per_priority[ to_size_t(p) ].load( std::memory_order_acquire ); agents_count += agents; distribute_value_for_work_thread( mbox, p, agents, *(disp.m_threads[ to_size_t(p) ]) ); } ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::agent_count(), agents_count ); } private: void distribute_value_for_work_thread( const mbox_t & mbox, priority_t priority, std::size_t agents_count, Work_Thread & wt ) { std::ostringstream ss; ss << m_base_prefix.c_str() << "/wt-p" << to_size_t(priority); const stats::prefix_t prefix{ ss.str() }; so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::work_thread_queue_size(), wt.demands_count() ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::agent_count(), agents_count ); send_thread_activity_stats( mbox, prefix, wt ); } }; //! Data source for run-time monitoring. stats::auto_registered_source_holder_t< disp_data_source_t > m_data_source; //! Working threads for every priority. std::vector< std::unique_ptr< Work_Thread > > m_threads; //! Counters for agent count for every priority. std::array< std::atomic< std::size_t >, so_5::prio::total_priorities_count > m_agents_per_priority; //! Allocate work threads for dispatcher. void allocate_work_threads( environment_t & env, const disp_params_t & params ) { m_threads.reserve( so_5::prio::total_priorities_count ); so_5::prio::for_each_priority( [&]( so_5::priority_t ) { auto lock_factory = params.queue_params().lock_factory(); auto t = std::make_unique< Work_Thread >( acquire_work_thread( params, env ), std::move(lock_factory) ); m_threads.push_back( std::move(t) ); } ); } //! Start all working threads. void launch_work_threads() { using namespace std; using namespace so_5::details; using namespace so_5::prio; // This helper vector will be used for shutdown of // started threads in the case of an exception. std::array< Work_Thread *, total_priorities_count > started_threads; // Initially all items must be NULL. fill( begin(started_threads), end(started_threads), nullptr ); do_with_rollback_on_exception( [&] { for( std::size_t i = 0; i != total_priorities_count; ++i ) { m_agents_per_priority[ i ].store( 0, std::memory_order_release ); m_threads[ i ]->start(); // Thread successfully started. Pointer to it // must be used on rollback. started_threads[ i ] = m_threads[ i ].get(); } }, [&] { invoke_noexcept_code( [&] { // Shutdown all started threads. for( auto t : started_threads ) if( t ) { t->shutdown(); t->wait(); } else // No more started threads. break; } ); } ); } }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( disp_binder_shptr_t binder ) noexcept { return { std::move( binder ) }; } }; } /* namespace impl */ // // make_dispatcher // SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, disp_params_t params ) { using namespace so_5::disp::reuse; using dispatcher_no_activity_tracking_t = impl::dispatcher_template_t< work_thread::work_thread_no_activity_tracking_t >; using dispatcher_with_activity_tracking_t = impl::dispatcher_template_t< work_thread::work_thread_with_activity_tracking_t >; disp_binder_shptr_t binder = so_5::disp::reuse::make_actual_dispatcher< disp_binder_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params) ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace one_per_prio */ } /* namespace prio_dedicated_threads */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/prio_dedicated_threads/one_per_prio/pub.hpp000066400000000000000000000137531523065210100270730ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Functions for creating and binding of the dispatcher with * dedicated threads per priority. * * \since * v.5.5.8 */ #if !defined( SO_5_DISP_PRIO_DEDICATED_THREADS_ONE_PER_PRIO_PUB_HPP ) #define SO_5_DISP_PRIO_DEDICATED_THREADS_ONE_PER_PRIO_PUB_HPP #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace prio_dedicated_threads { namespace one_per_prio { /*! * \brief Alias for namespace with traits of event queue. * * \since * v.5.5.10 */ namespace queue_traits = so_5::disp::mpsc_queue_traits; // // disp_params_t // /*! * \brief Parameters for a dispatcher. * * \since * v.5.5.10 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() = default; friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< work_thread_factory_mixin_t & >(a), static_cast< work_thread_factory_mixin_t & >(b) ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code namespace prio_disp = so_5::disp::prio_dedicated_threads::one_per_prio; auto disp = prio_disp::make_dispatcher( env, "my_prio_disp", prio_disp::disp_params_t{}.tune_queue_params( []( prio_disp::queue_traits::queue_params_t & p ) { p.lock_factory( prio_disp::queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const { return m_queue_params; } private : //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; namespace impl { class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \since * v.5.6.0 * * \brief A handle for %prio_dedicated_threads::one_per_prio dispatcher. */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! Binder for the dispatcher. disp_binder_shptr_t m_binder; dispatcher_handle_t( disp_binder_shptr_t binder ) noexcept : m_binder{ std::move(binder) } {} //! Is this handle empty? bool empty() const noexcept { return !m_binder; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. [[nodiscard]] disp_binder_shptr_t binder() const noexcept { return m_binder; } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_binder.reset(); } }; // // make_dispatcher // /*! * \brief Create an instance of %one_per_prio dispatcher. * * \par Usage sample \code using namespace so_5::disp::prio_dedicated_threads::one_per_prio; auto common_thread_disp = make_dispatcher( env, "request_processor" disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & p ) { p.lock_factory( queue_traits::simple_lock_factory() ); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // private strictly_ordered dispatcher. common_thread_disp.binder() ); \endcode * * \since * v.5.6.0 */ SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Parameters for the dispatcher. disp_params_t params ); // // make_dispatcher // /*! * \brief Create an instance of %one_per_prio dispatcher. * * \par Usage sample \code using namespace so_5::disp::prio_dedicated_threads::one_per_prio; auto common_thread_disp = make_dispatcher( env, "request_processor" ); auto coop = env.make_coop( // The main dispatcher for that coop will be // private strictly_ordered dispatcher. common_thread_disp.binder() ); \endcode * * \since * v.5.5.8 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base ) { return make_dispatcher( env, data_sources_name_base, disp_params_t{} ); } // // make_dispatcher // /*! * \brief Create an instance of %one_per_prio dispatcher. * * \par Usage sample \code using namespace so_5::disp::prio_dedicated_threads::one_per_prio; auto common_thread_disp = make_dispatcher( env ); auto coop = env.make_coop( // The main dispatcher for that coop will be // private strictly_ordered dispatcher. common_thread_disp.binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( environment_t & env ) { return make_dispatcher( env, std::string_view{} ); } } /* namespace one_per_prio */ } /* namespace prio_dedicated_threads */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/000077500000000000000000000000001523065210100215735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/quoted_round_robin/000077500000000000000000000000001523065210100254745ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/quoted_round_robin/impl/000077500000000000000000000000001523065210100264355ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/quoted_round_robin/impl/demand_queue.hpp000066400000000000000000000220051523065210100316010ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \since * v.5.5.8 * * \file * \brief A demand queue for dispatcher with one common working * thread and round-robin processing of prioritised demand on * quoted basic. */ #if !defined( SO_5_DISP_PRIO_ONE_THREAD_QUITED_ROUND_ROBIN_IMPL_DEMAND_QUEUE_HPP ) #define SO_5_DISP_PRIO_ONE_THREAD_QUITED_ROUND_ROBIN_IMPL_DEMAND_QUEUE_HPP #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif namespace so_5 { namespace disp { namespace prio_one_thread { namespace quoted_round_robin { namespace impl { namespace queue_traits = so_5::disp::mpsc_queue_traits; // // demand_t // /*! * \since * v.5.5.8 * * \brief A single execution demand. */ struct demand_t final : public execution_demand_t { //! Next demand in the queue. demand_t * m_next = nullptr; //! Initializing constructor. demand_t( execution_demand_t && source ) : execution_demand_t( std::move( source ) ) {} }; // // demand_unique_ptr_t // /*! * \since * v.5.5.8 * * \brief An alias for unique_ptr to demand. */ using demand_unique_ptr_t = std::unique_ptr< demand_t >; // // demand_queue_t // /*! * \since * v.5.5.8 * * \brief A demand queue for dispatcher with one common working * thread and round-robin processing of prioritised demand on * quoted basic. */ class demand_queue_t { friend struct queue_for_one_priority_t; //! Description of queue for one priority. struct queue_for_one_priority_t : public event_queue_t { //! Pointer to main demand queue. demand_queue_t * m_demand_queue = nullptr; //! Head of the queue. /*! Null if queue is empty. */ demand_t * m_head = nullptr; //! Tail of the queue. /*! Null if queue is empty. */ demand_t * m_tail = nullptr; //! A quote for this subqueue. /*! * \note Actual value will be set later in the constructor * of demand_queue_t. */ std::size_t m_quote = 0; //! Count of processed demands on the current iterations. std::size_t m_demands_processed = 0; /*! * \name Information for run-time monitoring. * \{ */ //! Count of agents attached to that queue. std::atomic< std::size_t > m_agents_count = { 0 }; //! Count of demands in the queue. std::atomic< std::size_t > m_demands_count = { 0 }; /*! * \} */ void push( execution_demand_t exec_demand ) override { demand_unique_ptr_t what{ new demand_t{ std::move( exec_demand ) } }; m_demand_queue->push( this, std::move( what ) ); } /*! * \note * Delegates the work to the push() method. */ void push_evt_start( execution_demand_t demand ) override { this->push( std::move(demand) ); } /*! * \note * Delegates the work to the push() method. * * \attention * Terminates the whole application if the push() throws. */ void push_evt_finish( execution_demand_t demand ) noexcept override { this->push( std::move(demand) ); } }; public : //! This exception is thrown when pop is called after stop. class shutdown_ex_t : public std::exception {}; //! Statistic about one subqueue. struct queue_stats_t { priority_t m_priority; std::size_t m_quote; std::size_t m_agents_count; std::size_t m_demands_count; }; demand_queue_t( queue_traits::lock_unique_ptr_t lock, const quotes_t & quotes ) : m_lock{ std::move(lock) } , m_current_priority( &m_priorities[ to_size_t( so_5::priority_t::p_max ) ] ) { so_5::prio::for_each_priority( [&]( priority_t p ) { auto & q = m_priorities[ to_size_t(p) ]; // Every subqueue must have a valid pointer to main demand // queue. q.m_demand_queue = this; // Quote for the subqueue must be defined. q.m_quote = quotes.query( p ); } ); } ~demand_queue_t() { for( auto & q : m_priorities ) cleanup_queue( q ); } //! Set the shutdown signal. void stop() { queue_traits::lock_guard_t lock{ *m_lock }; m_shutdown = true; if( !m_total_demands_count ) // There could be a sleeping working thread. // It must be notified. lock.notify_one(); } //! Pop demand from the queue. /*! * \throw shutdown_ex_t in the case when queue is shut down. */ demand_unique_ptr_t pop() { queue_traits::unique_lock_t lock{ *m_lock }; while( !m_shutdown && !m_total_demands_count ) lock.wait_for_notify(); if( m_shutdown ) throw shutdown_ex_t(); // Note: this loop should not be infinitife because // m_total_demands_count is not a zero. It means that // there is at least one demand somewhere. while( !m_current_priority->m_head ) switch_to_lower_priority(); // There is a demand to extract. demand_unique_ptr_t result{ m_current_priority->m_head }; m_current_priority->m_head = result->m_next; if( !m_current_priority->m_head ) m_current_priority->m_tail = nullptr; result->m_next = nullptr; --(m_current_priority->m_demands_count); --m_total_demands_count; ++(m_current_priority->m_demands_processed); if( m_current_priority->m_demands_processed >= m_current_priority->m_quote ) { // Processing of this priority on the current // iteration is finished. switch_to_lower_priority(); } return result; } //! Get queue for the priority specified. event_queue_t & event_queue_by_priority( priority_t priority ) { return m_priorities[ to_size_t(priority) ]; } //! Notification about attachment of yet another agent to the queue. void agent_bound( priority_t priority ) { ++(m_priorities[ to_size_t(priority) ].m_agents_count); } //! Notification about detachment of an agent from the queue. void agent_unbound( priority_t priority ) { --(m_priorities[ to_size_t(priority) ].m_agents_count); } //! A special method for handling statistical data for //! every subqueue. template< class Lambda > void handle_stats_for_each_prio( Lambda handler ) { so_5::prio::for_each_priority( [&]( so_5::priority_t p ) { const auto & subqueue = m_priorities[ to_size_t(p) ]; handler( queue_stats_t{ p, subqueue.m_quote, subqueue.m_agents_count.load( std::memory_order_relaxed ), subqueue.m_demands_count.load( std::memory_order_relaxed ) } ); } ); } private : //! Queue lock. queue_traits::lock_unique_ptr_t m_lock; //! Shutdown flag. bool m_shutdown = false; //! Total count of demands in the queue. std::size_t m_total_demands_count = 0; //! Subqueues for priorities. queue_for_one_priority_t m_priorities[ static_cast< std::size_t >( priority_t::p_max ) + 1 ]; //! Pointer to the current subqueue. queue_for_one_priority_t * m_current_priority = nullptr; //! Destroy all demands in the queue specified. void cleanup_queue( queue_for_one_priority_t & queue_info ) { auto h = queue_info.m_head; while( h ) { demand_unique_ptr_t t{ h }; h = h->m_next; } } //! Push a new demand to the queue. void push( //! Subqueue for the demand. queue_for_one_priority_t * subqueue, //! Demand to be pushed. demand_unique_ptr_t demand ) { queue_traits::lock_guard_t lock{ *m_lock }; add_demand_to_queue( *subqueue, std::move( demand ) ); ++m_total_demands_count; if( 1 == m_total_demands_count ) // Queue was empty. A sleeping working thread must // be notified. lock.notify_one(); } //! Add a new demand to the tail of the queue specified. void add_demand_to_queue( queue_for_one_priority_t & queue, demand_unique_ptr_t demand ) { if( queue.m_tail ) { // Queue is not empty. Tail will be modified. queue.m_tail->m_next = demand.release(); queue.m_tail = queue.m_tail->m_next; } else { // Queue is empty. The whole description will be modified. queue.m_head = demand.release(); queue.m_tail = queue.m_head; } ++(queue.m_demands_count); } void switch_to_lower_priority() { // Iteration on the current priority is finished. // Count of processed demands must be started from zero. m_current_priority->m_demands_processed = 0; // Try to find next subqueue. if( m_current_priority > &m_priorities[ 0 ] ) --m_current_priority; else // Start new iteration from the highest priority. m_current_priority = &m_priorities[ to_size_t( so_5::priority_t::p_max ) ]; } }; } /* namespace impl */ } /* namespace quoted_round_robin */ } /* namespace prio_one_thread */ } /* namespace disp */ } /* namespace so_5 */ #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic pop #endif #endif sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/quoted_round_robin/pub.cpp000066400000000000000000000153771523065210100270030ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Functions for creating and binding of the single thread dispatcher * with priority support (quoted round robin policy). * * \since * v.5.5.8 */ #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace prio_one_thread { namespace quoted_round_robin { namespace impl { namespace stats = so_5::stats; namespace { void send_thread_activity_stats( const so_5::mbox_t &, const stats::prefix_t &, so_5::disp::prio_one_thread::reuse::work_thread_no_activity_tracking_t< demand_queue_t > & ) { /* Nothing to do */ } void send_thread_activity_stats( const so_5::mbox_t & mbox, const stats::prefix_t & prefix, so_5::disp::prio_one_thread::reuse::work_thread_with_activity_tracking_t< demand_queue_t > & wt ) { so_5::send< stats::messages::work_thread_activity >( mbox, prefix, stats::suffixes::work_thread_activity(), wt.thread_id(), wt.take_activity_stats() ); } } /* namespace anonymous */ // // dispatcher_template_t // /*! * \brief An implementation of dispatcher with one working thread and support * of demand priorities (quoted round robin policy) in form of template class. * * \since * v.5.5.8, v.5.5.18, v.5.6.0 */ template< typename Work_Thread > class dispatcher_template_t final : public disp_binder_t { friend class disp_data_source_t; public: dispatcher_template_t( outliving_reference_t< environment_t > env, const std::string_view name_base, disp_params_t params, const quotes_t & quotes ) : m_demand_queue{ params.queue_params().lock_factory()(), quotes } , m_work_thread{ so_5::disp::reuse::acquire_work_thread( params, env.get() ), m_demand_queue } , m_data_source{ outliving_mutable(env.get().stats_repository()), name_base, outliving_mutable(*this) } { m_work_thread.start(); } ~dispatcher_template_t() noexcept override { m_demand_queue.stop(); m_work_thread.join(); } void preallocate_resources( agent_t & /*agent*/ ) override { // Nothing to do. } void undo_preallocation( agent_t & /*agent*/ ) noexcept override { // Nothing to do. } void bind( agent_t & agent ) noexcept override { const auto priority = agent.so_priority(); agent.so_bind_to_dispatcher( m_demand_queue.event_queue_by_priority( priority ) ); m_demand_queue.agent_bound( priority ); } void unbind( agent_t & agent ) noexcept override { const auto priority = agent.so_priority(); m_demand_queue.agent_unbound( priority ); } private: /*! * \brief Data source for run-time monitoring of whole dispatcher. * * \since * v.5.5.8 */ class disp_data_source_t : public stats::source_t { //! Dispatcher to work with. outliving_reference_t< dispatcher_template_t > m_dispatcher; //! Basic prefix for data sources. stats::prefix_t m_base_prefix; public : disp_data_source_t( const std::string_view name_base, outliving_reference_t< dispatcher_template_t > disp ) : m_dispatcher{ disp } , m_base_prefix{ so_5::disp::reuse::make_disp_prefix( "pot-qrr", name_base, &(disp.get()) ) } {} void distribute( const mbox_t & mbox ) override { auto & disp = m_dispatcher.get(); std::size_t agents_count = 0; disp.m_demand_queue.handle_stats_for_each_prio( [&]( const demand_queue_t::queue_stats_t & stat ) { distribute_value_for_priority( mbox, stat.m_priority, stat.m_quote, stat.m_agents_count, stat.m_demands_count ); agents_count += stat.m_agents_count; } ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::agent_count(), agents_count ); send_thread_activity_stats( mbox, m_base_prefix, disp.m_work_thread ); } private: void distribute_value_for_priority( const mbox_t & mbox, priority_t priority, std::size_t quote, std::size_t agents_count, std::size_t demands_count ) { std::ostringstream ss; ss << m_base_prefix.c_str() << "/p" << to_size_t(priority); const stats::prefix_t prefix{ ss.str() }; so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::demand_quote(), quote ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::agent_count(), agents_count ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::work_thread_queue_size(), demands_count ); } }; //! Demand queue for the dispatcher. demand_queue_t m_demand_queue; //! Working thread for the dispatcher. Work_Thread m_work_thread; //! Data source for run-time monitoring. stats::auto_registered_source_holder_t< disp_data_source_t > m_data_source; }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( disp_binder_shptr_t binder ) noexcept { return { std::move( binder ) }; } }; } /* namespace impl */ // // make_dispatcher // SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, const quotes_t & quotes, disp_params_t params ) { using namespace so_5::disp::reuse; using namespace so_5::disp::prio_one_thread::reuse; using dispatcher_no_activity_tracking_t = impl::dispatcher_template_t< work_thread_no_activity_tracking_t< impl::demand_queue_t > >; using dispatcher_with_activity_tracking_t = impl::dispatcher_template_t< work_thread_with_activity_tracking_t< impl::demand_queue_t > >; disp_binder_shptr_t binder = so_5::disp::reuse::make_actual_dispatcher< disp_binder_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params), quotes ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace quoted_round_robin */ } /* namespace prio_one_thread */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/quoted_round_robin/pub.hpp000066400000000000000000000150741523065210100270020ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Functions for creating and binding of the single thread dispatcher * with priority support (quoted round robin policy). * * \since * v.5.5.8 */ #if !defined( SO_5_DISP_PRIO_ONE_THREAD_QUITED_ROUND_ROBIN_PUB_HPP ) #define SO_5_DISP_PRIO_ONE_THREAD_QUITED_ROUND_ROBIN_PUB_HPP #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace prio_one_thread { namespace quoted_round_robin { /*! * \brief Alias for namespace with traits of event queue. * * \since * v.5.5.10 */ namespace queue_traits = so_5::disp::mpsc_queue_traits; // // disp_params_t // /*! * \brief Parameters for a dispatcher. * * \since * v.5.5.10 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() {} friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< work_thread_factory_mixin_t & >(a), static_cast< work_thread_factory_mixin_t & >(b) ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code namespace prio_disp = so_5::disp::prio_one_thread::quoted_round_robin; auto disp = prio_disp::make_dispatcher( env, "my_prio_disp", prio_disp::disp_params_t{}.tune_queue_params( []( prio_disp::queue_traits::queue_params_t & p ) { p.lock_factory( prio_disp::queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const { return m_queue_params; } private : //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; namespace impl { class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \since * v.5.6.0 * * \brief A handle for %prio_one_thread::strictly_ordered dispatcher. */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! Binder for the dispatcher. disp_binder_shptr_t m_binder; dispatcher_handle_t( disp_binder_shptr_t binder ) noexcept : m_binder{ std::move(binder) } {} //! Is this handle empty? bool empty() const noexcept { return !m_binder; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. [[nodiscard]] disp_binder_shptr_t binder() const noexcept { return m_binder; } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_binder.reset(); } }; // // make_dispatcher // /*! * \brief Create an instance of %quoted_round_robin dispatcher. * * \par Usage sample \code using namespace so_5::disp::prio_one_thread::quoted_round_robin; auto common_thread_disp = make_dispatcher( env, "request_processor", quotes_t{ 75 }.set( so_5::prio::p7, 150 ).set( so_5::prio::p6, 125 ), disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & p ) { p.lock_factory( queue_traits::simple_lock_factory() ); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of quoted_round_robin dispatcher. common_thread_disp.binder() ); \endcode * * \since * v.5.6.0 */ SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Quotes for every priority. const quotes_t & quotes, //! Parameters for the dispatcher. disp_params_t params ); // // make_dispatcher // /*! * \brief Create an instance of %quoted_round_robin dispatcher. * * \par Usage sample \code using namespace so_5::disp::prio_one_thread::quoted_round_robin; auto common_thread_disp = make_dispatcher( env, "request_processor", quotes_t{ 75 }.set( so_5::prio::p7, 150 ).set( so_5::prio::p6, 125 ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of quoted_round_robin dispatcher. common_thread_disp->binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Quotes for every priority. const quotes_t & quotes ) { return make_dispatcher( env, data_sources_name_base, quotes, disp_params_t{} ); } // // make_dispatcher // /*! * \brief Create an instance of %quoted_round_robin dispatcher. * * \par Usage sample \code using namespace so_5::disp::prio_one_thread::quoted_round_robin; auto common_thread_disp = make_dispatcher( env, quotes_t{ 75 }.set( so_5::prio::p7, 150 ).set( so_5::prio::p6, 125 ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of quoted_round_robin dispatcher. common_thread_disp.binder() ); \endcode * * \since * v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Quotes for every priority. const quotes_t & quotes ) { return make_dispatcher( env, std::string_view{}, quotes ); } } /* namespace quoted_round_robin */ } /* namespace prio_one_thread */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/quoted_round_robin/quotes.hpp000066400000000000000000000051631523065210100275320ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.8 * * \file * \brief A storage of quotes for priorities. */ #if !defined( SO_5_DISP_PRIO_ONE_THREAD_QUOTED_ROUND_ROBIN_QUOTES_HPP ) #define SO_5_DISP_PRIO_ONE_THREAD_QUOTED_ROUND_ROBIN_QUOTES_HPP #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif namespace so_5 { namespace disp { namespace prio_one_thread { namespace quoted_round_robin { /*! * \since 5.5.8 * \brief A storage of quotes for priorities. * * A usage example: \code using namespace so_5::disp::prio_one_thread::quoted_round_robin; quotes_t quotes{ 150 }; // Default value for all priorities. quotes.set( so_5::prio::p7, 350 ); // New quote for p7. quotes.set( so_5::prio::p6, 250 ); // New quote for p6. // All other quotes will be 150. ... auto disp = make_dispatcher( env, quotes ); \endcode Another example: \code using namespace so_5::disp::prio_one_thread::quoted_round_robin; auto disp = make_dispatcher( env, quotes_t{ 150 } // Default value for all priorites. .set( so_5::prio::p7, 350 ) // New quote for p7. .set( so_5::prio::p6, 250 ) // New quote for p6 ); \endcode * \attention Value of 0 is illegal. An exception will be throw on * attempt of setting 0 as a quote value. */ class quotes_t { public : //! Initializing constructor sets the default value for //! every priority. quotes_t( std::size_t default_value ) { ensure_quote_not_zero( default_value ); std::fill( std::begin(m_quotes), std::end(m_quotes), default_value ); } //! Set a new quote for a priority. quotes_t & set( //! Priority to which a new quote to be set. priority_t prio, //! Quote value. std::size_t quote ) { ensure_quote_not_zero( quote ); m_quotes[ to_size_t( prio ) ] = quote; return *this; } //! Get the quote for a priority. size_t query( priority_t prio ) const { return m_quotes[ to_size_t( prio ) ]; } private : //! Quotes for every priority. std::size_t m_quotes[ so_5::prio::total_priorities_count ]; static void ensure_quote_not_zero( std::size_t value ) { if( !value ) SO_5_THROW_EXCEPTION( rc_priority_quote_illegal_value, "quote for a priority cannot be zero" ); } }; } /* namespace quoted_round_robin */ } /* namespace prio_one_thread */ } /* namespace disp */ } /* namespace so_5 */ #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic pop #endif #endif sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/reuse/000077500000000000000000000000001523065210100227165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/reuse/work_thread.hpp000066400000000000000000000130351523065210100257420ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief A working thread for dispatcher with one common working * thread and support of demands priority. * * \since v.5.5.8 */ #if !defined( SO_5_DISP_PRIO_ONE_THREAD_REUSE_WORK_THREAD_HPP ) #define SO_5_DISP_PRIO_ONE_THREAD_REUSE_WORK_THREAD_HPP #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace prio_one_thread { namespace reuse { namespace work_thread_details { // // common_data_t // /*! * \brief A common data for all work thread implementations. * * \since v.5.5.18 */ template< typename Demand_Queue > struct common_data_t { //! Demands queue to work for. Demand_Queue & m_queue; //! Thread object. work_thread_holder_t m_thread_holder; //! ID of the work thread. /*! * \note Receives actual value only after successful start * of the thread. */ so_5::current_thread_id_t m_thread_id; common_data_t( work_thread_holder_t thread_holder, Demand_Queue & queue ) : m_queue( queue ) , m_thread_holder{ std::move(thread_holder) } {} }; // // no_activity_tracking_impl_t // /*! * \brief A part of implementation of work thread without activity tracking. * * \since v.5.5.18 */ template< typename Demand_Queue > class no_activity_tracking_impl_t : protected common_data_t< Demand_Queue > { using base_type_t = common_data_t< Demand_Queue >; public : no_activity_tracking_impl_t( work_thread_holder_t thread_holder, Demand_Queue & queue ) : base_type_t( std::move(thread_holder), queue ) {} protected : void work_started() { /* Nothing to do. */ } void work_finished() { /* Nothing to do. */ } void wait_started() { /* Nothing to do. */ } void wait_finished() { /* Nothing to do. */ } }; // // with_activity_tracking_impl_t // /*! * \brief A part of implementation of work thread with activity tracking. * * \since v.5.5.18 */ template< typename Demand_Queue > class with_activity_tracking_impl_t : protected common_data_t< Demand_Queue > { using base_type_t = common_data_t< Demand_Queue >; public : with_activity_tracking_impl_t( work_thread_holder_t thread_holder, Demand_Queue & queue ) : base_type_t( std::move(thread_holder), queue ) {} so_5::stats::work_thread_activity_stats_t take_activity_stats() { so_5::stats::work_thread_activity_stats_t result; result.m_working_stats = m_working_stats.take_stats(); result.m_waiting_stats = m_waiting_stats.take_stats(); return result; } protected : //! Statictics for work activity. so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::internal_lock > m_working_stats; //! Statictics for wait activity. so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::internal_lock > m_waiting_stats; void work_started() { m_working_stats.start(); } void work_finished() { m_working_stats.stop(); } void wait_started() { m_waiting_stats.start(); } void wait_finished() { m_waiting_stats.stop(); } }; } /* namespace work_thread_details */ // // work_thread_template_t // /*! * \brief A working thread for dispatcher with one common working * thread and support of demands priority. * * \since v.5.5.8, v.5.5.18 */ template< typename Demand_Queue, template class Work_Thread > class work_thread_template_t : public Work_Thread< Demand_Queue > { using base_type_t = Work_Thread< Demand_Queue >; public : //! Initializing constructor. work_thread_template_t( work_thread_holder_t thread_holder, Demand_Queue & queue ) : base_type_t( std::move(thread_holder), queue ) {} void start() { this->m_thread_holder.unchecked_get().start( [this]() { body(); } ); } void join() { so_5::impl::ensure_join_from_different_thread( this->m_thread_id ); this->m_thread_holder.unchecked_get().join(); } [[nodiscard]] so_5::current_thread_id_t thread_id() const { return this->m_thread_id; } private : void body() { this->m_thread_id = so_5::query_current_thread_id(); try { for(;;) { auto d = this->pop_demand(); this->call_handler( *d ); } } catch( const typename Demand_Queue::shutdown_ex_t & ) {} } [[nodiscard]] auto pop_demand() -> decltype(std::declval().pop()) { this->wait_started(); auto wait_meter_stopper = so_5::details::at_scope_exit( [this] { this->wait_finished(); } ); return this->m_queue.pop(); } void call_handler( so_5::execution_demand_t & demand ) { this->work_started(); auto work_meter_stopper = so_5::details::at_scope_exit( [this] { this->work_finished(); } ); demand.call_handler( this->m_thread_id ); } }; // // work_thread_no_activity_tracking_t // template< typename Demand_Queue > using work_thread_no_activity_tracking_t = work_thread_template_t< Demand_Queue, work_thread_details::no_activity_tracking_impl_t >; // // work_thread_with_activity_tracking_t // template< typename Demand_Queue > using work_thread_with_activity_tracking_t = work_thread_template_t< Demand_Queue, work_thread_details::with_activity_tracking_impl_t >; } /* namespace reuse */ } /* namespace prio_one_thread */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/strictly_ordered/000077500000000000000000000000001523065210100251545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/strictly_ordered/impl/000077500000000000000000000000001523065210100261155ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/strictly_ordered/impl/demand_queue.hpp000066400000000000000000000176311523065210100312720ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief A demand queue for dispatcher with one common working * thread and support of demands priority. * * \since * v.5.5.8 */ #if !defined( SO_5_DISP_PRIO_ONE_THREAD_STRICTLY_ORDERED_IMPL_DEMAND_QUEUE_HPP ) #define SO_5_DISP_PRIO_ONE_THREAD_STRICTLY_ORDERED_IMPL_DEMAND_QUEUE_HPP #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif namespace so_5 { namespace disp { namespace prio_one_thread { namespace strictly_ordered { namespace impl { namespace queue_traits = so_5::disp::mpsc_queue_traits; // // demand_t // /*! * \brief A single execution demand. * * \since v.5.5.8 */ struct demand_t final : public execution_demand_t { //! Next demand in the queue. /*! * \note * It's a dynamically allocated object that has to be deallocated * manually during the destruction of the queue. */ demand_t * m_next = nullptr; //! Initializing constructor. demand_t( execution_demand_t && source ) : execution_demand_t( std::move( source ) ) {} }; // // demand_unique_ptr_t // /*! * \brief An alias for unique_ptr to demand. * * \since v.5.5.8 */ using demand_unique_ptr_t = std::unique_ptr< demand_t >; // // demand_queue_t // /*! * \brief A demand queue with support of demands priorities. * * \since v.5.5.8 */ class demand_queue_t { friend struct queue_for_one_priority_t; //! Description of queue for one priority. struct queue_for_one_priority_t : public event_queue_t { //! Pointer to main demand queue. demand_queue_t * m_demand_queue = nullptr; //! Head of the queue. /*! Null if queue is empty. */ demand_t * m_head = nullptr; //! Tail of the queue. /*! Null if queue is empty. */ demand_t * m_tail = nullptr; /*! * \name Information for run-time monitoring. * \{ */ //! Count of agents attached to that queue. std::atomic< std::size_t > m_agents_count = { 0 }; //! Count of demands in the queue. std::atomic< std::size_t > m_demands_count = { 0 }; /*! * \} */ void push( execution_demand_t exec_demand ) override { demand_unique_ptr_t what{ new demand_t{ std::move( exec_demand ) } }; m_demand_queue->push( this, std::move( what ) ); } /*! * \note * Delegates the work to the push() method. */ void push_evt_start( execution_demand_t demand ) override { this->push( std::move(demand) ); } /*! * \note * Delegates the work to the push() method. * * \attention * Terminates the whole application if the push() throws. */ void push_evt_finish( execution_demand_t demand ) noexcept override { this->push( std::move(demand) ); } }; public : //! This exception is thrown when pop is called after stop. class shutdown_ex_t : public std::exception {}; //! Statistic about one subqueue. struct queue_stats_t { priority_t m_priority; std::size_t m_agents_count; std::size_t m_demands_count; }; demand_queue_t( //! Lock to be used for queue protection. queue_traits::lock_unique_ptr_t lock ) : m_lock{ std::move(lock) } { // Every subqueue must have a valid pointer to main demand queue. for( auto & q : m_priorities ) q.m_demand_queue = this; } ~demand_queue_t() { for( auto & q : m_priorities ) cleanup_queue( q ); } //! Set the shutdown signal. void stop() { queue_traits::lock_guard_t lock{ *m_lock }; m_shutdown = true; if( !m_current_priority ) // There could be a sleeping working thread. // It must be notified. lock.notify_one(); } //! Pop demand from the queue. /*! * \throw shutdown_ex_t in the case when queue is shut down. */ [[nodiscard]] demand_unique_ptr_t pop() { queue_traits::unique_lock_t lock{ *m_lock }; while( !m_shutdown && !m_current_priority ) lock.wait_for_notify(); if( m_shutdown ) throw shutdown_ex_t(); demand_unique_ptr_t result{ m_current_priority->m_head }; m_current_priority->m_head = result->m_next; result->m_next = nullptr; --(m_current_priority->m_demands_count); if( !m_current_priority->m_head ) { // Queue become empty. m_current_priority->m_tail = nullptr; // A non-empty subqueue with lower priority needs to be found. while( m_current_priority > m_priorities ) { --m_current_priority; if( m_current_priority->m_head ) return result; } m_current_priority = nullptr; } return result; } //! Get queue for the priority specified. event_queue_t & event_queue_by_priority( priority_t priority ) { return m_priorities[ to_size_t(priority) ]; } //! Notification about attachment of yet another agent to the queue. void agent_bound( priority_t priority ) { ++(m_priorities[ to_size_t(priority) ].m_agents_count); } //! Notification about detachment of an agent from the queue. void agent_unbound( priority_t priority ) { --(m_priorities[ to_size_t(priority) ].m_agents_count); } //! A special method for handling statistical data for //! every subqueue. template< class Lambda > void handle_stats_for_each_prio( Lambda handler ) { so_5::prio::for_each_priority( [&]( so_5::priority_t p ) { const auto & subqueue = m_priorities[ to_size_t(p) ]; handler( queue_stats_t{ p, subqueue.m_agents_count.load( std::memory_order_relaxed ), subqueue.m_demands_count.load( std::memory_order_relaxed ) } ); } ); } private : //! Queue lock. queue_traits::lock_unique_ptr_t m_lock; //! Shutdown flag. bool m_shutdown = false; //! Pointer to the current subqueue. /*! * This pointer will point to the non-empty subqueue with the * highest priority demand. If there is no such demand then * this pointer will be nullptr. */ queue_for_one_priority_t * m_current_priority = nullptr; //! Subqueues for priorities. queue_for_one_priority_t m_priorities[ so_5::prio::total_priorities_count ]; //! Destroy all demands in the queue specified. void cleanup_queue( queue_for_one_priority_t & queue_info ) { auto h = queue_info.m_head; while( h ) { demand_unique_ptr_t t{ h }; h = h->m_next; } } //! Push a new demand to the queue. void push( //! Subqueue for the demand. queue_for_one_priority_t * subqueue, //! Demand to be pushed. demand_unique_ptr_t demand ) { queue_traits::lock_guard_t lock{ *m_lock }; add_demand_to_queue( *subqueue, std::move( demand ) ); if( !m_current_priority ) { // Queue was empty. A sleeping working thread must // be notified. m_current_priority = subqueue; lock.notify_one(); } else if( m_current_priority < subqueue ) // New demand has greater priority than the previous. m_current_priority = subqueue; } //! Add a new demand to the tail of the queue specified. void add_demand_to_queue( queue_for_one_priority_t & queue, demand_unique_ptr_t demand ) { if( queue.m_tail ) { // Queue is not empty. Tail will be modified. queue.m_tail->m_next = demand.release(); queue.m_tail = queue.m_tail->m_next; } else { // Queue is empty. The whole description will be modified. queue.m_head = demand.release(); queue.m_tail = queue.m_head; } ++(queue.m_demands_count); } }; } /* namespace impl */ } /* namespace strictly_ordered */ } /* namespace prio_one_thread */ } /* namespace disp */ } /* namespace so_5 */ #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic pop #endif #endif sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/strictly_ordered/pub.cpp000066400000000000000000000146361523065210100264600ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Functions for creating and binding of the single thread dispatcher * with priority support. * * \since v.5.5.8 */ #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace prio_one_thread { namespace strictly_ordered { namespace impl { namespace stats = so_5::stats; namespace { void send_thread_activity_stats( const so_5::mbox_t &, const stats::prefix_t &, so_5::disp::prio_one_thread::reuse::work_thread_no_activity_tracking_t< demand_queue_t > & ) { /* Nothing to do */ } void send_thread_activity_stats( const so_5::mbox_t & mbox, const stats::prefix_t & prefix, so_5::disp::prio_one_thread::reuse::work_thread_with_activity_tracking_t< demand_queue_t > & wt ) { so_5::send< stats::messages::work_thread_activity >( mbox, prefix, stats::suffixes::work_thread_activity(), wt.thread_id(), wt.take_activity_stats() ); } } /* namespace anonymous */ // // dispatcher_template_t // /*! * \brief An implementation of dispatcher with one working * thread and support of demand priorities in form of template class. * * \since v.5.5.8, v.5.5.18, v.5.6.0 */ template< typename Work_Thread > class dispatcher_template_t final : public disp_binder_t { friend class disp_data_source_t; public: dispatcher_template_t( outliving_reference_t< environment_t > env, const std::string_view name_base, disp_params_t params ) : m_demand_queue{ params.queue_params().lock_factory()() } , m_work_thread{ so_5::disp::reuse::acquire_work_thread( params, env.get() ), m_demand_queue } , m_data_source{ outliving_mutable(env.get().stats_repository()), name_base, outliving_mutable(*this) } { m_work_thread.start(); } ~dispatcher_template_t() noexcept override { m_demand_queue.stop(); m_work_thread.join(); } void preallocate_resources( agent_t & /*agent*/ ) override { // Nothing to do. } void undo_preallocation( agent_t & /*agent*/ ) noexcept override { // Nothing to do. } void bind( agent_t & agent ) noexcept override { const auto priority = agent.so_priority(); agent.so_bind_to_dispatcher( m_demand_queue.event_queue_by_priority( priority ) ); m_demand_queue.agent_bound( priority ); } void unbind( agent_t & agent ) noexcept override { const auto priority = agent.so_priority(); m_demand_queue.agent_unbound( priority ); } private: /*! * \brief Data source for run-time monitoring of whole dispatcher. * * \since v.5.5.8 */ class disp_data_source_t : public stats::source_t { //! Dispatcher to work with. outliving_reference_t< dispatcher_template_t > m_dispatcher; //! Basic prefix for data sources. stats::prefix_t m_base_prefix; public : disp_data_source_t( const std::string_view name_base, outliving_reference_t< dispatcher_template_t > disp ) : m_dispatcher{ disp } , m_base_prefix{ so_5::disp::reuse::make_disp_prefix( "pot-so", name_base, &(disp.get()) ) } {} void distribute( const mbox_t & mbox ) override { auto & disp = m_dispatcher.get(); std::size_t agents_count = 0; disp.m_demand_queue.handle_stats_for_each_prio( [&]( const demand_queue_t::queue_stats_t & stat ) { distribute_value_for_priority( mbox, stat.m_priority, stat.m_agents_count, stat.m_demands_count ); agents_count += stat.m_agents_count; } ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::agent_count(), agents_count ); send_thread_activity_stats( mbox, m_base_prefix, disp.m_work_thread ); } private: void distribute_value_for_priority( const mbox_t & mbox, priority_t priority, std::size_t agents_count, std::size_t demands_count ) { std::ostringstream ss; ss << m_base_prefix.c_str() << "/p" << to_size_t(priority); const stats::prefix_t prefix{ ss.str() }; so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::agent_count(), agents_count ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, prefix, stats::suffixes::work_thread_queue_size(), demands_count ); } }; //! Demand queue for the dispatcher. demand_queue_t m_demand_queue; //! Working thread for the dispatcher. Work_Thread m_work_thread; //! Data source for run-time monitoring. stats::auto_registered_source_holder_t< disp_data_source_t > m_data_source; }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( disp_binder_shptr_t binder ) noexcept { return { std::move( binder ) }; } }; } /* namespace impl */ // // make_dispatcher // SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, disp_params_t params ) { using namespace so_5::disp::reuse; using namespace so_5::disp::prio_one_thread::reuse; using dispatcher_no_activity_tracking_t = impl::dispatcher_template_t< work_thread_no_activity_tracking_t< impl::demand_queue_t > >; using dispatcher_with_activity_tracking_t = impl::dispatcher_template_t< work_thread_with_activity_tracking_t< impl::demand_queue_t > >; disp_binder_shptr_t binder = so_5::disp::reuse::make_actual_dispatcher< disp_binder_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params) ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace strictly_ordered */ } /* namespace prio_one_thread */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/prio_one_thread/strictly_ordered/pub.hpp000066400000000000000000000141011523065210100264500ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Functions for creating and binding of the single thread dispatcher * with priority support. * * \since v.5.5.8 */ #if !defined( SO_5_DISP_PRIO_ONE_THREAD_STRICTLY_ORDERED_PUB_HPP ) #define SO_5_DISP_PRIO_ONE_THREAD_STRICTLY_ORDERED_PUB_HPP #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace prio_one_thread { namespace strictly_ordered { /*! * \brief Alias for namespace with traits of event queue. * * \since v.5.5.10 */ namespace queue_traits = so_5::disp::mpsc_queue_traits; // // disp_params_t // /*! * \brief Parameters for a dispatcher. * * \since v.5.5.10 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() = default; friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< work_thread_factory_mixin_t & >(a), static_cast< work_thread_factory_mixin_t & >(b) ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code namespace prio_disp = so_5::disp::prio_one_thread::strictly_ordered; auto disp = prio_disp::make_dispatcher( env, "my_prio_disp", prio_disp::disp_params_t{}.tune_queue_params( []( prio_disp::queue_traits::queue_params_t & p ) { p.lock_factory( prio_disp::queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const noexcept { return m_queue_params; } private : //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; namespace impl { class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \brief A handle for %prio_one_thread::strictly_ordered dispatcher. * * \since v.5.6.0 */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! Binder for the dispatcher. disp_binder_shptr_t m_binder; dispatcher_handle_t( disp_binder_shptr_t binder ) noexcept : m_binder{ std::move(binder) } {} //! Is this handle empty? bool empty() const noexcept { return !m_binder; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. [[nodiscard]] disp_binder_shptr_t binder() const noexcept { return m_binder; } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_binder.reset(); } }; // // make_dispatcher // /*! * \brief Create an instance of %strictly_ordered dispatcher. * * \par Usage sample \code auto common_thread_disp = so_5::disp::prio_one_thread::strictly_ordered::make_dispatcher( env, "request_processor", so_5::disp::prio_one_thread::strictly_ordered::disp_params_t{}.tune_queue_params( []( so_5::disp::prio_one_thread::strictly_ordered::queue_traits::queue_params_t & p ) { p.lock_factory( so_5::disp::prio_one_thread::strictly_ordered::queue_traits::simple_lock_factory() ); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of strictly_ordered dispatcher. common_thread_disp.binder() ); \endcode * * \since v.5.6.0 */ SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Parameters for the dispatcher. disp_params_t params ); // // make_dispatcher // /*! * \brief Create an instance of %strictly_ordered dispatcher. * * \par Usage sample \code auto common_thread_disp = so_5::disp::prio_one_thread::strictly_ordered::make_dispatcher( env, "request_processor" ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of strictly_ordered dispatcher. common_thread_disp.binder() ); \endcode * * \since v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base ) { return make_dispatcher( env, data_sources_name_base, disp_params_t{} ); } // // make_dispatcher // /*! * \brief Create an instance of %strictly_ordered dispatcher. * * \par Usage sample \code auto common_thread_disp = so_5::disp::prio_one_thread::strictly_ordered::make_dispatcher( env ); auto coop = env.make_coop( // The main dispatcher for that coop will be // private strictly_ordered dispatcher. common_thread_disp.binder() ); \endcode * * \since v.5.6.0 */ inline dispatcher_handle_t make_dispatcher( environment_t & env ) { return make_dispatcher( env, std::string_view{} ); } } /* namespace strictly_ordered */ } /* namespace prio_one_thread */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/reuse/000077500000000000000000000000001523065210100175555ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/reuse/actual_work_thread_factory_to_use.hpp000066400000000000000000000042031523065210100272340ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helper for detection of actual work thread factory to use. * * \since v.5.7.3 */ #if !defined( SO_5_DISP_REUSE_ACTUAL_WORK_THREAD_FACTORY_TO_USE_HPP ) #define SO_5_DISP_REUSE_ACTUAL_WORK_THREAD_FACTORY_TO_USE_HPP #include #include namespace so_5 { namespace disp { namespace reuse { // // actual_work_thread_factory_to_use // // IMPLEMENTATION NOTE: this function can't be defined in // so_5/disp/reuse/work_thread_factory_params.hpp because // it needs a full definition of so_5::environment_t class. // This definition isn't available yet, because the header // so_5/disp/reuse/work_thread_factory_params.hpp is included // in so_5/environment.hpp. // /*! * \brief Helper to detect actual work thread factory to be used. * * If \a params defines a factory then the factory from \a params * is returned. Otherwise, the factory from \a env is returned. */ template< typename Params > [[nodiscard]] abstract_work_thread_factory_shptr_t actual_work_thread_factory_to_use( const work_thread_factory_mixin_t< Params > & params, const environment_t & env ) noexcept { auto result = params.work_thread_factory(); if( !result ) result = env.work_thread_factory(); return result; } // // acquire_work_thread // /*! * \brief Helper function for acquiring a new worker thread from * an appropriate work thread factory. * * If \a params defines a factory then the factory from \a params * is used for acquiring new work thread. Otherwise the factory * from \a env is used. * * \since v.5.7.3 */ template< typename Params > [[nodiscard]] work_thread_holder_t acquire_work_thread( const work_thread_factory_mixin_t< Params > & params, environment_t & env ) { auto factory = actual_work_thread_factory_to_use( params, env ); auto & thread = factory->acquire( env ); // This block of code shouldn't throw. return so_5::details::invoke_noexcept_code( [&]() -> work_thread_holder_t { return { thread, std::move(factory) }; } ); } } /* namespace reuse */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/reuse/data_source_prefix_helpers.hpp000066400000000000000000000033711523065210100256620ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief Helpers creation of data source name prefixes. */ #if !defined( SO_5_DISP_REUSE_DATA_SOURCE_PREFIX_HELPERS_HPP ) #define SO_5_DISP_REUSE_DATA_SOURCE_PREFIX_HELPERS_HPP #include #include #include namespace so_5 { namespace disp { namespace reuse { namespace ios_helpers = so_5::details::ios_helpers; /*! * \since * v.5.5.4 * * \brief Create basic prefix for dispatcher data source names. */ inline so_5::stats::prefix_t make_disp_prefix( //! Textual marker of dispatcher type (like ot, ao, ag, tp, atp, ...). const std::string_view disp_type, //! User-supplied optional base for name. Can be empty. const std::string_view data_sources_name_base, //! Pointer to dispatcher instance. //! Will be used if \a data_sources_name_base is empty. const void * disp_this_pointer ) { const std::size_t max_name_base_fragment = 24; std::ostringstream ss; ss << "disp/" << disp_type << "/"; if( !data_sources_name_base.empty() ) ss << ios_helpers::length_limited_string{ data_sources_name_base, max_name_base_fragment }; else ss << ios_helpers::pointer{ disp_this_pointer }; return so_5::stats::prefix_t{ ss.str() }; } /*! * \since * v.5.5.4 * * \brief Create prefix for dispatcher's working thread data source. */ inline so_5::stats::prefix_t make_disp_working_thread_prefix( //! Prefix for the dispatcher. const so_5::stats::prefix_t & disp_prefix, //! Thread ordinal number. std::size_t thread_number ) { std::ostringstream ss; ss << disp_prefix.c_str() << "/wt-" << thread_number; return ss.str(); } } /* namespace reuse */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/reuse/default_thread_pool_size.hpp000066400000000000000000000013201523065210100253200ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Reusable function for the default size for a thread pool. */ #if !defined( SO_5_DISP_REUSE_DEFAULT_THREAD_POOL_SIZE_HPP ) #define SO_5_DISP_REUSE_DEFAULT_THREAD_POOL_SIZE_HPP #include namespace so_5::disp::reuse { // // default_thread_pool_size // /*! * \brief A helper function for detecting default thread count for * thread pool. * * Returns value of std::thread::hardware_concurrency() or 2 if * hardware_concurrency() returns 0. * * \since v.5.8.0 */ [[nodiscard]] inline std::size_t default_thread_pool_size() { auto c = std::thread::hardware_concurrency(); if( !c ) c = 2; return c; } } /* namespace so_5::disp::reuse */ #endif sobjectizer-5.8.6/dev/so_5/disp/reuse/make_actual_dispatcher.hpp000066400000000000000000000056251523065210100247520ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A set of helpers for making actual dispatcher instances. * * \since * v.5.6.0 */ #if !defined( SO_5_DISP_REUSE_MAKE_ACTUAL_DISPATCHER_HPP ) #define SO_5_DISP_REUSE_MAKE_ACTUAL_DISPATCHER_HPP #include #include #include namespace so_5 { namespace disp { namespace reuse { /*! * \brief Helper functions to adjust some dispatcher parameters with * respect to settings from environment. * * If dispatcher params doesn't have lock-factory specified the * lock-factory will be inherited from environment. * * \tparam Disp_Params_Type type of struct/class with dispatcher-related * parameters. This type should have queue_params() method. * * \since * v.5.6.0 */ template< typename Disp_Params_Type > void modify_disp_params( so_5::environment_t & env, Disp_Params_Type & params ) { auto lf = params.queue_params().lock_factory(); if( !lf ) { auto queue_params = params.queue_params(); queue_params.lock_factory( so_5::impl::default_lock_factory( env, lf ) ); params.set_queue_params( std::move(queue_params) ); } } /*! * \brief Helper function for creation of dispatcher instance with * respect to work thread activity tracking flag. * * \note * Calls modify_disp_params() before creation of dispatcher instance. * * \attention * Dispatcher constructor should have the following format: * \code * Dispatcher( * outliving_reference_t env, * const std::string_view data_source_base_name, * Disp_Params_Type disp_params, * Args && ...additional_args ); * \endcode * * \tparam Disp_Iface_Type a type of interface for actual dispatcher. This type * is expected to be a base class for Disp_No_Tracking and Disp_With_Tracking * types. * * \tparam Disp_No_Tracking a type of dispatcher for case when thread * activity tracking is not used. * * \tparam Disp_With_Tracking a type of dispatcher for case when thread * activity tracking is used. * * \tparam Disp_Params_Type a type of dispatcher-specific parameters. * * \tparam Args types of additional arguments for dispatcher's constructor. * * \since * v.5.6.0 */ template< typename Disp_Iface_Type, typename Disp_No_Tracking, typename Disp_With_Tracking, typename Disp_Params_Type, typename... Args > std::unique_ptr< Disp_Iface_Type > make_actual_dispatcher( outliving_reference_t< environment_t > env, const std::string_view name_base, Disp_Params_Type disp_params, Args && ...args ) { modify_disp_params( env.get(), disp_params ); using so_5::stats::activity_tracking_stuff::create_appropriate_disp; return create_appropriate_disp< Disp_Iface_Type, Disp_No_Tracking, Disp_With_Tracking >( env, name_base, std::move(disp_params), std::forward(args)... ); } } /* namespace reuse */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/reuse/queue_of_queues.hpp000066400000000000000000000156301523065210100234720ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Multi-producer/Multi-consumer queue of pointers to event queues. * * \since v.5.4.0, v.5.8.0 */ #if !defined( SO_5_DISP_REUSE_QUEUE_OF_QUEUES_HPP ) #define SO_5_DISP_REUSE_QUEUE_OF_QUEUES_HPP #include #include #include #include namespace so_5 { namespace disp { namespace reuse { // // queue_of_queues_t // /*! * \brief Multi-producer/Multi-consumer queue of pointers to event queues. * * \note * Since v.5.8.0 this type implements intrusive queue and requires that * type \a T provides the following methods: * \code * T * intrusive_queue_giveout_next() noexcept; * void intrusive_queue_set_next( T * next ) noexcept; * \endcode * * \tparam T type of event queue. * * \since v.5.4.0, v.5.8.0 */ template< class T > class queue_of_queues_t { public : using item_t = T; queue_of_queues_t( const so_5::disp::mpmc_queue_traits::queue_params_t & queue_params, std::size_t thread_count ) : m_lock{ queue_params.lock_factory()() } , m_max_thread_count{ thread_count } , m_next_thread_wakeup_threshold{ queue_params.next_thread_wakeup_threshold() } { // Reserve some space for storing infos about waiting // customer threads. m_waiting_customers.reserve( thread_count ); } //! Initiate shutdown for working threads. inline void shutdown() noexcept { std::lock_guard< so_5::disp::mpmc_queue_traits::lock_t > lock{ *m_lock }; m_shutdown = true; while( !m_waiting_customers.empty() ) pop_and_notify_one_waiting_customer(); } //! Get next active queue. /*! * \retval nullptr is the case of dispatcher shutdown. */ inline T * pop( so_5::disp::mpmc_queue_traits::condition_t & condition ) noexcept { std::lock_guard< so_5::disp::mpmc_queue_traits::lock_t > lock{ *m_lock }; do { if( m_shutdown ) break; if( m_head ) { // The queue isn't empty, the head has to be extracted. auto r = pop_head(); // There could be non-empty queue and sleeping workers... try_wakeup_someone_if_possible(); return r; } // Exception safety note: it seems that there should not be // dynamic memory allocation because m_waiting_customers is // reserved in the constructor and only push_back and pop_front // are used. So if the actual count of worker threads equals // to thread_count constructor's parameter, then there is // no need to expand m_waiting_customers vector. m_waiting_customers.push_back( &condition ); condition.wait(); // If we are here then the current wakeup procedure is // finished. m_wakeup_in_progress = false; } while( true ); return nullptr; } //! Switch the current non-empty queue to another one if it is possible. /*! * \return nullptr is the case of dispatcher shutdown. * * \since v.5.5.15.1 */ [[nodiscard]] inline T * try_switch_to_another( T * current ) noexcept { std::lock_guard< so_5::disp::mpmc_queue_traits::lock_t > lock{ *m_lock }; if( m_shutdown ) return nullptr; if( m_head ) { auto r = pop_head(); // Old non-empty queue must be stored for further processing. // No need to wakup someone because the length of the queue // didn't changed. push_to_queue( current ); return r; } return current; } //! Schedule execution of demands from the queue. void schedule( T * queue ) noexcept { std::lock_guard< so_5::disp::mpmc_queue_traits::lock_t > lock{ *m_lock }; push_to_queue( queue ); try_wakeup_someone_if_possible(); } so_5::disp::mpmc_queue_traits::condition_unique_ptr_t allocate_condition() { return m_lock->allocate_condition(); } private : //! Object's lock. so_5::disp::mpmc_queue_traits::lock_unique_ptr_t m_lock; //! Shutdown flag. bool m_shutdown{ false }; /*! * \brief The current head of the intrusive queue. * * Holds nullptr if the queue is empty. * * \since v.5.8.0 */ T * m_head{ nullptr }; /*! * \brief The current tail of the intrusive queue. * * Holds nullptr if the queue is empty. * It is equal to m_head if the queue contains just one item. * * \since v.5.8.0 */ T * m_tail{ nullptr }; /*! * \brief The current size of the intrusive queue. * * \since v.5.8.0 */ std::size_t m_queue_size{}; /*! * \brief Is some working thread in wakeup process now? * * \since v.5.5.15.1 * */ bool m_wakeup_in_progress{ false }; /*! * \brief Maximum count of working threads to be used with * that mpmc_queue. * * \since v.5.5.16 */ const std::size_t m_max_thread_count; /*! * \brief Threshold for wake up next working thread if there are * non-empty agent queues. * * \since v.5.5.16 */ const std::size_t m_next_thread_wakeup_threshold; //! Waiting threads. std::vector< so_5::disp::mpmc_queue_traits::condition_t * > m_waiting_customers; void pop_and_notify_one_waiting_customer() noexcept { auto & condition = *m_waiting_customers.back(); m_waiting_customers.pop_back(); m_wakeup_in_progress = true; condition.notify(); } /*! * \brief An attempt to wakeup another sleeping thread if it's necessary * and possible. * * \note Since v.5.5.16 there are some changes in wakeup conditions. * A working thread is awakened when: * - there are something in the queue; * - there are waiting customers but no one of them is in wakeup now; * - count of items in the queue is greater than * m_next_thread_wakeup_threshold or there is no active customers at * all. * * \since v.5.5.15.1 */ void try_wakeup_someone_if_possible() noexcept { if( m_head && !m_waiting_customers.empty() && !m_wakeup_in_progress && ( m_queue_size > m_next_thread_wakeup_threshold || m_max_thread_count == m_waiting_customers.size() ) ) { pop_and_notify_one_waiting_customer(); } } /*! * \brief Helper method that extracts the head item from the queue. * * \attention * This method must only be called if the queue isn't empty. * The method doesn't check this condition by itself. * * \since v.5.8.0 */ [[nodiscard]] T * pop_head() noexcept { auto r = m_head; m_head = r->intrusive_queue_giveout_next(); if( !m_head ) m_tail = nullptr; --m_queue_size; return r; } /*! * \brief Helper method that pushes a new item to the end of the queue. * * \since v.5.8.0 */ void push_to_queue( T * new_tail ) noexcept { if( m_tail ) { m_tail->intrusive_queue_set_next( new_tail ); m_tail = new_tail; } else { m_head = m_tail = new_tail; } ++m_queue_size; } }; } /* namespace reuse */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/reuse/thread_pool_stats.hpp000066400000000000000000000256671523065210100240240ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief Reusable tools for run-time monitoring of thread-pool-like dispatchers. */ #if !defined( SO_5_DISP_REUSE_THREAD_POOL_STATS_HPP ) #define SO_5_DISP_REUSE_THREAD_POOL_STATS_HPP #include #include #include #include #include #include namespace so_5 { namespace disp { namespace reuse { namespace thread_pool_stats { namespace stats = so_5::stats; /*! * \since * v.5.5.4 * * \brief Description of one event queue. */ struct queue_description_t { //! Prefix for data-sources related to that queue. stats::prefix_t m_prefix; //! Count of agents bound to that queue. std::size_t m_agent_count; //! Current queue size. std::size_t m_queue_size; }; /*! * \since * v.5.5.4 * * \brief A holder of one event queue information block. * * This holder must be allocated as dynamic object. * * There may be two references to it: * - one (main reference) is in coresponding event queue object inside * dispatcher. This reference will be destroyed with the event queue. * It means that while the event queue exists there will be at least * one reference to the holder; * - second (temporary reference) is created only for data source update * operation. This reference ensures that holder will not be destroyed * while data source update operation is in progress (but the event queue * could be destroyed). * * The most important thing about the holder is: actual value for * m_next atribute must be set only during data source update operation, * and must be dropped to nullptr after it. */ struct queue_description_holder_t : private atomic_refcounted_t { friend class intrusive_ptr_t< queue_description_holder_t >; //! Actual description for the event queue. queue_description_t m_desc; //! Next item in the chain of queues descriptions. /*! * Receives actual value only for small amount of time during * data source update operation. */ intrusive_ptr_t< queue_description_holder_t > m_next; }; /*! * \since * v.5.5.4 * * \brief Typedef for smart pointer to queue_description. */ using queue_description_holder_ref_t = intrusive_ptr_t< queue_description_holder_t >; /*! * \since * v.5.5.4 * * \brief Helper function for creating queue_description_holder object. */ inline queue_description_holder_ref_t make_queue_desc_holder( const stats::prefix_t & prefix, coop_id_t coop_id, std::size_t agent_count ) { queue_description_holder_ref_t result( new queue_description_holder_t() ); std::ostringstream ss; ss << prefix.c_str() << "/cq/" << coop_id; result->m_desc.m_prefix = stats::prefix_t{ ss.str() }; result->m_desc.m_agent_count = agent_count; result->m_desc.m_queue_size = 0; return result; } /*! * \since * v.5.5.4 * * \brief Helper function for creating queue_description_holder object. * * Must be used for the case when agent uses individual FIFO. */ inline queue_description_holder_ref_t make_queue_desc_holder( const stats::prefix_t & prefix, const void * agent ) { queue_description_holder_ref_t result( new queue_description_holder_t() ); std::ostringstream ss; ss << prefix.c_str() << "/aq/" << so_5::disp::reuse::ios_helpers::pointer{ agent }; result->m_desc.m_prefix = stats::prefix_t{ ss.str() }; result->m_desc.m_agent_count = 1; result->m_desc.m_queue_size = 0; return result; } /*! * \since * v.5.5.4 * * \brief An interface of collector of information about thread-pool-like * dispatcher state. */ class stats_consumer_t { protected : ~stats_consumer_t() {} public : //! Informs consumer about actual actual thread count. virtual void set_thread_count( std::size_t value ) = 0; //! Informs counsumer about yet another event queue. virtual void add_queue( const intrusive_ptr_t< queue_description_holder_t > & queue_desc ) = 0; /*! * \brief Informs consumer about yet another working thread * activity. * * \note This method is called only if thread activity tracking * is turned on. */ virtual void add_work_thread_activity( //! ID of working thread. const so_5::current_thread_id_t & thread_id, //! Statistics of working thread. const so_5::stats::work_thread_activity_stats_t & stats ) = 0; }; /*! * \since * v.5.5.4 * * \brief An interface of supplier of information about thread-pool-like * dispatcher state. * * It is intended to be used as mixin for a dispatcher class. */ class stats_supplier_t { protected : // Just to make compilers happy. virtual ~stats_supplier_t() {} public : virtual void supply( stats_consumer_t & consumer ) = 0; }; /*! * \since * v.5.5.4 * * \brief Type of data source for thread-pool-like dispatchers. */ class data_source_t : public stats::source_t { public : //! Initializing constructor. data_source_t( //! Supplier of statistical information. stats_supplier_t & supplier ) : m_supplier( supplier ) {} //! Setting of data-source basic name. void set_data_sources_name_base( //! Type of the dispatcher. //! Like 'tp' for thread-pool-dispatcher or //! 'atp' for adv-thread-pool-dispatcher. const std::string_view disp_type, //! Optional name to be used as part of data-source prefix. const std::string_view name_basic, //! Pointer to the dispatcher object. //! Will be used if \a name_basic is empty. const void * disp_pointer ) { m_prefix = make_disp_prefix( disp_type, name_basic, disp_pointer ); } //! Distribution of statistical information. void distribute( const mbox_t & mbox ) override { // Collecting... collector_t collector( m_wt_activity ); m_supplier.supply( collector ); // Distributing... so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_prefix, stats::suffixes::disp_thread_count(), collector.thread_count() ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_prefix, stats::suffixes::agent_count(), collector.agent_count() ); collector.for_each_thread_activity( [this, &mbox]( const so_5::current_thread_id_t & thread_id, const so_5::stats::work_thread_activity_stats_t & stats ) { so_5::send< stats::messages::work_thread_activity >( mbox, make_work_thread_prefix( thread_id ), stats::suffixes::work_thread_activity(), thread_id, stats ); } ); collector.for_each_queue( [&mbox]( const queue_description_t & queue ) { so_5::send< stats::messages::quantity< std::size_t > >( mbox, queue.m_prefix, stats::suffixes::agent_count(), queue.m_agent_count ); so_5::send< stats::messages::quantity< std::size_t > >( mbox, queue.m_prefix, stats::suffixes::work_thread_queue_size(), queue.m_queue_size ); } ); } //! Basic prefix for data source names. const stats::prefix_t & prefix() const { return m_prefix; } private : /*! * \brief Activity stats for a particular work thread. * \since * v.5.5.18 */ struct wt_activity_info_t { so_5::current_thread_id_t m_thread_id; stats::work_thread_activity_stats_t m_stats; wt_activity_info_t( const so_5::current_thread_id_t & thread_id, const stats::work_thread_activity_stats_t & stats ) : m_thread_id( thread_id ) , m_stats( stats ) {} }; /*! * \brief Type of storage for work thread activity info. * \since * v.5.5.18 */ using wt_activity_info_container_t = std::vector< wt_activity_info_t >; //! Statistical information supplier. stats_supplier_t & m_supplier; //! Prefix for data-source names. stats::prefix_t m_prefix; /*! * \brief Container for collecting work activity stats * from working threads. * * This container is stored in data_source itself and will * be reused on each distribution cycle. * * \since * v.5.5.18 */ wt_activity_info_container_t m_wt_activity; #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnon-virtual-dtor" #endif //! Actual type of statical information collector. class collector_t : public stats_consumer_t { public : collector_t( //! Where to store thread activity stats. wt_activity_info_container_t & wt_activity_holder ) : m_wt_activity( wt_activity_holder ) { // Old content must be reset. m_wt_activity.clear(); } ~collector_t() { // Chain of queue data sources must be cleaned up. auto holder = m_queue_desc_head; m_queue_desc_head.reset(); while( holder ) { auto current = holder; holder = holder->m_next; current->m_next.reset(); } m_queue_desc_tail.reset(); } virtual void set_thread_count( std::size_t thread_count ) override { m_thread_count = thread_count; } virtual void add_queue( const intrusive_ptr_t< queue_description_holder_t > & info ) override { m_agent_count += info->m_desc.m_agent_count; if( m_queue_desc_tail ) { m_queue_desc_tail->m_next = info; m_queue_desc_tail = info; } else { m_queue_desc_head = info; m_queue_desc_tail = info; } } virtual void add_work_thread_activity( const so_5::current_thread_id_t & thread_id, const stats::work_thread_activity_stats_t & stats ) override { m_wt_activity.emplace_back( thread_id, stats ); } std::size_t thread_count() const { return m_thread_count; } std::size_t agent_count() const { return m_agent_count; } template< typename Lambda > void for_each_queue( Lambda lambda ) const { const queue_description_holder_t * p = m_queue_desc_head.get(); while( p ) { lambda( p->m_desc ); p = p->m_next.get(); } } template< typename Lambda > void for_each_thread_activity( Lambda lambda ) const { for( const auto & wt : m_wt_activity ) lambda( wt.m_thread_id, wt.m_stats ); } private : std::size_t m_thread_count = { 0 }; std::size_t m_agent_count = { 0 }; wt_activity_info_container_t & m_wt_activity; intrusive_ptr_t< queue_description_holder_t > m_queue_desc_head; intrusive_ptr_t< queue_description_holder_t > m_queue_desc_tail; }; #if defined(__clang__) #pragma clang diagnostic pop #endif stats::prefix_t make_work_thread_prefix( const so_5::current_thread_id_t & tid ) { std::ostringstream ss; ss << m_prefix << "/wt-" << so_5::raw_id_from_current_thread_id( tid ); return stats::prefix_t{ ss.str() }; } }; } /* namespace thread_pool_stats */ } /* namespace reuse */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/reuse/work_thread/000077500000000000000000000000001523065210100220665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/reuse/work_thread/work_thread.hpp000066400000000000000000000366301523065210100251200ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Working thread for dispatchers. */ #if !defined( SO_5_DISP_REUSE_WORK_THREAD_WORK_THREAD_HPP ) #define SO_5_DISP_REUSE_WORK_THREAD_WORK_THREAD_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace reuse { namespace work_thread { //! Typedef for demand's container. using demand_container_t = std::deque< execution_demand_t >; namespace queue_traits = so_5::disp::mpsc_queue_traits; //! Typedef for atomic demands counter. /*! * \since v.5.5.7 */ using demands_counter_t = std::atomic< std::size_t >; /*! * \brief Type for result of demand extraction. */ enum class extraction_result_t { //! Demand has been extracted. demand_extracted = 1, //! Demand has not been extracted because of shutdown. shutting_down = 2, //! Demand has not been extracted because the demand queue is empty. no_demands = 3 }; namespace demand_queue_details { /*! * \brief Common data for all implementations of demand_queue. * * \since v.5.5.18 */ struct common_data_t { //! Demand queue. demand_container_t m_demands; //! \name Objects for the thread safety. //! \{ queue_traits::lock_unique_ptr_t m_lock; //! \} //! Service flag. /*! true -- shall do the service, methods push/pop must work. false -- the service is stopped or will be stopped. */ bool m_in_service{ false }; //! Initializing constructor. common_data_t( //! Lock object to be used by queue. queue_traits::lock_unique_ptr_t lock ) : m_lock( std::move(lock) ) {} ~common_data_t() { m_demands.clear(); } }; /*! * \brief A part of demand queue implementation for the case * when activity tracking is not used. */ class no_activity_tracking_impl_t : protected common_data_t { public : no_activity_tracking_impl_t( queue_traits::lock_unique_ptr_t lock ) : common_data_t( std::move(lock) ) {} protected : void wait_started() {} void wait_finished() {} }; /*! * \brief A part of demand queue implementation for the case * when activity tracking is used. */ class with_activity_tracking_impl_t : protected common_data_t { public : with_activity_tracking_impl_t( queue_traits::lock_unique_ptr_t lock ) : common_data_t( std::move(lock) ) , m_waiting_stats( *m_lock ) {} so_5::stats::activity_stats_t take_activity_stats() { return m_waiting_stats.take_stats(); } protected : void wait_started() { m_waiting_stats.start(); } void wait_finished() { m_waiting_stats.stop(); } private : so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::external_lock< queue_traits::lock_t, so_5::stats::activity_tracking_stuff::no_lock_at_start_stop_policy > > m_waiting_stats; }; // // demand_queue_template_t // //! Implementation of demand_queue in form of a template. /*! demand_queue_t has shutdown flag inside. demand_queue_t is thread safe and is intended to be used by several concurrent threads. */ template< typename Impl > class queue_template_t final : public event_queue_t , public Impl { public: queue_template_t( //! Lock object to be used by queue. queue_traits::lock_unique_ptr_t lock ) : Impl( std::move(lock) ) {} /*! * \name Implementation of event_queue interface. * \{ */ virtual void push( execution_demand_t demand ) override { queue_traits::lock_guard_t guard{ *(this->m_lock) }; if( this->m_in_service ) { const bool demands_empty_before_service = this->m_demands.empty(); this->m_demands.push_back( std::move( demand ) ); if( demands_empty_before_service ) { // May be someone is waiting... // It should be informed about new demands. guard.notify_one(); } } } /*! * \note * Delegates the work to the push() method. */ void push_evt_start( execution_demand_t demand ) override { this->push( std::move(demand) ); } /*! * \note * Delegates the work to the push() method. * * \attention * Terminates the whole application if the push() throws. */ void push_evt_finish( execution_demand_t demand ) noexcept override { this->push( std::move(demand) ); } /*! * \} */ //! Try to extract demands from the queue. /*! If there is no demands in queue then current thread will sleep until: - the new demand is put in the queue; - a shutdown signal. \note Since v.5.5.7 this method also updates external demands counter. This update is performed under queue's lock. It should prevent errors when run-time monitor can get wrong quantity of demands. */ extraction_result_t pop( /*! Receiver for extracted demands. */ demand_container_t & demands, /*! External demands counter to be updated. */ demands_counter_t & external_counter ) { queue_traits::unique_lock_t lock{ *(this->m_lock) }; while( true ) { if( this->m_in_service && !this->m_demands.empty() ) { swap( demands, this->m_demands ); // It's time to update external counter. external_counter.store( demands.size(), std::memory_order_release ); break; } else if( !this->m_in_service ) return extraction_result_t::shutting_down; else { // Queue is empty. We should wait for a demand or // a shutdown signal. // Since v.5.5.18 we must take care about activity tracking. this->wait_started(); lock.wait_for_notify(); this->wait_finished(); } } return extraction_result_t::demand_extracted; } //! Start demands processing. void start_service() { queue_traits::lock_guard_t lock{ *(this->m_lock) }; this->m_in_service = true; } //! Stop demands processing. void stop_service() { queue_traits::lock_guard_t lock{ *(this->m_lock) }; this->m_in_service = false; // If the demands queue is empty then someone is waiting // for new demands inside pop(). if( this->m_demands.empty() ) lock.notify_one(); } //! Clear demands queue. void clear() { queue_traits::lock_guard_t lock{ *(this->m_lock) }; this->m_demands.clear(); } /*! * \brief Get the count of demands in the queue. * * \note Since v.5.5.7 this method also uses external demands * counter. Addition of demands quantity inside demands queue and * the value of external counter is performed under the queue lock. * * \since v.5.5.4 */ std::size_t demands_count( const demands_counter_t & external_counter ) { queue_traits::lock_guard_t lock{ *(this->m_lock) }; return this->m_demands.size() + external_counter.load( std::memory_order_acquire ); } }; } /* namespace demand_queue_details */ /*! * \brief An alias for demand_queue without activity tracking. * * \since v.5.5.18 */ using demand_queue_no_activity_tracking_t = demand_queue_details::queue_template_t< demand_queue_details::no_activity_tracking_impl_t >; /*! * \brief An alias for demand_queue with activity tracking. * * \since v.5.5.18 */ using demand_queue_with_activity_tracking_t = demand_queue_details::queue_template_t< demand_queue_details::with_activity_tracking_impl_t >; namespace details { //! Thread status flag. enum class status_t : int { //! 0 - thread execution should be stopped. stopped = 0, //! 1 - thread execution should be continued. working = 1 }; /*! * \brief Common data for all work thread implementations. * * \since v.5.5.18 */ template< typename Demand_Queue > struct common_data_t { //! Working thread. work_thread_holder_t m_thread_holder; //! Thread status flag. std::atomic< status_t > m_status{ status_t::stopped }; //! Demands queue. Demand_Queue m_queue; /*! * \brief ID of working thread. * * \attention Receive the value only after start of thread body. */ std::atomic< so_5::current_thread_id_t > m_thread_id; /*! * \brief A counter for calculating count of demands in * the queue. * * \note Will be used for run-time monitoring. */ demands_counter_t m_demands_count = { 0 }; common_data_t( work_thread_holder_t thread_holder, queue_traits::lock_factory_t queue_lock_factory ) : m_thread_holder{ std::move(thread_holder) } , m_queue( queue_lock_factory() ) {} }; /*! * \brief Part of implementation of work thread without activity tracking. * * \since v.5.5.18 */ class no_activity_tracking_impl_t : protected common_data_t< demand_queue_no_activity_tracking_t > { public : no_activity_tracking_impl_t( work_thread_holder_t thread_holder, queue_traits::lock_factory_t queue_lock_factory ) : common_data_t{ std::move(thread_holder), std::move(queue_lock_factory) } {} protected : //! Main method for serving block of demands. void serve_demands_block( //! ID of worker thread. so_5::current_thread_id_t thread_id, //! Bunch of demands to be processed. demand_container_t & demands ) { while( !demands.empty() ) { auto & demand = demands.front(); demand.call_handler( thread_id ); demands.pop_front(); --(this->m_demands_count); } } }; /*! * \brief Part of implementation of work thread with activity tracking. * * \since * v.5.5.18 */ class activity_tracking_impl_t : protected common_data_t< demand_queue_with_activity_tracking_t > { using activity_tracking_traits = so_5::stats::activity_tracking_stuff::traits; public : activity_tracking_impl_t( work_thread_holder_t thread_holder, queue_traits::lock_factory_t queue_lock_factory ) : common_data_t{ std::move(thread_holder), std::move(queue_lock_factory) } {} /*! * \brief Get the activity stats. */ so_5::stats::work_thread_activity_stats_t take_activity_stats() { namespace stats = so_5::stats; stats::work_thread_activity_stats_t result; bool is_working{ false }; stats::clock_type_t::time_point working_started_at; { std::lock_guard< activity_tracking_traits::lock_t > lock{ m_stats_lock }; result.m_working_stats = m_activity_stats; // Special care to the current activity (if exists). if( m_activity_started_at ) { is_working = true; working_started_at = *m_activity_started_at; } } if( is_working ) { stats::details::update_stats_from_current_time( result.m_working_stats, working_started_at ); } result.m_waiting_stats = m_queue.take_activity_stats(); return result; } protected : //! Main method for serving block of demands. void serve_demands_block( //! ID of worker thread. so_5::current_thread_id_t thread_id, //! Bunch of demands to be processed. demand_container_t & demands ) { auto activity_started_at = so_5::stats::clock_type_t::now(); { std::lock_guard< activity_tracking_traits::lock_t > lock{ m_stats_lock }; m_activity_started_at = &activity_started_at; m_activity_stats.m_count += 1; } while( !demands.empty() ) { auto & demand = demands.front(); demand.call_handler( thread_id ); const auto activity_finished_at = so_5::stats::clock_type_t::now(); demands.pop_front(); --m_demands_count; { std::lock_guard< activity_tracking_traits::lock_t > lock{ m_stats_lock }; so_5::stats::details::update_stats_from_duration( m_activity_stats, activity_finished_at - activity_started_at ); if( demands.empty() ) m_activity_started_at = nullptr; else { activity_started_at = activity_finished_at; m_activity_stats.m_count += 1; } } } } private : /*! * \brief Lock for manipulation of activity stats. */ activity_tracking_traits::lock_t m_stats_lock; /*! * \brief Pointer to time_point object related to the current activity. * * Value nullptr means that there is no any event running on * the work thread. */ const so_5::stats::clock_type_t::time_point * m_activity_started_at{}; /*! * \brief Activity statistics. */ so_5::stats::activity_stats_t m_activity_stats{}; }; /*! * \brief Implementation of work thread in form of template. * * \since * v.5.5.18 */ template< typename Impl > class work_thread_template_t final : public Impl { public : work_thread_template_t( //! Holder of the work thread. work_thread_holder_t thread_holder, //! Factory for creation of lock object for demand queue. queue_traits::lock_factory_t queue_lock_factory ) : Impl( std::move(thread_holder), std::move(queue_lock_factory) ) {} //! Start the working thread. void start() { // NOTE: those actions have to be rollbacked if work thread can't // be started. this->m_queue.start_service(); this->m_status = status_t::working; so_5::details::do_with_rollback_on_exception( [this]() { this->m_thread_holder.unchecked_get().start( [this]() { this->body(); } ); }, [this]() { // We can't recover if that code throws. so_5::details::invoke_noexcept_code( [this]() { this->m_status = status_t::stopped; this->m_queue.stop_service(); } ); } ); } //! Send the shutdown signal to the working thread. void shutdown() { this->m_status = status_t::stopped; this->m_queue.stop_service(); } //! Wait the full stop of the working thread. /*! * All non-processed demands from the queue will be destroyed * after a stop of the working thread. */ void wait() { so_5::impl::ensure_join_from_different_thread( this->m_thread_id.load() ); this->m_thread_holder.unchecked_get().join(); this->m_queue.clear(); } /*! * \brief Get the underlying event_queue object. */ event_queue_t & event_queue() { return this->m_queue; } /*! * \brief Get a binding information for an agent. */ event_queue_t * get_agent_binding() { return &this->event_queue(); } /*! * \brief Get the count of demands in the queue. */ std::size_t demands_count() { return this->m_queue.demands_count( this->m_demands_count ); } /*! * \brief Get ID of work thread. * * \note This method returns correct value only after start * of the thread. * * \since * v.5.5.18 */ so_5::current_thread_id_t thread_id() const { return this->m_thread_id.load(); } private : //! Main thread body. void body() { // Store current thread ID to attribute to avoid thread ID // request on every event execution. const auto thread_id = so_5::query_current_thread_id(); this->m_thread_id = thread_id; // Local demands queue. demand_container_t demands; auto result = extraction_result_t::no_demands; while( status_t::working == this->m_status ) { // If the local queue is empty then we should try // to get new demands. if( demands.empty() ) result = this->m_queue.pop( demands, this->m_demands_count ); // Serve demands if any. if( extraction_result_t::demand_extracted == result ) this->serve_demands_block( thread_id, demands ); } } }; } /* namespace details */ // // work_thread_t // /*! * \brief Type of work thread without activity tracking. */ using work_thread_no_activity_tracking_t = details::work_thread_template_t< details::no_activity_tracking_impl_t >; // // work_thread_with_activity_tracking_t // //! Working thread with activity tracking. /*! * \since * v.5.5.18 */ using work_thread_with_activity_tracking_t = details::work_thread_template_t< details::activity_tracking_impl_t >; } /* namespace work_thread */ } /* namespace reuse */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/reuse/work_thread_activity_tracking.hpp000066400000000000000000000034721523065210100264030ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Various helpers to work with thread activity tracking stuff. * * \since v.5.5.18 */ #if !defined( SO_5_DISP_REUSE_WORK_THREAD_ACTIVITY_TRACKING_HPP ) #define SO_5_DISP_REUSE_WORK_THREAD_ACTIVITY_TRACKING_HPP #include namespace so_5 { namespace disp { namespace reuse { /*! * \brief Mixin with thread activity tracking flag. * * Indended to be used as mixin for various disp_params_t classes. * * \since v.5.5.18 */ template< typename Params > class work_thread_activity_tracking_flag_mixin_t { work_thread_activity_tracking_t m_flag{ work_thread_activity_tracking_t::unspecified }; public : //! Getter for work thread activity tracking. work_thread_activity_tracking_t work_thread_activity_tracking() const noexcept { return m_flag; } friend inline void swap( work_thread_activity_tracking_flag_mixin_t & a, work_thread_activity_tracking_flag_mixin_t & b ) noexcept { using std::swap; swap( a.m_flag, b.m_flag ); } //! Setter for work thread activity tracking. Params & work_thread_activity_tracking( work_thread_activity_tracking_t v ) noexcept { m_flag = v; return static_cast< Params & >(*this); } //! Helper for turning work thread activity tracking on. /*! * \since v.5.5.18 */ Params & turn_work_thread_activity_tracking_on() noexcept { return work_thread_activity_tracking( work_thread_activity_tracking_t::on ); } //! Helper for turning work thread activity tracking off. /*! * \since v.5.5.18 */ Params & turn_work_thread_activity_tracking_off() noexcept { return work_thread_activity_tracking( work_thread_activity_tracking_t::off ); } }; } /* namespace reuse */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/reuse/work_thread_factory_params.hpp000066400000000000000000000024751523065210100257010ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Various helpers to work with work thread factories. * * \since v.5.7.3 */ #if !defined( SO_5_DISP_REUSE_WORK_THREAD_FACTORY_PARAMS_HPP ) #define SO_5_DISP_REUSE_WORK_THREAD_FACTORY_PARAMS_HPP #include namespace so_5 { namespace disp { namespace reuse { /*! * \brief Mixin that holds optional work thread factory. * * Indended to be used as mixin for various disp_params_t classes. * * \since v.5.7.3 */ template< typename Params > class work_thread_factory_mixin_t { /*! * Factory to be used. * * \note * It can be nullptr. */ abstract_work_thread_factory_shptr_t m_factory; public : //! Getter for work thread factory. [[nodiscard]] abstract_work_thread_factory_shptr_t work_thread_factory() const noexcept { return m_factory; } friend void swap( work_thread_factory_mixin_t & a, work_thread_factory_mixin_t & b ) noexcept { using std::swap; swap( a.m_factory, b.m_factory ); } //! Setter for work thread factory. Params & work_thread_factory( abstract_work_thread_factory_shptr_t v ) noexcept { m_factory = std::move(v); return static_cast< Params & >(*this); } }; } /* namespace reuse */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/thread_pool/000077500000000000000000000000001523065210100207325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/thread_pool/impl/000077500000000000000000000000001523065210100216735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/disp/thread_pool/impl/basic_event_queue.hpp000066400000000000000000000161171523065210100261000ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A reusable implementation of agent_queue that can be used by * various thread-pool dispatchers. * * \since v.5.8.0 */ #if !defined( SO_5_DISP_THREAD_POOL_IMPL_BASIC_EVENT_QUEUE_HPP ) #define SO_5_DISP_THREAD_POOL_IMPL_BASIC_EVENT_QUEUE_HPP #include #include #include #include namespace so_5 { namespace disp { namespace thread_pool { namespace impl { using spinlock_t = so_5::default_spinlock_t; // // basic_event_queue_t // class basic_event_queue_t : public event_queue_t { protected : //! Actual demand in event queue. struct demand_t : public execution_demand_t { //! Next item in queue. /*! * \note * It's a dynamically allocated object that has to be deallocated * manually during the destruction of the queue. */ demand_t * m_next; demand_t() : m_next( nullptr ) {} demand_t( execution_demand_t && original ) : execution_demand_t( std::move( original ) ) , m_next( nullptr ) {} }; public : basic_event_queue_t( std::size_t max_demands_at_once ) : m_max_demands_at_once( max_demands_at_once ) , m_tail_demand( &m_head_demand ) {} ~basic_event_queue_t() override { while( m_head_demand.m_next ) remove_head(); } /*! * \brief Helper method that implements pushing of a new preallocated * demand to the queue. * * It's intended to be used in implementation of push(), * push_evt_start() and push_evt_finish() methods. * * \since v.5.8.0 */ void push_preallocated( std::unique_ptr< demand_t > tail_demand ) noexcept { const bool was_empty = [&]() noexcept { std::lock_guard< spinlock_t > lock( m_lock ); const bool queue_was_empty = (nullptr == m_head_demand.m_next); m_tail_demand->m_next = tail_demand.release(); m_tail_demand = m_tail_demand->m_next; ++m_size; return queue_was_empty; }(); // Scheduling of the queue must be done when queue lock // is unlocked. if( was_empty ) this->schedule_on_disp_queue(); } //! Push next demand to queue. void push( execution_demand_t demand ) override { std::unique_ptr< demand_t > tail_demand{ new demand_t( std::move( demand ) ) }; push_preallocated( std::move(tail_demand) ); } //! Push evt_start demand to the queue. void push_evt_start( execution_demand_t demand ) override { // Just delegate the work. this->push( std::move(demand) ); } //! Push evt_start demand to the queue. /*! * \attention * This method is noexcept but it allocates and allocation * can throw. In that case the whole application will be * terminated. */ void push_evt_finish( execution_demand_t demand ) noexcept override { // Just delegate the work. this->push( std::move(demand) ); } //! Get the front demand from queue. /*! * \attention This method must be called only on non-empty queue. */ execution_demand_t & front() { return *(m_head_demand.m_next); } /*! * \brief Queue emptyness indication. * * \since v.5.5.15.1 */ enum class emptyness_t { empty, not_empty }; /*! * \brief Indication of possibility of continuation of demands processing. * * \since v.5.5.15.1 */ enum class processing_continuation_t { //! Next demand can be processed. enabled, disabled }; /*! * \brief A result of erasing of the front demand from queue. * * \since v.5.5.15.1 */ struct pop_result_t { //! Can demands processing be continued? processing_continuation_t m_continuation; //! Is event queue empty? emptyness_t m_emptyness; }; //! Remove the front demand. /*! * \note Return processing_continuation_t::disabled if * \a demands_processed exceeds m_max_demands_at_once or if * event queue is empty. */ pop_result_t pop( //! Count of consequently processed demands from that queue. std::size_t demands_processed ) { // Actual deletion of old head must be performed // when m_lock will be released. std::unique_ptr< demand_t > old_head; { std::lock_guard< spinlock_t > lock( m_lock ); old_head = remove_head(); const auto emptyness = m_head_demand.m_next ? emptyness_t::not_empty : emptyness_t::empty; if( emptyness_t::empty == emptyness ) m_tail_demand = &m_head_demand; return pop_result_t{ detect_continuation( emptyness, demands_processed ), emptyness }; } } /*! * \brief Wait while queue becomes empty. * * It is necessary because there is a possibility that * after processing of demand_handler_on_finish cooperation * will be destroyed and agents will be unbound from dispatcher * before the return from demand_handler_on_finish. * * Without waiting for queue emptyness it could lead to * dangling pointer to agent_queue in worker thread. */ void wait_for_emptyness() noexcept { bool empty = false; while( !empty ) { { std::lock_guard< spinlock_t > lock( m_lock ); empty = (nullptr == m_head_demand.m_next); } if( !empty ) std::this_thread::yield(); } } /*! * \brief Get the current size of the queue. * * \since v.5.5.4 */ [[nodiscard]] std::size_t size() const noexcept { return m_size.load( std::memory_order_acquire ); } protected: /*! * \brief Perform scheduling of processing of this event queue * * This method should be overrided in a derived class to call * schedule() method for an appropriate dispatcher queue. * See so_5::disp::reuse::queue_of_queues_t::schedule(). */ virtual void schedule_on_disp_queue() noexcept = 0; private : //! Maximum count of demands to be processed consequently. const std::size_t m_max_demands_at_once; //! Object's lock. spinlock_t m_lock; //! Head of the demand's queue. /*! * Never contains actual demand. Only m_next field is used. */ demand_t m_head_demand; //! Tail of the demand's queue. /*! * Must point to m_head_demand if queue is empty or to the very * last queue item otherwise. */ demand_t * m_tail_demand; /*! * \brief Current size of the queue. * * \since v.5.5.4 */ std::atomic< std::size_t > m_size = { 0 }; //! Helper method for deleting queue's head object. inline std::unique_ptr< demand_t > remove_head() noexcept { std::unique_ptr< demand_t > to_be_deleted{ m_head_demand.m_next }; m_head_demand.m_next = m_head_demand.m_next->m_next; --m_size; return to_be_deleted; } //! Can processing be continued? inline processing_continuation_t detect_continuation( emptyness_t emptyness, const std::size_t processed ) { return emptyness_t::not_empty == emptyness && processed < m_max_demands_at_once ? processing_continuation_t::enabled : processing_continuation_t::disabled; } }; } /* namespace impl */ } /* namespace thread_pool */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/thread_pool/impl/common_implementation.hpp000066400000000000000000000271451523065210100270120ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Reusable common implementation for thread-pool-like dispatchers. * * \since v.5.5.4 */ #if !defined( SO_5_DISP_THREAD_POOL_IMPL_COMMON_IMPLEMENTATION_HPP ) #define SO_5_DISP_THREAD_POOL_IMPL_COMMON_IMPLEMENTATION_HPP #include #include #include #include #include #include namespace so_5 { namespace disp { namespace thread_pool { namespace common_implementation { namespace stats = so_5::stats; namespace tp_stats = so_5::disp::reuse::thread_pool_stats; // // dispatcher_t // /*! * \brief Reusable common implementation for thread-pool-like dispatchers. * * \tparam Work_Thread type of worker thread to be used. * * \tparam Dispatcher_Queue type of dispatcher queue. A single instance of * dispatcher queue will be used for scheduling separate agent queues. * It'e expected to be so_5::disp::reuse::queue_params_t. * * \tparam Bind_Params type of bind_params_t with parameters for binding * just one agent to the dispatcher. * * \tparam Adaptations type with static method. This method adapts the * behavoir of Dispatcher_Queue (and corresponding agent queues) to the * needs of the dispatcher. See so_5::disp::thread_pool::impl::adaptation_t * or so_5::disp::adv_thread_pool::impl::adaptation_t as examples. * * \since v.5.5.4 */ template< typename Work_Thread, typename Dispatcher_Queue, typename Bind_Params, typename Adaptations > class dispatcher_t final : public tp_stats::stats_supplier_t { private : //! A short alias for agent queue type. using agent_queue_t = typename Dispatcher_Queue::item_t; using agent_queue_ref_t = so_5::intrusive_ptr_t< agent_queue_t >; //! Data for one cooperation. struct cooperation_data_t { //! Event queue for the cooperation. agent_queue_ref_t m_queue; //! Count of agents form that cooperation. /*! * When this counter is zero then cooperation data * must be destroyed. */ std::size_t m_agents; /*! * \brief Description of that queue for run-time monitoring. * * \since v.5.5.4 */ tp_stats::queue_description_holder_ref_t m_queue_desc; cooperation_data_t( agent_queue_ref_t queue, std::size_t agents, const stats::prefix_t & data_source_name_prefix, coop_id_t coop_id ) : m_queue( std::move( queue ) ) , m_agents( agents ) , m_queue_desc( tp_stats::make_queue_desc_holder( data_source_name_prefix, coop_id, agents ) ) {} /*! * \brief Update queue information for run-time monitoring. * * \since v.5.5.4 */ void update_queue_stats() { m_queue_desc->m_desc.m_agent_count = m_agents; m_queue_desc->m_desc.m_queue_size = m_queue->size(); } }; //! Map from cooperation name to the cooperation data. using cooperation_map_t = std::map< coop_id_t, cooperation_data_t >; //! Data for one agent. struct agent_data_t { //! Event queue for the agent. /*! * It could be individual queue or queue for the whole * cooperation (to which agent is belonging). */ agent_queue_ref_t m_queue; /*! * \brief Description of that queue for run-time monitoring. * * \note This description is created only if agent * uses individual FIFO. * * \since v.5.5.4 * */ tp_stats::queue_description_holder_ref_t m_queue_desc; //! Constructor for the case when agent uses cooperation FIFO. agent_data_t( agent_queue_ref_t queue ) : m_queue( std::move( queue ) ) {} //! Constructor for the case when agent uses individual FIFO. /*! * In this case a queue_description object must be created. */ agent_data_t( agent_queue_ref_t queue, const stats::prefix_t & data_source_name_prefix, const agent_t * agent_ptr ) : m_queue( std::move( queue ) ) , m_queue_desc( tp_stats::make_queue_desc_holder( data_source_name_prefix, agent_ptr ) ) {} /*! * \brief Does agent use cooperation FIFO? * * \since v.5.5.4 */ [[nodiscard]] bool cooperation_fifo() const { return !m_queue_desc; } /*! * \brief Update queue description with current information. * * \attention Must be called only if !cooperation_fifo(). * * \since v.5.5.4 */ void update_queue_stats() { m_queue_desc->m_desc.m_agent_count = 1; m_queue_desc->m_desc.m_queue_size = m_queue->size(); } }; //! Map from agent pointer to the agent data. using agent_map_t = std::map< agent_t *, agent_data_t >; public : dispatcher_t( const dispatcher_t & ) = delete; dispatcher_t & operator=( const dispatcher_t & ) = delete; //! Constructor. template< typename Dispatcher_Params > dispatcher_t( environment_t & env, const so_5::disp::reuse::work_thread_factory_mixin_t< Dispatcher_Params > & disp_params, const std::string_view name_base, std::size_t thread_count, const so_5::disp::mpmc_queue_traits::queue_params_t & queue_params ) : m_queue{ queue_params, thread_count } , m_thread_count( thread_count ) , m_data_source( stats_supplier() ) { m_threads.reserve( thread_count ); for( std::size_t i = 0; i != m_thread_count; ++i ) { // Since v.5.7.3 an instance of the actual work thread // has to be acquired via factory. auto work_thread_holder = acquire_work_thread( disp_params, env ); m_threads.emplace_back( std::unique_ptr< Work_Thread >( new Work_Thread{ outliving_mutable(m_queue), std::move(work_thread_holder) } ) ); } m_data_source.get().set_data_sources_name_base( Adaptations::dispatcher_type_name(), name_base, this ); } void start( environment_t & env ) { m_data_source.start( outliving_mutable(env.stats_repository()) ); for( auto & t : m_threads ) t->start(); } void shutdown_then_wait() noexcept { m_queue.shutdown(); for( auto & t : m_threads ) t->join(); m_data_source.stop(); } //! Preallocate all necessary resources for a new agent. void preallocate_resources_for_agent( agent_t & agent, const Bind_Params & params ) { std::lock_guard< std::mutex > lock{ m_lock }; if( Adaptations::is_individual_fifo( params ) ) bind_agent_with_inidividual_fifo( agent_ref_t{ &agent }, params ); else bind_agent_with_cooperation_fifo( agent_ref_t{ &agent }, params ); } //! Undo preallocation of resources for a new agent. void undo_preallocation_for_agent( agent_t & agent ) noexcept { std::lock_guard< std::mutex > lock{ m_lock }; auto it = m_agents.find( &agent ); if( it != m_agents.end() ) { if( it->second.cooperation_fifo() ) { auto it_coop = m_cooperations.find( agent.so_coop().id() ); if( it_coop != m_cooperations.end() && 0 == --(it_coop->second.m_agents) ) { // agent_queue object can be destroyed // only when it is empty. Adaptations::wait_for_queue_emptyness( *(it_coop->second.m_queue) ); m_cooperations.erase( it_coop ); } } else // agent_queue object can be destroyed // only when it is empty. Adaptations::wait_for_queue_emptyness( *(it->second.m_queue) ); m_agents.erase( it ); } } //! Get resources allocated for an agent. [[nodiscard]] event_queue_t * query_resources_for_agent( agent_t & agent ) noexcept { std::lock_guard< std::mutex > lock{ m_lock }; auto it = m_agents.find( &agent ); if( it->second.cooperation_fifo() ) return m_cooperations.find( agent.so_coop().id() )-> second.m_queue.get(); else return it->second.m_queue.get(); } //! Unbind agent from the dispatcher. void unbind_agent( agent_t & agent ) noexcept { undo_preallocation_for_agent( agent ); } private : //! Queue for active agent's queues. Dispatcher_Queue m_queue; //! Count of working threads. const std::size_t m_thread_count; //! Pool of work threads. std::vector< std::unique_ptr< Work_Thread > > m_threads; //! Object's lock. std::mutex m_lock; //! Information about cooperations. /*! * Information to this map is added only if an agent is * using cooperation FIFO mechanism. */ cooperation_map_t m_cooperations; //! Information of agents. agent_map_t m_agents; /*! * \since * v.5.5.4 * * \brief Data source for the run-time monitoring. */ stats::manually_registered_source_holder_t< tp_stats::data_source_t > m_data_source; //! Creation event queue for an agent with individual FIFO. void bind_agent_with_inidividual_fifo( agent_ref_t agent, const Bind_Params & params ) { auto queue = make_new_agent_queue( params ); m_agents.emplace( agent.get(), agent_data_t{ queue, m_data_source.get().prefix(), agent.get() } ); } //! Creation event queue for an agent with individual FIFO. /*! * If the data for the agent's cooperation is not created yet * it will be created. */ void bind_agent_with_cooperation_fifo( agent_ref_t agent, const Bind_Params & params ) { const auto id = agent->so_coop().id(); auto it = m_cooperations.find( id ); if( it == m_cooperations.end() ) it = m_cooperations.emplace( id, cooperation_data_t( make_new_agent_queue( params ), 1, m_data_source.get().prefix(), id ) ) .first; else it->second.m_agents += 1; so_5::details::do_with_rollback_on_exception( [&] { m_agents.emplace( agent.get(), agent_data_t{ it->second.m_queue } ); }, [&] { // Rollback m_cooperations modification. if( 0 == --(it->second.m_agents) ) m_cooperations.erase( it ); } ); } //! Helper method for creating event queue for agents/cooperations. agent_queue_ref_t make_new_agent_queue( const Bind_Params & params ) { return agent_queue_ref_t( new agent_queue_t{ outliving_mutable(m_queue), params } ); } /*! * \since * v.5.5.4 * * \brief Helper method for casting to stats_supplier-object. */ tp_stats::stats_supplier_t & stats_supplier() { return *this; } /*! * \since * v.5.5.4 * * \brief Implementation of stats_supplier-related stuff. */ virtual void supply( tp_stats::stats_consumer_t & consumer ) override { // Statics must be collected on locked object. std::lock_guard< std::mutex > lock{ m_lock }; consumer.set_thread_count( m_threads.size() ); for( auto & t : m_threads ) { using stats_t = so_5::stats::work_thread_activity_stats_t; Work_Thread & wt = *t; wt.take_activity_stats( [&wt, &consumer]( const stats_t & st ) { consumer.add_work_thread_activity( wt.thread_id(), st ); } ); } for( auto & q : m_cooperations ) { auto & s = q.second; s.update_queue_stats(); consumer.add_queue( s.m_queue_desc ); } for( auto & a : m_agents ) { auto & s = a.second; if( !s.cooperation_fifo() ) { s.update_queue_stats(); consumer.add_queue( s.m_queue_desc ); } } } }; } /* namespace common_implementation */ } /* namespace thread_pool */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/thread_pool/impl/disp.hpp000066400000000000000000000066771523065210100233630ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief An implementation of thread pool dispatcher. * * \since v.5.4.0 */ #if !defined( SO_5_DISP_THREAD_POOL_IMPL_DISP_HPP ) #define SO_5_DISP_THREAD_POOL_IMPL_DISP_HPP #include #include #include namespace so_5 { namespace disp { namespace thread_pool { namespace impl { class agent_queue_t; // // dispatcher_queue_t // using dispatcher_queue_t = so_5::disp::reuse::queue_of_queues_t< agent_queue_t >; // // agent_queue_t // /*! * \brief Event queue for the agent (or cooperation). * * \note * This class isn't final since v.5.8.0 * * \since v.5.4.0 */ class agent_queue_t : public basic_event_queue_t , private so_5::atomic_refcounted_t { friend class so_5::intrusive_ptr_t< agent_queue_t >; public : //! Initializing constructor. agent_queue_t( //! Dispatcher queue to work with. outliving_reference_t< dispatcher_queue_t > disp_queue, //! Parameters for the queue. const bind_params_t & params ) : basic_event_queue_t{ params.query_max_demands_at_once() } , m_disp_queue{ disp_queue.get() } {} /*! * \brief Give away a pointer to the next agent_queue. * * \note * This method is a part of interface required by * so_5::disp::reuse::queue_of_queues_t. * * \since v.5.8.0 */ [[nodiscard]] agent_queue_t * intrusive_queue_giveout_next() noexcept { auto * r = m_intrusive_queue_next; m_intrusive_queue_next = nullptr; return r; } /*! * \brief Set a pointer to the next agent_queue. * * \note * This method is a part of interface required by * so_5::disp::reuse::queue_of_queues_t. * * \since v.5.8.0 */ void intrusive_queue_set_next( agent_queue_t * next ) noexcept { m_intrusive_queue_next = next; } protected: void schedule_on_disp_queue() noexcept override { m_disp_queue.schedule( this ); } private : //! Dispatcher queue with that the agent queue has to be used. dispatcher_queue_t & m_disp_queue; /*! * \brief The next item in intrusive queue of agent_queues. * * This field is necessary to implement interface required by * so_5::disp::reuse::queue_of_queues_t. * * \since v.5.8.0 */ agent_queue_t * m_intrusive_queue_next{ nullptr }; }; // // adaptation_t // /*! * \brief Adaptation of common implementation of thread-pool-like dispatcher * to the specific of this thread-pool dispatcher. * * \since v.5.5.4 */ struct adaptation_t { [[nodiscard]] static constexpr std::string_view dispatcher_type_name() noexcept { return { "tp" }; // thread_pool. } [[nodiscard]] static bool is_individual_fifo( const bind_params_t & params ) noexcept { return fifo_t::individual == params.query_fifo(); } static void wait_for_queue_emptyness( agent_queue_t & queue ) noexcept { queue.wait_for_emptyness(); } }; // // dispatcher_template_t // /*! * \brief Template for dispatcher. * * This template depends on work_thread type (with or without activity * tracking). * * \since v.5.5.18 */ template< typename Work_Thread > using dispatcher_template_t = common_implementation::dispatcher_t< Work_Thread, dispatcher_queue_t, bind_params_t, adaptation_t >; } /* namespace impl */ } /* namespace thread_pool */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/thread_pool/impl/work_thread_template.hpp000066400000000000000000000216171523065210100266170ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A reusable implementation of work_thread_template that can be used by * various thread-pool dispatchers. * * \since v.5.8.0 */ #if !defined( SO_5_DISP_THREAD_POOL_IMPL_WORK_THREAD_TEMPLATE_HPP ) #define SO_5_DISP_THREAD_POOL_IMPL_WORK_THREAD_TEMPLATE_HPP #include #include #include #include namespace so_5 { namespace disp { namespace thread_pool { namespace impl { namespace work_thread_details { /*! * \brief Part of %common_data_t that depends on a template parameter. * * Intended to be used as a mixin for common_data_t type. */ template< typename Disp_Queue > struct common_data_template_dependent_t { //! Dispatcher's queue. Disp_Queue * m_disp_queue; common_data_template_dependent_t( outliving_reference_t< Disp_Queue > disp_queue ) : m_disp_queue{ std::addressof(disp_queue.get()) } {} }; /*! * \brief Part of %common_data_t that doesn't depend on a template parameter. * * Intended to be used as a mixin for common_data_t type. */ struct common_data_template_independent_t { //! ID of thread. /*! * Receives actual value inside body(). */ so_5::current_thread_id_t m_thread_id; //! Actual thread. work_thread_holder_t m_thread_holder; //! Waiting object for long wait. so_5::disp::mpmc_queue_traits::condition_unique_ptr_t m_condition; common_data_template_independent_t( work_thread_holder_t thread_holder, so_5::disp::mpmc_queue_traits::condition_unique_ptr_t condition ) : m_thread_holder{ std::move(thread_holder) } , m_condition{ std::move(condition) } {} }; /*! * \brief Main data for work_thread. * * \since v.5.5.18 */ template< typename Disp_Queue > struct common_data_t : public common_data_template_dependent_t< Disp_Queue > , public common_data_template_independent_t { common_data_t( outliving_reference_t< Disp_Queue > queue, work_thread_holder_t thread_holder ) : common_data_template_dependent_t< Disp_Queue >{ queue } , common_data_template_independent_t{ std::move(thread_holder), queue.get().allocate_condition() } {} }; /*! * \brief Part of implementation of work thread without activity tracing. * * \since v.5.5.18 */ template< typename Disp_Queue > class no_activity_tracking_impl_t : protected common_data_t< Disp_Queue > { public : /*! * \brief Mandatory alias for Disp_Queue. * * This alias is then be used by the work_thread_template_t template. */ using disp_queue_t = Disp_Queue; //! Initializing constructor. no_activity_tracking_impl_t( outliving_reference_t< Disp_Queue > queue, work_thread_holder_t thread_holder ) : common_data_t< Disp_Queue >( queue, std::move(thread_holder) ) {} template< typename L > void take_activity_stats( L ) { /* Nothing to do */ } protected : void work_started() {} void work_finished() {} void wait_started() {} void wait_finished() {} }; /*! * \brief Part of implementation of work thread with activity tracing. * * \since v.5.5.18 */ template< typename Disp_Queue > class with_activity_tracking_impl_t : protected common_data_t< Disp_Queue > { using activity_tracking_traits = so_5::stats::activity_tracking_stuff::traits; public : /*! * \brief Mandatory alias for Disp_Queue. * * This alias is then be used by the work_thread_template_t template. */ using disp_queue_t = Disp_Queue; //! Initializing constructor. with_activity_tracking_impl_t( outliving_reference_t< Disp_Queue > queue, work_thread_holder_t thread_holder ) : common_data_t< Disp_Queue >( queue, std::move(thread_holder) ) {} template< typename L > void take_activity_stats( L lambda ) { so_5::stats::work_thread_activity_stats_t result; result.m_working_stats = m_work_activity_collector.take_stats(); result.m_waiting_stats = m_waiting_stats_collector.take_stats(); lambda( result ); } protected : //! Lock for activity statistics. activity_tracking_traits::lock_t m_stats_lock; //! A collector for work activity. so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::external_lock<> > m_work_activity_collector{ m_stats_lock }; //! A collector for waiting stats. so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::external_lock<> > m_waiting_stats_collector{ m_stats_lock }; void work_started() { m_work_activity_collector.start(); } void work_finished() { m_work_activity_collector.stop(); } void wait_started() { m_waiting_stats_collector.start(); } void wait_finished() { m_waiting_stats_collector.stop(); } }; // // work_thread_template_t // /*! * \brief Implementation of work_thread in form of template class. * * \since v.5.5.18 */ template< typename Impl > class work_thread_template_t final : public Impl { public : using disp_queue_t = typename Impl::disp_queue_t; private: using agent_queue_t = typename disp_queue_t::item_t; public: //! Initializing constructor. work_thread_template_t( outliving_reference_t< disp_queue_t > queue, work_thread_holder_t thread_holder ) : Impl( queue, std::move(thread_holder) ) {} void join() { so_5::impl::ensure_join_from_different_thread( this->m_thread_id ); this->m_thread_holder.unchecked_get().join(); } //! Launch work thread. void start() { this->m_thread_holder.unchecked_get().start( [this]() { body(); } ); } /*! * \brief Get ID of work thread. * * \note This method returns correct value only after start * of the thread. * * \since v.5.5.18 */ so_5::current_thread_id_t thread_id() const { return this->m_thread_id; } private : //! Thread body method. void body() { this->m_thread_id = so_5::query_current_thread_id(); agent_queue_t * agent_queue; while( nullptr != (agent_queue = this->pop_agent_queue()) ) { this->do_queue_processing( agent_queue ); } } /*! * \brief An attempt of extraction of non-empty agent queue. * * \note This is noexcept method because its logic can't survive * an exception from m_disp_queue->pop. * * \since v.5.5.18 */ [[nodiscard]] agent_queue_t * pop_agent_queue() noexcept { agent_queue_t * result = nullptr; this->wait_started(); result = this->m_disp_queue->pop( *(this->m_condition) ); this->wait_finished(); return result; } /*! * \brief Starts processing of demands from the queue specified. * * Starts from \a current_queue. Processes up to enabled number * of events from that queue. Then if the queue is not empty * tries to find another non-empty queue. If there is no such queue * then continue processing of \a current_queue. * * \since v.5.5.15.1 */ void do_queue_processing( agent_queue_t * current_queue ) { do { const auto e = this->process_queue( *current_queue ); if( agent_queue_t::emptyness_t::not_empty == e ) { // We can continue processing of that queue if // there is no more non-empty queues waiting. current_queue = this->m_disp_queue->try_switch_to_another( current_queue ); } else // Handling of the current queue should be stopped. current_queue = nullptr; } while( current_queue != nullptr ); } //! Processing of demands from agent queue. [[nodiscard]] typename agent_queue_t::emptyness_t process_queue( agent_queue_t & queue ) { std::size_t demands_processed = 0; typename agent_queue_t::pop_result_t pop_result; do { auto & d = queue.front(); this->work_started(); d.call_handler( this->m_thread_id ); this->work_finished(); ++demands_processed; pop_result = queue.pop( demands_processed ); } while( agent_queue_t::processing_continuation_t::enabled == pop_result.m_continuation ); return pop_result.m_emptyness; } }; } /* namespace work_thread_details */ // // work_thread_no_activity_tracking_t // /*! * \brief Type of work thread without activity tracking. * * \since v.5.5.18 */ template< typename Disp_Queue > using work_thread_no_activity_tracking_t = work_thread_details::work_thread_template_t< work_thread_details::no_activity_tracking_impl_t< Disp_Queue > >; // // work_thread_with_activity_tracking_t // /*! * \brief Type of work thread without activity tracking. * * \since v.5.5.18 */ template< typename Disp_Queue > using work_thread_with_activity_tracking_t = work_thread_details::work_thread_template_t< work_thread_details::with_activity_tracking_impl_t< Disp_Queue > >; } /* namespace impl */ } /* namespace thread_pool */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp/thread_pool/pub.cpp000066400000000000000000000131011523065210100222200ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Public interface of thread pool dispatcher. * * \since v.5.4.0 */ #include #include #include #include #include #include namespace so_5 { namespace disp { namespace thread_pool { namespace impl { // // actual_dispatcher_iface_t // /*! * \brief An actual interface of thread-pool dispatcher. * * This interface defines a set of methods necessary for binder. * * \since v.5.6.0 */ class actual_dispatcher_iface_t : public basic_dispatcher_iface_t { public : //! Preallocate all necessary resources for a new agent. virtual void preallocate_resources_for_agent( agent_t & agent, const bind_params_t & params ) = 0; //! Undo preallocation of resources for a new agent. virtual void undo_preallocation_for_agent( agent_t & agent ) noexcept = 0; //! Get resources allocated for an agent. [[nodiscard]] virtual event_queue_t * query_resources_for_agent( agent_t & agent ) noexcept = 0; //! Unbind agent from the dispatcher. virtual void unbind_agent( agent_t & agent ) noexcept = 0; }; // // actual_dispatcher_iface_shptr_t // using actual_dispatcher_iface_shptr_t = std::shared_ptr< actual_dispatcher_iface_t >; // // actual_binder_t // /*! * \brief Actual implementation of dispatcher binder for %thread_pool dispatcher. * * \since v.5.6.0 */ class actual_binder_t final : public disp_binder_t { //! Dispatcher to be used. actual_dispatcher_iface_shptr_t m_disp; //! Binding parameters. const bind_params_t m_params; public : actual_binder_t( actual_dispatcher_iface_shptr_t disp, bind_params_t params ) noexcept : m_disp{ std::move(disp) } , m_params{ params } {} void preallocate_resources( agent_t & agent ) override { m_disp->preallocate_resources_for_agent( agent, m_params ); } void undo_preallocation( agent_t & agent ) noexcept override { m_disp->undo_preallocation_for_agent( agent ); } void bind( agent_t & agent ) noexcept override { auto queue = m_disp->query_resources_for_agent( agent ); agent.so_bind_to_dispatcher( *queue ); } void unbind( agent_t & agent ) noexcept override { m_disp->unbind_agent( agent ); } }; // // actual_dispatcher_implementation_t // /*! * \brief Actual implementation of binder for %thread_pool dispatcher. * * \since v.5.6.0 */ template< typename Work_Thread > class actual_dispatcher_implementation_t final : public actual_dispatcher_iface_t { //! Real dispatcher. dispatcher_template_t< Work_Thread > m_impl; public : actual_dispatcher_implementation_t( //! SObjectizer Environment to work in. outliving_reference_t< environment_t > env, //! Base part of data sources names. const std::string_view name_base, //! Dispatcher's parameters. disp_params_t params ) : m_impl{ env.get(), params, name_base, params.thread_count(), params.queue_params() } { m_impl.start( env.get() ); } ~actual_dispatcher_implementation_t() noexcept override { m_impl.shutdown_then_wait(); } [[nodiscard]] disp_binder_shptr_t binder( bind_params_t params ) override { return std::make_shared< actual_binder_t >( this->shared_from_this(), params ); } void preallocate_resources_for_agent( agent_t & agent, const bind_params_t & params ) override { m_impl.preallocate_resources_for_agent( agent, params ); } void undo_preallocation_for_agent( agent_t & agent ) noexcept override { m_impl.undo_preallocation_for_agent( agent ); } event_queue_t * query_resources_for_agent( agent_t & agent ) noexcept override { return m_impl.query_resources_for_agent( agent ); } void unbind_agent( agent_t & agent ) noexcept override { m_impl.unbind_agent( agent ); } }; // // dispatcher_handle_maker_t // class dispatcher_handle_maker_t { public : static dispatcher_handle_t make( actual_dispatcher_iface_shptr_t disp ) noexcept { return { std::move( disp ) }; } }; } /* namespace impl */ namespace { using namespace so_5::disp::thread_pool::impl; /*! * \brief Sets the thread count to default value if used do not * specify actual thread count. * * \since v.5.5.11 */ inline void adjust_thread_count( disp_params_t & params ) { if( !params.thread_count() ) params.thread_count( default_thread_pool_size() ); } } /* namespace anonymous */ // // make_dispatcher // SO_5_FUNC dispatcher_handle_t make_dispatcher( environment_t & env, const std::string_view data_sources_name_base, disp_params_t params ) { using namespace so_5::disp::reuse; adjust_thread_count( params ); using dispatcher_no_activity_tracking_t = impl::actual_dispatcher_implementation_t< impl::work_thread_no_activity_tracking_t< impl::dispatcher_queue_t > >; using dispatcher_with_activity_tracking_t = impl::actual_dispatcher_implementation_t< impl::work_thread_with_activity_tracking_t< impl::dispatcher_queue_t > >; auto binder = so_5::disp::reuse::make_actual_dispatcher< impl::actual_dispatcher_iface_t, dispatcher_no_activity_tracking_t, dispatcher_with_activity_tracking_t >( outliving_mutable(env), data_sources_name_base, std::move(params) ); return impl::dispatcher_handle_maker_t::make( std::move(binder) ); } } /* namespace thread_pool */ } /* namespace disp */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/disp/thread_pool/pub.hpp000066400000000000000000000266231523065210100222420ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Public interface of thread pool dispatcher. * * \since v.5.4.0 */ #if !defined( SO_5_DISP_THREAD_POOL_PUB_HPP ) #define SO_5_DISP_THREAD_POOL_PUB_HPP #include #include #include #include #include #include #include #include #include namespace so_5 { namespace disp { namespace thread_pool { /*! * \brief Alias for namespace with traits of event queue. * * \since v.5.5.11 */ namespace queue_traits = so_5::disp::mpmc_queue_traits; // // disp_params_t // /*! * \brief Parameters for %thread_pool dispatcher. * * \since v.5.5.11 */ class disp_params_t : public so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t > , public so_5::disp::reuse::work_thread_factory_mixin_t< disp_params_t > { using activity_tracking_mixin_t = so_5::disp::reuse:: work_thread_activity_tracking_flag_mixin_t< disp_params_t >; using thread_factory_mixin_t = so_5::disp::reuse:: work_thread_factory_mixin_t< disp_params_t >; public : //! Default constructor. disp_params_t() = default; friend inline void swap( disp_params_t & a, disp_params_t & b ) noexcept { using std::swap; swap( static_cast< activity_tracking_mixin_t & >(a), static_cast< activity_tracking_mixin_t & >(b) ); swap( static_cast< work_thread_factory_mixin_t & >(a), static_cast< work_thread_factory_mixin_t & >(b) ); swap( a.m_thread_count, b.m_thread_count ); swap( a.m_queue_params, b.m_queue_params ); } //! Setter for thread count. disp_params_t & thread_count( std::size_t count ) { m_thread_count = count; return *this; } //! Getter for thread count. std::size_t thread_count() const { return m_thread_count; } //! Setter for queue parameters. disp_params_t & set_queue_params( queue_traits::queue_params_t p ) { m_queue_params = std::move(p); return *this; } //! Tuner for queue parameters. /*! * Accepts lambda-function or functional object which tunes * queue parameters. \code using namespace so_5::disp::thread_pool; auto disp = make_dispatcher( env, "workers_disp", disp_params_t{} .thread_count( 10 ) .tune_queue_params( []( queue_traits::queue_params_t & p ) { p.lock_factory( queue_traits::simple_lock_factory() ); } ) ); \endcode */ template< typename L > disp_params_t & tune_queue_params( L tunner ) { tunner( m_queue_params ); return *this; } //! Getter for queue parameters. const queue_traits::queue_params_t & queue_params() const { return m_queue_params; } private : //! Count of working threads. /*! * Value 0 means that actual thread will be detected automatically. */ std::size_t m_thread_count = { 0 }; //! Queue parameters. queue_traits::queue_params_t m_queue_params; }; // // fifo_t // /*! * \brief Type of FIFO mechanism for agent's demands. * * \since v.5.4.0 */ enum class fifo_t { //! A FIFO for demands for all agents from the same cooperation. /*! * It means that agents from the same cooperation for which this * FIFO mechanism is used will be worked on the same thread. * * If the same disp_binder with fifo_t::cooperation is used for * several cooperations then each coop will have a separate * event queue (thus agents from different coops may work on * different worker threads). */ cooperation, //! A FIFO for demands only for one agent. /*! * It means that FIFO is only supported for the concrete agent. * If several agents from a cooperation have this FIFO type they * will process demands independently and on different threads. */ individual }; // // bind_params_t // /*! * \brief Parameters for binding agents to %thread_pool dispatcher. * * \since v.5.5.11 */ class bind_params_t { public : //! Set FIFO type. bind_params_t & fifo( fifo_t v ) { m_fifo = v; return *this; } //! Get FIFO type. [[nodiscard]] fifo_t query_fifo() const { return m_fifo; } //! Set maximum count of demands to be processed at once. bind_params_t & max_demands_at_once( std::size_t v ) { m_max_demands_at_once = v; return *this; } //! Get maximum count of demands to do processed at once. [[nodiscard]] std::size_t query_max_demands_at_once() const { return m_max_demands_at_once; } private : //! FIFO type. fifo_t m_fifo = { fifo_t::cooperation }; //! Maximum count of demands to be processed at once. std::size_t m_max_demands_at_once = { 4 }; }; // // default_thread_pool_size // using so_5::disp::reuse::default_thread_pool_size; namespace impl { class actual_dispatcher_iface_t; // // basic_dispatcher_iface_t // /*! * \brief The very basic interface of %thread_pool dispatcher. * * This class contains a minimum that is necessary for implementation * of dispatcher_handle class. * * \since v.5.6.0 */ class basic_dispatcher_iface_t : public std::enable_shared_from_this { public : virtual ~basic_dispatcher_iface_t() noexcept = default; [[nodiscard]] virtual disp_binder_shptr_t binder( bind_params_t params ) = 0; }; using basic_dispatcher_iface_shptr_t = std::shared_ptr< basic_dispatcher_iface_t >; class dispatcher_handle_maker_t; } /* namespace impl */ // // dispatcher_handle_t // /*! * \brief A handle for %thread_pool dispatcher. * * \since v.5.6.0 */ class [[nodiscard]] dispatcher_handle_t { friend class impl::dispatcher_handle_maker_t; //! A reference to actual implementation of a dispatcher. impl::basic_dispatcher_iface_shptr_t m_dispatcher; dispatcher_handle_t( impl::basic_dispatcher_iface_shptr_t dispatcher ) noexcept : m_dispatcher{ std::move(dispatcher) } {} //! Is this handle empty? bool empty() const noexcept { return !m_dispatcher; } public : dispatcher_handle_t() noexcept = default; //! Get a binder for that dispatcher. /*! * Usage example: * \code * using namespace so_5::disp::thread_pool; * * so_5::environment_t & env = ...; * auto disp = make_dispatcher( env ); * bind_params_t params; * params.fifo( fifo_t::individual ); * * env.introduce_coop( [&]( so_5::coop_t & coop ) { * coop.make_agent_with_binder< some_agent_type >( * disp.binder( params ), * ... ); * * coop.make_agent_with_binder< another_agent_type >( * disp.binder( params ), * ... ); * * ... * } ); * \endcode * * \attention * An attempt to call this method on empty handle is UB. */ [[nodiscard]] disp_binder_shptr_t binder( bind_params_t params ) const { return m_dispatcher->binder( params ); } //! Create a binder for that dispatcher. /*! * This method allows parameters tuning via lambda-function * or other functional objects. * * Usage example: * \code * using namespace so_5::disp::thread_pool; * * so_5::environment_t & env = ...; * env.introduce_coop( [&]( so_5::coop_t & coop ) { * coop.make_agent_with_binder< some_agent_type >( * // Create dispatcher instance. * make_dispatcher( env ) * // Make and tune binder for that dispatcher. * .binder( []( auto & params ) { * params.fifo( fifo_t::individual ); * } ), * ... ); * \endcode * * \attention * An attempt to call this method on empty handle is UB. */ template< typename Setter > [[nodiscard]] std::enable_if_t< std::is_invocable_v< Setter, bind_params_t& >, disp_binder_shptr_t > binder( //! Function for the parameters tuning. Setter && params_setter ) const { bind_params_t p; params_setter( p ); return this->binder( p ); } //! Get a binder for that dispatcher with default binding params. /*! * \attention * An attempt to call this method on empty handle is UB. */ [[nodiscard]] disp_binder_shptr_t binder() const { return this->binder( bind_params_t{} ); } //! Is this handle empty? operator bool() const noexcept { return empty(); } //! Does this handle contain a reference to dispatcher? bool operator!() const noexcept { return !empty(); } //! Drop the content of handle. void reset() noexcept { m_dispatcher.reset(); } }; // // make_dispatcher // /*! * \brief Create an instance %thread_pool dispatcher. * * \par Usage sample \code using namespace so_5::disp::thread_pool; auto disp = make_dispatcher( env, "db_workers_pool", disp_params_t{} .thread_count( 16 ) .tune_queue_params( []( queue_traits::queue_params_t & params ) { params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of thread_pool dispatcher. disp.binder() ); \endcode * * \since v.5.6.0 */ [[nodiscard]] SO_5_FUNC dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Parameters for the dispatcher. disp_params_t disp_params ); // // make_dispatcher // /*! * \brief Create an instance of %thread_pool dispatcher. * * \par Usage sample \code auto disp = so_5::disp::thread_pool::make_dispatcher( env, "db_workers_pool", 16 ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of thread_pool dispatcher. disp.binder() ); \endcode * * \since v.5.6.0 */ [[nodiscard]] inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Value for creating names of data sources for //! run-time monitoring. const std::string_view data_sources_name_base, //! Count of working threads. std::size_t thread_count ) { return make_dispatcher( env, data_sources_name_base, disp_params_t{}.thread_count( thread_count ) ); } /*! * \brief Create an instance of %thread_pool dispatcher. * * \par Usage sample \code auto disp = so_5::disp::thread_pool::make_dispatcher( env, 16 ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of thread_pool dispatcher. disp.binder() ); \endcode * * \since v.5.6.0 */ [[nodiscard]] inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env, //! Count of working threads. std::size_t thread_count ) { return make_dispatcher( env, std::string_view{}, thread_count ); } // // make_dispatcher // /*! * \brief Create an instance of %thread_pool dispatcher with the default * count of working threads. * * Count of work threads will be detected by default_thread_pool_size() * function. * * \par Usage sample \code auto disp = so_5::disp::thread_pool::make_instance( env ); auto coop = env.make_coop( // The main dispatcher for that coop will be // this instance of thread_pool dispatcher. disp.binder() ); \endcode * * \since v.5.6.0 */ [[nodiscard]] inline dispatcher_handle_t make_dispatcher( //! SObjectizer Environment to work in. environment_t & env ) { return make_dispatcher( env, std::string_view{}, default_thread_pool_size() ); } } /* namespace thread_pool */ } /* namespace disp */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/disp_binder.hpp000066400000000000000000000051761523065210100204770ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Interface for the dispatcher binders definition. */ #if !defined( SO_5_DISP_BINDER_HPP ) #define SO_5_DISP_BINDER_HPP #include #include #include #include namespace so_5 { // // disp_binder_t // //! Interface for dispatcher binders. /*! * Dispatcher binders are used in the agent registration process to * binding of agents to desired dispatchers. * * \attention * If an implementation of disp_binder_t interface stores smart * pointers to agents in methods preallocate_resources() and bind() * then it must drop (or reset) these stored smart references in * undo_preallocation() and unbind() methods. Otherwise there will * be circular references between disp_binder and agents and this * will lead to memory leaks and other related problems (for * example, destructors for agents/disp_binders won't be called). */ class disp_binder_t : private std::enable_shared_from_this< disp_binder_t > { // Note: clang-3.9 requires this on Windows platform. disp_binder_t( const disp_binder_t & ) = delete; disp_binder_t( disp_binder_t && ) = delete; disp_binder_t & operator=( const disp_binder_t & ) = delete; disp_binder_t & operator=( disp_binder_t && ) = delete; public: disp_binder_t() = default; virtual ~disp_binder_t() noexcept = default; //! Allocate resources in dispatcher for new agent. /*! * This method can and should throw on failure. */ virtual void preallocate_resources( //! Agent to be bound. agent_t & agent ) = 0; //! Undo resources allocation. /*! * This method will be called after preallocate_resources() if * cooperation can't be registered by some reasons. * * This method can't throw. */ virtual void undo_preallocation( //! Agent for that previous preallocate_resources() was called. agent_t & agent ) noexcept = 0; //! Bind agent to dispatcher. /*! * This method will be called after preallocate_resources(). * * The dispatcher should use resources created during * preallocate_resources() call. Because of that this method * must not throw. */ virtual void bind( //! Agent for that previous preallocate_resources() was called. agent_t & agent ) noexcept = 0; //! Unbind agent from dispatcher. /*! * This method will be called after bind() method. * * This method can't throw. */ virtual void unbind( //! Agent for that previous bind() was called. agent_t & agent ) noexcept = 0; }; //! Typedef for the disp_binder smart pointer. using disp_binder_shptr_t = std::shared_ptr< disp_binder_t >; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/env_infrastructures.hpp000066400000000000000000000130031523065210100223140ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief User-visible parameters for various environment infrastructures. * * \since * v.5.5.19 */ #if !defined( SO_5_ENV_INFRASTRUCTURES_HPP ) #define SO_5_ENV_INFRASTRUCTURES_HPP #include #include namespace so_5 { namespace env_infrastructures { namespace default_mt { // NOTE: implemented in so_5/impl/mt_env_infrastructure.cpp /*! * \brief A factory for creation the default multitheading * environment infrastructure. * * Usage example: * \code so_5::launch( [](so_5::environment_t & env) { ... }, [](so_5::environment_params_t & params) { params.infrastructure_factory( so_5::env_infrastructures::default_mt::factory() ); ... } ); * \endcode * * \note * This factory is used by default. * * \since * v.5.5.19 */ SO_5_FUNC environment_infrastructure_factory_t factory(); } /* namespace default_mt */ namespace simple_mtsafe { // // params_t // /*! * \brief Parameters for simple thread-safe single-thread environment. * * Usage example: * \code so_5::env_infrastructures::simple_mtsafe::params_t params; params.timer_manager( so_5::timer_list_manager_factory() ); env_params.infrastructure_factory( factory(std::move(params)) ); * \endcode * * \since * v.5.5.19 */ class params_t { //! Timer manager factory for environment. /*! * thread_heap mechanism is used by default. */ timer_manager_factory_t m_timer_factory{ timer_heap_manager_factory() }; public : //! Setter for timer_manager factory. params_t & timer_manager( timer_manager_factory_t factory ) & { m_timer_factory = std::move(factory); return *this; } //! Setter for timer_manager factory. params_t && timer_manager( timer_manager_factory_t factory ) && { m_timer_factory = std::move(factory); return std::move(*this); } //! Getter for timer_manager factory. const timer_manager_factory_t & timer_manager() const { return m_timer_factory; } }; // NOTE: implemented in so_5/impl/simple_mtsafe_st_env_infastructure.cpp // // factory // /*! * \brief A factory for creation of simple thread-safe * single-thread environment infrastructure object. * * Usage example: * \code so_5::launch( [](so_5::environment_t & env) { ... }, [](so_5::environment_params_t & params) { params.infrastructure_factory( factory( so_5::env_infrastructures::simple_mtsafe::params_t{} .timer_manager( so_5::timer_list_manager_factory() ) ) ); ... } ); * \endcode * * \since * v.5.5.19 */ SO_5_FUNC environment_infrastructure_factory_t factory( params_t && params ); /*! * \brief A factory for creation of simple thread-safe * single-thread environment infrastructure object with * default parameters. * * Usage example: * \code so_5::launch( [](so_5::environment_t & env) { ... }, [](so_5::environment_params_t & params) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); ... } ); * \endcode * * \since * v.5.5.19 */ inline environment_infrastructure_factory_t factory() { return factory( params_t() ); } } /* namespace simple_mtsafe */ namespace simple_not_mtsafe { // // params_t // /*! * \brief Parameters for simple not-thread-safe single-thread environment. * * Usage example: * \code so_5::env_infrastructures::simple_not_mtsafe::params_t params; params.timer_manager( so_5::timer_list_manager_factory() ); env_params.infrastructure_factory( factory(std::move(params)) ); * \endcode * * \since * v.5.5.19 */ class params_t { //! Timer manager factory for environment. /*! * thread_heap mechanism is used by default. */ timer_manager_factory_t m_timer_factory{ timer_heap_manager_factory() }; public : //! Setter for timer_manager factory. params_t & timer_manager( timer_manager_factory_t factory ) & { m_timer_factory = std::move(factory); return *this; } //! Setter for timer_manager factory. params_t && timer_manager( timer_manager_factory_t factory ) && { m_timer_factory = std::move(factory); return std::move(*this); } //! Getter for timer_manager factory. const timer_manager_factory_t & timer_manager() const { return m_timer_factory; } }; // NOTE: implemented in so_5/impl/simple_not_mtsafe_st_env_infastructure.cpp // // factory // /*! * \brief A factory for creation of simple not-thread-safe * single-thread environment infrastructure object. * * Usage example: * \code so_5::launch( [](so_5::environment_t & env) { ... }, [](so_5::environment_params_t & params) { params.infrastructure_factory( factory( so_5::env_infrastructures::simple_not_mtsafe::params_t{} .timer_manager( so_5::timer_list_manager_factory() ) ) ); ... } ); * \endcode * * \since * v.5.5.19 */ SO_5_FUNC environment_infrastructure_factory_t factory( params_t && params ); /*! * \brief A factory for creation of simple not-thread-safe * single-thread environment infrastructure object with * default parameters. * * Usage example: * \code so_5::launch( [](so_5::environment_t & env) { ... }, [](so_5::environment_params_t & params) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); ... } ); * \endcode * * \since * v.5.5.19 */ inline environment_infrastructure_factory_t factory() { return factory( params_t() ); } } /* namespace simple_not_mtsafe */ } /* namespace env_infrastructures */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/enveloped_msg.cpp000066400000000000000000000056441523065210100210370ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * * \brief Stuff related to enveloped messages. * * \since v.5.5.23 */ #include #include namespace so_5 { namespace enveloped_msg { namespace { #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnon-virtual-dtor" #endif // // payload_access_handler_invoker_t // /*! * \brief An implementation of handler_invoker interface for * extraction of payload because of various reasons. * * \since v.5.5.23 */ class payload_access_handler_invoker_t final : public handler_invoker_t { const access_context_t m_context; optional< ::so_5::enveloped_msg::payload_info_t > m_payload; public: payload_access_handler_invoker_t( access_context_t context ) : m_context{ context } {} ~payload_access_handler_invoker_t() = default; void invoke( const payload_info_t & payload ) noexcept override { using namespace so_5::enveloped_msg::impl; switch( message_kind( payload.message() ) ) { case message_t::kind_t::signal : [[fallthrough]]; case message_t::kind_t::classical_message : [[fallthrough]]; case message_t::kind_t::user_type_message : m_payload = payload; break; case message_t::kind_t::enveloped_msg : auto & envelope = message_to_envelope( payload.message() ); envelope.access_hook( m_context, *this ); break; } } [[nodiscard]] optional< so_5::enveloped_msg::payload_info_t > try_get_payload( const message_ref_t & envelope_to_process ) noexcept { using namespace so_5::enveloped_msg::impl; auto & actual_envelope = message_to_envelope( envelope_to_process ); actual_envelope.access_hook( m_context, *this ); return m_payload; } }; #if defined(__clang__) #pragma clang diagnostic pop #endif } /* namespace anonymous */ [[nodiscard]] SO_5_FUNC optional< payload_info_t > extract_payload_for_message_transformation( const message_ref_t & envelope_to_process ) { return payload_access_handler_invoker_t{ access_context_t::transformation }.try_get_payload( envelope_to_process ); } [[nodiscard]] SO_5_FUNC optional< message_ref_t > message_to_be_inspected( const message_ref_t & msg_or_envelope ) { message_ref_t result = msg_or_envelope; switch( message_kind( msg_or_envelope ) ) { case message_t::kind_t::signal: // Already has a value. break; case message_t::kind_t::classical_message: // Already has a value. break; case message_t::kind_t::user_type_message: // Already has a value. break; case message_t::kind_t::enveloped_msg: { auto opt_payload = payload_access_handler_invoker_t{ access_context_t::inspection }.try_get_payload( msg_or_envelope ); if( opt_payload ) result = opt_payload->message(); } break; } return { result }; } } /* namespace enveloped_msg */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/enveloped_msg.hpp000066400000000000000000000161211523065210100210340ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Stuff related to enveloped messages. * * \since v.5.5.23 */ #if !defined( SO_5_ENVELOPED_MSG_HPP ) #define SO_5_ENVELOPED_MSG_HPP #include #include namespace so_5 { namespace enveloped_msg { // // payload_info_t // /*! * \brief An information about payload inside envelope. * * This class is necessary for incapsulation of information related * to payload inside an envelope. In v.5.5.23 this class contains only * reference to the payload message. But additional info can be added * in some future versions. * * \since v.5.5.23 */ class payload_info_t { //! Actual enveloped message. /*! * This member is mutable because we need to return non-const * reference to message_ref_t from const method message(). * * \note * This pointer can be null if the enveloped message is a signal. */ mutable message_ref_t m_message; public: //! Initializing constructor. payload_info_t( message_ref_t message ) : m_message{ std::move(message) } {} /*! * \name Getters * \{ */ [[nodiscard]] message_ref_t & message() const noexcept { return m_message; } /*! * \} */ }; // // handler_invoker_t // /*! * \brief An extended version of handling_context which can be used * for calling event handler. * * \note * This class has non-virtual and protected destructor because * creation on instances derived classes in dynamic memory * is not intended. * * \since v.5.5.23 */ class SO_5_TYPE handler_invoker_t { protected: // clang requires this. handler_invoker_t() = default; handler_invoker_t( const handler_invoker_t & ) = default; handler_invoker_t & operator=( const handler_invoker_t & ) = default; handler_invoker_t( handler_invoker_t && ) = default; handler_invoker_t & operator=( handler_invoker_t && ) noexcept = default; ~handler_invoker_t() = default; public: //! Call an actual handler for the enveloped message/signal. virtual void invoke( const payload_info_t & payload ) noexcept = 0; }; // // access_context_t // /*! * \brief Information about context on that enveloped message is handled. * * \since v.5.5.23 */ enum class access_context_t { //! Enveloped message is delivered to a receiver and the payload //! is necessary for calling event handler. handler_found, //! The content of enveloped message should be transformed to //! another representation. //! For example it can be necessary for limit_then_transform //! overload reaction. transformation, //! The content of enveloped message should be analyzed for //! the further delivery. //! For example it can be necessary for delivery filters. inspection }; // // envelope_t // /*! * \brief An interface of envelope with some message/signal inside. * * SObjectizer v.5.5.23 introduced a new thing: enveloped messages. * It means that actual message/signal is placed into a special * container called 'envelope'. This envelope is delivered to all * receivers of the original message/signal. But before the calling * of event handler in a receiver the original message/signal (e.g. payload) * are extracted from envelope and passed to the event handler. * * This interface describes 'envelope' for such containers. All envelopes * should implement this interface. * * Method access_hook() is called by SObjectizer when the payload * of enveloped message should be accessed. For example: * * - envelope is delivered to a receiver and receiver is ready to handle a * message from envelope; * - envelope can't be delivered to a receiver in its current form and there is * a need to transform the message/signal from the envelope to another type * of message/signal. For example it can happen when limit_then_transform is * used for overload control. In that case the payload should be extracted * and passed to a transformation function; * - envelope should be analyzed by a delivery filter for further delivery * of message. * * When access_hook() is called the envelope should check the * availability of the payload and, if the payload is available for processing, * should pass the payload info to handler_invoker_t::invoke() method. * * Please note that call of handler_invoker_t::invoke() is not guaranteed. * Envelope can check some conditions (like payload expiration or revocation) * and does call to invoke() only if these conditions are meet. But if some * conditions are not fulfilled then access_hook() won't call * handler_invoker_t::invoke() method. * * \since v.5.5.23 */ class SO_5_TYPE envelope_t : public message_t { public: // Introduce some names into the scope of this class to // be easily used in derived classes outside so_5 namespace. using payload_info_t = ::so_5::enveloped_msg::payload_info_t; using handler_invoker_t = ::so_5::enveloped_msg::handler_invoker_t; using access_context_t = ::so_5::enveloped_msg::access_context_t; // clang requires this. envelope_t() = default; envelope_t( const envelope_t & ) = default; envelope_t & operator=( const envelope_t & ) = default; envelope_t( envelope_t && ) = default; envelope_t & operator=( envelope_t && ) noexcept = default; virtual ~envelope_t() override = default; virtual void access_hook( //! Why this hook is called. access_context_t context, //! Proxy object which can call an actual event handler. handler_invoker_t & invoker ) noexcept = 0; private : kind_t so5_message_kind() const noexcept override { return kind_t::enveloped_msg; } }; // // extract_payload_for_message_transformation // /*! * \brief Helper function for extraction of a payload * from enveloped message. * * Extraction of a payload from an envelope is not an easy task. * It is necessary to create an implementation of handler_invoker_t * interface and pass it to envelope_t::transformation_hook() method. * This implementation should check type of the payload: if it is * another envelope then next call to transformation_hook() should * be done and so on. * * Because of that extraction of the payload from an envelope is a boring * task. This helper function preforms all described actions an returns * optional with payload inside (if the payload available). * * \attention * Argument \a envelope should not be nullptr. * * \since v.5.5.23 */ [[nodiscard]] SO_5_FUNC optional< payload_info_t > extract_payload_for_message_transformation( //! Envelope with message inside. const message_ref_t & envelope ); // // message_to_be_inspected // /*! * \brief Helper function for extraction of a payload from enveloped * message. * * This function checks the kind of \a msg_or_envelope. If this is * an enveloped message message_to_be_inspected() will try to extract * the payload and return it. In that case an empty \a optional object * can be returned. * * If \a msg_or_envelope is not an envelope then \a msg_or_envelope * is returned as a result. * * \since v.5.5.23 */ [[nodiscard]] SO_5_FUNC optional< message_ref_t > message_to_be_inspected( const message_ref_t & msg_or_envelope ); } /* namespace enveloped_msg */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/environment.cpp000066400000000000000000000626421523065210100205550ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { // // environment_params_t // environment_params_t::environment_params_t() : m_event_exception_logger( create_std_event_exception_logger() ) , m_exception_reaction( abort_on_exception ) , m_autoshutdown_disabled( false ) , m_error_logger( create_stderr_logger() ) , m_default_disp_params{ so_5::disp::one_thread::disp_params_t{} } , m_work_thread_activity_tracking( work_thread_activity_tracking_t::unspecified ) , m_infrastructure_factory( env_infrastructures::default_mt::factory() ) , m_event_queue_hook( make_empty_event_queue_hook_unique_ptr() ) { } environment_params_t::environment_params_t( environment_params_t && other ) : m_timer_thread_factory( std::move( other.m_timer_thread_factory ) ) , m_so_layers( std::move( other.m_so_layers ) ) , m_coop_listener( std::move( other.m_coop_listener ) ) , m_event_exception_logger( std::move( other.m_event_exception_logger ) ) , m_exception_reaction( other.m_exception_reaction ) , m_autoshutdown_disabled( other.m_autoshutdown_disabled ) , m_error_logger( std::move( other.m_error_logger ) ) , m_message_delivery_tracer( std::move( other.m_message_delivery_tracer ) ) , m_message_delivery_tracer_filter( std::move( other.m_message_delivery_tracer_filter ) ) , m_default_disp_params( std::move( other.m_default_disp_params ) ) , m_work_thread_activity_tracking( other.m_work_thread_activity_tracking ) , m_queue_locks_defaults_manager( std::move( other.m_queue_locks_defaults_manager ) ) , m_infrastructure_factory( std::move(other.m_infrastructure_factory) ) , m_event_queue_hook( std::move(other.m_event_queue_hook) ) , m_work_thread_factory( std::move(other.m_work_thread_factory) ) , m_default_subscription_storage_factory( std::move(other.m_default_subscription_storage_factory) ) {} environment_params_t::~environment_params_t() { } environment_params_t & environment_params_t::operator=( environment_params_t && other ) noexcept { environment_params_t tmp( std::move( other ) ); swap( *this, tmp ); return *this; } SO_5_FUNC void swap( environment_params_t & a, environment_params_t & b ) noexcept { using std::swap; swap( a.m_timer_thread_factory, b.m_timer_thread_factory ); swap( a.m_so_layers, b.m_so_layers ); swap( a.m_coop_listener, b.m_coop_listener ); swap( a.m_event_exception_logger, b.m_event_exception_logger ); swap( a.m_exception_reaction, b.m_exception_reaction ); swap( a.m_autoshutdown_disabled, b.m_autoshutdown_disabled ); swap( a.m_error_logger, b.m_error_logger ); swap( a.m_message_delivery_tracer, b.m_message_delivery_tracer ); swap( a.m_message_delivery_tracer_filter, b.m_message_delivery_tracer_filter ); swap( a.m_default_disp_params, b.m_default_disp_params ); swap( a.m_work_thread_activity_tracking, b.m_work_thread_activity_tracking ); swap( a.m_queue_locks_defaults_manager, b.m_queue_locks_defaults_manager ); swap( a.m_infrastructure_factory, b.m_infrastructure_factory ); swap( a.m_event_queue_hook, b.m_event_queue_hook ); swap( a.m_work_thread_factory, b.m_work_thread_factory ); swap( a.m_default_subscription_storage_factory, b.m_default_subscription_storage_factory ); } environment_params_t & environment_params_t::timer_thread( so_5::timer_thread_factory_t factory ) { m_timer_thread_factory = std::move( factory ); return *this; } environment_params_t & environment_params_t::coop_listener( coop_listener_unique_ptr_t coop_listener ) { m_coop_listener = std::move( coop_listener ); return *this; } environment_params_t & environment_params_t::event_exception_logger( event_exception_logger_unique_ptr_t logger ) { if( nullptr != logger.get() ) m_event_exception_logger = std::move( logger ); return *this; } void environment_params_t::add_layer( const std::type_index & type, layer_unique_ptr_t layer_ptr ) { m_so_layers[ type ] = layer_ref_t( layer_ptr.release() ); } namespace { /*! * \brief A bunch of data sources for core objects. * * \since v.5.5.4 */ class core_data_sources_t { public : core_data_sources_t( outliving_reference_t< stats::repository_t > ds_repository, impl::mbox_core_t & mbox_repository, so_5::environment_infrastructure_t & infrastructure ) : m_mbox_repository( ds_repository, mbox_repository ) , m_coop_repository( ds_repository, infrastructure ) , m_timer_thread( ds_repository, infrastructure ) {} private : //! Data source for mboxes repository. stats::auto_registered_source_holder_t< stats::impl::ds_mbox_core_stats_t > m_mbox_repository; //! Data source for cooperations repository. stats::auto_registered_source_holder_t< stats::impl::ds_agent_core_stats_t > m_coop_repository; //! Data source for timer thread. stats::auto_registered_source_holder_t< stats::impl::ds_timer_thread_stats_t > m_timer_thread; }; /*! * \brief Helper function for creation of appropriate manager * object if necessary. * * \since v.5.5.18 */ queue_locks_defaults_manager_unique_ptr_t ensure_locks_defaults_manager_exists( //! The current value. Note: can be nullptr. queue_locks_defaults_manager_unique_ptr_t current ) { queue_locks_defaults_manager_unique_ptr_t result( std::move(current) ); if( !result ) result = make_defaults_manager_for_combined_locks(); return result; } // // default_event_queue_hook_t // /*! * \brief Default implementation of event_queue_hook. * * Do nothing. * * \since v.5.5.24 */ class default_event_queue_hook_t final : public event_queue_hook_t { public : [[nodiscard]] event_queue_t * on_bind( agent_t * /*agent*/, event_queue_t * original_queue ) noexcept override { return original_queue; } void on_unbind( agent_t * /*agent*/, event_queue_t * /*queue*/ ) noexcept override { } }; /*! * \brief Helper function for creation of appropriate event_queue_hook * object if necessary. * * \since v.5.5.24 */ [[nodiscard]] event_queue_hook_unique_ptr_t ensure_event_queue_hook_exists( //! The current value. Note: can be nullptr. event_queue_hook_unique_ptr_t current ) { event_queue_hook_unique_ptr_t result( std::move(current) ); if( !result ) result = make_event_queue_hook< default_event_queue_hook_t >( &event_queue_hook_t::default_deleter ); return result; } /*! * \brief Helper function for creation of the default global work * thread factory. * * If \a user_provided_factory is nullptr then standard work thread * factory will be created. Otherwise \a user_provided_factory is * returned. * * \since v.5.7.3 */ [[nodiscard]] so_5::disp::abstract_work_thread_factory_shptr_t ensure_work_thread_factory_exists( //! The current value provided by a user. Note: it can be nullptr. so_5::disp::abstract_work_thread_factory_shptr_t user_provided_factory ) { so_5::disp::abstract_work_thread_factory_shptr_t result{ std::move(user_provided_factory) }; if( !result ) result = so_5::disp::make_std_work_thread_factory(); return result; } /*! * \brief Helper function for creation of the default subscription storage * factory. * * If \a user_provided_factory is nullptr then standard default subscription * storage factory will be returned. Otherwise \a user_provided_factory is * returned. * * \since v.5.8.2 */ [[nodiscard]] so_5::subscription_storage_factory_t ensure_subscription_storage_factory_exists( //! The current value provided by a user. Note: it can be nullptr. subscription_storage_factory_t user_provided_factory ) { if( !user_provided_factory ) user_provided_factory = default_subscription_storage_factory(); return user_provided_factory; } } /* namespace anonymous */ // // environment_t::internals_t // /*! * \since v.5.5.0 * * \brief Internal details of SObjectizer Environment object. */ struct environment_t::internals_t { /*! * \brief Error logger object for this environment. * * \attention Must be the first attribute of the object! * It must be created and initilized first and destroyed last. * * \since v.5.5.0 */ error_logger_shptr_t m_error_logger; /*! * \brief Holder of stuff related to message delivery tracing. * * \attention This field must be declared and initialized * before m_mbox_core because a reference to that object will be passed * to the constructor of m_mbox_core. * * \since v.5.5.22 */ so_5::msg_tracing::impl::std_holder_t m_msg_tracing_stuff; //! An utility for mboxes. impl::mbox_core_ref_t m_mbox_core; /*! * \brief A repository of stop_guards. * * \since v.5.5.19.2 */ impl::stop_guard_repository_t m_stop_guards; /*! * \brief A specific infrastructure for environment. * * Note: infrastructure takes care about coop repository, * timer threads/managers and default dispatcher. * * \since v.5.5.19 */ environment_infrastructure_unique_ptr_t m_infrastructure; //! An utility for layers. impl::layer_core_t m_layer_core; /*! * \brief An exception reaction for the whole SO Environment. * * \since v.5.3.0 */ const exception_reaction_t m_exception_reaction; /*! * \brief Is autoshutdown when there is no more cooperation disabled? * * \see environment_params_t::disable_autoshutdown() * * \since v.5.4.0 */ const bool m_autoshutdown_disabled; /*! * \brief Data sources for core objects. * * \attention This instance must be created after stats_controller * and destroyed before it. Because of that m_core_data_sources declared * after m_stats_controller and after all corresponding objects. * NOTE: since v.5.5.19 stats_controller and stats_repository are parts * of environment_infrastructure. Because of that m_core_data_sources * declared and created after m_infrastructure. * * \since v.5.5.4 */ core_data_sources_t m_core_data_sources; /*! * \brief Work thread activity tracking for the whole Environment. * * \since v.5.5.18 */ work_thread_activity_tracking_t m_work_thread_activity_tracking; /*! * \brief Manager for defaults of queue locks. * * \since v.5.5.18 */ queue_locks_defaults_manager_unique_ptr_t m_queue_locks_defaults_manager; /*! * \brief Actual event_queue_hook. * * \note * If there is no event_queue_hook in environment_params_t then * an instance of default_event_queue_hook_t will be created and used. * * \since v.5.5.24 */ event_queue_hook_unique_ptr_t m_event_queue_hook; /*! * \brief Actual global work thread factory. * * \note * If there is no work_thread_factory in environment_params_t then * an instance of the standard work thread factory will be created * and used. * * \since v.5.7.3 */ so_5::disp::abstract_work_thread_factory_shptr_t m_work_thread_factory; /*! * \brief Lock object for protection of exception logger object. * * \note * Manipulations with m_event_exception_logger are performed * only under that lock. * * \since v.5.6.0 */ std::mutex m_event_exception_logger_lock; /*! * \brief Logger for exceptions thrown from event-handlers. * * \since v.5.6.0 */ event_exception_logger_unique_ptr_t m_event_exception_logger; /*! * \brief Factory to be used as default subscription storage factory. * * \note * It can't be null. This has to be checked in the constructor. * * \since v.5.8.2 */ subscription_storage_factory_t m_default_subscription_storage_factory; //! Constructor. internals_t( environment_t & env, environment_params_t && params ) : m_error_logger( params.so5_error_logger() ) , m_msg_tracing_stuff{ params.so5_giveout_message_delivery_tracer_filter(), params.so5_giveout_message_delivery_tracer() } , m_mbox_core( new impl::mbox_core_t{ outliving_mutable( m_msg_tracing_stuff ) } ) , m_infrastructure( (params.infrastructure_factory())( env, params, // A special mbox for distributing monitoring information // must be created and passed to stats_controller. m_mbox_core->create_mbox(env) ) ) , m_layer_core( env, params.so5_giveout_layers_map() ) , m_exception_reaction( params.exception_reaction() ) , m_autoshutdown_disabled( params.autoshutdown_disabled() ) , m_core_data_sources( outliving_mutable(m_infrastructure->stats_repository()), *m_mbox_core, *m_infrastructure ) , m_work_thread_activity_tracking( params.work_thread_activity_tracking() ) , m_queue_locks_defaults_manager( ensure_locks_defaults_manager_exists( params.so5_giveout_queue_locks_defaults_manager() ) ) , m_event_queue_hook( ensure_event_queue_hook_exists( params.so5_giveout_event_queue_hook() ) ) , m_work_thread_factory( ensure_work_thread_factory_exists( params.so5_giveout_work_thread_factory() ) ) , m_event_exception_logger{ params.so5_giveout_event_exception_logger() } , m_default_subscription_storage_factory{ ensure_subscription_storage_factory_exists( params.default_subscription_storage_factory() ) } {} }; // // environment_t // environment_t & environment_t::self_ref() { return *this; } environment_t::environment_t( environment_params_t && params ) : m_impl( new internals_t( self_ref(), std::move(params) ) ) { } environment_t::~environment_t() { } mbox_t environment_t::create_mbox() { return m_impl->m_mbox_core->create_mbox( *this ); } mbox_t environment_t::create_mbox( nonempty_name_t nonempty_name ) { return m_impl->m_mbox_core->create_mbox( *this, std::move(nonempty_name) ); } mbox_t environment_t::introduce_named_mbox( mbox_namespace_name_t mbox_namespace, nonempty_name_t mbox_name, const std::function< mbox_t() > & mbox_factory ) { return m_impl->m_mbox_core->introduce_named_mbox( std::move(mbox_namespace), std::move(mbox_name), mbox_factory ); } mchain_t environment_t::create_mchain( const mchain_params_t & params ) { return m_impl->m_mbox_core->create_mchain( *this, params ); } void environment_t::install_exception_logger( event_exception_logger_unique_ptr_t logger ) { if( logger ) { std::lock_guard< std::mutex > lock{ m_impl->m_event_exception_logger_lock }; using std::swap; swap( m_impl->m_event_exception_logger, logger ); m_impl->m_event_exception_logger->on_install( std::move( logger ) ); } } [[nodiscard]] coop_unique_holder_t environment_t::make_coop() { return m_impl->m_infrastructure->make_coop( coop_handle_t{}, // No parent. so_make_default_disp_binder() ); } [[nodiscard]] coop_unique_holder_t environment_t::make_coop( disp_binder_shptr_t disp_binder ) { return m_impl->m_infrastructure->make_coop( coop_handle_t{}, // No parent. std::move(disp_binder) ); } [[nodiscard]] coop_unique_holder_t environment_t::make_coop( coop_handle_t parent ) { return m_impl->m_infrastructure->make_coop( std::move(parent), so_make_default_disp_binder() ); } [[nodiscard]] coop_unique_holder_t environment_t::make_coop( coop_handle_t parent, disp_binder_shptr_t disp_binder ) { return m_impl->m_infrastructure->make_coop( std::move(parent), std::move(disp_binder) ); } coop_handle_t environment_t::register_coop( coop_unique_holder_t agent_coop ) { return m_impl->m_infrastructure->register_coop( std::move( agent_coop ) ); } so_5::timer_id_t environment_t::so_schedule_timer( const low_level_api::schedule_timer_params_t params ) { // Since v.5.5.21 pause and period should be checked for negative // values. using duration = std::chrono::steady_clock::duration; if( params.m_pause < duration::zero() ) SO_5_THROW_EXCEPTION( so_5::rc_negative_value_for_pause, "an attempt to call schedule_timer() with negative pause value" ); if( params.m_period < duration::zero() ) SO_5_THROW_EXCEPTION( so_5::rc_negative_value_for_period, "an attempt to call schedule_timer() with negative period value" ); // If it is a mutable message then there must be some restrictions: if( message_mutability_t::mutable_message == message_mutability(params.m_msg) ) { // Mutable message can be sent only as delayed message. if( std::chrono::steady_clock::duration::zero() != params.m_period ) SO_5_THROW_EXCEPTION( so_5::rc_mutable_msg_cannot_be_periodic, "unable to schedule periodic timer for mutable message," " msg_type=" + std::string(params.m_msg_type.name()) ); // Mutable message can't be passed to MPMC-mbox. else if( mbox_type_t::multi_producer_multi_consumer == params.m_mbox->type() ) SO_5_THROW_EXCEPTION( so_5::rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox, "unable to schedule timer for mutable message and " "MPMC mbox, msg_type=" + std::string(params.m_msg_type.name()) ); } return m_impl->m_infrastructure->schedule_timer( params.m_msg_type, params.m_msg, params.m_mbox, params.m_pause, params.m_period ); } void environment_t::so_single_timer( const low_level_api::single_timer_params_t params ) { // Since v.5.5.21 pause should be checked for negative values. using duration = std::chrono::steady_clock::duration; if( params.m_pause < duration::zero() ) SO_5_THROW_EXCEPTION( so_5::rc_negative_value_for_pause, "an attempt to call single_timer() with negative pause value" ); // Mutable message can't be passed to MPMC-mbox. if( message_mutability_t::mutable_message == message_mutability(params.m_msg) && mbox_type_t::multi_producer_multi_consumer == params.m_mbox->type() ) SO_5_THROW_EXCEPTION( so_5::rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox, "unable to schedule single timer for mutable message and " "MPMC mbox, msg_type=" + std::string(params.m_msg_type.name()) ); m_impl->m_infrastructure->single_timer( params.m_msg_type, params.m_msg, params.m_mbox, params.m_pause ); } layer_t * environment_t::query_layer( const std::type_index & type ) const { return m_impl->m_layer_core.query_layer( type ); } void environment_t::add_extra_layer( const std::type_index & type, const layer_ref_t & layer ) { m_impl->m_layer_core.add_extra_layer( type, layer ); } void environment_t::run() { try { imp_run_stats_controller_and_go_further(); } catch( const so_5::exception_t & ) { // Rethrow our exception because it already has all information. throw; } catch( const std::exception & x ) { SO_5_THROW_EXCEPTION( rc_environment_error, std::string( "some unexpected error during " "environment launching: " ) + x.what() ); } } void environment_t::stop() noexcept { // Since v.5.5.19.2 there is a new shutdown procedure: const auto action = m_impl->m_stop_guards.initiate_stop(); if( impl::stop_guard_repository_t::action_t::do_actual_stop == action ) m_impl->m_infrastructure->stop(); } void environment_t::call_exception_logger( const std::exception & event_exception, const coop_handle_t & coop ) noexcept { std::lock_guard< std::mutex > lock{ m_impl->m_event_exception_logger_lock }; m_impl->m_event_exception_logger->log_exception( event_exception, coop ); } exception_reaction_t environment_t::exception_reaction() const noexcept { return m_impl->m_exception_reaction; } error_logger_t & environment_t::error_logger() const { return *(m_impl->m_error_logger); } stats::controller_t & environment_t::stats_controller() { return m_impl->m_infrastructure->stats_controller(); } stats::repository_t & environment_t::stats_repository() { return m_impl->m_infrastructure->stats_repository(); } so_5::disp::abstract_work_thread_factory_shptr_t environment_t::work_thread_factory() const noexcept { return m_impl->m_work_thread_factory; } work_thread_activity_tracking_t environment_t::work_thread_activity_tracking() const { return m_impl->m_work_thread_activity_tracking; } disp_binder_shptr_t environment_t::so_make_default_disp_binder() { return m_impl->m_infrastructure->make_default_disp_binder(); } bool environment_t::autoshutdown_disabled() const { return m_impl->m_autoshutdown_disabled; } mbox_t environment_t::do_make_custom_mbox( custom_mbox_details::creator_iface_t & creator ) { return m_impl->m_mbox_core->create_custom_mbox( *this, creator ); } stop_guard_t::setup_result_t environment_t::setup_stop_guard( stop_guard_shptr_t guard, stop_guard_t::what_if_stop_in_progress_t reaction_on_stop_in_progress ) { const auto result = m_impl->m_stop_guards.setup_guard( std::move(guard) ); if( stop_guard_t::setup_result_t::stop_already_in_progress == result && stop_guard_t::what_if_stop_in_progress_t::throw_exception == reaction_on_stop_in_progress ) { SO_5_THROW_EXCEPTION( rc_cannot_set_stop_guard_when_stop_is_started, "stop_guard can't be set because the stop operation is " "already in progress" ); } return result; } void environment_t::remove_stop_guard( stop_guard_shptr_t guard ) { const auto action = m_impl->m_stop_guards.remove_guard( std::move(guard) ); if( impl::stop_guard_repository_t::action_t::do_actual_stop == action ) m_impl->m_infrastructure->stop(); } void environment_t::change_message_delivery_tracer_filter( so_5::msg_tracing::filter_shptr_t filter ) { if( !m_impl->m_msg_tracing_stuff.is_msg_tracing_enabled() ) SO_5_THROW_EXCEPTION( rc_msg_tracing_disabled, "msg_tracing's filter can't be changed when msg_tracing " "is disabled" ); m_impl->m_msg_tracing_stuff.change_filter( std::move(filter) ); } void environment_t::imp_run_stats_controller_and_go_further() { impl::run_stage( "run_stats_controller", [] { /* there is no need to turn_on controller automatically */ }, [this] { m_impl->m_infrastructure->stats_controller().turn_off(); }, [this] { imp_run_layers_and_go_further(); } ); } void environment_t::imp_run_layers_and_go_further() { impl::run_stage( "run_layers", [this] { m_impl->m_layer_core.start(); }, [this] { m_impl->m_layer_core.finish(); }, [this] { imp_run_infrastructure(); } ); } namespace { class autoshutdown_guard_t final { environment_t & m_env; const bool m_autoshutdown_disabled; coop_handle_t m_guard_coop; public : autoshutdown_guard_t( environment_t & env, bool autoshutdown_disabled ) : m_env{ env } , m_autoshutdown_disabled{ autoshutdown_disabled } { if( !m_autoshutdown_disabled ) { m_guard_coop = env.register_coop( env.make_coop() ); } } ~autoshutdown_guard_t() { if( !m_autoshutdown_disabled ) m_env.deregister_coop( m_guard_coop, dereg_reason::normal ); } }; } /* namespace anonymous */ void environment_t::imp_run_infrastructure() { m_impl->m_infrastructure->launch( [this]() { // init method must be protected from autoshutdown feature. autoshutdown_guard_t guard{ *this, m_impl->m_autoshutdown_disabled }; // Initilizing environment. init(); } ); } namespace impl { mbox_t internal_env_iface_t::create_ordinary_mpsc_mbox( agent_t & single_consumer ) { return m_env.m_impl->m_mbox_core->create_ordinary_mpsc_mbox( m_env, single_consumer ); } mbox_t internal_env_iface_t::create_limitless_mpsc_mbox( agent_t & single_consumer ) { return m_env.m_impl->m_mbox_core->create_limitless_mpsc_mbox( m_env, single_consumer ); } void internal_env_iface_t::ready_to_deregister_notify( coop_shptr_t coop ) noexcept { m_env.m_impl->m_infrastructure->ready_to_deregister_notify( std::move(coop) ); } void internal_env_iface_t::final_deregister_coop( coop_shptr_t coop ) noexcept { bool any_cooperation_alive = m_env.m_impl->m_infrastructure->final_deregister_coop( std::move(coop) ); if( !any_cooperation_alive && !m_env.m_impl->m_autoshutdown_disabled ) m_env.stop(); } bool internal_env_iface_t::is_msg_tracing_enabled() const { return m_env.m_impl->m_msg_tracing_stuff.is_msg_tracing_enabled(); } [[nodiscard]] so_5::msg_tracing::holder_t & internal_env_iface_t::msg_tracing_stuff() const { if( !is_msg_tracing_enabled() ) SO_5_THROW_EXCEPTION( rc_msg_tracing_disabled, "msg_tracer cannot be accessed because msg_tracing is disabled" ); return m_env.m_impl->m_msg_tracing_stuff; } [[nodiscard]] so_5::msg_tracing::holder_t & internal_env_iface_t::msg_tracing_stuff_nonchecked() const noexcept { return m_env.m_impl->m_msg_tracing_stuff; } so_5::disp::mpsc_queue_traits::lock_factory_t internal_env_iface_t::default_mpsc_queue_lock_factory() const { return m_env.m_impl->m_queue_locks_defaults_manager-> mpsc_queue_lock_factory(); } so_5::disp::mpmc_queue_traits::lock_factory_t internal_env_iface_t::default_mpmc_queue_lock_factory() const { return m_env.m_impl->m_queue_locks_defaults_manager-> mpmc_queue_lock_factory(); } [[nodiscard]] event_queue_t * internal_env_iface_t::event_queue_on_bind( agent_t * agent, event_queue_t * original_queue ) noexcept { return m_env.m_impl->m_event_queue_hook->on_bind( agent, original_queue ); } void internal_env_iface_t::event_queue_on_unbind( agent_t * agent, event_queue_t * queue ) noexcept { m_env.m_impl->m_event_queue_hook->on_unbind( agent, queue ); } [[nodiscard]] mbox_id_t internal_env_iface_t::allocate_mbox_id() noexcept { return m_env.m_impl->m_mbox_core->allocate_mbox_id(); } [[nodiscard]] subscription_storage_factory_t internal_env_iface_t::default_subscription_storage_factory() const noexcept( noexcept(subscription_storage_factory_t{} = subscription_storage_factory_t{}) ) { return m_env.m_impl->m_default_subscription_storage_factory; } } /* namespace impl */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/environment.hpp000066400000000000000000002005271523065210100205560ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief SObjectizer Environment definition. */ #if !defined( SO_5_ENVIRONMENT_HPP ) #define SO_5_ENVIRONMENT_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { namespace low_level_api { struct schedule_timer_params_t { //! Message type. const std::type_index & m_msg_type; //! Message to be sent after timeout. const message_ref_t & m_msg; //! Mbox to which message will be delivered. const mbox_t & m_mbox; //! Timeout before the first delivery. std::chrono::steady_clock::duration m_pause; //! Period of the delivery repetition for periodic messages. std::chrono::steady_clock::duration m_period; }; struct single_timer_params_t { //! Message type. const std::type_index & m_msg_type; //! Message to be sent after timeout. const message_ref_t & m_msg; //! Mbox to which message will be delivered. const mbox_t & m_mbox; //! Timeout before the delivery. std::chrono::steady_clock::duration m_pause; }; } /* namespace low_level_api */ // // environment_params_t // //! Parameters for the SObjectizer Environment initialization. /*! * This class is used for setting SObjectizer Parameters. * * \see http://www.parashift.com/c++-faq/named-parameter-idiom.html */ class SO_5_TYPE environment_params_t { public: /*! * \brief A sum type for holding parameters for the default disp. * * \since v.5.8.0 */ using default_disp_params_t = std::variant< disp::one_thread::disp_params_t, disp::nef_one_thread::disp_params_t >; /*! * \brief Constructor. * * Sets default values for parameters. */ environment_params_t(); /*! * \brief Move constructor. * * \since v.5.2.3 */ environment_params_t( environment_params_t && other ); ~environment_params_t(); /*! * \brief Move operator. * * \since v.5.2.3 */ environment_params_t & operator=( environment_params_t && other ) noexcept; /*! * \brief Swap operation. * * \since v.5.2.3 */ friend SO_5_FUNC void swap( environment_params_t & a, environment_params_t & b ) noexcept; //! Set the timer_thread factory. /*! * If \a factory is a null then the default timer thread * will be used. */ environment_params_t & timer_thread( //! timer_thread factory to be used. so_5::timer_thread_factory_t factory ); //! Add an additional layer to the SObjectizer Environment. /*! * If this layer is already added it will be replaced by \a layer_ptr. * * The method distinguishes layers from each other by the type SO_LAYER. */ template< class SO_Layer > environment_params_t & add_layer( //! A layer to be added. std::unique_ptr< SO_Layer > layer_ptr ) { if( layer_ptr.get() ) { layer_unique_ptr_t ptr( layer_ptr.release() ); add_layer( std::type_index( typeid( SO_Layer ) ), std::move( ptr ) ); } return *this; } //! Set cooperation listener object. environment_params_t & coop_listener( coop_listener_unique_ptr_t coop_listener ); //! Set exception logger object. environment_params_t & event_exception_logger( event_exception_logger_unique_ptr_t logger ); /*! * \name Exception reaction flag management methods. * \{ */ /*! * \brief Get exception reaction flag value. * * \note * This method is noexcept since v.5.8.0. * * \since v.5.3.0 */ inline exception_reaction_t exception_reaction() const noexcept { return m_exception_reaction; } /*! * \brief Set exception reaction flag value. * * Usage example: * \code * so_5::launch([](so_5::environment_t & env) {...}, * [](so_5::environment_params_t & params) { * params.exception_reaction(so_5::exception_reaction_t::shutdown_sobjectizer_on_exception); * ... * }); * \endcode * * \note * This method is noexcept since v.5.8.0. * * \since v.5.3.0 */ environment_params_t & exception_reaction( exception_reaction_t value ) noexcept { m_exception_reaction = value; return *this; } /*! * \} */ /*! * \brief Do not shutdown SO Environment when it is becomes empty. * * \par Description * Since v.5.4.0 SO Environment checks count of live cooperations * after every cooperation deregistration. If there is no more * live cooperations then SO Environment will be shutted down. * If it is not appropriate then this method must be called. * It disables autoshutdown of SO Environment. Event if there is * no more live cooperations SO Environment will work until * explicit call to environment_t::stop() method. * * \since v.5.4.0 */ environment_params_t & disable_autoshutdown() { m_autoshutdown_disabled = true; return *this; } /*! * \brief Is autoshutdown disabled? * * \see disable_autoshutdown() * * \since v.5.4.0 */ [[nodiscard]] bool autoshutdown_disabled() const { return m_autoshutdown_disabled; } /*! * \brief Set error logger for the environment. * * \since v.5.5.0 */ environment_params_t & error_logger( error_logger_shptr_t logger ) { m_error_logger = std::move(logger); return *this; } /*! * \brief Set message delivery tracer for the environment. * * Usage example: * \code * so_5::launch( [](so_5::environment_t & env) { ... }, * [](so_5::environment_params_t & params) { * params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); * ... * } ); * \endcode * * \since v.5.5.9 */ environment_params_t & message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t tracer ) { m_message_delivery_tracer = std::move( tracer ); return *this; } /*! * \brief Set message tracer filter for the environment. * * \since v.5.5.22 */ environment_params_t & message_delivery_tracer_filter( so_5::msg_tracing::filter_shptr_t filter ) { m_message_delivery_tracer_filter = std::move( filter ); return *this; } /*! * \brief Set parameters for a case when one_thread-disp * must be used as the default dispatcher. * * \par Usage example: \code so_5::launch( []( so_5::environment_t & env ) { ... }, []( so_5::environment_params_t & env_params ) { using namespace so_5::disp::one_thread; // Event queue for the default dispatcher must use mutex as lock. env_params.default_disp_params( disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & queue_params ) { queue_params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); } ); \endcode * * \note * If some parameters have already been set, the old parameters will be * replaced by new ones. * * \since v.5.5.10 */ environment_params_t & default_disp_params( so_5::disp::one_thread::disp_params_t params ) { m_default_disp_params = std::move(params); return *this; } /*! * \brief Set parameters for a case when * nef_one_thread-disp must be used as the default dispatcher. * * \par Usage example: \code so_5::launch( []( so_5::environment_t & env ) { ... }, []( so_5::environment_params_t & env_params ) { using namespace so_5::disp::nef_one_thread; // Event queue for the default dispatcher must use mutex as lock. env_params.default_disp_params( disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & queue_params ) { queue_params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); } ); \endcode * * \note * If some parameters have already been set, the old parameters will be * replaced by new ones. * * \since v.5.8.0 */ environment_params_t & default_disp_params( so_5::disp::nef_one_thread::disp_params_t params ) { m_default_disp_params = std::move(params); return *this; } /*! * \brief Get the parameters for the default dispatcher. * * \attention * The returned reference will be invalidated by any subsequent * calls to default_disp_params-setters. * * \note * Since v.5.8.0 it returns a sum type with parameters for different * types of dispatchers. Therefore, the returned value has to be examined * accordingly, for example: * \code * so_5::environment_params_t & params = ... * const auto & disp_params = params.default_disp_params(); * if( const auto * one_thread = * std::get_if< so_5::disp::one_thread::disp_params_t >( &disp_params ) ) * { * ... // Handling. * } * else if( const auto * nef_one_thread = * std::get_if< so_5::disp::nef_one_thread::disp_params_t >( &disp_params ) ) * { * ... // Handling. * } * \endcode * * \since v.5.5.10 */ const default_disp_params_t & default_disp_params() const { return m_default_disp_params; } /*! * \brief Set activity tracking flag for the whole SObjectizer Environment. * * \since v.5.5.18 */ environment_params_t & work_thread_activity_tracking( work_thread_activity_tracking_t flag ) { m_work_thread_activity_tracking = flag; return *this; } /*! * \brief Get activity tracking flag for the whole SObjectizer Environment. * * \since v.5.5.18 */ work_thread_activity_tracking_t work_thread_activity_tracking() const { return m_work_thread_activity_tracking; } //! Helper for turning work thread activity tracking on. /*! * \since v.5.5.18 */ environment_params_t & turn_work_thread_activity_tracking_on() { return work_thread_activity_tracking( work_thread_activity_tracking_t::on ); } //! Helper for turning work thread activity tracking off. /*! * \since v.5.5.18 */ environment_params_t & turn_work_thread_activity_tracking_off() { return work_thread_activity_tracking( work_thread_activity_tracking_t::off ); } //! Set manager for queue locks defaults. /*! * \since v.5.5.18 */ environment_params_t & queue_locks_defaults_manager( queue_locks_defaults_manager_unique_ptr_t manager ) { m_queue_locks_defaults_manager = std::move(manager); return *this; } //! Get the current environment infrastructure factory. /*! * \since v.5.5.19 */ const environment_infrastructure_factory_t & infrastructure_factory() const { return m_infrastructure_factory; } //! Set new environment infrastructure factory. /*! * Usage example: * \code * so_5::launch( []( so_5::environment_t & env ) { * ... // Some initial actions. * }, * []( so_5::environment_params_t & params ) { * // Set infrastructure factory to make simple not-thread-safe * // single-threaded infrastructure. * params.infrastructure_factory( * so_5::env_infrastructures::simple_not_mtsafe::factory() ); * } ); * \endcode * * \since v.5.5.19 */ environment_params_t & infrastructure_factory( environment_infrastructure_factory_t factory ) { m_infrastructure_factory = std::move(factory); return *this; } /*! * \brief Set event_queue_hook object. * * Since v.5.5.24 it is possible to use special event_queue_hook * object. If it is used it should be set for SObjectizer * Environment before the Environment will be started. This method * allows to specify event_queue_hook object for a new Environment * object. * * Usage example: * \code * so_5::launch( * [](so_5::environment_t & env) {...}, // Some stating actions. * [](so_5::environment_params_t & params) { * // Set my own event_queue hook object. * so_5::make_event_queue_hook( * // Object is created dynamically and should be * // destroyed the normal way. * so_5::event_queue_hook_t::default_deleter, * arg1, arg3, arg3 // and all other arguments for my_hook's constructor. * ); * }); * \endcode * * \note * The previous event_queue_hook object (if it was set earlier) * will just be dropped. * * \since v.5.5.24 */ void event_queue_hook( event_queue_hook_unique_ptr_t hook ) { m_event_queue_hook = std::move(hook); } /*! * \brief Set work thread factory to be used by default. * * Since v.5.7.3 it's possible to use custom worker threads instead of * standard ones provided by SObjectizer. To do so it's required to * provide an appropriate worker thread factory. It can be done by * specifying a factory in parameters for a dispatcher. Or it can be done * globally for the whole SObjectizer's Environment by this method. * * This method sets a global factory that will be used by default. It * means that: * * - the global factory won't be used if there is a factory in * dispatcher's parameters (in that case dispatcher's factory has * higher priority and the default factory is ignored); * - the global factory will be used if there is no factory specified in * dispatcher's parameters; * - the global factory will be used for the default one_thread dispatcher * created by default multi-thread environment infrastructure. * * Usage example: * \code * class my_thread_factory final : public so_5::disp::abstract_work_thread_factory_t * { * ... * }; * ... * so_5::launch( [](so_5::environment_t & env) { * env.introduce_coop( [](so_5::coop_t & coop) { * // This agent will be bound to the default dispatcher. * // Worker thread for the default dispatcher will be provided * // by default factory. * coop.make_agent(...); * * // This agent will be bound to an instance of new one_thread * // dispatcher. A worker thread for that new dispatcher will * // be provided by the default factory. * coop.make_agent_with_binder( * so_5::disp::one_thread::make_dispatcher(coop.environment()).binder(), * ...); * * // This dispatcher will use another own instance of * // thread factory. * auto tp_disp = so_5::disp::thread_pool::make_dispatcher( * coop.environment(), * "my_pool_disp", * so_5::disp::thread_pool::disp_params_t{} * .thread_count(10) * .work_thread_factory(std::make_shared(...)) ); * // This agent will be bound to the new thread_pool dispatcher * // and will work on a thread provided by a separate thread factory. * coop.make_agent_with_binder(tp_disp.binder()); * } ); * ... * }, * [](so_5::environment_params_t & params) { * params.work_thread_factory( std::make_shared(...) ); * } ); * \endcode * * \since v.5.7.3 */ environment_params_t & work_thread_factory( so_5::disp::abstract_work_thread_factory_shptr_t factory ) { m_work_thread_factory = std::move(factory); return *this; } /*! * \brief Set subscription storage factory to be used by default. * * Usage example: * * \code * so_5::launch( [](so_5::environment_t & env) {...}, * [](so_5::environment_params_t & params) { * params.default_subscription_storage_factory( * so_5::flat_set_based_subscription_storage_factory(32u) ); * } ); * \endcode * * \note * The \a factory can be null (empty). This can be useful if there is a need * to drop the previous value. For example: * \code * void tune_env_params(so_5::environment_params_t & params) { * ... // Some settings. * params.default_subscription_storage_factory( * so_5::flat_set_based_subscription_storage_factory(32u)); * ... // Some more settings. * } * ... * so_5::launch( [](so_5::environment_t & env) {...}, * [](so_5::environment_params_t & params) { * // The main settings. * tune_env_params(params); * * #if !defined(NDEBUG) * // Have to drop some settings in the Debug mode. * params.default_subscription_storage_factory({}); * #endif * } ); * \endcode * * \since v.5.8.2 */ environment_params_t & default_subscription_storage_factory( subscription_storage_factory_t factory ) { m_default_subscription_storage_factory = std::move(factory); return *this; } /*! * \brief Get the current default subscription storage factory. * * \note * This can be null (empty factory) if the factory isn't set * explicitly. * * \since v.5.8.2 */ [[nodiscard]] const subscription_storage_factory_t & default_subscription_storage_factory() const noexcept { return m_default_subscription_storage_factory; } /*! * \name Methods for internal use only. * \{ */ //! Get map of default SObjectizer's layers. [[nodiscard]] layer_map_t so5_giveout_layers_map() { return std::move( m_so_layers ); } //! Get cooperation listener. coop_listener_unique_ptr_t so5_giveout_coop_listener() { return std::move( m_coop_listener ); } //! Get exception logger. event_exception_logger_unique_ptr_t so5_giveout_event_exception_logger() { return std::move( m_event_exception_logger ); } //! Get the timer_thread factory. so_5::timer_thread_factory_t so5_giveout_timer_thread_factory() { return std::move( m_timer_thread_factory ); } //! Get error logger for the environment. const error_logger_shptr_t & so5_error_logger() const { return m_error_logger; } /*! * \brief Get message delivery tracer for the environment. * * \since v.5.5.9 */ so_5::msg_tracing::tracer_unique_ptr_t so5_giveout_message_delivery_tracer() { return std::move( m_message_delivery_tracer ); } /*! * \brief Get message delivery tracer filter for the environment. * * \since v.5.5.22 */ so_5::msg_tracing::filter_shptr_t so5_giveout_message_delivery_tracer_filter() { return std::move( m_message_delivery_tracer_filter ); } //! Take out queue locks defaults manager. /*! * \since v.5.5.18 */ queue_locks_defaults_manager_unique_ptr_t so5_giveout_queue_locks_defaults_manager() { return std::move( m_queue_locks_defaults_manager ); } //! Take out event_queue_hook object. /*! * \since v.5.5.24 */ event_queue_hook_unique_ptr_t so5_giveout_event_queue_hook() { return std::move( m_event_queue_hook ); } //! Take out work_thread_factory object. /*! * \since v.5.7.3 */ so_5::disp::abstract_work_thread_factory_shptr_t so5_giveout_work_thread_factory() { return std::move( m_work_thread_factory ); } /*! * \} */ private: //! Add an additional layer. /*! * If this layer is already added it will be replaced by \a layer_ptr. * * The method distinguishes layers from each other by the type SO_LAYER. */ void add_layer( //! Type identification for layer. const std::type_index & type, //! A layer to be added. layer_unique_ptr_t layer_ptr ); //! Timer thread factory. so_5::timer_thread_factory_t m_timer_thread_factory; //! Additional layers. layer_map_t m_so_layers; //! Cooperation listener. coop_listener_unique_ptr_t m_coop_listener; //! Exception logger. event_exception_logger_unique_ptr_t m_event_exception_logger; /*! * \brief Exception reaction flag for the whole SO Environment. * * \since v.5.3.0 */ exception_reaction_t m_exception_reaction; /*! * \brief Is autoshutdown when there is no more cooperation disabled? * * \see disable_autoshutdown() * * \since v.5.4.0 */ bool m_autoshutdown_disabled; /*! * \brief Error logger for the environment. * * \since v.5.5.0 */ error_logger_shptr_t m_error_logger; /*! * \brief Tracer for message delivery. * * \since v.5.5.9 */ so_5::msg_tracing::tracer_unique_ptr_t m_message_delivery_tracer; /*! * \brief Message delivery tracer filter to be used with environment. * * \since v.5.5.22 */ so_5::msg_tracing::filter_shptr_t m_message_delivery_tracer_filter; /*! * \brief Parameters for the default dispatcher. * * \since v.5.5.10 */ default_disp_params_t m_default_disp_params; /*! * \brief Work thread activity tracking for the whole Environment. * * \since v.5.5.18 */ work_thread_activity_tracking_t m_work_thread_activity_tracking; /*! * \brief Manager for defaults of queue locks. * * \since v.5.5.18 */ queue_locks_defaults_manager_unique_ptr_t m_queue_locks_defaults_manager; /*! * \brief A factory for environment infrastructure entity. * * \since v.5.5.19 */ environment_infrastructure_factory_t m_infrastructure_factory; /*! * \brief An event_queue_hook object. * * \note * It can be a nullptr. It means that no event_queue_hook should * be used. * * \since v.5.5.24 */ event_queue_hook_unique_ptr_t m_event_queue_hook; /*! * \brief Global factory for work threads. * * \note * It can be a nullptr. It means that standard work thread factory * has to be used as the global factory. * * \since v.5.7.3 */ so_5::disp::abstract_work_thread_factory_shptr_t m_work_thread_factory; /*! * \brief Default subscription storage factory. * * This factory will be used by default if a specific factory isn't * set for agent explicitly. * * \note * It can be a nullptr. It means that standard default subscription * storage factory has to be used as the global factory. * * \since v.5.8.2 */ subscription_storage_factory_t m_default_subscription_storage_factory; }; // // environment_t // //! SObjectizer Environment. /*! * \section so_env__intro Basic information * * The SObjectizer Environment provides a basic infrastructure for * the SObjectizer Run-Time execution. * * The main method of starting SObjectizer Environment creates a * class derived from the environment_t and reimplementing the * environment_t::init() method. * This method should be used to define starting actions of * application. For example first application cooperations can * be registered here and starting messages can be sent to them. * * The SObjectizer Environment calls the environment_t::init() when * the SObjectizer Run-Time is successfully started. * If something happened during the Run-Time startup then * the method init() will not be called. * * The SObjectizer Run-Time is started by the environment_t::run(). * This method blocks the caller thread until SObjectizer completely * finished its work. * * The SObjectizer Run-Time is finished by the environment_t::stop(). * This method doesn't block the caller thread. Instead it sends a special * shutdown signal to the Run-Time. The SObjectizer Run-Time then * informs agents about this and waits finish of agents work. * The SObjectizer Run-Time finishes if all agents are stopped and * all cooperations are deregistered. * * Methods of the SObjectizer Environment can be splitted into the * following groups: * - working with mboxes; * - working with dispatchers, exception loggers and handlers; * - working with cooperations; * - working with delayed and periodic messages; * - working with additional layers; * - initializing/running/stopping/waiting of the Run-Time. * * \section so_env__mbox_methods Methods for working with mboxes. * * SObjectizer Environment allows creation of named and anonymous mboxes. * Syncronization objects for these mboxes can be obtained from * common pools or assigned by a user during mbox creation. * * Mboxes are created by environment_t::create_mbox() methods. * All these methods return the mbox_t which is a smart reference * to the mbox. * * An anonymous mbox is automatically destroyed when the last reference to it is * destroyed. So, to save the anonymous mbox, the mbox_ref from * the create_mbox() should be stored somewhere. * * \section so_env__coop_methods Methods for working with cooperations. * * Cooperations can be created by environment_t::make_coop() methods. * * The method environment_t::register_coop() should be used for the * cooperation registration. * * Method environment_t::deregister_coop() should be used for the * cooperation deregistration. */ class SO_5_TYPE environment_t { friend class so_5::impl::internal_env_iface_t; //! Auxiliary methods for getting reference to itself. /*! * Could be used in constructors without compiler warnings. */ environment_t & self_ref(); public: explicit environment_t( //! Initialization params. environment_params_t && so_environment_params ); virtual ~environment_t(); environment_t( const environment_t & ) = delete; environment_t & operator=( const environment_t & ) = delete; /*! * \name Methods for working with mboxes. * \{ */ //! Create an anonymous MPMC mbox. /*! * Usage example: * \code * class my_agent final : public so_5::agent_t { * const so_5::mbox_t broadcast_mbox_; * ... * public: * my_agent(context_t ctx) * : so_5::agent_t{std::move(ctx)} * , broadcast_mbox_{so_environment().create_mbox()} * {} * ... * }; * \endcode * * \note always creates a new mbox. */ [[nodiscard]] mbox_t create_mbox(); //! Create named MPMC mbox. /*! * If \a mbox_name is unique then a new mbox will be created. * If not the reference to existing mbox will be returned. * * Usage example: * \code * class first_participant final : public so_5::agent_t { * const so_5::mbox_t broadcast_mbox_; * ... * public: * first_participant(context_t ctx) * : so_5::agent_t{std::move(ctx)} * , broadcast_mbox_{so_environment().create_mbox("message-board")} * {} * ... * }; * * class second_participant final : public so_5::agent_t { * const so_5::mbox_t broadcast_mbox_; * ... * public: * second_participant(context_t ctx) * : so_5::agent_t{std::move(ctx)} * , broadcast_mbox_{so_environment().create_mbox("message-board")} * {} * ... * }; * \endcode * In this example both agents will use the same mbox instance. * * \attention * Mboxes created by this method live in a separate namespace, * they are not mixed with named mboxes introduced via * introduce_named_mbox() method. */ [[nodiscard]] mbox_t create_mbox( //! Mbox name. nonempty_name_t mbox_name ); /*! * \brief Introduce named mbox with user-provided factory. * * This method allows a user to create own named mbox. * * The create_mbox(nonempty_name_t) method always creates a standard * MPMC mbox. This isn't always desirable. For example, a user may want * to have a named unique_subscribers mbox. The introduce_named_mbox() * allows to achieve this: * \code * class first_participant final : public so_5::agent_t { * const so_5::mbox_t broadcast_mbox_; * ... * public: * first_participant(context_t ctx) * : so_5::agent_t{std::move(ctx)} * , broadcast_mbox_{so_environment().introduce_named_mbox( * so_5::mbox_namespace_name_t{"demo"}, * "message-board", * [this]() { return so_5::make_unique_subscribers_mbox(so_environment()); } ) * } * {} * ... * }; * * class second_participant final : public so_5::agent_t { * const so_5::mbox_t broadcast_mbox_; * ... * public: * second_participant(context_t ctx) * : so_5::agent_t{std::move(ctx)} * , broadcast_mbox_{so_environment().introduce_named_mbox( * so_5::mbox_namespace_name_t{"demo"}, * "message-board", * [this]() { return so_5::make_unique_subscribers_mbox(so_environment()); } ) * } * {} * ... * }; * \endcode * * The `introduce_named_mbox` work the following way: * * * - it checks for the existence of a mbox with the given name in the * specified namespace. If such a mbox is found it will be returned and * \a mbox_factory is not called; * - otherwise, the \a mbox_factory is called; * - after the completion of \a mbox_factory the existence of a mbox with * the given name is checked again; * - if there is still no such a mbox, then the mbox obtained from \a * mbox_factory is stored inside the SOEnv and returned as the result * of the `introduce_named_mbox` method; * - but if a mbox is found (it may have been created a little earlier by * a parallel call to the `introduce_named_mbox`), then the the value * returned by \a mbox_factory is discarded and the existing named mbox * is returned. * * It means then the \a mbox_factory is used if there is no a mbox with * the name specified yet. If such a mbox is already exists then the * already created mbox will be returned without calling \a mbox_factory. * * Note that named mboxes created by this method live in separate * namespaces and those namespaces aren't intersect with "the default" * namespace used by the create_mbox(nonempty_name_t) method. It means * that a user can create named mboxes with the same name in different * namespaces and they will be different mboxes: * \code * so_5::environment_t & env = ...; * auto std_mbox = env.create_mbox("alice"); * auto my_mbox1 = env.introduce_named_mbox( * so_5::mbox_namespace_name_t{"a"}, * "alice", * [&env]() { return env.create_mbox(); }); * auto my_mbox2 = env.introduce_named_mbox( * so_5::mbox_namespace_name_t{"b"}, * "alice", * [&env]() { return env.create_mbox(); }); * // NOTE: it's just a reference to my_mbox2. * auto my_duplicate = env.introduce_named_mbox( * so_5::mbox_namespace_name_t{"b"}, * "alice", * [&env]() { return env.create_mbox(); }); * * assert(std_mbox->id() != my_mbox1->id()); * assert(std_mbox->id() != my_mbox2->id()); * assert(my_mbox1->id() != my_mbox2->id()); * assert(my_mbox2->id() == my_duplicate->id()); * \endcode * * \attention * The \a mbox_factory should return a valid so_5::mbox_t. * If \a mbox_factory returns empty so_5::mbox_t (nullptr), then * introduce_named_mbox() will throw an exception with * so_5::rc_nullptr_as_result_of_user_mbox_factory error code. * If \a mbox_factory can't create a valid so_5::mbox_t it * has to thrown a user-defined exception. This exception * will be let out from introduce_named_mbox(). For example: * \code * so_5::environment_t & env = ...; * try * { * auto mbox = env.introduce_named_mbox( * so_5::mbox_namespace_name_t{"b"}, * "alice", * [&]() { * auto mbox = try_to_get_new_mbox(); * if(!mbox) * throw my_exception{...}; * return mbox; * }); * } * catch( const my_exception & x ) { ... } * \endcode * * \note * If several parallel calls to introduce_named_mbox() with the same * parameters are made at the same time then \a mbox_factory can * be called several times (at most once for every introduce_named_mbox() * invocation). But only one result of those calls will be used, * all other returned values will be discarded. * * \attention * The \a mbox_factory can be called in parallel (so \a mbox_factory * should support this behavior). * The \a mbox_factory can safely call environment_t's methods * like create_mbox() and so on. * * \since v.5.8.0 */ [[nodiscard]] mbox_t introduce_named_mbox( //! Name of mbox_namespace for a new mbox. mbox_namespace_name_t mbox_namespace, //! Name for a new mbox. nonempty_name_t mbox_name, //! Factory for new mbox. const std::function< mbox_t() > & mbox_factory ); /*! * \} */ /*! * \name Method for working with message chains. * \{ */ /*! * \brief Create message chain. * * \par Usage examples: \code so_5::environment_t & env = ...; // Create mchain with size-unlimited queue. auto ch1 = env.create_mchain( so_5::make_unlimited_mchain_params() ); // Create mchain with size-limited queue without a timeout // on attempt to push another message to full mchain... auto ch2 = env.create_mchain( so_5::make_limited_without_waiting_mchain_params( // ...maximum size of the chain. 100, // ...memory for chain will be allocated and deallocated dynamically... so_5::mchain_props::memory_usage_t::dynamic, // ...an exception will be thrown on overflow. so_5::mchain_props::overflow_reaction_t::throw_exception ) ); // Create mchain with size-limited queue with a timeout for 200ms // on attempt to push another message to full mchain... auto ch3 = env.create_mchain( so_5::make_limited_with_waiting_mchain_params( // ...maximum size of the chain. 100, // ...memory for chain will be preallocated... so_5::mchain_props::memory_usage_t::preallocated, // ...an oldest message from mchain will be removed on overflow... so_5::mchain_props::overflow_reaction_t::remove_oldest, // ...timeout for waiting on attempt to push a message into full mchain. std::chrono::milliseconds(200) ) ); // Create size-unlimited mchain with custom notificator for // 'not_empty' situations. auto ch4 = env.create_mchain( so_5::make_unlimited_mchain_params().not_empty_notificator( [&] { some_widget.send_notify(); } ) ); \endcode * * \since v.5.5.13 */ mchain_t create_mchain( //! Parameters for a new bag. const mchain_params_t & params ); /*! * \} */ /*! * \name Method for working with dispatchers. * \{ */ //! Set up an exception logger. void install_exception_logger( event_exception_logger_unique_ptr_t logger ); /*! * \} */ /*! * \name Methods for working with cooperations. * \{ */ //! Create a cooperation. /*! * Usage example: * \code * so_5::environmet_t & env = ...; * // A binder for the default dispatcher will be used. * auto coop = env.make_coop(); * coop.make_agent(...); * coop.make_agent(...); * // Registration of the coop. * env.register_coop(std::move(coop)); * \endcode * * \return A new cooperation. This cooperation * will use default dispatcher binders. * * \since v.5.6.0 */ [[nodiscard]] coop_unique_holder_t make_coop(); //! Create a cooperation with specified dispatcher binder. /*! * A binder \a disp_binder will be used for binding cooperation * agents to the dispatcher. This binder will be default binder for * this cooperation. * \code so_5::environment_t & so_env = ...; so_5::coop_unique_holder_t coop = so_env.make_coop( so_5::disp::active_group::make_dispatcher( so_env ).binder( "some_active_group" ) ); // That agent will be bound to the dispatcher "active_group" // and will be member of an active group with name // "some_active_group". coop->make_agent< a_some_agent_t >(); \endcode * * \since v.5.6.0 */ [[nodiscard]] coop_unique_holder_t make_coop( //! A default binder for this cooperation. disp_binder_shptr_t disp_binder ); /*! * \brief Create a new cooperation that will be a child for * specified parent coop. * * The new cooperation will use the default dispatcher binder. * * Usage example: * \code * class parent_t final : public so_5::agent_t { * void on_some_event(mhood_t) { * // We need to create a child coop. * auto coop = so_environment().make_coop( * // We as a parent. * so_coop()); * ... * } * }; * \endcode * * \since v.5.6.0 */ [[nodiscard]] coop_unique_holder_t make_coop( //! Parent coop. coop_handle_t parent ); /*! * \brief Create a new cooperation that will be a child for * specified parent coop. * * The new cooperation will use the specified dispatcher binder. * * Usage example: * \code * class parent_t final : public so_5::agent_t { * void on_some_event(mhood_t) { * // We need to create a child coop. * auto coop = so_environment().make_coop( * // We as a parent. * so_coop(), * // The default dispatcher for the new coop. * so_5::disp::active_obj::make_dispatcher( * so_environment() ).binder() ); * ... * } * }; * \endcode * * \since v.5.6.0 */ [[nodiscard]] coop_unique_holder_t make_coop( //! Parent coop. coop_handle_t parent, //! A default binder for this cooperation. disp_binder_shptr_t disp_binder ); //! Register a cooperation. /*! * The cooperation registration includes following steps: * * - binding agents to the cooperation object; * - checking uniques of the cooperation name. The cooperation will * not be registered if its name isn't unique; * - agent_t::so_define_agent() will be called for each agent * in the cooperation; * - binding of each agent to the dispatcher. * * If all these actions are successful then the cooperation is * marked as registered. * * \par Usage examples * * Very simple case. * \code * so_5::environment_t & env = ...; * * auto simple_coop = env.make_coop(); * simple_coop->make_agent(...); * env.register_coop(std::move(simple_coop)); * \endcode * * More complex case with storing coop_handle and using it for * coop deregistration: * \code * so_5::environment_t & env = ...; * so_5::coop_handle_t coop_handle; * * auto simple_coop = env.make_coop(); * simple_coop->make_agent(...); * coop_handle = env.register_coop(std::move(simple_coop)); * ... * // Some time later. * env.deregister_coop(coop_handle, so_5::dereg_reason::normal); * \endcode * * A typical scenario for register_coop() when an instance of coop * is created by a separate function: * \code * [[nodiscard]] so_5::coop_unique_holder_t create_coop( * so_5::environment_t & env) * { * auto coop = env.make_coop(); * coop->make_agent(...); * ... // Some other actions like creation of additional agents. * * // Now the coop can be returned back. * return coop; * } * * so_5::environment_t & env = ...; * ... * env.register_coop(create_coop(env)); * \endcode */ coop_handle_t register_coop( //! Cooperation to be registered. coop_unique_holder_t agent_coop ); /*! * \brief Register single agent as a cooperation. * * It is just a helper methods for convience. * * Usage sample: * \code std::unique_ptr< my_agent > a( new my_agent(...) ); so_env.register_agent_as_coop( std::move(a) ); * \endcode */ template< class A > coop_handle_t register_agent_as_coop( std::unique_ptr< A > agent ) { auto coop = make_coop(); coop->add_agent( std::move( agent ) ); return register_coop( std::move( coop ) ); } /*! * \brief Register single agent as a cooperation with specified * dispatcher binder. * * It is just a helper methods for convience. * * Usage sample: * \code * so_5::environment_t & env = ...; * env.register_agent_as_coop( * std::make_unique(...), * so_5::disp::active_group::make_dispatcher(env) * .binder("some_active_group") ); * \endcode * * \since v.5.2.1 */ template< class A > coop_handle_t register_agent_as_coop( std::unique_ptr< A > agent, disp_binder_shptr_t disp_binder ) { auto coop = make_coop( std::move( disp_binder ) ); coop->add_agent( std::move( agent ) ); return register_coop( std::move( coop ) ); } //! Deregister the cooperation. /*! * Method searches the cooperation within registered cooperations and if * it is found deregisters it. * * Deregistration can take some time. * * At first a special signal is sent to cooperation agents. * By receiving these signal agents stop receiving new messages. * When the local event queue for an agent becomes empty the * agent informs the cooperation about this. When the cooperation * receives all these signals from agents it informs * the SObjectizer Run-Time. * * Only after this the cooperation is deregistered on the special * context. * * After the cooperation deregistration agents are unbound from * dispatchers. * * Usage example: * \code * so_5::environment_t & env = ...; * so_5::coop_handle_t coop_handle; * * auto simple_coop = env.make_coop(); * simple_coop->make_agent(...); * coop_handle = env.register_coop(std::move(simple_coop)); * ... * // Some time later. * env.deregister_coop(coop_handle, so_5::dereg_reason::normal); * \endcode * * \note * This method is marked as noexcept because there is no way * to recover if any exception is raised here. * * \attention * It's safe to pass an empty coop_handle_t to deregister_coop(). * It's also safe to pass a coop_handle of a coop that is already deregistered. * For example, this is not an error: * \code * class demo { * so_5::environment_t & m_env; * so_5::coop_handle_t m_child_coop; * ... * public: * demo( * so_5::environment_t & env, * so_5::coop_handle_t child_coop) * : m_env{env} * , m_child_coop{std::move(child_coop)} * {} * * ~demo() { * // Force the deregistration of the child coop. * // Don't care if drop_child() has already been called. * m_env.deregister_coop(m_child_coop, so_5::dereg_reason::normal); * } * ... * void drop_child() { * m_env.deregister_coop(m_child_coop, so_5::dereg_reason::normal); * } * }; * ... * void some_action(so_5::environment_t & env) { * ... * coop_handle = env.register_coop(...); * demo holder{env, coop_handle}; * ... * holder.drop_child(); // The first call to deregister_coop. * ... * } // Another call to deregister_coop in the destructor of demo object. * \endcode */ void deregister_coop( //! The coop to be deregistered. coop_handle_t coop, //! Deregistration reason. int reason ) noexcept { auto coop_shptr = low_level_api::to_shptr_noexcept( coop ); if( coop_shptr ) coop_shptr->deregister( reason ); } /*! * \} */ /*! * \name Methods for working with layers. * \{ */ //! Get access to the layer without raising exception if layer //! is not found. template< class SO_Layer > SO_Layer * query_layer_noexcept() const { static_assert( std::is_base_of< layer_t, SO_Layer >::value, "SO_Layer must be derived from so_layer_t class" ); return dynamic_cast< SO_Layer * >( query_layer( std::type_index( typeid( SO_Layer ) ) ) ); } //! Get access to the layer with exception if layer is not found. template< class SO_Layer > SO_Layer * query_layer() const { auto layer = query_layer_noexcept< SO_Layer >(); if( !layer ) SO_5_THROW_EXCEPTION( rc_layer_does_not_exist, "layer does not exist" ); return layer; } //! Add an additional layer. template< class SO_Layer > void add_extra_layer( std::unique_ptr< SO_Layer > layer_ptr ) { add_extra_layer( std::type_index( typeid( SO_Layer ) ), layer_ref_t( layer_ptr.release() ) ); } /*! * \} */ /*! * \name Methods for starting, initializing and stopping of the Run-Time. * \{ */ //! Run the SObjectizer Run-Time. void run(); //! Initialization hook. /*! * \attention A hang inside of this method will prevent the Run-Time * from stopping. For example if a dialog with an application user * is performed inside init() then SObjectizer cannot finish * its work until this dialog is finished. */ virtual void init() = 0; //! Send a shutdown signal to the Run-Time. /*! * \note * This method is noexcept since v.5.8.0. */ void stop() noexcept; /*! * \} */ /*! * \brief Call event exception logger for logging an exception. * * \note * Since v.5.6.0 this method is marked as noexcept. * * \since v.5.2.3 */ void call_exception_logger( //! Exception caught. const std::exception & event_exception, //! A cooperation to which agent is belong. const coop_handle_t & coop ) noexcept; /*! * \brief An exception reaction for the whole SO Environment. * * \note * This method is noexcept since v.5.8.0. * * \since v.5.3.0 */ exception_reaction_t exception_reaction() const noexcept; /*! * \brief Get the error_logger object. * * \since v.5.5.0 */ error_logger_t & error_logger() const; /*! * \brief Helper method for simplification of agents creation. * * \since v.5.5.4 * * \note Creates an instance of agent of type \a Agent by using * environment_t::make_agent() template function and adds it to * the cooperation. Uses the fact that most agent types use reference * to the environment object as the first argument. * * \return unique pointer to the new agent. * * \tparam Agent type of agent to be created. * \tparam Args type of parameters list for agent constructor. * * \par Usage sample: \code so_5::environment_t & env = ...; // For the case of constructor like my_agent(environmen_t&). auto a1 = env.make_agent< my_agent >(); // For the case of constructor like your_agent(environment_t&, std::string). auto a2 = env.make_agent< your_agent >( "hello" ); // For the case of constructor like their_agent(environment_t&, std::string, mbox_t). auto a3 = env.make_agent< their_agent >( "bye", a2->so_direct_mbox() ); \endcode */ template< class Agent, typename... Args > std::unique_ptr< Agent > make_agent( Args &&... args ) { return std::unique_ptr< Agent >( new Agent( *this, std::forward(args)... ) ); } /*! * \brief Access to controller of run-time monitoring. * * \since v.5.5.4 */ stats::controller_t & stats_controller(); /*! * \brief Access to repository of data sources for run-time monitoring. * * \since v.5.5.4 */ stats::repository_t & stats_repository(); /*! * \brief Access to the global work thread factory. * * \since v.5.7.3 */ [[nodiscard]] so_5::disp::abstract_work_thread_factory_shptr_t work_thread_factory() const noexcept; /*! * \brief Helper method for simplification of cooperation creation * and registration. * * \return The value returned from lambda-function. Or void if * the lambda-function returns void. * * \since v.5.5.5 * * \par Usage samples: \code // The default dispatcher will be used for binding. env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< first_agent >(...); coop.make_agent< second_agent >(...); }); // For the case when dispatcher binder is specified. env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), []( so_5::coop_t & coop ) { coop.make_agent< first_agent >(...); coop.make_agent< second_agent >(...); } ); // Usage of return value from the lambda function. so_5::mbox_t mbox = env.introduce_coop( [](so_5::coop_t & coop) { auto * a = coop.make_agent< first_agent >(...); coop.make_agent< second_agent >(...); return a->so_direct_mbox(); }); \endcode */ template< typename... Args > decltype(auto) introduce_coop( Args &&... args ); /*! * \brief Get activity tracking flag for the whole SObjectizer Environment. * * \since v.5.5.18 */ work_thread_activity_tracking_t work_thread_activity_tracking() const; /*! * \brief Get binding to the default dispatcher. * * \note * This method is part of environment_t for possibility to * write custom implementations of environment_infrastructure_t. * Because of that this method can be changed or removed in * future versions of SObjectizer. * * \since v.5.5.19 */ disp_binder_shptr_t so_make_default_disp_binder(); /*! * \brief Get autoshutdown_disabled flag. * * Autoshutdown feature is on by default. It can be turned off * in environment_params_t. This methods returns true if * autoshutdown is turned off. * * \since v.5.5.19 */ bool autoshutdown_disabled() const; //! Schedule timer event. /*! * \attention * Values of \a pause and \a period should be non-negative. * * \note * This method is a part of low-level SObjectizer's API. * Because of that it can be changed or removed in some * future version of SObjectizer without prior notice. */ so_5::timer_id_t so_schedule_timer( //! Parameters for new timer. const low_level_api::schedule_timer_params_t params ); //! Schedule a single shot timer event. /*! * \attention * Value of \a pause should be non-negative. * * \note * This method is a part of low-level SObjectizer's API. * Because of that it can be changed or removed in some * future version of SObjectizer without prior notice. */ void so_single_timer( //! Parameters for new timer. const low_level_api::single_timer_params_t params ); //! Create a custom mbox. /*! * \tparam Lambda type of actual lambda with all creation actions. * The Lambda must be lambda-function or functional objects with * the following format: * \code * so_5::mbox_t lambda(const so_5::mbox_creation_data_t &); * \endcode * * \since v.5.5.19.2 */ template< typename Lambda > mbox_t make_custom_mbox( Lambda && lambda ) { using namespace custom_mbox_details; creator_template_t< Lambda > creator( std::forward(lambda) ); return do_make_custom_mbox( creator ); } /*! * \name Methods for working with stop_guards. * \{ */ //! Set up a new stop_guard. /*! * Usage examples: * \code * // Add a stop_guard. * // Note: an exception can be thrown if stop is in progress * class my_stop_guard * : public so_5::stop_guard_t * , public std::enable_shared_from_this< my_stop_guard > * {...}; * * class my_agent : public so_5::agent_t * { * ... * void on_some_event() * { * // We need a stop_guard here. * m_my_guard = std::make_shared< my_stop_guard >(...); * so_environment().setup_stop_guard( m_my_guard ); * } * private : * so_5::stop_guard_shptr_t m_my_guard; * }; * * // * // Add a stop_guard without throwing an exception if stop is in progress * // * class my_stop_guard * : public so_5::stop_guard_t * , public std::enable_shared_from_this< my_stop_guard > * {...}; * * class my_agent : public so_5::agent_t * { * ... * void on_some_event() * { * // We need a stop_guard here. * m_my_guard = std::make_shared< my_stop_guard >(...); * const auto r = so_environment().setup_stop_guard( * m_my_guard, * so_5::stop_guard_t::what_if_stop_in_progress_t::return_negative_result ); * if( so_5::stop_guard_t::setup_result_t::stop_already_in_progress == r ) * ... // handle error here. * } * private : * so_5::stop_guard_shptr_t m_my_guard; * }; * \endcode * * \note * Uniqueness of stop_guard is not checked. It means that * it is possible to add the same stop_guard several times. * But it seems to be useless. * * \since v.5.5.19.2 */ stop_guard_t::setup_result_t setup_stop_guard( //! Stop guard to be set. //! Should not be nullptr. stop_guard_shptr_t guard, //! What to do is the stop operation is already in progress? stop_guard_t::what_if_stop_in_progress_t reaction_on_stop_in_progress = stop_guard_t::what_if_stop_in_progress_t::throw_exception ); //! Remove stop_guard and complete the stop operation if necessary. /*! * Every stop_guard which was added to the environment must be * explicitely removed from the environment. It is done by this method. * If there is no more stop_guard and the stop operation is in progress * then the environment will complete the stop operation. * * Usage examples: * \code * // Note: an exception can be thrown if stop is in progress * class my_stop_guard * : public so_5::stop_guard_t * , public std::enable_shared_from_this< my_stop_guard > * {...}; * * class my_agent : public so_5::agent_t * { * ... * void on_some_event() * { * // We need a stop_guard here. * m_my_guard = std::make_shared< my_stop_guard >(...); * so_environment().setup_stop_guard( m_my_guard ); * } * * void on_work_finished_signal() * { * // Stop_guard must be removed now. * so_environment().remove_stop_guard( m_my_guard ); * } * private : * so_5::stop_guard_shptr_t m_my_guard; * }; * \endcode * * \since v.5.5.19.2 */ void remove_stop_guard( //! Stop guard to be removed. stop_guard_shptr_t guard ); /*! * \} */ /*! * \name Methods for working with msg_tracing's filters. * \{ */ /*! * \brief Change the current msg_tracing's filter to a new one. * * Usage example: * \code * so_5::launch([](so_5::environment_t & env) {...}, * [](so_5::environment_params_t & params) { * // Turn message delivery tracing on. * params.message_delivery_tracer( * so_5::msg_tracing::std_cout_tracer()); * // Disable all trace messages. * // It is expected that trace filter will be changed in the future. * params.message_delivery_tracer_filter( * so_5::msg_tracing::make_disable_all_filter()); * ... * } ); * ... * void some_agent_t::turn_msg_tracing_on() { * // Remove trace filter. As result all trace messages will be printed. * so_environment().change_message_delivery_tracer_filter( * so_5::msg_tracing::no_filter()); * ... * } * \endcode * * \note * It is possible that there are active calls to * so_5::msg_tracing::filter_t::filter() methods at the time of * invocation of change_message_delivery_tracer_filter(). In this * case all active calls will be completed with the previous * filter. This could lead to mixture of messages in the trace: * some of them will be enabled by old filter and some of them * will be enabled by new filter. And it is possible that * messages enabled by new filter will precede messages enabled * by old filter. * * \throw exception_t if message delivery tracing is disabled. * * \since v.5.5.22 */ void change_message_delivery_tracer_filter( //! A new filter to be used. //! It can be an empty pointer. In this case all trace messages //! will be passed to tracer object. so_5::msg_tracing::filter_shptr_t filter ); /*! * \} */ private: //! Access to an additional layer. layer_t * query_layer( const std::type_index & type ) const; //! Add an additional layer. void add_extra_layer( const std::type_index & type, const layer_ref_t & layer ); //! Remove an additional layer. void remove_extra_layer( const std::type_index & type ); //! Actual creation of a custom mbox. /*! * \since v.5.5.19.2 */ mbox_t do_make_custom_mbox( custom_mbox_details::creator_iface_t & creator ); struct internals_t; //! SObjectizer Environment internals. std::unique_ptr< internals_t > m_impl; /*! * \name Implementation details related to run/stop functionality. * \{ */ /*! * \brief Run controller for run-time monitoring * and call next run stage. * * \since v.5.5.4 */ void imp_run_stats_controller_and_go_further(); /*! * \brief Run layers and call next run stage. */ void imp_run_layers_and_go_further(); /*! * \brief Launch environment infrastructure and wait for finish. * * \since v.5.5.19 */ void imp_run_infrastructure(); /*! * \} */ }; namespace details { /*! * \brief Helper class for building and registering new cooperation. * * \since v.5.5.5 */ class introduce_coop_helper_t { private : //! Environment for creation of cooperation. environment_t & m_env; //! Optional parent coop. coop_handle_t m_parent; template< typename Lambda > decltype(auto) build_and_register_coop( disp_binder_shptr_t binder, Lambda && lambda ) { const auto coop_maker = [this, b = std::move(binder)] { if( m_parent ) return m_env.make_coop( m_parent, std::move(b) ); else return m_env.make_coop( std::move(b) ); }; auto coop = coop_maker(); using return_type = std::invoke_result_t; if constexpr( std::is_void_v ) { lambda( *coop ); m_env.register_coop( std::move( coop ) ); } else if constexpr( std::is_reference_v ) { auto && ret_val = lambda( *coop ); m_env.register_coop( std::move( coop ) ); return std::forward(ret_val); } else { auto ret_val = lambda( *coop ); m_env.register_coop( std::move( coop ) ); return ret_val; } } public : //! Constructor for the case of creation a cooperation without parent. introduce_coop_helper_t( environment_t & env ) : m_env{ env } {} //! Constructor for the case of creation a cooperation with parent. introduce_coop_helper_t( environment_t & env, coop_handle_t parent ) : m_env{ env } , m_parent{ std::move(parent) } {} /*! * For the case: * - default dispatcher is used. */ template< typename L > decltype(auto) introduce( L && lambda ) { return build_and_register_coop( m_env.so_make_default_disp_binder(), std::forward< L >( lambda ) ); } /*! * For the case: * - dispatcher builder is specified. */ template< typename L > decltype(auto) introduce( disp_binder_shptr_t binder, L && lambda ) { return build_and_register_coop( std::move( binder ), std::forward< L >( lambda ) ); } }; } /* namespace details */ template< typename... Args > decltype(auto) environment_t::introduce_coop( Args &&... args ) { details::introduce_coop_helper_t helper{ *this }; return helper.introduce( std::forward< Args >( args )... ); } /*! * \brief A simple way for creating child cooperation. * * \since v.5.5.3 * * \par Usage sample \code class owner : public so_5::agent_t { public : ... virtual void so_evt_start() override { auto child = so_5::create_child_coop( *this ); child->make_agent< worker >(); ... so_environment().register_coop( std::move( child ) ); } }; \endcode */ template< typename... Args > [[nodiscard]] coop_unique_holder_t create_child_coop( //! Owner of the cooperation. agent_t & owner, //! Arguments for the environment_t::make_coop() method. Args&&... args ) { auto coop = owner.so_environment().make_coop( owner.so_coop(), std::forward< Args >(args)... ); return coop; } /*! * \brief A simple way for creating child cooperation when there is * a reference to the parent cooperation object. * * \since v.5.5.8 * * \par Usage sample \code class parent final : public so_5::agent_t { void on_some_event(mhood_t) { auto child = so_5::create_child_coop( // We as the parent coop. so_coop(), // The default binder for the new coop. so_5::disp::one_thread::make_dispatcher( so_environment().binder() ) ); ... so_environment().register_coop( std::move(child) ); } ... }; \endcode */ template< typename... Args > [[nodiscard]] coop_unique_holder_t create_child_coop( //! Parent cooperation. coop_handle_t parent, //! Arguments for the environment_t::make_coop() method. Args&&... args ) { return low_level_api::to_shptr(parent)->environment().make_coop( parent, std::forward< Args >(args)... ); } /*! * \brief A simple way for creating and registering child cooperation. * * \since v.5.5.5 * * \par Usage sample \code class owner : public so_5::agent_t { public : ... virtual void so_evt_start() override { so_5::introduce_child_coop( *this, []( so_5::coop_t & coop ) { coop.make_agent< worker >(); } ); } }; \endcode * \note This function is just a tiny wrapper around * so_5::environment_t::introduce_coop() helper method. For more * examples with usage of introduce_coop() please see description of * that method. */ template< typename... Args > decltype(auto) introduce_child_coop( //! Owner of the cooperation. agent_t & owner, //! Arguments for the environment_t::introduce_coop() method. Args&&... args ) { return details::introduce_coop_helper_t{ owner.so_environment(), owner.so_coop() }.introduce( std::forward< Args >(args)... ); } /*! * \brief A simple way for creating and registering child cooperation * when there is a reference to parent coop. * * \since v.5.5.8 * * \par Usage sample \code class owner : public so_5::agent_t { public : ... virtual void so_evt_start() override { so_5::introduce_child_coop( so_coop(), []( so_5::coop_t & coop ) { coop.make_agent< worker >(); } ); } }; \endcode * \note This function is just a tiny wrapper around * so_5::environment_t::introduce_coop() helper method. For more * examples with usage of introduce_coop() please see description of * that method. */ template< typename... Args > decltype(auto) introduce_child_coop( //! Parent cooperation. coop_handle_t parent, //! Arguments for the environment_t::introduce_coop() method. Args&&... args ) { return details::introduce_coop_helper_t{ low_level_api::to_shptr(parent)->environment(), parent }.introduce( std::forward< Args >(args)... ); } // // make_default_disp_binder // /*! * \brief Create an instance of the default dispatcher binder. * * \note This function takes into account a possibility to have * different types of environment infrastructures (introduced in v.5.5.19) * and creates a default dispatcher binder with respect to the * actual environment infrastructure type. * * Usage example: * \code * so_5::launch( [](so_5::environment_t & env) { * env.introduce_coop( * // Agents from that coop will be bound to the default dispatcher. * so_5::make_default_disp_binder(env), * [](so_5::coop_t & coop) { * coop.make_agent<...>(...); * } ); * } ); * \endcode * * \since v.5.5.19 */ inline disp_binder_shptr_t make_default_disp_binder( environment_t & env ) { return env.so_make_default_disp_binder(); } namespace low_level_api { //! Schedule periodic timer event. /*! * \note * This function is a part of low-level SObjectizer's interface. * Because of that this function can be removed or changed in some * future version without prior notice. * * \attention * Values of \a pause and \a period should be non-negative. * * \since v.5.6.0 */ [[nodiscard]] inline so_5::timer_id_t schedule_timer( //! Message type for searching subscribers. const std::type_index & subscription_type, //! Message to be sent after timeout. message_ref_t msg, //! Mbox to which message will be delivered. const mbox_t & mbox, //! Timeout before the first delivery. std::chrono::steady_clock::duration pause, //! Period of the delivery repetition for periodic messages. /*! \note Value 0 indicates that it's not periodic message (will be delivered one time). */ std::chrono::steady_clock::duration period ) { return mbox->environment().so_schedule_timer( schedule_timer_params_t{ std::cref(subscription_type), std::cref(msg), std::cref(mbox), pause, period } ); } //! Schedule single timer event. /*! * \note * This function is a part of low-level SObjectizer's interface. * Because of that this function can be removed or changed in some * future version without prior notice. * * \attention * Value \a period should be non-negative. * * \since v.5.6.0 */ inline void single_timer( //! Message type for searching subscribers. const std::type_index & subscription_type, //! Message to be sent after timeout. message_ref_t msg, //! Mbox to which message will be delivered. const mbox_t & mbox, //! Timeout before the delivery. std::chrono::steady_clock::duration pause ) { return mbox->environment().so_single_timer( single_timer_params_t{ std::cref(subscription_type), std::cref(msg), std::cref(mbox), pause } ); } } /* namespace low_level_api */ } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/environment_infrastructure.hpp000066400000000000000000000200001523065210100237000ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Interface of environment_infrastructure entity. * * \since v.5.5.19 */ #if !defined( SO_5_ENVIRONMENT_INFRASTRUCTURE_HPP ) #define SO_5_ENVIRONMENT_INFRASTRUCTURE_HPP #include #include #include #include #include #include #include namespace so_5 { class environment_infrastructure_t; /*! * \brief A type for deleter for environment_infrastructure objects. * * \since v.5.5.19 */ using environment_infrastructure_deleter_fnptr_t = void (*)(environment_infrastructure_t *); // // environment_infrastructure_t // /*! * \brief An interface for environment_infrastructure entity. * * Environment infrastructure implements several important parts of * SObjectizer's Environment: * * - repository of registered cooperations (including actions for * final cooperation deregistration); * - default dispatcher; * - timer thread or manager; * - run-time stats_controller and stats_repository. * * This class defines the interface of environment_infrastructure. * All environment_infrastructure implementations must inherit this interface. * * \since v.5.5.19 */ class SO_5_TYPE environment_infrastructure_t { public : environment_infrastructure_t() = default; environment_infrastructure_t( const environment_infrastructure_t & ) = delete; environment_infrastructure_t( environment_infrastructure_t && ) = delete; environment_infrastructure_t & operator=( const environment_infrastructure_t & ) = delete; environment_infrastructure_t & operator=( environment_infrastructure_t && ) = delete; virtual ~environment_infrastructure_t() noexcept = default; //! Default deleter for environment_infrastructure object. /*! * This function is intended for simplification of code * where environment_infrastructure_unique_ptr_t must be created * with default deleter. * * For example: * \code auto my_env = new some_user_specific_env_infrastructure(...); return so_5::environment_infrastructure_unique_ptr_t( my_env, so_5::environment_infrastructure_t::default_deleter() ); * \endcode */ inline static environment_infrastructure_deleter_fnptr_t default_deleter() { return [](environment_infrastructure_t * p) { delete p; }; } //! Type of function for performing environment initialization actions. using env_init_t = std::function< void() >; // // coop_repository_stats_t // /*! * \brief Statistical data for run-time monitoring of coop repository * content. * * \note * Since v.5.6.0 there is only total count of coops without * separation to registered and deregistered coops count. * * \since 5.5.19 */ struct coop_repository_stats_t { //! Count of cooperations inside the environment. std::size_t m_total_coop_count; //! Count of registered agents. std::size_t m_total_agent_count; /*! * \brief Count of coops waiting for the final deregistration. * * \since v.5.5.12 */ std::size_t m_final_dereg_coop_count; }; //! Do actual launch of SObjectizer's Environment. /*! * This method is called as a final step of environment lauch procedure. * * Stats_controller, all additional layers and dispatchers are already * started before calling on this method. * * This method must lock the current thread and return only when * SObjectizer's Environment finishes its work. */ virtual void launch( //! Actions from Environment which must be performed to //! complete launch procedure. env_init_t init_fn ) = 0; //! Initiate a signal for shutdown of Environment. /*! * \note * This method is noexcept since v.5.8.0 */ virtual void stop() noexcept = 0; //! Create an instance of a new coop. /*! * \since v.5.6.0 */ [[nodiscard]] virtual coop_unique_holder_t make_coop( //! Optinal parent coop. coop_handle_t parent, //! Default binder for that coop. disp_binder_shptr_t default_binder ) = 0; //! Register new cooperation. [[nodiscard]] virtual coop_handle_t register_coop( //! Cooperation to be registered. coop_unique_holder_t coop ) = 0; //! Notification about cooperation for which the final dereg step //! can be performed. virtual void ready_to_deregister_notify( //! A cooperation which can be finaly deregistered. coop_shptr_t coop ) noexcept = 0; //! Do final actions of the cooperation deregistration. /*! * \note * This method is noexcept since v.5.8.0 * * \retval true there are some live cooperations. * \retval false there is no more live cooperations. */ virtual bool final_deregister_coop( //! Cooperation to be deregistered. coop_shptr_t coop ) noexcept = 0; //! Initiate a timer (delayed or periodic message). virtual so_5::timer_id_t schedule_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) = 0; //! Initiate a delayed message. virtual void single_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause ) = 0; //! Get stats controller for the environment. /*! * \note * An instance of stats_controller is created and destroyed by * environment_infrastructure object. But stats_controller is started and * stopped by environment object itself. It means that stats_controller * must be constructed and must be ready to use just after creation of * environment_infrastructure instance and long before invocation of * launch() method. */ virtual ::so_5::stats::controller_t & stats_controller() noexcept = 0; //! Get stats repository for the environment. /*! * \note * The same rules apply as for stats_controller() method. */ virtual ::so_5::stats::repository_t & stats_repository() noexcept = 0; //! Query run-time statistics for cooperation repository. virtual coop_repository_stats_t query_coop_repository_stats() = 0; //! Query run-time statistics for timer (thread or manager). virtual timer_thread_stats_t query_timer_thread_stats() = 0; //! Create a binder for the default dispatcher. virtual disp_binder_shptr_t make_default_disp_binder() = 0; }; // // environment_infrastructure_unique_ptr_t // /*! * \brief A type of unique pointer for environment_infrastructure entity. * * \note * A deleter should be specified for entity object. It makes possible * to create unique pointers for objects which must not be delete via * delete operator. For example: * \code // Object is created on stack. Need not to be deleted. my_special_infrastructure_t my( ... ); so_5::launch( [](so_5::environment_t & env) {...}, [&](so_5::environment_params_t & p) { p.environment_infrastructure( [&](so_5::environment_t &, so_5::environment_params_t &) { return so_5::environment_infrastructure_unique_ptr_t( &my, [](so_5::environment_infrastructure_t *){} ); } ); ... } ); * \endcode * * \since v.5.5.19 */ using environment_infrastructure_unique_ptr_t = std::unique_ptr< environment_infrastructure_t, environment_infrastructure_deleter_fnptr_t >; // // environment_infrastructure_factory_t // /*! * \brief Type of factory for environment infrastructure. * * A factory must be a function with the following format: * \code * environment_infrastructure_unique_ptr_t * factory( * // Environment for which env_infrastructure will be created. * environment_t & env, * // Parameters for that environment. * environment_params_t & params, * // Message box for distribution of run-time statistics. * mbox_t stats_distribution_mbox ); * \endcode * * \since v.5.5.19 */ using environment_infrastructure_factory_t = std::function< environment_infrastructure_unique_ptr_t( environment_t &, environment_params_t &, mbox_t) >; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/error_logger.cpp000066400000000000000000000040441523065210100206710ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.0 * * \file * \brief Tools for logging error messages inside SObjectizer core. */ #include #include #include #include #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) // Warning about non-secure localtime and sprintf. #pragma warning(disable: 4996) #endif #if defined( SO_5_CLANG ) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif namespace so_5 { // // stderr_logger_t // /*! * \since * v.5.5.0 * * \brief A standard implementation of error_logger interface. */ class stderr_logger_t : public error_logger_t { public : stderr_logger_t() {} virtual void log( const char * file, unsigned int line, const std::string & message ) override; }; void stderr_logger_t::log( const char * file, unsigned int line, const std::string & message ) { using namespace std; using namespace chrono; ostringstream total_message; auto now = system_clock::now(); auto ms = duration_cast< milliseconds >( now.time_since_epoch() ); time_t unix_time = duration_cast< seconds >( ms ).count(); char date_time_first_part[ 64 ]; strftime( date_time_first_part, sizeof( date_time_first_part ) - 1, "%Y-%m-%d %H:%M:%S", localtime( &unix_time ) ); char date_time_second_part[ 16 ]; sprintf( date_time_second_part, ".%03u", static_cast< unsigned int >( ms.count() % 1000u ) ); total_message << "[" << date_time_first_part << date_time_second_part << " TID:" << query_current_thread_id() << "] " << message << " (" << file << ":" << line << ")\n"; cerr << total_message.str(); } // // create_stderr_logger // SO_5_FUNC error_logger_shptr_t create_stderr_logger() { return error_logger_shptr_t( new stderr_logger_t() ); } } /* namespace so_5 */ #if defined( SO_5_CLANG ) #pragma clang diagnostic pop #endif #if defined( SO_5_MSVC ) #pragma warning(pop) #endif sobjectizer-5.8.6/dev/so_5/error_logger.hpp000066400000000000000000000061261523065210100207010ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.0 * * \file * \brief Tools for logging error messages inside SObjectizer core. */ #if !defined( SO_5_ERROR_LOGGER_HPP ) #define SO_5_ERROR_LOGGER_HPP #include #include #include #include namespace so_5 { // // error_logger_t // /*! * \since * v.5.5.0 * * \brief An interface for logging error messages. */ class SO_5_TYPE error_logger_t { error_logger_t( const error_logger_t & ) = delete; error_logger_t & operator=( error_logger_t & ) = delete; public : error_logger_t() = default; virtual ~error_logger_t() noexcept = default; //! A method for logging message. /*! * \note * This method is not noexcept. It means that logger can throws, * but in most cases logger is called in cases when the application * is prepared to be aborted due to some fatal errors. In those cases * an exception from log() will just terminate the application a bit * earlier. In other cases exceptions thrown by log() will be ignored. */ virtual void log( //! Source file name. const char * file_name, //! Line number inside source file. unsigned int line, //! Text to log. const std::string & message ) = 0; }; // // error_logger_shptr_t // /*! * \since * v.5.5.0 * * \brief An alias for shared_ptr to error_logger. */ using error_logger_shptr_t = std::shared_ptr< error_logger_t >; // // create_stderr_logger // /*! * \since * v.5.5.0 * * \brief A factory for creating error_logger implemenation which * uses std::stderr as log stream. */ SO_5_FUNC error_logger_shptr_t create_stderr_logger(); namespace log_msg_details { class conductor_t { public : inline conductor_t( error_logger_t & logger, const char * file, unsigned int line ) : m_logger( logger ) , m_file( file ) , m_line( line ) {} template< class Env > conductor_t( const Env & env, const char * file, unsigned int line ) : m_logger( env.error_logger() ) , m_file( file ) , m_line( line ) {} inline bool completed() const { return m_completed; } inline std::ostringstream & stream() { return m_stream; } inline void log_message() { m_completed = true; m_logger.log( m_file, m_line, m_stream.str() ); } private : error_logger_t & m_logger; const char * m_file; unsigned int m_line; bool m_completed = false; std::ostringstream m_stream; }; } /* namespace log_msg_details */ } /* namespace so_5 */ // // SO_5_LOG_ERROR // /*! * \since * v.5.5.0 * * \brief An implementation for SO_5_LOG_ERROR macro. */ #define SO_5_LOG_ERROR_IMPL(logger, file, line, var_name) \ for( so_5::log_msg_details::conductor_t conductor_( logger, file, line ); \ !conductor_.completed(); ) \ for( std::ostringstream & var_name = conductor_.stream(); \ !conductor_.completed(); conductor_.log_message() ) // // SO_5_LOG_ERROR // /*! * \since * v.5.5.0 * * \brief A special macro for helping error logging. * */ #define SO_5_LOG_ERROR(logger, var_name) \ SO_5_LOG_ERROR_IMPL(logger, __FILE__, __LINE__, var_name ) #endif sobjectizer-5.8.6/dev/so_5/event_exception_logger.cpp000066400000000000000000000021151523065210100227340ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include namespace so_5 { // // event_exception_logger_t // void event_exception_logger_t::on_install( event_exception_logger_unique_ptr_t previous_logger ) noexcept { // Default implementation deletes old logger. previous_logger.reset(); } namespace /* ananymous */ { // // std_event_exception_logger_t // //! Standard exception logger implementation. class std_event_exception_logger_t : public event_exception_logger_t { public: std_event_exception_logger_t() = default; void log_exception( const std::exception & event_exception, const coop_handle_t & coop ) noexcept override { std::cerr << "SObjectizer event exception caught: " << event_exception.what() << "; cooperation: " << coop << std::endl; } }; } /* anonymous namespace */ // // create_std_event_exception_logger // SO_5_FUNC event_exception_logger_unique_ptr_t create_std_event_exception_logger() { return event_exception_logger_unique_ptr_t( new std_event_exception_logger_t() ); } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/event_exception_logger.hpp000066400000000000000000000044151523065210100227460ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief An interface for the exception logging definition. */ #if !defined( SO_5_EVENT_EXCEPTION_LOGGER_HPP ) #define SO_5_EVENT_EXCEPTION_LOGGER_HPP #include #include #include #include #include #include namespace so_5 { // // event_exception_logger_t // class event_exception_logger_t; //! Typedef for the event_exception_logger autopointer. using event_exception_logger_unique_ptr_t = std::unique_ptr< event_exception_logger_t >; //! An interface for the exception logging. /*! * Exception logger should handle two actions: * * 1. Installation of a logger into the SObjectizer Environment. * The on_install() method could be reimplemented for this. * 2. Caught of an exception. The log_exception() method * should be reimplemented for this. */ class SO_5_TYPE event_exception_logger_t { // Note: clang-3.9 requires this on Windows platform. event_exception_logger_t( const event_exception_logger_t & ) = delete; event_exception_logger_t( event_exception_logger_t && ) = delete; event_exception_logger_t & operator=( const event_exception_logger_t & ) = delete; event_exception_logger_t & operator=( event_exception_logger_t && ) = delete; public: event_exception_logger_t() = default; virtual ~event_exception_logger_t() noexcept = default; /*! * \brief Log the exception caught. * * \attention * This method is marked as noexcept in v.5.6.0 */ virtual void log_exception( //! Exception caught. const std::exception & event_exception, //! Cooperation to which agent is belong. const coop_handle_t & coop ) noexcept = 0; /*! * \brief Installation hook. * * A new exception logger should decide what happened with * the old logger. * * Default implementation simply erases the old logger. * * \attention * This method is marked as noexcept in v.5.6.0 */ virtual void on_install( //! Old logger. event_exception_logger_unique_ptr_t previous_logger ) noexcept; }; // // create_std_event_exception_logger // //! Create the default exception logger. SO_5_FUNC event_exception_logger_unique_ptr_t create_std_event_exception_logger(); } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/event_queue.hpp000066400000000000000000000043261523065210100205360ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \since v.5.4.0 \brief An interface of event queue for agent. */ #if !defined( SO_5_EVENT_QUEUE_HPP ) #define SO_5_EVENT_QUEUE_HPP #include namespace so_5 { /*! * \since * v.5.4.0 * * \brief An interface of event queue for agent. */ class SO_5_TYPE event_queue_t { // Note: clang-3.9 requires this on Windows platform. event_queue_t( const event_queue_t & ) = delete; event_queue_t( event_queue_t && ) = delete; event_queue_t & operator=( const event_queue_t & ) = delete; event_queue_t & operator=( event_queue_t && ) = delete; public : event_queue_t() = default; virtual ~event_queue_t() noexcept = default; //! Enqueue new event to the queue. /*! * Since v.5.8.0 this method will be used for all demands except * evt_start and evt_finish events. For these events * special methods push_evt_start() and push_evt_finish() are used. * * \note * This method can throw and it's expected. */ virtual void push( execution_demand_t demand ) = 0; /*! * \brief Enqueue a demand for evt_start event. * * The first event for an agent is evt_start event. Since v.5.8.0 * a demand for the first event is pushed to agent's event_queue * via this method. The ordinal push() method is used for all * other demands except the evt_start and evt_finish demands. * * \note * This method can throw and it's expected. * * \since v.5.8.0 */ virtual void push_evt_start( execution_demand_t demand ) = 0; /*! * \brief Enqueue a demand for evt_finish event. * * The last event for an agent is evt_finish event. Since v.5.8.0 * a demand for the last event is pushed to agent's event_queue * via this method. The ordinal push() method is used for all * other demands except the evt_start and evt_finish demands. * * \note * This method it expected to be noexcept, but if it can't be * implemented for a particular dispatcher then a call to * std::terminate() is appropriate on exception. Because * the normal work can't be continued anyway. * * \since v.5.8.0 */ virtual void push_evt_finish( execution_demand_t demand ) noexcept = 0; }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/event_queue_hook.cpp000066400000000000000000000005671523065210100215540ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Implementation details of event_queue_hook. * * \since * v.5.5.24 */ #include namespace so_5 { void event_queue_hook_t::default_deleter( event_queue_hook_t * what ) noexcept { delete what; } void event_queue_hook_t::noop_deleter( event_queue_hook_t * ) noexcept {} } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/event_queue_hook.hpp000066400000000000000000000135321523065210100215550ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Interface of event_queue_hook object. * * \since * v.5.5.24 */ #if !defined( SO_5_EVENT_QUEUE_HOOK_HPP ) #define SO_5_EVENT_QUEUE_HOOK_HPP #include #include #include #include #include namespace so_5 { // // event_queue_hook_deleter_fnptr_t // /*! * \brief A type for deleter of event_queue_hook object. * * \since * v.5.5.24 */ using event_queue_hook_deleter_fnptr_t = void (*)(event_queue_hook_t *) /*noexcept*/; // // event_queue_hook_t // /*! * \brief Interface of event_queue_hook object * * Since v.5.5.24 there is a new mechanism for customization of SObjectizer: * hook for event_queue. Such hooks allows to replace an original event_queue * provided by a dispatcher to some custom version of event_queue. That * custom version can do some additional stuff, for example, collect some * stats about demands. * * This class describes an interface of event_queue_hook. The interface * contains the following methods: * * - on_bind(). This method will be called by every agent in SObjectizer * when agent is being bound to a dispatcher. This method can be used * for replacing an original event_queue by custom one; * - on_unbind(). This method will be called by every agent when agent * is being unbound from the dispatcher. This method should be used * for releasing any resources allocated for a custom queue created or * used in on_bind() method. * * \since * v.5.5.24 */ class SO_5_TYPE event_queue_hook_t { // Note: clang-3.9 requires this on Windows platform. event_queue_hook_t( const event_queue_hook_t & ) = delete; event_queue_hook_t( event_queue_hook_t && ) = delete; event_queue_hook_t & operator=( const event_queue_hook_t & ) = delete; event_queue_hook_t & operator=( event_queue_hook_t && ) = delete; public : event_queue_hook_t() = default; virtual ~event_queue_hook_t() = default; //! A reaction to binding of an agent to some event_queue. /*! * This method can return \a original_queue or a pointer to * a different queue. The agent must use the value returned. */ [[nodiscard]] virtual event_queue_t * on_bind( //! An agent that is being bound to some event_queue. agent_t * agent, //! An event_queue created for that agent by a dispatcher. event_queue_t * original_queue ) noexcept = 0; //! A reaction to unbinding of an agent from some event_queue. /*! * This method must be called by an agent even if the previous * call to on_bind() returned pointer to the original queue. */ virtual void on_unbind( //! An agent that is being bound to some event_queue. agent_t * agent, //! The pointer returned by previous call to on_bind. event_queue_t * queue ) noexcept = 0; /*! * \brief An implementation of deleter that use operator delete * for destroying object of type event_queue_hook. * * Pointer to this method is intended to be used with * event_queue_hook_unique_ptr_t. For example: * \code * class my_hook final : public so_5::event_queue_hook_t { * ... * }; * ... * so_5::event_queue_hook_unique_ptr_t hook_ptr{ * new my_hook(...), * so_5::event_queue_hook_t::default_deleter }; * ... * so_5::launch( * [](so_5::environment_t & env) {...}, * [&](so_5::environment_params_t & params) { * params.event_queue_hook(std::move(hook_ptr)); * }); * \endcode */ static void default_deleter( event_queue_hook_t * what ) noexcept; /*! * \brief An implementation of no-op deleter. * * This method do nothing. It is intended to be used with * objects those not to be deleted by operator delete. For * example: * \code * class my_hook final : public so_5::event_queue_hook_t { * ... * }; * ... * // Note! This object is created on stack. * my_hook hook{...}; * ... * so_5::launch( * [](so_5::environment_t & env) {...}, * [&](so_5::environment_params_t & params) { * params.event_queue_hook( * so_5::event_queue_hook_unique_ptr_t{ * &hook, * so_5::event_queue_hook_t::noop_deleter}); * }); * \endcode */ static void noop_deleter( event_queue_hook_t * ) noexcept; }; // // event_queue_hook_unique_ptr_t // /*! * \brief Alias for unique pointer to event_queue_hook. * * \since * v.5.5.24 */ using event_queue_hook_unique_ptr_t = std::unique_ptr< event_queue_hook_t, event_queue_hook_deleter_fnptr_t >; // // make_event_queue_hook // /*! * \brief Helper function for simplify creation of event_queue_hook * object. * * This helper function allows to write: * \code * auto hook = so_5::make_event_queue_hook( * so_5::event_queue_hook_t::default_deleter, * ...); // Some arguments for my_hook's constructor. * \endcode * instead of: * \code * so_5::event_queue_hook_unique_ptr_t hook{ * new my_hook{...}, // Some arguments for my_hook's constructor. * so_5::event_queue_hook_t::default_deleter }; * \endcode * * \since * v.5.5.24 */ template< typename Hook_Type, typename... Args > event_queue_hook_unique_ptr_t make_event_queue_hook( event_queue_hook_deleter_fnptr_t deleter, Args && ...args ) { return { new Hook_Type( std::forward(args)... ), deleter }; } // // make_empty_event_queue_hook_unique_ptr // /*! * \brief Helper function for creation of empty unique_ptr for event_queue_hook. * * This function allows to write: * \code * auto hook = so_5::make_empty_event_queue_hook_unique_ptr(); * \endcode * instead of * \code * so_5::event_queue_hook_unique_ptr_t hook{nullptr, so_5::event_queue_hook_t::default_deleter}; * \endcode * * \since * v.5.5.24 */ [[nodiscard]] inline event_queue_hook_unique_ptr_t make_empty_event_queue_hook_unique_ptr() { return { nullptr, &event_queue_hook_t::default_deleter }; } } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/exception.cpp000066400000000000000000000006661523065210100202050ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include namespace so_5 { // // exception_t // void exception_t::raise( const char * file_name, unsigned int line_number, std::string_view error_descr, int error_code ) { std::ostringstream s; s << "(" << file_name << ":" << line_number << "): error(" << error_code << ") " << error_descr; throw exception_t( s.str(), error_code ); } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/exception.hpp000066400000000000000000000027371523065210100202130ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief An exception class. */ #if !defined( SO_5_EXCEPTION_HPP ) #define SO_5_EXCEPTION_HPP #include #include #include #include #include #include namespace so_5 { #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4275) #endif // // exception_t // //! The base class for all SObjectizer exceptions. class SO_5_TYPE exception_t : public std::runtime_error { public: exception_t( const std::string & error_descr, int error_code ) : std::runtime_error( error_descr ) , m_error_code( error_code ) {} exception_t( const exception_t & ) = default; exception_t( exception_t && ) = default; exception_t & operator=( exception_t & o ) = default; exception_t & operator=( exception_t && o ) noexcept = default; //! Error code getter. int error_code() const noexcept { return m_error_code; } static void raise( const char * file_name, unsigned int line_number, std::string_view error_descr, int error_code ); private: //! Error code. int m_error_code; }; #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #define SO_5_THROW_EXCEPTION_IMPL(file, line, error_code, desc)\ so_5::exception_t::raise(file, line, (desc), (error_code)) #define SO_5_THROW_EXCEPTION(error_code, desc)\ SO_5_THROW_EXCEPTION_IMPL(__FILE__, __LINE__, error_code, desc) } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/exception_control_flags.hpp000066400000000000000000000035551523065210100231260ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Types and constants for exception control flags. */ #if !defined( SO_5_EXCEPTION_CONTROL_FLAGS_HPP ) #define SO_5_EXCEPTION_CONTROL_FLAGS_HPP namespace so_5 { /*! * \brief Type that indicates that exceptions are enabled. * * \since * v.5.7.3 */ struct exceptions_enabled_t {}; /*! * \brief Value that indicates that exceptions are enabled. * * Usage example: * \code * auto ch = so_5::create_mchain(env); * ... * so_5::close_drop_content( so_5::exceptions_enabled, ch ); * \endcode * * \since * v.5.7.3 */ inline constexpr exceptions_enabled_t exceptions_enabled{}; /*! * \brief Type that indicates that an exception leads to the termination of * the whole application. * * \since * v.5.7.3 */ struct terminate_if_throws_t {}; /*! * \brief Value that indicates that an exception leads to the termination of * the whole application. * * Usage example: * \code * class some_class { * so_5::mchain_t ch_; * ... * public: * some_class(so_5::environment_t & env) * : ch_{so_5::create_mchain(env)} * {} * ~some_class() noexcept * { * // There is no sense to allow exceptions during close operation. * ch_->close( * so_5::terminate_if_throws, * so_5::mchain_props::close_mode_t::drop_content ); * } * ... * }; * \endcode * * \since * v.5.7.3 */ inline constexpr terminate_if_throws_t terminate_if_throws; namespace details { /*! * \brief Metafunction that tells is exceptions enabled or not. * * \since * v.5.7.3 */ template< typename Flag > struct should_terminate_if_throws_t; template<> struct should_terminate_if_throws_t< exceptions_enabled_t > { static constexpr bool value = false; }; template<> struct should_terminate_if_throws_t< terminate_if_throws_t > { static constexpr bool value = true; }; } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/execution_demand.hpp000066400000000000000000000166011523065210100215230ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \since v.5.4.0 \brief Event-related stuff. */ #if !defined( SO_5_EXECUTION_DEMAND_HPP ) #define SO_5_EXECUTION_DEMAND_HPP #include #include #include #include namespace so_5 { // // event_handler_method_t // /*! * \since * v.5.3.0 * * \brief Type of event handler method. */ using event_handler_method_t = std::function< void(message_ref_t &) >; struct execution_demand_t; // // demand_handler_pfn_t // /*! * \since * v.5.2.0 * * \brief Demand handler prototype. */ using demand_handler_pfn_t = void (*)( current_thread_id_t, execution_demand_t & ); // // execution_demand_t // /*! * \since * v.5.4.0 * * \brief A description of event execution demand. */ struct execution_demand_t { //! Receiver of demand. agent_t * m_receiver; //! Optional message limit for that message. const message_limit::control_block_t * m_limit; //! ID of mbox. mbox_id_t m_mbox_id; //! Type of the message. std::type_index m_msg_type; //! Event incident. message_ref_t m_message_ref; //! Demand handler. demand_handler_pfn_t m_demand_handler; //! Default constructor. execution_demand_t() noexcept : m_receiver( nullptr ) , m_limit( nullptr ) , m_mbox_id( 0 ) , m_msg_type( typeid(void) ) , m_demand_handler( nullptr ) {} execution_demand_t( agent_t * receiver, const message_limit::control_block_t * limit, mbox_id_t mbox_id, std::type_index msg_type, message_ref_t message_ref, demand_handler_pfn_t demand_handler ) noexcept : m_receiver( receiver ) , m_limit( limit ) , m_mbox_id( mbox_id ) , m_msg_type( msg_type ) , m_message_ref( std::move( message_ref ) ) , m_demand_handler( demand_handler ) {} /*! * \since * v.5.5.8 * * \brief Helper method to simplify demand execution. */ inline void call_handler( current_thread_id_t thread_id ) { (*m_demand_handler)( thread_id, *this ); } }; // // execution_hint_t // /*! * \brief A hint for a dispatcher for execution of event * for the concrete execution_demand. * * An instance of execution hint can be in two "states": * * - holds actual event handler. In that case an invocation of * exec() method will decrement message limit counter and then * will call the event handler; * - empty. In that case an invocation of exec() method will only * decrement message limit counter and nothing more. * * \since v.5.4.0 */ class execution_hint_t { public : //! Type of function for calling event handler directly. using direct_func_t = std::function< void( execution_demand_t &, current_thread_id_t ) >; //! Initializing constructor. execution_hint_t( execution_demand_t & demand, direct_func_t direct_func, thread_safety_t thread_safety ) : m_demand( demand ) , m_direct_func( std::move( direct_func ) ) , m_thread_safety( thread_safety ) {} //! Call event handler directly. void exec( current_thread_id_t working_thread_id ) const { // If message limit is defined then message count // must be decremented. message_limit::control_block_t::decrement( m_demand.m_limit ); // Now demand can be handled. if( m_direct_func ) m_direct_func( m_demand, is_thread_safe() ? null_current_thread_id() : working_thread_id ); } //! Is thread safe handler? bool is_thread_safe() const { return thread_safe == m_thread_safety; } //! Create execution_hint object for the case when //! event handler not found. /*! * This hint is necessary only for decrementing the counter of * messages if message limit is used for the message to be processed. */ static execution_hint_t create_empty_execution_hint( execution_demand_t & demand ) { return execution_hint_t( demand ); } private : //! A reference to demand for which that hint has been created. execution_demand_t & m_demand; //! Function for call event handler directly. direct_func_t m_direct_func; //! Thread safety for event handler. thread_safety_t m_thread_safety; //! A special constructor for the case when there is no //! handler for message. execution_hint_t( execution_demand_t & demand ) : m_demand( demand ) , m_direct_func() , m_thread_safety( thread_safe ) {} // Only for the unit-testing purposes! #if defined( SO_5_EXECUTION_HINT_UNIT_TEST ) public : //! Is event handler defined for the demand? operator bool() const { return static_cast< bool >(m_direct_func); } #endif }; namespace details { // // msg_type_and_handler_pair_t // /*! * \since * v.5.5.13 * * \brief Description of an event handler. */ struct msg_type_and_handler_pair_t { //! Type of a message or signal. std::type_index m_msg_type; //! A handler for processing this message or signal. event_handler_method_t m_handler; //! What message is expected by handler: mutable or immutable. /*! * By default immutable message is expected. * * \since * v.5.5.19 */ message_mutability_t m_mutability; //! Default constructor. msg_type_and_handler_pair_t() : m_msg_type{ typeid(void) } , m_mutability{ message_mutability_t::immutable_message } {} //! Constructor for the case when only msg_type is known. /*! * This constructor is intended for cases when * msg_type_and_handler_pair_t instance is used * as a key for searching in ordered sequences. */ msg_type_and_handler_pair_t( //! Type of a message or signal. std::type_index msg_type ) : m_msg_type{ std::move(msg_type) } , m_mutability{ message_mutability_t::immutable_message } {} //! Initializing constructor. msg_type_and_handler_pair_t( //! Type of a message or signal. std::type_index msg_type, //! A handler for processing this message or signal. event_handler_method_t handler, //! What message is expected by handler: mutable or immutable? message_mutability_t mutability ) : m_msg_type{ std::move(msg_type) } , m_handler{ std::move(handler) } , m_mutability{ mutability } {} //! Copy constructor. msg_type_and_handler_pair_t( const msg_type_and_handler_pair_t & o ) : m_msg_type{ o.m_msg_type } , m_handler{ o.m_handler } , m_mutability{ o.m_mutability } {} //! Move constructor. msg_type_and_handler_pair_t( msg_type_and_handler_pair_t && o ) noexcept : m_msg_type{ std::move(o.m_msg_type) } , m_handler{ std::move(o.m_handler) } , m_mutability{ std::move(o.m_mutability) } {} //! Swap operation. friend void swap( msg_type_and_handler_pair_t & a, msg_type_and_handler_pair_t & b ) noexcept { using std::swap; swap( a.m_msg_type, b.m_msg_type ); swap( a.m_handler, b.m_handler ); swap( a.m_mutability, b.m_mutability ); } //! Copy operator. msg_type_and_handler_pair_t & operator=( const msg_type_and_handler_pair_t & o ) { msg_type_and_handler_pair_t tmp{ o }; swap( *this, tmp ); return *this; } //! Move operator. msg_type_and_handler_pair_t & operator=( msg_type_and_handler_pair_t && o ) noexcept { msg_type_and_handler_pair_t tmp{ std::move(o) }; swap( *this, tmp ); return *this; } //! Comparison (strictly less than). bool operator<( const msg_type_and_handler_pair_t & o ) const { return m_msg_type < o.m_msg_type; } //! Comparison (strictly equal). bool operator==( const msg_type_and_handler_pair_t & o ) const { return m_msg_type == o.m_msg_type; } }; } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/experimental/000077500000000000000000000000001523065210100201705ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/experimental/testing.hpp000066400000000000000000000004161523065210100223570ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Top-level header file for all testing-related stuff. * * \since v.5.5.24 */ #if !defined( SO_5_EXPERIMENTAL_TESTING_HPP ) #define SO_5_EXPERIMENTAL_TESTING_HPP #include #endif sobjectizer-5.8.6/dev/so_5/experimental/testing/000077500000000000000000000000001523065210100216455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/experimental/testing/v1/000077500000000000000000000000001523065210100221735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/experimental/testing/v1/all.cpp000066400000000000000000001341771523065210100234640ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Testing related stuff. * * \since v.5.5.24 */ #include #include #include namespace so_5 { namespace experimental { namespace testing { inline namespace v1 { namespace details { trigger_t::trigger_t( incident_status_t incident_status, const agent_t & target, std::type_index msg_type, mbox_id_t src_mbox_id ) : m_incident_status( incident_status ) , m_target_agent( target ) , m_target_id( target.so_direct_mbox()->id() ) , m_msg_type( std::move(msg_type) ) , m_src_mbox_id( src_mbox_id ) {} trigger_t::~trigger_t() {} [[nodiscard]] const agent_t & trigger_t::target_agent() const noexcept { return m_target_agent; } void trigger_t::set_completion( completion_function_t fn ) { if( !m_completion ) // Simple store the new function. m_completion = std::move(fn); else { // We have to join the old and the new ones. completion_function_t joined_version = [old_fn = m_completion, new_fn = std::move(fn)] ( const trigger_completion_context_t & ctx ) { // NOTE: don't care about exception because completion // functions have to be noexcept. old_fn( ctx ); new_fn( ctx ); }; m_completion = std::move(joined_version); } } void trigger_t::set_activation( activation_function_t fn ) { if( !m_activation ) // Simple store the new function. m_activation = std::move(fn); else { // We have to join the old and the new ones. activation_function_t joined_version = [old_fn = m_activation, new_fn = std::move(fn)] ( const trigger_activation_context_t & ctx ) { // NOTE: don't care about exception because completion // functions have to be noexcept. old_fn( ctx ); new_fn( ctx ); }; m_activation = std::move(joined_version); } } [[nodiscard]] bool trigger_t::check( const incident_status_t incident_status, const incident_info_t & info ) const noexcept { return incident_status == m_incident_status && info.m_agent->so_direct_mbox()->id() == m_target_id && info.m_msg_type == m_msg_type && info.m_src_mbox_id == m_src_mbox_id; } [[nodiscard]] bool trigger_t::requires_completion() const noexcept { return static_cast(m_completion); } void trigger_t::activate( const trigger_activation_context_t & context ) noexcept { if( m_activation ) m_activation( context ); } void trigger_t::complete( const trigger_completion_context_t & context ) noexcept { m_completion( context ); } /*! * \brief An actual implementation of step of testing scenario. * * \since v.5.5.24 */ class real_scenario_step_t final : public abstract_scenario_step_t { //! Type of container for preactivation actions. using preactivate_actions_container_t = std::vector< preactivate_action_t >; //! Name of a step. const std::string m_name; //! All preactivation actions. preactivate_actions_container_t m_preactivate_actions; //! All constraints. constraint_container_t m_constraints; //! All triggers. /*! * This containers holds all triggers as for when_all, as * for when_any case. * * There is an important trick: activated triggers moved to * the end of that container. It means that first N triggers * in that containers wait for activation. The value of N * is defined by m_last_non_activated_trigger attribute. */ trigger_container_t m_triggers; //! Index of last trigger in the first part of trigger's container. /*! * Receives actual value in setup_triggers() method. * At the begining, if m_triggers.size() is not 0, it holds * (m_triggers.size()-1). Then it is decremented after activation * of yet another trigger. */ std::size_t m_last_non_activated_trigger = 0u; //! Count of triggers those should be activated for activation //! of the step. /*! * Receives actual value in setup_triggers() method. */ std::size_t m_triggers_to_activate; //! Count of triggers those are activated. std::size_t m_triggers_activated = 0u; //! Count of activated triggers those are not completed yet. /*! * Condition for completion of the step looks like: * \code * (m_triggers_to_activate == m_triggers_activated) && (0u == m_triggers_to_completion) * \endcode */ std::size_t m_triggers_to_completion = 0u; //! The current state of the step. status_t m_status = status_t::passive; public : real_scenario_step_t( std::string name ) : m_name( std::move(name) ) {} [[nodiscard]] const std::string & name() const noexcept override { return m_name; } void preactivate() noexcept override { change_status( status_t::preactivated ); } [[nodiscard]] token_t pre_handler_hook( const scenario_in_progress_accessor_t & scenario_accessor, const incident_info_t & info, const message_ref_t & incoming_msg ) noexcept override { token_t result; if( status_t::preactivated == m_status ) result = try_activate( trigger_activation_context_t{ scenario_accessor, *this, incoming_msg }, incident_status_t::handled, info ); return result; } void post_handler_hook( const scenario_in_progress_accessor_t & scenario_accessor, token_t token ) noexcept override { if( token.valid() ) { token.trigger().complete( trigger_completion_context_t{ scenario_accessor, *this } ); --m_triggers_to_completion; // If step activated and there is no triggers to // be completed then the step can be completed. // But there can be cases when m_triggers_to_completion // is zero, but the step is not activated yet // (for example when `when_all` is used). if( !m_triggers_to_completion && status_t::active == m_status ) change_status( status_t::completed ); } } void no_handler_hook( const scenario_in_progress_accessor_t & scenario_accessor, const incident_info_t & info, const message_ref_t & incoming_msg ) noexcept override { if( status_t::preactivated == m_status ) (void)try_activate( trigger_activation_context_t{ scenario_accessor, *this, incoming_msg, }, incident_status_t::ignored, info ); } [[nodiscard]] status_t status() const noexcept override { return m_status; } void add_preactivate_action( preactivate_action_t action ) override { m_preactivate_actions.emplace_back( std::move(action) ); } void setup_triggers( trigger_container_t triggers, std::size_t triggers_to_activate ) noexcept override { using std::swap; swap( m_triggers, triggers ); m_triggers_to_activate = triggers_to_activate; m_last_non_activated_trigger = m_triggers.size(); if( m_last_non_activated_trigger ) --m_last_non_activated_trigger; } void setup_constraints( constraint_container_t constraints ) noexcept override { using std::swap; swap( m_constraints, constraints ); } private : //! Change the state of the step and perform all necessary //! transitional actions. /*! * If a new state is status_t::preactivated then preactivation * actions will be called and constraint_t::start() will be called * for all constraints. * * If a new state is status_t::completed then constraint_t::finish() * will be called for all constraints. */ void change_status( status_t status ) noexcept { m_status = status; switch( status ) { case status_t::preactivated: { for( auto & act : m_preactivate_actions ) act(); for( auto & c : m_constraints ) c->start(); } break; case status_t::completed: { for( auto & c : m_constraints ) c->finish(); } break; case status_t::passive: break; case status_t::active: break; } } //! An attempt to check constraints for a new incident. /*! * \retval true if all constraints fullfilled. */ [[nodiscard]] bool try_pass_constraints( const incident_status_t incident_status, const incident_info_t & info ) const noexcept { for( auto & c : m_constraints ) if( !c->check( incident_status, info ) ) return false; return true; } //! An attempt to activate the step when a new incident arrives. [[nodiscard]] token_t try_activate( const trigger_activation_context_t & context, const incident_status_t incident_status, const incident_info_t & info ) noexcept { token_t result; // All constraints must be fullfilled. if( !try_pass_constraints( incident_status, info ) ) return result; // Check triggers those are not activated yet. // Those triggers are in the first part of m_triggers. // It is safe to add 1 to m_last_non_activated_trigger, because // this method is called only if there is at least one // non-activated trigger in the triggers container. auto end = std::begin(m_triggers) + static_cast( m_last_non_activated_trigger + 1u); auto it = std::find_if( std::begin(m_triggers), end, [incident_status, &info] ( trigger_unique_ptr_t & trigger ) { return trigger->check( incident_status, info ); } ); if( it == end ) return result; trigger_t * active_trigger = it->get(); // Trigger has to be activated. active_trigger->activate( context ); // Actual trigger should be stored separatelly. if( m_last_non_activated_trigger ) { // Actual trigger should be moved to the end of triggers list. std::swap( *it, m_triggers[ m_last_non_activated_trigger ] ); --m_last_non_activated_trigger; } ++m_triggers_activated; if( active_trigger->requires_completion() ) { ++m_triggers_to_completion; // Actual trigger should be returned in result token. result = token_t{ active_trigger }; } // Now the new state of the step can be calculated. if( m_triggers_activated == m_triggers_to_activate ) { // All expected triggers are activated. // New state should be `active` or `completed`. change_status( (0u != m_triggers_to_completion) ? status_t::active : status_t::completed ); } return result; } }; /*! * \brief An interface for object that will unfreeze all registered * agents when testing scenario starts. * * It is necessary for testing scenarios to keep all registered agents in * frozen state. Those agents should be present in the Environment but should * not receive nor handle messages. It is done by a special implementation of * event queues and event_queue_hook. * * But at some moment all frozen agents should become unfrozen. This moment is * known only for testing scenario. It means that testing scenario should * issue a command to unfreeze all frozen agents. * * This interface allows testing scenario to unfreeze agents but hides the * details of this procedure. * * \since v.5.5.24 */ class agent_unfreezer_t { agent_unfreezer_t( const agent_unfreezer_t & ) = delete; agent_unfreezer_t & operator=( const agent_unfreezer_t & ) = delete; agent_unfreezer_t( agent_unfreezer_t && ) = delete; agent_unfreezer_t & operator=( agent_unfreezer_t && ) = delete; public : agent_unfreezer_t() = default; virtual ~agent_unfreezer_t() = default; //! Issue a command to unfreeze all frozen agents. virtual void unfreeze() noexcept = 0; }; /*! * \brief The actual implementation of testing scenario. * * \since v.5.5.24 */ class real_scenario_t final : public abstract_scenario_t { private : //! Object lock. /*! * \note * It is mutable because it has to be used in a const method. */ mutable std::mutex m_lock; //! Condition variable for waiting completion of the scenario. std::condition_variable m_completion_cv; //! The current state of the scenario. scenario_status_t m_status = scenario_status_t::not_started; //! Scenario's steps. /*! * Can be empty. */ std::vector< step_unique_ptr_t > m_steps; //! Set of active step those are not completed yet. /*! * If a step switches from preactivated state to the completed * without staying in active state then this step won't go * to this set. */ std::set< abstract_scenario_step_t * > m_active_steps; //! Index of the current preactivated step. std::size_t m_waiting_step_index = 0u; //! Type of container for holding stored state names. /*! * The key consists of step_name and tag_name. */ using state_name_map_t = std::map< std::pair< std::string, std::string >, std::string >; //! Type of container for holding stored inspection results for messages. /*! * The key consists of step_name and tag_name. */ using inspection_result_map_t = std::map< std::pair< std::string, std::string >, std::string >; //! Container for holding stored state names. state_name_map_t m_stored_states; //! Container for holding stored inspection results for messages. /*! * \since v.5.8.3 */ inspection_result_map_t m_stored_inspection_results; //! Unfreezer for registered agents. /*! * \note Will receive an actual value later. */ agent_unfreezer_t * m_unfreezer{}; public : real_scenario_t() = default; //! Set the unfreezer for registered agents. /*! * \attention * This method must be called before start of the scenario. */ void setup_unfreezer( agent_unfreezer_t & unfreezer ) noexcept { m_unfreezer = &unfreezer; } [[nodiscard]] step_definition_proxy_t define_step( nonempty_name_t step_name ) override { std::lock_guard< std::mutex > lock{ m_lock }; if( scenario_status_t::not_started != m_status ) SO_5_THROW_EXCEPTION( rc_unable_to_define_new_step, "new testing scenario step can be defined only when " "scenario is not started yet" ); m_steps.emplace_back( std::make_unique< real_scenario_step_t >( step_name.giveout_value() ) ); return { m_steps.back().get() }; } [[nodiscard]] scenario_result_t result() const noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; if( scenario_status_t::completed == m_status ) return { scenario_status_t::completed }; else return { m_status, describe_current_state() }; } void run_for( std::chrono::steady_clock::duration run_time ) override { std::unique_lock< std::mutex > lock{ m_lock }; if( scenario_status_t::not_started == m_status ) { // Note. There is a trick: unfreezing of agents is performed // when scenario is locked. It means that event handlers of // dispatched messages will wait while that method completes. m_unfreezer->unfreeze(); if( m_steps.empty() ) m_status = scenario_status_t::completed; else { m_status = scenario_status_t::in_progress; preactivate_current_step(); ::so_5::details::wait_for_big_interval( lock, m_completion_cv, run_time, [this]{ return scenario_status_t::completed == m_status; }); if( scenario_status_t::completed != m_status ) m_status = scenario_status_t::timed_out; } } } [[nodiscard]] token_t pre_handler_hook( const incident_info_t & info, const message_ref_t & incoming_msg ) noexcept override { token_t result; std::lock_guard< std::mutex > lock{ m_lock }; // There can be cases when pre_handler_hook is called // when testing scenario is already finished. if( scenario_status_t::in_progress == m_status && m_waiting_step_index < m_steps.size() ) { result = react_on_pre_handler_hook( info, incoming_msg ); } return result; } void post_handler_hook( token_t token ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; // There can be cases when post_handler_hook is called // when testing scenario is already finished. // Also we can ignore that event if token has no valid info inside. if( scenario_status_t::in_progress == m_status && token.valid() ) { // There is a waiting step for that post_handler_hook // should be called. auto & step_to_check = token.activated_step(); step_to_check.post_handler_hook( make_accessor(), token.step_token() ); if( abstract_scenario_step_t::status_t::completed == step_to_check.status() ) { m_active_steps.erase( &step_to_check ); check_scenario_completion(); } } } void no_handler_hook( const incident_info_t & info, const message_ref_t & incoming_msg ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; // There can be cases when no_handler_hook is called // when testing scenario is already finished. if( scenario_status_t::in_progress == m_status && m_waiting_step_index < m_steps.size() ) { react_on_no_handler_hook( info, incoming_msg ); } } void store_state_name( const scenario_in_progress_accessor_t & /*accessor*/, const abstract_scenario_step_t & step, const std::string & tag, const std::string & state_name ) override { m_stored_states[ std::make_pair(step.name(), tag) ] = state_name; } void store_msg_inspection_result( const scenario_in_progress_accessor_t & /*accessor*/, const abstract_scenario_step_t & step, const std::string & tag, const std::string & inspection_result ) override { m_stored_inspection_results[ std::make_pair(step.name(), tag) ] = inspection_result; } [[nodiscard]] std::string stored_state_name( const std::string & step_name, const std::string & tag ) const override { std::lock_guard< std::mutex > lock{ m_lock }; if( scenario_status_t::completed != m_status ) SO_5_THROW_EXCEPTION( rc_scenario_must_be_completed, "scenario must be completed before call to " "stored_state_name()" ); const auto it = m_stored_states.find( std::make_pair(step_name, tag) ); if( it == m_stored_states.end() ) SO_5_THROW_EXCEPTION( rc_stored_state_name_not_found, "unable to find stored state name for <" + step_name + "," + tag + ">" ); return it->second; } [[nodiscard]] bool has_stored_state_name( const std::string & step_name, const std::string & tag ) const override { std::lock_guard< std::mutex > lock{ m_lock }; if( scenario_status_t::completed != m_status ) SO_5_THROW_EXCEPTION( rc_scenario_must_be_completed, "scenario must be completed before call to " "stored_state_name()" ); return m_stored_states.end() != m_stored_states.find( std::make_pair(step_name, tag) ); } [[nodiscard]] std::string stored_msg_inspection_result( const std::string & step_name, const std::string & tag ) const override { std::lock_guard< std::mutex > lock{ m_lock }; if( scenario_status_t::completed != m_status ) SO_5_THROW_EXCEPTION( rc_scenario_must_be_completed, "scenario must be completed before call to " "stored_msg_inspection_result()" ); const auto it = m_stored_inspection_results.find( std::make_pair(step_name, tag) ); if( it == m_stored_inspection_results.end() ) SO_5_THROW_EXCEPTION( rc_stored_msg_inspection_result_not_found, "unable to find stored msg inspection result for <" + step_name + "," + tag + ">" ); return it->second; } [[nodiscard]] bool has_stored_msg_inspection_result( const std::string & step_name, const std::string & tag ) const override { std::lock_guard< std::mutex > lock{ m_lock }; if( scenario_status_t::completed != m_status ) SO_5_THROW_EXCEPTION( rc_scenario_must_be_completed, "scenario must be completed before call to " "has_stored_msg_inspection_result()" ); return m_stored_inspection_results.end() != m_stored_inspection_results.find( std::make_pair(step_name, tag) ); } private : void preactivate_current_step() { m_steps[ m_waiting_step_index ]->preactivate(); } [[nodiscard]] token_t react_on_pre_handler_hook( const incident_info_t & info, const message_ref_t & incoming_msg ) noexcept { token_t result; // pre_handler_hook on the current waiting step must be called. auto & step_to_check = *(m_steps[ m_waiting_step_index ]); auto step_token = step_to_check.pre_handler_hook( make_accessor(), info, incoming_msg ); if( step_token.valid() ) // Because step's token is not NULL, we should return // our valid token. // This is the case when token is necessary for the // subsequent post_handler_hook() call. result = token_t{ &step_to_check, std::move(step_token) }; // The step can change its status... switch( step_to_check.status() ) { case abstract_scenario_step_t::status_t::active : // The step is activated and we need to wait its completion. m_active_steps.insert( &step_to_check ); switch_to_next_step_if_possible(); break; case abstract_scenario_step_t::status_t::completed : // There is no need to wait the completion of the step. switch_to_next_step_if_possible(); check_scenario_completion(); break; case abstract_scenario_step_t::status_t::passive: break; case abstract_scenario_step_t::status_t::preactivated: break; } return result; } void react_on_no_handler_hook( const incident_info_t & info, const message_ref_t & incoming_msg ) noexcept { // no_handler_hook on the current waiting step must be called. auto & step_to_check = *(m_steps[ m_waiting_step_index ]); step_to_check.no_handler_hook( make_accessor(), info, incoming_msg ); // The step can change its status... switch( step_to_check.status() ) { case abstract_scenario_step_t::status_t::active : // The step is activated and we need to wait its completion. m_active_steps.insert( &step_to_check ); switch_to_next_step_if_possible(); break; case abstract_scenario_step_t::status_t::completed : // There is no need to wait the completion of the step. switch_to_next_step_if_possible(); check_scenario_completion(); break; case abstract_scenario_step_t::status_t::passive: break; case abstract_scenario_step_t::status_t::preactivated: break; } } void switch_to_next_step_if_possible() { ++m_waiting_step_index; if( m_waiting_step_index < m_steps.size() ) { preactivate_current_step(); } } //! Checks the possibility of completion of the scenario and //! completes scenario if it is possible. void check_scenario_completion() noexcept { if( m_active_steps.empty() && m_waiting_step_index >= m_steps.size() ) { m_status = scenario_status_t::completed; m_completion_cv.notify_all(); } } [[nodiscard]] std::string describe_current_state() const { std::ostringstream ss; if( m_waiting_step_index < m_steps.size() ) ss << "preactivated step:" << m_steps[ m_waiting_step_index ]->name(); else ss << "all steps handled"; ss << ";"; if( !m_active_steps.empty() ) { ss << " active steps:{"; bool need_comma = false; for( auto * s : m_active_steps ) { if( need_comma ) ss << ", "; else need_comma = true; ss << s->name(); } ss << "};"; } if( !m_stored_states.empty() ) { ss << " stored states:{"; bool need_comma = false; for( auto & p : m_stored_states ) { if( need_comma ) ss << ", "; else need_comma = true; ss << "[" << p.first.first << ", " << p.first.second << "]=" << p.second; } ss << "};"; } return ss.str(); } }; // // msg_catcher_map_layer_t // namespace mbox_receives_msg_impl { msg_catcher_map_layer_t::msg_catcher_map_layer_t() = default; msg_catcher_map_layer_t::~msg_catcher_map_layer_t() noexcept = default; } /* namespace mbox_receives_msg_impl */ } /* namespace details */ namespace impl { namespace details = so_5::experimental::testing::v1::details; /*! * \brief A special envelope that is necessary for testing scenarios. * * Testing scenario should know about every handled or rejected message * delivered to agents. To make this possible a trick with enveloped * messages is used: every message is enveloped into a special envelope. * This envelope has a reference to testing scenario and informs this * scenario when message is handled by a receiver. * * But the situation with rejected messages is not that simple. * We assume that if access_hook() wasn't called then message was rejected. * Because of that envelope has a boolean flag m_handled that is set * in access_hook(). This flag is analyzed in the destructor and, if it * is not set, we assume that message was rejected. * * In theory there can be cases when envelope is destroyed without delivery * to an agent. But for testing scenarios we just ignore those case. * * \since v.5.5.24 */ class special_envelope_t final : public so_5::enveloped_msg::envelope_t { //! Delivery result for a message inside the envelope. //! //! If the message is another envelope then there could be //! case when the actual message will be suppressed by a nested envelope. //! This case has to be handled separatelly: in that case message //! isn't handled nor delivered. //! //! \since v.5.8.3 enum class delivery_result_t { //! Message ignored by the destination agent. ignored, //! Message delivered to the destination agent. delivered, //! Message suppressed by a nested envelope. suppressed_by_envelope }; //! A testing scenario for that envelope. outliving_reference_t< details::abstract_scenario_t > m_scenario; //! Information about enveloped message. details::incident_info_t m_demand_info; //! Enveloped message. message_ref_t m_message; //! Was this message handled by a receiver? delivery_result_t m_delivery_result{ delivery_result_t::ignored }; //! A special invoker to be used to call pre_handler_hook. class pre_handler_hook_invoker_t final : public handler_invoker_t { //! Owner of this invoker. outliving_reference_t< special_envelope_t > m_owner; //! Invoker to be used to call the actual event handler. outliving_reference_t< handler_invoker_t > m_actual_invoker; public: //! Intializing constructor. pre_handler_hook_invoker_t( //! Owner of this invoker. outliving_reference_t< special_envelope_t > owner, //! Invoker to be used to call the actual event handler. outliving_reference_t< handler_invoker_t > actual_invoker ) : m_owner{ owner } , m_actual_invoker{ actual_invoker } {} void invoke( const payload_info_t & payload ) noexcept override { // We must get token... auto token = m_owner.get() .m_scenario.get() .pre_handler_hook( m_owner.get().m_demand_info, payload.message() ); // Actual event handler has to be called. m_actual_invoker.get().invoke( payload ); // And now the token must be passed back. m_owner.get().m_scenario.get().post_handler_hook( token ); } }; //! A special invoker to be used to call no_handler_hook. class no_handler_invoker_t final : public handler_invoker_t { //! Owner of this invoker. outliving_reference_t< special_envelope_t > m_owner; public: //! Initializing constructor. explicit no_handler_invoker_t( outliving_reference_t< special_envelope_t > owner ) : m_owner{ owner } {} void invoke( const payload_info_t & payload ) noexcept override { m_owner.get().m_scenario.get().no_handler_hook( m_owner.get().m_demand_info, payload.message() ); } }; //! Special handler invoker that tries to extract the actual message. //! //! This invoker is necessary because the message may be enveloped //! more than once and one of the nested envelopes may suppress it. //! //! \since v.5.8.3 class invoker_for_message_extraction_t final : public so_5::enveloped_msg::handler_invoker_t { //! Handler invoker that has to be used for extracted message. outliving_reference_t< so_5::enveloped_msg::handler_invoker_t > m_invoker; //! Context for accessing enveloped message. const so_5::enveloped_msg::access_context_t m_access_context; //! Has the message actually been handled? bool m_handled{ false }; public: //! Initializing constructor. invoker_for_message_extraction_t( //! Handler invoker that has to be used if message is extracted. outliving_reference_t< so_5::enveloped_msg::handler_invoker_t > invoker, //! Context for accessing enveloped message. so_5::enveloped_msg::access_context_t access_context ) : m_invoker{ invoker } , m_access_context{ access_context } {} void invoke( const so_5::enveloped_msg::payload_info_t & payload ) noexcept override { const auto msg_kind = message_kind( payload.message() ); switch( msg_kind ) { case message_t::kind_t::signal : [[fallthrough]]; case message_t::kind_t::classical_message : [[fallthrough]]; case message_t::kind_t::user_type_message : m_handled = true; m_invoker.get().invoke( payload ); break; case message_t::kind_t::enveloped_msg : { // Do recurvise call. // Value for was_handled will be detected in the // nested call. auto & nested_envelope = so_5::enveloped_msg::impl::message_to_envelope( payload.message() ); nested_envelope.access_hook( m_access_context, *this ); } break; } } //! Has the message actually been handled? [[nodiscard]] bool handled() const noexcept { return m_handled; } }; public : //! Initializing constructor. special_envelope_t( outliving_reference_t< details::abstract_scenario_t > scenario, const execution_demand_t & demand ) : m_scenario( scenario ) , m_demand_info( demand.m_receiver, demand.m_msg_type, demand.m_mbox_id ) , m_message( demand.m_message_ref ) {} ~special_envelope_t() noexcept override { // If message wasn't handled we assume that agent rejected // this message. if( delivery_result_t::ignored == m_delivery_result ) { // But we have to check that message isn't suppressed by // a nested envelope. no_handler_invoker_t invoker_no_handler_hook{ outliving_mutable( *this ) }; invoker_for_message_extraction_t special_invoker{ outliving_mutable( invoker_no_handler_hook ), access_context_t::inspection }; // If the message is not suppressed then no_handler_hook // will be called by invoker_no_handler_hook. special_invoker.invoke( payload_info_t{ m_message } ); } } void access_hook( //! Why this hook is called. access_context_t context, //! Proxy object which can call an actual event handler. handler_invoker_t & invoker ) noexcept override { switch( context ) { case access_context_t::handler_found : { // This invoker will call pre/post_handler_hooks and // the actual event-handler. pre_handler_hook_invoker_t pre_handler_hook_invoker{ outliving_mutable( *this ), outliving_mutable( invoker ) }; // The message can be envelope itself. // And the envelope can hide the message and does not return it. // This case has to be handled. invoker_for_message_extraction_t special_invoker{ outliving_mutable( pre_handler_hook_invoker ), context }; // The special invoker will do the back call of // call_handler_invoker method. special_invoker.invoke( payload_info_t{ m_message } ); // If we don't change m_handled flag there the // destructor will call no_handler_hook(). m_delivery_result = special_invoker.handled() ? delivery_result_t::delivered : delivery_result_t::suppressed_by_envelope; } break; case access_context_t::transformation : invoker.invoke( payload_info_t{ m_message } ); break; case access_context_t::inspection : invoker.invoke( payload_info_t{ m_message } ); break; } } }; /*! * \brief A mode of work for special_event_queue. * * Special event queue can work in two modes: * - buffered, when all messages must be stored in a local queue; * - direct, when all messages go directly to the original queue. * * This enumeration describes mode of operation for special_event_queue_t. * * \since v.5.5.24 */ enum class queue_mode_t { //! All messages must be stored locally. buffer, //! All messages should go to the original queue without buffering. direct }; /*! * \brief A special event_queue to be used for testing scenarios. * * \par Support for agents freeze * * If a testing scenario is not started yet all registered agents * should be in frozen state. It means that they are present in * the SObjectizer Environment, but do not receive nor handle any message * (even special message for invoking so_evt_start() method). * * To implement that special event_queue is created for every agent. * This event_queue works in two modes, described by * queue_mode_t enumeration. * * In queue_mode_t::buffer mode all messages are stored in local queue * and not passed to the actual event_queue created for agent by a dispatcher. * This way we prevent delivery of messages to agents. * * In queue_mode_t::direct mode all message are passed to the actual * event_queue directly, without storing them locally. In that mode * special event_queue works just as a proxy. * * Transfer of locally stored events is performed when mode is * switched from queue_mode_t::buffer to queue_mode_t::direct. * * \par Creation of special envelopes * * There is yet another task for special event_queue: all ordinary * messages/signals, service requests and enveloped messages must be * wrapped into a special envelope. This is done in push() method. * * \since v.5.5.24 */ class special_event_queue_t final : public event_queue_t { //! Object lock. /*! * All operations will be performed under that lock. */ std::mutex m_lock; //! Testing scenario for that this queue was created. outliving_reference_t< details::abstract_scenario_t > m_scenario; //! Original event_queue. outliving_reference_t< event_queue_t > m_original_queue; //! The current mode of operation. queue_mode_t m_mode; //! Local storage for demands to be used in buffered mode. std::vector< execution_demand_t > m_buffer; static bool is_ordinary_demand( const execution_demand_t & demand ) noexcept { return agent_t::get_demand_handler_on_message_ptr() == demand.m_demand_handler || agent_t::get_demand_handler_on_enveloped_msg_ptr() == demand.m_demand_handler; } void push_to_queue( execution_demand_t demand ) { std::lock_guard< std::mutex > lock{ m_lock }; if( queue_mode_t::buffer == m_mode ) m_buffer.push_back( std::move(demand) ); else m_original_queue.get().push( std::move(demand) ); } public : special_event_queue_t( outliving_reference_t< details::abstract_scenario_t > scenario, outliving_reference_t< event_queue_t > original_queue, queue_mode_t queue_mode ) : m_scenario{ scenario } , m_original_queue{ original_queue } , m_mode{ queue_mode } {} void push( execution_demand_t demand ) override { if( !is_ordinary_demand( demand ) ) // Demand must go into the original queue without transformations. push_to_queue( std::move(demand) ); else { // Original message must be wrapped into a special // envelope and original demand must be modified. message_ref_t new_env{ std::make_unique< special_envelope_t >( m_scenario, std::cref(demand) ) }; demand.m_message_ref = std::move(new_env); demand.m_demand_handler = agent_t::get_demand_handler_on_enveloped_msg_ptr(); push_to_queue( std::move(demand) ); } } void push_evt_start( execution_demand_t demand ) override { // Demand must go into the original queue without transformations. push_to_queue( std::move(demand) ); } void push_evt_finish( execution_demand_t demand ) noexcept override { // Demand must go into the original queue without transformations. push_to_queue( std::move(demand) ); } void switch_to_direct_mode() { // Cleanup of buffer should be performed when // object is unlocked. decltype(m_buffer) tmp; { std::lock_guard< std::mutex > lock{ m_lock }; m_mode = queue_mode_t::direct; for( auto & d : m_buffer ) m_original_queue.get().push( std::move(d) ); using std::swap; swap( tmp, m_buffer ); } } }; /*! * \brief A special stop guard that unfreezes all agents if this * is not done yet. * * There can be cases when testing environment is started, some * agents are registered, but testing scenario is not started. For example * the work of test case is cancelled by an exception. * * In those cases testing environment should be correctly stopped. * All agents have to be deregistered. We can do that only if they * is not frozen. But they are frozen. * * This special stop_guard is intended to unfreeze all agents if * testing environment finishes its work without launching the * testing scenario. * * \since v.5.5.24 */ class stop_guard_for_unfreezer_t final : public stop_guard_t , public std::enable_shared_from_this< stop_guard_for_unfreezer_t > { outliving_reference_t< details::agent_unfreezer_t > m_unfreezer; outliving_reference_t< environment_t > m_env; public : stop_guard_for_unfreezer_t( outliving_reference_t< details::agent_unfreezer_t > unfreezer, outliving_reference_t< environment_t > env ) : m_unfreezer{ unfreezer } , m_env{ env } {} void stop() noexcept override { so_5::details::invoke_noexcept_code( [this] { // Agents should become unfrozen. m_unfreezer.get().unfreeze(); // We should remove itself to allow the environment // to continue shutdown operation. m_env.get().remove_stop_guard( shared_from_this() ); } ); } }; /*! * \brief A special event_queue_hook that creates instances of * special event_queue for testing scenario. * * This type is also an implementation of agent_unfreezer_t interface. * * To implement unfreezing of agents object of this types stores pointers * to all created event_queue in internal container. In unfreeze() * method all those queues are switched from buffered to direct mode. * * \since v.5.5.24 */ class special_event_queue_hook_t final : public event_queue_hook_t , public details::agent_unfreezer_t { //! Lock for this object. std::mutex m_lock; //! Mode of operation for new queues. queue_mode_t m_mode{ queue_mode_t::buffer }; //! Testing scenario for that this object is created. outliving_reference_t< details::abstract_scenario_t > m_scenario; //! List of all queues created before unfreeze was called. /*! * If unfreeze() was called then pointers to new event queues * are not stored in that list. * * \note * Items from this containers are not removed in on_unbind method. * We assume that agents can't be deregistered when all agents * are frozen. */ std::vector< special_event_queue_t * > m_created_queues; public : special_event_queue_hook_t( outliving_reference_t< details::abstract_scenario_t > scenario ) : m_scenario{ scenario } {} [[nodiscard]] event_queue_t * on_bind( agent_t * /*agent*/, event_queue_t * original_queue ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; auto sq = std::make_unique< special_event_queue_t >( m_scenario, outliving_mutable( *original_queue ), m_mode ); if( queue_mode_t::buffer == m_mode ) m_created_queues.push_back( sq.get() ); return sq.release(); } void on_unbind( agent_t * /*agent*/, event_queue_t * queue ) noexcept override { // Assume that queue is an instance of special_event_queue_t // and simply delete it. delete queue; } void unfreeze() noexcept override { // Mode will be switched under locked mutex. // But actual switching of queue operation mode // for already created queue will be performed with // unlocked mutex. decltype(m_created_queues) tmp; { std::lock_guard< std::mutex > lock{ m_lock }; m_mode = queue_mode_t::direct; using std::swap; swap( tmp, m_created_queues ); } // Now we can switch mode for all queues those were created // in buffered mode. for( auto * sq : tmp ) sq->switch_to_direct_mode(); } }; /*! * \brief A helper object for synchronization between helper worker where * testing environment is launched and user thread. * * We must stop work of testing_env_t constructor until * testing_env_t::tune_environment_on_start() finishes its work. * To do that constructor will wait on a future object for that * promise, and tune_environment_on_start() will set this promise. * * \since v.5.5.24 */ struct init_completed_data_t { std::promise m_completed; }; } /* namespace impl */ /*! * \brief Internal data for testing environment. * * \attention * real_scenario object must be declared before special_hook object. */ struct testing_env_t::internals_t { details::real_scenario_t m_scenario; impl::special_event_queue_hook_t m_special_hook; impl::init_completed_data_t m_init_completed; internals_t() : m_special_hook{ outliving_mutable(m_scenario) } { m_scenario.setup_unfreezer( m_special_hook ); } [[nodiscard]] static std::unique_ptr< internals_t > make() { return std::make_unique(); } }; namespace impl { void setup_special_queue_hook( outliving_reference_t< testing_env_t::internals_t > internals, environment_params_t & to ) { to.event_queue_hook( event_queue_hook_unique_ptr_t{ std::addressof( internals.get().m_special_hook ), event_queue_hook_t::noop_deleter } ); } [[nodiscard]] environment_params_t make_tuned_params( so_5::generic_simple_so_env_params_tuner_t env_params_tuner ) { environment_params_t result; env_params_tuner( result ); return result; } [[nodiscard]] environment_params_t make_special_params( outliving_reference_t< testing_env_t::internals_t > internals, environment_params_t && params ) { setup_special_queue_hook( internals, params ); // Special layer has to be added to the environment. using so_5::experimental::testing::v1::details::mbox_receives_msg_impl ::msg_catcher_map_layer_t; params.add_layer( std::make_unique< msg_catcher_map_layer_t >() ); return std::move(params); } } /* namespace impl */ // // scenario_proxy_t // scenario_proxy_t::scenario_proxy_t( outliving_reference_t< details::abstract_scenario_t > scenario ) : m_scenario{ scenario } {} step_definition_proxy_t scenario_proxy_t::define_step( nonempty_name_t step_name ) { return m_scenario.get().define_step( std::move(step_name) ); } scenario_result_t scenario_proxy_t::result() const { return m_scenario.get().result(); } void scenario_proxy_t::run_for( std::chrono::steady_clock::duration run_time ) { return m_scenario.get().run_for( run_time ); } std::string scenario_proxy_t::stored_state_name( const std::string & step_name, const std::string & tag ) const { return m_scenario.get().stored_state_name( step_name, tag ); } bool scenario_proxy_t::has_stored_state_name( const std::string & step_name, const std::string & tag ) const { return m_scenario.get().has_stored_state_name( step_name, tag ); } std::string scenario_proxy_t::stored_msg_inspection_result( const std::string & step_name, const std::string & tag ) const { return m_scenario.get().stored_msg_inspection_result( step_name, tag ); } bool scenario_proxy_t::has_stored_msg_inspection_result( const std::string & step_name, const std::string & tag ) const { return m_scenario.get().has_stored_msg_inspection_result( step_name, tag ); } // // testing_env_t // testing_env_t::testing_env_t() : testing_env_t{ environment_params_t{} } {} testing_env_t::testing_env_t( so_5::generic_simple_so_env_params_tuner_t env_params_tuner ) : testing_env_t{ impl::make_tuned_params( std::move(env_params_tuner) ) } {} testing_env_t::testing_env_t( environment_params_t && env_params ) : m_internals{ internals_t::make() } , m_sobjectizer{ [this]( environment_t & env ) { tune_environment_on_start( env ); }, impl::make_special_params( outliving_mutable( *m_internals ), std::forward(env_params) ) } { // We must wait completion of tune_environment_on_start(). wait_init_completion(); } testing_env_t::~testing_env_t() {} environment_t & testing_env_t::environment() const { return m_sobjectizer.environment(); } void testing_env_t::stop() { m_sobjectizer.stop(); } void testing_env_t::join() { m_sobjectizer.join(); } void testing_env_t::stop_then_join() { m_sobjectizer.stop_then_join(); } [[nodiscard]] scenario_proxy_t testing_env_t::scenario() noexcept { return { outliving_mutable(m_internals->m_scenario) }; } void testing_env_t::tune_environment_on_start( environment_t & env ) { // stop_guard for unfreezing agents must be installed before // an user will start work with the environment. env.setup_stop_guard( std::make_shared< impl::stop_guard_for_unfreezer_t >( outliving_mutable(m_internals->m_special_hook), outliving_mutable(env) ) ); // This action was done on separate thread. // The constructor of testing_env_t waits completion of // this method on another thread. // Do allow the constructor to complete its work. m_internals->m_init_completed.m_completed.set_value(); } void testing_env_t::wait_init_completion() { m_internals->m_init_completed.m_completed.get_future().wait(); } } /* namespace v1 */ } /* namespace testing */ } /* namespace experimental */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/experimental/testing/v1/all.hpp000066400000000000000000002237301523065210100234630ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Testing related stuff. * * \since v.5.5.24 */ #if !defined( SO_5_EXPERIMENTAL_TESTING_V1_ALL_HPP ) #define SO_5_EXPERIMENTAL_TESTING_V1_ALL_HPP #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { namespace experimental { namespace testing { inline namespace v1 { namespace details { // // Forward declarations // class abstract_scenario_step_t; class abstract_scenario_t; class scenario_in_progress_accessor_t; /*! * \brief A description of an event for testing scenario. * * Instances of that type will be passed to various hooks of * testing scenario and scenario's steps. * * \since v.5.5.24 */ struct incident_info_t final { //! Target of an event. const agent_t * m_agent; //! Type of message or signal. const std::type_index m_msg_type; //! ID of mbox from that message/signal was received. mbox_id_t m_src_mbox_id; incident_info_t( const agent_t * agent, const std::type_index & msg_type, mbox_id_t src_mbox_id ) : m_agent( agent ) , m_msg_type( msg_type ) , m_src_mbox_id( src_mbox_id ) {} }; /*! * \brief What happened with source of an event. * * When a message or signal is delivered to an agent that message/signal * can be either handled or ignored. Some scenario triggers are activated * when source message/signal is handled, some are activated when incident * is ignored. This enumeration can be used for selection of that cases. * * \since v.5.5.24 */ enum class incident_status_t { //! Message or signal has been handled. handled, //! Message or signal has been ignored. ignored }; /*! * \brief Description of context on that a trigger is completed. * * Some triggers should do some actions on completion. Because * of that triggers should have access to scenario's step or even to * the whole scenario. This type holds a reference to objects those * can be accessible to a trigger. * * \note * The references inside that object are valid only for small amount * of time. They shouldn't be used after completion of trigger. * * \since v.5.5.24 */ struct trigger_completion_context_t { const scenario_in_progress_accessor_t & m_scenario_accessor; abstract_scenario_step_t & m_step; }; /*! * \brief Description of context on that an attempt to activate a trigger * is performing. * * Some triggers should do some actions on activation. Because * of that triggers should have access to scenario's step or even to * the whole scenario. This type holds a reference to objects those * can be accessible to a trigger. * * \note * The references inside that object are valid only for small amount * of time. They shouldn't be used after completion of trigger. * * \since v.5.8.3 */ struct trigger_activation_context_t { //! Access to the running scenario. const scenario_in_progress_accessor_t & m_scenario_accessor; //! The current step for that activation is being performed. abstract_scenario_step_t & m_step; //! Incoming message or signal. /*! * \note * This will be a nullptr in case of a signal. */ const message_ref_t & m_incoming_msg; }; /*! * \brief An implementation of trigger for scenario's step. * * In the current version trigger is implemented as a concrete class (just for * simplicity), but in the future versions it could be an abstract interface. * * \since v.5.5.24 */ class SO_5_TYPE trigger_t final { public : //! Type of functor to be used on the completion of the trigger. using completion_function_t = std::function< void(const trigger_completion_context_t &) /*noexcept*/ >; //! Type of functor to be used on the activation of the trigger. /*! * \since v.5.8.3 */ using activation_function_t = std::function< void(const trigger_activation_context_t &) /*noexcept*/ >; private : //! What should happen with initial message/signal. const incident_status_t m_incident_status; //! A reference to the target agent. /*! * Note that this reference should be used with care. * In complex scenarios an agent can be deregistered and * this reference can point to free memory or reallocated * for another agent memory. * * Before access to that reference it is necessary to check * m_target_id field. */ const agent_t & m_target_agent; //! The unique ID of target's direct mbox. /*! * ID of mbox is a unique value. ID is not reused even if * the agent is destroyed and its memory is reallocated for * another agent. */ const mbox_id_t m_target_id; //! Type of message/signal to activate the trigger. const std::type_index m_msg_type; //! ID of source mbox of message/signal to activate the trigger. const mbox_id_t m_src_mbox_id; //! Optional function for completion of the trigger. /*! * If m_completion is empty then there is no need to * separate completion action for the trigger and trigger * become completed just after activation. */ completion_function_t m_completion; //! Optional function for activation of the trigger. /*! * \since v.5.8.3 */ activation_function_t m_activation; // Note. These are required by Clang. trigger_t( const trigger_t & ) = delete; trigger_t( trigger_t && ) = delete; public : //! Initializing constructor. trigger_t( incident_status_t incident_status, const agent_t & target, std::type_index msg_type, mbox_id_t src_mbox_id ); ~trigger_t(); //! Get the reference of the target agent. /*! * \attention * This method should be used with care because if target agent * is deregistered then a dangling reference will be returned. */ [[nodiscard]] const agent_t & target_agent() const noexcept; //! Setter for completion function. /*! * @note * If a completon function is already set then the old and new functions * are joined. It means that the old function will be called first and * then the new function. */ void set_completion( completion_function_t fn ); //! Setter for activation function. /*! * \note * If an activation function is already set then the old and new functions * are joined. It means that the old function will be called first and * then the new function. * * \since v.5.8.3 */ void set_activation( activation_function_t fn ); //! Check for activation of the trigger. /*! * \retval true Trigger is activated. * \retval false Trigger is not activated and information about * the event can be used for checking for other triggers. */ [[nodiscard]] bool check( //! What happened with message/signal? const incident_status_t incident_status, //! Informationa about the incident. const incident_info_t & info ) const noexcept; //! Does this trigger require separate completion action? [[nodiscard]] bool requires_completion() const noexcept; //! Do activation of the trigger. /*! * If there is an activation function it will be called. * * \since v.5.8.3 */ void activate( const trigger_activation_context_t & context ) noexcept; //! Do completion of a trigger. void complete( const trigger_completion_context_t & context ) noexcept; }; /*! * \brief An alias for unique_ptr of trigger. * * \since v.5.5.24 */ using trigger_unique_ptr_t = std::unique_ptr< trigger_t >; /*! * \brief An alias for type of tigger's container. * * \since v.5.5.24 */ using trigger_container_t = std::vector< trigger_unique_ptr_t >; /*! * \brief A special data class with partial info for a new trigger. * * This data class contains a type of message/signal and optional * mbox_id for source mbox. If mbox_id is not specified then the direct * mbox of a target agent will be used as source mbox. * * \since v.5.5.24 */ template< incident_status_t Status > struct trigger_source_t final { std::type_index m_msg_type; optional m_src_mbox_id; trigger_source_t( std::type_index msg_type, mbox_id_t src_mbox_id) : m_msg_type( std::move(msg_type) ) , m_src_mbox_id( src_mbox_id ) {} trigger_source_t( std::type_index msg_type ) : m_msg_type( std::move(msg_type) ) {} }; /*! * \brief A special data object for case of store-state-name completion action. * * \since v.5.5.24 */ struct store_agent_state_name_t { //! Name of tag for store-state-name action. std::string m_tag; }; /*! * \brief A special data object for case when a step should be completed only * after returning from the event handler. * * \since v.5.8.3 */ struct wait_event_handler_completion_t { // Yes, it's an empty type. }; /*! * \brief A special data object for case when a message inspector has * to be used on an incoming message. * * \since v.5.8.3 */ struct store_msg_inspection_result_t { //! Name of a tag for store-msg-inspection action. std::string m_tag; //! Inspector for a message. std::function< std::string(const message_ref_t &) > m_inspector; }; /*! * \brief An interface of step's constraints. * * \since v.5.5.24 */ class constraint_t { public : constraint_t() = default; virtual ~constraint_t() noexcept = default; constraint_t( const constraint_t & ) = delete; constraint_t & operator=( const constraint_t & ) = delete; constraint_t( constraint_t && ) = delete; constraint_t & operator=( constraint_t && ) = delete; //! Hook for step preactivation. /*! * This hook will be called when step is preactivated. * Constraint object can do some initial actions here. For * example the current timestamp can be store. Or some resources * can be acquired. */ virtual void start() noexcept = 0; //! Hook for step completion. /*! * This hook will be called when step is completed. * Constraint object can do some cleanup actions here. For example * resources acquired in start() method can be released. */ virtual void finish() noexcept = 0; //! Check for fulfillment of constraint. /*! * \retval true If constraint fulfilled. * \retval false If constraint is not fulfilled and an incident * should be ignored. */ [[nodiscard]] virtual bool check( //! What happened with message/signal? const incident_status_t incident_status, //! Context for that event. const incident_info_t & info ) const noexcept = 0; }; /*! * \brief An alias for unique_ptr of constraint. * * \since v.5.5.24 */ using constraint_unique_ptr_t = std::unique_ptr< constraint_t >; /*! * \brief An alias for container of constraints. * * \since v.5.5.24 */ using constraint_container_t = std::vector< constraint_unique_ptr_t >; /*! * \brief Implementation of 'not_before' constraint. * * \since v.5.5.24 */ class not_before_constraint_t final : public constraint_t { //! Value to be used. const std::chrono::steady_clock::duration m_pause; //! Time point of step preactivation. /*! * Receives value only in start() method. */ std::chrono::steady_clock::time_point m_started_at; public : not_before_constraint_t( std::chrono::steady_clock::duration pause ) : m_pause( pause ) {} void start() noexcept override { m_started_at = std::chrono::steady_clock::now(); } void finish() noexcept override { /* nothing to do */ } [[nodiscard]] bool check( const incident_status_t /*incident_status*/, const incident_info_t & /*info*/ ) const noexcept override { return m_started_at + m_pause <= std::chrono::steady_clock::now(); } }; /*! * \brief Implementation of 'not_after' constraint. * * \since v.5.5.24 */ class not_after_constraint_t final : public constraint_t { //! Value to be used. const std::chrono::steady_clock::duration m_pause; //! Time point of step preactivation. /*! * Receives value only in start() method. */ std::chrono::steady_clock::time_point m_started_at; public : not_after_constraint_t( std::chrono::steady_clock::duration pause ) : m_pause( pause ) {} void start() noexcept override { m_started_at = std::chrono::steady_clock::now(); } void finish() noexcept override { /* nothing to do */ } [[nodiscard]] bool check( const incident_status_t /*incident_status*/, const incident_info_t & /*info*/ ) const noexcept override { return m_started_at + m_pause > std::chrono::steady_clock::now(); } }; /*! * \brief An alias for type of step's preactivation action. * * \since v.5.5.24 */ using preactivate_action_t = std::function< void() /*noexcept*/ >; /*! * \brief An interface of testing scenario step. * * This interface is described in a public header file just for * definition of step_definition_proxy_t class. But abstract_scenario_step_t * is an internal and implementation-specific type, please don't use * it in end-user code. * * \since v.5.5.24 */ class SO_5_TYPE abstract_scenario_step_t { public : //! Status of step. enum class status_t { //! Step is not preactivated yet. //! Step can be changed while it in this state. passive, //! Step is preactivated. //! It means that this step is the current step in testing //! scenario now. But not all required triggers are activated //! yet. preactivated, //! Step is activated. //! It means that all required triggers are activated, but //! some triggers wait for completion actions. active, //! Step is completed. //! It means that all required triggers were activated and //! all of them were completed. completed }; /*! * \brief Type of token returned from pre-handler-hook. * * This token can be in valid or invalid states. If it is in * valid state then it should be passed as is to * abstract_scenario_step_t::post_handler_hook() method. * * \since v.5.5.24 */ class token_t final { //! Activated trigger. /*! * Can be null. It means that there was no activated triggers * at pre_handler_hook() method. And token is in invalid state. * * This pointer can also be null if trigger was activated but * wasn't require completion. It means that trigger switched * to completed state and there is no need in separate * completion action. * * If this pointer is not null then post_handler_hook() * should be called for scenario step. */ trigger_t * m_trigger; public: token_t() noexcept : m_trigger( nullptr ) {} token_t( trigger_t * trigger ) noexcept : m_trigger( trigger ) {} [[nodiscard]] bool valid() const noexcept { return m_trigger != nullptr; } //! Get a reference to activated trigger. /*! * This method should be called only if token is in valid * state. */ [[nodiscard]] trigger_t & trigger() const noexcept { return *m_trigger; } }; // Note. These are required by Clang compiler. abstract_scenario_step_t() = default; abstract_scenario_step_t( const abstract_scenario_step_t & ) = delete; abstract_scenario_step_t & operator=( const abstract_scenario_step_t & ) = delete; abstract_scenario_step_t( abstract_scenario_step_t && ) = delete; abstract_scenario_step_t & operator=( abstract_scenario_step_t && ) = delete; virtual ~abstract_scenario_step_t() = default; //! Get the name of the step. [[nodiscard]] virtual const std::string & name() const noexcept = 0; //! Perform preactivation of the step. /*! * Preactivation means that the step becomes the current step * of the scenario and all events will go to * pre_handler_hook(), post_handler_hook() and * no_handler_hook() of that step. * * This method should call all preactivation actions passed to * the step via add_peactivate_action() method. */ virtual void preactivate() noexcept = 0; //! Hook that should be called before invocation of event-handler. /*! * This hook should be called before invocation of any event-handler * for ordinary message, service request or enveloped message. * * The step should update its status inside that method. * * If a valid token is returned then this token should be passed * to subsequent call to post_handler_hook() method. * * \note * The \a incoming_msg may be nullptr in case of a signal. */ [[nodiscard]] virtual token_t pre_handler_hook( const scenario_in_progress_accessor_t & scenario_accessor, const incident_info_t & info, const message_ref_t & incoming_msg ) noexcept = 0; //! Hook that should be called just after completion of event-handler. /*! * If previous call to pre_handler_hook() returned a valid * token object then this hook should be called just after * completion of event-handler. */ virtual void post_handler_hook( const scenario_in_progress_accessor_t & scenario_accessor, token_t token ) noexcept = 0; //! Hook that should be called if there is no event-handler for //! a message or service request. /*! * The step should update its status inside that method. * * \note * The \a incoming_msg may be nullptr in case of a signal. */ virtual void no_handler_hook( const scenario_in_progress_accessor_t & scenario_accessor, const incident_info_t & info, const message_ref_t & incoming_msg ) noexcept = 0; //! Get the current status of the step. [[nodiscard]] virtual status_t status() const noexcept = 0; //! Add another preactivation action. /*! * Can be called several times. New action will be added to the end * of list of preactivation actions. */ virtual void add_preactivate_action( preactivate_action_t action ) = 0; //! Setup triggers for the step. /*! * It is assumed that this method will be called only once per step. * If it is called several times the new information will replace * the old one. * * \note * Value of \a triggers_to_activate has sence for case of * step_definition_proxy_t::when_any() method: there can be * several triggers in \a triggers container, but only one of * them has to be triggered to activate the step. */ virtual void setup_triggers( trigger_container_t triggers, std::size_t triggers_to_activate ) noexcept = 0; //! Setup constraints for the step. /*! * It is assumed that this method will be called only once per * step. If it is called several times the new information will * replace the old one. */ virtual void setup_constraints( constraint_container_t constraints ) noexcept = 0; }; /*! * \brief An alias for unique_ptr of scenario-step. * * \since v.5.5.24 */ using step_unique_ptr_t = std::unique_ptr< abstract_scenario_step_t >; /*! * \brief A helper class for holding unique_ptr to a trigger while * trigger is being configured. * * \note * This class is Movable, but not Copyable. * * \since v.5.5.24 */ template< incident_status_t Status > class trigger_holder_t final { trigger_unique_ptr_t m_trigger; public : trigger_holder_t( trigger_unique_ptr_t trigger ) noexcept : m_trigger( std::move(trigger) ) {} trigger_holder_t( trigger_holder_t && ) noexcept = default; trigger_holder_t & operator=( trigger_holder_t && ) noexcept = default; trigger_holder_t( const trigger_holder_t & ) = delete; trigger_holder_t & operator=( const trigger_holder_t & ) = delete; //! Get the trigger object from the holder. /*! * \note * Holder becomes empty and should not be used anymore * (except for assigning a new value); */ trigger_unique_ptr_t giveout_trigger() noexcept { return std::move(m_trigger); } }; } /* namespace details */ /*! * \brief A special object that should be used for definition of a step * of a testing scenario. * * Usage example: * \code * using namespace so_5::experimental::testing; * testing_env_t env; * * so_5::agent_t * test_agent = ...; * so_5::agent_t * another_agent = ...; * * auto scenario = env.scenario(); * * // Create a simple step. It triggers when agent receives a message * // from its direct mbox. * scenario.define_step("one").when(*test_agent & reacts_to()); * * // Create a step with constraint: a message should be received only * // after a 15ms from preactivation of this step. * scenario.define_step("two") * .constraints(not_before(std::chrono::milliseconds(15))) * .when(*test_agent & reacts_to(some_mbox)); * * // Create a step with initial actions: several messages will be sent * // during preactivation of this step. * // There are also two constraints: a time range for receiving expected * // message. * scenario.define_step("three") * .impact(first_target) * .impact(second_target, arg1, arg2, arg3) * .constraints( * not_before(std::chrono::milliseconds(15)), * not_after(std::chrono::seconds(2))) * .when(*test_agent & reacts_to(some_mbox)); * * // Create a step that trigges when both agents receive messages. * scenario.define_step("four") * .when_all( * *test_agent & reacts_to(some_mbox), * *another_agent & reacts_to(another_mbox)); * * // Create a step that triggers when one of agents receive a message. * scenario.define_step("five") * .when_any( * *test_agent & reacts_to(some_mbox), * *another_agent & reacts_to(another_mbox)); * \endcode * * \note * The object of this type can be stored to define a step in several * footsteps: * \code * auto step = env.scenario().define_step("my_step"); * if(some_condition) * step.constraints(...); * if(another_condition) * step.impact(...); * if(third_condition) * step.when(...); * else * step.when_all(...); * \endcode * But all definitions should be done before calling of * scenario_proxy_t::run_for(). * * \attention * This class is not thread-safe. Because of that it should be used on * a context of a signle thread. * * \since v.5.5.24 */ class step_definition_proxy_t { details::abstract_scenario_step_t * m_step; void append_trigger_to( details::trigger_container_t & /*to*/ ) {} template< details::incident_status_t Status, typename... Args > void append_trigger_to( details::trigger_container_t & to, details::trigger_holder_t event, Args && ...args ) { to.emplace_back( event.giveout_trigger() ); append_trigger_to( to, std::forward(args)... ); } void append_constraint_to( details::constraint_container_t & /*to*/ ) {} template< typename... Args > void append_constraint_to( details::constraint_container_t & to, details::constraint_unique_ptr_t head, Args && ...tail ) { to.emplace_back( std::move(head) ); append_constraint_to( to, std::forward(tail)... ); } public : //! Initializing constructor. /*! * Despite the fact that this is a public constructor * it is a part of SObjectizer implementation and is subject * of change without a notice. Don't use it in end-user code. */ step_definition_proxy_t( details::abstract_scenario_step_t * step ) : m_step( step ) {} /*! * \brief Define a preactivation action in form of * sending a message/signal to the specified target. * * This method creates and stores an instance of a message/signal * and then sends this instance when step is preactivated. * * Usage example: * \code * env.scenario().define_step("my_step") * .impact(*test_agent, arg1, arg2, arg3) * .impact(some_mbox) * .impact(mchain) * ...; * \endcode * * Please note that this method can be called several times. * * \tparam Msg_Type type of a message/signal to send. * \tparam Target type of a target for message/signal. * It can be a reference to mbox, a reference to agent * (agent's direct mbox will be used in that case), or * a reference to mchain. * \tparam Args types of arguments for Msg_Type's constructor. */ template< typename Msg_Type, typename Target, typename... Args > step_definition_proxy_t & impact( Target && target, Args && ...args ) { // Deduce actual mbox of the recevier. // This mbox will be captured by lambda function. auto target_mbox = so_5::send_functions_details::arg_to_mbox( std::forward(target) ); // Make an instance of a message. // This instance will be captured by lambda function. // Mutability of a message will be changed appropriately // in make_message_instance. message_ref_t msg_instance{ so_5::details::make_message_instance( std::forward(args)... ) }; // Now we can create a lambda-function that will send // the message instance at the appropriate time. m_step->add_preactivate_action( [to = std::move(target_mbox), msg = std::move(msg_instance)]() noexcept { using namespace so_5::low_level_api; deliver_message( so_5::message_delivery_mode_t::ordinary, *to, message_payload_type< Msg_Type > ::subscription_type_index(), std::move(msg) ); } ); return *this; } /*! * \brief Add preactivation action in form of lambda-object. * * This method can be used for non-trivial actions on step * preactivation, like sending enveloped messages. * * Usage example: * \code * env.scenario().define_step("my_step") * .impact([some_mbox, some_data] { * some_mbox->do_deliver_enveloped_msg( * so_5::message_payload_type::subscription_type_index(), * std::make_unique>(some_data), * 1u); * }); * \endcode * * Please note that this method can be called several times. * * \note * Preactivation of scenario step is performed when scenario object * is locked. Becase of that actions inside \a lambda must be performed * with care to avoid deadlocks or blocking of scenario for too long. * * \attention * \a lambda should be noexcept-function. */ template< typename Lambda > step_definition_proxy_t & impact( Lambda && lambda ) { m_step->add_preactivate_action( [lambda]() noexcept { lambda(); } ); return *this; } /*! * \brief Add a tigger for activation of that step * * Step is activated when this trigger is activated. * * Usage example: * \code * env.scenario().define_step("my_step") * .when(some_agent & reacts_to()); * \endcode * * \note * This method is indended to be called only once. * All subsequent calls to that method will replace triggers * those where set by previous calls to when(), when_all() and * when_any() methods. */ template< details::incident_status_t Status > step_definition_proxy_t & when( details::trigger_holder_t event ) { details::trigger_container_t cnt; cnt.emplace_back( event.giveout_trigger() ); m_step->setup_triggers( std::move(cnt), 1u ); return *this; } /*! * \brief Add a list of tiggers for activation of that step * * Step is activated when \b any of those triggers is activated. * * Usage example: * \code * env.scenario().define_step("my_step") * .when_any( * some_agent & reacts_to(), * another_agent & reacts_to(), * yet_another_agent & reacts_to()); * \endcode * * \note * This method is indended to be called only once. * All subsequent calls to that method will replace triggers * those where set by previous calls to when(), when_all() and * when_any() methods. */ template< details::incident_status_t Status, typename... Args > step_definition_proxy_t & when_any( details::trigger_holder_t event, Args && ...args ) { details::trigger_container_t cnt; cnt.reserve( 1u + sizeof...(args) ); append_trigger_to( cnt, std::move(event), std::forward(args)... ); m_step->setup_triggers( std::move(cnt), 1u ); return *this; } /*! * \brief Add a list of tiggers for activation of that step * * Step is activated when \a all of those triggers is activated. * * Usage example: * \code * env.scenario().define_step("my_step") * .when_all( * some_agent & reacts_to(), * another_agent & reacts_to(), * yet_another_agent & reacts_to()); * \endcode * * \note * This method is indended to be called only once. * All subsequent calls to that method will replace triggers * those where set by previous calls to when(), when_all() and * when_any() methods. */ template< details::incident_status_t Status, typename... Args > step_definition_proxy_t & when_all( details::trigger_holder_t event, Args && ...args ) { const auto total_triggers = 1u + sizeof...(args); details::trigger_container_t cnt; cnt.reserve( total_triggers ); append_trigger_to( cnt, std::move(event), std::forward(args)... ); m_step->setup_triggers( std::move(cnt), total_triggers ); return *this; } /*! * \brief Add a list of constraints for that step * * All specified constraints should be fulfilled to enable * activation of that step. * * Usage example: * \code * env.scenario().define_step("my_step") * .constraints( * not_before(std::chrono::milliseconds(10))) * ...; * env.scenario().define_step("another_step") * .constraints( * not_after(std::chrono::milliseconds(500), * not_before(std::chrono::milliseconds(10))) * ...; * \endcode * * \note * This method is indended to be called only once. * All subsequent calls to that method will replace constraints * those where set by previous calls to constraints(). */ template< typename... Args > step_definition_proxy_t & constraints( details::constraint_unique_ptr_t head, Args && ...tail ) { details::constraint_container_t cnt; cnt.reserve( 1u + sizeof...(tail) ); append_constraint_to( cnt, std::move(head), std::forward(tail)... ); m_step->setup_constraints( std::move(cnt) ); return *this; } }; /*! * \brief Status of testing scenario. * * This enumeration is used by testing scenario itself and by * scenario_result_t type. * * \since v.5.5.24 */ enum class scenario_status_t { //! Testing scenario is not started yet. //! New step can be added when scenario is in state. not_started, //! Testing scenario is started but not finished yet. //! New steps can't be added. in_progress, //! Testing scenario is successfuly completed. completed, //! Testing scenario is not working any more, but it is not //! completed becase there is no more time to run the scenario. timed_out }; /*! * \brief The result of run of testing scenario. * * The result contains the status of the scenario * (in form of scenario_status_t enumeration) and optional textual * description of the result. * * Note that description is missing if testing scenario completed * successfuly (it means that scenario has scenario_status_t::completed * state after completion of scenario_proxy_t::run_for() method). * * The content and format of textual description is not specified and * can be changed in the future versions of SObjectizer. * * Usage example: * \code * TEST_CASE("some_case") { * using namespace so_5::experimental::testing; * * testing_env_t env; * ... * env.scenario().run_for(std::chrono::milliseconds(500)); * * REQUIRE(completed() == env.scenario().result()); * } * \endcode * * \note * Object of scenario_result_t type can be dumped to std::ostream. * For example: * \code * auto result = env.scenario().result(); * if(completed() != result) * std::cout << "The result is: " << result << std::endl; * \endcode * * \since v.5.5.24 */ class scenario_result_t { scenario_status_t m_status; optional< std::string > m_description; public : //! The constructor for a case when there is only status of scenario. scenario_result_t( scenario_status_t status ) : m_status( status ) {} //! The constructor for a case when there are status and description //! of scenario. scenario_result_t( scenario_status_t status, std::string description ) : m_status( status ) , m_description( std::move(description) ) {} //! Check for equality. /*! * \note * Only status is compared. */ [[nodiscard]] bool operator==( const scenario_result_t & o ) const noexcept { return m_status == o.m_status; } //! Check for inequality. /*! * \note * Only status is compared. */ [[nodiscard]] bool operator!=( const scenario_result_t & o ) const noexcept { return m_status != o.m_status; } //! Dump of object's content to ostream. friend std::ostream & operator<<( std::ostream & to, const scenario_result_t & v ) { const auto status_name = [](scenario_status_t st) -> const char * { const char * result{}; switch( st ) { case scenario_status_t::not_started : result = "not_started"; break; case scenario_status_t::in_progress : result = "in_progress"; break; case scenario_status_t::completed : result = "completed"; break; case scenario_status_t::timed_out : result = "timed_out"; break; } return result; }; to << "[" << status_name( v.m_status ); if( v.m_description ) to << ",{" << *v.m_description << "}"; to << "]"; return to; } }; /*! * \brief Create a value that means that scenario completed successfuly. * * Usage example: * \code * TEST_CASE("some_case") { * using namespace so_5::experimental::testing; * * testing_env_t env; * ... * env.scenario().run_for(std::chrono::milliseconds(500)); * * REQUIRE(completed() == env.scenario().result()); * } * \endcode * * \since v.5.5.24 */ [[nodiscard]] inline scenario_result_t completed() { return { scenario_status_t::completed }; } /*! * \brief Define a trigger that activates when an agent receives and * handles a message from the direct mbox. * * Usage example: * \code * using namespace so_5::experimental::testing; * ... * env.scenario().define_step("my_step") * .when(some_agent & reacts_to()); * \endcode * * \since v.5.5.24 */ template [[nodiscard]] details::trigger_source_t< details::incident_status_t::handled > reacts_to() { return { message_payload_type::subscription_type_index() }; } /*! * \brief Define a trigger that activates when an agent receives and * handles a message from the specific mbox. * * Usage example: * \code * using namespace so_5::experimental::testing; * ... * env.scenario().define_step("my_step") * .when(some_agent & reacts_to(some_mbox)); * \endcode * * \since v.5.5.24 */ template [[nodiscard]] details::trigger_source_t< details::incident_status_t::handled > reacts_to( const so_5::mbox_t & mbox ) { return { message_payload_type::subscription_type_index(), mbox->id() }; } /*! * \brief Create a special marker for a trigger for storing * agent's state name inside scenario. * * Usage example: * \code * using namespace so_5::experimental::testing; * ... * env.scenario().define_step("my_step") * .when(some_agent & reacts_to() & store_agent_name("my_agent")); * ... * env.scenario().run_for(std::chrono::seconds(1)); * * REQUIRE(completed() == env.scenario().result()); * REQUIRE("some_state" == env.scenario().stored_state_name("my_step", "my_agent")); * \endcode * * \since v.5.5.24 */ [[nodiscard]] inline details::store_agent_state_name_t store_state_name( std::string tag ) { return { std::move(tag) }; } /*! * \brief Create a special marker for a trigger for inspecting an * incoming message and storing the inspection result inside the scenario. * * Usage example: * \code * using namespace so_5::experimental::testing; * ... * env.scenario().define_step("my_step") * .when(some_agent & reacts_to() * & inspect_msg("msg-check", [](const some_message & msg) -> std::string { * return msg.some_field == expected_value ? "OK" : "FAILED"; * }) * ); * ... * env.scenario().run_for(std::chrono::seconds(1)); * * REQUIRE(completed() == env.scenario().result()); * REQUIRE("OK" == env.scenario().stored_msg_inspection_result("my_step", "msg-check")); * \endcode * * The inspect_msg() can be used for mutable messages too: * * \code * env.scenario().define_step("my_step") * .when(some_agent & reacts_to>() * & inspect_msg("msg-check", [](const some_message & msg) -> std::string { * return msg.some_field == expected_value ? "OK" : "FAILED"; * }) * ); * \endcode * * The inspect_msg() can't be used with signals, because signals have no actual data. * * \attention * The inspect_msg() doesn't check the type of the message. So it's possible to * make a mistake like this: * \code * env.scenario().define_step("my_step") * .when(some_agent & reacts_to() * & inspect_msg("msg-check", [](const another_msg & msg) -> std::string { * return msg.some_field == expected_value ? "OK" : "FAILED"; * }) * ); * \endcode * And this will lead to a run-time error (with possible termination of the * application). * * \since v.5.8.3 */ template< typename Lambda > [[nodiscard]] details::store_msg_inspection_result_t inspect_msg( //! Tag to be used for indentification of the inspection result. std::string tag, //! Inspection functor. Lambda && inspector ) { using namespace so_5::details::lambda_traits; using lambda_type = std::remove_reference_t< Lambda >; using argument_type = typename argument_type_if_lambda< lambda_type >::type; // Signal can't be inspected. ensure_not_signal< argument_type >(); return { std::move(tag), [lambda_to_call = std::move(inspector)] ( const message_ref_t & message ) -> std::string { const argument_type & payload_ref = message_payload_type< argument_type >::payload_reference( // Because inspect_msg can't be used for signals // we can assume that message is not nullptr. *message ); return lambda_to_call( payload_ref ); } }; } /*! * \brief Create a special marker for a trigger that requires waiting for * completion of an event handler. * * Usage example: * \code * using namespace so_5::experimental::testing; * ... * env.scenario().define_step("my_step") * .when(some_agent & reacts_to() & wait_event_handler_completion()); * ... * env.scenario().run_for(std::chrono::seconds(1)); * * REQUIRE(completed() == env.scenario().result()); * \endcode * * @note * This marker can be combined with reacts_to(), but not with ignores(). * * \since v.5.8.3 */ [[nodiscard]] inline details::wait_event_handler_completion_t wait_event_handler_completion() { return {}; } /*! * \brief Define a trigger that activates when an agent rejects a message from * the direct mbox. * * Usage example: * \code * using namespace so_5::experimental::testing; * ... * env.scenario().define_step("my_step") * .when(some_agent & ignores()); * \endcode * * \attention * It is necessary that agent should be subscribed to that message but * ignores it in the current state. * If an agent is not subscribed to a message then the message can be * simply skipped inside a call to send() function. It means that there * won't be delivery of message at all. * * \since v.5.5.24 */ template [[nodiscard]] details::trigger_source_t< details::incident_status_t::ignored > ignores() { return { message_payload_type::subscription_type_index() }; } /*! * \brief Define a trigger that activates when an agent rejects a message from * the direct mbox. * * Usage example: * \code * using namespace so_5::experimental::testing; * ... * env.scenario().define_step("my_step") * .when(some_agent & ignores(some_mbox)); * \endcode * * \attention * It is necessary that agent should be subscribed to that message but * ignores it in the current state. * If an agent is not subscribed to a message then the message can be * simply skipped inside a call to send() function. It means that there * won't be delivery of message at all. * * \since v.5.5.24 */ template [[nodiscard]] details::trigger_source_t< details::incident_status_t::ignored > ignores( const so_5::mbox_t & mbox ) { return { message_payload_type::subscription_type_index(), mbox->id() }; } /*! * \brief Create a constraint not-before. * * That constraint is fulfilled if an event is happened after * a specified \a pause. Time is calculated from moment of preactivation * of a scenario's step. * * Usage example: * \code * using namespace so_5::experimental::testing; * env.scenario().define_step("my_step") * .constraints(not_before(std::chrono::milliseconds(50))) * .when(some_agent & reacts_to()); * \endcode * In that case step won't be activated if agent receives a message * after, for example, 15ms. * * \note * If not_before() is used with not_after() then the correctness of those * constraints is not checked. * * \since v.5.5.24 */ [[nodiscard]] inline details::constraint_unique_ptr_t not_before( std::chrono::steady_clock::duration pause ) { return std::make_unique< details::not_before_constraint_t >(pause); } /*! * \brief Create a constraint not-after. * * That constraint is fulfilled if an event is happened before * a specified \a pause. Time is calculated from moment of preactivation * of a scenario's step. * * Usage example: * \code * using namespace so_5::experimental::testing; * env.scenario().define_step("my_step") * .constraints(not_after(std::chrono::milliseconds(50))) * .when(some_agent & reacts_to()); * \endcode * In that case step won't be activated if agent receives a message * after, for example, 55ms. * * \note * If not_after() is used with not_before() then the correctness of those * constraints is not checked. * * \since v.5.5.24 */ [[nodiscard]] inline details::constraint_unique_ptr_t not_after( std::chrono::steady_clock::duration pause ) { return std::make_unique< details::not_after_constraint_t >(pause); } namespace details { /*! * \brief A special objects that allows to call some specific methods * of a testing scenario. * * In the current version abstract_scenario_t has at least one * method that should be called only when testing scenario is in * progess. This method requires an instance of that class as a special * token. * * \since v.5.5.24 */ class scenario_in_progress_accessor_t final { friend class abstract_scenario_t; private : outliving_reference_t m_scenario; scenario_in_progress_accessor_t( outliving_reference_t scenario ) : m_scenario( scenario ) {} scenario_in_progress_accessor_t( const scenario_in_progress_accessor_t & ) = delete; scenario_in_progress_accessor_t & operator=( const scenario_in_progress_accessor_t & ) = delete; scenario_in_progress_accessor_t( scenario_in_progress_accessor_t && ) = delete; scenario_in_progress_accessor_t & operator=( scenario_in_progress_accessor_t && ) = delete; public : abstract_scenario_t & scenario() const noexcept { return m_scenario.get(); } }; /*! * \brief An interface of testing scenario. * * Please note that this type is a part of SObjectizer implementation * and is subject of changes in upcoming version. Do not use it in your * code, there is scenario_proxy_t call intended to be used by * end-users. * * This class is described in a public header-file in the current * version of SObjectizer. This is just to simplify implementation of * some testing-related stuff. The definition of that class can be move * to another file in future versions of SObjectizer without any * previous notice. * * \since v.5.5.24 */ class abstract_scenario_t { protected : //! Helper method for creation of scenario_in_progress_accessor instance. [[nodiscard]] scenario_in_progress_accessor_t make_accessor() noexcept { return { outliving_mutable(*this) }; } public : /*! * \brief Type of token returned by pre-event-handler hook. * * Object of that type should be stored and then it should * be passed to abstract_scenario_t::post_handler_hook() method. * * A token can be valid. It means that token holds an actual * pointer to some scenario step. * * Or token can be invalid. It means that there is no a valid * pointer to scenario step. In that case methods like * activated_step() and step_token() should not be called. * * \since v.5.5.24 */ class token_t final { abstract_scenario_step_t * m_activated_step; abstract_scenario_step_t::token_t m_step_token; public : token_t() : m_activated_step( nullptr ) {} token_t( abstract_scenario_step_t * activated_step, abstract_scenario_step_t::token_t step_token ) : m_activated_step( activated_step ) , m_step_token( step_token ) {} [[nodiscard]] bool valid() const noexcept { return nullptr != m_activated_step; } [[nodiscard]] abstract_scenario_step_t & activated_step() const noexcept { return *m_activated_step; } [[nodiscard]] abstract_scenario_step_t::token_t step_token() const noexcept { return m_step_token; } }; public : abstract_scenario_t() = default; abstract_scenario_t( const abstract_scenario_t & ) = delete; abstract_scenario_t & operator=( const abstract_scenario_t & ) = delete; abstract_scenario_t( abstract_scenario_t && ) = delete; abstract_scenario_t & operator=( abstract_scenario_t && ) = delete; virtual ~abstract_scenario_t() = default; //! Create a new step and return proxy for it. [[nodiscard]] virtual step_definition_proxy_t define_step( nonempty_name_t step_name ) = 0; //! Get the result of scenario execution. [[nodiscard]] virtual scenario_result_t result() const noexcept = 0; //! Run the scenario until completion or for specific amount of time. virtual void run_for( std::chrono::steady_clock::duration run_time ) = 0; //! Hook that should be called before invocation of event-handler. /*! * This hook should be called before invocation of any event-handler * for ordinary message or enveloped message. * * The token returned should then be passed to post_handler_hook(). */ [[nodiscard]] virtual token_t pre_handler_hook( //! Information about incoming message/signal. const incident_info_t & info, //! Reference to the incoming message. //! It may be nullptr in case of a signal. const message_ref_t & incoming_msg ) noexcept = 0; //! Hook that should be called just after completion of event-handler. /*! * This hook should be called just after completion of any event-handler * for ordinary message, service request or enveloped message. */ virtual void post_handler_hook( //! Token returned by previous pre_handler_hook() call. token_t token ) noexcept = 0; //! Hook that should be called if there is no event-handler for //! a message or service request. virtual void no_handler_hook( //! Information about incoming message/signal. const incident_info_t & info, //! Reference to the incoming message. //! It may be nullptr in case of a signal. const message_ref_t & incoming_msg ) noexcept = 0; //! Store a name of an agent state in the scenario. /*! * Note this method can be accessed only when scenario object is locked. */ virtual void store_state_name( const scenario_in_progress_accessor_t & /*accessor*/, const abstract_scenario_step_t & step, const std::string & tag, const std::string & state_name ) = 0; //! Get the stored state name. /*! * Should be called only after completion of scenario. * * Will throw an exception if there is no stored state for * a pair of (\a step_name,\a tag). */ [[nodiscard]] virtual std::string stored_state_name( const std::string & step_name, const std::string & tag ) const = 0; //! Check presence of the stored state name. /*! * Should be called only after completion of scenario. * * \since v.5.8.4 */ [[nodiscard]] virtual bool has_stored_state_name( const std::string & step_name, const std::string & tag ) const = 0; //! Store msg inspection result in the scenario. /*! * Note this method can be accessed only when scenario object is locked. * * \since v.5.8.3 */ virtual void store_msg_inspection_result( const scenario_in_progress_accessor_t & /*accessor*/, const abstract_scenario_step_t & step, const std::string & tag, const std::string & inspection_result ) = 0; //! Get a value of stored msg inspection result. /*! * Note this method can be accessed only when scenario object is locked. * * \throw exception_t if there is no msg inspecation result with such * tag name for the specified step name. * * \since v.5.8.3 */ [[nodiscard]] virtual std::string stored_msg_inspection_result( const std::string & step_name, const std::string & tag ) const = 0; /*! * \brief Is there the inspection result? * * Note this method can be accessed only when scenario object is locked. * * \throw exception_t if the scenario is not completed yet. * * \since v.5.8.4 */ [[nodiscard]] virtual bool has_stored_msg_inspection_result( const std::string & step_name, const std::string & tag ) const = 0; }; /*! * \brief A helper operator to create a trigger for the specified agent. * * \since v.5.5.24 */ template< incident_status_t Status > trigger_holder_t operator&( const so_5::agent_t & agent, const trigger_source_t & src ) { const auto src_mbox_id = src.m_src_mbox_id ? *(src.m_src_mbox_id) : agent.so_direct_mbox()->id(); return { std::make_unique( Status, agent, src.m_msg_type, src_mbox_id ) }; } /*! * \brief A helper operator to create a tigger that stores the * name of the current agent's state. * * \since v.5.5.24 */ inline trigger_holder_t operator&( trigger_holder_t && old_holder, store_agent_state_name_t data_to_store ) { auto trigger_ptr = old_holder.giveout_trigger(); auto * target_agent = &(trigger_ptr->target_agent()); trigger_ptr->set_completion( [data = std::move(data_to_store), target_agent]( const trigger_completion_context_t & ctx ) noexcept { ctx.m_scenario_accessor.scenario().store_state_name( ctx.m_scenario_accessor, ctx.m_step, data.m_tag, target_agent->so_current_state().query_name() ); } ); return { std::move(trigger_ptr) }; } /*! * \brief A helper operator to create a tigger that inspects the incoming * message and stores the result into the scenario. * * \since v.5.8.3 */ template< incident_status_t Status > trigger_holder_t< Status > operator&( trigger_holder_t && old_holder, store_msg_inspection_result_t inspection_info ) { auto trigger_ptr = old_holder.giveout_trigger(); trigger_ptr->set_activation( [info = std::move(inspection_info)]( const trigger_activation_context_t & ctx ) noexcept { std::string result = info.m_inspector( ctx.m_incoming_msg ); ctx.m_scenario_accessor.scenario().store_msg_inspection_result( ctx.m_scenario_accessor, ctx.m_step, info.m_tag, result ); } ); return { std::move(trigger_ptr) }; } /*! * \brief A helper operator to create a tigger that requires the * completion of an event handler. * * \since v.5.8.3 */ inline trigger_holder_t operator&( trigger_holder_t && old_holder, wait_event_handler_completion_t /*data_to_store*/ ) { auto trigger_ptr = old_holder.giveout_trigger(); trigger_ptr->set_completion( []( const trigger_completion_context_t & /*ctx*/ ) noexcept { // Do nothing. // The presence of this completion_function requires // waiting of the return from an event handler. } ); return { std::move(trigger_ptr) }; } namespace mbox_receives_msg_impl { /*! * \brief Agent that receives a message/signal from specified mbox. * * This agent is a part of `receives` trigger. * * \tparam Msg type of message/signal to be received. * * \since v.5.8.3 */ template< typename Msg > class a_msg_catcher_t final : public agent_t { //! Source for a message. const mbox_t m_from; public: a_msg_catcher_t( context_t ctx, mbox_t from ) : agent_t{ std::move(ctx) } , m_from{ std::move(from) } {} void so_define_agent() override { so_subscribe( m_from ).event( &a_msg_catcher_t::evt_msg_arrived ); } private: void evt_msg_arrived( mhood_t ) { // Nothing to do. } }; // // msg_catcher_map_layer_t // /// \brief Special layer that holds a map of catcher agents. /// /// There should be just one agent for every pair of (msg_type, mbox), /// otherwise there would be errors on attempt to use receives() with /// MPSC mboxes. Because of that a map of catcher agent is necessary. /// This layer holds such a map and provides a method to create a /// new catcher agent or get a pointer to existing one. /// /// This layer will be automatically added to testing_env_t. /// /// When a receives_indicator_t is transformed into a trigger this /// layer will be obtained from mbox's environment. /// /// \note /// This layer holds pointers to agents. It's not a good idea in /// general, because the lifetime of catcher agents is not under /// control of the layer. But in this use case it seems to be safe /// because pointers are used only during creation of triggers and /// at this moment all catcher agents are alive. /// /// \since v.5.8.4 class SO_5_TYPE msg_catcher_map_layer_t final : public layer_t { public: /// Information about a catcher agent. /// /// This information has to be used for building a trigger. /// /// @note /// At the current moment just a pointer is enough. But it's better /// to place it into a struct to have a possibility to extend it /// in the future. struct catcher_info_t { /// Pointer to the agent. const agent_t * m_target_agent; /// Initializing constructor. explicit catcher_info_t( const agent_t * target_agent ) : m_target_agent{ target_agent } {} }; private: /// Type of key for identifying agents. using catcher_key_t = std::tuple< // Type of message/signal to be received. std::type_index, // ID of source mbox. mbox_id_t >; /// Type of map of already registered agents. using agents_map_t = std::map< catcher_key_t, catcher_info_t >; /// The lock for thread safety. std::mutex m_lock; /// The map of already created catcher agents. agents_map_t m_agents; /// Helper for registration of a new agent. template< typename Msg > [[nodiscard]] const agent_t * register_new_catcher( const so_5::mbox_t & from ) const { // NOTE: the agent will be bound to the default dispatcher. return this->so_environment().introduce_coop( [&from]( coop_t & coop ) -> const agent_t * { using agent_to_create_t = a_msg_catcher_t< Msg >; return coop.make_agent< agent_to_create_t >( from ); } ); } public: msg_catcher_map_layer_t(); ~msg_catcher_map_layer_t() noexcept override; /// Get information about a catcher agent for (msg_type, mbox) pair. /// /// If there is no such an agent for this (msg_type, mbox) pair it /// will be created. Otherwise an information about already existing /// agent will be returned. template< typename Msg > [[nodiscard]] catcher_info_t catcher_for_mbox( const so_5::mbox_t & from ) { std::lock_guard< std::mutex > lock{ m_lock }; const catcher_key_t key{ message_payload_type< Msg >::subscription_type_index(), from->id() }; auto it = m_agents.find( key ); if( it == m_agents.end() ) { // For exception safety we add a key with nullptr // to the m_agents map, and then update the pointer if // there won't be problems with registration of a new agent. it = m_agents.emplace( key, catcher_info_t{ nullptr } ) .first; ::so_5::details::do_with_rollback_on_exception( [&]() { // It's necessary to create an agent first time. const agent_t * catcher_agent = register_new_catcher< Msg >( from ); // Now we have an actual agent pointer and can update // m_agents map. it->second.m_target_agent = catcher_agent; }, [&]() { // Information about new agent has to be removed // because it wasn't registered. m_agents.erase( it ); } ); } // Now the agent is present in the map. return it->second; } }; } /* namespace mbox_receives_msg_impl */ /*! * \brief Special indicator to be used in implementation of `receives` trigger. * * \tparam Msg type of message/signal to be received. * * \since v.5.8.3 */ template< typename Msg > struct receives_indicator_t {}; /*! * \brief A helper operator to create a tigger that receives a message/signal * from specified mbox. * * \tparam Msg type of message/signal to be received. * * \since v.5.8.3 */ template< typename Msg > trigger_holder_t< incident_status_t::handled > operator&( //! Mbox from that a message/signal has to be received. const mbox_t & from, //! Type of message to be received. receives_indicator_t< Msg > ) { // Information about catcher agents is stored in a special layer. using catcher_layer_t = mbox_receives_msg_impl::msg_catcher_map_layer_t; auto * catcher_layer = from->environment().query_layer< catcher_layer_t >(); // The appropriate catcher agent will be created or reused. const auto catcher_info = catcher_layer->catcher_for_mbox< Msg >( from ); return { std::make_unique( incident_status_t::handled, *(catcher_info.m_target_agent), message_payload_type< Msg >::subscription_type_index(), from->id() ) }; } } /* namespace details */ /*! * \brief Helper function to be used for a trigger that receives * a message/singal from a mbox. * * Usage example: * \code * so_5::testing::testing_env_t env; * ... * so_5::mbox_t dest = env.environment().create_mbox(); * ... * env.scenario().define_step("message_arrives") * .when(dest & tests::receives()); * ... * \endcode * * Since v.5.8.4 this helper can be used for the same mbox and the same * message type several times in one scenario (but in different scenario * steps): * \code * so_5::testing::testing_env_t env; * ... * so_5::mbox_t dest = env.environment().create_mbox(); * ... * env.scenario().define_step("message_arrives") * .when(dest & tests::receives()); * ... * env.scenario().define_step("another_time") * .when(dest & tests::receives()); * ... * env.scenario().define_step("and_yet_another_time") * .when(dest & tests::receives()); * \endcode * * \note * A mutable message can be specified too. But the mbox should allow * subscription for mutable messages. For example: * \code * so_5::testing::testing_env_t env; * ... * so_5::mbox_t dest = so_5::make_unique_subscribers_mbox(env.environment()); * ... * env.scenario().define_step("message_arrives") * .when(dest & tests::receives< so_5::mutable_msg >()); * ... * \endcode * * \since v.5.8.3 */ template< typename Msg > [[nodiscard]] details::receives_indicator_t< Msg > receives() { return {}; } /*! * \brief A special wrapper around scenario object. * * The class scenario_proxy_t is a public interface to scenario object. * The actual scenario object is inside of testing_env_t instance and * access to it is provided via scenario_proxy_t wrapper. * * Usage example: * \code * using namespace so_5::experimental::testing; * TEST_CASE("some_case") { * testing_env_t env; * * so_5::agent_t * test_agent; * env.environment().introduce_coop([&](so_5::coop_t & coop) { * test_agent = coop.make_agent(); * }); * * env.scenario().define_step("one") * .impact(*test_agent) * .when(*test_agent & reacts_to()); * * env.scenario().run_for(std::chrono::milliseconds(200)); * * REQUIRE(completed() == env.scenario().result()); * } * \endcode * Or in more conciseness form: * \code * using namespace so_5::experimental::testing; * TEST_CASE("some_case") { * testing_env_t env; * * so_5::agent_t * test_agent; * env.environment().introduce_coop([&](so_5::coop_t & coop) { * test_agent = coop.make_agent(); * }); * * auto scenario = env.scenario(); * * scenario.define_step("one") * .impact(*test_agent) * .when(*test_agent & reacts_to()); * * scenario.run_for(std::chrono::milliseconds(200)); * * REQUIRE(completed() == scenario.result()); * } * \endcode * * \note * scenario_proxy_t holds a reference to an object from testing_env_t * instance. It means that scenario_proxy shouldn't outlive the * corresponding testing_env_t object. For example this code will * lead to a dangling reference: * \code * so_5::experimental::testing::scenario_proxy_t get_scenario() { * so_5::experimental::testing::testing_env_t env; * return env.scenario(); // OOPS! A reference to destroyed object will be returned. * } * \endcode * * \since v.5.5.24 */ class SO_5_TYPE scenario_proxy_t final { friend class testing_env_t; private : outliving_reference_t< details::abstract_scenario_t > m_scenario; scenario_proxy_t( outliving_reference_t< details::abstract_scenario_t > scenario ); public : //! Start definition of a new scenario's step. /*! * New steps can be defined until run_for() is called. * When the scenario is in progress (or is already * finished) then an attempt to call to define_step() will * lead to an exception. * * \return A special wrapper around a new step instance. * This wrapped should be used to define the step. * * \note * The value of \a step_name has to be unique, but the current * version of SObjectizer doesn't controll that. */ [[nodiscard]] step_definition_proxy_t define_step( nonempty_name_t step_name ); //! Get the result of scenario execution. /*! * \note * This method is intended to be called only after the completion of * run_for() method. */ [[nodiscard]] scenario_result_t result() const; //! Runs the scenario for specified amount of time. /*! * This method does to things: * - unfreeze all agents registered in testing environment * up to this time; * - run scenario until scenario will be completed or * \a run_time elapsed. * * The result of scenario run can then be obtained by result() * method. * * Usage example: * \code * using namespace so_5::experimental::testing; * TEST_CASE("some_case") { * testing_env_t env; // Create a testing environment. * * // Introduce some agents. * env.environment().introduce_coop(...); * * // Do define the scenario for the test case. * auto scenario = env.scenario(); * ... * // Run the scenario for at most 1 second. * scenario.run_for(std::chrono::seconds(1)); * // Check the result of the scenario. * REQUIRE(completed() == scenario.result()); * } * \endcode */ void run_for( std::chrono::steady_clock::duration run_time ); //! Try to get stored name of an agent's state. /*! * This method allows to get state name stored by * store_state_name() trigger. For example: * \code * using namespace so_5::experimental::testing; * TEST_CASE("some_case") { * testing_env_t env; * * so_5::agent_t * test_agent; * env.environment().introduce_coop([&](so_5::coop_t & coop) { * test_agent = coop.make_agent(...); * }); * * env.scenario().define_step("one") * .impact(*test_agent, ...) * .when(*test_agent & reacts_to() * & store_state_name("my_agent")); * ... * env.scenario().run_for(std::chrono::seconds(1)); * * REQUIRE(completed() == env.scenario().result()); * * REQUIRE("some_state" == env.scenario().stored_state_name("one", "my_agent")); * } * \endcode * * \attention * This method can be called only after completion of the scenario. * Otherwise an instance of so_5::exception_t will be thrown. * * \return Name of stored state. If there is no stored name for * a pair of (\a step_name, \a tag) then an instance of * so_5::exception_t will be thrown. */ [[nodiscard]] std::string stored_state_name( const std::string & step_name, const std::string & tag ) const; //! Is there the inspection result? /*! * This method allows to check presence of the state name stored by * store_state_name() trigger. For example: * \code * using namespace so_5::experimental::testing; * TEST_CASE("some_case") { * testing_env_t env; * * so_5::agent_t * test_agent; * env.environment().introduce_coop([&](so_5::coop_t & coop) { * test_agent = coop.make_agent(...); * }); * * env.scenario().define_step("one") * .impact(*test_agent, ...) * .when_any( * *test_agent & reacts_to() * & store_state_name("my_agent"), * ... // Other triggers... * ); * ... * env.scenario().run_for(std::chrono::seconds(1)); * * REQUIRE(completed() == env.scenario().result()); * * // Because store_state_name was used in when_any then, the corresponding * // trigger may not have been activated. * if(env.scenario().has_stored_state_name("one", "my_agent")) * REQUIRE("some_state" == env.scenario().stored_state_name("one", "my_agent")); * } * \endcode * * \attention * This method can be called only after completion of the scenario. * Otherwise an instance of so_5::exception_t will be thrown. * * \since v.5.8.4 */ [[nodiscard]] bool has_stored_state_name( const std::string & step_name, const std::string & tag ) const; //! Try to get stored msg inspection result. /*! * This method allows to get msg inspection result stored by * inspect_msg() trigger. For example: * \code * using namespace so_5::experimental::testing; * TEST_CASE("some_case") { * testing_env_t env; * * so_5::agent_t * test_agent; * env.environment().introduce_coop([&](so_5::coop_t & coop) { * test_agent = coop.make_agent(...); * }); * * env.scenario().define_step("one") * .impact(*test_agent, ...) * .when(*test_agent & reacts_to() * & inspect_msg("msg-check" * [](const some_message & msg) -> std::string { * return msg.some_field = expected_value ? "OK" : "FAIL"; * }) * ); * ... * env.scenario().run_for(std::chrono::seconds(1)); * * REQUIRE(completed() == env.scenario().result()); * * REQUIRE("OK" == env.scenario().stored_msg_inspection_result("one", "msg-check")); * } * \endcode * * \attention * This method can be called only after completion of the scenario. * Otherwise an instance of so_5::exception_t will be thrown. * * \return The value of the inspection result. If there is no stored * result for a pair of (\a step_name, \a tag) then an instance of * so_5::exception_t will be thrown. * * \since v.5.8.3 */ [[nodiscard]] std::string stored_msg_inspection_result( const std::string & step_name, const std::string & tag ) const; //! Is there the inspection result? /*! * This method allows to check presence of them msg inspection result * stored by inspect_msg() trigger. For example: * \code * using namespace so_5::experimental::testing; * TEST_CASE("some_case") { * testing_env_t env; * * so_5::agent_t * test_agent; * env.environment().introduce_coop([&](so_5::coop_t & coop) { * test_agent = coop.make_agent(...); * }); * * env.scenario().define_step("one") * .impact(*test_agent, ...) * .when_any( * *test_agent & reacts_to() * & inspect_msg("msg-check" * [](const some_message & msg) -> std::string { * return msg.some_field = expected_value ? "OK" : "FAIL"; * }), * ... // some other triggers. * ); * ... * env.scenario().run_for(std::chrono::seconds(1)); * * REQUIRE(completed() == env.scenario().result()); * * // Because inspect_msg was used in when_any then, the corresponding * // trigger may not have been activated. * if(env.scenario().has_stored_msg_inspection_result("one", "msg-check")) * REQUIRE("OK" == env.scenario().stored_msg_inspection_result("one", "msg-check")); * } * \endcode * * \attention * This method can be called only after completion of the scenario. * Otherwise an instance of so_5::exception_t will be thrown. * * \since v.5.8.4 */ [[nodiscard]] bool has_stored_msg_inspection_result( const std::string & step_name, const std::string & tag ) const; }; /*! * \brief A special testing environment that should be used for * testing of agents. * * To allow testing of agent a special environment is necessary. * That environment creates special hooks those control event * handling. The class testing_env_t is an implementation of that * special environment. * * To create a test-case for some agent (or even several agents) * is it necessary to create an instance of testing_env_t class: * \code * using namespace so_5::experimental::testing; * TEST_CASE("some_case") { * testing_env_t env; * ... * } * \endcode * An instance of testing_env_t creates and launches SObjectizer * Environment in the constructor. That Environment will be shut down * in the destructor of testing_env_t instance automatically. * It is possible to shut down SObjectizer Environment manually * by using stop(), join(), stop_then_join() methods * (they play the same role as the corresponding methods of * so_5::wrapped_env_t class). * * The testing_env_t instance already has an instance of scenario * object inside. Access to that object can be obtained via * scenario() method: * \code * using namespace so_5::experimental::testing; * TEST_CASE("some_case") { * testing_env_t env; * ... * env.scenario().define_step("one")...; * ... * // Or, for conciseness: * auto scenario = env.scenario(); * scenario.define_step("two")...; * ... * } * \endcode * * Please note that this is a special environment. One important * feature of it is a behaviour of agents created inside that * environment. All agents registered before call to * scenario_proxy_t::run_for() method will be in "frozen" mode: * they are present in the SObjectizer Environment, but they can't * receive any incoming messages (even so_5::agent_t::so_evt_start() * is not called for them). * * All registered agents will be unfreezed when scenario_proxy_t::run_for() * will be called (or if testing_env_t is stopped). For example: * \code * class hello final : public so_5::agent_t { * public: * using so_5::agent_t::agent_t; * void so_evt_start() override { * std::cout << "Hello, World!" << std::endl; * } * }; * void testing_env_demo() { * so_5::experimental::testing::testing_env_t env; * * env.environment().introduce_coop([](so_5::coop_t & coop) { * coop.make_agent(); * }); * * std::cout << "Bye, bye!" << std::endl; * * env.scenario().run_for(std::chrono::milliseconds(100)); * } * \endcode * In that case we will have the following output: \verbatim Bye, bye! Hello, World! \endverbatim * * \since v.5.5.24 */ class SO_5_TYPE testing_env_t { public : //! Default constructor. /*! * \note * This constructor launches SObjectizer Environment with * default parameters. */ testing_env_t(); //! A constructor that allows to tune environment's parameters. /*! * Usage example: * \code * using namespace so_5::experimental::testing; * testing_env_t env{ * [](so_5::environment_params_t & params) { * // Turn message delivery tracing on. * params.message_delivery_tracer( * so_5::msg_tracing::std_cout_tracer()); * } * }; * \endcode * * \note * The testing_env_t class can change some values in environment_params_t * after the return from \a env_params_tuner. */ testing_env_t( //! Function for environment's params tuning. so_5::generic_simple_so_env_params_tuner_t env_params_tuner ); //! A constructor that receives already constructed //! environment parameters. /*! * Usage example: * \code * so_5::environment_params_t make_params() { * so_5::environment_params_t params; * ... * params.message_delivery_tracer( * so_5::msg_tracing::std_cout_tracer()); * ... * return params; * } * ... * TEST_CASE("some_case") { * using namespace so_5::experimental::testing; * testing_env_t env{ make_params() }; * ... * } * \endcode * * \note * The testing_env_t class can change some values in environment_params_t * before launching a SObjectizer Environment. */ testing_env_t( environment_params_t && env_params ); ~testing_env_t(); //! Access to wrapped environment. environment_t & environment() const; //! Send stop signal to environment. /*! * Please note that stop() just sends a shut down signal to the * SObjectizer Environment, but doesn't wait the completion of * the Environment. If you calls stop() method you have to * call join() to ensure that Environment is shut down. */ void stop(); //! Wait for complete finish of environment's work. void join(); //! Send stop signal and wait for complete finish of environment's work. void stop_then_join(); //! Access to the associated scenario. [[nodiscard]] scenario_proxy_t scenario() noexcept; struct internals_t; private : std::unique_ptr< internals_t > m_internals; wrapped_env_t m_sobjectizer; void tune_environment_on_start( environment_t & env ); void wait_init_completion(); }; } /* namespace v1 */ } /* namespace testing */ } /* namespace experimental */ } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/fwd.hpp000066400000000000000000000017201523065210100167640ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.13 * * \file * \brief Forward declaration for SObjectizer run-time related classes. */ #if !defined( SO_5_FWD_HPP ) #define SO_5_FWD_HPP namespace so_5 { namespace impl { class mpsc_mbox_t; struct event_handler_data_t; class delivery_filter_storage_t; class agent_core_t; class coop_private_iface_t; class internal_env_iface_t; class internal_message_iface_t; class internal_agent_iface_t; class layer_core_t; class state_switch_guard_t; class sinks_storage_t; } /* namespace impl */ class abstract_message_sink_t; class coop_dereg_reason_t; class state_t; class environment_t; class environment_params_t; class coop_t; class agent_t; class event_queue_t; class event_queue_hook_t; namespace enveloped_msg { namespace impl { class agent_demand_handler_invoker_t; } /* namespace impl */ } /* namespace enveloped_msg */ namespace low_level_api { } /* namespace low_level_api */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/handler_makers.cpp000066400000000000000000000036311523065210100211610ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Implementation of some parts of handler makers which can't be inline. * * \since * v.5.5.23 */ #include #include namespace so_5 { namespace details { namespace { bool process_envelope_when_handler_found( const msg_type_and_handler_pair_t & handler, message_ref_t & message ) { using namespace so_5::enveloped_msg::impl; bool result = false; // We don't expect exceptions here and can't restore after them. so_5::details::invoke_noexcept_code( [&] { auto & envelope = message_to_envelope( message ); mchain_demand_handler_invoker_t invoker{ handler }; envelope.access_hook( so_5::enveloped_msg::access_context_t::handler_found, invoker ); result = invoker.was_handled(); } ); return result; } } /* namespace anonymous */ SO_5_FUNC bool handlers_bunch_basics_t::find_and_use_handler( const msg_type_and_handler_pair_t * left, const msg_type_and_handler_pair_t * right, const std::type_index & msg_type, message_ref_t & message ) { bool ret_value = false; msg_type_and_handler_pair_t key{ msg_type }; auto it = std::lower_bound( left, right, key ); if( it != right && it->m_msg_type == key.m_msg_type ) { // Handler is found and must be called. switch( message_kind( message ) ) { case message_t::kind_t::signal : [[fallthrough]]; case message_t::kind_t::classical_message : [[fallthrough]]; case message_t::kind_t::user_type_message : // This is an async message. // Simple call is enough. ret_value = true; it->m_handler( message ); break; case message_t::kind_t::enveloped_msg : // Invocation must be done a special way. ret_value = process_envelope_when_handler_found( *it, message ); break; } } return ret_value; } } /* namespace details */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/handler_makers.hpp000066400000000000000000000332111523065210100211630ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.13 * * \file * \brief Various stuff for creation of event handlers. */ #if !defined( SO_5_HANDLER_MAKERS_HPP ) #define SO_5_HANDLER_MAKERS_HPP #include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif namespace so_5 { namespace details { /*! * \brief Various helpers for event subscription. * * \since * v.5.3.0 */ namespace event_subscription_helpers { /*! * \brief Get actual agent pointer. * * \throw exception_t if dynamic_cast fails. */ template< class Agent > Agent * get_actual_agent_pointer( agent_t & agent ) { // Agent must have right type. Agent * cast_result = dynamic_cast< Agent * >( &agent ); // Was conversion successful? if( nullptr == cast_result ) { // No. Actual type of the agent is not convertible to the Agent. SO_5_THROW_EXCEPTION( rc_agent_incompatible_type_conversion, std::string( "Unable convert agent to type: " ) + typeid(Agent).name() ); } return cast_result; } /*! * \brief A helper template for create an argument for event handler * in the case when argument is passed as value or const reference. * * \note Msg can't be a type of a signal. * * \since * v.5.5.14 */ template< typename Msg > struct event_handler_arg_maker { using traits_type = message_payload_type< Msg >; static void ensure_appropriate_type() { ensure_not_signal< Msg >(); } static const Msg & make_arg( message_ref_t & mf ) { auto msg = message_payload_type< Msg >::extract_payload_ptr( mf ); return *msg; } }; /*! * \brief A helper template for create an argument for event handler * in the case when argument is passed as message hood. * \since * v.5.5.14 */ template< typename Msg > struct event_handler_arg_maker< mhood_t< Msg > > { using traits_type = message_payload_type< Msg >; static void ensure_appropriate_type() { } static mhood_t< Msg > make_arg( message_ref_t & mf ) { return mhood_t< Msg >{ mf }; } }; /*! * \brief A helper template for create an argument for event handler * in the case when argument is passed as message hood. * * \since * v.5.5.19 */ template< typename Msg > struct event_handler_arg_maker< mhood_t< immutable_msg > > { using traits_type = message_payload_type< immutable_msg >; static void ensure_appropriate_type() { } static mhood_t< immutable_msg > make_arg( message_ref_t & mf ) { return { mf }; } }; /*! * \brief A helper template for create an argument for event handler * in the case when argument is passed as message hood. * * \since * v.5.5.19 */ template< typename Msg > struct event_handler_arg_maker< mhood_t< mutable_msg > > { using traits_type = message_payload_type< mutable_msg< Msg > >; static void ensure_appropriate_type() { ensure_not_signal(); } static mhood_t< mutable_msg > make_arg( message_ref_t & mf ) { return { mf }; } }; /* * Disable usage of mutable_msg as parameter of an event handler. */ template< typename Msg > struct event_handler_arg_maker< mutable_msg > { using traits_type = message_payload_type< mutable_msg >; static void ensure_appropriate_type() { static_assert(always_false::value, "mutable_msg can't be used as type of event handler parameter"); } static mutable_msg make_arg( message_ref_t & ) { return {}; } }; /* * Disable usage of immutable_msg as parameter of an event handler. */ template< typename Msg > struct event_handler_arg_maker< immutable_msg > { using traits_type = message_payload_type< immutable_msg >; static void ensure_appropriate_type() { static_assert(always_false::value, "immutable_msg can't be used as type of event handler parameter"); } static immutable_msg make_arg( message_ref_t & ) { return {}; } }; /*! * \brief Helper template for creation of event handler with actual * argument. * * \since * v.5.5.14 */ template< typename Handler_Type, typename Result, typename Arg > msg_type_and_handler_pair_t make_handler_with_arg( Handler_Type lambda ) { using arg_maker = event_handler_arg_maker< Arg >; arg_maker::ensure_appropriate_type(); auto method = [lambda](message_ref_t & message_ref) mutable { lambda( arg_maker::make_arg( message_ref ) ); }; return msg_type_and_handler_pair_t{ arg_maker::traits_type::subscription_type_index(), method, arg_maker::traits_type::mutability() }; } /*! * \brief A function for creation event handler. * * \since * v.5.6.0 */ template< class Lambda > details::msg_type_and_handler_pair_t make_handler_from_lambda_of_free_function( Lambda && lambda ) { using namespace so_5::details::lambda_traits; using namespace so_5::details::event_subscription_helpers; using Traits = traits< typename std::decay< Lambda >::type >; using Result = typename Traits::result_type; using Message = typename Traits::argument_type; using Transformed_Lambda = typename Traits::pass_by_type; return make_handler_with_arg< Transformed_Lambda, Result, Message >( std::forward< Lambda >(lambda) ); } /*! * \brief Helper template for creation of event handler with actual * argument. * * \since * v.5.5.14 */ template< typename Agent, typename Method_Pointer > typename std::enable_if< is_agent_method_pointer< method_arity::unary, Method_Pointer>::value, msg_type_and_handler_pair_t >::type make_handler_with_arg_for_agent( Agent * agent, Method_Pointer pfn ) { using pfn_traits = is_agent_method_pointer; static_assert( std::is_same::value, "Agent type must be the same" ); using arg_maker = event_handler_arg_maker< typename so_5::details::lambda_traits::plain_argument_type< typename pfn_traits::argument_type>::type >; arg_maker::ensure_appropriate_type(); auto method = [agent, pfn](message_ref_t & message_ref) { (agent->*pfn)( arg_maker::make_arg( message_ref ) ); }; return msg_type_and_handler_pair_t{ arg_maker::traits_type::subscription_type_index(), method, arg_maker::traits_type::mutability() }; } /*! * \brief Ensure that mutability of message is compatible with * mutability of target mbox. * * \since * v.5.5.21 */ inline void ensure_handler_can_be_used_with_mbox( const ::so_5::details::msg_type_and_handler_pair_t & handler, const ::so_5::mbox_t & target_mbox ) { if( message_mutability_t::mutable_message == handler.m_mutability && mbox_type_t::multi_producer_multi_consumer == target_mbox->type() ) SO_5_THROW_EXCEPTION( rc_subscription_to_mutable_msg_from_mpmc_mbox, std::string( "subscription to mutable message from MPMC mbox " "is disabled, msg_type=" ) + handler.m_msg_type.name() ); } } /* namespace event_subscription_helpers */ } /* namespace details */ // // preprocess_agent_event_handler // /*! * \brief Do preprocessing and some verification of event handler * and return msg_type_and_handler_pair for it. * * This overload is intended to be used for pointers to members. * * \note * Throws is handler can't be used with this type of mbox * (for example: handler is for mutable message but mbox is * Multi-Consumer one). * * \since * v.5.5.21 */ template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>::value, details::msg_type_and_handler_pair_t >::type preprocess_agent_event_handler( const mbox_t & mbox, agent_t & agent, Method_Pointer pfn ) { using namespace details::event_subscription_helpers; using agent_type = typename details::is_agent_method_pointer< details::method_arity::unary, Method_Pointer>::agent_type; // Agent must have right type. auto cast_result = get_actual_agent_pointer< agent_type >( agent ); const auto ev = make_handler_with_arg_for_agent( cast_result, pfn ); ensure_handler_can_be_used_with_mbox( ev, mbox ); return ev; } // // preprocess_agent_event_handler // /*! * \brief Do preprocessing and some verification of event handler * and return msg_type_and_handler_pair for it. * * This overload is intended to be used for lambdas or functional objects. * * \attention * Only lambda functions or functional objects in the following format * are supported: * \code * ret_type (message_type); * ret_type (const message_type &); * ret_type (mhood_t); * \endcode * * \since * v.5.5.21 */ template< typename Lambda > typename std::enable_if< details::lambda_traits::is_lambda::value, details::msg_type_and_handler_pair_t >::type preprocess_agent_event_handler( const mbox_t & mbox, agent_t & /*agent*/, Lambda && lambda ) { using namespace details::event_subscription_helpers; const auto ev = make_handler_from_lambda_of_free_function( std::forward(lambda) ); ensure_handler_can_be_used_with_mbox( ev, mbox ); return ev; } namespace details { // // handlers_bunch_basics_t // /*! * \brief Basic part of handlers_bunch implementation. * * \note This part is not depends on template parameters. * \since * v.5.5.13 */ struct handlers_bunch_basics_t { /*! * \brief Preparation of message handlers vector. * * Message handlers vector must be ordered by msg_type. * And must not contain two or more handlers for the same msg_type. * * Vector to be processed is defined by range [left, right). * * \throw so_5::exception_t if there are several handlers for * the same message type. */ static inline void prepare_handlers( msg_type_and_handler_pair_t * left, msg_type_and_handler_pair_t * right ) { std::sort( left, right ); auto duplicate = std::adjacent_find( left, right ); if( duplicate != right ) SO_5_THROW_EXCEPTION( rc_several_handlers_for_one_message_type, std::string( "several handlers are defined for message; " "msg_type: " ) + duplicate->m_msg_type.name() ); } /*! * \brief Find and exec message handler. * * Message handlers vector to be used in handling of message * is defined by range [left, right). * * \note The message handler vector [left, right) must be previously * prepared by prepare_handlers(). * * \retval true if handler has been found * \retval false if handler has not been found. */ SO_5_FUNC static bool find_and_use_handler( const msg_type_and_handler_pair_t * left, const msg_type_and_handler_pair_t * right, const std::type_index & msg_type, message_ref_t & message ); }; // // handlers_bunch // /*! * \brief Template class for storing bunch of message handlers. * \since * v.5.5.13 */ template< std::size_t N > class handlers_bunch_t : private handlers_bunch_basics_t { //! Vector of message handlers. /*! * Will be ordered by msg_type after invoking prepare() method. */ msg_type_and_handler_pair_t m_handlers[ N ]; public : handlers_bunch_t() {} //! Add another handler to the specified index. void add_handler( //! Index for new handler. std::size_t index, //! Message handler to be added. msg_type_and_handler_pair_t && handler ) { m_handlers[ index ] = std::move(handler); } //! Prepare bunch to use with actual messages. /*! * \note This method must be called only after all handlers are * stored in m_handlers vector. */ void prepare() { prepare_handlers( m_handlers, m_handlers + N ); } //! Find handler for a message and execute it. /*! * \retval true if handler was found. * \retval false if handler was not found. */ bool handle( //! Type of a message or signal. const std::type_index & msg_type, //! Message instance to be processed. message_ref_t & message ) const { return find_and_use_handler( m_handlers, m_handlers + N, msg_type, message ); } }; /*! * \brief A specialization of handlers_bunch_t for the case when there * is no any handlers. * \since * v.5.5.13 */ template<> class handlers_bunch_t< 0 > { public : handlers_bunch_t() {} void prepare() { /* NOTHING TO DO! */ } bool handle( const std::type_index & /*msg_type*/, message_ref_t & /*message*/ ) const { return false; } }; // // fill_handlers_bunch // template< typename Bunch > void fill_handlers_bunch( Bunch & bunch, std::size_t ) { bunch.prepare(); } template< typename Bunch, typename... Others > void fill_handlers_bunch( //! What to fill. Bunch & bunch, //! An index for next handler. std::size_t index, //! Next handler to be inserted. msg_type_and_handler_pair_t && handler, //! All other handlers. Others &&... other_handlers ) { bunch.add_handler( index, std::move(handler) ); fill_handlers_bunch( bunch, index + 1, std::forward< Others >(other_handlers)... ); } template< typename Bunch, typename Lambda, typename... Others > void fill_handlers_bunch( //! What to fill. Bunch & bunch, //! An index for next handler. std::size_t index, //! Next handler to be inserted. Lambda && lambda, //! All other handlers. Others &&... other_handlers ) { using namespace event_subscription_helpers; bunch.add_handler( index, make_handler_from_lambda_of_free_function( std::forward( lambda ) ) ); fill_handlers_bunch( bunch, index + 1, std::forward< Others >(other_handlers)... ); } } /* namespace details */ } /* namespace so_5 */ #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic pop #endif #endif sobjectizer-5.8.6/dev/so_5/impl/000077500000000000000000000000001523065210100164345ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/impl/agent_ptr_compare.hpp000066400000000000000000000017551523065210100226460ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief A special comparison for agent pointers. * * \since v.5.5.8 */ #if !defined( SO_5_IMPL_AGENT_PTR_COMPARE_HPP ) #define SO_5_IMPL_AGENT_PTR_COMPARE_HPP #include #include namespace so_5 { namespace impl { /*! * \brief A special comparison for agent pointers with respect to * agent's priorities. * * \retval true if \a a is less than \a b. * * \since v.5.5.8 */ [[nodiscard]] inline bool special_agent_ptr_compare( const agent_t & a, const agent_t & b ) noexcept { // Use std::less for compare pointers. // NOTE: it's UB to compare pointers that do not belong to the same array. using ptr_comparator_t = std::less< const agent_t * >; auto p1 = a.so_priority(); auto p2 = b.so_priority(); // NOTE: there should be inversion -- agent with higher // priority must be first. return ( p1 > p2 || (p1 == p2 && ptr_comparator_t{}( &a, &b ) ) ); } } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/coop_private_iface.hpp000066400000000000000000000054721523065210100227760ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Private interface for a coop. * * \since * v.5.6.0 */ #if !defined( SO_5_IMPL_COOP_PRIVATE_IFACE_HPP ) #define SO_5_IMPL_COOP_PRIVATE_IFACE_HPP #include namespace so_5 { namespace impl { // // coop_private_iface_t // /*! * \since * v.5.2.3 * * \brief A special class for accessing private members of agent_coop. */ class coop_private_iface_t { public : static coop_unique_holder_t make_coop( coop_id_t id, coop_handle_t parent, disp_binder_shptr_t default_binder, outliving_reference_t< environment_t > env ) { return { coop_shptr_t { new coop_t{ id, std::move(parent), std::move(default_binder), env } } }; } static coop_shptr_t make_from( coop_unique_holder_t holder ) noexcept { return holder.release(); } static void increment_usage_count( coop_t & coop ) noexcept { coop.increment_usage_count(); } static void decrement_usage_count( coop_t & coop ) { coop.decrement_usage_count(); } static void destroy_content( coop_t & coop ) { coop_impl_t::destroy_content( coop ); } static void do_registration_specific_actions( coop_t & coop ) { coop_impl_t::do_registration_specific_actions( coop ); } static void do_final_deregistration_actions( coop_t & coop ) { coop_impl_t::do_final_deregistration_actions( coop ); } static coop_reg_notificators_container_ref_t giveout_reg_notificators( coop_t & coop ) noexcept { return std::move(coop.m_reg_notificators); } static coop_dereg_notificators_container_ref_t giveout_dereg_notificators( coop_t & coop ) noexcept { return std::move(coop.m_dereg_notificators); } static coop_dereg_reason_t dereg_reason( const coop_t & coop ) noexcept { return coop.m_dereg_reason; } /*! * \brief Set the next coop in the chain of coops for the final * deregistration. * * It's expected that \a coop is the last item in this chain at * the moment. After completion the \a next_in_chain will be the * last item in the chain. * * \since v.5.8.0 */ static void set_next_in_final_dereg_chain( coop_t & coop, coop_shptr_t next_in_chain ) noexcept { coop.m_next_in_final_dereg_chain = std::move(next_in_chain); } /*! * \brief Extract a smart pointer to the next coop in the chain of * coops for the final deregistration. * * It may return empty coop_shptr_t if \a coop is the last item in * this chain. * * \since v.5.8.0 */ [[nodiscard]] static coop_shptr_t giveout_next_in_final_dereg_chain( coop_t & coop ) noexcept { return std::exchange( coop.m_next_in_final_dereg_chain, coop_shptr_t{} ); } }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/coop_repository_basis.cpp000066400000000000000000000207571523065210100235730ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Basic part of coop_repository functionality. * * \since * v.5.5.19 */ #include #include #include #include #include #include namespace so_5 { namespace impl { // // root_coop_t // /*! * \brief A type of special coop that will be used as the root coop. * * \since * v.5.6.0 */ class coop_repository_basis_t::root_coop_t final : public coop_t { public : root_coop_t( coop_id_t id, outliving_reference_t< environment_t > env ) : coop_t{ id, coop_handle_t{}, // No parent coop. disp_binder_shptr_t{}, // No binder. env } { // Automaticaly increment usage count to prevent // deregistration when last child coop will be destroyed. coop_private_iface_t::increment_usage_count( *this ); // Status of the coop should be changed because // this coop is already "registered". m_registration_status = registration_status_t::coop_registered; } void deregister_children_on_shutdown() noexcept { // List of children coop should be processed when // the object is locked. so_5::details::tsan_friendly_lock_guard_t< std::mutex > lock{ m_lock }; // Every child should be deregistered with 'shutdown' reason. for_each_child( []( coop_t & child ) { child.deregister( dereg_reason::shutdown ); } ); } }; coop_repository_basis_t::coop_repository_basis_t( outliving_reference_t< environment_t > environment, coop_listener_unique_ptr_t coop_listener ) : m_env{ environment } , m_coop_listener{ std::move(coop_listener) } { m_root_coop = std::make_shared< root_coop_t >( ++m_coop_id_counter, m_env ); } [[nodiscard]] coop_unique_holder_t coop_repository_basis_t::make_coop( coop_handle_t parent, disp_binder_shptr_t default_binder ) { if( !parent ) parent = m_root_coop->handle(); return coop_private_iface_t::make_coop( ++m_coop_id_counter, std::move(parent), std::move(default_binder), m_env ); } [[nodiscard]] coop_handle_t coop_repository_basis_t::register_coop( coop_unique_holder_t coop_ptr ) { // Phase 1: check the posibility of registration of new coop. // This check should be performed on locked object. { std::lock_guard< std::mutex > lock{ m_lock }; if( status_t::normal != m_status ) SO_5_THROW_EXCEPTION( rc_unable_to_register_coop_during_shutdown, "a new coop can't be registered when shutdown " "is in progress" ); // A counter of registration in progress should be incremented // to prevent shutdown. ++m_registrations_in_progress; } // Note: this code should be called only on locked object. const auto handle_registrations_in_progress = [this]() noexcept { // Decrement count of registration in progress to enable // pending shutdown (if it is). --m_registrations_in_progress; // If it was the last registration and there is a pending // shutdown then we should enable shutdown procedure. if( 0u == m_registrations_in_progress && status_t::pending_shutdown == m_status ) { m_shutdown_enabled_cond.notify_one(); } }; // Phase 2: registration by itself. coop_handle_t result; try { // This values is necessary for updating stats if registration // will be successful. const auto coop_size = coop_ptr->size(); // Should be performed on unlocked object. result = do_registration_specific_actions( std::move(coop_ptr) ); // Phase 3: finishing of registration. // Those actions should be performed on locked objects. details::invoke_noexcept_code( [&] { std::lock_guard< std::mutex > lock{ m_lock }; // Statistics should be updated. m_total_agents += coop_size; m_total_coops += 1; handle_registrations_in_progress(); } ); } catch( ... ) { // Pending shutdown should be handled even in the presence // of an exception. details::invoke_noexcept_code( [&] { std::lock_guard< std::mutex > lock{ m_lock }; handle_registrations_in_progress(); } ); // Exception should be handled at some higher level. throw; } return result; } coop_repository_basis_t::final_deregistration_result_t coop_repository_basis_t::final_deregister_coop( coop_shptr_t coop ) noexcept { // Count of live agent and coops should be decremented. { std::lock_guard< std::mutex > lock{ m_lock }; m_total_agents -= coop->size(); --m_total_coops; } // Coop should perform its final actions. coop_private_iface_t::do_final_deregistration_actions( *coop ); // Now the coop object should be released. // But before that we should store some values from it // to process dereg notifications. const auto handle = coop->handle(); const auto dereg_reason = coop_private_iface_t::dereg_reason( *coop ); const auto dereg_notificators = coop_private_iface_t::giveout_dereg_notificators( *coop ); // Release the coop. coop.reset(); // Coop's dereg notificators can be processed now. if( dereg_notificators ) dereg_notificators->call_all( m_env.get(), handle, dereg_reason ); // Coop's listener should be notified. if( m_coop_listener ) m_coop_listener->on_deregistered( m_env.get(), handle, dereg_reason ); // This additional lock is necessary because a new coop // can be registered while final deregistration actions // were in progress. return [this] { std::lock_guard< std::mutex > lock{ m_lock }; return final_deregistration_result_t{ // Coops those are in registration phase should also // be counted as live. 0u != m_total_coops || 0 != m_registrations_in_progress, status_t::shutdown == m_status && 0u == m_total_coops }; }(); } void coop_repository_basis_t::deregister_all_coop() noexcept { // Phase 1: check that shutdown is not in progress now. { std::unique_lock< std::mutex > lock{ m_lock }; if( !m_registrations_in_progress ) { m_status = status_t::shutdown; // NOTE: there is no need to wait on m_shutdown_enabled_cond // because there is no active registration procedures. } else { // There are active registration procedures. // We should wait for their completion. m_status = status_t::pending_shutdown; m_shutdown_enabled_cond.wait( lock, [this] { return 0 == m_registrations_in_progress; } ); m_status = status_t::shutdown; } } // Phase 2: deregistration of all coops. m_root_coop->deregister_children_on_shutdown(); } environment_t & coop_repository_basis_t::environment() { return m_env.get(); } environment_infrastructure_t::coop_repository_stats_t coop_repository_basis_t::query_stats() { std::lock_guard< std::mutex > lock{ m_lock }; return { m_total_coops, m_total_agents, 0u }; } namespace { /*! * \since * v.5.2.3 * * \brief Special guard to increment and decrement cooperation * usage counters. */ class coop_usage_counter_guard_t { public : coop_usage_counter_guard_t( coop_t & coop ) : m_coop( coop ) { coop_private_iface_t::increment_usage_count( coop ); } ~coop_usage_counter_guard_t() { coop_private_iface_t::decrement_usage_count( m_coop ); } private : coop_t & m_coop; }; } /* namespace anonymous */ coop_handle_t coop_repository_basis_t::do_registration_specific_actions( coop_unique_holder_t coop_ptr ) { // Cooperation object should life to the end of this routine. coop_shptr_t coop{ coop_private_iface_t::make_from( std::move(coop_ptr) ) }; // This guard will guarantee, that reg_notificators for // successfully registered coop will be called and completed before // the dereg_notificators for that coop. // Even if the coop will be deregitered while reg_notificators are // still working. coop_usage_counter_guard_t guard{ *coop }; coop_private_iface_t::do_registration_specific_actions( *coop ); auto result = coop->handle(); // We don't expect exceptions from the following actions. so_5::details::invoke_noexcept_code( [&] { // Coop's dereg notificators can be processed now. auto reg_notificators = coop_private_iface_t::giveout_reg_notificators( *coop ); if( reg_notificators ) reg_notificators->call_all( m_env.get(), result ); // Coop's listener should be notified. if( m_coop_listener ) m_coop_listener->on_registered( m_env.get(), result ); } ); return result; } } /* namespace impl */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/coop_repository_basis.hpp000066400000000000000000000107621523065210100235730ustar00rootroot00000000000000/* SObjectizer 5. */ /*! * \file * \brief Basic part of coop_repository functionality. * * \since * v.5.5.19 */ #if !defined( SO_5_IMPL_COOP_REPOSITORY_BASIS_HPP ) #define SO_5_IMPL_COOP_REPOSITORY_BASIS_HPP #include #include #include #include #include #include #include #include #include #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { namespace impl { // // coop_repository_basis_t // /*! * \brief A basic part for various implementations of coop_repository. * * \since * v.5.5.19 */ class SO_5_TYPE coop_repository_basis_t { coop_repository_basis_t( const coop_repository_basis_t & ) = delete; coop_repository_basis_t( coop_repository_basis_t && ) = delete; public: coop_repository_basis_t( //! SObjectizer Environment. outliving_reference_t< environment_t > environment, //! Cooperation action listener. coop_listener_unique_ptr_t coop_listener ); //! Create an instance of a new coop. /*! * \since * v.5.6.0 */ [[nodiscard]] coop_unique_holder_t make_coop( //! Parent coop. //! Can be empty. In that case there won't be any parent coop. coop_handle_t parent, //! Default binder for that coop. disp_binder_shptr_t default_binder ); //! Register cooperation. [[nodiscard]] coop_handle_t register_coop( //! Cooperation to be registered. coop_unique_holder_t agent_coop ); /*! * Type for return value of final_deregister_coop method. * * This method must return two boolean values: * - the first value equals to true if there is at least one live coop; * - the second value equals to true if total deregistration is in * progress and there is no more live coops; * * \since * v.5.5.19 */ struct [[nodiscard]] final_deregistration_result_t { bool m_has_live_coop; bool m_total_deregistration_completed; final_deregistration_result_t( bool has_live_coop, bool total_deregistration_completed ) : m_has_live_coop( has_live_coop ) , m_total_deregistration_completed( total_deregistration_completed ) {} }; //! Do final actions of the cooperation deregistration. /*! * \note * This method is noexcept since v.5.8.0. * * \retval true there are some live cooperations. * \retval false there is no more live cooperations. */ final_deregistration_result_t final_deregister_coop( //! Cooperation name to be deregistered. coop_shptr_t coop ) noexcept; //! Deregisted all cooperations. /*! * All cooperations will be deregistered at the SObjectizer shutdown. * * \return Count of coops to be completely deregistered. */ void deregister_all_coop() noexcept; /*! * \since * v.5.5.0 * * \brief Access to SObjectizer Environment. * * \note This access is necessary to use error_logger for * logging error messages. */ [[nodiscard]] environment_t & environment(); /*! * \since * v.5.5.4 * * \brief Get the current statistic for run-time monitoring. */ [[nodiscard]] environment_infrastructure_t::coop_repository_stats_t query_stats(); protected: class root_coop_t; //! Enumeration of possible repository statuses. enum class status_t { normal, pending_shutdown, shutdown }; //! Environment to work in. outliving_reference_t< environment_t > m_env; //! Counter for coop_ids. std::atomic_uint_fast64_t m_coop_id_counter{ 0 }; //! Lock for coop repository. std::mutex m_lock; //! Status of repository. status_t m_status{ status_t::normal }; //! Condition variable to wait a possibility to do actions //! in deregister_all_coop. std::condition_variable m_shutdown_enabled_cond; //! Total count of coops. std::size_t m_total_coops{}; //! Total count of agents. std::size_t m_total_agents{}; //! Count of coops those are in registration now. std::size_t m_registrations_in_progress{}; //! Cooperation actions listener. coop_listener_unique_ptr_t m_coop_listener; //! A special root coop. /*! * \attention * This coop can't be deregistered! */ std::shared_ptr< root_coop_t > m_root_coop; //! An actual implementation of registration of a coop. [[nodiscard]] coop_handle_t do_registration_specific_actions( coop_unique_holder_t coop_ptr ); }; } /* namespace impl */ } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/impl/delivery_filter_storage.hpp000066400000000000000000000103761523065210100240700ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.5 * * \file * \brief Definition of storage for delivery filters. */ #if !defined( SO_5_IMPL_DELIVERY_FILTER_STORAGE_HPP ) #define SO_5_IMPL_DELIVERY_FILTER_STORAGE_HPP #include #include #include #include #include namespace so_5 { namespace impl { // // delivery_filter_storage_t // /*! * \since * v.5.5.5 * * \brief Storage for message delivery filters. * * \attention This storage do not drop filters in the destructor. * It is because the reference to agent is necessary for filter dropping. * Storage do not have this reference. * Because of that it is necessary to drop all filters explicitely * by drop_all() method. */ class delivery_filter_storage_t { //! Type of key for filters map. struct key_t { //! Message mbox. mbox_t m_mbox; //! Message type. std::type_index m_msg_type; bool operator<( const key_t & o ) const { const auto id_a = m_mbox->id(); const auto id_b = o.m_mbox->id(); return id_a < id_b || ( id_a == id_b && m_msg_type < o.m_msg_type ); } }; //! Type of value for filters map. struct value_t { //! Delivery filter. /*! * @note * An instance of value_t owns the filter! */ delivery_filter_unique_ptr_t m_filter; //! Message sink for that the filter was set. /*! * A reference to message_sink is used instead of raw (non-owning) pointer * to indicate that a valid reference is required here (null is a valid * value for a pointer). * * @note * The lifetime of the sink is controlled elsewhere. */ std::reference_wrapper< abstract_message_sink_t > m_sink; value_t( delivery_filter_unique_ptr_t filter, so_5::outliving_reference_t< abstract_message_sink_t > sink ) : m_filter{ std::move(filter) } , m_sink{ sink.get() } {} }; //! Type of filters map. using map_t = std::map< key_t, value_t >; //! Information about defined filters. map_t m_filters; public : //! Drop all defined filters. /*! * Filters are removed from corresponding mboxes and destroyed. */ void drop_all() noexcept { for( auto & [k, v] : m_filters ) k.m_mbox->drop_delivery_filter( k.m_msg_type, v.m_sink.get() ); m_filters.clear(); } //! Set a delivery filter. /*! * If there already is previous filter it will be destroyed. */ void set_delivery_filter( const mbox_t & mbox, const std::type_index & msg_type, delivery_filter_unique_ptr_t filter, so_5::outliving_reference_t< abstract_message_sink_t > owner ) { const key_t key{ mbox, msg_type }; auto it = m_filters.find( key ); if( it == m_filters.end() ) { // There is no previous filter. // New filter must be added. auto ins_result = m_filters.emplace( key, value_t{ std::move( filter ), owner } ); so_5::details::do_with_rollback_on_exception( [&] { auto & insertion_it = ins_result.first; mbox->set_delivery_filter( msg_type, *(insertion_it->second.m_filter), owner.get() ); }, [this, &ins_result] { auto & insertion_it = ins_result.first; m_filters.erase( insertion_it ); } ); } else { // Replace previous filter with new one. value_t old_value{ std::move(it->second) }; it->second = value_t{ std::move( filter ), owner }; // Mbox must change delivery filter too. so_5::details::do_with_rollback_on_exception( [&] { mbox->set_delivery_filter( msg_type, *(it->second.m_filter), owner.get() ); }, [&] { it->second = std::move(old_value); } ); } } //! Remove delivery filter. void drop_delivery_filter( const mbox_t & mbox, const std::type_index & msg_type ) noexcept { auto it = m_filters.find( key_t{ mbox, msg_type } ); if( it != m_filters.end() ) { mbox->drop_delivery_filter( msg_type, it->second.m_sink.get() ); m_filters.erase( it ); } } }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/enveloped_msg_details.hpp000066400000000000000000000124731523065210100235100ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Some implementation details for enveloped messages. * * \since v.5.5.23 */ #if !defined( SO_5_IMPL_ENVELOPED_MSG_DETAILS_HPP ) #define SO_5_IMPL_ENVELOPED_MSG_DETAILS_HPP #include #include #include #include namespace so_5 { namespace enveloped_msg { namespace impl { // // message_to_envelope // /*! * \brief A helper function for casting message instance to * envelope instance. * * \note * This function throws if message is not an instance of envelope_t. * * \note * This function throws if \a src_msg is nullptr. * * \since v.5.5.23 */ [[nodiscard]] inline envelope_t & message_to_envelope( //! Message for casting. const message_ref_t & src_msg ) { // Pointer to message object can't be null. We can't continue our work // if it is null. auto * raw_msg_ptr = src_msg.get(); if( !raw_msg_ptr ) SO_5_THROW_EXCEPTION( rc_attempt_to_cast_to_envelope_on_nullptr, std::string( "Unexpected error: pointer to enveloped_msg is null." ) ); // If message is not instance of envelope_t an exception will be // thrown here by C++ run-time. return dynamic_cast(*raw_msg_ptr); } #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnon-virtual-dtor" #endif // // agent_demand_handler_invoker_t // /*! * \brief An implementation of handler_invoker interface. * * \since v.5.5.23 */ class agent_demand_handler_invoker_t : public handler_invoker_t { const current_thread_id_t m_work_thread_id; execution_demand_t & m_demand; const so_5::impl::event_handler_data_t & m_handler_data; public: //! Initializing constructor. agent_demand_handler_invoker_t( current_thread_id_t work_thread_id, execution_demand_t & demand, const so_5::impl::event_handler_data_t & handler_data ) : m_work_thread_id( work_thread_id ) , m_demand( demand ) , m_handler_data( handler_data ) {} ~agent_demand_handler_invoker_t() = default; void invoke( const payload_info_t & payload ) noexcept override { const auto msg_kind = message_kind( payload.message() ); execution_demand_t fresh_demand{ m_demand.m_receiver, m_demand.m_limit, m_demand.m_mbox_id, m_demand.m_msg_type, payload.message(), // May be it is not necessary at all but it // is better to have properly constructed demand. demand_handler_for_invocation_type( msg_kind ) }; switch( msg_kind ) { case message_t::kind_t::signal : [[fallthrough]]; case message_t::kind_t::classical_message : [[fallthrough]]; case message_t::kind_t::user_type_message : agent_t::process_message( m_work_thread_id, fresh_demand, m_handler_data.m_thread_safety, m_handler_data.m_method ); break; case message_t::kind_t::enveloped_msg : agent_t::process_enveloped_msg( m_work_thread_id, fresh_demand, m_handler_data ); break; } } private: /*! * Returns appropriate pointer to demand handler in dependency * on invocation type for that demand. */ static demand_handler_pfn_t demand_handler_for_invocation_type( message_t::kind_t msg_kind ) noexcept { demand_handler_pfn_t result = nullptr; switch( msg_kind ) { case message_t::kind_t::signal : [[fallthrough]]; case message_t::kind_t::classical_message : [[fallthrough]]; case message_t::kind_t::user_type_message : result = &agent_t::demand_handler_on_message; break; case message_t::kind_t::enveloped_msg : result = &agent_t::demand_handler_on_enveloped_msg; break; } return result; } }; // // mchain_demand_handler_invoker_t // /*! * \brief An implementation of handler_invoker interface for * the case when a mesage was sent to mchain. * * \since v.5.5.23 */ class mchain_demand_handler_invoker_t : public handler_invoker_t { const ::so_5::details::msg_type_and_handler_pair_t & m_handler; bool m_was_handled; public: //! Initializing constructor. mchain_demand_handler_invoker_t( const ::so_5::details::msg_type_and_handler_pair_t & handler ) : m_handler( handler ) , m_was_handled( false ) {} ~mchain_demand_handler_invoker_t() = default; bool was_handled() const noexcept { return m_was_handled; } void invoke( const payload_info_t & payload ) noexcept override { const auto msg_kind = message_kind( payload.message() ); switch( msg_kind ) { case message_t::kind_t::signal : [[fallthrough]]; case message_t::kind_t::classical_message : [[fallthrough]]; case message_t::kind_t::user_type_message : m_was_handled = true; m_handler.m_handler( payload.message() ); break; case message_t::kind_t::enveloped_msg : { // Do recurvise call. // Value for was_handled will be detected in the // nested call. auto & nested_envelope = message_to_envelope( payload.message() ); nested_envelope.access_hook( access_context_t::handler_found, *this ); } break; } } }; #if defined(__clang__) #pragma clang diagnostic pop #endif } /* namespace impl */ } /* namespace enveloped_msg */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/final_dereg_chain_helpers.hpp000066400000000000000000000104771523065210100243010ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helpers for working with a chain of coops for the final deregistration. * \since v.5.8.0 */ #if !defined( SO_5_IMPL_FINAL_DEREG_CHAIN_HELPERS_HPP ) #define SO_5_IMPL_FINAL_DEREG_CHAIN_HELPERS_HPP #include #include #include #include namespace so_5::impl { // // final_dereg_chain_holder_t // /*! * \brief Helper class for holding the current chain of coops for * the final deregistration. * * This chain is an intrusive list (via coop_t::m_next_in_final_dereg_chain), * but it's necessary to hold the head and the tail of this chain. * Class final_dereg_chain_holder_t does exactly that: it holds the * current head, the tail and the number of items it the chain. * * New items have to be added by append() method. * * When the current chain has to be processed it must be extracted * from the holder by using giveout_current_chain() method. * * \attention * This class isn't thread-safe. If thread-safety is necessary then * it should be provided by a user of this class. * * \note * This class isn't Copyable, not Moveable. * * \since v.5.8.0 */ class final_dereg_chain_holder_t { /*! * \brief Number of items in the chain of coops for the final * deregistration. * * This value is necessary for stats. * * \since v.5.8.0 */ std::size_t m_final_dereg_chain_size{}; /*! * \brief The head of the chain of coops for the final deregistration. * * It may be nullptr. It means that the chain is empty now. * * \since v.5.8.0 */ coop_shptr_t m_final_dereg_chain_head; /*! * \brief The tail of the chain of coops for the final deregistration. * * This value is used for fast addition of a new coop to the chain. * * It may be nullptr in the case when the chain is empty. * * \since v.5.8.0 */ coop_shptr_t m_final_dereg_chain_tail; public: final_dereg_chain_holder_t() = default; #if 1 ~final_dereg_chain_holder_t() = default; #else //NOTE: this code was kept here for testing purposes. ~final_dereg_chain_holder_t() { if( 0u != m_final_dereg_chain_size ) { std::cerr << "*** ~final_dereg_chain_holder_t: " "unexpected m_final_dereg_chain_size: " << m_final_dereg_chain_size << std::endl; std::abort(); } } #endif final_dereg_chain_holder_t( const final_dereg_chain_holder_t & ) = delete; final_dereg_chain_holder_t & operator=( const final_dereg_chain_holder_t & ) = delete; final_dereg_chain_holder_t( final_dereg_chain_holder_t && ) = delete; final_dereg_chain_holder_t & operator=( final_dereg_chain_holder_t && ) = delete; void append( coop_shptr_t coop ) noexcept { ++m_final_dereg_chain_size; if( !m_final_dereg_chain_head ) m_final_dereg_chain_head = coop; if( m_final_dereg_chain_tail ) coop_private_iface_t::set_next_in_final_dereg_chain( *m_final_dereg_chain_tail, coop ); m_final_dereg_chain_tail = std::move(coop); } [[nodiscard]] std::size_t size() const noexcept { return m_final_dereg_chain_size; } [[nodiscard]] bool empty() const noexcept { return !static_cast( m_final_dereg_chain_head ); } [[nodiscard]] coop_shptr_t giveout_current_chain() noexcept { coop_shptr_t head = std::exchange( m_final_dereg_chain_head, coop_shptr_t{} ); m_final_dereg_chain_tail = coop_shptr_t{}; m_final_dereg_chain_size = 0u; return head; } }; // // process_final_dereg_chain // /*! * \brief Helper function that does proceesing of final dereg chain. * * Usage example: * \code * final_dereg_chain_holder_t holder; * ... * holder.append(coop); * ... * holder.append(coop); * ... * process_final_dereg_chain(holder.giveout_current_chain()); * \endcode * * \since v.5.8.0 */ inline void process_final_dereg_chain( coop_shptr_t head ) noexcept { // Do final_deregister_coop for every item in the chain // one by one. while( head ) { coop_shptr_t next = coop_private_iface_t::giveout_next_in_final_dereg_chain( *head ); auto & env = head->environment(); internal_env_iface_t{ env }.final_deregister_coop( std::move(head) ); head = std::move(next); } } } /* namespace so_5::impl */ #endif sobjectizer-5.8.6/dev/so_5/impl/internal_agent_iface.hpp000066400000000000000000000047571523065210100233030ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.6.0 * * \file * \brief Helper class for accessing private functionality of agent-class. */ #if !defined( SO_5_IMPL_INTERNAL_AGENT_IFACE_HPP ) #define SO_5_IMPL_INTERNAL_AGENT_IFACE_HPP #include #include namespace so_5 { namespace impl { // // internal_agent_iface_t // /*! * \brief A special class for access to private/protected members * of agent class. * * \since * v.5.6.0 */ class internal_agent_iface_t final { agent_t & m_agent; public: explicit internal_agent_iface_t( agent_t & agent ) noexcept : m_agent{ agent } {} void bind_to_coop( coop_t & coop ) { m_agent.bind_to_coop( coop ); } void initiate_agent_definition() { m_agent.so_initiate_agent_definition(); } void shutdown_agent() noexcept { m_agent.shutdown_agent(); } /*! * \brief Setter for disp_binder. * * \attention * It's not a thread safe method. But it's expected that it will be used * in the context where just one entity owns the pointer to the agent. * * \since v.5.7.5 */ void set_disp_binder( disp_binder_shptr_t binder ) { if( m_agent.m_disp_binder ) SO_5_THROW_EXCEPTION( rc_disp_binder_already_set_for_agent, "m_agent.m_disp_binder is not nullptr when " "set_disp_binder is called" ); m_agent.m_disp_binder = std::move(binder); } /*! * \brief Getter for disp_binder. * * \attention * It's not a thread safe method. But it's expected that it will be used * in the context where just one entity owns the pointer to the agent. * * \since v.5.7.5 */ [[nodiscard]] disp_binder_t & query_disp_binder() const { if( !m_agent.m_disp_binder ) SO_5_THROW_EXCEPTION( rc_no_disp_binder_for_agent, "m_agent.m_disp_binder is nullptr when " "query_disp_binder is called" ); return *m_agent.m_disp_binder; } /*! * \brief Helper method that drops pointer to disp_binder. * * This method is intended to be used for rollback actions * (for example, when disp_binder is set for the agent, but * agent can't be stored in a coop). * * \attention * It's not a thread safe method. But it's expected that it will be used * in the context where just one entity owns the pointer to the agent. * * \since v.5.7.5 */ void drop_disp_binder() noexcept { m_agent.m_disp_binder.reset(); } }; } /* namespace impl */ } /* so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/internal_env_iface.hpp000066400000000000000000000145661523065210100227740ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.9 * * \file * \brief Helper class for accessing private functionality of environment-class. */ #if !defined( SO_5_IMPL_INTERNAL_ENV_IFACE_HPP ) #define SO_5_IMPL_INTERNAL_ENV_IFACE_HPP #include #include namespace so_5 { namespace impl { // // internal_env_iface_t // /* * NOTE: the implementation of this class is in environment.cpp file. */ /*! * \since * v.5.5.9 * * \brief A helper class for accessing the functionality of * environment-class which is specific for SObjectizer internals only. */ class SO_5_TYPE internal_env_iface_t { //! Environment instance to work with. environment_t & m_env; public : //! Initializing constructor. internal_env_iface_t( environment_t & env ) : m_env( env ) {} //! Create multi-producer/single-consumer mbox that handles message limits. [[nodiscard]] mbox_t create_ordinary_mpsc_mbox( //! The only consumer for the messages. agent_t & single_consumer ); //! Create multi-producer/single-consumer mbox that ignores message limits. [[nodiscard]] mbox_t create_limitless_mpsc_mbox( //! The only consumer for the messages. agent_t & single_consumer ); //! Notification about readiness to the deregistration. void ready_to_deregister_notify( //! Cooperation which is ready to be deregistered. coop_shptr_t coop ) noexcept; //! Do the final actions of a cooperation deregistration. /*! * \note * This method is noexcept since v.5.8.0 */ void final_deregister_coop( //! Cooperation name to be deregistered. coop_shptr_t coop ) noexcept; //! Is message delivery tracing enabled? bool is_msg_tracing_enabled() const; //! Get access to message delivery tracer stuff holder. /*! * \throw exception_t if message delivery tracing is disabled. */ [[nodiscard]] so_5::msg_tracing::holder_t & msg_tracing_stuff() const; //! Get access to message delivery tracer stuff holder. /*! * This method doesn't throw if delivery tracing is disabled. * It was introduced in v.5.7.0 to allow the creation of * custom mboxes and mchains in separate libraries (like so5extra). * * \since * v.5.7.0 */ [[nodiscard]] so_5::msg_tracing::holder_t & msg_tracing_stuff_nonchecked() const noexcept; //! Get default lock_factory for MPSC queues. /*! * \since * v.5.5.18 */ so_5::disp::mpsc_queue_traits::lock_factory_t default_mpsc_queue_lock_factory() const; //! Get default lock_factory for MPMC queues. /*! * \since * v.5.5.18 */ so_5::disp::mpmc_queue_traits::lock_factory_t default_mpmc_queue_lock_factory() const; /*! * \name Methods for working with event_queue_hooks * \{ */ /*! * \brief Call the event_queue_hook when an agent is being bound * to a particular event_queue. * * An agent should call this method when it is being bound to a * particular event queue. * * \attention * The pointer returned should be used as pointer to event queue. * * \since * v.5.5.24 */ [[nodiscard]] event_queue_t * event_queue_on_bind( agent_t * agent, event_queue_t * original_queue ) noexcept; /*! * \brief Call the event_queue_hook when an agent is being unbound * from its event_queue. * * An agent should call this method when it is being unbound from its * event queue. * * \attention * The value \a queue should be exactly the value returned from * previous call to event_queue_on_bind(). * * \since * v.5.5.24 */ void event_queue_on_unbind( agent_t * agent, event_queue_t * queue ) noexcept; /*! * \} */ /*! * \brief Allocate a new ID for a new custom mbox or mchain. * * \since v.5.7.0 */ [[nodiscard]] mbox_id_t allocate_mbox_id() noexcept; /*! * \brief Get the default storage subscription factory. * * \since v.5.8.2 */ [[nodiscard]] subscription_storage_factory_t default_subscription_storage_factory() const noexcept( noexcept(subscription_storage_factory_t{} = subscription_storage_factory_t{}) ); }; /*! * \brief Helper function to be used for extraction of lock_factory for MPSC queues. * * \note Intended to be used in template code: * \code template< typename Disp_Params > auto safe_get_lock_factory( so_5::environment_t & env, Disp_Params & params ) { auto lf = params.queue_params().lock_factory(); if( !lf ) lf = so_5::impl::default_lock_factory( env, lf ); return lf; } * \endcode * A call to default_lock_factory() will return appropriate lock_factory * for MPSC or MPMC queues. * * \since * v.5.5.18 */ inline so_5::disp::mpsc_queue_traits::lock_factory_t default_lock_factory( environment_t & env, const so_5::disp::mpsc_queue_traits::lock_factory_t & ) { return internal_env_iface_t{ env }.default_mpsc_queue_lock_factory(); } /*! * \brief Helper function to be used for extraction of lock_factory for MPSC queues. * * \note Intended to be used in template code: * \code template< typename Disp_Params > auto safe_get_lock_factory( so_5::environment_t & env, Disp_Params & params ) { auto lf = params.queue_params().lock_factory(); if( !lf ) lf = so_5::impl::default_lock_factory( env, lf ); return lf; } * \endcode * A call to default_lock_factory() will return appropriate lock_factory * for MPSC or MPMC queues. * * \since * v.5.5.18 */ inline so_5::disp::mpmc_queue_traits::lock_factory_t default_lock_factory( environment_t & env, const so_5::disp::mpmc_queue_traits::lock_factory_t & ) { return internal_env_iface_t{ env }.default_mpmc_queue_lock_factory(); } // // wrap_init_fn_call // /*! * \brief A special wrapper for calling init function. * * This wrapper calls init function and catch exception. * If an exception is derived from std::exception it will be rethrown. * If an exception is caught by catch(...) statement then an instance * of so_5::exception_t will be thrown instead of caught exception. * * \since * v.5.5.24.3 */ template< typename Init_Fn > void wrap_init_fn_call( Init_Fn init_fn ) { try { init_fn(); } catch( const std::exception & ) { throw; // Exception derived from std::exception // will be caught automatically. } catch( ... ) { SO_5_THROW_EXCEPTION( rc_unknown_exception_type, "exception of unknown type is thrown from init function" ); } } } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/layer_core.cpp000066400000000000000000000122701523065210100212660ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include #include #include namespace so_5 { namespace impl { // // typed_layer_ref_t // typed_layer_ref_t::typed_layer_ref_t() : m_true_type( std::type_index( typeid( int ) ) ) { } typed_layer_ref_t::typed_layer_ref_t( const layer_map_t::value_type & v ) : m_true_type( v.first ), m_layer( v.second ) { } typed_layer_ref_t::typed_layer_ref_t( const std::type_index & type, const layer_ref_t & layer ) : m_true_type( type ), m_layer( layer ) { } bool typed_layer_ref_t::operator < ( const typed_layer_ref_t & tl ) const { return m_true_type < tl.m_true_type; } // // layer_core_t // layer_core_t::layer_core_t( environment_t & env, layer_map_t && so_layers ) : m_env( env ), m_default_layers( so_layers.begin(), so_layers.end() ) { std::for_each( m_default_layers.begin(), m_default_layers.end(), [this]( so_layer_list_t::value_type & item ) { item.m_layer->bind_to_environment( &m_env ); } ); // The content of so_layers is no more needed. so_layers.clear(); } //! Find layer in container. inline so_layer_list_t::const_iterator search_for_layer( const so_layer_list_t & layers, const std::type_index & type ) { auto search_result = std::lower_bound( layers.begin(), layers.end(), type, []( const so_layer_list_t::value_type & a, const std::type_index & b ) { return a.m_true_type < b; } ); if( search_result != layers.end() && search_result->m_true_type == type ) return search_result; return layers.end(); } layer_t * layer_core_t::query_layer( const std::type_index & type ) const { // Try search within default layers first. so_layer_list_t::const_iterator layer_it = search_for_layer( m_default_layers, type ); if( m_default_layers.end() != layer_it ) return layer_it->m_layer.get(); // Layer not found yet. Search extra layers. std::lock_guard< std::mutex > lock( m_extra_layers_lock ); layer_it = search_for_layer( m_extra_layers, type ); if( m_extra_layers.end() != layer_it ) return layer_it->m_layer.get(); return nullptr; } void layer_core_t::start() { so_layer_list_t::iterator it = m_default_layers.begin(), it_end = m_default_layers.end(); for(; it != it_end; ++it ) { so_5::details::do_with_rollback_on_exception( [&] { it->m_layer->start(); }, [&] { std::for_each( m_default_layers.begin(), it, []( typed_layer_ref_t & l ) { l.m_layer->shutdown(); } ); std::for_each( m_default_layers.begin(), it, []( typed_layer_ref_t & l ) { l.m_layer->wait(); } ); } ); } } void layer_core_t::finish() { // Shutdown and wait extra layers. shutdown_extra_layers(); wait_extra_layers(); // Shutdown and wait default layers. shutdown_default_layers(); wait_default_layers(); } void layer_core_t::add_extra_layer( const std::type_index & type, const layer_ref_t & layer ) { if( nullptr == layer.get() ) SO_5_THROW_EXCEPTION( rc_trying_to_add_nullptr_extra_layer, "trying to add nullptr extra layer" ); if( m_default_layers.end() != search_for_layer( m_default_layers, type ) ) SO_5_THROW_EXCEPTION( rc_trying_to_add_extra_layer_that_already_exists_in_default_list, "trying to add extra layer that already exists in default list" ); std::lock_guard< std::mutex > lock( m_extra_layers_lock ); if( m_extra_layers.end() != search_for_layer( m_extra_layers, type ) ) SO_5_THROW_EXCEPTION( rc_trying_to_add_extra_layer_that_already_exists_in_extra_list, "trying to add extra layer that already exists in extra list" ); layer->bind_to_environment( &m_env ); try { layer->start(); } catch( const std::exception & x ) { SO_5_THROW_EXCEPTION( rc_unable_to_start_extra_layer, std::string( "layer raised an exception: " ) + x.what() ); } try { typed_layer_ref_t typed_layer( type, layer ); m_extra_layers.insert( std::lower_bound( m_extra_layers.begin(), m_extra_layers.end(), typed_layer ), typed_layer ); } catch( const std::exception & x ) { // Unable to store layer. Layer must be stopped... layer->shutdown(); layer->wait(); // ...And error must be reported. SO_5_THROW_EXCEPTION( rc_unable_to_start_extra_layer, std::string( "unable to store pointer to layer, exception: " ) + x.what() ); } } #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmissing-prototypes" #endif void call_shutdown( typed_layer_ref_t & tl ) { tl.m_layer->shutdown(); } void call_wait( typed_layer_ref_t & tl ) { tl.m_layer->wait(); } #if defined(__clang__) #pragma clang diagnostic pop #endif void layer_core_t::shutdown_extra_layers() { std::for_each( m_extra_layers.begin(), m_extra_layers.end(), call_shutdown ); } void layer_core_t::wait_extra_layers() { std::for_each( m_extra_layers.begin(), m_extra_layers.end(), call_wait ); m_extra_layers.clear(); } void layer_core_t::shutdown_default_layers() { std::for_each( m_default_layers.begin(), m_default_layers.end(), call_shutdown ); } void layer_core_t::wait_default_layers() { std::for_each( m_default_layers.begin(), m_default_layers.end(), call_wait ); } } /* namespace impl */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/layer_core.hpp000066400000000000000000000070601523065210100212740ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A definition of an utility class for work with layers. */ #if !defined( SO_5_IMPL_LAYER_CORE_HPP ) #define SO_5_IMPL_LAYER_CORE_HPP #include #include #include #include #include namespace so_5 { class environment_t; namespace impl { // // typed_layer_ref_t // /*! * \brief A special wrapper to store a layer with its type. */ struct typed_layer_ref_t { typed_layer_ref_t(); typed_layer_ref_t( const layer_map_t::value_type & v ); typed_layer_ref_t( const std::type_index & type, const layer_ref_t & layer ); bool operator < ( const typed_layer_ref_t & tl ) const; //! Layer type. std::type_index m_true_type; //! Layer itself. layer_ref_t m_layer; }; //! Typedef for typed_layer_ret container. using so_layer_list_t = std::vector< typed_layer_ref_t >; // // layer_core_t // //! An utility class for working with layers. /*! * There are two groups of layers: * \li \c default layers. They are known before the SObjectizer Run-Time started * and are retreived from SObjectizer Environment params. * These layers must be passed to the layer_core_t constructor; * \li \c extra layers. They are added when the SObjectizer Envoronment works. * * The main difference between them is that default layers start when * SObjectizer Envoronment is not working. They are started during * the SObjectizer initialization routine. * * The extra layer is started when it is added to the SObjectizer Envoronment. * * During the SObjectizer Environment shutdown extra layers must be shutdowned * before default layers. * * If the SObjectizer Environment is started several times then * the set of default layers can't be changed. * * However, extra layers have a different life cycle. * They must be formed from scratch at every start of the SObjectizer Environment. * When the SObjectizer Envoronment stops, it stops and deletes all of extra layers. * The extra layer can't be removed before stop of the SObjectizer Environment. */ class layer_core_t { public: layer_core_t( //! SObjectizer Environment to work with. environment_t & env, //! Layers which are known before SObjectizer start. //! Note: since v.5.7.5 is has to be a temporary object //! that content will be thrown away just after processing. layer_map_t && so_layers ); //! Get a layer. /*! * \retval nullptr if layer is not found. */ layer_t * query_layer( const std::type_index & type ) const; //! Start all layers. void start(); /*! * \since * v.5.2.0 * * \brief Shutdown all layers and wait for full stop of them. */ void finish(); //! Add an extra layer. void add_extra_layer( const std::type_index & type, const layer_ref_t & layer ); private: //! SObjectizer Environment to work with. environment_t & m_env; //! Default layers. /*! * Value is set in the constructor and will never change. */ so_layer_list_t m_default_layers; //! Object lock for the extra layers data. mutable std::mutex m_extra_layers_lock; //! Extra layers. so_layer_list_t m_extra_layers; //! Shutdown extra layers. void shutdown_extra_layers(); //! Blocking wait for the complete shutdown of all extra layers. /*! * All extra layers are destroyed. */ void wait_extra_layers(); //! Shutdown default layers. void shutdown_default_layers(); //! Blocking wait for the complete shutdown of all default layers. void wait_default_layers(); }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/local_mbox.hpp000066400000000000000000000515301523065210100212700ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A local mbox definition. */ #if !defined( SO_5_IMPL_LOCAL_MBOX_HPP ) #define SO_5_IMPL_LOCAL_MBOX_HPP #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace impl { namespace local_mbox_details { // // subscriber_adaptive_container_t // /*! * \since * v.5.5.12 * * \brief A special container for holding subscriber_info objects. * * \note Uses std::vector as a storage for small amount of * subscriber_infos and std::map for large amount. */ class subscriber_adaptive_container_t { /*! * \brief Information about one subscriber to be stored in a vector. * * It's necessary to have a pointer to message sink that will be * used as a search key. Such a pointer from m_info field can't be * used because it will be set to nullptr when agent drops the * subscription but keeps a delivery filter. */ struct subscribers_vector_item_t { //! Pointer to sink that has to be used as search key. /*! * \attention * It's must not be nullptr! */ abstract_message_sink_t * m_sink_as_key; //! Information about the subscription. subscription_info_with_sink_t m_info; //! Special constructor for cases when item is created for searching //! information in the vector. subscribers_vector_item_t( abstract_message_sink_t & sink_as_key ) : m_sink_as_key{ std::addressof(sink_as_key) } {} //! The normal initializing constructor. subscribers_vector_item_t( abstract_message_sink_t & sink_as_key, subscription_info_with_sink_t info ) : m_sink_as_key{ std::addressof(sink_as_key) } , m_info{ std::move(info) } {} }; /*! * \brief Predicate to be used for searching information in the vector. */ struct subscribers_vector_item_comparator { [[nodiscard]] bool operator()( const subscribers_vector_item_t & a, const subscribers_vector_item_t & b ) const noexcept { return abstract_message_sink_t::special_sink_ptr_compare( a.m_sink_as_key, b.m_sink_as_key ); } }; /*! * \brief Predicate to be used for comparing keys in subscriber map. */ struct subscriber_ptr_compare_type { [[nodiscard]] bool operator()( abstract_message_sink_t * a, abstract_message_sink_t * b ) const noexcept { return abstract_message_sink_t::special_sink_ptr_compare( a, b ); } }; using vector_type = std::vector< subscribers_vector_item_t >; using vector_iterator_type = vector_type::iterator; using const_vector_iterator_type = vector_type::const_iterator; using map_type = std::map< abstract_message_sink_t *, subscription_info_with_sink_t, subscriber_ptr_compare_type >; using map_iterator_type = map_type::iterator; using const_map_iterator_type = map_type::const_iterator; enum class storage_type { vector, map }; // NOTE! This is just arbitrary values. // There were no any benchmarks to prove that those values are useful. struct size_limits { static constexpr const std::size_t switch_to_vector = 16; static constexpr const std::size_t switch_to_map = 32; }; //! The current storage type to be used by container. storage_type m_storage = { storage_type::vector }; //! Container for small amount of subscriber_infos. vector_type m_vector; //! Container for large amount of subscriber_infos. map_type m_map; public : //! Iterator type. class iterator { friend class subscriber_adaptive_container_t; storage_type m_storage; vector_iterator_type m_it_v; map_iterator_type m_it_m; public : iterator( vector_iterator_type it_v ) : m_storage{ storage_type::vector } , m_it_v{ std::move(it_v) } {} iterator( map_iterator_type it_m ) : m_storage{ storage_type::map } , m_it_m{ std::move(it_m) } {} subscription_info_with_sink_t & operator*() { if( storage_type::vector == m_storage ) return m_it_v->m_info; else return m_it_m->second; } subscription_info_with_sink_t * operator->() { return std::addressof(**this); } iterator & operator++() { if( storage_type::vector == m_storage ) ++m_it_v; else ++m_it_m; return *this; } iterator operator++(int) { if( storage_type::vector == m_storage ) { iterator r{ *this }; ++m_it_v; return r; } else { iterator r{ *this }; ++m_it_m; return r; } } bool operator==( const iterator & o ) const { if( storage_type::vector == m_storage ) return m_it_v == o.m_it_v; else return m_it_m == o.m_it_m; } bool operator!=( const iterator & o ) const { return !( *this == o ); } }; //! Const iterator type. class const_iterator { storage_type m_storage; const_vector_iterator_type m_it_v; const_map_iterator_type m_it_m; public : const_iterator( const_vector_iterator_type it_v ) : m_storage{ storage_type::vector } , m_it_v{ std::move(it_v) } {} const_iterator( const_map_iterator_type it_m ) : m_storage{ storage_type::map } , m_it_m{ std::move(it_m) } {} const subscription_info_with_sink_t & operator*() const { if( storage_type::vector == m_storage ) return m_it_v->m_info; else return m_it_m->second; } const subscription_info_with_sink_t * operator->() const { return std::addressof(**this); } const_iterator & operator++() { if( storage_type::vector == m_storage ) ++m_it_v; else ++m_it_m; return *this; } const_iterator operator++(int) { if( storage_type::vector == m_storage ) { const_iterator r{ *this }; ++m_it_v; return r; } else { const_iterator r{ *this }; ++m_it_m; return r; } } bool operator==( const const_iterator & o ) const { if( storage_type::vector == m_storage ) return m_it_v == o.m_it_v; else return m_it_m == o.m_it_m; } bool operator!=( const const_iterator & o ) const { return !( *this == o ); } }; private : //! Is vector used as a storage. bool is_vector() const { return storage_type::vector == m_storage; } //! Insertion of new item to vector. void insert_to_vector( abstract_message_sink_t & sink_as_key, subscription_info_with_sink_t && info ) { subscribers_vector_item_t new_item{ sink_as_key, std::move(info) }; const auto insertion_place = std::lower_bound( m_vector.begin(), m_vector.end(), new_item, subscribers_vector_item_comparator{} ); m_vector.insert( insertion_place, std::move(new_item) ); } //! Insertion of new item to map. void insert_to_map( abstract_message_sink_t & sink_as_key, subscription_info_with_sink_t && info ) { m_map.emplace( std::addressof(sink_as_key), std::move(info) ); } //! Switching storage from vector to map. void switch_storage_to_map() { // All exceptions will be ignored. // It is because an exception can be thrown on stages 1-2, // but not on stage 3. try { // Stage 1. New containers. vector_type empty_vector; map_type new_storage; // Stage 2. Copying of items from the old container to new one. std::for_each( m_vector.begin(), m_vector.end(), [&new_storage]( const subscribers_vector_item_t & item ) { new_storage.emplace( item.m_sink_as_key, item.m_info ); } ); // No exceptions expected here. so_5::details::invoke_noexcept_code( [&]() { swap( m_map, new_storage ); swap( m_vector, empty_vector ); m_storage = storage_type::map; } ); } catch( ... ) {} } //! Switching storage from map to vector. void switch_storage_to_vector() { // All exceptions will be ignored. // It is because an exception can be thrown on stages 1-2-3, // but not on stage 4. try { // Stage 1. New containers to swap values with old containers. // An exception could be thrown in the constructors. map_type empty_map; vector_type new_storage; // Stage 2. Preallocate necessary space in the new vector. // An exception can be thrown here. new_storage.reserve( m_map.size() ); // Stage 3. Copy items from old map to the new vector. // We do not expect exceptions here at v.5.5.12, but // the situation can change in the future versions. // Use the fact that items in map is already ordered. std::for_each( m_map.begin(), m_map.end(), [&new_storage]( const map_type::value_type & info ) { new_storage.emplace_back( *(info.first), info.second ); } ); // Stage 4. Swapping. // No exceptions expected here. so_5::details::invoke_noexcept_code( [&]() { swap( m_vector, new_storage ); swap( m_map, empty_map ); m_storage = storage_type::vector; } ); } catch(...) {} } iterator find_in_vector( abstract_message_sink_t & subscriber ) { subscribers_vector_item_t info{ subscriber }; auto pos = std::lower_bound( m_vector.begin(), m_vector.end(), info, subscribers_vector_item_comparator{} ); if( pos != m_vector.end() && pos->m_sink_as_key == std::addressof(subscriber) ) return iterator{ pos }; else return iterator{ m_vector.end() }; } iterator find_in_map( abstract_message_sink_t & subscriber ) { return iterator{ m_map.find( std::addressof(subscriber) ) }; } public : //! Default constructor. subscriber_adaptive_container_t() {} //! Copy constructor. subscriber_adaptive_container_t( const subscriber_adaptive_container_t & o ) : m_storage{ o.m_storage } , m_vector( o.m_vector ) , m_map( o.m_map ) {} //! Move constructor. subscriber_adaptive_container_t( subscriber_adaptive_container_t && o ) noexcept : m_storage{ o.m_storage } , m_vector( std::move( o.m_vector ) ) , m_map( std::move( o.m_map ) ) { // Other object is now empty. // It must use vector as a storage. o.m_storage = storage_type::vector; } friend void swap( subscriber_adaptive_container_t & a, subscriber_adaptive_container_t & b ) noexcept { using std::swap; swap( a.m_storage, b.m_storage ); swap( a.m_vector, b.m_vector ); swap( a.m_map, b.m_map ); } //! Copy operator. subscriber_adaptive_container_t & operator=( const subscriber_adaptive_container_t & o ) { subscriber_adaptive_container_t tmp{ o }; swap( *this, tmp ); return *this; } //! Move operator. subscriber_adaptive_container_t & operator=( subscriber_adaptive_container_t && o ) noexcept { subscriber_adaptive_container_t tmp{ std::move(o) }; swap( *this, tmp ); return *this; } void insert( abstract_message_sink_t & sink_as_key, subscription_info_with_sink_t info ) { if( is_vector() ) { if( m_vector.size() == size_limits::switch_to_map ) switch_storage_to_map(); } if( is_vector() ) insert_to_vector( sink_as_key, std::move( info ) ); else insert_to_map( sink_as_key, std::move( info ) ); } template< typename... Args > void emplace( abstract_message_sink_t & sink_as_key, Args &&... args ) { insert( sink_as_key, subscription_info_with_sink_t{ std::forward( args )... } ); } void erase( const iterator & it ) { if( is_vector() ) m_vector.erase( it.m_it_v ); else { m_map.erase( it.m_it_m ); // May be it is a time for switching to smaller storage? if( m_map.size() < size_limits::switch_to_vector ) switch_storage_to_vector(); } } iterator find( abstract_message_sink_t & subscriber ) { if( is_vector() ) return find_in_vector( subscriber ); else return find_in_map( subscriber ); } iterator begin() { if( is_vector() ) return iterator{ m_vector.begin() }; else return iterator{ m_map.begin() }; } iterator end() { if( is_vector() ) return iterator{ m_vector.end() }; else return iterator{ m_map.end() }; } const_iterator begin() const { if( is_vector() ) return const_iterator{ m_vector.begin() }; else return const_iterator{ m_map.begin() }; } const_iterator end() const { if( is_vector() ) return const_iterator{ m_vector.end() }; else return const_iterator{ m_map.end() }; } bool empty() const { if( is_vector() ) return m_vector.empty(); else return m_map.empty(); } std::size_t size() const { if( is_vector() ) return m_vector.size(); else return m_map.size(); } }; // // data_t // /*! * \since * v.5.5.9 * * \brief A coolection of data required for local mbox implementation. */ struct data_t { data_t( mbox_id_t id, environment_t & env ) : m_id{ id } , m_env{ env } {} //! ID of this mbox. const mbox_id_t m_id; //! Environment for which the mbox is created. environment_t & m_env; //! Object lock. default_rw_spinlock_t m_lock; /*! * \since * v.5.4.0 * * \brief Map from message type to subscribers. */ using messages_table_t = std::map< std::type_index, subscriber_adaptive_container_t >; //! Map of subscribers to messages. messages_table_t m_subscribers; }; } /* namespace local_mbox_details */ // // local_mbox_template // //! A template with implementation of local mbox. /*! * \since * v.5.5.9 * * \tparam Tracing_Base base class with implementation of message * delivery tracing methods. */ template< typename Tracing_Base > class local_mbox_template : public abstract_message_box_t , private local_mbox_details::data_t , private Tracing_Base { public: template< typename... Tracing_Args > local_mbox_template( //! ID of this mbox. mbox_id_t id, //! Environment for which the mbox is created. environment_t & env, //! Optional parameters for Tracing_Base's constructor. Tracing_Args &&... args ) : local_mbox_details::data_t{ id, env } , Tracing_Base{ std::forward< Tracing_Args >(args)... } {} mbox_id_t id() const override { return m_id; } void subscribe_event_handler( const std::type_index & type_wrapper, abstract_message_sink_t & subscriber ) override { insert_or_modify_subscriber( type_wrapper, subscriber, [&] { return local_mbox_details::subscription_info_with_sink_t{ subscriber }; }, [&]( local_mbox_details::subscription_info_with_sink_t & info ) { info.set_sink( subscriber ); } ); } void unsubscribe_event_handler( const std::type_index & type_wrapper, abstract_message_sink_t & subscriber ) noexcept override { modify_and_remove_subscriber_if_needed( type_wrapper, subscriber, []( local_mbox_details::subscription_info_with_sink_t & info ) { info.drop_sink(); } ); } std::string query_name() const override { std::ostringstream s; s << ""; return s.str(); } mbox_type_t type() const override { return mbox_type_t::multi_producer_multi_consumer; } void do_deliver_message( message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) override { typename Tracing_Base::deliver_op_tracer tracer{ *this, // as Tracing_base *this, // as abstract_message_box_t "deliver_message", delivery_mode, msg_type, message, redirection_deep }; ensure_immutable_message( msg_type, message ); do_deliver_message_impl( tracer, delivery_mode, msg_type, message, redirection_deep ); } void set_delivery_filter( const std::type_index & msg_type, const delivery_filter_t & filter, abstract_message_sink_t & subscriber ) override { insert_or_modify_subscriber( msg_type, subscriber, [&] { return local_mbox_details::subscription_info_with_sink_t{ filter }; }, [&]( local_mbox_details::subscription_info_with_sink_t & info ) { info.set_filter( filter ); } ); } void drop_delivery_filter( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) noexcept override { modify_and_remove_subscriber_if_needed( msg_type, subscriber, []( local_mbox_details::subscription_info_with_sink_t & info ) { info.drop_filter(); } ); } environment_t & environment() const noexcept override { return m_env; } private : template< typename Info_Maker, typename Info_Changer > void insert_or_modify_subscriber( const std::type_index & type_wrapper, abstract_message_sink_t & subscriber, Info_Maker maker, Info_Changer changer ) { std::unique_lock< default_rw_spinlock_t > lock( m_lock ); auto it = m_subscribers.find( type_wrapper ); if( it == m_subscribers.end() ) { // There isn't such message type yet. local_mbox_details::subscriber_adaptive_container_t container; container.insert( subscriber, maker() ); m_subscribers.emplace( type_wrapper, std::move( container ) ); } else { auto & sinks = it->second; auto pos = sinks.find( subscriber ); if( pos != sinks.end() ) { // Agent is already in subscribers list. // But its state must be updated. changer( *pos ); } else // There is no subscriber in the container. // It must be added. sinks.insert( subscriber, maker() ); } } template< typename Info_Changer > void modify_and_remove_subscriber_if_needed( const std::type_index & type_wrapper, abstract_message_sink_t & subscriber, Info_Changer changer ) { std::unique_lock< default_rw_spinlock_t > lock( m_lock ); auto it = m_subscribers.find( type_wrapper ); if( it != m_subscribers.end() ) { auto & sinks = it->second; auto pos = sinks.find( subscriber ); if( pos != sinks.end() ) { // Subscriber is found and must be modified. changer( *pos ); // If info about subscriber becomes empty after modification // then subscriber info must be removed. if( pos->empty() ) sinks.erase( pos ); } if( sinks.empty() ) m_subscribers.erase( it ); } } void do_deliver_message_impl( typename Tracing_Base::deliver_op_tracer const & tracer, message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) { read_lock_guard_t< default_rw_spinlock_t > lock( m_lock ); auto it = m_subscribers.find( msg_type ); if( it != m_subscribers.end() ) { for( const auto & a : it->second ) do_deliver_message_to_subscriber( a, tracer, delivery_mode, msg_type, message, redirection_deep ); } else tracer.no_subscribers(); } void do_deliver_message_to_subscriber( const local_mbox_details::subscription_info_with_sink_t & subscriber_info, typename Tracing_Base::deliver_op_tracer const & tracer, message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) const { const auto delivery_status = subscriber_info.must_be_delivered( message, []( const message_ref_t & m ) -> message_t & { return *m; } ); if( delivery_possibility_t::must_be_delivered == delivery_status ) { subscriber_info.sink_reference().push_event( this->m_id, delivery_mode, msg_type, message, redirection_deep, tracer.overlimit_tracer() ); } else tracer.message_rejected( subscriber_info.sink_pointer(), delivery_status ); } /*! * \brief Ensures that message is an immutable message. * * Checks mutability flag and throws an exception if message is * a mutable one. * * \since * v.5.5.19 */ void ensure_immutable_message( const std::type_index & msg_type, const message_ref_t & what ) const { if( message_mutability_t::immutable_message != message_mutability( what ) ) SO_5_THROW_EXCEPTION( so_5::rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox, "an attempt to deliver mutable message via MPMC mbox" ", msg_type=" + std::string(msg_type.name()) ); } }; /*! * \since * v.5.5.9 * * \brief Alias for local mbox without message delivery tracing. */ using local_mbox_without_tracing = local_mbox_template< msg_tracing_helpers::tracing_disabled_base >; /*! * \since * v.5.5.9 * * \brief Alias for local mbox with message delivery tracing. */ using local_mbox_with_tracing = local_mbox_template< msg_tracing_helpers::tracing_enabled_base >; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/local_mbox_basic_subscription_info.hpp000066400000000000000000000176771523065210100262660ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Information about just one subscription. \since v.5.7.4 */ #if !defined( SO_5_IMPL_LOCAL_MBOX_BASIC_SUBSCRIPTION_INFO_HPP ) #define SO_5_IMPL_LOCAL_MBOX_BASIC_SUBSCRIPTION_INFO_HPP #include #include #include #include namespace so_5 { namespace impl { namespace local_mbox_details { /*! * \brief An information block about one subscription to one message type. * * This class is intended to be used for cases when there is no actual * reference to a sink. The presence/absence of a subscription is controlled * by m_state member (and subscription_defined()/subscription_dropped() methods). * * Delivery filter can be present, in that case a pointer to delivery filter * will be stored inside the instance of that class. * * \since v.5.7.4 */ class subscription_info_without_sink_t { protected: /*! * \brief Current status of the subscription. */ enum class state_t { nothing, only_subscriptions, only_filter, subscriptions_and_filter }; /*! * \brief Delivery filter for that message for that subscription. */ const delivery_filter_t * m_filter; /*! * \brief Current state of the subscription parameters. */ state_t m_state; public : /*! * \brief Helper type that tells that subscription is present. */ struct subscription_present_t {}; //! Default constructor. Creates an empty object. subscription_info_without_sink_t() : m_filter( nullptr ) , m_state( state_t::nothing ) {} //! Constructor for the case when info is being //! created during event subscription. subscription_info_without_sink_t( subscription_present_t /*subscription_presence*/ ) : m_filter( nullptr ) , m_state( state_t::only_subscriptions ) {} //! Constructor for the case when subscriber info is being //! created during setting a delivery filter. subscription_info_without_sink_t( const delivery_filter_t & filter ) : m_filter( std::addressof(filter) ) , m_state( state_t::only_filter ) {} [[nodiscard]] bool empty() const { return state_t::nothing == m_state; } /*! * \brief Inform about addition of a subscription. */ void subscription_defined() { m_state = ( state_t::nothing == m_state ? state_t::only_subscriptions : state_t::subscriptions_and_filter ); } /*! * \brief Inform about removal of a subscription. */ void subscription_dropped() { m_state = ( state_t::only_subscriptions == m_state ? state_t::nothing : state_t::only_filter ); } //! Set the delivery filter for the subscriber. void set_filter( const delivery_filter_t & filter ) { m_filter = &filter; m_state = ( state_t::nothing == m_state ? state_t::only_filter : state_t::subscriptions_and_filter ); } //! Drop the delivery filter for the subscriber. void drop_filter() { m_filter = nullptr; m_state = ( state_t::only_filter == m_state ? state_t::nothing : state_t::only_subscriptions ); } //! Must a message be delivered to the subscriber? template< typename Msg_Ref_Extractor > [[nodiscard]] delivery_possibility_t must_be_delivered( abstract_message_sink_t & subscriber, const message_ref_t & msg, Msg_Ref_Extractor msg_extractor ) const { // For the case when there are actual subscriptions. // We assume that will be in 99.9% cases. auto need_deliver = delivery_possibility_t::must_be_delivered; if( state_t::only_filter == m_state ) // Only filter, no actual subscriptions. // No message delivery for that case. need_deliver = delivery_possibility_t::no_subscription; else if( state_t::subscriptions_and_filter == m_state ) { // Delivery must be checked by delivery filter. // But message must be extracted from an envelope first. auto opt_msg = so_5::enveloped_msg::message_to_be_inspected( msg ); if( opt_msg ) { message_t & actual_msg = msg_extractor( *opt_msg ); need_deliver = m_filter->check( subscriber, actual_msg ) ? delivery_possibility_t::must_be_delivered : delivery_possibility_t::disabled_by_delivery_filter; } else need_deliver = delivery_possibility_t::hidden_by_envelope; } return need_deliver; } }; /*! * \brief An information block about one subscription to one message type * with presence of message_sink. * * This class is intended to be used for cases when actual * reference to a sink has to be stored if subscription is present. * * Delivery filter can be present, in that case a pointer to delivery filter * will be stored inside the instance of that class. * * \since v.5.7.4 */ class subscription_info_with_sink_t { protected: /*! * \brief Message sink for a subscriber. * * nullptr means that there is no subscriber. */ abstract_message_sink_t * m_sink; /*! * \brief Delivery filter for that message for that subscription. * * nullptr means that there is no delivery filter. */ const delivery_filter_t * m_filter; public : //! Default constructor. Creates an empty object. subscription_info_with_sink_t() : m_sink{ nullptr } , m_filter{ nullptr } {} //! Constructor for the case when info is being //! created during event subscription. subscription_info_with_sink_t( abstract_message_sink_t & sink ) : m_sink{ std::addressof(sink) } , m_filter{ nullptr } {} //! Constructor for the case when subscriber info is being //! created during setting a delivery filter. subscription_info_with_sink_t( const delivery_filter_t & filter ) : m_sink{ nullptr } , m_filter{ std::addressof(filter) } {} //! Constructor for the case when info is being //! created when subscriber and delivery_filter are known. subscription_info_with_sink_t( abstract_message_sink_t & sink, const delivery_filter_t & filter ) : m_sink{ std::addressof(sink) } , m_filter{ std::addressof(filter) } {} [[nodiscard]] bool empty() const noexcept { return !m_sink && !m_filter; } /*! * \brief Inform about addition of a subscription. */ void set_sink( abstract_message_sink_t & sink ) { m_sink = std::addressof(sink); } /*! * \brief Inform about removal of a subscription. */ void drop_sink() { m_sink = nullptr; } //! Set the delivery filter for the subscriber. void set_filter( const delivery_filter_t & filter ) { m_filter = std::addressof(filter); } //! Drop the delivery filter for the subscriber. void drop_filter() { m_filter = nullptr; } //! Must a message be delivered to the subscriber? template< typename Msg_Ref_Extractor > [[nodiscard]] delivery_possibility_t must_be_delivered( const message_ref_t & msg, Msg_Ref_Extractor msg_extractor ) const { // For the case when there are actual subscriptions. // We assume that will be in 99.9% cases. auto need_deliver = delivery_possibility_t::must_be_delivered; if( !m_sink ) // Only filter, no actual subscriptions. // No message delivery for that case. need_deliver = delivery_possibility_t::no_subscription; else if( m_filter ) { // Delivery must be checked by delivery filter. // But message must be extracted from an envelope first. auto opt_msg = so_5::enveloped_msg::message_to_be_inspected( msg ); if( opt_msg ) { message_t & actual_msg = msg_extractor( *opt_msg ); need_deliver = m_filter->check( *m_sink, actual_msg ) ? delivery_possibility_t::must_be_delivered : delivery_possibility_t::disabled_by_delivery_filter; } else need_deliver = delivery_possibility_t::hidden_by_envelope; } return need_deliver; } /*! * \brief Get a reference to the subscribed sink. * * \attention * It's UB to try to access a sink if sink isn't set. * There is no checking for performance reasons. */ [[nodiscard]] abstract_message_sink_t & sink_reference() const noexcept { return *m_sink; } /*! * \brief Get a pointer to the subscribed sink. */ [[nodiscard]] abstract_message_sink_t * sink_pointer() const noexcept { return m_sink; } }; } /* namespace local_mbox_details */ } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/make_mchain.hpp000066400000000000000000000025661523065210100214120ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A helper function for the creation of a new mchain. * * \since * v.5.7.0 */ #if !defined( SO_5_IMPL_MAKE_MCHAIN_HPP ) #define SO_5_IMPL_MAKE_MCHAIN_HPP #include #include namespace so_5 { namespace impl { /*! * \brief Helper function for creation of a new mchain with respect * to message tracing. * * This function was in mbox_core.cpp file until v.5.7.0 and is * placed in a separate header file in v.5.7.0. * * \tparam Q type of demand queue to be used for new mchain. * \tparam A type of arguments for mchain_template constructor. */ template< typename Q, typename... A > [[nodiscard]] mchain_t make_mchain( outliving_reference_t< so_5::msg_tracing::holder_t > tracer, const mchain_params_t & params, A &&... args ) { using namespace so_5::mchain_props; using namespace so_5::impl::msg_tracing_helpers; using D = mchain_tracing_disabled_base; using E = mchain_tracing_enabled_base; if( tracer.get().is_msg_tracing_enabled() && !params.msg_tracing_disabled() ) return mchain_t{ new mchain_template< Q, E >{ std::forward(args)..., params, tracer } }; else return mchain_t{ new mchain_template< Q, D >{ std::forward(args)..., params } }; } } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/mbox_core.cpp000066400000000000000000000172711523065210100211250ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include #include #include #include #include #include #include namespace so_5 { namespace impl { // // mbox_core_t // mbox_core_t::mbox_core_t( outliving_reference_t< so_5::msg_tracing::holder_t > msg_tracing_stuff ) : m_msg_tracing_stuff{ msg_tracing_stuff } , m_mbox_id_counter{ 1 } { } mbox_t mbox_core_t::create_mbox( environment_t & env ) { auto id = ++m_mbox_id_counter; if( !m_msg_tracing_stuff.get().is_msg_tracing_enabled() ) return mbox_t{ new local_mbox_without_tracing{ id, env } }; else return mbox_t{ new local_mbox_with_tracing{ id, env, m_msg_tracing_stuff } }; } mbox_t mbox_core_t::create_mbox( environment_t & env, nonempty_name_t mbox_name ) { mbox_t result; // Will be created later, full_named_mbox_id_t key{ default_global_mbox_namespace(), mbox_name.giveout_value() }; // NOTE: mbox_name can't be used anymore! std::lock_guard< std::mutex > lock( m_dictionary_lock ); named_mboxes_dictionary_t::iterator it = m_named_mboxes_dictionary.find( key ); if( m_named_mboxes_dictionary.end() != it ) { // For strong exception safety create a new instance // of named_local_mbox first... result = mbox_t{ new named_local_mbox_t( key, it->second.m_mbox, *this ) }; // ... now the count of references can be incremented safely // (exceptions is no more expected). ++(it->second.m_external_ref_count); } else { // There is no mbox with such name. New mbox should be created. // NOTE: it's safe to call create_mbox(env) when mbox_core is // locked because create_mbox(env) doesn't to lock the mbox_core. mbox_t mbox_ref = create_mbox( env ); // For strong exception safety create a new instance // of named_local_mbox first... result = mbox_t{ new named_local_mbox_t( key, mbox_ref, *this ) }; // ...now we can update the dictionary. If there will be an exception // then all new object will be destroyed automatically. m_named_mboxes_dictionary.emplace( key, named_mbox_info_t( mbox_ref ) ); } return result; } namespace { template< typename M1, typename M2, typename... A > std::unique_ptr< abstract_message_box_t > make_actual_mbox( outliving_reference_t msg_tracing_stuff, A &&... args ) { std::unique_ptr< abstract_message_box_t > result; if( !msg_tracing_stuff.get().is_msg_tracing_enabled() ) result.reset( new M1{ std::forward(args)... } ); else result.reset( new M2{ std::forward(args)..., msg_tracing_stuff } ); return result; } } /* namespace anonymous */ mbox_t mbox_core_t::create_ordinary_mpsc_mbox( environment_t & env, agent_t & owner ) { const auto id = ++m_mbox_id_counter; std::unique_ptr< abstract_message_box_t > actual_mbox = make_actual_mbox< ordinary_mpsc_mbox_without_tracing_t, ordinary_mpsc_mbox_with_tracing_t >( m_msg_tracing_stuff, id, env, outliving_mutable( owner ) ); return mbox_t{ actual_mbox.release() }; } mbox_t mbox_core_t::create_limitless_mpsc_mbox( environment_t & env, agent_t & owner ) { const auto id = ++m_mbox_id_counter; std::unique_ptr< abstract_message_box_t > actual_mbox = make_actual_mbox< limitless_mpsc_mbox_without_tracing_t, limitless_mpsc_mbox_with_tracing_t >( m_msg_tracing_stuff, id, env, outliving_mutable( owner ) ); return mbox_t{ actual_mbox.release() }; } void mbox_core_t::destroy_mbox( const full_named_mbox_id_t & name ) noexcept { std::lock_guard< std::mutex > lock( m_dictionary_lock ); named_mboxes_dictionary_t::iterator it = m_named_mboxes_dictionary.find( name ); if( m_named_mboxes_dictionary.end() != it ) { const unsigned int ref_count = --(it->second.m_external_ref_count); if( 0 == ref_count ) m_named_mboxes_dictionary.erase( it ); } } mbox_t mbox_core_t::create_custom_mbox( environment_t & env, ::so_5::custom_mbox_details::creator_iface_t & creator ) { const auto id = ++m_mbox_id_counter; return creator.create( mbox_creation_data_t{ outliving_mutable(env), id, m_msg_tracing_stuff } ); } mbox_t mbox_core_t::introduce_named_mbox( mbox_namespace_name_t mbox_namespace, nonempty_name_t mbox_name, const std::function< mbox_t() > & mbox_factory ) { mbox_t result; full_named_mbox_id_t key{ std::string{ mbox_namespace.query_name() }, mbox_name.giveout_value() }; // NOTE: mbox_name can't be used anymore! // Step 1. Check the presense of this mbox. // It's important to do that step on locked object. { std::lock_guard< std::mutex > lock( m_dictionary_lock ); named_mboxes_dictionary_t::iterator it = m_named_mboxes_dictionary.find( key ); if( m_named_mboxes_dictionary.end() != it ) { // For strong exception safety create a new instance // of named_local_mbox first... result = mbox_t{ new named_local_mbox_t( key, it->second.m_mbox, *this ) }; // ... now the count of references can be incremented safely // (exceptions is no more expected). ++(it->second.m_external_ref_count); } } if( !result ) { // Step 2. Create a new instance on mbox. // It's important to call mbox_factory when mbox_core isn't locked. auto fresh_mbox = mbox_factory(); if( !fresh_mbox ) SO_5_THROW_EXCEPTION( rc_nullptr_as_result_of_user_mbox_factory, "user-provided mbox_factory returns nullptr" ); // Step 3. Try to register the fresh_mbox. // It has to be done on locked object. { std::lock_guard< std::mutex > lock( m_dictionary_lock ); // Another search. This is necessary because the name may have been // created while mbox_factory() was running. named_mboxes_dictionary_t::iterator it = m_named_mboxes_dictionary.find( key ); if( m_named_mboxes_dictionary.end() != it ) { // Yes, the name has been created while we were inside // mbox_factory() call. The fresh_mbox has to be discarded. // // For strong exception safety create a new instance // of named_local_mbox first... result = mbox_t{ new named_local_mbox_t( key, it->second.m_mbox, *this ) }; // ... now the count of references can be incremented safely // (exceptions is no more expected). ++(it->second.m_external_ref_count); } else { // For strong exception safety create a new instance // of named_local_mbox first... result = mbox_t{ new named_local_mbox_t( key, fresh_mbox, *this ) }; // ...now we can update the dictionary. If there will be an // exception then all new object will be destroyed automatically. m_named_mboxes_dictionary.emplace( key, named_mbox_info_t( fresh_mbox ) ); } } } return result; } mchain_t mbox_core_t::create_mchain( environment_t & env, const mchain_params_t & params ) { using namespace so_5::mchain_props; using namespace so_5::mchain_props::details; auto id = ++m_mbox_id_counter; if( params.capacity().unlimited() ) return make_mchain< unlimited_demand_queue >( m_msg_tracing_stuff, params, env, id ); else if( memory_usage_t::dynamic == params.capacity().memory_usage() ) return make_mchain< limited_dynamic_demand_queue >( m_msg_tracing_stuff, params, env, id ); else return make_mchain< limited_preallocated_demand_queue >( m_msg_tracing_stuff, params, env, id ); } mbox_core_stats_t mbox_core_t::query_stats() { std::lock_guard< std::mutex > lock{ m_dictionary_lock }; return mbox_core_stats_t{ m_named_mboxes_dictionary.size() }; } [[nodiscard]] mbox_id_t mbox_core_t::allocate_mbox_id() noexcept { return ++m_mbox_id_counter; } } /* namespace impl */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/mbox_core.hpp000066400000000000000000000145161523065210100211310ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A definition of an utility class for work with mboxes. */ #if !defined( SO_5_IMPL_MBOX_CORE_HPP ) #define SO_5_IMPL_MBOX_CORE_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace impl { // // mbox_core_stats_t // /*! * \since * v.5.5.4 * * \brief Statistics from mbox_core for run-time monitoring. */ struct mbox_core_stats_t { //! Count of named mboxes. std::size_t m_named_mbox_count; }; // // full_named_mbox_id_t // /*! * \brief Full name for a named mbox. * * The full name includes mbox namespace and the name of the mbox. * * \note * The mbox namespace may be empty if named was created as ordinary * named mbox (via environment_t::create_mbox()). * * \since v.5.8.0 */ struct full_named_mbox_id_t { /*! * \brief Name of mbox namespace in that the mbox is defined. * * May be empty for ordinary named mbox. */ std::string m_namespace; /*! * \brief Own name of the mbox. * * \attention * Can't be empty. */ std::string m_name; //! Initializing constructor. full_named_mbox_id_t( std::string mbox_namespace, std::string mbox_name ) : m_namespace{ std::move(mbox_namespace) } , m_name{ std::move(mbox_name) } {} }; [[nodiscard]] inline bool operator<( const full_named_mbox_id_t & a, const full_named_mbox_id_t & b ) { return std::tie(a.m_namespace, a.m_name) < std::tie(b.m_namespace, b.m_name); } // // default_global_mbox_namespace // /*! * \brief Helper function that returns name of the default global * namespace for named mboxes. * * \note * This default global namespace has empty name in the current version * of SObjectizer. * * \since v.5.8.0 */ [[nodiscard]] inline std::string default_global_mbox_namespace() { return {}; } // // mbox_core_t // /*! * \brief A utility class for the work with mboxes. */ class mbox_core_t final : private atomic_refcounted_t { friend class intrusive_ptr_t< mbox_core_t >; mbox_core_t( const mbox_core_t & ) = delete; mbox_core_t & operator=( const mbox_core_t & ) = delete; public: mbox_core_t( //! Message delivery tracing stuff. outliving_reference_t< so_5::msg_tracing::holder_t > msg_tracing_stuff ); //! Create local anonymous mbox. /*! \note always creates a new mbox. */ [[nodiscard]] mbox_t create_mbox( environment_t & env ); //! Create local named mbox. /*! \note if mbox with specified name \a mbox_name is present, method won't create a new mbox. In this case method will return a new mbox_t, which links to the present mbox (with this name). */ [[nodiscard]] mbox_t create_mbox( //! Environment for which the mbox is created. environment_t & env, //! Mbox name. nonempty_name_t mbox_name ); /*! * \brief Create mpsc_mbox that handles message limits. * * \since v.5.8.0 */ [[nodiscard]] mbox_t create_ordinary_mpsc_mbox( //! Environment for which the mbox is created. environment_t & env, //! The only consumer for the mbox. agent_t & owner ); /*! * \brief Create mpsc_mbox that ignores message limits. * * \since v.5.8.0 */ [[nodiscard]] mbox_t create_limitless_mpsc_mbox( //! Environment for which the mbox is created. environment_t & env, //! The only consumer for the mbox. agent_t & owner ); //! Remove a reference to the named mbox. /*! * If it was a last reference to named mbox the mbox destroyed. */ void destroy_mbox( //! Mbox name. const full_named_mbox_id_t & name ) noexcept; /*! * \brief Create a custom mbox. * * \since * v.5.5.19.2 */ [[nodiscard]] mbox_t create_custom_mbox( //! Environment for which the mbox is created. environment_t & env, //! Creator for new mbox. ::so_5::custom_mbox_details::creator_iface_t & creator ); /*! * \brief Introduce named mbox with user-provided factory. * * \since v.5.8.0 */ [[nodiscard]] mbox_t introduce_named_mbox( //! Name of mbox_namespace for a new mbox. mbox_namespace_name_t mbox_namespace, //! Name for a new mbox. nonempty_name_t mbox_name, //! Factory for new mbox. const std::function< mbox_t() > & mbox_factory ); /*! * \since * v.5.5.13 * * \brief Create message chain. * * \par Usage examples: */ mchain_t create_mchain( //! SObjectizer Environment for which message chain will be created. environment_t & env, //! Parameters for a new chain. const mchain_params_t & params ); /*! * \since * v.5.5.4 * * \brief Get statistics for run-time monitoring. */ mbox_core_stats_t query_stats(); /*! * \brief Allocate an ID for a new custom mbox or mchain. * * \since * v.5.7.0 */ [[nodiscard]] mbox_id_t allocate_mbox_id() noexcept; private: /*! * \brief Data related to message delivery tracing. * * \since * v.5.5.22 */ outliving_reference_t< so_5::msg_tracing::holder_t > m_msg_tracing_stuff; //! Named mbox map's lock. std::mutex m_dictionary_lock; //! Named mbox information. struct named_mbox_info_t { named_mbox_info_t( mbox_t mbox ) : m_external_ref_count( 1 ), m_mbox( std::move(mbox) ) {} //! Reference count by external mbox_refs. unsigned int m_external_ref_count; //! Real mbox for that name. mbox_t m_mbox; }; //! Typedef for the map from the mbox name to the mbox information. using named_mboxes_dictionary_t = std::map< full_named_mbox_id_t, named_mbox_info_t, std::less<> // It's important. >; //! Named mboxes. named_mboxes_dictionary_t m_named_mboxes_dictionary; /*! * \since * v.5.4.0 * * \brief A counter for mbox ID generation. */ std::atomic< mbox_id_t > m_mbox_id_counter; }; //! Smart reference to the mbox_core_t. /*! * \note * It was a separate class until v.5.8.0. * Since v.5.8.0 it's just a typedef for intrusive_ptr_t. */ using mbox_core_ref_t = intrusive_ptr_t< mbox_core_t >; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/mbox_iface_for_timers.hpp000066400000000000000000000017241523065210100234760ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Definition of mbox_iface_for_timers. * * \since * v.5.5.19 */ #if !defined( SO_5_IMPL_MBOX_IFACE_FOR_TIMERS_HPP ) #define SO_5_IMPL_MBOX_IFACE_FOR_TIMERS_HPP #include namespace so_5 { namespace impl { // // mbox_iface_for_timers_t // /*! * \since * v.5.5.18 * * \brief Helper class for accessing protected members from mbox interface. */ class mbox_iface_for_timers_t { public : mbox_iface_for_timers_t( const mbox_t & mb ) : m_mb( *mb ) {} inline void deliver_message_from_timer( //! Type of the message to deliver. const std::type_index & msg_type, //! A message instance to be delivered. const message_ref_t & message ) { m_mb.do_deliver_message( message_delivery_mode_t::nonblocking, msg_type, message, 1u /* redirection_deep */ ); } private : abstract_message_box_t & m_mb; }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/mchain_details.hpp000066400000000000000000000547111523065210100221210ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Implementation details for message chains. * * \since v.5.5.13 */ #if !defined( SO_5_IMPL_MCHAIN_DETAILS_HPP ) #define SO_5_IMPL_MCHAIN_DETAILS_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace mchain_props { namespace details { // // ensure_queue_not_empty // /*! * \brief Helper function which throws an exception if queue is empty. * * \since v.5.5.13 */ template< typename Q > void ensure_queue_not_empty( Q && queue ) { if( queue.is_empty() ) SO_5_THROW_EXCEPTION( rc_msg_chain_is_empty, "an attempt to get message from empty demand queue" ); } // // ensure_queue_not_full // /*! * \brief Helper function which throws an exception if queue is full. * * \since v.5.5.13 */ template< typename Q > void ensure_queue_not_full( Q && queue ) { if( queue.is_full() ) SO_5_THROW_EXCEPTION( rc_msg_chain_is_full, "an attempt to push a message to full demand queue" ); } // // unlimited_demand_queue // /*! * \brief Implementation of demands queue for size-unlimited message chain. * * \since v.5.5.13 */ class unlimited_demand_queue { public : /*! * \note This constructor is necessary just for a convinience. */ unlimited_demand_queue( const capacity_t & ) {} //! Is queue full? /*! * \note Unlimited queue can't be null. Because of that this * method always returns \a false. */ [[nodiscard]] bool is_full() const noexcept { return false; } //! Is queue empty? [[nodiscard]] bool is_empty() const noexcept { return m_queue.empty(); } //! Access to front item from queue. [[nodiscard]] demand_t & front() { ensure_queue_not_empty( *this ); return m_queue.front(); } //! Remove the front item from queue. void pop_front() { ensure_queue_not_empty( *this ); m_queue.pop_front(); } //! Add a new item to the end of the queue. void push_back( demand_t && demand ) { m_queue.push_back( std::move(demand) ); } //! Size of the queue. [[nodiscard]] std::size_t size() const noexcept { return m_queue.size(); } private : //! Queue's storage. std::deque< demand_t > m_queue; }; // // limited_dynamic_demand_queue // /*! * \brief Implementation of demands queue for size-limited message chain with * dynamically allocated storage. * * \since v.5.5.13 */ class limited_dynamic_demand_queue { public : //! Initializing constructor. limited_dynamic_demand_queue( const capacity_t & capacity ) : m_max_size{ capacity.max_size() } {} //! Is queue full? [[nodiscard]] bool is_full() const noexcept { return m_max_size == m_queue.size(); } //! Is queue empty? [[nodiscard]] bool is_empty() const noexcept { return m_queue.empty(); } //! Access to front item from queue. [[nodiscard]] demand_t & front() { ensure_queue_not_empty( *this ); return m_queue.front(); } //! Remove the front item from queue. void pop_front() { ensure_queue_not_empty( *this ); m_queue.pop_front(); } //! Add a new item to the end of the queue. void push_back( demand_t && demand ) { ensure_queue_not_full( *this ); m_queue.push_back( std::move(demand) ); } //! Size of the queue. [[nodiscard]] std::size_t size() const noexcept { return m_queue.size(); } private : //! Queue's storage. std::deque< demand_t > m_queue; //! Maximum size of the queue. const std::size_t m_max_size; }; // // limited_preallocated_demand_queue // /*! * \brief Implementation of demands queue for size-limited message chain with * preallocated storage. * * \since v.5.5.13 */ class limited_preallocated_demand_queue { public : //! Initializing constructor. limited_preallocated_demand_queue( const capacity_t & capacity ) : m_storage( capacity.max_size(), demand_t{} ) , m_max_size{ capacity.max_size() } , m_head{ 0 } , m_size{ 0 } {} //! Is queue full? [[nodiscard]] bool is_full() const noexcept { return m_max_size == m_size; } //! Is queue empty? [[nodiscard]] bool is_empty() const noexcept { return 0 == m_size; } //! Access to front item from queue. [[nodiscard]] demand_t & front() { ensure_queue_not_empty( *this ); return m_storage[ m_head ]; } //! Remove the front item from queue. void pop_front() { ensure_queue_not_empty( *this ); m_storage[ m_head ] = demand_t{}; m_head = (m_head + 1) % m_max_size; --m_size; } //! Add a new item to the end of the queue. void push_back( demand_t && demand ) { ensure_queue_not_full( *this ); auto index = (m_head + m_size) % m_max_size; m_storage[ index ] = std::move(demand); ++m_size; } //! Size of the queue. [[nodiscard]] std::size_t size() const noexcept { return m_size; } private : //! Queue's storage. std::vector< demand_t > m_storage; //! Maximum size of the queue. const std::size_t m_max_size; //! Index of the queue head. std::size_t m_head; //! The current size of the queue. std::size_t m_size; }; // // status // /*! * \brief Status of the message chain. * * \since v.5.5.13 */ enum class status { //! Bag is open and can be used for message sending. open, //! Bag is closed. New messages cannot be sent to it. closed }; } /* namespace details */ // // mchain_template // /*! * \brief Template-based implementation of message chain. * * \tparam Queue type of demand queue for message chain. * \tparam Tracing_Base type with message tracing implementation details. * * \since v.5.5.13 */ template< typename Queue, typename Tracing_Base > class mchain_template : public abstract_message_chain_t , private Tracing_Base { public : //! Initializing constructor. template< typename... Tracing_Args > mchain_template( //! SObjectizer Environment for which message chain is created. so_5::environment_t & env, //! Mbox ID for this chain. mbox_id_t id, //! Chain parameters. const mchain_params_t & params, //! Arguments for Tracing_Base's constructor. Tracing_Args &&... tracing_args ) : Tracing_Base( std::forward(tracing_args)... ) , m_env( env ) , m_id( id ) , m_capacity( params.capacity() ) , m_not_empty_notificator( params.not_empty_notificator() ) , m_empty_notificator( params.empty_notificator() ) , m_queue( params.capacity() ) {} mbox_id_t id() const override { return m_id; } void subscribe_event_handler( const std::type_index & /*msg_type*/, abstract_message_sink_t & /*subscriber*/ ) override { SO_5_THROW_EXCEPTION( rc_msg_chain_doesnt_support_subscriptions, "mchain doesn't support subscription" ); } void unsubscribe_event_handler( const std::type_index & /*msg_type*/, abstract_message_sink_t & /*subscriber*/ ) noexcept override {} std::string query_name() const override { std::ostringstream s; s << ""; return s.str(); } mbox_type_t type() const override { return mbox_type_t::multi_producer_single_consumer; } void do_deliver_message( message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int /*redirection_deep*/ ) override { switch( delivery_mode ) { case message_delivery_mode_t::ordinary: this->try_to_store_message_to_queue_ordinary_mode( msg_type, message ); break; case message_delivery_mode_t::nonblocking: this->try_to_store_message_to_queue_nonblocking_mode( msg_type, message ); break; } } /*! * \attention Will throw an exception because delivery * filter is not applicable to mchains. */ void set_delivery_filter( const std::type_index & /*msg_type*/, const delivery_filter_t & /*filter*/, abstract_message_sink_t & /*subscriber*/ ) override { SO_5_THROW_EXCEPTION( rc_msg_chain_doesnt_support_delivery_filters, "set_delivery_filter is called for mchain" ); } void drop_delivery_filter( const std::type_index & /*msg_type*/, abstract_message_sink_t & /*subscriber*/ ) noexcept override {} [[nodiscard]] extraction_status_t extract( demand_t & dest, duration_t empty_queue_timeout ) override { std::unique_lock< std::mutex > lock{ m_lock }; // If queue is empty we must wait for some time. bool queue_empty = m_queue.is_empty(); if( queue_empty ) { if( details::status::closed == m_status ) // Waiting for new messages has no sence because // chain is closed. return extraction_status_t::chain_closed; auto predicate = [this, &queue_empty]() -> bool { queue_empty = m_queue.is_empty(); return !queue_empty || details::status::closed == m_status; }; // Count of sleeping thread must be incremented before // going to sleep and decremented right after. ++m_threads_to_wakeup; auto decrement_threads = so_5::details::at_scope_exit( [this] { --m_threads_to_wakeup; } ); // Wait until arrival of any message or closing of chain. ::so_5::details::wait_for_big_interval( lock, m_underflow_cond, empty_queue_timeout, predicate ); } // If queue is still empty nothing can be extracted and // we must stop operation. if( queue_empty ) return details::status::open == m_status ? // The chain is still open so there must be this result extraction_status_t::no_messages : // The chain is closed and there must be different result extraction_status_t::chain_closed; return extract_demand_from_not_empty_queue( dest ); } bool empty() const override { std::lock_guard< std::mutex > lock{ m_lock }; return m_queue.is_empty(); } std::size_t size() const override { std::lock_guard< std::mutex > lock{ m_lock }; return m_queue.size(); } environment_t & environment() const noexcept override { return m_env; } protected : [[nodiscard]] extraction_status_t extract( demand_t & dest, select_case_t & select_case ) override { std::unique_lock< std::mutex > lock{ m_lock }; const bool queue_empty = m_queue.is_empty(); if( queue_empty ) { if( details::status::closed == m_status ) // There is no need to wait for something. return extraction_status_t::chain_closed; // In other cases select_tail must be modified. select_case.set_next( m_select_tail ); m_select_tail = &select_case; return extraction_status_t::no_messages; } else return extract_demand_from_not_empty_queue( dest ); } [[nodiscard]] mchain_props::push_status_t push( const std::type_index & msg_type, const message_ref_t & message, mchain_props::select_case_t & select_case ) override { typename Tracing_Base::deliver_op_tracer tracer{ *this, // as tracing base. *this, // as chain. msg_type, message }; std::unique_lock< std::mutex > lock{ m_lock }; // Message cannot be stored to closed chain. if( details::status::closed == m_status ) return mchain_props::push_status_t::chain_closed; if( m_queue.is_full() ) { // The select_case should be stored until there will // be a free space in the chain (or chain will be closed). select_case.set_next( m_select_tail ); m_select_tail = &select_case; return mchain_props::push_status_t::deffered; } else { // Just store a new message to the queue. complete_store_message_to_queue( tracer, msg_type, message ); return mchain_props::push_status_t::stored; } } void remove_from_select( select_case_t & select_case ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; select_case_t * c = m_select_tail; select_case_t * prev = nullptr; while( c ) { select_case_t * const next = c->query_next(); if( c == &select_case ) { if( prev ) prev->set_next( next ); else m_select_tail = next; return; } prev = c; c = next; } } void actual_close( close_mode_t mode ) override { std::lock_guard< std::mutex > lock{ m_lock }; if( details::status::closed == m_status ) return; m_status = details::status::closed; const bool was_full = m_queue.is_full(); if( close_mode_t::drop_content == mode ) { while( !m_queue.is_empty() ) { this->trace_demand_drop_on_close( *this, m_queue.front() ); m_queue.pop_front(); } } // Since v.5.7.0 select operations must be notified // always, even if the mchain is not empty. notify_multi_chain_select_ops(); if( m_threads_to_wakeup ) // Someone is waiting on empty chain for new messages. // It must be informed that no new messages will be here. m_underflow_cond.notify_all(); if( was_full ) // Someone can wait on full chain for free place for new message. // It must be informed that the chain is closed. m_overflow_cond.notify_all(); } private : //! SObjectizer Environment for which message chain is created. environment_t & m_env; //! Status of the chain. details::status m_status = { details::status::open }; //! Mbox ID for chain. const mbox_id_t m_id; //! Chain capacity. const capacity_t m_capacity; //! Optional notificator for 'not_empty' condition. const not_empty_notification_func_t m_not_empty_notificator; //! Optional notificator for 'empty' condition. /*! * \since v.5.8.5 */ const empty_notification_func_t m_empty_notificator; //! Chain's demands queue. Queue m_queue; //! Chain's lock. /*! * \note * It's mutable to be able to lock it in const methods. */ mutable std::mutex m_lock; //! Condition variable for waiting on empty queue. std::condition_variable m_underflow_cond; //! Condition variable for waiting on full queue. std::condition_variable m_overflow_cond; /*! * \brief Count of threads sleeping on empty mchain. * * This value is incremented before sleeping on m_underflow_cond and * decremented just after a return from this sleep. * * \since v.5.5.16 */ std::size_t m_threads_to_wakeup = { 0 }; /*! * \brief A queue of multi-chain selects in which this chain is used. * * \since v.5.5.16 */ select_case_t * m_select_tail = nullptr; //! Actual implementation of pushing message to the queue. /*! * \note * This implementation must be used for ordinary delivery operations. * For delivery operations from timer thread another method must be * called (see try_to_store_message_to_queue_nonblocking_mode()). */ void try_to_store_message_to_queue_ordinary_mode( const std::type_index & msg_type, const message_ref_t & message ) { typename Tracing_Base::deliver_op_tracer tracer{ *this, // as tracing base. *this, // as chain. msg_type, message }; std::unique_lock< std::mutex > lock{ m_lock }; // Message cannot be stored to closed chain. if( details::status::closed == m_status ) return; // If queue full and waiting on full queue is enabled we // must wait for some time until there will be some space in // the queue. bool queue_full = m_queue.is_full(); if( queue_full && m_capacity.is_overflow_timeout_defined() ) { ::so_5::details::wait_for_big_interval( lock, m_overflow_cond, m_capacity.overflow_timeout(), [this, &queue_full] { queue_full = m_queue.is_full(); return !queue_full || details::status::closed == m_status; } ); // Message cannot be stored to closed chain. // // NOTE: this additional check is necessary after // wait for overflow_timeout because the chain can // be closed during that wait. if( details::status::closed == m_status ) return; } // If queue still full we must perform some reaction. if( queue_full ) { const auto reaction = m_capacity.overflow_reaction(); if( overflow_reaction_t::drop_newest == reaction ) { // New message must be simply ignored. tracer.overflow_drop_newest(); return; } else if( overflow_reaction_t::remove_oldest == reaction ) { // The oldest message must be simply removed. tracer.overflow_remove_oldest( m_queue.front() ); m_queue.pop_front(); } else if( overflow_reaction_t::throw_exception == reaction ) { tracer.overflow_throw_exception(); SO_5_THROW_EXCEPTION( rc_msg_chain_overflow, "an attempt to push message to full mchain " "with overflow_reaction_t::throw_exception policy" ); } else { so_5::details::abort_on_fatal_error( [&] { tracer.overflow_throw_exception(); SO_5_LOG_ERROR( m_env, log_stream ) { log_stream << "overflow_reaction_t::abort_app " "will be performed for mchain (id=" << m_id << "), msg_type: " << msg_type.name() << ". Application will be aborted" << std::endl; } } ); } } complete_store_message_to_queue( tracer, msg_type, message ); } /*! * \brief An implementation of storing another message to * chain for the case of delated/periodic messages. * * This implementation handles overloaded chains differently: * - there is no waiting on overloaded chain (even if such waiting * is specified in mchain params); * - overflow_reaction_t::throw_exception is replaced by * overflow_reaction_t::drop_newest. * * These defferences are necessary because the context of timer * thread is very special: there can't be any long-time operation * (like waiting for free space on overloaded chain) and there can't * be an exception about mchain's overflow. * * \since v.5.5.18 */ void try_to_store_message_to_queue_nonblocking_mode( const std::type_index & msg_type, const message_ref_t & message ) { typename Tracing_Base::deliver_op_tracer tracer{ *this, // as tracing base. *this, // as chain. msg_type, message }; std::unique_lock< std::mutex > lock{ m_lock }; // Message cannot be stored to closed chain. if( details::status::closed == m_status ) return; bool queue_full = m_queue.is_full(); // NOTE: there is no awaiting on full mchain. // If queue full we must perform some reaction. if( queue_full ) { const auto reaction = m_capacity.overflow_reaction(); if( overflow_reaction_t::drop_newest == reaction || overflow_reaction_t::throw_exception == reaction ) { // New message must be simply ignored. tracer.overflow_drop_newest(); return; } else if( overflow_reaction_t::remove_oldest == reaction ) { // The oldest message must be simply removed. tracer.overflow_remove_oldest( m_queue.front() ); m_queue.pop_front(); } else { so_5::details::abort_on_fatal_error( [&] { tracer.overflow_throw_exception(); SO_5_LOG_ERROR( m_env, log_stream ) { log_stream << "overflow_reaction_t::abort_app " "will be performed for mchain (id=" << m_id << "), msg_type: " << msg_type.name() << ". Application will be aborted" << std::endl; } } ); } } complete_store_message_to_queue( tracer, msg_type, message ); } /*! * \brief Implementation of extract operation for the case when * message queue is not empty. * * \attention This helper method must be called when chain object * is locked in some hi-level method. * * \since v.5.5.16 */ extraction_status_t extract_demand_from_not_empty_queue( demand_t & dest ) { // If queue was full then someone can wait on it. const bool queue_was_full = m_queue.is_full(); dest = std::move( m_queue.front() ); m_queue.pop_front(); this->trace_extracted_demand( *this, dest ); // Since v.5.8.5 there could be an empty notificator // that has to be used if the queue becomes empty. if( m_queue.is_empty() ) { if( m_empty_notificator ) so_5::details::invoke_noexcept_code( [this] { m_empty_notificator(); } ); } if( queue_was_full ) { // Since v.5.7.0 waiting select_cases should be // notified too because they are send_cases. notify_multi_chain_select_ops(); m_overflow_cond.notify_all(); } return extraction_status_t::msg_extracted; } /*! * \since v.5.5.16 */ void notify_multi_chain_select_ops() noexcept { if( m_select_tail ) { auto old = m_select_tail; m_select_tail = nullptr; old->notify(); } } /*! * \brief A reusable method with implementation of * last part of storing a message into chain. * * \note * Intended to be called from try_to_store_message_to_queue_ordinary_mode() * and try_to_store_message_to_queue_nonblocking_mode(). * * \since v.5.5.18 */ void complete_store_message_to_queue( typename Tracing_Base::deliver_op_tracer & tracer, const std::type_index & msg_type, const message_ref_t & message ) { const bool was_empty = m_queue.is_empty(); m_queue.push_back( demand_t{ msg_type, message } ); tracer.stored( m_queue ); // If chain was empty then multi-chain cases must be notified. // And if not_empty_notificator is defined then it must be used too. if( was_empty ) { if( m_not_empty_notificator ) so_5::details::invoke_noexcept_code( [this] { m_not_empty_notificator(); } ); notify_multi_chain_select_ops(); } // Should be wake up some sleeping thread? if( m_threads_to_wakeup && m_threads_to_wakeup >= m_queue.size() ) // Someone is waiting on empty queue. m_underflow_cond.notify_one(); } }; } /* namespace mchain_props */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/message_limit_action_msg_tracer.hpp000066400000000000000000000037251523065210100255410ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.9 * * \file * \brief Declaration of action_msg_tracer for overlimit action tracing. */ #if !defined( SO_5_IMPL_MESSAGE_LIMIT_ACTION_MSG_TRACER_HPP ) #define SO_5_IMPL_MESSAGE_LIMIT_ACTION_MSG_TRACER_HPP #include namespace so_5 { namespace message_limit { namespace impl { // // action_msg_tracer_t // /*! * \since * v.5.5.9 * * \brief An interface of tracer for message delivery tracing. */ class action_msg_tracer_t { protected : ~action_msg_tracer_t() {} public : //! Execution demand for agent will be pushed to the queue by a sink. virtual void push_to_queue( //! The sink that pushes an execution demand. const abstract_message_sink_t * sink, //! The owner of the sink and the target for execution queue. const agent_t * sink_owner ) const noexcept = 0; //! Application will be aborted as result of overlimit. virtual void reaction_abort_app( //! Receiver for the problematic message. const agent_t * subscriber ) const noexcept = 0; //! Message will be dropped as result of overlimit. virtual void reaction_drop_message( //! Receiver for the problematic message. const agent_t * subscriber ) const noexcept = 0; //! Message will be redirected to another mbox. virtual void reaction_redirect_message( //! Receiver for the problematic message. const agent_t * subscriber, //! A new destination for the message. const mbox_t & target ) const noexcept = 0; //! Message will be transformed and redirected. virtual void reaction_transform( //! Receiver for the problematic message. const agent_t * subscriber, //! A new destination for transformed message. const mbox_t & target, //! A new type of message. const std::type_index & msg_type, //! An instance of new message. const message_ref_t & transformed ) const noexcept = 0; }; } /* namespace impl */ } /* namespace message_limit */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/message_sink_for_agent.hpp000066400000000000000000000026631523065210100236500ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since v.5.8.0 * * \brief A base class for message sinks to be used by agents. */ #if !defined( SO_5_IMPL_MESSAGE_SINK_FOR_AGENT_HPP ) #define SO_5_IMPL_MESSAGE_SINK_FOR_AGENT_HPP #include namespace so_5 { namespace impl { // // message_sink_for_agent_t // /*! * \brief A base class for message sinks to be used by agents. * * \note * It's abstract class, it doesn't implement push() method. * But it introduces owner() method. * * \since v.5.8.0 */ class message_sink_for_agent_t : public abstract_message_sink_t { //! Owner of the sink. agent_t & m_owner; public: //! Constructor for a case when agent is being created. message_sink_for_agent_t( partially_constructed_agent_ptr_t owner_ptr ) : m_owner( *(owner_ptr.ptr()) ) {} //! Constructor for a case when agent is already created. message_sink_for_agent_t( outliving_reference_t< agent_t > owner ) : m_owner( owner.get() ) {} environment_t & environment() const noexcept override { return m_owner.so_environment(); } priority_t sink_priority() const noexcept override { return m_owner.so_priority(); } [[nodiscard]] agent_t & owner_reference() const noexcept { return m_owner; } [[nodiscard]] agent_t * owner_pointer() const noexcept { return std::addressof(m_owner); } }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/message_sink_with_message_limit.hpp000066400000000000000000000057621523065210100255640ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since v.5.8.0 * * \brief Implementation of message sink that handles message limits. */ #if !defined( SO_5_IMPL_MESSAGE_SINK_WITH_MESSAGE_LIMIT_HPP ) #define SO_5_IMPL_MESSAGE_SINK_WITH_MESSAGE_LIMIT_HPP #include namespace so_5 { namespace impl { namespace push_event_impl { #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunneeded-member-function" #pragma clang diagnostic ignored "-Wunused-member-function" #endif /*! * \since * v.5.5.4 * * \brief Helper class to rollback message count change in * case of an exception. */ struct decrement_on_exception_t { const so_5::message_limit::control_block_t * m_limit; bool m_commited = false; decrement_on_exception_t( const so_5::message_limit::control_block_t * limit ) : m_limit( limit ) {} ~decrement_on_exception_t() { if( !m_commited ) --(m_limit->m_count); } void commit() { m_commited = true; } }; #if defined(__clang__) #pragma clang diagnostic pop #endif } /* namespace push_event_impl */ // // message_sink_with_message_limit_t // /*! * \brief Implementation of message_sink that handles message limits. * * \since v.5.8.0 */ class message_sink_with_message_limit_t final : public message_sink_for_agent_t { //! Run-time data for the message type. so_5::message_limit::control_block_t m_control_block; public: //! Initializing constructor. message_sink_with_message_limit_t( //! Owner of the sink. partially_constructed_agent_ptr_t owner_ptr, //! Limit for that message type. unsigned int limit, //! Reaction to the limit overflow. so_5::message_limit::action_t action ) : message_sink_for_agent_t( owner_ptr ) , m_control_block( limit, std::move( action ) ) {} void push_event( mbox_id_t mbox_id, message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep, const message_limit::impl::action_msg_tracer_t * tracer ) override { if( m_control_block.m_limit < ++(m_control_block.m_count) ) { --(m_control_block.m_count); m_control_block.m_action( so_5::message_limit::overlimit_context_t{ mbox_id, delivery_mode, owner_reference(), m_control_block, redirection_deep, msg_type, message, tracer } ); } else { using namespace push_event_impl; decrement_on_exception_t exception_guard( std::addressof( m_control_block ) ); // The fact of pushing message to the queue // has to be logged if msg_tracing is on. if( tracer ) tracer->push_to_queue( this, owner_pointer() ); agent_t::call_push_event( owner_reference(), std::addressof( m_control_block ), mbox_id, msg_type, message ); exception_guard.commit(); } } }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/message_sink_without_message_limit.hpp000066400000000000000000000031051523065210100263010ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since v.5.8.0 * * \brief Implementation of message sink that ignores message limits. */ #if !defined( SO_5_IMPL_MESSAGE_SINK_WITHOUT_MESSAGE_LIMIT_HPP ) #define SO_5_IMPL_MESSAGE_SINK_WITHOUT_MESSAGE_LIMIT_HPP #include namespace so_5 { namespace impl { // // message_sink_without_message_limit_t // /*! * \brief Implementation of message_sink that has no message limits. * * \since v.5.8.0 */ class message_sink_without_message_limit_t final : public message_sink_for_agent_t { public: //! Constructor for a case when agent is being created. message_sink_without_message_limit_t( partially_constructed_agent_ptr_t owner_ptr ) : message_sink_for_agent_t{ owner_ptr } {} //! Constructor for a case when agent is already created. message_sink_without_message_limit_t( outliving_reference_t< agent_t > owner ) : message_sink_for_agent_t{ owner } {} void push_event( mbox_id_t mbox_id, message_delivery_mode_t /*delivery_mode*/, const std::type_index & msg_type, const message_ref_t & message, unsigned int /*redirection_deep*/, const message_limit::impl::action_msg_tracer_t * tracer ) override { // The fact of pushing message to the queue // has to be logged if msg_tracing is on. if( tracer ) tracer->push_to_queue( this, owner_pointer() ); agent_t::call_push_event( owner_reference(), nullptr /* no message limit */, mbox_id, msg_type, message ); } }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/mpsc_mbox.hpp000066400000000000000000000301361523065210100211370ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \since v.5.4.0 \brief A multi-producer/single-consumer mbox definition. */ #if !defined( SO_5_IMPL_MPSC_MBOX_HPP ) #define SO_5_IMPL_MPSC_MBOX_HPP #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace impl { /*! * \brief Mixin to be used in implementation of MPSC mbox with message limits. * * It returns a reference to a sink from subscription_info_with_sink_t object * in message_sink_to_use() method. */ class limitful_mpsc_mbox_mixin_t { agent_t & m_owner; protected: [[nodiscard]] agent_t & query_owner_reference() const noexcept { return m_owner; } [[nodiscard]] abstract_message_sink_t & message_sink_to_use( const local_mbox_details::subscription_info_with_sink_t & info ) const noexcept { return info.sink_reference(); } public: limitful_mpsc_mbox_mixin_t( outliving_reference_t< agent_t > owner ) : m_owner{ owner.get() } {} }; /*! * \brief Mixin to be used in implementation of MPSC mbox without message limits. * * Holds an actual instance of message_sink_without_message_limit_t. * * It ignores \a info parameter to message_sink_to_use() method and returns * a reference to m_actual_sink. */ class limitless_mpsc_mbox_mixin_t { //! Actual message sink to be used. message_sink_without_message_limit_t m_actual_sink; protected: [[nodiscard]] agent_t & query_owner_reference() const noexcept { return m_actual_sink.owner_reference(); } [[nodiscard]] abstract_message_sink_t & message_sink_to_use( const local_mbox_details::subscription_info_with_sink_t & /*info*/ ) noexcept { return m_actual_sink; } public: limitless_mpsc_mbox_mixin_t( outliving_reference_t< agent_t > owner ) : m_actual_sink{ owner } {} }; namespace { /*! * \brief Helper the ensures that sink can be used with agent. * * It throws if: * * - \a sink is not a message_sink_for_agent_t (or derived class); * - \a sink is created for a different owner. */ void ensure_sink_for_same_owner( agent_t & actual_owner, abstract_message_sink_t & sink ) { auto * p = dynamic_cast( std::addressof(sink) ); if( !p ) SO_5_THROW_EXCEPTION( rc_illegal_subscriber_for_mpsc_mbox, "unexpected type of message_sink is used for subscription " "to agent's direct mbox" ); if( std::addressof(actual_owner) != p->owner_pointer() ) SO_5_THROW_EXCEPTION( rc_illegal_subscriber_for_mpsc_mbox, "the only one consumer can create subscription to mpsc_mbox" ); } } /* namespace anonymous */ // // mpsc_mbox_template_t // /*! * \since * v.5.4.0, v.5.7.1 * * \brief A multi-producer/single-consumer mbox definition. * * \note Renamed from limitful_mpsc_mbox_t to limitful_mpsc_mbox_template * in v.5.5.9. * \note Renamed from limitful_mpsc_mbox_template to * limitful_mpsc_mbox_template_t in v.5.7.1. * \note Renamed from limitful_mpsc_mbox_template_t to * mpsc_mbox_template_t in v.5.7.4. */ template< typename Tracing_Base, typename Limits_Handling_Mixin > class mpsc_mbox_template_t final : public abstract_message_box_t , private Limits_Handling_Mixin , protected Tracing_Base { public: template< typename... Tracing_Args > mpsc_mbox_template_t( mbox_id_t id, environment_t & env, outliving_reference_t< agent_t > owner, Tracing_Args &&... tracing_args ) : Limits_Handling_Mixin{ owner } , Tracing_Base{ std::forward< Tracing_Args >( tracing_args )... } , m_id{ id } , m_env{ env } {} mbox_id_t id() const override { return m_id; } void subscribe_event_handler( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) override { std::lock_guard< default_rw_spinlock_t > lock{ m_lock }; ensure_sink_for_same_owner( this->query_owner_reference(), subscriber ); insert_or_modify_subscription( msg_type, [&] { return subscription_info_t{ subscriber }; }, [&]( subscription_info_t & info ) { info.set_sink( subscriber ); } ); } void unsubscribe_event_handler( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) noexcept override { std::lock_guard< default_rw_spinlock_t > lock{ m_lock }; // ensure_sink_for_same_owner can't be used here because // we're in noexcept method. // So let's check the possiblity of unsubscription right here. { auto * p = dynamic_cast( std::addressof(subscriber) ); if( !p ) return; if( std::addressof(this->query_owner_reference()) != p->owner_pointer() ) return; } modify_and_remove_subscription_if_needed( msg_type, []( subscription_info_t & info ) { info.drop_sink(); } ); } std::string query_name() const override { std::ostringstream s; s << ""; return s.str(); } mbox_type_t type() const override { return mbox_type_t::multi_producer_single_consumer; } void do_deliver_message( message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) override { typename Tracing_Base::deliver_op_tracer tracer{ *this, // as Tracing_Base *this, // as abstract_message_box_t "deliver_message", delivery_mode, msg_type, message, redirection_deep }; this->do_delivery( msg_type, message, tracer, [&]( const subscription_info_t & info ) { // NOTE: method message_sink_to_use is inherited from // Limits_Handling_Mixin. In the case of // limitful_mpsc_mbox_mixin_t this method returns a reference // to a separate sink with message limit info. In the case of // limitless_mpsc_mbox_mixin_t it will be a reference to the // single message sink without information about limits. this->message_sink_to_use( info ).push_event( this->m_id, delivery_mode, msg_type, message, redirection_deep, tracer.overlimit_tracer() ); } ); } void set_delivery_filter( const std::type_index & msg_type, const delivery_filter_t & filter, abstract_message_sink_t & subscriber ) override { std::lock_guard< default_rw_spinlock_t > lock{ m_lock }; ensure_sink_for_same_owner( this->query_owner_reference(), subscriber ); insert_or_modify_subscription( msg_type, [&] { return subscription_info_t{ filter }; }, [&]( subscription_info_t & info ) { info.set_filter( filter ); } ); } void drop_delivery_filter( const std::type_index & msg_type, abstract_message_sink_t & /*subscriber*/ ) noexcept override { std::lock_guard< default_rw_spinlock_t > lock{ m_lock }; modify_and_remove_subscription_if_needed( msg_type, []( subscription_info_t & info ) { info.drop_filter(); } ); } environment_t & environment() const noexcept override { return m_env; } protected : /*! * \brief Information related to a subscribed message type. * * \since v.5.7.4 */ using subscription_info_t = local_mbox_details::subscription_info_with_sink_t; /*! * \brief Type of dictionary for information about the current * subscriptions. * * \since v.5.7.1 */ using subscriptions_map_t = std::map< std::type_index, subscription_info_t >; /*! * \brief ID of this mbox. */ const mbox_id_t m_id; /*! * \brief Environment in that the mbox was created. * * \note * Previous versions of SObjectizer didn't hold this reference * in MPSC-mbox implementations. Instead, the reference was * obtained via m_single_consumer. But this approach lead to * access violation when MPSC-mbox's environment() method was * called after the deregistration of m_single_consumer. * Because of than the reference to SOEnv is stored inside * MPSC-mbox and is returned by environment() method. * * \since v.5.7.5 */ environment_t & m_env; /*! * \brief Protection of object from modification. * * \since v.5.5.9 */ default_rw_spinlock_t m_lock; /*! * \brief Information about the current subscriptions. * * \since v.5.7.1 */ subscriptions_map_t m_subscriptions; /*! * \brief Helper for performing insertion or modification * of subscription info. * * \since v.5.7.4 */ template< typename Info_Maker, typename Info_Changer > void insert_or_modify_subscription( const std::type_index & msg_type, Info_Maker maker, Info_Changer changer ) { auto it = m_subscriptions.find( msg_type ); if( it == m_subscriptions.end() ) { // Subscription for that type has to be created. m_subscriptions.emplace( msg_type, maker() ); } else { // Subscription for that type has to be modified. changer( it->second ); } } /*! * \brief Helper for modification and deletion of * subscription info. * * \since v.5.7.4 */ template< typename Info_Changer > void modify_and_remove_subscription_if_needed( const std::type_index & msg_type, Info_Changer changer ) { auto it = m_subscriptions.find( msg_type ); if( it != m_subscriptions.end() ) { changer( it->second ); if( it->second.empty() ) m_subscriptions.erase( it ); } } /*! * \since * v.5.5.9 * * \brief Helper method to do delivery actions under locked object. * * \tparam L lambda with actual delivery actions. */ template< typename L > void do_delivery( //! Type of message/signal to be delivered. const std::type_index & msg_type, //! Message object to be delivered. const message_ref_t & message, //! Tracer object to log the case of abscense of subscriptions. typename Tracing_Base::deliver_op_tracer const & tracer, //! Lambda with actual delivery actions. L l ) { read_lock_guard_t< default_rw_spinlock_t > lock{ m_lock }; const auto it = m_subscriptions.find( msg_type ); if( it != m_subscriptions.end() ) { // Since v.5.7.4 we have to ask delivery_filter before the // delivery attempt. const auto delivery_status = it->second.must_be_delivered( message, []( const message_ref_t & m ) -> message_t & { return *m; } ); if( delivery_possibility_t::must_be_delivered == delivery_status ) { l( it->second ); } else { tracer.message_rejected( // It's safe to pass nullptr as a pointer // to the subscriber. nullptr, delivery_status ); } } else { tracer.no_subscribers(); } } }; /*! * \since v.5.5.9, v.5.7.4, v.5.8.0 * * \brief Alias for mpsc_mbox without message delivery tracing. */ using ordinary_mpsc_mbox_without_tracing_t = mpsc_mbox_template_t< msg_tracing_helpers::tracing_disabled_base, limitful_mpsc_mbox_mixin_t >; /*! * \since v.5.5.9, v.5.7.4, v.5.8.0 * * \brief Alias for mpsc_mbox with message delivery tracing. */ using ordinary_mpsc_mbox_with_tracing_t = mpsc_mbox_template_t< msg_tracing_helpers::tracing_enabled_base, limitful_mpsc_mbox_mixin_t >; /*! * \brief Alias for mpsc_mbox without message delivery tracing that * ignores message limits. * * \since v.5.8.0 */ using limitless_mpsc_mbox_without_tracing_t = mpsc_mbox_template_t< msg_tracing_helpers::tracing_disabled_base, limitless_mpsc_mbox_mixin_t >; /*! * \brief Alias for mpsc_mbox with message delivery tracing that * ignores message limits. * * \since v.5.8.0 */ using limitless_mpsc_mbox_with_tracing_t = mpsc_mbox_template_t< msg_tracing_helpers::tracing_enabled_base, limitless_mpsc_mbox_mixin_t >; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/msg_tracing_helpers.cpp000066400000000000000000000052121523065210100231570ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Implementation of some helpers for message tracing. * * \since v.5.5.22 */ #include namespace so_5 { namespace impl { namespace msg_tracing_helpers { namespace details { // // actual_trace_data_t // optional actual_trace_data_t::tid() const noexcept { return m_tid; } optional actual_trace_data_t::agent() const noexcept { return m_agent; } optional actual_trace_data_t::message_sink() const noexcept { return m_message_sink; } optional actual_trace_data_t::msg_type() const noexcept { return m_msg_type; } optional actual_trace_data_t::msg_source() const noexcept { return m_msg_source; } optional actual_trace_data_t::message_or_signal() const noexcept { return m_message_or_signal; } optional actual_trace_data_t::message_instance_info() const noexcept { return m_message_instance_info; } optional actual_trace_data_t::compound_action() const noexcept { return m_compound_action; } optional actual_trace_data_t::event_handler_data_ptr() const noexcept { return m_event_handler_data_ptr; } void actual_trace_data_t::set_tid( current_thread_id_t tid ) noexcept { m_tid = tid; } void actual_trace_data_t::set_agent( const agent_t * agent ) noexcept { m_agent = agent; } void actual_trace_data_t::set_message_sink( const abstract_message_sink_t * sink ) noexcept { m_message_sink = sink; } void actual_trace_data_t::set_msg_type( const std::type_index & msg_type ) noexcept { m_msg_type = msg_type; } void actual_trace_data_t::set_msg_source( so_5::msg_tracing::msg_source_t info ) noexcept { m_msg_source = info; } void actual_trace_data_t::set_message_or_signal( so_5::msg_tracing::message_or_signal_flag_t flag ) noexcept { m_message_or_signal = flag; } void actual_trace_data_t::set_message_instance_info( so_5::msg_tracing::message_instance_info_t info ) noexcept { m_message_instance_info = info; } void actual_trace_data_t::set_compound_action( so_5::msg_tracing::compound_action_description_t desc ) noexcept { m_compound_action = desc; } void actual_trace_data_t::set_event_handler_data_ptr( const so_5::impl::event_handler_data_t * ptr ) noexcept { m_event_handler_data_ptr = ptr; } } /* namespace details */ } /* namespace msg_tracing_helpers */ } /* namespace impl */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/msg_tracing_helpers.hpp000066400000000000000000000575561523065210100232060ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Various helpers for message delivery tracing stuff. * * \since v.5.5.9 */ #if !defined( SO_5_IMPL_MSG_TRACING_HELPERS_HPP ) #define SO_5_IMPL_MSG_TRACING_HELPERS_HPP #include #include #include #include #include #include #include #include #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { namespace impl { namespace msg_tracing_helpers { namespace details { using namespace so_5::details::ios_helpers; // // actual_trace_data_t // /*! * \brief An actual implementation of trace data interface. * * \since * v.5.5.22 */ class SO_5_TYPE actual_trace_data_t : public so_5::msg_tracing::trace_data_t { public : virtual optional tid() const noexcept override; virtual optional agent() const noexcept override; virtual optional message_sink() const noexcept override; virtual optional msg_type() const noexcept override; virtual optional msg_source() const noexcept override; virtual optional message_or_signal() const noexcept override; virtual optional message_instance_info() const noexcept override; virtual optional compound_action() const noexcept override; virtual optional event_handler_data_ptr() const noexcept override; void set_tid( current_thread_id_t tid ) noexcept; void set_agent( const agent_t * agent ) noexcept; void set_message_sink( const abstract_message_sink_t * message_sink ) noexcept; void set_msg_type( const std::type_index & msg_type ) noexcept; void set_msg_source( so_5::msg_tracing::msg_source_t info ) noexcept; void set_message_or_signal( so_5::msg_tracing::message_or_signal_flag_t flag ) noexcept; void set_message_instance_info( so_5::msg_tracing::message_instance_info_t info ) noexcept; void set_compound_action( so_5::msg_tracing::compound_action_description_t desc ) noexcept; void set_event_handler_data_ptr( const so_5::impl::event_handler_data_t * ptr ) noexcept; private : optional m_tid; optional m_agent; optional m_message_sink; optional m_msg_type; optional m_msg_source; optional m_message_or_signal; optional m_message_instance_info; optional m_compound_action; optional m_event_handler_data_ptr; }; struct redirection_deep { unsigned int m_deep; // Note: this constructor is necessary for compatibility with MSVC++2013. redirection_deep( unsigned int deep ) : m_deep{ deep } {} }; struct mbox_identification { mbox_id_t m_id; }; struct mbox_as_msg_source { const abstract_message_box_t & m_mbox; }; struct mbox_as_msg_destination { const abstract_message_box_t & m_mbox; }; struct mchain_as_msg_source { const abstract_message_chain_t & m_mchain; }; struct mchain_identification { mbox_id_t m_id; }; struct text_separator { const char * m_text; }; struct composed_action_name { const char * m_1; const char * m_2; }; struct chain_size { std::size_t m_size; }; struct original_msg_type { const std::type_index & m_type; }; struct type_of_removed_msg { const std::type_index & m_type; }; struct type_of_transformed_msg { const std::type_index & m_type; }; inline void make_trace_to_1( std::ostream & s, current_thread_id_t tid ) { s << "[tid=" << tid << "]"; } inline void fill_trace_data_1( actual_trace_data_t & d, current_thread_id_t tid ) { d.set_tid( tid ); } inline void make_trace_to_1( std::ostream & s, mbox_identification id ) { s << "[mbox_id=" << id.m_id << "]"; } inline void fill_trace_data_1( actual_trace_data_t & d, mbox_identification id ) { d.set_msg_source( so_5::msg_tracing::msg_source_t{ id.m_id, so_5::msg_tracing::msg_source_type_t::unknown } ); } inline void make_trace_to_1( std::ostream & s, mchain_identification id ) { s << "[mchain_id=" << id.m_id << "]"; } inline void fill_trace_data_1( actual_trace_data_t & d, mchain_identification id ) { d.set_msg_source( so_5::msg_tracing::msg_source_t{ id.m_id, so_5::msg_tracing::msg_source_type_t::mchain } ); } inline void make_trace_to_1( std::ostream & s, const mbox_as_msg_source mbox ) { make_trace_to_1( s, mbox_identification{ mbox.m_mbox.id() } ); } inline void fill_trace_data_1( actual_trace_data_t & d, const mbox_as_msg_source & mbox ) { d.set_msg_source( so_5::msg_tracing::msg_source_t{ mbox.m_mbox.id(), so_5::msg_tracing::msg_source_type_t::mbox } ); } inline void make_trace_to_1( std::ostream & s, const mbox_as_msg_destination mbox ) { make_trace_to_1( s, mbox_identification{ mbox.m_mbox.id() } ); } inline void fill_trace_data_1( actual_trace_data_t & /*d*/, const mbox_as_msg_destination & /*mbox*/ ) { // Just for compilation. } inline void make_trace_to_1( std::ostream & s, const abstract_message_chain_t & chain ) { make_trace_to_1( s, mchain_identification{ chain.id() } ); } inline void fill_trace_data_1( actual_trace_data_t & d, const abstract_message_chain_t & chain ) { fill_trace_data_1( d, mchain_identification{ chain.id() } ); } inline void make_trace_to_1( std::ostream & s, const original_msg_type msg_type ) { s << "[msg_type=" << msg_type.m_type.name() << "]"; } inline void fill_trace_data_1( actual_trace_data_t & d, const original_msg_type msg_type ) { d.set_msg_type( msg_type.m_type ); } inline void make_trace_to_1( std::ostream & s, const type_of_removed_msg msg_type ) { s << "removed:[msg_type=" << msg_type.m_type.name() << "]"; } inline void fill_trace_data_1( actual_trace_data_t & /*d*/, const type_of_removed_msg /*msg_type*/ ) { // Just for compilation. } inline void make_trace_to_1( std::ostream & s, const type_of_transformed_msg msg_type ) { s << "[msg_type=" << msg_type.m_type.name() << "]"; } inline void fill_trace_data_1( actual_trace_data_t & /*d*/, const type_of_transformed_msg /*msg_type*/ ) { // Just for compilation. } inline void make_trace_to_1( std::ostream & s, const abstract_message_sink_t * sink ) { s << "[msg_sink_ptr=" << pointer{sink} << "]"; } inline void fill_trace_data_1( actual_trace_data_t & d, const abstract_message_sink_t * sink ) { d.set_message_sink( sink ); } inline void make_trace_to_1( std::ostream & s, const agent_t * agent ) { s << "[agent_ptr=" << pointer{agent} << "]"; } inline void fill_trace_data_1( actual_trace_data_t & d, const agent_t * agent ) { d.set_agent( agent ); } inline void make_trace_to_1( std::ostream & s, const state_t * state ) { s << "[state=" << state->query_name() << "]"; } inline void fill_trace_data_1( actual_trace_data_t & /*d*/, const state_t * /*state*/ ) { // Just for compilation. } inline void make_trace_to_1( std::ostream & s, const event_handler_data_t * handler ) { s << "[evt_handler="; if( handler ) s << pointer{handler}; else s << "NONE"; s << "]"; } inline void fill_trace_data_1( actual_trace_data_t & d, const event_handler_data_t * handler ) { d.set_event_handler_data_ptr( handler ); } inline void make_trace_to_1( std::ostream & s, const so_5::message_limit::control_block_t * limit ) { s << "[limit_ptr=" << pointer{limit} << "]"; } inline void fill_trace_data_1( actual_trace_data_t & /*d*/, const so_5::message_limit::control_block_t * /*limit*/ ) { // Just for compilation. } inline void make_trace_to_1( std::ostream & s, const message_ref_t & message ) { const void * envelope = message.get(); if( envelope ) s << "[envelope_ptr=" << pointer{envelope} << "]"; else s << "[signal]"; if( message_mutability_t::mutable_message == message_mutability(message) ) s << "[mutable]"; } inline void fill_trace_data_1( actual_trace_data_t & d, const message_ref_t & message ) { const void * envelope = message.get(); if( !envelope ) { // This is a signal. d.set_message_or_signal( so_5::msg_tracing::message_or_signal_flag_t::signal ); } else { // This is a message. d.set_message_or_signal( so_5::msg_tracing::message_or_signal_flag_t::message ); d.set_message_instance_info( so_5::msg_tracing::message_instance_info_t{ envelope, message_mutability(message) } ); } } inline void make_trace_to_1( std::ostream & s, const redirection_deep limit ) { s << "[redirection_deep=" << limit.m_deep << "]"; } inline void fill_trace_data_1( actual_trace_data_t & /*d*/, const redirection_deep /*limit*/ ) { // Just for compilation. } inline void make_trace_to_1( std::ostream & s, const composed_action_name name ) { s << " " << name.m_1 << "." << name.m_2 << " "; } inline void fill_trace_data_1( actual_trace_data_t & d, const composed_action_name name ) { d.set_compound_action( so_5::msg_tracing::compound_action_description_t{ name.m_1, name.m_2 } ); } inline void make_trace_to_1( std::ostream & s, const text_separator text ) { s << " " << text.m_text << " "; } inline void fill_trace_data_1( actual_trace_data_t & /*d*/, const text_separator /*text*/ ) { // Just for compilation. } inline void make_trace_to_1( std::ostream & s, chain_size size ) { s << "[chain_size=" << size.m_size << "]"; } inline void fill_trace_data_1( actual_trace_data_t & /*d*/, chain_size /*size*/ ) { // Just for compilation. } inline void make_trace_to_1( std::ostream & s, message_delivery_mode_t mode ) { const auto mode_to_str = []( auto m ) -> const char * { const char * r = "unknown"; switch( m ) { case message_delivery_mode_t::ordinary: r = "ordinary"; break; case message_delivery_mode_t::nonblocking: r = "nonblocking"; break; } return r; }; s << "[delivery_mode=" << mode_to_str( mode ) << "]"; } inline void fill_trace_data_1( actual_trace_data_t & /*d*/, message_delivery_mode_t /*mode*/ ) { // Just for compilation. } inline void make_trace_to( std::ostream & ) {} inline void fill_trace_data( actual_trace_data_t & ) {} template< typename A, typename... Other > void make_trace_to( std::ostream & s, A && a, Other &&... other ) { make_trace_to_1( s, std::forward< A >(a) ); make_trace_to( s, std::forward< Other >(other)... ); } template< typename A, typename... Other > void fill_trace_data( actual_trace_data_t & d, A && a, Other &&... other ) { fill_trace_data_1( d, std::forward< A >(a) ); fill_trace_data( d, std::forward< Other >(other)... ); } template< typename... Args > void make_trace( so_5::msg_tracing::holder_t & msg_tracing_stuff, Args &&... args ) noexcept { const auto tid = query_current_thread_id(); // Since v.5.5.22 we should check the presence of filter. // If filter is present then we should pass a trace via filter. auto filter = msg_tracing_stuff.take_filter(); bool need_trace = true; if( filter ) { actual_trace_data_t data; fill_trace_data( data, tid, std::forward(args)... ); need_trace = filter->filter( data ); } if( need_trace ) { // Trace message should go to the tracer. std::ostringstream s; make_trace_to( s, tid, std::forward< Args >(args)... ); msg_tracing_stuff.tracer().trace( s.str() ); } } } /* namespace details */ // // tracing_disabled_base // /*! * \brief Base class for a mbox for the case when message delivery * tracing is disabled. * * \since v.5.5.9 */ struct tracing_disabled_base { class deliver_op_tracer { public : deliver_op_tracer( const tracing_disabled_base &, const abstract_message_box_t &, const char *, message_delivery_mode_t, const std::type_index &, const message_ref_t &, const unsigned int ) {} // NOTE: this dummy method added in v.5.5.19.3. template< typename... Args > void make_trace( const char * /*action_name_suffix*/, Args &&... /*args*/ ) const {} void no_subscribers() const {} void message_rejected( // NOTE: it can be nullptr. const abstract_message_sink_t *, const delivery_possibility_t ) const {} const so_5::message_limit::impl::action_msg_tracer_t * overlimit_tracer() const { return nullptr; } }; }; // // tracing_enabled_base // /*! * \brief Base class for a mbox for the case when message delivery * tracing is enabled. * * \since v.5.5.9 */ class tracing_enabled_base { private : so_5::msg_tracing::holder_t & m_tracer; public : tracing_enabled_base( outliving_reference_t< so_5::msg_tracing::holder_t > tracer ) : m_tracer( tracer.get() ) {} so_5::msg_tracing::holder_t & tracer() const { return m_tracer; } #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnon-virtual-dtor" #endif class deliver_op_tracer : protected so_5::message_limit::impl::action_msg_tracer_t { private : so_5::msg_tracing::holder_t & m_tracer; const abstract_message_box_t & m_mbox; const char * m_op_name; const message_delivery_mode_t m_delivery_mode; const std::type_index & m_msg_type; const message_ref_t & m_message; const details::redirection_deep m_redirection_deep; public : deliver_op_tracer( const tracing_enabled_base & tracing_base, const abstract_message_box_t & mbox, const char * op_name, message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, const unsigned int redirection_deep ) : m_tracer( tracing_base.tracer() ) , m_mbox( mbox ) , m_op_name( op_name ) , m_delivery_mode( delivery_mode ) , m_msg_type( msg_type ) , m_message( message ) , m_redirection_deep( redirection_deep ) { } // Note: since v.5.5.19.3 this is a public method. template< typename... Args > void make_trace( const char * action_name_suffix, Args &&... args ) const { details::make_trace( m_tracer, details::mbox_as_msg_source{ m_mbox }, details::composed_action_name{ m_op_name, action_name_suffix }, details::original_msg_type{ m_msg_type }, m_delivery_mode, m_message, m_redirection_deep, std::forward< Args >(args)... ); } void no_subscribers() const { make_trace( "no_subscribers" ); } void message_rejected( // NOTE: it can be nullptr. const abstract_message_sink_t * subscriber, const delivery_possibility_t status ) const { switch( status ) { case delivery_possibility_t::must_be_delivered: // NOTE: we don't expected this value here! break; case delivery_possibility_t::no_subscription: no_subscribers(); break; case delivery_possibility_t::disabled_by_delivery_filter: make_trace( "message_rejected", subscriber ); break; case delivery_possibility_t::hidden_by_envelope: make_trace( "hidden_by_envelope" ); break; } } const so_5::message_limit::impl::action_msg_tracer_t * overlimit_tracer() const { return this; } protected : void push_to_queue( const abstract_message_sink_t * sink, const agent_t * sink_owner ) const noexcept override { make_trace( "push_to_queue", sink, sink_owner ); } void reaction_abort_app( const agent_t * subscriber ) const noexcept override { make_trace( "overlimit.abort", subscriber ); } void reaction_drop_message( const agent_t * subscriber ) const noexcept override { make_trace( "overlimit.drop", subscriber ); } void reaction_redirect_message( const agent_t * subscriber, const mbox_t & target ) const noexcept override { make_trace( "overlimit.redirect", subscriber, details::text_separator{ "==>" }, details::mbox_as_msg_destination{ *target } ); } void reaction_transform( const agent_t * subscriber, const mbox_t & target, const std::type_index & msg_type, const message_ref_t & transformed ) const noexcept override { make_trace( "overlimit.transform", subscriber, details::text_separator{ "==>" }, details::mbox_as_msg_destination{ *target }, details::type_of_transformed_msg{ msg_type }, transformed ); } }; #if defined(__clang__) #pragma clang diagnostic pop #endif }; /*! * \brief Helper for tracing the result of event handler search. * * \since v.5.5.9 */ inline void trace_event_handler_search_result( const execution_demand_t & demand, const char * context_marker, const event_handler_data_t * search_result ) { details::make_trace( internal_env_iface_t{ demand.m_receiver->so_environment() }.msg_tracing_stuff(), demand.m_receiver, details::composed_action_name{ context_marker, "find_handler" }, details::mbox_identification{ demand.m_mbox_id }, details::original_msg_type{ demand.m_msg_type }, demand.m_message_ref, &(demand.m_receiver->so_current_state()), search_result ); } /*! * \brief Helper for tracing the result of search of deadletter handler. * * \since v.5.5.21 */ inline void trace_deadletter_handler_search_result( const execution_demand_t & demand, const char * context_marker, const event_handler_data_t * search_result ) { details::make_trace( internal_env_iface_t{ demand.m_receiver->so_environment() }.msg_tracing_stuff(), demand.m_receiver, details::composed_action_name{ context_marker, "deadletter_handler" }, details::mbox_identification{ demand.m_mbox_id }, details::original_msg_type{ demand.m_msg_type }, demand.m_message_ref, &(demand.m_receiver->so_current_state()), search_result ); } /*! * \brief Helper for tracing the fact of leaving a state. * * \note This helper checks status of msg tracing by itself. It means that * it is safe to call this function if msg tracing is disabled. * * \since v.5.5.15 */ inline void safe_trace_state_leaving( const agent_t & state_owner, const state_t & state ) { internal_env_iface_t env{ state_owner.so_environment() }; if( env.is_msg_tracing_enabled() ) details::make_trace( env.msg_tracing_stuff(), &state_owner, details::composed_action_name{ "state", "leaving" }, &state ); } /*! * \brief Helper for tracing the fact of entering into a state. * * \note This helper checks status of msg tracing by itself. It means that * it is safe to call this function if msg tracing is disabled. * * \since v.5.5.15 */ inline void safe_trace_state_entering( const agent_t & state_owner, const state_t & state ) { internal_env_iface_t env{ state_owner.so_environment() }; if( env.is_msg_tracing_enabled() ) details::make_trace( env.msg_tracing_stuff(), &state_owner, details::composed_action_name{ "state", "entering" }, &state ); } // // mchain_tracing_disabled_base // /*! * \brief Base class for a mchain for the case when message delivery * tracing is disabled. * * \since v.5.5.13 */ struct mchain_tracing_disabled_base { void trace_extracted_demand( const abstract_message_chain_t &, const mchain_props::demand_t & ) {} void trace_demand_drop_on_close( const abstract_message_chain_t &, const mchain_props::demand_t & ) {} class deliver_op_tracer { public : deliver_op_tracer( const mchain_tracing_disabled_base &, const abstract_message_chain_t &, const std::type_index &, const message_ref_t & ) {} template< typename Queue > void stored( const Queue & /*queue*/ ) {} void overflow_drop_newest() {} void overflow_remove_oldest( const so_5::mchain_props::demand_t & ) {} void overflow_throw_exception() {} void overflow_abort_app() {} }; }; // // mchain_tracing_enabled_base // /*! * \brief Base class for a mchain for the case when message delivery * tracing is enabled. * * \since v.5.5.13 */ class mchain_tracing_enabled_base { private : so_5::msg_tracing::holder_t & m_tracer; static const char * message_kind_to_string( const message_ref_t & what ) { const char * result = ""; switch( message_kind(what) ) { case message_kind_t::signal : result = "signal"; break; case message_kind_t::classical_message : result = "classical_message"; break; case message_kind_t::user_type_message : result = "user_type_message"; break; case message_kind_t::enveloped_msg : result = "enveloped_msg"; break; } return result; } public : mchain_tracing_enabled_base( outliving_reference_t< so_5::msg_tracing::holder_t > tracer ) : m_tracer( tracer.get() ) {} so_5::msg_tracing::holder_t & tracer() const { return m_tracer; } void trace_extracted_demand( const abstract_message_chain_t & chain, const mchain_props::demand_t & d ) { details::make_trace( m_tracer, chain, details::composed_action_name{ message_kind_to_string( d.m_message_ref ), "extracted" }, details::original_msg_type{ d.m_msg_type }, d.m_message_ref ); } void trace_demand_drop_on_close( const abstract_message_chain_t & chain, const mchain_props::demand_t & d ) { details::make_trace( m_tracer, chain, details::composed_action_name{ message_kind_to_string( d.m_message_ref ), "dropped_on_close" }, details::original_msg_type{ d.m_msg_type }, d.m_message_ref ); } class deliver_op_tracer { private : so_5::msg_tracing::holder_t & m_tracer; const abstract_message_chain_t & m_chain; const char * m_op_name; const std::type_index & m_msg_type; const message_ref_t & m_message; template< typename... Args > void make_trace( const char * action_name_suffix, Args &&... args ) const { details::make_trace( m_tracer, m_chain, details::composed_action_name{ m_op_name, action_name_suffix }, details::original_msg_type{ m_msg_type }, m_message, std::forward< Args >(args)... ); } public : deliver_op_tracer( const mchain_tracing_enabled_base & tracing_base, const abstract_message_chain_t & chain, const std::type_index & msg_type, const message_ref_t & message ) : m_tracer( tracing_base.tracer() ) , m_chain( chain ) , m_op_name( message_kind_to_string( message ) ) , m_msg_type( msg_type ) , m_message( message ) {} template< typename Queue > void stored( const Queue & queue ) { make_trace( "stored", details::chain_size{ queue.size() } ); } void overflow_drop_newest() { make_trace( "overflow.drop_newest" ); } void overflow_remove_oldest( const so_5::mchain_props::demand_t & d ) { make_trace( "overflow.remove_oldest", details::type_of_removed_msg{ d.m_msg_type }, d.m_message_ref ); } void overflow_throw_exception() { make_trace( "overflow.throw_exception" ); } void overflow_abort_app() { make_trace( "overflow.abort_app" ); } }; }; } /* namespace msg_tracing_helpers */ } /* namespace impl */ } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/impl/mt_env_infrastructure.cpp000066400000000000000000000267451523065210100236060ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Default implementation of multithreaded environment infrastructure. * * \since v.5.5.19 */ #include #include #include #include #include #include namespace so_5 { namespace env_infrastructures { namespace default_mt { namespace impl { // // coop_repo_t // coop_repo_t::coop_repo_t( outliving_reference_t< environment_t > env, coop_listener_unique_ptr_t coop_listener ) : coop_repository_basis_t( env, std::move(coop_listener) ) , m_final_dereg_thread_shutdown_flag{ false } { } void coop_repo_t::start() { // A separate thread for doing the final dereg must be started. m_final_dereg_thread = std::thread{ [this] { final_dereg_thread_body(); } }; } void coop_repo_t::finish() { // Deregistration of all cooperations should be initiated. deregister_all_coop(); // Deregistration of all cooperations should be finished. wait_all_coop_to_deregister(); // Notify a dedicated thread and wait while it will be stopped. { std::lock_guard< std::mutex > lock{ m_final_dereg_chain_lock }; m_final_dereg_thread_shutdown_flag = true; m_final_dereg_chain_cond.notify_one(); } m_final_dereg_thread.join(); } void coop_repo_t::ready_to_deregister_notify( coop_shptr_t coop ) { std::lock_guard< std::mutex > lck{ m_final_dereg_chain_lock }; // Update the final_dereg_chain. m_final_dereg_chain.append( std::move(coop) ); if( 1u == m_final_dereg_chain.size() ) { // Final deregistration thread may wait, have to wake it up. m_final_dereg_chain_cond.notify_one(); } } bool coop_repo_t::final_deregister_coop( coop_shptr_t coop ) noexcept { const auto result = coop_repository_basis_t::final_deregister_coop( std::move(coop) ); if( result.m_total_deregistration_completed ) m_deregistration_finished_cond.notify_one(); return result.m_has_live_coop; } void coop_repo_t::start_deregistration() { std::lock_guard< std::mutex > lock{ m_lock }; const auto result = try_switch_to_shutdown(); if( try_switch_to_shutdown_result_t::switched == result ) { m_deregistration_started_cond.notify_one(); } } void coop_repo_t::wait_for_start_deregistration() { std::unique_lock< std::mutex > lck{ m_lock }; m_deregistration_started_cond.wait( lck, [this] { return status_t::normal != m_status; } ); } void coop_repo_t::wait_all_coop_to_deregister() { std::unique_lock< std::mutex > lck{ m_lock }; // Must wait for a signal is there are cooperations in // the deregistration process. m_deregistration_finished_cond.wait( lck, [this] { return 0u == m_total_coops; } ); } environment_infrastructure_t::coop_repository_stats_t coop_repo_t::query_stats() { const auto final_dereg_coops = [this]() { std::lock_guard< std::mutex > lck{ m_final_dereg_chain_lock }; return m_final_dereg_chain.size(); }(); const auto basis_stats = coop_repository_basis_t::query_stats(); return { basis_stats.m_total_coop_count, basis_stats.m_total_agent_count, final_dereg_coops }; } void coop_repo_t::final_dereg_thread_body() { std::unique_lock< std::mutex > lck{ m_final_dereg_chain_lock }; for( bool should_continue = true; should_continue; ) { bool should_wait = true; // If there are some waiting coops they have to be processed even // if m_final_dereg_thread_shutdown_flag is set. if( !m_final_dereg_chain.empty() ) { // There are some coops to be deregistered. process_current_final_dereg_chain( lck ); // Because the processing takes some time there is no need // to sleep before new check for m_final_dereg_chain. should_wait = false; } if( m_final_dereg_thread_shutdown_flag ) { // It's time to finish the work. should_continue = false; } else if( should_wait ) { // No coops to deregister. Have to wait. m_final_dereg_chain_cond.wait( lck ); } } } void coop_repo_t::process_current_final_dereg_chain( std::unique_lock< std::mutex > & lck ) noexcept { // // NOTE: don't expect exceptions here. // // There are some coops to be deregistered. // Have to extract the current value of final dereg chain from // the coop_repo instance. coop_shptr_t head = m_final_dereg_chain.giveout_current_chain(); // All following actions has to be performed on unlocked mutex. lck.unlock(); // Do final_deregister_coop for every item in the chain // one by one. so_5::impl::process_final_dereg_chain( std::move(head) ); // Have to reacquire the lock back. lck.lock(); } [[nodiscard]] coop_repo_t::try_switch_to_shutdown_result_t coop_repo_t::try_switch_to_shutdown() noexcept { if( status_t::normal == m_status ) { m_status = status_t::pending_shutdown; return try_switch_to_shutdown_result_t::switched; } else return try_switch_to_shutdown_result_t::already_in_shutdown_state; } // // mt_env_infrastructure_t // mt_env_infrastructure_t::mt_env_infrastructure_t( environment_t & env, environment_params_t::default_disp_params_t default_disp_params, timer_thread_unique_ptr_t timer_thread, coop_listener_unique_ptr_t coop_listener, mbox_t stats_distribution_mbox ) : m_env( env ) , m_default_dispatcher_params{ std::move(default_disp_params) } , m_timer_thread( std::move(timer_thread) ) , m_coop_repo( outliving_mutable(env), std::move(coop_listener) ) , m_stats_controller( std::move(stats_distribution_mbox) ) { } void mt_env_infrastructure_t::launch( env_init_t init_fn ) { run_default_dispatcher_and_go_further( std::move(init_fn) ); } void mt_env_infrastructure_t::stop() noexcept { // Sends shutdown signal for all agents. m_coop_repo.start_deregistration(); } [[nodiscard]] coop_unique_holder_t mt_env_infrastructure_t::make_coop( coop_handle_t parent, disp_binder_shptr_t default_binder ) { return m_coop_repo.make_coop( std::move(parent), std::move(default_binder) ); } coop_handle_t mt_env_infrastructure_t::register_coop( coop_unique_holder_t coop ) { return m_coop_repo.register_coop( std::move(coop) ); } void mt_env_infrastructure_t::ready_to_deregister_notify( coop_shptr_t coop ) noexcept { m_coop_repo.ready_to_deregister_notify( std::move(coop) ); } bool mt_env_infrastructure_t::final_deregister_coop( coop_shptr_t coop ) noexcept { return m_coop_repo.final_deregister_coop( std::move(coop) ); } so_5::timer_id_t mt_env_infrastructure_t::schedule_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) { return m_timer_thread->schedule( type_wrapper, mbox, msg, pause, period ); } void mt_env_infrastructure_t::single_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause ) { m_timer_thread->schedule_anonymous( type_wrapper, mbox, msg, pause, std::chrono::milliseconds::zero() ); } ::so_5::stats::controller_t & mt_env_infrastructure_t::stats_controller() noexcept { return m_stats_controller; } ::so_5::stats::repository_t & mt_env_infrastructure_t::stats_repository() noexcept { return m_stats_controller; } environment_infrastructure_t::coop_repository_stats_t mt_env_infrastructure_t::query_coop_repository_stats() { return m_coop_repo.query_stats(); } timer_thread_stats_t mt_env_infrastructure_t::query_timer_thread_stats() { return m_timer_thread->query_stats(); } namespace { //! Helper class to be used with std::visit. struct binder_getter_t { [[nodiscard]] disp_binder_shptr_t operator()( so_5::disp::one_thread::dispatcher_handle_t & handle ) const { return handle.binder(); } [[nodiscard]] disp_binder_shptr_t operator()( so_5::disp::nef_one_thread::dispatcher_handle_t & handle ) const { return handle.binder(); } }; } /* namespace anonymous */ disp_binder_shptr_t mt_env_infrastructure_t::make_default_disp_binder() { return std::visit( binder_getter_t{}, m_default_dispatcher ); } namespace { //! Helper class to be used with std::visit. struct dispatcher_maker_t { environment_t & m_env; explicit dispatcher_maker_t( environment_t & env ) : m_env{ env } {} [[nodiscard]] mt_env_infrastructure_t::default_dispatcher_holder_t operator()( const so_5::disp::one_thread::disp_params_t & params ) const { return { so_5::disp::one_thread::make_dispatcher( m_env, std::string{ "DEFAULT" }, params ) }; } [[nodiscard]] mt_env_infrastructure_t::default_dispatcher_holder_t operator()( const so_5::disp::nef_one_thread::disp_params_t & params ) const { return { so_5::disp::nef_one_thread::make_dispatcher( m_env, std::string{ "DEFAULT" }, params ) }; } }; //! Helper class to be used with std::visit. struct handle_reseter_t { void operator()( so_5::disp::one_thread::dispatcher_handle_t & handle ) const { handle.reset(); } void operator()( so_5::disp::nef_one_thread::dispatcher_handle_t & handle ) const { handle.reset(); } }; } /* namespace anonymous */ void mt_env_infrastructure_t::run_default_dispatcher_and_go_further( env_init_t init_fn ) { ::so_5::impl::run_stage( "run_default_dispatcher", [this] { // Default dispatcher should be created. m_default_dispatcher = std::visit( dispatcher_maker_t{ m_env }, m_default_dispatcher_params ); }, [this] { // Default dispatcher is no more needed. std::visit( handle_reseter_t{}, m_default_dispatcher ); }, [this, init_fn] { run_timer_thread_and_go_further( std::move(init_fn) ); } ); } void mt_env_infrastructure_t::run_timer_thread_and_go_further( env_init_t init_fn ) { ::so_5::impl::run_stage( "run_timer", [this] { m_timer_thread->start(); }, [this] { m_timer_thread->finish(); }, [this, init_fn] { run_agent_core_and_go_further( std::move(init_fn) ); } ); } void mt_env_infrastructure_t::run_agent_core_and_go_further( env_init_t init_fn ) { ::so_5::impl::run_stage( "run_agent_core", [this] { m_coop_repo.start(); }, [this] { m_coop_repo.finish(); }, [this, init_fn] { run_user_supplied_init_and_wait_for_stop( std::move(init_fn) ); } ); } void mt_env_infrastructure_t::run_user_supplied_init_and_wait_for_stop( env_init_t init_fn ) { so_5::details::do_with_rollback_on_exception( [this, &init_fn] { so_5::impl::wrap_init_fn_call( std::move(init_fn) ); m_coop_repo.wait_for_start_deregistration(); }, [this] { stop(); m_coop_repo.wait_for_start_deregistration(); } ); } } /* namespace impl */ // // factory // SO_5_FUNC environment_infrastructure_factory_t factory() { return []( environment_t & env, environment_params_t & params, mbox_t stats_distribution_mbox ) { // Timer thread is necessary for that environment. auto timer = so_5::internal_timer_helpers::create_appropriate_timer_thread( params.so5_error_logger(), params.so5_giveout_timer_thread_factory() ); // Now the environment object can be created. auto obj = new impl::mt_env_infrastructure_t( env, params.default_disp_params(), std::move(timer), params.so5_giveout_coop_listener(), std::move(stats_distribution_mbox) ); return environment_infrastructure_unique_ptr_t( obj, environment_infrastructure_t::default_deleter() ); }; } } /* namespace default_mt */ } /* namespace env_infrastructures */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/mt_env_infrastructure.hpp000066400000000000000000000221461523065210100236020ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Default implementation of multithreaded environment infrastructure. * * \since v.5.5.19 */ #if !defined( SO_5_IMPL_MT_ENV_INFRASTRUCTURE_HPP ) #define SO_5_IMPL_MT_ENV_INFRASTRUCTURE_HPP #include #include #include #include #include #include #include #include #include namespace so_5 { namespace env_infrastructures { namespace default_mt { namespace impl { // // coop_repo_t // /*! * \brief Implementation of coop_repository for * multithreaded environment infrastructure. * * \since v.5.5.19 */ class coop_repo_t final : protected ::so_5::impl::coop_repository_basis_t { public : //! Initializing constructor. coop_repo_t( //! SObjectizer Environment. outliving_reference_t< environment_t > env, //! Cooperation action listener. coop_listener_unique_ptr_t coop_listener ); //! Do initialization. void start(); //! Finish work. /*! * Initiates deregistration of all agents. Waits for complete * deregistration for all of them. Waits for termination of * cooperation deregistration thread. */ void finish(); using coop_repository_basis_t::make_coop; using coop_repository_basis_t::register_coop; //! Notification about readiness of the cooperation deregistration. void ready_to_deregister_notify( coop_shptr_t coop ); //! Do final actions of the cooperation deregistration. /*! * \retval true there are some live cooperations. * \retval false there is no more live cooperations. */ bool final_deregister_coop( //! Cooperation to be deregistered. coop_shptr_t coop ) noexcept; //! Initiate start of the cooperation deregistration. void start_deregistration(); //! Wait for a signal about start of the cooperation deregistration. void wait_for_start_deregistration(); //! Wait for end of all cooperations deregistration. void wait_all_coop_to_deregister(); /*! * \brief Get the current statistic for run-time monitoring. * * \since v.5.5.4 */ environment_infrastructure_t::coop_repository_stats_t query_stats(); private : //! Condition variable for the deregistration start indication. std::condition_variable m_deregistration_started_cond; //! Condition variable for the deregistration finish indication. std::condition_variable m_deregistration_finished_cond; /*! * \name Stuff for final coop deregistration. * \{ */ /*! * \brief Lock object for thread-safety of the chain of coops * ready for the final deregistration. * * \since v.5.8.0 */ std::mutex m_final_dereg_chain_lock; /*! * \brief Notification object to inform that the chain of * coops for the final deregistration isn't empty anymore. * * \attention * It has to be used only when m_final_dereg_chain_lock is acquired. * * \since v.5.8.0 */ std::condition_variable m_final_dereg_chain_cond; /*! * \brief The chain of coops for the final deregistration. * * \since v.5.8.0 */ so_5::impl::final_dereg_chain_holder_t m_final_dereg_chain; /*! * \brief The flag for shutting down the final deregistration thread. * * Value `true` means that the final deregistration thread has to * be finished. * * \attention * This value should be set/checked only when m_final_dereg_chain_lock * is acquired. * * \since v.5.8.0 */ bool m_final_dereg_thread_shutdown_flag; /*! * \brief A separate thread for doing the final deregistration. * * \note Actual thread is started inside start() method. * * \since v.5.5.13 */ std::thread m_final_dereg_thread; /*! * \} */ /*! * \brief Method that implements the body of final deregistration thread. * * \since v.5.8.0 */ void final_dereg_thread_body(); /*! * \brief Method that performs the final deregistration for * coops in the m_final_dereg_chain. * * \attention * It's expected that m_final_dereg_chain isn't empty. * * \since v.5.8.0 */ void process_current_final_dereg_chain( //! Lock object for acquired m_final_dereg_chain_lock. std::unique_lock< std::mutex > & lck ) noexcept; /*! * \brief Result of attempt to switch to shutdown state. * * \note * It was a part of coop_repository_basis_t until v.5.8.6. * * \since v.5.6.0 */ enum class [[nodiscard]] try_switch_to_shutdown_result_t { switched, already_in_shutdown_state }; /*! * \brief Try to switch repository to shutdown state. * * \note * This method doesn't call deregister_all_coop(). * It only changes state of repository to 'shutdown'. * This prevents from registration of new cooperations. * * \note * It was a part of coop_repository_basis_t until v.5.8.6. * * \attention * This method must be called only when * coop_repository_basis_t::m_lock is acquired by the current thread. * * \since v.5.6.0 */ try_switch_to_shutdown_result_t try_switch_to_shutdown() noexcept; }; // // mt_env_infrastructure_t // /*! * \brief Default implementation of multithreaded environment infrastructure. * * \since v.5.5.19 */ class mt_env_infrastructure_t : public environment_infrastructure_t { public : //! Type for a holder of actual default dispatcher handle. /*! * \note * This type has to be declared as public type. Otherwise it is * necessary to add several helper classes as friends to * mt_env_infrastructure_t. */ using default_dispatcher_holder_t = std::variant< so_5::disp::one_thread::dispatcher_handle_t, so_5::disp::nef_one_thread::dispatcher_handle_t >; //! Initializing constructor. mt_env_infrastructure_t( //! Environment to work in. environment_t & env, //! Parameters for the default dispatcher, environment_params_t::default_disp_params_t default_disp_params, //! Timer thread to be used by environment. timer_thread_unique_ptr_t timer_thread, //! Cooperation action listener. coop_listener_unique_ptr_t coop_listener, //! Run-time stats distribution mbox. mbox_t stats_distribution_mbox ); void launch( env_init_t init_fn ) override; void stop() noexcept override; [[nodiscard]] coop_unique_holder_t make_coop( coop_handle_t parent, disp_binder_shptr_t default_binder ) override; coop_handle_t register_coop( coop_unique_holder_t coop ) override; void ready_to_deregister_notify( coop_shptr_t coop ) noexcept override; bool final_deregister_coop( coop_shptr_t coop_name ) noexcept override; so_5::timer_id_t schedule_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) override; void single_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause ) override; ::so_5::stats::controller_t & stats_controller() noexcept override; ::so_5::stats::repository_t & stats_repository() noexcept override; coop_repository_stats_t query_coop_repository_stats() override; timer_thread_stats_t query_timer_thread_stats() override; disp_binder_shptr_t make_default_disp_binder() override; private : //! SOEnv for that this infrastructure was created. environment_t & m_env; //! Parameters for the default dispatcher. /*! * \note * There wasn't such attribute in previous versions of SObjectizer-5 * because creation and running of the default dispatcher was separated. * In v.5.6.0 the default dispatcher is created inside lauch() and * we have to store parameters for the default dispatcher somewhere. * * \note * Since v.5.8.0 it main contain parameters for one_thread-dispatcher * or nef_one_thread-dispatcher. * * \since v.5.6.0 */ const environment_params_t::default_disp_params_t m_default_dispatcher_params; //! Default dispatcher. /*! * \attention * The actual value is created only in * run_default_dispatcher_and_go_further() function. And this * value is dropped after return from that function. * It means that default dispatcher exists only while * lauch() is running. */ default_dispatcher_holder_t m_default_dispatcher; //! Timer thread to be used by the environment. timer_thread_unique_ptr_t m_timer_thread; //! Repository of registered cooperations. coop_repo_t m_coop_repo; //! Run-time stats controller to be used by the environment. ::so_5::stats::impl::std_controller_t m_stats_controller; void run_default_dispatcher_and_go_further( env_init_t init_fn ); void run_timer_thread_and_go_further( env_init_t init_fn ); void run_agent_core_and_go_further( env_init_t init_fn ); void run_user_supplied_init_and_wait_for_stop( env_init_t init_fn ); }; } /* namespace impl */ } /* namespace default_mt */ } /* namespace env_infrastructures */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/named_local_mbox.cpp000066400000000000000000000037751523065210100224370ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include namespace so_5 { namespace impl { // // named_local_mbox_t // named_local_mbox_t::named_local_mbox_t( full_named_mbox_id_t name, const mbox_t & mbox, impl::mbox_core_t & mbox_core ) : m_name( std::move(name) ), m_mbox_core( &mbox_core ), m_mbox( mbox ) { } named_local_mbox_t::~named_local_mbox_t() { m_mbox_core->destroy_mbox( m_name ); } mbox_id_t named_local_mbox_t::id() const { return m_mbox->id(); } void named_local_mbox_t::subscribe_event_handler( const std::type_index & type_wrapper, abstract_message_sink_t & subscriber ) { m_mbox->subscribe_event_handler( type_wrapper, subscriber ); } void named_local_mbox_t::unsubscribe_event_handler( const std::type_index & type_wrapper, abstract_message_sink_t & subscriber ) noexcept { return m_mbox->unsubscribe_event_handler( type_wrapper, subscriber ); } std::string named_local_mbox_t::query_name() const { if( default_global_mbox_namespace() == m_name.m_namespace ) return m_name.m_name; else return m_name.m_namespace + "::" + m_name.m_name; } mbox_type_t named_local_mbox_t::type() const { return m_mbox->type(); } void named_local_mbox_t::do_deliver_message( message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) { m_mbox->do_deliver_message( delivery_mode, msg_type, message, redirection_deep ); } void named_local_mbox_t::set_delivery_filter( const std::type_index & msg_type, const delivery_filter_t & filter, abstract_message_sink_t & subscriber ) { m_mbox->set_delivery_filter( msg_type, filter, subscriber ); } void named_local_mbox_t::drop_delivery_filter( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) noexcept { m_mbox->drop_delivery_filter( msg_type, subscriber ); } environment_t & named_local_mbox_t::environment() const noexcept { return m_mbox->environment(); } } /* namespace impl */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/named_local_mbox.hpp000066400000000000000000000037661523065210100224440ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A definition of the named local mbox. */ #if !defined( SO_5_IMPL_NAMED_LOCAL_MBOX_HPP ) #define SO_5_IMPL_NAMED_LOCAL_MBOX_HPP #include #include #include #include namespace so_5 { namespace impl { // // named_local_mbox_t // //! A class for the named local mbox. /*! * \note This class is necessary because there is a difference between * reference counting for anonymous and named local mboxes. Named * local mboxes should have only one instance inside * SObjectizer Environment. */ class named_local_mbox_t final : public abstract_message_box_t { friend class impl::mbox_core_t; named_local_mbox_t( full_named_mbox_id_t full_name, const mbox_t & mbox, impl::mbox_core_t & mbox_core ); public: ~named_local_mbox_t() override; mbox_id_t id() const override; void subscribe_event_handler( const std::type_index & type_wrapper, abstract_message_sink_t & subscriber ) override; void unsubscribe_event_handler( const std::type_index & type_wrapper, abstract_message_sink_t & subscriber ) noexcept override; std::string query_name() const override; mbox_type_t type() const override; void do_deliver_message( message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) override; void set_delivery_filter( const std::type_index & msg_type, const delivery_filter_t & filter, abstract_message_sink_t & subscriber ) override; void drop_delivery_filter( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) noexcept override; environment_t & environment() const noexcept override; private: //! Mbox name. const full_named_mbox_id_t m_name; //! An utility for this mbox. impl::mbox_core_ref_t m_mbox_core; //! Actual mbox. mbox_t m_mbox; }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/process_unhandled_exception.cpp000066400000000000000000000152471523065210100247270ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.4.0 * * \file * \brief Helpers for handling unhandled exceptions from agent's event handlers. */ #include #include #include #include namespace so_5 { namespace impl { namespace { /*! * \since * v.5.4.0 * * \brief Switch agent to special state and deregister its cooperation. * * Calls abort() if an exception is raised during work. */ void switch_agent_to_special_state_and_deregister_coop( //! Agent who is the producer of the exception. so_5::agent_t & a_exception_producer ) noexcept { // Declared here because it'll be used in catch section. coop_handle_t coop; try { // agent_t::so_coop() isn't noexcept method, so it has to be // called inside try section. coop = a_exception_producer.so_coop(); a_exception_producer.so_switch_to_awaiting_deregistration_state(); a_exception_producer.so_environment().deregister_coop( coop, so_5::dereg_reason::unhandled_exception ); } catch( const std::exception & x ) { // The work can't continued, but information about the problem has to // be logged. so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( a_exception_producer.so_environment(), log_stream ) { log_stream << "An exception '" << x.what() << "' during deregistring cooperation " << coop << " on unhandled exception" "processing. Application will be aborted."; } } ); } } /*! * \since * v.5.4.0 * * \brief Switch agent to special state and initiate stopping * of SObjectizer Environment. * * Calls abort() if an exception is raised during work. */ void switch_agent_to_special_state_and_shutdown_sobjectizer( //! Agent who is the producer of the exception. agent_t & a_exception_producer ) noexcept { try { // NOTE: so_switch_to_awaiting_deregistration_state() isn't noexcept, // so call it inside try section to catch and log a possible // exception. a_exception_producer.so_switch_to_awaiting_deregistration_state(); a_exception_producer.so_environment().stop(); } catch( const std::exception & x ) { // The work can't continued, but information about the problem has to // be logged. so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( a_exception_producer.so_environment(), log_stream ) { log_stream << "An exception '" << x.what() << "' during shutting down SObjectizer on unhandled " "exception processing. Application will be aborted."; } } ); } } /*! * \since * v.5.4.0 * * \brief Log unhandled exception from cooperation. * * \note * This function is noexcept since v.5.6.0. */ void log_unhandled_exception( //! Raised and caught exception. const std::exception & ex_to_log, //! Agent who is the producer of the exception. agent_t & a_exception_producer ) noexcept { a_exception_producer.so_environment().call_exception_logger( ex_to_log, a_exception_producer.so_coop() ); } } /* namespace anonymous */ // // process_unhandled_exception // void process_unhandled_exception( current_thread_id_t working_thread_id, const std::exception & ex, agent_t & a_exception_producer ) noexcept { log_unhandled_exception( ex, a_exception_producer ); auto reaction = a_exception_producer.so_exception_reaction(); if( working_thread_id == null_current_thread_id() && ignore_exception != reaction && abort_on_exception != reaction ) { so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( a_exception_producer.so_environment(), log_stream ) { log_stream << "Illegal exception_reaction code " "for the multithreadded agent: " << static_cast< int >(reaction) << ". " "The only allowed exception_reaction for " "such kind of agents are ignore_exception or " "abort_on_exception. " "Application will be aborted. " "Unhandled exception '" << ex.what() << "' from cooperation " << a_exception_producer.so_coop(); } } ); } if( abort_on_exception == reaction ) { so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( a_exception_producer.so_environment(), log_stream ) { log_stream << "Application will be aborted due to unhandled " "exception '" << ex.what() << "' from cooperation " << a_exception_producer.so_coop(); } } ); } else if( shutdown_sobjectizer_on_exception == reaction ) { // Since v.5.6.2 all logging-related exceptions are suppressed here. so_5::details::suppress_exceptions( [&] { SO_5_LOG_ERROR( a_exception_producer.so_environment(), log_stream ) { log_stream << "SObjectizer will be shutted down due to " "unhandled exception '" << ex.what() << "' from cooperation " << a_exception_producer.so_coop(); } } ); switch_agent_to_special_state_and_shutdown_sobjectizer( a_exception_producer ); } else if( deregister_coop_on_exception == reaction ) { // Since v.5.6.2 all logging-related exceptions are suppressed here. so_5::details::suppress_exceptions( [&] { SO_5_LOG_ERROR( a_exception_producer.so_environment(), log_stream ) { log_stream << "Cooperation " << a_exception_producer.so_coop() << " will be deregistered due to unhandled exception '" << ex.what() << "'"; } } ); switch_agent_to_special_state_and_deregister_coop( a_exception_producer ); } else if( ignore_exception == reaction ) { // Since v.5.6.2 all logging-related exceptions are suppressed here. so_5::details::suppress_exceptions( [&] { SO_5_LOG_ERROR( a_exception_producer.so_environment(), log_stream ) { log_stream << "Ignore unhandled exception '" << ex.what() << "' from cooperation " << a_exception_producer.so_coop(); } } ); } else { so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( a_exception_producer.so_environment(), log_stream ) { log_stream << "Unknown exception_reaction code: " << static_cast< int >(reaction) << ". Application will be aborted. Unhandled exception '" << ex.what() << "' from cooperation " << a_exception_producer.so_coop(); } } ); } } void process_unhandled_unknown_exception( current_thread_id_t working_thread_id, agent_t & a_exception_producer ) noexcept { // Just call process_unhandled_exception with dummy exception object. exception_t dummy{ "an exception of unknown type is caught", rc_unknown_exception_type }; process_unhandled_exception( working_thread_id, dummy, a_exception_producer ); } } /* namespace impl */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/process_unhandled_exception.hpp000066400000000000000000000031301523065210100247200ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.4.0 * * \file * \brief Helpers for handling unhandled exceptions from agent's event handlers. */ #if !defined( SO_5_IMPL_PROCESS_UNHANDLED_EXCEPTION_HPP ) #define SO_5_IMPL_PROCESS_UNHANDLED_EXCEPTION_HPP #include namespace so_5 { namespace impl { /*! * \since * v.5.4.0 * * \brief Processor of unhandled exception from agent's event handler. * * \note * This function is noexcept since v.5.6.2. */ void process_unhandled_exception( //! ID of working thread for the problematic agent. //! NULL thread id means that agent is multithreadded. //! For such cases only subset of exception_reaction is //! allowed (ignore_exception, abort_on_exception). current_thread_id_t working_thread_id, //! Raised and caught exception. const std::exception & ex, //! Agent who is the producer of the exception. agent_t & a_exception_producer ) noexcept; /*! * \since * v.5.5.24.3 * * \brief Processor of unhandled exception of unknown type from agent's event * handler. * * \note * This function will be called from catch(...) and because of that there * is no actual exception object. */ void process_unhandled_unknown_exception( //! ID of working thread for the problematic agent. //! NULL thread id means that agent is multithreadded. //! For such cases only subset of exception_reaction is //! allowed (ignore_exception, abort_on_exception). current_thread_id_t working_thread_id, //! Agent who is the producer of the exception. agent_t & a_exception_producer ) noexcept; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/run_stage.hpp000066400000000000000000000027121523065210100211360ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helper template function for doing initialization phase with * rollback on failure. * * \since * v.5.5.19 */ #if !defined( SO_5_IMPL_RUN_STAGE_HPP ) #define SO_5_IMPL_RUN_STAGE_HPP #include namespace so_5 { namespace impl { /*! * \brief Helper template function for doing initialization phase with * rollback on failure. * * \since * v.5.5.19 */ template< typename Init_Fn, typename Deinit_Fn, typename Next_Stage > void run_stage( const std::string & stage_name, Init_Fn && init_fn, Deinit_Fn && deinit_fn, Next_Stage && next_stage ) { try { init_fn(); } catch( const std::exception & x ) { SO_5_THROW_EXCEPTION( rc_unexpected_error, stage_name + ": initialization failed, exception is: '" + x.what() + "'" ); } try { next_stage(); } catch( const std::exception & x ) { try { deinit_fn(); } catch( const std::exception & nested ) { SO_5_THROW_EXCEPTION( rc_unexpected_error, stage_name + ": deinitialization failed during " "exception handling. Original exception is: '" + x.what() + "', deinitialization exception is: '" + nested.what() + "'" ); } throw; } try { deinit_fn(); } catch( const std::exception & x ) { SO_5_THROW_EXCEPTION( rc_unexpected_error, stage_name + ": deinitialization failed, exception is: '" + x.what() + "'" ); } } } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/simple_mtsafe_st_env_infrastructure.cpp000066400000000000000000000532101523065210100265070ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A very simple multithreaded-safe single thread * environment infrastructure. * * \since v.5.5.19 */ #include #include #include #include #include #include #include #include #include namespace so_5 { namespace env_infrastructures { namespace simple_mtsafe { namespace impl { namespace helpers { template< typename Action > auto unlock_do_and_lock_again( std::unique_lock< std::mutex > & acquired_lock, Action && action ) -> decltype(action()) { acquired_lock.unlock(); auto relock_again = so_5::details::at_scope_exit( [&acquired_lock]{ acquired_lock.lock(); } ); return action(); } } /* namespace helpers */ //! A short name for namespace with run-time stats stuff. namespace stats = ::so_5::stats; //! A short name for namespace with reusable stuff. namespace reusable = ::so_5::env_infrastructures::st_reusable_stuff; /*! * \brief Status of the main thread on which environment is working. * * Main thread can handle some events or can sleep and wait for new events. * * \since v.5.5.19 */ enum class main_thread_status_t { working, waiting }; /*! * \brief A bunch of sync objects which need to be shared between * various parts of env_infrastructure. * * \since v.5.5.19 */ struct main_thread_sync_objects_t { //! Main lock for environment infrastructure. std::mutex m_lock; //! A condition to sleep on when no activities to handle. std::condition_variable m_wakeup_condition; //! The current status of the main thread. main_thread_status_t m_status{ main_thread_status_t::working }; }; /*! * \note * Mutex from sync_objects must be already acquired! */ inline void wakeup_if_waiting( main_thread_sync_objects_t & sync_objects ) { if( main_thread_status_t::waiting == sync_objects.m_status ) sync_objects.m_wakeup_condition.notify_one(); } // // shutdown_status_t // using shutdown_status_t = reusable::shutdown_status_t; // // event_queue_impl_t // /*! * \brief Implementation of event_queue interface for this type of * environment infrastructure. * * \since v.5.5.19 */ class event_queue_impl_t final : public so_5::event_queue_t { public : //! Type for representation of statistical data for this event queue. struct stats_t { //! The current size of the demands queue. std::size_t m_demands_count; }; event_queue_impl_t( main_thread_sync_objects_t & sync_objects ) : m_sync_objects( sync_objects ) {} /*! * \note * This method locks the main mutex by itself. */ void push( execution_demand_t demand ) override { std::lock_guard< std::mutex > lock( m_sync_objects.m_lock ); m_demands.push_back( std::move(demand) ); wakeup_if_waiting( m_sync_objects ); } /*! * \note * Delegates the work to the push() method. */ void push_evt_start( execution_demand_t demand ) override { this->push( std::move(demand) ); } /*! * \note * Delegates the work to the push() method. * * \attention * Terminates the whole application if the push() throws. */ void push_evt_finish( execution_demand_t demand ) noexcept override { this->push( std::move(demand) ); } /*! * \note * This method locks the main mutex by itself. */ stats_t query_stats() const { std::lock_guard< std::mutex > lock( m_sync_objects.m_lock ); return { m_demands.size() }; } //! Type for result of extraction operation. enum class pop_result_t { extracted, empty_queue }; /*! * \note * NOTE: this method must be called only when main thread's mutex * is locked. */ pop_result_t pop( execution_demand_t & receiver ) noexcept { if( !m_demands.empty() ) { receiver = std::move(m_demands.front()); m_demands.pop_front(); return pop_result_t::extracted; } return pop_result_t::empty_queue; } private : main_thread_sync_objects_t & m_sync_objects; std::deque< execution_demand_t > m_demands; }; // // coop_repo_t // /*! * \brief Implementation of coop_repository for * simple thread-safe single-threaded environment infrastructure. * * \since v.5.5.19 */ using coop_repo_t = reusable::coop_repo_t; // // disp_ds_name_parts_t // /*! * \brief A special class for generation of names for dispatcher data sources. * * \since v.5.5.19 */ struct disp_ds_name_parts_t { static constexpr const char * disp_type_part() noexcept { return "mtsafe_st_env"; } }; // // default_dispatcher_t // /*! * \brief An implementation of dispatcher to be used in * places where default dispatcher is needed. * * \tparam Activity_Tracker a type of activity tracker to be used * for run-time statistics. * * \since v.5.5.19 */ template< typename Activity_Tracker > using default_dispatcher_t = reusable::default_dispatcher_t< event_queue_impl_t, Activity_Tracker, disp_ds_name_parts_t >; // // stats_controller_t // /*! * \brief Implementation of stats_controller for that type of * single-threaded environment. * * \since v.5.5.19 */ using stats_controller_t = reusable::stats_controller_t< so_5::details::actual_lock_holder_t<> >; // // env_infrastructure_t // /*! * \brief Default implementation of multithreaded environment infrastructure. * * \tparam Activity_Tracker A type for tracking activity of main working thread. * * \since v.5.5.19 */ template< typename Activity_Tracker > class env_infrastructure_t : public environment_infrastructure_t { public : env_infrastructure_t( //! Environment to work in. environment_t & env, //! Factory for timer manager. timer_manager_factory_t timer_factory, //! Error logger necessary for timer_manager. error_logger_shptr_t error_logger, //! Cooperation action listener. coop_listener_unique_ptr_t coop_listener, //! Mbox for distribution of run-time stats. mbox_t stats_distribution_mbox ); void launch( env_init_t init_fn ) override; void stop() noexcept override; [[nodiscard]] coop_unique_holder_t make_coop( coop_handle_t parent, disp_binder_shptr_t default_binder ) override; [[nodiscard]] coop_handle_t register_coop( coop_unique_holder_t coop ) override; void ready_to_deregister_notify( coop_shptr_t coop ) noexcept override; bool final_deregister_coop( coop_shptr_t coop ) noexcept override; so_5::timer_id_t schedule_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) override; void single_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause ) override; stats::controller_t & stats_controller() noexcept override; stats::repository_t & stats_repository() noexcept override; so_5::environment_infrastructure_t::coop_repository_stats_t query_coop_repository_stats() override; timer_thread_stats_t query_timer_thread_stats() override; disp_binder_shptr_t make_default_disp_binder() override; private : environment_t & m_env; //! All sync objects to be shared between different parts. main_thread_sync_objects_t m_sync_objects; /*! * \brief The chain of coops for the final deregistration. * * \since v.5.8.0 */ so_5::impl::final_dereg_chain_holder_t m_final_dereg_chain; //! Status of shutdown procedure. shutdown_status_t m_shutdown_status{ shutdown_status_t::not_started }; //! A collector for elapsed timers. reusable::actual_elapsed_timers_collector_t m_timers_collector; //! A timer manager to be used. timer_manager_unique_ptr_t m_timer_manager; //! Queue for execution_demands which must be handled on the main thread. event_queue_impl_t m_event_queue; //! Repository of registered coops. coop_repo_t m_coop_repo; //! Actual activity tracker for main working thread. Activity_Tracker m_activity_tracker; //! Dispatcher to be used as default dispatcher. /*! * \note * Has an actual value only inside launch() method. */ std::shared_ptr< default_dispatcher_t< Activity_Tracker > > m_default_disp; //! Stats controller for this environment. stats_controller_t m_stats_controller; void run_default_dispatcher_and_go_further( env_init_t init_fn ); void run_user_supplied_init_and_do_main_loop( env_init_t init_fn ); void run_main_loop() noexcept; void process_final_deregs_if_any( std::unique_lock< std::mutex > & acquired_lock ) noexcept; void perform_shutdown_related_actions_if_needed( std::unique_lock< std::mutex > & acquired_lock ) noexcept; void handle_expired_timers_if_any( std::unique_lock< std::mutex > & acquired_lock ) noexcept; void try_handle_next_demand( std::unique_lock< std::mutex > & acquired_lock ) noexcept; }; template< typename Activity_Tracker > env_infrastructure_t< Activity_Tracker >::env_infrastructure_t( environment_t & env, timer_manager_factory_t timer_factory, error_logger_shptr_t error_logger, coop_listener_unique_ptr_t coop_listener, mbox_t stats_distribution_mbox ) : m_env( env ) , m_timer_manager( timer_factory( std::move(error_logger), outliving_mutable(m_timers_collector) ) ) , m_event_queue( m_sync_objects ) , m_coop_repo( outliving_mutable(env), std::move(coop_listener) ) , m_stats_controller( std::move(stats_distribution_mbox), stats::impl::st_env_stuff::next_turn_mbox_t::make(m_env) ) {} template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::launch( env_init_t init_fn ) { run_default_dispatcher_and_go_further( std::move(init_fn) ); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::stop() noexcept { std::lock_guard< std::mutex > lock( m_sync_objects.m_lock ); if( shutdown_status_t::not_started == m_shutdown_status ) { m_shutdown_status = shutdown_status_t::must_be_started; wakeup_if_waiting( m_sync_objects ); } } template< typename Activity_Tracker > coop_unique_holder_t env_infrastructure_t< Activity_Tracker >::make_coop( coop_handle_t parent, disp_binder_shptr_t default_binder ) { return m_coop_repo.make_coop( std::move(parent), std::move(default_binder) ); } template< typename Activity_Tracker > coop_handle_t env_infrastructure_t< Activity_Tracker >::register_coop( coop_unique_holder_t coop ) { return m_coop_repo.register_coop( std::move(coop) ); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::ready_to_deregister_notify( coop_shptr_t coop ) noexcept { std::lock_guard< std::mutex > lock( m_sync_objects.m_lock ); m_final_dereg_chain.append( std::move(coop) ); wakeup_if_waiting( m_sync_objects ); } template< typename Activity_Tracker > bool env_infrastructure_t< Activity_Tracker >::final_deregister_coop( coop_shptr_t coop ) noexcept { return m_coop_repo.final_deregister_coop( std::move(coop) ) .m_has_live_coop; } template< typename Activity_Tracker > so_5::timer_id_t env_infrastructure_t< Activity_Tracker >::schedule_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) { std::lock_guard< std::mutex > lock( m_sync_objects.m_lock ); auto timer = m_timer_manager->schedule( type_wrapper, mbox, msg, pause, period ); wakeup_if_waiting( m_sync_objects ); return timer; } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::single_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause ) { std::lock_guard< std::mutex > lock( m_sync_objects.m_lock ); m_timer_manager->schedule_anonymous( type_wrapper, mbox, msg, pause, std::chrono::milliseconds::zero() ); wakeup_if_waiting( m_sync_objects ); } template< typename Activity_Tracker > stats::controller_t & env_infrastructure_t< Activity_Tracker >::stats_controller() noexcept { return m_stats_controller; } template< typename Activity_Tracker > stats::repository_t & env_infrastructure_t< Activity_Tracker >::stats_repository() noexcept { return m_stats_controller; } template< typename Activity_Tracker > so_5::environment_infrastructure_t::coop_repository_stats_t env_infrastructure_t< Activity_Tracker >::query_coop_repository_stats() { std::lock_guard< std::mutex > lock( m_sync_objects.m_lock ); const auto stats = m_coop_repo.query_stats(); return environment_infrastructure_t::coop_repository_stats_t{ stats.m_total_coop_count, stats.m_total_agent_count, m_final_dereg_chain.size() }; } template< typename Activity_Tracker > timer_thread_stats_t env_infrastructure_t< Activity_Tracker >::query_timer_thread_stats() { std::lock_guard< std::mutex > lock( m_sync_objects.m_lock ); return m_timer_manager->query_stats(); } template< typename Activity_Tracker > disp_binder_shptr_t env_infrastructure_t< Activity_Tracker >::make_default_disp_binder() { return { m_default_disp }; } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::run_default_dispatcher_and_go_further( env_init_t init_fn ) { ::so_5::impl::run_stage( "run_default_dispatcher", [this] { m_default_disp = std::make_shared< default_dispatcher_t< Activity_Tracker > >( outliving_mutable(m_env), outliving_mutable(m_event_queue), outliving_mutable(m_activity_tracker) ); }, [this] { m_default_disp.reset(); }, [this, init_func=std::move(init_fn)] { run_user_supplied_init_and_do_main_loop( std::move(init_func) ); } ); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::run_user_supplied_init_and_do_main_loop( env_init_t init_fn ) { /* If init_fn throws an exception we can found ourselves in a situation where there are some working coops. Those coops should be correctly deregistered. It means that we should usual main loop even in the case of an exception from init_fn. But this main loop should work only until all coops will be deregistered. To do that we will catch an exception from init and initiate shutdown even before the call to run_main_loop(). Then we call run_main_loop() and wail for its completion. Then we reraise the exception caught. Note that in this scheme run_main_loop() should be noexcept function because otherwise we will loose the initial exception from init_fn. */ optional< std::exception_ptr > exception_from_init; try { so_5::impl::wrap_init_fn_call( std::move(init_fn) ); } catch( ... ) { // We can't restore if there will be an exception. so_5::details::invoke_noexcept_code( [&] { // Store the content of the exception to reraise it later. exception_from_init = std::current_exception(); // Execution should be stopped. stop(); } ); } // We don't expect exceptions from the main loop. so_5::details::invoke_noexcept_code( [this] { run_main_loop(); } ); // If there was an exception from init_fn this exception // should be rethrown. if( exception_from_init ) std::rethrow_exception( *exception_from_init ); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::run_main_loop() noexcept { // Assume that waiting for new demands is started. // This call is necessary because if there is a demand // in event queue then m_activity_tracker.wait_stopped() will be // called without previous m_activity_tracker.wait_started(). m_activity_tracker.wait_started(); // Acquire the main lock for the first time. // It will be released and reacquired many times later. std::unique_lock< std::mutex > lock( m_sync_objects.m_lock ); for(;;) { // The first step: all pending final deregs must be processed. process_final_deregs_if_any( lock ); // There can be pending shutdown operation. It must be handled. perform_shutdown_related_actions_if_needed( lock ); if( shutdown_status_t::completed == m_shutdown_status ) break; // The next step: all timers must be converted to events. handle_expired_timers_if_any( lock ); // The last step: an attempt to process demand. // Or sleep for some time until next demand arrived. try_handle_next_demand( lock ); } } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::process_final_deregs_if_any( std::unique_lock< std::mutex > & acquired_lock ) noexcept { // This loop is necessary because it is possible that new // final dereg demand will be added during processing of // the current final dereg demand. while( !m_final_dereg_chain.empty() ) { coop_shptr_t head = m_final_dereg_chain.giveout_current_chain(); helpers::unlock_do_and_lock_again( acquired_lock, [&head] { so_5::impl::process_final_dereg_chain( std::move(head) ); } ); } } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::perform_shutdown_related_actions_if_needed( std::unique_lock< std::mutex > & acquired_lock ) noexcept { if( shutdown_status_t::must_be_started == m_shutdown_status ) { // Shutdown procedure must be started. m_shutdown_status = shutdown_status_t::in_progress; // All registered cooperations must be deregistered now. // We must unlock out main lock because there is a need to // push a final event for deregisteing agents to event queue. helpers::unlock_do_and_lock_again( acquired_lock, [this] { m_coop_repo.deregister_all_coop(); } ); } if( shutdown_status_t::in_progress == m_shutdown_status ) { // If there is no more live coops then shutdown must be completed. if( !m_coop_repo.has_live_coop() ) m_shutdown_status = shutdown_status_t::completed; } } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::handle_expired_timers_if_any( std::unique_lock< std::mutex > & acquired_lock ) noexcept { // All expired timers must be collected. m_timer_manager->process_expired_timers(); if( !m_timers_collector.empty() ) // Actual handling of elapsed timers must be done // on unlocked env_infrastructure. // It is necessary because there can be an attempt to // deliver delayed/periodic messages into event_queue of // the default_dispatcher. But it is impossible to do if // env_infrastructure's mutex is locked. helpers::unlock_do_and_lock_again( acquired_lock, [this] { m_timers_collector.process(); } ); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::try_handle_next_demand( std::unique_lock< std::mutex > & acquired_lock ) noexcept { execution_demand_t demand; const auto pop_result = m_event_queue.pop( demand ); // If there is no demands we must go to sleep for some time... if( event_queue_impl_t::pop_result_t::empty_queue == pop_result ) { // ... but we should go to sleep only if there is no // pending final deregistration actions. if( m_final_dereg_chain.empty() ) { // Tracking time for 'waiting' state must be turned on. m_activity_tracker.wait_start_if_not_started(); const auto sleep_time = m_timer_manager->timeout_before_nearest_timer( std::chrono::minutes(1) ); m_sync_objects.m_status = main_thread_status_t::waiting; m_sync_objects.m_wakeup_condition.wait_for( acquired_lock, sleep_time ); m_sync_objects.m_status = main_thread_status_t::working; } } else { // Tracking time for 'waiting' must be turned off, but // tracking time for 'working' must be tuned on and then off again. m_activity_tracker.wait_stopped(); m_activity_tracker.work_started(); auto work_tracking_stopper = so_5::details::at_scope_exit( [this]{ m_activity_tracker.work_stopped(); } ); // There is at least one demand to process. helpers::unlock_do_and_lock_again( acquired_lock, [this, &demand] { m_default_disp->handle_demand( demand ); } ); } } } /* namespace impl */ // // simple_mtsafe_st_env_infrastructure_factory // SO_5_FUNC environment_infrastructure_factory_t factory( params_t && infrastructure_params ) { using namespace impl; return [infrastructure_params]( environment_t & env, environment_params_t & env_params, mbox_t stats_distribution_mbox ) { environment_infrastructure_t * obj = nullptr; auto timer_manager_factory = infrastructure_params.timer_manager(); // Create environment infrastructure object in dependence of // work thread activity tracking flag. const auto tracking = env_params.work_thread_activity_tracking(); if( work_thread_activity_tracking_t::on == tracking ) obj = new env_infrastructure_t< reusable::real_activity_tracker_t >( env, std::move(timer_manager_factory), env_params.so5_error_logger(), env_params.so5_giveout_coop_listener(), std::move(stats_distribution_mbox) ); else obj = new env_infrastructure_t< reusable::fake_activity_tracker_t >( env, std::move(timer_manager_factory), env_params.so5_error_logger(), env_params.so5_giveout_coop_listener(), std::move(stats_distribution_mbox) ); return environment_infrastructure_unique_ptr_t( obj, environment_infrastructure_t::default_deleter() ); }; } } /* namespace simple_mtsafe */ } /* namespace env_infrastructures */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/simple_not_mtsafe_st_env_infrastructure.cpp000066400000000000000000000447051523065210100274000ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A very simple not-multithreaded-safe single thread * environment infrastructure. * * \since v.5.5.19 */ #include #include #include #include #include #include #include #include #include namespace so_5 { namespace env_infrastructures { namespace simple_not_mtsafe { namespace impl { //! A short name for namespace with run-time stats stuff. namespace stats = ::so_5::stats; //! A short name for namespace with reusable stuff. namespace reusable = ::so_5::env_infrastructures::st_reusable_stuff; // // shutdown_status_t // using shutdown_status_t = reusable::shutdown_status_t; // // event_queue_impl_t // /*! * \brief Implementation of event_queue interface for this type of * environment infrastructure. * * \since v.5.5.19 */ class event_queue_impl_t final : public so_5::event_queue_t { public : //! Type for representation of statistical data for this event queue. struct stats_t { //! The current size of the demands queue. std::size_t m_demands_count; }; void push( execution_demand_t demand ) override { m_demands.push_back( std::move(demand) ); } /*! * \note * Delegates the work to the push() method. */ void push_evt_start( execution_demand_t demand ) override { this->push( std::move(demand) ); } /*! * \note * Delegates the work to the push() method. * * \attention * Terminates the whole application if the push() throws. */ void push_evt_finish( execution_demand_t demand ) noexcept override { this->push( std::move(demand) ); } stats_t query_stats() const { return { m_demands.size() }; } //! Type for result of extraction operation. enum class pop_result_t { extracted, empty_queue }; pop_result_t pop( execution_demand_t & receiver ) noexcept { if( !m_demands.empty() ) { receiver = std::move(m_demands.front()); m_demands.pop_front(); return pop_result_t::extracted; } return pop_result_t::empty_queue; } private : std::deque< execution_demand_t > m_demands; }; // // coop_repo_t // /*! * \brief Implementation of coop_repository for * simple thread-safe single-threaded environment infrastructure. * * \since v.5.5.19 */ using coop_repo_t = reusable::coop_repo_t; // // disp_ds_name_parts_t // /*! * \brief A special class for generation of names for dispatcher data sources. * * \since v.5.5.19 */ struct disp_ds_name_parts_t { static constexpr const char * disp_type_part() noexcept { return "not_mtsafe_st_env"; } }; // // default_dispatcher_t // /*! * \brief An implementation of dispatcher to be used in * places where default dispatcher is needed. * * \tparam Activity_Tracker a type of activity tracker to be used * for run-time statistics. * * \since v.5.5.19 */ template< typename Activity_Tracker > using default_dispatcher_t = reusable::default_dispatcher_t< event_queue_impl_t, Activity_Tracker, disp_ds_name_parts_t >; // // stats_controller_t // /*! * \brief Implementation of stats_controller for that type of * single-threaded environment. * * \since v.5.5.19 */ using stats_controller_t = reusable::stats_controller_t< so_5::details::no_lock_holder_t >; // // env_infrastructure_t // /*! * \brief Default implementation of not-thread safe single-threaded environment * infrastructure. * * \attention * This class doesn't have any mutex inside. * * \tparam Activity_Tracker A type for tracking activity of main working thread. * * \since v.5.5.19 */ template< typename Activity_Tracker > class env_infrastructure_t : public environment_infrastructure_t { public : env_infrastructure_t( //! Environment to work in. environment_t & env, //! Factory for timer manager. timer_manager_factory_t timer_factory, //! Error logger necessary for timer_manager. error_logger_shptr_t error_logger, //! Cooperation action listener. coop_listener_unique_ptr_t coop_listener, //! Mbox for distribution of run-time stats. mbox_t stats_distribution_mbox ); void launch( env_init_t init_fn ) override; void stop() noexcept override; [[nodiscard]] coop_unique_holder_t make_coop( coop_handle_t parent, disp_binder_shptr_t default_binder ) override; coop_handle_t register_coop( coop_unique_holder_t coop ) override; void ready_to_deregister_notify( coop_shptr_t coop ) noexcept override; bool final_deregister_coop( coop_shptr_t coop_name ) noexcept override; so_5::timer_id_t schedule_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) override; void single_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause ) override; stats::controller_t & stats_controller() noexcept override; stats::repository_t & stats_repository() noexcept override; so_5::environment_infrastructure_t::coop_repository_stats_t query_coop_repository_stats() override; timer_thread_stats_t query_timer_thread_stats() override; disp_binder_shptr_t make_default_disp_binder() override; private : environment_t & m_env; /*! * \brief The chain of coops for the final deregistration. * * \since v.5.8.0 */ so_5::impl::final_dereg_chain_holder_t m_final_dereg_chain; //! Status of shutdown procedure. shutdown_status_t m_shutdown_status{ shutdown_status_t::not_started }; //! A collector for elapsed timers. reusable::direct_delivery_elapsed_timers_collector_t m_timers_collector; //! A timer manager to be used. timer_manager_unique_ptr_t m_timer_manager; //! Queue for execution_demands which must be handled on the main thread. event_queue_impl_t m_event_queue; //! Repository of registered coops. coop_repo_t m_coop_repo; //! Actual activity tracker for main working thread. Activity_Tracker m_activity_tracker; //! Dispatcher to be used as default dispatcher. /*! * \note * Has an actual value only inside launch() method. */ std::shared_ptr< default_dispatcher_t< Activity_Tracker > > m_default_disp; //! Stats controller for this environment. stats_controller_t m_stats_controller; void run_default_dispatcher_and_go_further( env_init_t init_fn ); void run_user_supplied_init_and_do_main_loop( env_init_t init_fn ); void run_main_loop() noexcept; void process_final_deregs_if_any() noexcept; void perform_shutdown_related_actions_if_needed() noexcept; void handle_expired_timers_if_any() noexcept; void try_handle_next_demand() noexcept; }; template< typename Activity_Tracker > env_infrastructure_t< Activity_Tracker >::env_infrastructure_t( environment_t & env, timer_manager_factory_t timer_factory, error_logger_shptr_t error_logger, coop_listener_unique_ptr_t coop_listener, mbox_t stats_distribution_mbox ) : m_env( env ) , m_timer_manager( timer_factory( std::move(error_logger), outliving_mutable(m_timers_collector) ) ) , m_coop_repo( outliving_mutable(env), std::move(coop_listener) ) , m_stats_controller( std::move(stats_distribution_mbox), stats::impl::st_env_stuff::next_turn_mbox_t::make(m_env) ) {} template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::launch( env_init_t init_fn ) { run_default_dispatcher_and_go_further( std::move(init_fn) ); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::stop() noexcept { if( shutdown_status_t::not_started == m_shutdown_status ) { m_shutdown_status = shutdown_status_t::must_be_started; } } template< typename Activity_Tracker > coop_unique_holder_t env_infrastructure_t< Activity_Tracker >::make_coop( coop_handle_t parent, disp_binder_shptr_t default_binder ) { return m_coop_repo.make_coop( std::move(parent), std::move(default_binder) ); } template< typename Activity_Tracker > coop_handle_t env_infrastructure_t< Activity_Tracker >::register_coop( coop_unique_holder_t coop ) { return m_coop_repo.register_coop( std::move(coop) ); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::ready_to_deregister_notify( coop_shptr_t coop ) noexcept { m_final_dereg_chain.append( std::move(coop) ); } template< typename Activity_Tracker > bool env_infrastructure_t< Activity_Tracker >::final_deregister_coop( coop_shptr_t coop ) noexcept { return m_coop_repo.final_deregister_coop( std::move(coop) ) .m_has_live_coop; } template< typename Activity_Tracker > so_5::timer_id_t env_infrastructure_t< Activity_Tracker >::schedule_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) { auto timer = m_timer_manager->schedule( type_wrapper, mbox, msg, pause, period ); return timer; } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::single_timer( const std::type_index & type_wrapper, const message_ref_t & msg, const mbox_t & mbox, std::chrono::steady_clock::duration pause ) { m_timer_manager->schedule_anonymous( type_wrapper, mbox, msg, pause, std::chrono::milliseconds::zero() ); } template< typename Activity_Tracker > stats::controller_t & env_infrastructure_t< Activity_Tracker >::stats_controller() noexcept { return m_stats_controller; } template< typename Activity_Tracker > stats::repository_t & env_infrastructure_t< Activity_Tracker >::stats_repository() noexcept { return m_stats_controller; } template< typename Activity_Tracker > so_5::environment_infrastructure_t::coop_repository_stats_t env_infrastructure_t< Activity_Tracker >::query_coop_repository_stats() { const auto stats = m_coop_repo.query_stats(); return environment_infrastructure_t::coop_repository_stats_t{ stats.m_total_coop_count, stats.m_total_agent_count, m_final_dereg_chain.size() }; } template< typename Activity_Tracker > timer_thread_stats_t env_infrastructure_t< Activity_Tracker >::query_timer_thread_stats() { return m_timer_manager->query_stats(); } template< typename Activity_Tracker > disp_binder_shptr_t env_infrastructure_t< Activity_Tracker >::make_default_disp_binder() { return { m_default_disp }; } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::run_default_dispatcher_and_go_further( env_init_t init_fn ) { ::so_5::impl::run_stage( "run_default_dispatcher", [this] { m_default_disp = std::make_shared< default_dispatcher_t< Activity_Tracker > >( outliving_mutable(m_env), outliving_mutable(m_event_queue), outliving_mutable(m_activity_tracker) ); }, [this] { m_default_disp.reset(); }, [this, init_func=std::move(init_fn)] { run_user_supplied_init_and_do_main_loop( std::move(init_func) ); } ); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::run_user_supplied_init_and_do_main_loop( env_init_t init_fn ) { /* If init_fn throws an exception we can found ourselves in a situation where there are some working coops. Those coops should be correctly deregistered. It means that we should usual main loop even in the case of an exception from init_fn. But this main loop should work only until all coops will be deregistered. To do that we will catch an exception from init and initiate shutdown even before the call to run_main_loop(). Then we call run_main_loop() and wail for its completion. Then we reraise the exception caught. Note that in this scheme run_main_loop() should be noexcept function because otherwise we will loose the initial exception from init_fn. */ optional< std::exception_ptr > exception_from_init; try { so_5::impl::wrap_init_fn_call( std::move(init_fn) ); } catch( ... ) { // We can't restore if there will be an exception. so_5::details::invoke_noexcept_code( [&] { // Store the content of the exception to reraise it later. exception_from_init = std::current_exception(); // Execution should be stopped. stop(); } ); } // We don't expect exceptions from the main loop. so_5::details::invoke_noexcept_code( [this] { run_main_loop(); } ); // If there was an exception from init_fn this exception // should be rethrown. if( exception_from_init ) std::rethrow_exception( *exception_from_init ); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::run_main_loop() noexcept { // Assume that waiting for new demands is started. // This call is necessary because if there is a demand // in event queue then m_activity_tracker.wait_stopped() will be // called without previous m_activity_tracker.wait_started(). m_activity_tracker.wait_started(); for(;;) { // The first step: all pending final deregs must be processed. process_final_deregs_if_any(); // There can be pending shutdown operation. It must be handled. perform_shutdown_related_actions_if_needed(); if( shutdown_status_t::completed == m_shutdown_status ) break; // The next step: all timers must be converted to events. handle_expired_timers_if_any(); // The last step: an attempt to process demand. // Or sleep for some time until next demand arrived. try_handle_next_demand(); } } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::process_final_deregs_if_any() noexcept { // This loop is necessary because it is possible that new // final dereg demand will be added during processing of // the current final dereg demand. while( !m_final_dereg_chain.empty() ) { so_5::impl::process_final_dereg_chain( m_final_dereg_chain.giveout_current_chain() ); } } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::perform_shutdown_related_actions_if_needed() noexcept { if( shutdown_status_t::must_be_started == m_shutdown_status ) { // Shutdown procedure must be started. m_shutdown_status = shutdown_status_t::in_progress; // All registered cooperations must be deregistered now. m_coop_repo.deregister_all_coop(); } if( shutdown_status_t::in_progress == m_shutdown_status ) { // If there is no more live coops then shutdown must be completed. if( !m_coop_repo.has_live_coop() ) m_shutdown_status = shutdown_status_t::completed; } } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::handle_expired_timers_if_any() noexcept { // All expired timers must be collected. // NOTE: a direct delivery happens in this environment infrastructure. m_timer_manager->process_expired_timers(); } template< typename Activity_Tracker > void env_infrastructure_t< Activity_Tracker >::try_handle_next_demand() noexcept { execution_demand_t demand; const auto pop_result = m_event_queue.pop( demand ); // If there is no demands we must go to sleep for some time... if( event_queue_impl_t::pop_result_t::empty_queue == pop_result ) { // ... but we should go to sleep only if there is no // pending final deregistration actions. if( m_final_dereg_chain.empty() ) { // We must try to sleep for next timer but only if // there is any timer. if( !m_timer_manager->empty() ) { // Tracking time for 'waiting' state must be turned on. m_activity_tracker.wait_start_if_not_started(); const auto sleep_time = m_timer_manager->timeout_before_nearest_timer( // We can use very large value here. std::chrono::hours(24) ); std::this_thread::sleep_for( sleep_time ); } else // There are no demands and there are no timers. // Environment's work must be finished. stop(); } } else { // Tracking time for 'waiting' must be turned off, but // tracking time for 'working' must be tuned on and then off again. m_activity_tracker.wait_stopped(); m_activity_tracker.work_started(); auto work_tracking_stopper = so_5::details::at_scope_exit( [this]{ m_activity_tracker.work_stopped(); } ); // There is at least one demand to process. m_default_disp->handle_demand( demand ); } } // // ensure_autoshutdown_enabled // /*! * Throws an exception if autoshutdown feature is disabled. * * \since v.5.5.19 */ void ensure_autoshutdown_enabled( const environment_params_t & env_params ) { if( env_params.autoshutdown_disabled() ) SO_5_THROW_EXCEPTION( rc_autoshutdown_must_be_enabled, "autoshutdown feature must be enabled for " "so_5::env_infrastructures::simple_not_mtsafe" ); } } /* namespace impl */ // // simple_not_mtsafe_st_env_infrastructure_factory // SO_5_FUNC environment_infrastructure_factory_t factory( params_t && infrastructure_params ) { using namespace impl; return [infrastructure_params]( environment_t & env, environment_params_t & env_params, mbox_t stats_distribution_mbox ) { ensure_autoshutdown_enabled( env_params ); environment_infrastructure_t * obj = nullptr; auto timer_manager_factory = infrastructure_params.timer_manager(); // Create environment infrastructure object in dependence of // work thread activity tracking flag. const auto tracking = env_params.work_thread_activity_tracking(); if( work_thread_activity_tracking_t::on == tracking ) obj = new env_infrastructure_t< reusable::real_activity_tracker_t >( env, std::move(timer_manager_factory), env_params.so5_error_logger(), env_params.so5_giveout_coop_listener(), std::move(stats_distribution_mbox) ); else obj = new env_infrastructure_t< reusable::fake_activity_tracker_t >( env, std::move(timer_manager_factory), env_params.so5_error_logger(), env_params.so5_giveout_coop_listener(), std::move(stats_distribution_mbox) ); return environment_infrastructure_unique_ptr_t( obj, environment_infrastructure_t::default_deleter() ); }; } } /* namespace simple_not_mtsafe */ } /* namespace env_infrastructures */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/st_env_infrastructure_reuse.hpp000066400000000000000000000410351523065210100250110ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Reusable stuff to be used in implementations of * various single thread environment infrastructure. * * \since v.5.5.19 */ #if !defined( SO_5_IMPL_ST_ENV_INFRASTRUCTURE_HPP ) #define SO_5_IMPL_ST_ENV_INFRASTRUCTURE_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace env_infrastructures { namespace st_reusable_stuff { //! A short name for namespace with run-time stats stuff. namespace stats = ::so_5::stats; /*! * \brief Status of shutdown procedure. * * \since v.5.5.19 */ enum class shutdown_status_t { //! Shutdown is not started yet. not_started, //! Shutdown must be started as soon as possible. must_be_started, //! Shutdown is initiated but not finished yet. in_progress, //! Shutdown completed and work of environment must be finished. completed }; // // fake_activity_tracker_t // /*! * \brief An implementation of work thread activity tracking stuff for * the case when activity tracking is not used. * * \since v.5.5.19 */ struct fake_activity_tracker_t final { void wait_started() {} void wait_start_if_not_started() {} void wait_stopped() {} void work_started() {} void work_stopped() {} }; // // real_activity_tracker_t // /*! * \brief An implementation of work thread activity tracking stuff for * the case when activity tracking is used. * * \attention * There is no need to use some sync object (like mutex or spin_lock) because * all actions with activity tracker will be done on the context of * single thread. * * \since v.5.5.19 */ class real_activity_tracker_t final { stats::activity_tracking_stuff::stats_collector_t< stats::activity_tracking_stuff::null_lock > m_waiting{}; stats::activity_tracking_stuff::stats_collector_t< stats::activity_tracking_stuff::null_lock > m_working{}; public : void wait_started() { m_waiting.start(); } void wait_start_if_not_started() { m_waiting.start_if_not_started(); } void wait_stopped() { m_waiting.stop(); } void work_started() { m_working.start(); } void work_stopped() { m_working.stop(); } stats::work_thread_activity_stats_t take_activity_stats() { stats::work_thread_activity_stats_t result; result.m_working_stats = m_working.take_stats(); result.m_waiting_stats = m_waiting.take_stats(); return result; } }; inline void send_thread_activity_stats( const mbox_t &, const stats::prefix_t &, const current_thread_id_t &, fake_activity_tracker_t & ) { /* Nothing to do */ } inline void send_thread_activity_stats( const so_5::mbox_t & mbox, const stats::prefix_t & prefix, const current_thread_id_t & thread_id, real_activity_tracker_t & activity_tracker ) { so_5::send< stats::messages::work_thread_activity >( mbox, prefix, stats::suffixes::work_thread_activity(), thread_id, activity_tracker.take_activity_stats() ); } // // coop_repo_t // /*! * \brief Implementation of coop_repository for * single-threaded environment infrastructure. * * \since v.5.5.19 */ class coop_repo_t final : public ::so_5::impl::coop_repository_basis_t { public : //! Initializing constructor. coop_repo_t( //! SObjectizer Environment. outliving_reference_t< environment_t > env, //! Cooperation action listener. coop_listener_unique_ptr_t coop_listener ) : coop_repository_basis_t{ env, std::move(coop_listener) } {} //! Is there any live coop? bool has_live_coop() { // A lock is necessary here because coop_repo can be used // in thread-safe environment where access to environment from // different thread is allowed. std::lock_guard< std::mutex > l{ m_lock }; return 0u != m_registrations_in_progress || 0u != m_total_coops; } }; // // default_dispatcher_basis_t // /*! * \brief A basic part of implementation of dispatcher to be used in * places where default dispatcher is needed. * * \note * This part is not dependent of activity tracking policy which can be used in * derived classes. * * \note * Implements disp_binder_t interface. * * \tparam Event_Queue_Type An actual type of event queue. * * \since v.5.5.19, v.5.6.0 */ template< typename Event_Queue_Type > class default_dispatcher_basis_t : public disp_binder_t { public : default_dispatcher_basis_t( outliving_reference_t< Event_Queue_Type > event_queue ) : m_event_queue{ std::move(event_queue) } , m_thread_id{ query_current_thread_id() } {} void handle_demand( execution_demand_t & demand ) { demand.call_handler( m_thread_id ); } Event_Queue_Type & event_queue() const noexcept { return m_event_queue.get(); } current_thread_id_t thread_id() const noexcept { return m_thread_id; } void preallocate_resources( agent_t & /*agent*/ ) override { // Nothing to do. } void undo_preallocation( agent_t & /*agent*/ ) noexcept override { // Nothing to do. } void bind( agent_t & agent ) noexcept override { agent.so_bind_to_dispatcher( this->event_queue() ); ++m_agents_bound; } void unbind( agent_t & /*agent*/ ) noexcept override { --m_agents_bound; } std::size_t agents_bound() const noexcept { return m_agents_bound.load( std::memory_order_relaxed ); } protected : //! Event queue for that dispatcher. outliving_reference_t< Event_Queue_Type > m_event_queue; //! ID of the main thread. /*! * Will be passed to event handlers which are called on the context * of the main thread. * * \note * Receives value in the constructor. */ current_thread_id_t m_thread_id; //! Counter of agents bound to that dispatcher. std::atomic< std::size_t > m_agents_bound{ 0 }; }; // // default_dispatcher_t // /*! * \brief An implementation of dispatcher to be used in * places where default dispatcher is needed. * * \note * Implements disp_binder_t interface (via inheritance from * default_dispatcher_basis_t). * * \tparam Event_Queue_Type a type of actual event queue for the dispatcher. * * \tparam Activity_Tracker a type of activity tracker to be used * for run-time statistics. * * \tparam Data_Source_Name_Parts a type with methods for generation * of names for dispatcher's data sources. Must be a type like that: * \code struct disp_ds_name_parts_t { static const char * disp_type_part() { return "mtsafe_st_env"; } }; * \endcode * * \note * This type is not marked as final because it can be used as a base * class in external projects (like so_5_extra). * * \since v.5.6.0 */ template< typename Event_Queue_Type, typename Activity_Tracker, typename Data_Source_Name_Parts > class default_dispatcher_t : public default_dispatcher_basis_t< Event_Queue_Type > { public : default_dispatcher_t( outliving_reference_t< environment_t > env, outliving_reference_t< Event_Queue_Type > event_queue, outliving_reference_t< Activity_Tracker > activity_tracker ) : default_dispatcher_basis_t< Event_Queue_Type >{ std::move(event_queue) } , m_data_source{ outliving_mutable(env.get().stats_repository()), outliving_mutable( *this ) } , m_activity_tracker{ std::move(activity_tracker) } {} Activity_Tracker & activity_tracker() noexcept { return m_activity_tracker.get(); } private : /*! * \brief Data source for run-time monitoring of whole dispatcher. * * \since v.5.5.19 */ class disp_data_source_t final : public stats::source_t { //! Dispatcher to work with. outliving_reference_t< default_dispatcher_t > m_dispatcher; //! Basic prefix for data sources. stats::prefix_t m_base_prefix; public : disp_data_source_t( outliving_reference_t< default_dispatcher_t > disp ) : m_dispatcher{ disp } { // Name of data source should be constructed. using namespace so_5::disp::reuse; m_base_prefix = make_disp_prefix( Data_Source_Name_Parts::disp_type_part(), "DEFAULT", &m_dispatcher.get() ); } void distribute( const mbox_t & mbox ) override { so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::agent_count(), m_dispatcher.get().agents_bound() ); const auto evt_queue_stats = m_dispatcher.get().event_queue().query_stats(); so_5::send< stats::messages::quantity< std::size_t > >( mbox, m_base_prefix, stats::suffixes::work_thread_queue_size(), evt_queue_stats.m_demands_count ); send_thread_activity_stats( mbox, m_base_prefix, m_dispatcher.get().thread_id(), m_dispatcher.get().activity_tracker() ); } }; //! Data source for speading run-time stats. stats::auto_registered_source_holder_t< disp_data_source_t > m_data_source; //! Activity tracker. outliving_reference_t< Activity_Tracker > m_activity_tracker; }; // // actual_elapsed_timers_collector_t // /*! * \brief Implementation of elapsed_timers_collector interface. * * \since v.5.5.19 */ class actual_elapsed_timers_collector_t final : public timer_manager_t::elapsed_timers_collector_t { //! Type of demand created from elapsed timer. struct demand_t { std::type_index m_type_index; mbox_t m_mbox; message_ref_t m_message; demand_t( std::type_index type_index, mbox_t mbox, message_ref_t message ) : m_type_index( std::move(type_index) ) , m_mbox( std::move(mbox) ) , m_message( std::move(message) ) {} }; //! Type of container for demands. using demands_container_t = std::deque< demand_t >; //! Collected demands. demands_container_t m_demands; public : virtual void accept( std::type_index type_index, mbox_t mbox, message_ref_t msg ) override { m_demands.emplace_back( std::move(type_index), std::move(mbox), std::move(msg) ); } /*! * \return true if there is no any pending demands. */ bool empty() const noexcept { return m_demands.empty(); } /*! * Convert all demands into actual message sends. */ void process() { for( auto & d : m_demands ) { so_5::impl::mbox_iface_for_timers_t{ d.m_mbox } .deliver_message_from_timer( d.m_type_index, d.m_message ); } // A defense from cases where were too many timers. if( m_demands.size() < 1000 ) // A simple clean is enough. m_demands.clear(); else { // Old container must be utilized. demands_container_t demands; swap( demands, m_demands ); } } }; // // direct_delivery_elapsed_timers_collector_t // /*! * \brief Implementation of elapsed_timers_collector interface which * is not multi-thread safe. * * This implementation is intended to use in not-mtsafe environment * infrastructures where is possible to transform elapsed timer into * a demand directly. * * It means that actual delivery is performed in accept() method. * * \since v.5.5.19 */ class direct_delivery_elapsed_timers_collector_t final : public timer_manager_t::elapsed_timers_collector_t { public : virtual void accept( std::type_index type_index, mbox_t mbox, message_ref_t msg ) override { so_5::impl::mbox_iface_for_timers_t{ mbox } .deliver_message_from_timer( type_index, msg ); } }; // // stats_controller_t // /*! * \brief Implementation of stats_controller for that type of * single-threaded environment. * * \tparam Lock_Holder a type for defense from multi-threaded access. * Expected to be so_5::details::actual_lock_holder_t or * so_5::details::no_lock_holder_t. * * \since v.5.5.19 */ template< typename Lock_Holder > class stats_controller_t final : public stats::controller_t , public stats::repository_t , public stats::impl::st_env_stuff::next_turn_handler_t , protected Lock_Holder { public : //! Initializing constructor. stats_controller_t( //! Mbox for sending messages with run-time statistics. mbox_t distribution_mbox, //! Mbox for delayed messages for initiation of next turn. mbox_t next_turn_mbox ) : m_distribution_mbox( std::move(distribution_mbox) ) , m_next_turn_mbox( std::move(next_turn_mbox) ) {} // Implementation of controller_t interface. virtual const mbox_t & mbox() const override { return m_distribution_mbox; } virtual void turn_on() override { this->lock_and_perform( [&]{ if( status_t::off == m_status ) { // Do the part where exceptions are possible. const auto run_id = m_run_id + 1; send_next_message( very_small_timeout(), run_id ); // We don't expect exceptions from here. m_status = status_t::on; m_run_id = run_id; } } ); } virtual void turn_off() override { this->lock_and_perform( [&] { m_status = status_t::off; } ); } virtual std::chrono::steady_clock::duration set_distribution_period( std::chrono::steady_clock::duration period ) override { return this->lock_and_perform( [&] { auto ret_value = m_distribution_period; m_distribution_period = period; return ret_value; } ); } // Implementation of repository_t interface. virtual void add( stats::source_t & what ) override { this->lock_and_perform( [&] { source_list_add( what, m_head, m_tail ); } ); } virtual void remove( stats::source_t & what ) noexcept override { this->lock_and_perform( [&] { source_list_remove( what, m_head, m_tail ); } ); } // Implementation of next_turn_handler_t interface. virtual void on_next_turn( int run_id ) override { this->lock_and_perform( [&] { if( status_t::on == m_status && run_id == m_run_id ) { const auto actual_duration = distribute_current_data(); if( actual_duration < m_distribution_period ) // There is some time to sleep. send_next_message( m_distribution_period - actual_duration, m_run_id ); else // We must distribute next portion of data // without long waiting. send_next_message( very_small_timeout(), m_run_id ); } } ); } private : //! Status of stats_controller. enum class status_t { off, on }; //! Mbox for sending messages with run-time statistics. const mbox_t m_distribution_mbox; //! Mbox for delayed messages for initiation of next turn. const mbox_t m_next_turn_mbox; //! Object's lock. /*! * In single-threaded invironment we need to have only one lock. */ std::mutex m_lock; //! Current status of stats_controller. status_t m_status{ status_t::off }; //! ID of stats distribution. /*! * Will be increased on every turn_on call. */ int m_run_id{ 0 }; /*! * \name Data sources-related part of controller's data. * \{ */ //! Head of data sources list. stats::source_t * m_head = { nullptr }; //! Tail of data sources list. stats::source_t * m_tail = { nullptr }; std::chrono::steady_clock::duration m_distribution_period{ default_distribution_period() }; /*! * \} */ static std::chrono::steady_clock::duration very_small_timeout() { return std::chrono::milliseconds{1}; } //! Actual distribution of the current statistics. std::chrono::steady_clock::duration distribute_current_data() { auto started_at = std::chrono::steady_clock::now(); send< so_5::stats::messages::distribution_started >( m_distribution_mbox ); auto s = m_head; while( s ) { s->distribute( m_distribution_mbox ); s = source_list_next( *s ); } send< so_5::stats::messages::distribution_finished >( m_distribution_mbox ); return std::chrono::steady_clock::now() - started_at; } //! Helper method for sending next instance of next_turn message. void send_next_message( std::chrono::steady_clock::duration pause, const int run_id ) { send_delayed< next_turn >( m_next_turn_mbox, pause, outliving_mutable( *this ), run_id ); } }; } /* namespace st_reusable_stuff */ } /* namespace env_infrastructures */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/std_message_sinks.hpp000066400000000000000000000242771523065210100226660ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since v.5.8.0 * * \brief Private part of message limit implementation. */ #if !defined( SO_5_IMPL_STD_MESSAGE_SINKS_HPP ) #define SO_5_IMPL_STD_MESSAGE_SINKS_HPP #include #include #include #include #include #include #include #include namespace so_5 { namespace impl { // // sinks_storage_t // /*! * \brief An interface for storage of message_sinks for one agent. * * \since v.5.8.0 */ class sinks_storage_t { sinks_storage_t( const sinks_storage_t & ) = delete; sinks_storage_t & operator=( const sinks_storage_t & ) = delete; public : sinks_storage_t() = default; virtual ~sinks_storage_t() = default; [[nodiscard]] virtual abstract_message_sink_t * find_or_create( const std::type_index & msg_type ) = 0; }; // // storage_without_limits_t // /*! * \brief Implementation of message sink storage for a case when * message limits are not used. * * It holds just one sink that will be used for all types of messages. * * \since v.5.8.0 */ class storage_without_limits_t final : public sinks_storage_t { //! The single sinks that is needed if message limits are not used. message_sink_without_message_limit_t m_sink; public: storage_without_limits_t( partially_constructed_agent_ptr_t owner_ptr ) : m_sink{ owner_ptr } {} abstract_message_sink_t * find_or_create( const std::type_index & /*msg_type*/ ) override { return std::addressof( m_sink ); } }; // // message_sinks_with_limits_holder_t // /*! * \brief Type that holds message_sink_with_message_limit objects. * * It plays a role of associative container with std::type_index as * a key, and message_sink_with_message_limit_t as a value. * * It creates a new instance of message_sink_with_message_limit_t in * find_or_create() method if msg_type is unknown yet. */ class message_sinks_with_limits_holder_t { //! Type of storage for message_sinks. using storage_t = std::map< std::type_index, message_sink_with_message_limit_t >; //! All created message_sinks. storage_t m_sinks; //! Helper for build an initial storage. [[nodiscard]] static storage_t build_sinks( partially_constructed_agent_ptr_t owner_ptr, so_5::message_limit::description_container_t && descriptions ) { storage_t result; for( auto & d : descriptions ) result.emplace( d.m_msg_type, message_sink_with_message_limit_t{ owner_ptr, d.m_limit, std::move(d.m_action) } ); return result; } public: //! Initializing constructor. message_sinks_with_limits_holder_t( partially_constructed_agent_ptr_t owner_ptr, so_5::message_limit::description_container_t && descriptions ) : m_sinks{ build_sinks( owner_ptr, std::move(descriptions) ) } {} /*! * \note * Returns nullptr if there is no sink for \a msg_type. */ [[nodiscard]] abstract_message_sink_t * find( const std::type_index & msg_type ) { auto it = m_sinks.find( msg_type ); if( it == m_sinks.end() ) return nullptr; return std::addressof( it->second ); } /*! * \note * If \a msg_type isn't known then a new message_sink_with_message_limit_t * is created and a pointer to that new object is returned. */ [[nodiscard]] abstract_message_sink_t * find_or_create( const std::type_index & msg_type, partially_constructed_agent_ptr_t owner_ptr, const so_5::message_limit::description_t & limit_description ) { auto it = m_sinks.find( msg_type ); if( it == m_sinks.end() ) { it = m_sinks.emplace( msg_type, message_sink_with_message_limit_t{ owner_ptr, limit_description.m_limit, limit_description.m_action } ) .first; } return std::addressof( it->second ); } }; // // fixed_sinks_storage_t // /*! * \brief A fixed-capacity storage for message sinks with * message limits for one agent. * * \since v.5.8.0 */ class fixed_sinks_storage_t final : public sinks_storage_t { public : //! Initializing constructor. fixed_sinks_storage_t( //! Owner for all sinks to be constructed. partially_constructed_agent_ptr_t owner_ptr, //! Source description of limits. //! Since v.5.7.1 this container is expected to be //! sorted and checked for duplicates. so_5::message_limit::description_container_t && descriptions ) : m_holder{ owner_ptr, std::move(descriptions) } {} [[nodiscard]] abstract_message_sink_t * find_or_create( const std::type_index & msg_type ) override { return m_holder.find( msg_type ); } private : //! All sinks. message_sinks_with_limits_holder_t m_holder; }; // // growable_sinks_storage_t // /*! * \brief A storage of growable capacity for message limits for one agent. * * \since v.5.8.0 */ class growable_sinks_storage_t final : public sinks_storage_t { public : //! Initializing constructor. growable_sinks_storage_t( //! Owner for all sinks to be constructed. partially_constructed_agent_ptr_t owner_ptr, //! Description of the default limit. so_5::message_limit::description_t && default_limit_description, //! Source description of limits. so_5::message_limit::description_container_t && descriptions ) : m_owner_ptr{ owner_ptr } , m_default_limit_description{ std::move(default_limit_description) } , m_holder{ owner_ptr, std::move(descriptions) } {} [[nodiscard]] abstract_message_sink_t * find_or_create( const std::type_index & msg_type ) override { return m_holder.find_or_create( msg_type, m_owner_ptr, m_default_limit_description ); } private : //! Owner of sinks and limits. const partially_constructed_agent_ptr_t m_owner_ptr; //! Description of the default limit. const so_5::message_limit::description_t m_default_limit_description; //! All sinks and limits. message_sinks_with_limits_holder_t m_holder; }; namespace description_preparation_details { /*! * \brief Check presence of limit for %msg_state_timeout and add it if there is * no such a limit. * * The addition of a pseudo-limit for so_5::details::msg_state_timeout is * neccessary to avoid exceptions during an attempt to make a subscription for * it when message limits are used. * * \since v.5.8.3 */ inline void append_dummy_limit_for_state_timeout_msg( so_5::message_limit::description_container_t & original_descriptions ) { const std::type_index type_to_find = message_payload_type< ::so_5::details::msg_state_timeout >::subscription_type_index(); // Check that this message is not specified explicitly. const auto it = std::find_if( std::begin( original_descriptions ), std::end( original_descriptions ), [&type_to_find]( const auto & d ) { return d.m_msg_type == type_to_find; } ); if( it == end( original_descriptions ) ) { // Now we can add another description. original_descriptions.push_back( { type_to_find, // No real limit. Don't expect it to be reached. std::numeric_limits::max(), // No real action. [](const ::so_5::message_limit::overlimit_context_t &) -> void {} } ); } } /*! * * Returns sorted array as the first item of the result tuple. * * If there is a description for any_unspecified_message type then * this description is removed from \a original_descriptions and * it is returned as the second item of the result tuple. * * Throws if there is a duplicate in the \a original_descriptions. * * \since * v.5.7.1 */ [[nodiscard]] inline std::tuple< so_5::message_limit::description_container_t, std::optional< so_5::message_limit::description_t > > prepare( so_5::message_limit::description_container_t original_descriptions ) { using namespace std; optional< so_5::message_limit::description_t > default_limit; // Pay attention to so_5::state_t::time_limit_t::msg_timeout... append_dummy_limit_for_state_timeout_msg( original_descriptions ); // Descriptions must be sorted. sort( begin( original_descriptions ), end( original_descriptions ), []( auto & a, auto & b ) { return a.m_msg_type < b.m_msg_type; } ); // There must not be duplicates. auto duplicate = adjacent_find( begin( original_descriptions ), end( original_descriptions ), []( auto & a, auto & b ) { return a.m_msg_type == b.m_msg_type; } ); if( duplicate != end( original_descriptions ) ) SO_5_THROW_EXCEPTION( rc_several_limits_for_one_message_type, std::string( "several limits are defined for message; " "msg_type: " ) + duplicate->m_msg_type.name() ); // Try to find a description for special `any_unspecified_message` mark. const std::type_index mark_type{ typeid(so_5::message_limit::any_unspecified_message) }; auto mark_it = find_if( begin( original_descriptions ), end( original_descriptions ), [mark_type]( const auto & d ) { return d.m_msg_type == mark_type; } ); if( mark_it != end( original_descriptions ) ) { default_limit = std::move(*mark_it); original_descriptions.erase( mark_it ); } return { std::move(original_descriptions), std::move(default_limit) }; } } /* namespace description_preparation_details */ //! Create info_storage object if there are some message limits. [[nodiscard]] inline static std::unique_ptr< sinks_storage_t > create_sinks_storage_if_necessary( partially_constructed_agent_ptr_t owner_ptr, so_5::message_limit::description_container_t && descriptions ) { std::unique_ptr< sinks_storage_t > result; if( descriptions.empty() ) { result = std::make_unique< storage_without_limits_t >( owner_ptr ); } else { auto [sorted_descs, default_limit] = description_preparation_details::prepare( std::move(descriptions) ); if( !default_limit ) // There is no default limit, so fixed_sinks_storage_t // should be used. result = std::make_unique< fixed_sinks_storage_t >( owner_ptr, std::move(sorted_descs) ); else result = std::make_unique< growable_sinks_storage_t >( owner_ptr, std::move(*default_limit), std::move(sorted_descs) ); } return result; } } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/std_msg_tracer_holder.hpp000066400000000000000000000034631523065210100235100ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Standard implementation of message tracer holder. * * \since * v.5.5.22 */ #if !defined( SO_5_IMPL_STD_MSG_TRACER_HOLDER_HPP ) #define SO_5_IMPL_STD_MSG_TRACER_HOLDER_HPP #include #include #include namespace so_5 { namespace msg_tracing { namespace impl { // // std_holder_t // /*! * \brief Standard implementation of message tracer holder. * * This class also contains additional feature: it allows to change * message tracing filter. * * \since * v.5.5.22 */ class std_holder_t : public holder_t { public : //! Initializing constructor. std_holder_t( //! Optional message tracing filter. //! This can be empty pointer. It doesn't matter because //! a filter can be changed latter. filter_shptr_t filter, //! Message tracer. //! If it is an empty pointer then message tracing is disabled. //! This value can't be changed in the future. tracer_unique_ptr_t tracer ) : m_filter{ std::move(filter) } , m_tracer{ std::move(tracer) } {} virtual bool is_msg_tracing_enabled() const noexcept override { return nullptr != m_tracer.get(); } virtual filter_shptr_t take_filter() noexcept override { std::lock_guard< default_spinlock_t > l{ m_lock }; return m_filter; } void change_filter( filter_shptr_t filter ) noexcept { std::lock_guard< default_spinlock_t > l{ m_lock }; m_filter = std::move(filter); } virtual tracer_t & tracer() const noexcept override { return *m_tracer; } private : //! A lock for protecting filter object. default_spinlock_t m_lock; filter_shptr_t m_filter; const tracer_unique_ptr_t m_tracer; }; } /* namespace impl */ } /* namespace msg_tracing */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/stop_guard_repo.hpp000066400000000000000000000165331523065210100223510ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Repository of stop_guards. * * \since * v.5.5.19.2 */ #if !defined( SO_5_IMPL_STOP_GUARD_REPO_HPP ) #define SO_5_IMPL_STOP_GUARD_REPO_HPP #include #include #include #include #include #include namespace so_5 { namespace impl { // // stop_guard_repository_t // /*! * \brief Repository of stop_guards. * * \note * Performs multitheading defense by very simple way: via std::mutex object. * * \since * v.5.5.19.2 */ class stop_guard_repository_t : public ::so_5::details::lock_holder_detector< std::mutex >::type { public : //! Action which must be performed as result of operation on repository. enum class action_t { //! Nothing to do. Stop operation is not started. do_nothing, //! Stop operation is started but can't be finished right now. wait_for_completion, //! Stop operation must be finished. do_actual_stop }; stop_guard_repository_t() { constexpr std::size_t initial_capacity = 4; m_guards.reserve( initial_capacity ); m_container_for_shutdown.reserve( initial_capacity ); } stop_guard_repository_t( const stop_guard_repository_t & ) = delete; stop_guard_repository_t( stop_guard_repository_t && ) = delete; //! Setup new stop_guard. /*! * \note * Uniqueness of stop_guard is not checked. It means that the same * stop_guard can be added to repository several times. * * \retval stop_guard_t::setup_result_t::ok if stop is not in progress * and new stop_guard has been set up successfully. * \retval stop_guard_t::setup_result_t::stop_already_in_progress new * stop_guard is not set because stop is already in progress. */ stop_guard_t::setup_result_t setup_guard( stop_guard_shptr_t guard ) { return this->lock_and_perform( [&] { auto ret_value = stop_guard_t::setup_result_t::ok; if( status_t::not_started != m_status ) ret_value = stop_guard_t::setup_result_t::stop_already_in_progress; else { // Ensure that m_container_for_shutdown has enough space for // updated content of m_guards. if( m_guards.size() + 1 > m_container_for_shutdown.capacity() ) // Just double the capacity. // NOTE: don't care about exceptions. If this operation // it's not a problem because nothing has been changed yet. m_container_for_shutdown.reserve( m_container_for_shutdown.capacity() * 2u ); // Now we can modify the main container. auto it = std::lower_bound( std::begin(m_guards), std::end(m_guards), guard ); m_guards.insert( it, std::move(guard) ); } return ret_value; } ); } //! Remove stop_guard. /*! * \retval action_t::do_nothing there is no stop in progress and nothing * need to be done. * \retval action_t::wait_for_completion there is stop in progress but * there are also other stop_guards which do not complete their * actions yet. * \retval action_t::do_actual_stop stop operation needs to be completed. */ action_t remove_guard( stop_guard_shptr_t guard ) noexcept { return so_5::details::invoke_noexcept_code( [&] { return this->lock_and_perform( [&] { auto ret_value = action_t::do_nothing; const auto it = std::lower_bound( std::begin(m_guards), std::end(m_guards), guard ); if( it != std::end(m_guards) && *it == guard ) m_guards.erase(it); if( status_t::started == m_status ) ret_value = m_guards.empty() ? action_t::do_actual_stop : action_t::wait_for_completion; return ret_value; } ); } ); } //! Initiate stop operation. /*! * \note * It is safe to call this method when stop is already in progress. * * \retval action_t::wait_for_completion there are some stop_guards * which do not complete their actions yet. * \retval action_t::do_actual_stop stop operation needs to be completed. */ action_t initiate_stop() noexcept { return so_5::details::invoke_noexcept_code( [&] { auto ret_value = action_t::wait_for_completion; // The first stage: change status and take a copy // of actual guards list if necessary. bool need_call_stop = false; this->lock_and_perform( [&] { if( status_t::not_started == m_status ) { // We expect that there won't be exceptions because: // // 1. m_container_for_shutdown has enough capacity to // hold all values from m_guards without additional // allocations. // 2. std::shared_ptr::operator= is noexcept. m_container_for_shutdown = m_guards; // The stop operation is not started yet. m_status = status_t::start_in_progress; need_call_stop = true; } } ); // The second stage: calling stop() for all stop_guards. if( need_call_stop ) { for( auto & g : m_container_for_shutdown ) { g->stop(); } // We don't need the content of m_container_for_shutdown anymore. // It seems that modifying m_container_for_shutdown here is safe, // because only one thread can be in this place. m_container_for_shutdown.clear(); } // The third stage: check for possibitility to complete // the stop operation right now. // It is possible that all stop_guards will be removed // on the second stage. this->lock_and_perform( [&] { if( m_guards.empty() ) { m_status = status_t::completed; ret_value = action_t::do_actual_stop; } else // The stop operation is started. We must wait // while all live stop_guards finish their work. m_status = status_t::started; } ); return ret_value; } ); } private : //! Status of the stop operation. enum class status_t { not_started, start_in_progress, started, completed }; //! The current status of the stop operation status_t m_status = status_t::not_started; //! Type of the stop_guards list. using guards_container_t = std::vector< stop_guard_shptr_t >; //! List of actual stop_guards. guards_container_t m_guards; //! Additional container to be used on shutdown operation. /*! * We need a copy of m_guards in initiate_stop(). But the initiate_stop() * method is noexcept and we can't just create a copy of m_guards this way: * * \code * guards_container_t guards_to_stop = m_guards; * \endcode * * because it will require allocations and allocations may throw. * * To make initiate_stop() noexcept we have to reserve space for that * a copy earlier. The m_container_for_shutdown is used for that purpose. * * Every time a new item added to m_guards the capacity of m_container_for_shutdown * is checked. If there is no enough space to copy m_guards to * m_container_for_shutdown then the capacity of m_container_for_shutdown * is increased. Thus we always have space in m_container_for_shutdown * to make a full copy of m_guards. * * \note * In the current implementation of SObjectizer the capacity of * m_container_for_shutdown is always growth but never descreases. * * \since v.5.8.2 */ guards_container_t m_container_for_shutdown; }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/subscr_storage_adaptive.cpp000066400000000000000000000154641523065210100240540ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.3 * * \file * \brief An adaptive storage for agent's subscriptions information. */ #include #include #include namespace so_5 { namespace impl { /*! * \since * v.5.5.3 * * \brief An adaptive storage for agent's subscriptions information. */ namespace adaptive_subscr_storage { /*! * \since * v.5.5.3 * * \brief An adaptive storage for agent's subscriptions information. * * Uses two actual storages: one for small amount of subscriptions, * another for the big amount. * * Controls the size of the current storage. If size of the small storage * exceeded threshold then switches from small to the big one. If size of the * big storage drops below the threshold then switches to the small storage. */ class storage_t : public subscription_storage_t { public : storage_t( std::size_t threshold, subscription_storage_unique_ptr_t small_storage, subscription_storage_unique_ptr_t large_storage ); virtual void create_event_subscription( const mbox_t & mbox_ref, const std::type_index & type_index, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) override; virtual void drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) noexcept override; void drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) noexcept override; void drop_all_subscriptions() noexcept override; const event_handler_data_t * find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept override; void debug_dump( std::ostream & to ) const override; void drop_content() noexcept override; subscription_storage_common::subscr_info_vector_t query_content() const override; void setup_content( subscription_storage_common::subscr_info_vector_t && info ) override; std::size_t query_subscriptions_count() const override; private : const std::size_t m_threshold; subscription_storage_unique_ptr_t m_small_storage; subscription_storage_unique_ptr_t m_large_storage; subscription_storage_t * m_current_storage = nullptr; void try_switch_to_smaller_storage() noexcept; }; storage_t::storage_t( std::size_t threshold, subscription_storage_unique_ptr_t small_storage, subscription_storage_unique_ptr_t large_storage ) : m_threshold( threshold ) , m_small_storage( std::move( small_storage ) ) , m_large_storage( std::move( large_storage ) ) { m_current_storage = m_small_storage.get(); } void storage_t::create_event_subscription( const mbox_t & mbox, const std::type_index & msg_type, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) { if( m_current_storage == m_small_storage.get() && m_threshold <= m_current_storage->query_subscriptions_count() ) { // Storage must be switched to the large one. // Exceptions are going out. // It means that exception during switching // to the large storage will prohibit subscription. m_large_storage->setup_content( m_small_storage->query_content() ); m_small_storage->drop_content(); m_current_storage = m_large_storage.get(); } m_current_storage->create_event_subscription( mbox, msg_type, message_sink, target_state, method, thread_safety, handler_kind ); } void storage_t::drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) noexcept { m_current_storage->drop_subscription( mbox, msg_type, target_state ); try_switch_to_smaller_storage(); } void storage_t::drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) noexcept { m_current_storage->drop_subscription_for_all_states( mbox, msg_type ); try_switch_to_smaller_storage(); } void storage_t::drop_all_subscriptions() noexcept { m_current_storage->drop_all_subscriptions(); m_current_storage = m_small_storage.get(); } const event_handler_data_t * storage_t::find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept { return m_current_storage->find_handler( mbox_id, msg_type, current_state ); } void storage_t::debug_dump( std::ostream & to ) const { m_current_storage->debug_dump( to ); } void storage_t::drop_content() noexcept { m_current_storage->drop_content(); m_current_storage = m_small_storage.get(); } subscription_storage_common::subscr_info_vector_t storage_t::query_content() const { return m_current_storage->query_content(); } void storage_t::setup_content( subscription_storage_common::subscr_info_vector_t && info ) { auto s = info.size() <= m_threshold ? m_small_storage.get() : m_large_storage.get(); s->setup_content( std::move( info ) ); m_current_storage = s; } std::size_t storage_t::query_subscriptions_count() const { return m_current_storage->query_subscriptions_count(); } void storage_t::try_switch_to_smaller_storage() noexcept { if( m_current_storage == m_large_storage.get() && m_threshold >= m_large_storage->query_subscriptions_count() ) { // All exceptions are ignored because // query_content, setup_content and drop_content // must provide strong exception guarantee. try { m_small_storage->setup_content( m_large_storage->query_content() ); m_large_storage->drop_content(); m_current_storage = m_small_storage.get(); } catch( ... ) {} } } } /* namespace adaptive_subscr_storage */ } /* namespace impl */ SO_5_FUNC subscription_storage_factory_t adaptive_subscription_storage_factory( std::size_t threshold ) { return [threshold]() { return impl::subscription_storage_unique_ptr_t( new impl::adaptive_subscr_storage::storage_t( threshold, vector_based_subscription_storage_factory( threshold )(), map_based_subscription_storage_factory()() ) ); }; } SO_5_FUNC subscription_storage_factory_t adaptive_subscription_storage_factory( std::size_t threshold, const subscription_storage_factory_t & small_storage_factory, const subscription_storage_factory_t & large_storage_factory ) { return [=]() { return impl::subscription_storage_unique_ptr_t( new impl::adaptive_subscr_storage::storage_t( threshold, small_storage_factory(), large_storage_factory() ) ); }; } SO_5_FUNC subscription_storage_factory_t default_subscription_storage_factory() { return adaptive_subscription_storage_factory( 8 ); } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/subscr_storage_flat_set_based.cpp000066400000000000000000000333231523065210100252100ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief A flat-set based storage for agent's subscriptions information. * * \since v.5.8.2 */ #include #include #include #include #include #include namespace so_5 { namespace impl { /*! * \brief A flat-set storage for agent's subscriptions information. * * \since v.5.8.2 * */ namespace flat_set_based_subscr_storage { /*! * \brief A flat-set based storage for agent's subscriptions information. * * This implementation uses a sorted vector (aka "flat_set") of subscr_info_t. * Binary search is used for searching subscription, create and drop subscription * operations. * * An std::vector is used as underlying storage. It will grow as necessary. Initial * capacity is specified in the constructor. * * \since v.5.8.2 */ class storage_t : public subscription_storage_t { public : storage_t( std::size_t initial_capacity ); ~storage_t() override; virtual void create_event_subscription( const mbox_t & mbox_ref, const std::type_index & type_index, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) override; virtual void drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) noexcept override; void drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) noexcept override; void drop_all_subscriptions() noexcept override; const event_handler_data_t * find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept override; void debug_dump( std::ostream & to ) const override; void drop_content() noexcept override; subscription_storage_common::subscr_info_vector_t query_content() const override; void setup_content( subscription_storage_common::subscr_info_vector_t && info ) override; std::size_t query_subscriptions_count() const override; private : using info_t = subscription_storage_common::subscr_info_t; using subscr_info_vector_t = subscription_storage_common::subscr_info_vector_t; //! A helper predicate for searching the same mbox and message type pairs. /*! * This predicate is useful when the pointer to target state has be * be ignored. For example, when we have to remove all subscriptions * for all states. */ struct is_same_mbox_msg_t { const mbox_id_t m_id; const std::type_index & m_type; [[nodiscard]] bool operator()( const info_t & info ) const noexcept { return m_id == info.m_mbox->id() && m_type == info.m_msg_type; } }; /*! * @brief Helper type for storing only key information about a subscription. * * For fast search in a vector of subscriptions we have to deal with * only a few of key fields of subscr_info_t. This helper type allows to * agregate all those fields in a (rather) small object. */ struct key_info_t { mbox_id_t m_mbox_id; const std::type_index & m_msg_type; const state_t * m_state; }; //! A helper predicate for searching specific subscription /*! * This predicate is intended to be used for adding new subscription * and removing of an existing subscription. * * It has several operator() to be used for comparison if different * applications. */ struct key_info_comparator_t { [[nodiscard]] bool operator()( const key_info_t & a, const key_info_t & b ) const noexcept { if( a.m_mbox_id < b.m_mbox_id ) return true; else if( a.m_mbox_id == b.m_mbox_id ) { if( a.m_msg_type < b.m_msg_type ) return true; else if( a.m_msg_type == b.m_msg_type ) { // NOTE: it's UB to compare two arbitrary pointers. using ptr_comparator_t = std::less< const state_t * >; return ptr_comparator_t{}( a.m_state, b.m_state ); } } return false; } [[nodiscard]] bool operator()( const info_t & a, const key_info_t & b ) const noexcept { return (*this)( key_info_t{ a.m_mbox->id(), a.m_msg_type, a.m_state }, b ); } [[nodiscard]] bool operator()( const info_t & a, const info_t & b ) const noexcept { return (*this)( key_info_t{ a.m_mbox->id(), a.m_msg_type, a.m_state }, key_info_t{ b.m_mbox->id(), b.m_msg_type, b.m_state } ); } }; //! Subscription information. subscr_info_vector_t m_events; void destroy_all_subscriptions() noexcept; /*! * @brief Helper for checking presence of subscriptions for the same * message from the same mbox. * * Accepts a valid iterator and returns true if there are at least one * item around it (one to the left or/and one to the right) that * has the same mbox_id and msg_type. * * This helper handles case when @a it points to the first or * the right item of m_events. * * @attention * The @a it should not be "past the end" iterator. */ [[nodiscard]] bool check_presence_of_mbox_msg_type_info_around_it( subscr_info_vector_t::iterator it, const is_same_mbox_msg_t & predicate ) const noexcept; }; namespace { /*! * @brief Helper to check if two objects are for the same subscription. * * @note * Only mbox_id, msg_type and target_state are compared. */ [[nodiscard]] bool is_equal( const subscription_storage_common::subscr_info_t & a, const subscription_storage_common::subscr_info_t & b ) noexcept { return a.m_mbox->id() == b.m_mbox->id() && a.m_msg_type == b.m_msg_type && a.m_state == b.m_state ; } /*! * @brief Helper to check if subscription information the same. * * @note * Only mbox_id, msg_type and target_state are compared. */ [[nodiscard]] bool is_equal( const subscription_storage_common::subscr_info_t & a, mbox_id_t mbox_id, const std::type_index & msg_type, const state_t * target_state ) noexcept { return a.m_mbox->id() == mbox_id && a.m_msg_type == msg_type && a.m_state == target_state ; } } /* namespace anonymous */ storage_t::storage_t( std::size_t initial_capacity ) { m_events.reserve( initial_capacity ); } storage_t::~storage_t() { destroy_all_subscriptions(); } void storage_t::create_event_subscription( const mbox_t & mbox, const std::type_index & msg_type, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) { using namespace std; using namespace subscription_storage_common; info_t info_to_store{ mbox, msg_type, message_sink, target_state, method, thread_safety, handler_kind }; // Check that this subscription is new. auto it = std::lower_bound( m_events.begin(), m_events.end(), info_to_store, key_info_comparator_t{} ); if( it != m_events.end() && is_equal( *it, info_to_store ) ) // Subscription already exists. It's an error! SO_5_THROW_EXCEPTION( rc_evt_handler_already_provided, "agent is already subscribed to message, " + make_subscription_description( mbox, msg_type, target_state ) ); // Just add subscription to the storage. it = m_events.insert( it, std::move(info_to_store) ); // Need to check is there existing information for (mbox_id, msg_type) // pair. If it's already here then there is no need to call // subscribe_event_handler for the mbox. const bool info_for_mbox_msg_type_exists = check_presence_of_mbox_msg_type_info_around_it( it, is_same_mbox_msg_t{ mbox->id(), msg_type } ); // Note: since v.5.5.9 mbox subscription is initiated even if // it is MPSC mboxes. It is important for the case of message // delivery tracing. if( !info_for_mbox_msg_type_exists ) { // Mbox must create subscription. so_5::details::do_with_rollback_on_exception( [&] { mbox->subscribe_event_handler( msg_type, message_sink ); }, [&] { m_events.erase( it ); } ); } } void storage_t::drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) noexcept { using namespace std; auto existed_position = std::lower_bound( m_events.begin(), m_events.end(), key_info_t{ mbox->id(), msg_type, std::addressof(target_state) }, key_info_comparator_t{} ); if( existed_position != m_events.end() && is_equal( *existed_position, mbox->id(), msg_type, std::addressof(target_state) ) ) { // This value may be necessary for unsubscription. abstract_message_sink_t & message_sink = existed_position->m_message_sink.get(); // Need to check is there existing information for (mbox_id, msg_type) // pair. If it's already here then there is no need to call // subscribe_event_handler for the mbox. const bool info_for_mbox_msg_type_exists = check_presence_of_mbox_msg_type_info_around_it( existed_position, is_same_mbox_msg_t{ mbox->id(), msg_type } ); // Item is no more needed. m_events.erase( existed_position ); // Note v.5.5.9 unsubscribe_event_handler is called for // mbox even if it is MPSC mbox. It is necessary for the case // of message delivery tracing. // If there is no more subscriptions to that mbox then // the mbox must remove information about that agent. if( !info_for_mbox_msg_type_exists ) { // If we are here then there is no more references // to the mbox. And mbox must not hold reference // to the agent. mbox->unsubscribe_event_handler( msg_type, message_sink ); } } } void storage_t::drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) noexcept { using namespace std; const auto predicate = is_same_mbox_msg_t{ mbox->id(), msg_type }; if( auto it = std::lower_bound( m_events.begin(), m_events.end(), // NOTE: use NULL instead of actual pointer to a state. key_info_t{ mbox->id(), msg_type, nullptr }, key_info_comparator_t{} ); it != m_events.end() && predicate( *it ) ) { // There are subscriptions to be removed. // Have to store message_sink reference because it has to // be passed to unsubscribe_event_handler. auto & message_sink = it->m_message_sink.get(); // Remove all items that match the predicate. m_events.erase( remove_if( it, end( m_events ), predicate ), end( m_events ) ); // Note: since v.5.5.9 mbox unsubscription is initiated even if // it is MPSC mboxes. It is important for the case of message // delivery tracing. mbox->unsubscribe_event_handler( msg_type, message_sink ); } } void storage_t::drop_all_subscriptions() noexcept { destroy_all_subscriptions(); } const event_handler_data_t * storage_t::find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept { auto existed_position = std::lower_bound( m_events.begin(), m_events.end(), key_info_t{ mbox_id, msg_type, std::addressof(current_state) }, key_info_comparator_t{} ); if( existed_position != m_events.end() && is_equal( *existed_position, mbox_id, msg_type, std::addressof(current_state) ) ) { return std::addressof(existed_position->m_handler); } else return nullptr; } void storage_t::debug_dump( std::ostream & to ) const { for( const auto & e : m_events ) to << "{" << e.m_mbox->id() << ", " << e.m_msg_type.name() << ", " << e.m_state->query_name() << "}" << std::endl; } void storage_t::destroy_all_subscriptions() noexcept { if( m_events.empty() ) // Nothing to do at empty subscription list. return; // Destroy all subscriptions for unique (mbox, msg_type). const auto total_items = m_events.size(); for( std::size_t i = 0u; i < total_items; ) { auto & current_info = m_events[ i ]; current_info.m_mbox->unsubscribe_event_handler( current_info.m_msg_type, current_info.m_message_sink ); // We should skip all consequtive items with the same // (mbox, msg_type) pairs. std::size_t j = 1u; for( ; (i+j) < total_items; ++j ) { const auto & next_info = m_events[ i+j ]; if( current_info.m_mbox->id() != next_info.m_mbox->id() || current_info.m_msg_type != next_info.m_msg_type ) break; } i += j; } // Cleanup subscription vector. drop_content(); } bool storage_t::check_presence_of_mbox_msg_type_info_around_it( subscr_info_vector_t::iterator it, const is_same_mbox_msg_t & predicate ) const noexcept { bool result = false; if( it != m_events.begin() ) result = predicate( *(std::prev(it)) ); if( !result ) { if( auto next = std::next(it); next != m_events.end() ) result = predicate( *(std::next(it)) ); } return result; } void storage_t::drop_content() noexcept { m_events.clear(); } subscription_storage_common::subscr_info_vector_t storage_t::query_content() const { return m_events; } void storage_t::setup_content( subscription_storage_common::subscr_info_vector_t && info ) { m_events = std::move( info ); std::sort( m_events.begin(), m_events.end(), key_info_comparator_t{} ); } std::size_t storage_t::query_subscriptions_count() const { return m_events.size(); } } /* namespace flat_set_based_subscr_storage */ } /* namespace impl */ SO_5_FUNC subscription_storage_factory_t flat_set_based_subscription_storage_factory( std::size_t initial_capacity ) { return [initial_capacity]() { return impl::subscription_storage_unique_ptr_t( new impl::flat_set_based_subscr_storage::storage_t( initial_capacity ) ); }; } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/subscr_storage_hash_table_based.cpp000066400000000000000000000304271523065210100255030ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.3 * * \file * \brief A hash_table-based storage for agent's subscriptions information. */ #include #include #include #include #include #include namespace so_5 { namespace impl { /*! * \since * v.5.4.0 * * \brief A hash_table-based storage for agent's subscriptions information. */ namespace hash_table_subscr_storage { //! Subscription key type. struct key_t { //! Unique ID of mbox. mbox_id_t m_mbox_id; //! Message type. std::type_index m_msg_type; //! State of agent. const state_t * m_state; //! Default constructor. inline key_t() : m_mbox_id( null_mbox_id() ) , m_msg_type( typeid(void) ) , m_state( nullptr ) {} //! Constructor for the case when it is necessary to //! find all keys with (mbox_id, msg_type) prefix. inline key_t( mbox_id_t mbox_id, std::type_index msg_type ) : m_mbox_id( mbox_id ) , m_msg_type( msg_type ) , m_state( nullptr ) {} //! Initializing constructor. inline key_t( mbox_id_t mbox_id, std::type_index msg_type, const state_t & state ) : m_mbox_id( mbox_id ) , m_msg_type( msg_type ) , m_state( &state ) {} inline bool operator<( const key_t & o ) const noexcept { if( m_mbox_id < o.m_mbox_id ) return true; else if( m_mbox_id == o.m_mbox_id ) { if( m_msg_type < o.m_msg_type ) return true; else if( m_msg_type == o.m_msg_type ) return m_state < o.m_state; } return false; } inline bool operator==( const key_t & o ) const noexcept { return m_mbox_id == o.m_mbox_id && m_msg_type == o.m_msg_type && m_state == o.m_state; } inline bool is_same_mbox_msg_pair( const key_t & o ) const noexcept { return m_mbox_id == o.m_mbox_id && m_msg_type == o.m_msg_type; } }; // // hash_t // /*! * \since * v.5.4.0 * * \brief A special class for calculating hash value via pointer to key. */ struct hash_t { [[nodiscard]] std::size_t operator()( const key_t * ptr ) const noexcept { // This details have been borrowed from documentation fo // boost::hash_combine function: // http://www.boost.org/doc/libs/1_46_1/doc/html/hash/reference.html#boost.hash_combine // const auto h1 = std::hash< so_5::mbox_id_t >()( ptr->m_mbox_id ); const auto h2 = h1 ^ (std::hash< std::type_index >()( ptr->m_msg_type ) + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); return h2 ^ (std::hash< const state_t * >()( ptr->m_state ) + 0x9e3779b9 + (h2 << 6) + (h2 >> 2)); } }; // // equal_to_t // /*! * \since * v.5.4.0 * * \brief A special class for checking equality via pointer to key. */ struct equal_to_t { [[nodiscard]] bool operator()( const key_t * a, const key_t * b ) const { return (*a) == (*b); } }; namespace { template< class S > [[nodiscard]] bool is_known_mbox_msg_pair( S & s, typename S::iterator it ) { if( it != s.begin() ) { typename S::iterator prev = it; --prev; if( it->first.is_same_mbox_msg_pair( prev->first ) ) return true; } typename S::iterator next = it; ++next; if( next != s.end() ) return it->first.is_same_mbox_msg_pair( next->first ); return false; } } /* namespace anonymous */ /*! * \since * v.5.4.0 * * \brief A storage for agent's subscriptions information. * * The motivation for this class is the following: * * Initially an agent had only one subscription map where the key * was a compound key: (mbox_id, msg_type, state). * * This map was used in both operation: subscription creation/removing * and event handler searching. But this data structure was very inefficient * for event handler lookup. Especially when agent receive the same * message from same mbox in different states. It means that for most * lookup operations (mbox_id, msg_type) were the same. * * This class uses std::map and std::unordered_map for different purposes: * - std::map is used for subscription management (strictly ordered data * structure makes subscription management a lot easier); * - std::unordered_map is used for event handler lookup operations * (hash_table is more effiecient especially for the cases where the * only difference in lookup keys is in state field). */ class storage_t : public subscription_storage_t { public : storage_t(); ~storage_t() override; virtual void create_event_subscription( const mbox_t & mbox_ref, const std::type_index & type_index, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) override; virtual void drop_subscription( const mbox_t & mbox_ref, const std::type_index & type_index, const state_t & target_state ) noexcept override; void drop_subscription_for_all_states( const mbox_t & mbox_ref, const std::type_index & type_index ) noexcept override; void drop_all_subscriptions() noexcept override; const event_handler_data_t * find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept override; void debug_dump( std::ostream & to ) const override; void drop_content() noexcept override; subscription_storage_common::subscr_info_vector_t query_content() const override; void setup_content( subscription_storage_common::subscr_info_vector_t && info ) override; std::size_t query_subscriptions_count() const override; private : //! Information about mbox and message_sink used for //! subscription to that mbox. struct mbox_with_sink_info_t { mbox_t m_mbox; std::reference_wrapper< abstract_message_sink_t > m_message_sink; }; //! Type of subscription map. using map_t = std::map< key_t, mbox_with_sink_info_t >; //! Map of subscriptions. /*! * It is important to has right order of elements in this map: * all subscription for the same (mbox,message) must be groupped * together. */ map_t m_map; //! Type of event handlers hash table. using hash_table_t = std::unordered_map< const key_t *, event_handler_data_t, hash_t, equal_to_t >; //! Hash table of event handlers. hash_table_t m_hash_table; void destroy_all_subscriptions() noexcept; }; storage_t::storage_t() {} storage_t::~storage_t() { destroy_all_subscriptions(); } void storage_t::create_event_subscription( const mbox_t & mbox, const std::type_index & type_index, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) { using namespace subscription_storage_common; key_t key{ mbox->id(), type_index, target_state }; auto insertion_result = m_map.emplace( key, mbox_with_sink_info_t{ mbox, std::ref(message_sink) } ); if( !insertion_result.second ) SO_5_THROW_EXCEPTION( rc_evt_handler_already_provided, "agent is already subscribed to message, " + make_subscription_description( mbox, type_index, target_state ) ); so_5::details::do_with_rollback_on_exception( [&] { m_hash_table.emplace( &(insertion_result.first->first), event_handler_data_t( method, thread_safety, handler_kind ) ); }, [&] { m_map.erase( insertion_result.first ); } ); auto mbox_msg_known = is_known_mbox_msg_pair( m_map, insertion_result.first ); if( !mbox_msg_known ) { // Mbox must create subscription. so_5::details::do_with_rollback_on_exception( [&] { mbox->subscribe_event_handler( type_index, message_sink ); }, [&] { m_hash_table.erase( &(insertion_result.first->first) ); m_map.erase( insertion_result.first ); } ); } } void storage_t::drop_subscription( const mbox_t & mbox_ref, const std::type_index & type_index, const state_t & target_state ) noexcept { key_t key( mbox_ref->id(), type_index, target_state ); auto it = m_map.find( key ); if( m_map.end() != it ) { bool mbox_msg_known = is_known_mbox_msg_pair( m_map, it ); // A reference to message_sink has to be stored for a case when // unsubscribe_event_handler has to be called. abstract_message_sink_t & sink = it->second.m_message_sink.get(); m_hash_table.erase( &(it->first) ); m_map.erase( it ); if( !mbox_msg_known ) { mbox_ref->unsubscribe_event_handler( type_index, sink ); } } } void storage_t::drop_subscription_for_all_states( const mbox_t & mbox_ref, const std::type_index & type_index ) noexcept { const key_t key( mbox_ref->id(), type_index ); auto it = m_map.lower_bound( key ); auto need_erase = [&] { return it != m_map.end() && key.is_same_mbox_msg_pair( it->first ); }; const bool found = need_erase(); if( found ) { // A reference to message_sink is necessary for calling // unsubscribe_event_handler. Store it here before removing // items from m_hash_table and m_map. abstract_message_sink_t & sink = it->second.m_message_sink.get(); do { m_hash_table.erase( &(it->first) ); m_map.erase( it++ ); } while( need_erase() ); mbox_ref->unsubscribe_event_handler( type_index, sink ); } } void storage_t::drop_all_subscriptions() noexcept { destroy_all_subscriptions(); } const event_handler_data_t * storage_t::find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept { key_t k( mbox_id, msg_type, current_state ); auto it = m_hash_table.find( &k ); if( it != m_hash_table.end() ) return &(it->second); else return nullptr; } void storage_t::debug_dump( std::ostream & to ) const { for( const auto & v : m_map ) to << "{" << v.first.m_mbox_id << ", " << v.first.m_msg_type.name() << ", " << v.first.m_state->query_name() << "}" << std::endl; } void storage_t::destroy_all_subscriptions() noexcept { { const map_t::value_type * previous = nullptr; for( auto & i : m_map ) { // Optimisation: for several consequtive keys with // the same (mbox, msg_type) pair it is necessary to // call unsubscribe_event_handler only once. if( !previous || !previous->first.is_same_mbox_msg_pair( i.first ) ) { i.second.m_mbox->unsubscribe_event_handler( i.first.m_msg_type, i.second.m_message_sink.get() ); } previous = &i; } } drop_content(); } void storage_t::drop_content() noexcept { m_hash_table.clear(); m_map.clear(); } subscription_storage_common::subscr_info_vector_t storage_t::query_content() const { using namespace std; using namespace subscription_storage_common; subscr_info_vector_t events; if( !m_hash_table.empty() ) { events.reserve( m_hash_table.size() ); transform( begin(m_hash_table), end(m_hash_table), back_inserter(events), [this]( const hash_table_t::value_type & i ) { auto map_item = m_map.find( *(i.first) ); return subscr_info_t { map_item->second.m_mbox, map_item->first.m_msg_type, map_item->second.m_message_sink.get(), *(map_item->first.m_state), i.second.m_method, i.second.m_thread_safety, i.second.m_kind }; } ); } return events; } void storage_t::setup_content( subscription_storage_common::subscr_info_vector_t && info ) { using namespace std; using namespace subscription_storage_common; map_t fresh_map; hash_table_t fresh_table; for_each( begin(info), end(info), [&]( const subscr_info_t & i ) { key_t k{ i.m_mbox->id(), i.m_msg_type, *(i.m_state) }; auto ins_result = fresh_map.emplace( k, mbox_with_sink_info_t{ i.m_mbox, i.m_message_sink } ); fresh_table.emplace( &(ins_result.first->first), i.m_handler ); } ); swap( m_map, fresh_map ); swap( m_hash_table, fresh_table ); } std::size_t storage_t::query_subscriptions_count() const { return m_hash_table.size(); } } /* namespace hash_table_subscr_storage */ } /* namespace impl */ SO_5_FUNC subscription_storage_factory_t hash_table_based_subscription_storage_factory() { return []() { return impl::subscription_storage_unique_ptr_t( new impl::hash_table_subscr_storage::storage_t() ); }; } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/subscr_storage_map_based.cpp000066400000000000000000000253011523065210100241610ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.3 * * \file * \brief A map-based storage for agent's subscriptions information. */ #include #include #include #include #include namespace so_5 { namespace impl { /*! * \since * v.5.5.3 * * \brief A map-based storage for agent's subscriptions information. */ namespace map_based_subscr_storage { /*! * \since * v.5.5.3 * * \brief A map-based storage for agent's subscriptions information. * * This is very simple implementation of subscription storage which * uses std::map for storing information. */ class storage_t : public subscription_storage_t { public : storage_t(); ~storage_t() override; void create_event_subscription( const mbox_t & mbox_ref, const std::type_index & type_index, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) override; void drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) noexcept override; void drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) noexcept override; void drop_all_subscriptions() noexcept override; const event_handler_data_t * find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept override; void debug_dump( std::ostream & to ) const override; void drop_content() noexcept override; subscription_storage_common::subscr_info_vector_t query_content() const override; void setup_content( subscription_storage_common::subscr_info_vector_t && info ) override; std::size_t query_subscriptions_count() const override; private : //! Type of key in subscription's map. struct key_t { mbox_id_t m_mbox_id; std::type_index m_msg_type; const state_t * m_state; key_t( mbox_id_t mbox_id, std::type_index msg_type, const state_t * state ) : m_mbox_id( mbox_id ) , m_msg_type( std::move( msg_type ) ) , m_state( state ) {} bool operator<( const key_t & o ) const { if( m_mbox_id < o.m_mbox_id ) return true; else if( m_mbox_id == o.m_mbox_id ) { if( m_msg_type < o.m_msg_type ) return true; else if( m_msg_type == o.m_msg_type ) return m_state < o.m_state; } return false; } }; //! Type of value for subscription map's item. struct value_t { //! Reference to mbox. /*! * Reference must be stored because we must have * access to mbox during destroyment of all * subscriptions in destructor. */ const mbox_t m_mbox; /*! * Message sink used for that mbox. */ const std::reference_wrapper< abstract_message_sink_t > m_message_sink; /*! * Event handler for that subscription. */ const event_handler_data_t m_handler; }; //! Type of subscriptions map. using subscr_map_t = std::map< key_t, value_t >; //! Subscription information. subscr_map_t m_events; void destroy_all_subscriptions() noexcept; }; namespace { template< class C > [[nodiscard]] auto find( C & c, const mbox_id_t & mbox_id, const std::type_index & msg_type, const state_t & target_state ) -> decltype( c.begin() ) { return c.find( typename C::key_type { mbox_id, msg_type, &target_state } ); } struct is_same_mbox_msg { const mbox_id_t m_id; const std::type_index & m_type; template< class K > [[nodiscard]] bool operator()( const K & k ) const { return m_id == k.m_mbox_id && m_type == k.m_msg_type; } }; template< class M, class IT > [[nodiscard]] bool is_known_mbox_msg_pair( M & s, IT it ) { const is_same_mbox_msg predicate{ it->first.m_mbox_id, it->first.m_msg_type }; if( it != s.begin() ) { IT prev = std::prev( it ); if( predicate( prev->first ) ) return true; } IT next = std::next( it ); if( next != s.end() ) return predicate( next->first ); return false; } } /* namespace anonymous */ storage_t::storage_t() {} storage_t::~storage_t() { destroy_all_subscriptions(); } void storage_t::create_event_subscription( const mbox_t & mbox, const std::type_index & msg_type, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) { using namespace std; using namespace subscription_storage_common; const auto mbox_id = mbox->id(); // Check that this subscription is new. auto existed_position = find( m_events, mbox_id, msg_type, target_state ); if( existed_position != m_events.end() ) SO_5_THROW_EXCEPTION( rc_evt_handler_already_provided, "agent is already subscribed to message, " + make_subscription_description( mbox, msg_type, target_state ) ); // Just add subscription to the end. auto ins_result = m_events.emplace( key_t { mbox_id, msg_type, &target_state }, value_t { mbox, std::ref( message_sink ), event_handler_data_t { method, thread_safety, handler_kind } } ); // Note: since v.5.5.9 mbox subscription is initiated even if // it is MPSC mboxes. It is important for the case of message // delivery tracing. // If there was no subscription for that mbox // then new subscription in the mbox must be created. if( !is_known_mbox_msg_pair( m_events, ins_result.first ) ) { so_5::details::do_with_rollback_on_exception( [&] { mbox->subscribe_event_handler( msg_type, message_sink ); }, [&] { m_events.erase( ins_result.first ); } ); } } void storage_t::drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) noexcept { auto existed_position = find( m_events, mbox->id(), msg_type, target_state ); if( existed_position != m_events.end() ) { // Note v.5.5.9 unsubscribe_event_handler is called for // mbox even if it is MPSC mbox. It is necessary for the case // of message delivery tracing. // We must destroy mbox subscription in case if the agent has no // more subscriptions for that mbox+msg_type pair. // Detect this while existed_position is not invalidated. bool must_unsubscribe_mbox = !is_known_mbox_msg_pair( m_events, existed_position ); // Store a reference to message_sink for a case if // unsubscribe_event_handler has to be called. abstract_message_sink_t & sink = existed_position->second.m_message_sink.get(); m_events.erase( existed_position ); if( must_unsubscribe_mbox ) { mbox->unsubscribe_event_handler( msg_type, sink ); } } } void storage_t::drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) noexcept { const is_same_mbox_msg is_same{ mbox->id(), msg_type }; auto lower_bound = m_events.lower_bound( key_t{ mbox->id(), msg_type, nullptr } ); auto need_erase = [&] { return lower_bound != std::end(m_events) && is_same( lower_bound->first ); }; const bool events_found = need_erase(); if( events_found ) { // Store a reference to message_sink because it's required // for unsubscribe_event_handler call. abstract_message_sink_t & sink = lower_bound->second.m_message_sink.get(); // Erase all subscribed event handlers. do { m_events.erase( lower_bound++ ); } while( need_erase() ); // Note: since v.5.5.9 mbox unsubscription is initiated even if // it is MPSC mboxes. It is important for the case of message // delivery tracing. mbox->unsubscribe_event_handler( msg_type, sink ); } } void storage_t::drop_all_subscriptions() noexcept { destroy_all_subscriptions(); } const event_handler_data_t * storage_t::find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept { auto it = find( m_events, mbox_id, msg_type, current_state ); if( it != std::end( m_events ) ) return &(it->second.m_handler); else return nullptr; } void storage_t::debug_dump( std::ostream & to ) const { for( const auto & e : m_events ) to << "{" << e.first.m_mbox_id << ", " << e.first.m_msg_type.name() << ", " << e.first.m_state->query_name() << "}" << std::endl; } void storage_t::destroy_all_subscriptions() noexcept { using namespace std; auto it = begin( m_events ); while( it != end( m_events ) ) { auto cur = it++; if( it == end( m_events ) || !is_same_mbox_msg{ cur->first.m_mbox_id, cur->first.m_msg_type }( it->first ) ) { cur->second.m_mbox->unsubscribe_event_handler( cur->first.m_msg_type, cur->second.m_message_sink.get() ); } m_events.erase( cur ); } } void storage_t::drop_content() noexcept { m_events.clear(); } subscription_storage_common::subscr_info_vector_t storage_t::query_content() const { using namespace std; using namespace subscription_storage_common; subscr_info_vector_t result; if( !m_events.empty() ) { result.reserve( m_events.size() ); transform( begin( m_events ), end( m_events ), back_inserter( result ), []( const subscr_map_t::value_type & e ) { return subscr_info_t( e.second.m_mbox, e.first.m_msg_type, e.second.m_message_sink.get(), *(e.first.m_state), e.second.m_handler.m_method, e.second.m_handler.m_thread_safety, e.second.m_handler.m_kind ); } ); } return result; } void storage_t::setup_content( subscription_storage_common::subscr_info_vector_t && info ) { using namespace std; using namespace subscription_storage_common; subscr_map_t events; transform( begin(info), end(info), inserter( events, events.begin() ), []( const subscr_info_t & i ) { return subscr_map_t::value_type { key_t { i.m_mbox->id(), i.m_msg_type, i.m_state }, value_t { i.m_mbox, i.m_message_sink, i.m_handler } }; } ); swap( m_events, events ); } std::size_t storage_t::query_subscriptions_count() const { return m_events.size(); } } /* namespace map_based_subscr_storage */ } /* namespace impl */ SO_5_FUNC subscription_storage_factory_t map_based_subscription_storage_factory() { return []() { return impl::subscription_storage_unique_ptr_t( new impl::map_based_subscr_storage::storage_t() ); }; } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/subscr_storage_vector_based.cpp000066400000000000000000000245421523065210100247140ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.3 * * \file * \brief A vector-based storage for agent's subscriptions information. */ #include #include #include #include #include namespace so_5 { namespace impl { /*! * \since * v.5.5.3 * * \brief A vector-based storage for agent's subscriptions information. */ namespace vector_based_subscr_storage { /*! * \since * v.5.5.3 * * \brief A vector-based storage for agent's subscriptions information. * * This is very simple implementation of subscription storage which * uses std::vector for storing information. * * All manipulation is performed by very simple linear search inside * that vector. For agents with few subscriptions this will be the most * efficient approach. */ class storage_t : public subscription_storage_t { public : storage_t( std::size_t initial_capacity ); ~storage_t() override; virtual void create_event_subscription( const mbox_t & mbox_ref, const std::type_index & type_index, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) override; virtual void drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) noexcept override; void drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) noexcept override; void drop_all_subscriptions() noexcept override; const event_handler_data_t * find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept override; void debug_dump( std::ostream & to ) const override; void drop_content() noexcept override; subscription_storage_common::subscr_info_vector_t query_content() const override; void setup_content( subscription_storage_common::subscr_info_vector_t && info ) override; std::size_t query_subscriptions_count() const override; private : using info_t = subscription_storage_common::subscr_info_t; using subscr_info_vector_t = subscription_storage_common::subscr_info_vector_t; //! A helper predicate for searching the same //! mbox and message type pairs. struct is_same_mbox_msg { const mbox_id_t m_id; const std::type_index & m_type; bool operator()( const info_t & info ) const { return m_id == info.m_mbox->id() && m_type == info.m_msg_type; } }; //! Subscription information. subscr_info_vector_t m_events; void destroy_all_subscriptions() noexcept; }; namespace { template< class Container > auto find( Container & c, const mbox_id_t & mbox_id, const std::type_index & msg_type, const state_t & target_state ) -> decltype( c.begin() ) { using namespace std; return find_if( begin( c ), end( c ), [&]( typename Container::value_type const & o ) { return ( o.m_mbox->id() == mbox_id && o.m_msg_type == msg_type && o.m_state == &target_state ); } ); } } /* namespace anonymous */ storage_t::storage_t( std::size_t initial_capacity ) { m_events.reserve( initial_capacity ); } storage_t::~storage_t() { destroy_all_subscriptions(); } void storage_t::create_event_subscription( const mbox_t & mbox, const std::type_index & msg_type, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) { using namespace std; using namespace subscription_storage_common; const auto mbox_id = mbox->id(); // Check that this subscription is new. bool has_subscriptions_from_that_mbox = false; for( auto it = m_events.begin(), it_end = m_events.end(); it != it_end; ++it ) { if( it->m_mbox->id() == mbox_id && it->m_msg_type == msg_type ) { has_subscriptions_from_that_mbox = true; if( it->m_state == std::addressof(target_state) ) SO_5_THROW_EXCEPTION( rc_evt_handler_already_provided, "agent is already subscribed to message, " + make_subscription_description( mbox, msg_type, target_state ) ); } } // If we're here then there is no such a subscription yet. // Just add subscription to the end. m_events.emplace_back( mbox, msg_type, message_sink, target_state, method, thread_safety, handler_kind ); // Note: since v.5.5.9 mbox subscription is initiated even if // it is MPSC mboxes. It is important for the case of message // delivery tracing. if( !has_subscriptions_from_that_mbox ) { // Mbox must create subscription. so_5::details::do_with_rollback_on_exception( [&] { mbox->subscribe_event_handler( msg_type, message_sink ); }, [&] { m_events.pop_back(); } ); } } void storage_t::drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) noexcept { using namespace std; const auto mbox_id = mbox->id(); // Try to find a subscription. And calculate number of subscriptions // from the same mbox for same msg_type. std::size_t number_of_subscriptions{}; subscr_info_vector_t::iterator it = m_events.begin(); subscr_info_vector_t::iterator it_end = m_events.end(); for(; it != it_end; ++it ) { if( it->m_mbox->id() == mbox_id && it->m_msg_type == msg_type ) { ++number_of_subscriptions; if( it->m_state == std::addressof(target_state) ) // Subscription found. break; } } if( it != it_end ) { // This value may be necessary for unsubscription. abstract_message_sink_t & message_sink = it->m_message_sink.get(); // Item is no more needed. it = m_events.erase( it ); --number_of_subscriptions; // Note v.5.5.9 unsubscribe_event_handler is called for // mbox even if it is MPSC mbox. It is necessary for the case // of message delivery tracing. // We have to check presence of other subscriptions from this mbox // for the same msg_type. if( !number_of_subscriptions ) { // Maybe there are subscriptions in the right part of m_events? if( m_events.end() != std::find_if( it, m_events.end(), is_same_mbox_msg{ mbox_id, msg_type } ) ) number_of_subscriptions = 1; } // If there is no more subscriptions to that mbox then // the mbox must remove information about that agent. if( !number_of_subscriptions ) { // If we are here then there is no more references // to the mbox. And mbox must not hold reference // to the agent. mbox->unsubscribe_event_handler( msg_type, message_sink ); } } } void storage_t::drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) noexcept { using namespace std; const auto predicate = is_same_mbox_msg{ mbox->id(), msg_type }; if( auto it = find_if( begin( m_events ), end( m_events ), predicate ); it != end( m_events ) ) { // There are subscriptions to be removed. // Have to store message_sink reference because it has to // be passed to unsubscribe_event_handler. auto & message_sink = it->m_message_sink.get(); // Remove all items that match the predicate. m_events.erase( remove_if( it, end( m_events ), predicate ), end( m_events ) ); // Note: since v.5.5.9 mbox unsubscription is initiated even if // it is MPSC mboxes. It is important for the case of message // delivery tracing. mbox->unsubscribe_event_handler( msg_type, message_sink ); } } void storage_t::drop_all_subscriptions() noexcept { destroy_all_subscriptions(); } const event_handler_data_t * storage_t::find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept { auto it = find( m_events, mbox_id, msg_type, current_state ); if( it != std::end( m_events ) ) return &(it->m_handler); else return nullptr; } void storage_t::debug_dump( std::ostream & to ) const { for( const auto & e : m_events ) to << "{" << e.m_mbox->id() << ", " << e.m_msg_type.name() << ", " << e.m_state->query_name() << "}" << std::endl; } void storage_t::destroy_all_subscriptions() noexcept { if( m_events.empty() ) // Nothing to do at empty subscription list. return; using namespace std; // Step one. // // Sort all event_info to have all subscriptions for the // same (mbox, msg_type) one after another. sort( begin( m_events ), end( m_events ), []( const auto & a, const auto & b ) { return a.m_mbox->id() < b.m_mbox->id() || ( a.m_mbox->id() == b.m_mbox->id() && a.m_msg_type < b.m_msg_type ); } ); // Step two. // // Destroy all subscriptions for unique (mbox, msg_type). const auto total_items = m_events.size(); for( std::size_t i = 0u; i < total_items; ) { auto & current_info = m_events[ i ]; current_info.m_mbox->unsubscribe_event_handler( current_info.m_msg_type, current_info.m_message_sink ); // We should skip all consequtive items with the same // (mbox, msg_type) pairs. std::size_t j = 1u; for( ; (i+j) < total_items; ++j ) { const auto & next_info = m_events[ i+j ]; if( current_info.m_mbox->id() != next_info.m_mbox->id() || current_info.m_msg_type != next_info.m_msg_type ) break; } i += j; } // Third step. // // Cleanup subscription vector. drop_content(); } void storage_t::drop_content() noexcept { m_events.clear(); } subscription_storage_common::subscr_info_vector_t storage_t::query_content() const { return m_events; } void storage_t::setup_content( subscription_storage_common::subscr_info_vector_t && info ) { m_events = std::move( info ); } std::size_t storage_t::query_subscriptions_count() const { return m_events.size(); } } /* namespace vector_based_subscr_storage */ } /* namespace impl */ SO_5_FUNC subscription_storage_factory_t vector_based_subscription_storage_factory( std::size_t initial_capacity ) { return [initial_capacity]() { return impl::subscription_storage_unique_ptr_t( new impl::vector_based_subscr_storage::storage_t( initial_capacity ) ); }; } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/subscription_storage_iface.cpp000066400000000000000000000004621523065210100245410ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.3 * * \file * \brief An interface of subscription storage. */ #include namespace so_5 { namespace impl { subscription_storage_t::subscription_storage_t() {} } /* namespace impl */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/impl/subscription_storage_iface.hpp000066400000000000000000000126511523065210100245510ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.3 * * \file * \brief An interface of subscription storage. */ #if !defined( SO_5_IMPL_SUBSCRIPTION_STORAGE_IFACE_HPP ) #define SO_5_IMPL_SUBSCRIPTION_STORAGE_IFACE_HPP #include #include #include #include #include #include #include #include #include namespace so_5 { namespace impl { // // event_handler_data_t // /*! * \since * v.5.4.0 * * \brief Information about event_handler and its properties. */ struct event_handler_data_t { //! Method for handling event. event_handler_method_t m_method; //! Is event handler thread safe or not. thread_safety_t m_thread_safety; //! Kind of this event handler. /*! * \since * v.5.7.0 */ event_handler_kind_t m_kind; event_handler_data_t( event_handler_method_t method, thread_safety_t thread_safety, event_handler_kind_t kind ) : m_method( std::move( method ) ) , m_thread_safety( thread_safety ) , m_kind( kind ) {} }; /*! * \since * v.5.5.3 * * \brief Common stuff for various subscription storage implementations. */ namespace subscription_storage_common { /*! * \since * v.5.5.3 * * \brief An information about one subscription. */ struct subscr_info_t { //! Reference to mbox. /*! * Reference must be stored because we must have * access to mbox during destroyment of all * subscriptions in destructor. */ mbox_t m_mbox; std::type_index m_msg_type; //! Message sink used for subscription. std::reference_wrapper< abstract_message_sink_t > m_message_sink; const state_t * m_state; event_handler_data_t m_handler; subscr_info_t( mbox_t mbox, std::type_index msg_type, abstract_message_sink_t & message_sink, const state_t & state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) : m_mbox( std::move( mbox ) ) , m_msg_type( std::move( msg_type ) ) , m_message_sink( message_sink ) , m_state( &state ) , m_handler( method, thread_safety, handler_kind ) {} }; /*! * \since * v.5.5.3 * * \brief Type of vector with subscription information. */ using subscr_info_vector_t = std::vector< subscr_info_t >; /*! * \since * v.5.5.3 * * \brief A helper function for creating subscription description. */ inline std::string make_subscription_description( const mbox_t & mbox_ref, std::type_index msg_type, const state_t & state ) { std::ostringstream s; s << "(mbox:'" << mbox_ref->query_name() << "', msg_type:'" << msg_type.name() << "', state:'" << state.query_name() << "')"; return s.str(); } } /* namespace subscription_storage_common */ /*! * \since * v.5.5.3 * * \brief An interface of subscription storage * * Prior to v.5.5.3 there where only one subscription_storage implementation. * But this implementation was not efficient for all cases. * * Sometimes an agent has very few subscriptions and efficient * implementation for that case can be based on std::vector. * Sometimes an agent has very many subscriptions and efficient * implementation can require std::map or std::unordered_map. * * The purpose of this interface is hiding details of concrete * subscription_storage implementation. */ class subscription_storage_t { public : subscription_storage_t(); virtual ~subscription_storage_t() noexcept = default; virtual void create_event_subscription( const mbox_t & mbox, const std::type_index & msg_type, abstract_message_sink_t & message_sink, const state_t & target_state, const event_handler_method_t & method, thread_safety_t thread_safety, event_handler_kind_t handler_kind ) = 0; virtual void drop_subscription( const mbox_t & mbox, const std::type_index & msg_type, const state_t & target_state ) noexcept = 0; virtual void drop_subscription_for_all_states( const mbox_t & mbox, const std::type_index & msg_type ) noexcept = 0; /*! * \brief Drop all subscriptions. * * All subscribed event-handlers have to be unsubscribed. * The object should remain in the valid state and creation * of new subscriptions have to be allowed. * * This method is intended to be used when an agents what * to erase all subscriptions at once. * * \since v.5.7.3 */ virtual void drop_all_subscriptions() noexcept = 0; virtual const event_handler_data_t * find_handler( mbox_id_t mbox_id, const std::type_index & msg_type, const state_t & current_state ) const noexcept = 0; virtual void debug_dump( std::ostream & to ) const = 0; //! Drop all content. /*! * All information about subscription must be erased, * but without real unsubscription. * * This method will be called after successful copy of * subscription information to another storage. */ virtual void drop_content() noexcept = 0; //! Get content for copying subscription information //! to another storage object. virtual subscription_storage_common::subscr_info_vector_t query_content() const = 0; //! Setup content from information from another storage object. virtual void setup_content( subscription_storage_common::subscr_info_vector_t && info ) = 0; //! Count of subscriptions in the storage. virtual std::size_t query_subscriptions_count() const = 0; }; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/impl/thread_join_stuff.hpp000066400000000000000000000022221523065210100226400ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Various stuff related to worker thread's join. * * \since * v.5.6.0 */ #if !defined( SO_5_IMPL_THREAD_JOIN_STUFF_HPP ) #define SO_5_IMPL_THREAD_JOIN_STUFF_HPP #include #include #include namespace so_5 { namespace impl { // // ensure_join_from_different_thread // /*! * \brief Ensures that join will be called from different thread. * * It's a mistake if thread::join() is called from the context of * thread to be joined. Unfortunately, different implementations * of stdlib work differently. There can be an exception. Or there * can be deadlock, or there can be memory damage. * * It is better to check for that error manually and throw an exception * if the error is detected. * * \since * v.5.6.0 */ inline void ensure_join_from_different_thread( current_thread_id_t thread_to_be_joined ) { if( query_current_thread_id() == thread_to_be_joined ) SO_5_THROW_EXCEPTION( rc_unable_to_join_thread_by_itself, "worker thread attempts to join() itself" ); } } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/mbox.cpp000066400000000000000000000050701523065210100171460ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include #include namespace so_5 { // // abstract_message_box_t // // // wrap_to_msink // namespace as_msink_impl { // // mbox_as_sink_t // /*! * \brief Implementation of abstract_message_sink for a case when * the destination is a mbox. * * \note * Because mbox has no priority, but message_sink should have it, the * priority has to be specified in the constructor. */ class mbox_as_sink_t final : public abstract_message_sink_t { //! The destination for messages. const mbox_t m_mbox; //! The priority for the sink. const priority_t m_sink_priority; public: //! Initializing constructor. mbox_as_sink_t( mbox_t mbox, priority_t priority ) : m_mbox{ std::move(mbox) } , m_sink_priority{ priority } {} environment_t & environment() const noexcept override { return m_mbox->environment(); } priority_t sink_priority() const noexcept override { return m_sink_priority; } /*! * Actual implementation of so_5::abstract_message_sink_t::push_event(). * * If \a redirection_deep exceeds so_5::max_redirection_deep then message is * ignored (with logging). Otherwise the message is pushed to m_mbox. */ void push_event( mbox_id_t /*mbox_id*/, message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep, const message_limit::impl::action_msg_tracer_t * /*tracer*/ ) override { if( redirection_deep >= max_redirection_deep ) { // NOTE: this fragment can throw but it isn't a problem // because transform_reaction() is called during message // delivery process and exceptions are expected in that // process. SO_5_LOG_ERROR( this->environment().error_logger(), logger ) logger << "maximum message redirection deep exceeded on " "mbox_as_sink::push_event; message will be ignored;" << " msg_type: " << msg_type.name() << ", target_mbox: " << m_mbox->query_name(); } else { m_mbox->do_deliver_message( delivery_mode, msg_type, message, // redirection_deep has to be increased. redirection_deep + 1u ); } } }; } /* namespace as_msink_impl */ msink_t wrap_to_msink( const mbox_t & mbox, so_5::priority_t sink_priority ) { return msink_t{ std::make_unique< simple_sink_owner_t< as_msink_impl::mbox_as_sink_t > >( mbox, sink_priority ) }; } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/mbox.hpp000066400000000000000000000254011523065210100171530ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Mbox definition. */ #if !defined( SO_5_MBOX_HPP ) #define SO_5_MBOX_HPP #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { /*! * \since * v.5.5.9 * * \brief Result of checking delivery posibility. */ enum class delivery_possibility_t { must_be_delivered, no_subscription, disabled_by_delivery_filter, /*! * The actual message is hidden by an envelope. * * \since * v.5.5.23 */ hidden_by_envelope }; // // delivery_filter_t // /*! * \since * v.5.5.5 * * \brief An interface of delivery filter object. */ class SO_5_TYPE delivery_filter_t { // Note: clang-3.9 requires this on Windows platform. delivery_filter_t( const delivery_filter_t & ) = delete; delivery_filter_t( delivery_filter_t && ) = delete; delivery_filter_t & operator=( const delivery_filter_t & ) = delete; delivery_filter_t & operator=( delivery_filter_t && ) = delete; public : delivery_filter_t() = default; virtual ~delivery_filter_t() noexcept = default; //! Checker for a message instance. /*! * \retval true message must be delivered to a receiver. * \retval false message must be descarded. */ [[nodiscard]] virtual bool check( //! Receiver of the message. const abstract_message_sink_t & receiver, //! Message itself. message_t & msg ) const noexcept = 0; }; // // delivery_filter_unique_ptr_t // /*! * \since * v.5.5.5 * * \brief An alias of unique_ptr for delivery_filter. */ using delivery_filter_unique_ptr_t = std::unique_ptr< delivery_filter_t >; namespace low_level_api { /*! * \brief Helper function that throws if a pointer to delivery_filter is null. * * \since v.5.8.0 */ inline void ensure_not_null( const delivery_filter_unique_ptr_t & ptr ) { if( !ptr ) SO_5_THROW_EXCEPTION( rc_nullptr_as_delivery_filter_pointer, "a pointer to delivery_filter can't be nullptr" ); } /*! * \brief An implementation of delivery filter represented by lambda-function * like object. * * NOTE. This template is moved into low_level_api namespace in v.5.8.0. * * \tparam Lambda type of lambda-function or functional object. * * \since v.5.5.5, v.5.8.0 */ template< typename Lambda, typename Message > class lambda_as_filter_t : public delivery_filter_t { Lambda m_filter; public : lambda_as_filter_t( Lambda && filter ) : m_filter( std::forward< Lambda >( filter ) ) {} bool check( const abstract_message_sink_t & /*receiver*/, message_t & msg ) const noexcept override { // Delivery filter should receive a const reference. // But message_payload_type::payload_reference returns non-const // reference. const Message & payload_ref = message_payload_type< Message >::payload_reference( msg ); return m_filter( payload_ref ); } }; } /* namespace low_level_api */ // // mbox_type_t // /*! * \since * v.5.5.3 * * \brief Type of the message box. * * \note * This type is no more marked as deprecated. */ enum class mbox_type_t { //! Mbox is Multi-Producer and Multi-Consumer. //! Anyone can send messages to it, there can be many subscribers. multi_producer_multi_consumer, //! Mbox is Multi-Producer and Single-Consumer. //! Anyone can send messages to it, there can be only one subscriber. multi_producer_single_consumer }; // // abstract_message_box_t // //! Mail box class. /*! * The class serves as an interface for sending and receiving messages. * * All mboxes can be created via the SObjectizer Environment. References to * mboxes are stored and manipulated by so_5::mbox_t objects. * * \see environment_t::schedule_timer(), environment_t::single_timer(). */ class SO_5_TYPE abstract_message_box_t : protected atomic_refcounted_t { friend class intrusive_ptr_t< abstract_message_box_t >; /*! * It is necessary for for access to do_deliver_message_from_timer(). * * \note * Added in v.5.5.18. */ friend class so_5::impl::mbox_iface_for_timers_t; abstract_message_box_t( const abstract_message_box_t & ) = delete; abstract_message_box_t( abstract_message_box_t && ) = delete; abstract_message_box_t & operator=( const abstract_message_box_t & ) = delete; abstract_message_box_t & operator=( abstract_message_box_t && ) = delete; public: abstract_message_box_t() = default; virtual ~abstract_message_box_t() noexcept = default; /*! * \since * v.5.4.0 * * \brief Unique ID of this mbox. */ [[nodiscard]] virtual mbox_id_t id() const = 0; //! Add the message handler. virtual void subscribe_event_handler( //! Message type. const std::type_index & type_index, //! Subscriber. abstract_message_sink_t & subscriber ) = 0; //! Remove all message handlers. virtual void unsubscribe_event_handler( //! Message type. const std::type_index & type_index, //! Subscriber. abstract_message_sink_t & subscriber ) noexcept = 0; //! Get the mbox name. [[nodiscard]] virtual std::string query_name() const = 0; /*! * \since * v.5.5.3 * * \brief Get the type of message box. * * \note This method is primarily intended for internal usage. * It is useful sometimes in subscription-related operations * because there is no need to do some actions for MPSC mboxes. */ [[nodiscard]] virtual mbox_type_t type() const = 0; /*! * \since * v.5.5.4 * * \brief Deliver message for all subscribers with respect to message * limits. * * A message delivery from timer thread is somewhat different from * an ordinary message delivery. Especially in the case when * target mbox is a message chain. If that message chain is * full and some kind of overflow reaction is specified (like waiting * for some time or throwing an exception) then it can lead to * undesired behaviour of the whole application. To take care about * these cases a new method is introduced. * * \note * Since v.5.6.0 this method is used for deliverance of ordinary * messages/signals and for deliverance of enveloped messages. */ virtual void do_deliver_message( //! Can the delivery blocks the current thread? message_delivery_mode_t delivery_mode, //! Type of the message to deliver. const std::type_index & msg_type, //! A message instance to be delivered. const message_ref_t & message, //! Current deep of overlimit reaction recursion. unsigned int redirection_deep ) = 0; /*! * \name Methods for working with delivery filters. * \{ */ /*! * \since * v.5.5.5 * * \brief Set a delivery filter for message type and subscriber. * * \note If there already is a delivery filter for that * (msg_type,subscriber) pair then old delivery filter will * be replaced by new one. */ virtual void set_delivery_filter( //! Message type to be filtered. const std::type_index & msg_type, //! Filter to be set. //! A caller must guaranted the validity of this reference. const delivery_filter_t & filter, //! A subscriber for the message. abstract_message_sink_t & subscriber ) = 0; /*! * \since * v.5.5.5 * * \brief Removes delivery filter for message type and subscriber. */ virtual void drop_delivery_filter( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) noexcept = 0; /*! * \} */ //! SObjectizer Environment for which the mbox is created. /*! * \since * v.5.6.0 */ [[nodiscard]] virtual so_5::environment_t & environment() const noexcept = 0; }; // // wrap_to_msink // /*! * \brief Helper for wrapping an existing mbox into message_sink. * * Usage example: * \code * const so_5::mbox_t source = ...; * const so_5::mbox_t dest = ...; * * so_5::single_sink_binding_t source_to_dest_binding; * source_to_dest_binding.bind( * // Source mbox has to be specified as is. * source, * // The destination has to be wrapped into a msink. * so_5::wrap_to_msink(dest)); * \endcode * * \attention * The \a mbox is expected to be not-null. */ [[nodiscard]] msink_t SO_5_FUNC wrap_to_msink( const mbox_t & mbox, priority_t sink_priority = prio::p0 ); namespace low_level_api { //! Deliver message. /*! * Mbox takes care about destroying a message object. * * \attention * This function ensures that Message is a classical message * with an actual data (e.g. \a msg shouldn't be nullptr). * * \note * This function is a part of low-level SObjectizer's interface. * Because of that this function can be removed or changed in some * future version without prior notice. * * \since * v.5.6.0 */ template< class Message > void deliver_message( //! Can the delivery blocks the current thread? message_delivery_mode_t delivery_mode, //! Destination for message. abstract_message_box_t & target, //! Subscription type for that message. std::type_index subscription_type, //! Message data. std::unique_ptr< Message > msg ) { ensure_classical_message< Message >(); ensure_message_with_actual_data( msg.get() ); target.do_deliver_message( delivery_mode, std::move(subscription_type), message_ref_t{ msg.release() }, 1u ); } //! Deliver message. /*! * This function is necessary for cases when message object * is already present as message_ref_t. * * \note * This function is a part of low-level SObjectizer's interface. * Because of that this function can be removed or changed in some * future version without prior notice. * * \since * v.5.6.0 */ inline void deliver_message( //! Can the delivery blocks the current thread? message_delivery_mode_t delivery_mode, //! Destination for message. abstract_message_box_t & target, //! Subscription type for that message. std::type_index subscription_type, //! Message data. message_ref_t msg ) { target.do_deliver_message( delivery_mode, std::move(subscription_type), std::move(msg), 1u ); } //! Deliver signal. /*! * \attention * This function ensures that Message is a type of a signal. * * \note * This function is a part of low-level SObjectizer's interface. * Because of that this function can be removed or changed in some * future version without prior notice. * * \since * v.5.6.0 */ template< class Message > void deliver_signal( //! Can the delivery blocks the current thread? message_delivery_mode_t delivery_mode, //! Destination for signal. abstract_message_box_t & target ) { ensure_signal< Message >(); target.do_deliver_message( delivery_mode, message_payload_type< Message >::subscription_type_index(), message_ref_t(), 1u ); } } /* namespace low_level_api */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/mbox_fwd.hpp000066400000000000000000000010171523065210100200100ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \since v.5.5.1 \brief A forward declaration of mbox_t. */ #if !defined( SO_5_MBOX_FWD_HPP ) #define SO_5_MBOX_FWD_HPP #include namespace so_5 { class abstract_message_box_t; // // mbox_t // //! Smart reference for the abstract_message_box. /*! * \note Defined as typedef since v.5.2.0 */ using mbox_t = intrusive_ptr_t< abstract_message_box_t >; namespace impl { class mbox_iface_for_timers_t; } /* namespace impl */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/mbox_namespace_name.hpp000066400000000000000000000023401523065210100221640ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A mbox_namespace_name_t class definition. \since v.5.8.0 */ #if !defined( SO_5_MBOX_NAMESPACE_NAME_HPP ) #define SO_5_MBOX_NAMESPACE_NAME_HPP #include #include #include namespace so_5 { // // mbox_namespace_name_t // //! A class for the name of mbox_namespace. /*! * Checks value in the constructor. Throws an exception if the name is empty. * * \note * This class can be seen as a strong typedef for std::string_view. * It doesn't create a copy of the name, just holds a "view" to the name. * * \since v.5.8.0 */ class mbox_namespace_name_t { //! The name. std::string_view m_name; public: /*! * There is no way to create an empty name. */ mbox_namespace_name_t() = delete; //! Constructor with check for the empty value. explicit mbox_namespace_name_t( std::string_view name ) : m_name( std::move(name) ) { if( m_name.empty() ) SO_5_THROW_EXCEPTION( rc_empty_name, "empty mbox_namespace name" ); } //! Get the value. [[nodiscard]] std::string_view query_name() const noexcept( noexcept( std::string_view{m_name} ) ) { return m_name; } }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/mchain.cpp000066400000000000000000000004521523065210100174370ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Public part of message chain related stuff. * * \since v.5.5.13 */ #include namespace so_5 { // // abstract_message_chain_t // mbox_t abstract_message_chain_t::as_mbox() { return mbox_t{ this }; } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/mchain.hpp000066400000000000000000001523371523065210100174560ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Public part of message chain related stuff. * * \since v.5.5.13 */ #if !defined( SO_5_MCHAIN_HPP ) #define SO_5_MCHAIN_HPP #include #include #include #include #include #include #include #include namespace so_5 { namespace mchain_props { /*! * \brief An alias for type for repesenting timeout values. * * \since v.5.5.13 */ using duration_t = std::chrono::high_resolution_clock::duration; namespace details { // // no_wait_special_timevalue // /*! * \brief Special value of %duration to indicate 'no_wait' case. * * \since v.5.5.13 */ inline duration_t no_wait_special_timevalue() { return duration_t::zero(); } // // infinite_wait_special_timevalue // /*! * \brief Special value of %duration to indicate 'infinite_wait' case. * * \since v.5.5.13 */ inline duration_t infinite_wait_special_timevalue() { return duration_t::max(); } // // is_no_wait_timevalue // /*! * \brief Is time value means 'no_wait'? * * \since v.5.5.13 */ inline bool is_no_wait_timevalue( duration_t v ) { return v == no_wait_special_timevalue(); } // // is_infinite_wait_timevalue // /*! * \brief Is time value means 'infinite_wait'? * * \since v.5.5.13 */ inline bool is_infinite_wait_timevalue( duration_t v ) { return v == infinite_wait_special_timevalue(); } // // actual_timeout // /*! * \brief Helper function for detection of actual value for waiting timeout. * * \note This helper implements convention that infinite waiting is * represented as duration_t::max() value. * * \since v.5.5.13 */ inline duration_t actual_timeout( infinite_wait_indication ) { return infinite_wait_special_timevalue(); } /*! * \brief Helper function for detection of actual value for waiting timeout. * * \note This helper implements convention that no waiting is * represented as duration_t::zero() value. * * \since v.5.5.13 */ inline duration_t actual_timeout( no_wait_indication ) { return no_wait_special_timevalue(); } /*! * \brief Helper function for detection of actual value for waiting timeout. * * \since v.5.5.13 */ template< typename V > duration_t actual_timeout( V value ) { return duration_t( value ); } } /* namespace details */ // // demand_t // /*! * \brief Description of one demand in message chain. * * \since v.5.5.13 */ struct demand_t { //! Type of the message. std::type_index m_msg_type; //! Event incident. so_5::message_ref_t m_message_ref; //! Default constructor. demand_t() : m_msg_type( typeid(void) ) {} //! Initializing constructor. demand_t( std::type_index msg_type, so_5::message_ref_t message_ref ) : m_msg_type{ std::move(msg_type) } , m_message_ref{ std::move(message_ref) } {} //! Swap operation. friend void swap( demand_t & a, demand_t & b ) noexcept { using std::swap; swap( a.m_msg_type, b.m_msg_type ); swap( a.m_message_ref, b.m_message_ref ); } }; // // memory_usage_t // /*! * \brief Memory allocation for storage for size-limited chains. * * \since v.5.5.13 */ enum class memory_usage_t { //! Storage can be allocated and deallocated dynamically. dynamic, //! Storage must be preallocated once and doesn't change after that. preallocated }; // // overflow_reaction_t // /*! * \brief What reaction must be performed on attempt to push new message to * the full message chain. * * \since v.5.5.13 */ enum class overflow_reaction_t { //! Application must be aborted. abort_app, //! An exception must be thrown. /*! * \note * Since v.5.5.18 this value leads to an exception only if * ordinary `send` is used for pushing message to overloaded * message chain. If there is an attempt to push * delayed or periodic message to overloaded message chain then * throw_exception reaction is replaced by drop_newest. It is becasue * the context of timer thread is a special contex. No exceptions * should be thrown on it. */ throw_exception, //! New message must be ignored and droped. drop_newest, //! Oldest message in chain must be removed. remove_oldest }; // // capacity_t // /*! * \brief Parameters for defining chain size. * * \since v.5.5.13 */ class capacity_t { //! Has chain unlimited size? bool m_unlimited = { true }; // NOTE: all other atributes have sence only if m_unlimited != true. //! Max size of the chain with limited size. std::size_t m_max_size; //! Type of the storage for size-limited chain. memory_usage_t m_memory; //! Type of reaction for chain overflow. overflow_reaction_t m_overflow_reaction; //! Timeout for waiting on full chain during 'message push' operation. /*! * \note Value 'zero' means that there must not be waiting on * full chain. */ duration_t m_overflow_timeout; //! Initializing constructor for size-limited message chain. capacity_t( std::size_t max_size, memory_usage_t memory_usage, overflow_reaction_t overflow_reaction, duration_t overflow_timeout ) : m_unlimited{ false } , m_max_size{ max_size } , m_memory{ memory_usage } , m_overflow_reaction{ overflow_reaction } , m_overflow_timeout( overflow_timeout ) {} public : //! Default constructor. /*! * Creates description for size-unlimited chain. */ capacity_t() {} //! Create capacity description for size-unlimited message chain. inline static capacity_t make_unlimited() { return capacity_t{}; } //! Create capacity description for size-limited message chain //! without waiting on full queue during 'push message' operation. inline static capacity_t make_limited_without_waiting( //! Max size of the chain. std::size_t max_size, //! Type of chain storage. memory_usage_t memory_usage, //! Reaction on chain overflow. overflow_reaction_t overflow_reaction ) { return capacity_t{ max_size, memory_usage, overflow_reaction, details::no_wait_special_timevalue() }; } //! Create capacity description for size-limited message chain //! with waiting on full queue during 'push message' operation. inline static capacity_t make_limited_with_waiting( //! Max size of the chain. std::size_t max_size, //! Type of chain storage. memory_usage_t memory_usage, //! Reaction on chain overflow. overflow_reaction_t overflow_reaction, //! Waiting time on full message chain. duration_t wait_timeout ) { return capacity_t{ max_size, memory_usage, overflow_reaction, wait_timeout }; } //! Is message chain have no size limit? bool unlimited() const { return m_unlimited; } //! Max size for size-limited chain. /*! * \attention Has sence only for size-limited chain. */ std::size_t max_size() const { return m_max_size; } //! Memory allocation type for size-limited chain. /*! * \attention Has sence only for size-limited chain. */ memory_usage_t memory_usage() const { return m_memory; } //! Overflow reaction for size-limited chain. /*! * \attention Has sence only for size-limited chain. */ overflow_reaction_t overflow_reaction() const { return m_overflow_reaction; } //! Is waiting timeout for overflow case defined? /*! * \attention Has sence only for size-limited chain. */ bool is_overflow_timeout_defined() const { return !details::is_no_wait_timevalue( m_overflow_timeout ); } //! Get the value of waiting timeout for overflow case. /*! * \attention Has sence only for size-limited chain. */ duration_t overflow_timeout() const { return m_overflow_timeout; } }; // // extraction_status_t // /*! * \brief Result of extraction of message from a message chain. * * \since v.5.5.13 */ enum class extraction_status_t { //! No available messages in the chain. no_messages, //! Message extracted successfully. msg_extracted, //! Message cannot be extracted because chain is closed. chain_closed }; // // push_status_t // /*! * \brief Result of attempt of pushing a message into a message chain. * * \since v.5.7.0 */ enum class push_status_t { //! Message wasn't stored. not_stored, //! Message stored into a message chain. stored, //! Message is not stored but the store operation is registered //! into a message chain. deffered, //! Message wasn't stored because chain is closed. chain_closed }; // // close_mode_t // /*! * \brief What to do with chain's content at close. * * \since v.5.5.13 */ enum class close_mode_t { //! All messages must be removed from chain. drop_content, //! All messages must be retained until they will be //! processed at receiver's side. retain_content }; // // not_empty_notification_func_t // /*! * \brief Type of functor for notifies about arrival of a message to * the empty chain. * * \attention This function must be noexcept. * * \since v.5.5.13 */ using not_empty_notification_func_t = std::function< void() >; // // empty_notification_func_t // /*! * \brief Type of functor to be called when a mchain becomes empty. * * \attention This function must be noexcept. * * \since v.5.8.5 */ using empty_notification_func_t = std::function< void() >; // // Forward declarations related to multi chain select operations. // class select_case_t; } /* namespace mchain_props */ // // abstract_message_chain_t // /*! * \brief An interface of message chain. * * \since v.5.5.13 */ class SO_5_TYPE abstract_message_chain_t : protected so_5::abstract_message_box_t { friend class intrusive_ptr_t< abstract_message_chain_t >; friend class mchain_props::select_case_t; abstract_message_chain_t( const abstract_message_chain_t & ) = delete; abstract_message_chain_t & operator=( const abstract_message_chain_t & ) = delete; protected : abstract_message_chain_t() = default; ~abstract_message_chain_t() noexcept override = default; public : using abstract_message_box_t::id; using abstract_message_box_t::environment; [[nodiscard]] virtual mchain_props::extraction_status_t extract( //! Destination for extracted messages. mchain_props::demand_t & dest, //! Max time to wait on empty queue. mchain_props::duration_t empty_queue_timeout ) = 0; //! Cast message chain to message box. [[nodiscard]] so_5::mbox_t as_mbox(); //! Is message chain empty? [[nodiscard]] virtual bool empty() const = 0; //! Count of messages in the chain. [[nodiscard]] virtual std::size_t size() const = 0; //! Close the chain. /*! * Since v.5.7.3 this is the recommended way of closing a mchain. * This method allows to specify how exceptions have to be treated: * \code * auto ch1 = so_5::create_mchain(env); * auto ch2 = so_5::create_mchain(env); * ... * // Enable exceptions during 'close' operation. * ch1->close( * so_5::exceptions_enabled, * so_5::mchain_props::close_mode_t::drop_content ); * * // Terminate the application if 'close' operation throws. * ch2->close( * so_5::terminate_if_throws, * so_5::mchain_props::close_mode_t::drop_content ); * \endcode * * This method was introduced because 'close' operation can throw but * it's not obvious enough. Because 'close' can throw additional care has * to be taken when 'close' is initiated in noexcept-contexts (like * destructor and, often, in catch-blocks). This new method allows * expressing intentions more clearly. For example: * \code * class some_class { * so_5::mchain_t ch_; * ... * public: * some_class(so_5::environment_t & env) * : ch_{so_5::create_mchain(env)} * {} * ~some_class() noexcept * { * // There is no sense to allow exceptions during close operation. * ch_->close( * so_5::terminate_if_throws, * so_5::mchain_props::close_mode_t::drop_content ); * } * ... * }; * ... * void some_function(so_5::environment_t & env) * { * auto ch = so_5::create_mchain(env); * ... * // Have to close ch. * // This is not noexcept-context, so we can allow exceptions. * ch_->close( * so_5::exceptions_enabled, * so_5::mchain_props::close_mode_t::retain_content ); * ... * } * \endcode * * \since v.5.7.3 */ template< typename Exceptions_Control > void close( //! Are exceptions enabled or should the application be //! terminated if 'close' throws. Exceptions_Control /*exceptions_control*/, //! What to do with chain's content. mchain_props::close_mode_t mode ) noexcept(noexcept( details::should_terminate_if_throws_t::value)) { //NOTE: call close() from previous versions until v.5.8.0 //will be released. this->actual_close( mode ); } protected : /*! * \brief An extraction attempt as a part of multi chain select. * * \attention * This method is a pure virtual since v.5.6.2. * * \note This method is intended to be used by select_case_t. * * \since v.5.5.16 */ [[nodiscard]] virtual mchain_props::extraction_status_t extract( //! Destination for extracted messages. mchain_props::demand_t & dest, //! Select case to be stored for notification if mchain is empty. mchain_props::select_case_t & select_case ) = 0; /*! * \brief An attempt to push a new message into the mchain. * * Unlike do_deliver_message() method the push() doesn't apply * the overload reaction if the mchain if full. The \a select_case * is stored to select_cases list instead. * * \note * This method is intended to be used by select_case_t. * * \since v.5.7.0 */ [[nodiscard]] virtual mchain_props::push_status_t push( //! Type of message/signal to be pushed. const std::type_index & msg_type, //! Message/signal to be pushed. const message_ref_t & message, //! Select case to be stored for notification if mchain is full. mchain_props::select_case_t & select_case ) = 0; /*! * \brief Removement of mchain from multi chain select. * * \attention * This method is a pure virtual and noexcept since v.5.6.2. * * \note This method is intended to be used by select_case_t. * * \since v.5.5.16 */ virtual void remove_from_select( //! Select case to be removed from notification queue. mchain_props::select_case_t & select_case ) noexcept = 0; //! Close the chain. /*! * \attention * This method can throw exception. * * \since v.5.7.3 */ virtual void actual_close( //! What to do with chain's content. mchain_props::close_mode_t mode ) = 0; }; // // mchain_t // /*! * \brief Short name for smart pointer to message chain. * * \since v.5.5.13 */ using mchain_t = intrusive_ptr_t< abstract_message_chain_t >; // // close_drop_content // /*! * \brief Helper function for closing a message chain with dropping * all its content. * * Usage example. \code so_5::mchain_t & ch = ...; ... // Some work with chain. close_drop_content( so_5::exceptions_enabled, ch ); // Or: ch->close( so_5::exceptions_enabled, so_5::mchain_props::close_mode_t::drop_content ); \endcode * * \note Because of ADL it can be used without specifying namespaces. * * \tparam Exceptions_Control it is expected to be so_5::exceptions_enabled_t or * so_5::terminate_if_throws_t. So the first parameter to close_drop_content * has to be so_5::exceptions_enabled or so_5::terminate_if_throws. * * \since v.5.7.3 */ template< typename Exceptions_Control > inline void close_drop_content( //! What to do with exceptions. //! It's expected to be so_5::exceptions_enabled or so_5::terminate_if_throws. Exceptions_Control exceptions_control, //! Chain to be closed. const mchain_t & ch ) noexcept(noexcept( details::should_terminate_if_throws_t::value)) { ch->close( exceptions_control, mchain_props::close_mode_t::drop_content ); } // // close_retain_content // /*! * \brief Helper function for closing a message chain with retaining * all its content. * * \note Because of ADL it can be used without specifying namespaces. * * \par Usage example. \code so_5::mchain_t & ch = ...; ... // Some work with chain. close_retain_content( so_5::exceptions_enabled, ch ); // Or: ch->close( so_5::exceptions_enabled, so_5::mchain_props::close_mode_t::retain_content ); \endcode * * \tparam Exceptions_Control it is expected to be so_5::exceptions_enabled_t or * so_5::terminate_if_throws_t. So the first parameter to close_drop_content * has to be so_5::exceptions_enabled or so_5::terminate_if_throws. * * \since v.5.7.3 */ template< typename Exceptions_Control > inline void close_retain_content( //! What to do with exceptions. //! It's expected to be so_5::exceptions_enabled or so_5::terminate_if_throws. Exceptions_Control exceptions_control, //! Chain to be closed. const mchain_t & ch ) noexcept(noexcept( details::should_terminate_if_throws_t::value)) { ch->close( exceptions_control, mchain_props::close_mode_t::retain_content ); } // // mchain_params_t // /*! * \since v.5.5.13 * * \brief Parameters for message chain. */ class mchain_params_t { //! Chain's capacity. mchain_props::capacity_t m_capacity; //! An optional notificator for 'not_empty' condition. mchain_props::not_empty_notification_func_t m_not_empty_notificator; //! An optional notificator for 'empty' condition. /*! * \since v.5.8.5 */ mchain_props::empty_notification_func_t m_empty_notificator; //! Is message delivery tracing disabled explicitly? bool m_msg_tracing_disabled = { false }; public : //! Initializing constructor. mchain_params_t( //! Chain's capacity and related params. mchain_props::capacity_t capacity ) : m_capacity{ capacity } {} //! Set chain's capacity and related params. mchain_params_t & capacity( mchain_props::capacity_t capacity ) { m_capacity = capacity; return *this; } //! Get chain's capacity and related params. const mchain_props::capacity_t & capacity() const { return m_capacity; } //! Set chain's notificator for 'not_empty' condition. /*! * This notificator will be called when a message is stored to * the empty chain and chain becomes not empty. */ mchain_params_t & not_empty_notificator( mchain_props::not_empty_notification_func_t notificator ) { m_not_empty_notificator = std::move(notificator); return *this; } //! Get chain's notificator for 'not_empty' condition. const mchain_props::not_empty_notification_func_t & not_empty_notificator() const { return m_not_empty_notificator; } //! Set chain's notificator for 'empty' condition. /*! * This notificator will be called when the mchain becomes * empty after extraction of the last message. * * \since v.5.8.5 */ mchain_params_t & empty_notificator( mchain_props::empty_notification_func_t notificator ) { m_empty_notificator = std::move(notificator); return *this; } //! Get chain's notificator for 'empty' condition. const mchain_props::empty_notification_func_t & empty_notificator() const { return m_empty_notificator; } //! Disable message delivery tracing explicitly. /*! * If this method called then message delivery tracing will * not be used for that mchain even if message delivery * tracing will be used for the whole SObjectizer Environment. */ mchain_params_t & disable_msg_tracing() { m_msg_tracing_disabled = true; return *this; } //! Is message delivery tracing disabled explicitly? bool msg_tracing_disabled() const { return m_msg_tracing_disabled; } }; /*! * \name Helper functions for creating parameters for %mchain. * \{ */ /*! * \brief Create parameters for size-unlimited %mchain. * * \par Usage example: \code so_5::environment_t & env = ...; auto chain = env.create_mchain( so_5::make_unlimited_mchain_params() ); \endcode * * \since v.5.5.13 */ inline mchain_params_t make_unlimited_mchain_params() { return mchain_params_t{ mchain_props::capacity_t::make_unlimited() }; } /*! * \brief Create parameters for size-limited %mchain without waiting on overflow. * * \par Usage example: \code so_5::environment_t & env = ...; auto chain = env.create_mchain( so_5::make_limited_without_waiting_mchain_params( // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest ) ); \endcode * * \since v.5.5.13 */ inline mchain_params_t make_limited_without_waiting_mchain_params( //! Max capacity of %mchain. std::size_t max_size, //! Type of chain storage. mchain_props::memory_usage_t memory_usage, //! Reaction on chain overflow. mchain_props::overflow_reaction_t overflow_reaction ) { return mchain_params_t{ mchain_props::capacity_t::make_limited_without_waiting( max_size, memory_usage, overflow_reaction ) }; } /*! * \brief Create parameters for size-limited %mchain with waiting on overflow. * * \par Usage example: \code so_5::environment_t & env = ...; auto chain = env.create_mchain( so_5::make_limited_with_waiting_mchain_params( // No more than 200 messages in the chain. 200, // Memory will be preallocated. so_5::mchain_props::memory_usage_t::preallocated, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest, // But before dropping a new message there will be 500ms timeout std::chrono::milliseconds(500) ) ); \endcode * * \note * Since v.5.5.18 there is an important difference in mchain behavior. * If an ordinary `send` is used for message pushing then there will * be waiting for free space if the message chain is full. * But if message push is performed from timer thread (it means that * message is a delayed or a periodic message) then there will not be * any waiting. It is because the context of timer thread is very * special: there is no possibility to spend some time on waiting for * some free space in message chain. All operations on the context of * timer thread must be done as fast as possible. * * \since v.5.5.13 */ inline mchain_params_t make_limited_with_waiting_mchain_params( //! Max size of the chain. std::size_t max_size, //! Type of chain storage. mchain_props::memory_usage_t memory_usage, //! Reaction on chain overflow. mchain_props::overflow_reaction_t overflow_reaction, //! Waiting time on full message chain. mchain_props::duration_t wait_timeout ) { return mchain_params_t { mchain_props::capacity_t::make_limited_with_waiting( max_size, memory_usage, overflow_reaction, wait_timeout ) }; } /*! * \} */ // // mchain_receive_result_t // /*! * \brief A result of receive from %mchain. * * \since v.5.5.13 */ class mchain_receive_result_t { //! Count of extracted messages. std::size_t m_extracted; //! Count of handled messages. std::size_t m_handled; //! Extraction status (e.g. no messages, chain closed and so on). mchain_props::extraction_status_t m_status; public : //! Default constructor. mchain_receive_result_t() noexcept : m_extracted{ 0 } , m_handled{ 0 } , m_status{ mchain_props::extraction_status_t::no_messages } {} //! Initializing constructor. mchain_receive_result_t( //! Count of extracted messages. std::size_t extracted, //! Count of handled messages. std::size_t handled, //! Status of extraction operation. mchain_props::extraction_status_t status ) noexcept : m_extracted{ extracted } , m_handled{ handled } , m_status{ status } {} //! Count of extracted messages. [[nodiscard]] std::size_t extracted() const noexcept { return m_extracted; } //! Count of handled messages. [[nodiscard]] std::size_t handled() const noexcept { return m_handled; } //! Extraction status (e.g. no messages, chain closed and so on). [[nodiscard]] mchain_props::extraction_status_t status() const noexcept { return m_status; } }; // /*! * \brief part of tuple protocol for mchain_receive_result_t. * * \since v.5.8.5 */ template auto get(const mchain_receive_result_t& result) { static_assert(Index < 3, "Index out of bounds for mchain_receive_result_t"); if constexpr (Index == 0) return result.extracted(); if constexpr (Index == 1) return result.handled(); if constexpr (Index == 2) return result.status(); } // // mchain_send_result_t // /*! * \brief A result of attempt of sending messages to a message chain. * * This type plays the same role as mchain_receive_result_t but is used * for send operations. * * \since v.5.7.0 */ class mchain_send_result_t { //! Count of messages sent. std::size_t m_sent; //! The status of send operation. mchain_props::push_status_t m_status; public: //! Default constructor. /*! * Sets push_status_t::not_stored status. */ mchain_send_result_t() noexcept : m_sent{ 0u } , m_status{ mchain_props::push_status_t::not_stored } {} //! Initializing constructor. mchain_send_result_t( std::size_t sent, mchain_props::push_status_t status ) : m_sent{ sent } , m_status{ status } {} //! Count of messages sent. [[nodiscard]] std::size_t sent() const noexcept { return m_sent; } //! Status of send operation. [[nodiscard]] mchain_props::push_status_t status() const noexcept { return m_status; } }; namespace mchain_props { // // msg_count_status_t // /*! * \brief Status of limit for messages to be extracted/handled * during a bulk operation on a mchain. * * \since v.5.6.0 */ enum class msg_count_status_t { //! Message count limit is not set yet. undefined, //! Message count limit is set. defined }; namespace details { // // bulk_processing_basic_data_t // struct bulk_processing_basic_data_t { //! Type of stop-predicate. /*! * Must return \a true if receive procedure should be stopped. */ using stop_predicate_t = std::function< bool() >; //! Type of chain-closed event. using chain_closed_handler_t = std::function< void(const mchain_t &) >; //! Minimal count of messages to be extracted. /*! * Value 0 means that this parameter is not set. */ std::size_t m_to_extract = { 0 }; //! Minimal count of messages to be handled. /*! * Value 0 means that this parameter it not set. */ std::size_t m_to_handle = { 0 }; //! Timeout for waiting on empty queue. mchain_props::duration_t m_empty_timeout = { mchain_props::details::infinite_wait_special_timevalue() }; //! Total time for all work of advanced receive. mchain_props::duration_t m_total_time = { mchain_props::details::infinite_wait_special_timevalue() }; //! Optional stop-predicate. stop_predicate_t m_stop_predicate; //! Optional chain-closed handler. chain_closed_handler_t m_chain_closed_handler; }; // // mchain_bulk_processing_basic_params_t // template< typename Basic_Data > class mchain_bulk_processing_basic_params_t { public : //! Type of stop-predicate. using stop_predicate_t = typename Basic_Data::stop_predicate_t; //! Type of chain-closed event. using chain_closed_handler_t = typename Basic_Data::chain_closed_handler_t; private : Basic_Data m_data; protected : //! Set limit for count of messages to be extracted. void set_extract_n( std::size_t v ) noexcept { m_data.m_to_extract = v; } //! Set limit for count of messages to be handled. void set_handle_n( std::size_t v ) noexcept { m_data.m_to_handle = v; } //! Set timeout for waiting on empty chain. /*! * \note This value will be ignored if total_time() is also used * to set total receive time. * * \note Argument \a v can be of type duration_t or * so_5::infinite_wait or so_5::no_wait. */ template< typename Timeout > void set_empty_timeout( Timeout v ) noexcept { m_data.m_empty_timeout = mchain_props::details::actual_timeout( v ); } //! Set total time for the whole receive operation. /*! * \note Argument \a v can be of type duration_t or * so_5::infinite_wait or so_5::no_wait. */ template< typename Timeout > void set_total_time( Timeout v ) noexcept { m_data.m_total_time = mchain_props::details::actual_timeout( v ); } //! Set user condition for stopping receive operation. /*! * \note \a predicate should return \a true if receive must * be stopped. */ void set_stop_on( stop_predicate_t predicate ) noexcept { m_data.m_stop_predicate = std::move(predicate); } //! Set handler for chain-closed event. /*! * If there is a previously set handler the old handler will be lost. */ void set_on_close( chain_closed_handler_t handler ) noexcept { m_data.m_chain_closed_handler = std::move(handler); } public : //! Default constructor. mchain_bulk_processing_basic_params_t() = default; //! Initializing constructor. mchain_bulk_processing_basic_params_t( Basic_Data data ) : m_data{ std::move(data) } {} //! Get limit for count of messages to be extracted. std::size_t to_extract() const noexcept { return m_data.m_to_extract; } //! Get limit for count of message to be handled. std::size_t to_handle() const noexcept { return m_data.m_to_handle; } //! Get timeout for waiting on empty chain. const mchain_props::duration_t & empty_timeout() const noexcept { return m_data.m_empty_timeout; } //! Get total time for the whole receive operation. const mchain_props::duration_t & total_time() const noexcept { return m_data.m_total_time; } //! Get user condition for stopping receive operation. const stop_predicate_t & stop_on() const noexcept { return m_data.m_stop_predicate; } //! Get handler for chain-closed event. const chain_closed_handler_t & closed_handler() const noexcept { return m_data.m_chain_closed_handler; } //! Access to internal data. const auto & so5_data() const noexcept { return m_data; } }; } /* namespace details */ } /* namespace mchain_props */ // // mchain_bulk_processing_params_t // /*! * \brief Basic parameters for advanced receive from %mchain and for * multi chain select. * * \since v.5.5.16 */ template< typename Data, typename Derived > class mchain_bulk_processing_params_t : public mchain_props::details::mchain_bulk_processing_basic_params_t< Data > { using basic_t = mchain_props::details::mchain_bulk_processing_basic_params_t; public : using actual_type = Derived; using data_type = Data; protected : //! Helper method to get a reference to itself but with a //! type of the derived class. actual_type & self_reference() { return static_cast< actual_type & >(*this); } //! Helper method to make a clone with msg_count_status_t::defined //! status. decltype(auto) clone_as_defined() noexcept { return self_reference().template so5_clone_if_necessary< mchain_props::msg_count_status_t::defined >(); } public : //! Default constructor. mchain_bulk_processing_params_t() = default; //! Initializing constructor. mchain_bulk_processing_params_t( Data data ) : basic_t{ std::move(data) } {} //! A directive to handle all messages until chain will be closed //! or receiving will be stopped manually. /*! * Usage example: * \code * so_5::receive(so_5::from(ch).handle_all(), ...); * \endcode * * \since v.5.6.0 */ decltype(auto) handle_all() noexcept { this->set_handle_n( 0u ); return clone_as_defined(); } //! Set limit for count of messages to be extracted. /*! * When extract_n() is used then receive() will be finished * after extraction of the specified number of message. * * Usage example: * \code * so_5::receive(so_5::from(ch).extract_n(2), ...); * \endcode */ decltype(auto) extract_n( std::size_t v ) noexcept { this->set_extract_n( v ); return clone_as_defined(); } //! Set limit for count of messages to be handled. /*! * When handled_n() is used then receive() will be finished * after handling of the specified number of message. * * Usage example: * \code * so_5::receive(so_5::from(ch).handle_n(2), ...); * \endcode */ decltype(auto) handle_n( std::size_t v ) noexcept { this->set_handle_n( v ); return clone_as_defined(); } //! Set timeout for waiting on empty chain. /*! * \note This value will be ignored if total_time() is also used * to set total receive time. * * \note Argument \a v can be of type duration_t or * so_5::infinite_wait or so_5::no_wait. */ template< typename Timeout > actual_type & empty_timeout( Timeout v ) noexcept { this->set_empty_timeout( std::move(v) ); return self_reference(); } using basic_t::empty_timeout; /*! * \brief Disable waiting on the empty queue. * * \par Usage example: * \code so_5::mchain_t ch = env.create_mchain(...); receive( from(ch).no_wait_on_empty(), ... ); * \endcode * * \note It is just a shorthand for: * \code receive( from(chain).empty_timeout(std::chrono::seconds(0)), ...); * \endcode */ actual_type & no_wait_on_empty() noexcept { return empty_timeout( mchain_props::details::no_wait_special_timevalue() ); } //! Set total time for the whole receive operation. /*! * \note Argument \a v can be of type duration_t or * so_5::infinite_wait or so_5::no_wait. */ template< typename Timeout > actual_type & total_time( Timeout v ) noexcept { this->set_total_time( std::move(v) ); return self_reference(); } using basic_t::total_time; //! Set user condition for stopping receive operation. /*! * \note \a predicate should return \a true if receive must * be stopped. */ actual_type & stop_on( typename basic_t::stop_predicate_t predicate ) noexcept { this->set_stop_on( std::move(predicate) ); return self_reference(); } using basic_t::stop_on; //! Set handler for chain-closed event. /*! * If there is a previously set handler the old handler will be lost. * * Usage example: * \code * so_5::mchain_t ch1 = so_5::create_mchain(...); * so_5::mchain_t ch2 = so_5::create_mchain(...); * ... * // Stop reading channels when any of channels is closed. * bool some_ch_closed = false; * so_5::select( * so_5::from_all() * .handle_all() * .on_close([&some_ch_closed](const so_5::mchain_t &) { * some_ch_closed = true; * }) * .stop_on([&some_ch_closed]{ return some_ch_closed; }), * receive_case(ch1, ...) * receive_case(ch2, ...) * ...); * \endcode * * \since v.5.5.17 */ actual_type & on_close( typename basic_t::chain_closed_handler_t handler ) noexcept { this->set_on_close( std::move(handler) ); return self_reference(); } }; namespace mchain_props { namespace details { // // adv_receive_data_t // /*! * \brief Container of parameters for receive() function. * * \since v.5.6.0 */ struct adv_receive_data_t : public bulk_processing_basic_data_t { //! A chain to be used in receive operation. mchain_t m_chain; //! Default constructor. adv_receive_data_t() = default; //! Initializing constructor. adv_receive_data_t( mchain_t chain ) : m_chain{ std::move(chain) } {} }; } /* namespace details */ } /* namespace mchain_props */ // // mchain_receive_params_t // /*! * \brief Parameters for advanced receive from %mchain. * * \sa so_5::from(). * * \note Derived from basic_receive_params_t since v.5.5.16. * * \tparam Msg_Count_Status status of message count limit. * * \since v.5.5.13 */ template< mchain_props::msg_count_status_t Msg_Count_Status > class mchain_receive_params_t final : public mchain_bulk_processing_params_t< mchain_props::details::adv_receive_data_t, mchain_receive_params_t< Msg_Count_Status > > { //! Short alias for base type. using base_type = mchain_bulk_processing_params_t< mchain_props::details::adv_receive_data_t, mchain_receive_params_t< Msg_Count_Status > >; public : //! Make of clone with different Msg_Count_Status or return //! a reference to the same object. template< mchain_props::msg_count_status_t New_Msg_Count_Status > [[nodiscard]] decltype(auto) so5_clone_if_necessary() noexcept { if constexpr( New_Msg_Count_Status != Msg_Count_Status ) return mchain_receive_params_t< New_Msg_Count_Status >{ this->so5_data() }; else return *this; } //! Initializing constructor. mchain_receive_params_t( //! Chain from which messages must be extracted and handled. mchain_t chain ) : base_type{ typename base_type::data_type{ std::move(chain) } } {} //! Initializing constructor for the case of cloning. mchain_receive_params_t( typename base_type::data_type data ) : base_type{ std::move(data) } {} //! Chain from which messages must be extracted and handled. const mchain_t & chain() const { return this->so5_data().m_chain; } }; // // from // /*! * \brief A helper function for simplification of creation of %mchain_receive_params instance. * * \attention * Since v.5.6.0 at least handle_all(), handle_n() or extract_n() should be * called before passing result of from() to receive() function. * * \par Usage examples: \code so_5::mchain_t chain = env.create_mchain(...); // Receive and handle 3 messages. // If there is no 3 messages in the mchain the receive will wait infinitely. // A return from receive will be after handling of 3 messages or // if the mchain is closed explicitely. receive( from(chain).handle_n( 3 ), handlers... ); // Receive and handle 3 messages. // If there is no 3 messages in the mchain the receive will wait // no more that 200ms. // A return from receive will be after handling of 3 messages or // if the mchain is closed explicitely, or if there is no messages // for more than 200ms. receive( from(chain).handle_n( 3 ).empty_timeout( milliseconds(200) ), handlers... ); // Receive all messages from the chain. // If there is no message in the chain then wait no more than 500ms. // A return from receive will be after explicit close of the chain // or if there is no messages for more than 500ms. receive( from(chain).handle_all().empty_timeout( milliseconds(500) ), handlers... ); // Receve any number of messages from the chain but do waiting and // handling for no more than 2s. receive( from(chain).handle_all().total_time( seconds(2) ), handlers... ); // Receve 1000 messages from the chain but do waiting and // handling for no more than 2s. receive( from(chain).extract_n( 1000 ).total_time( seconds(2) ), handlers... ); \endcode * * \since v.5.5.13 */ inline mchain_receive_params_t< mchain_props::msg_count_status_t::undefined > from( mchain_t chain ) { return { std::move(chain) }; } namespace mchain_props { namespace details { // // receive_actions_performer_t // /*! * \brief Helper class with implementation of main actions of * advanced receive operation. * * \since v.5.5.13 */ template< typename Bunch > class receive_actions_performer_t { const mchain_receive_params_t< msg_count_status_t::defined > & m_params; const Bunch & m_bunch; std::size_t m_extracted_messages = 0; std::size_t m_handled_messages = 0; extraction_status_t m_status; public : receive_actions_performer_t( const mchain_receive_params_t< msg_count_status_t::defined > & params, const Bunch & bunch ) : m_params( params ) , m_bunch( bunch ) {} void handle_next( duration_t empty_timeout ) { demand_t extracted_demand; m_status = m_params.chain()->extract( extracted_demand, empty_timeout ); if( extraction_status_t::msg_extracted == m_status ) { ++m_extracted_messages; const bool handled = m_bunch.handle( extracted_demand.m_msg_type, extracted_demand.m_message_ref ); if( handled ) ++m_handled_messages; } // Since v.5.5.17 we must check presence of chain-closed handler. // This handler must be used if chain is closed. else if( extraction_status_t::chain_closed == m_status ) { if( const auto & handler = m_params.closed_handler() ) so_5::details::invoke_noexcept_code( [&handler, this] { handler( m_params.chain() ); } ); } } extraction_status_t last_status() const { return m_status; } bool can_continue() const { if( extraction_status_t::chain_closed == m_status ) return false; if( m_params.to_handle() && m_handled_messages >= m_params.to_handle() ) return false; if( m_params.to_extract() && m_extracted_messages >= m_params.to_extract() ) return false; if( m_params.stop_on() && m_params.stop_on()() ) return false; return true; } mchain_receive_result_t make_result() const { return mchain_receive_result_t{ m_extracted_messages, m_handled_messages, m_extracted_messages ? extraction_status_t::msg_extracted : m_status }; } }; /*! * \brief An implementation of advanced receive when a limit for total * operation time is defined. * * \since v.5.5.13 */ template< typename Bunch > inline mchain_receive_result_t receive_with_finite_total_time( const mchain_receive_params_t & params, const Bunch & bunch ) { receive_actions_performer_t< Bunch > performer( params, bunch ); so_5::details::remaining_time_counter_t remaining_time( params.total_time() ); do { // Since v.5.8.4 we have to take the empty_timeout value // into the account. const duration_t this_iteration_wait_time = std::min( remaining_time.remaining(), params.empty_timeout() ); performer.handle_next( this_iteration_wait_time ); if( extraction_status_t::no_messages == performer.last_status() ) // There is no need to continue. // This status means that empty_timeout has some value // and there is no any new message during empty_timeout. // And this means a condition for return from advanced // receive. break; if( !performer.can_continue() ) break; remaining_time.update(); } while( remaining_time ); return performer.make_result(); } /*! * \brief An implementation of advanced receive when there is no * limit for total operation time is defined. * * \since v.5.5.13 */ template< typename Bunch > inline mchain_receive_result_t receive_without_total_time( const mchain_receive_params_t & params, const Bunch & bunch ) { receive_actions_performer_t< Bunch > performer{ params, bunch }; do { performer.handle_next( params.empty_timeout() ); if( extraction_status_t::no_messages == performer.last_status() ) // There is no need to continue. // This status means that empty_timeout has some value // and there is no any new message during empty_timeout. // And this means a condition for return from advanced // receive. break; } while( performer.can_continue() ); return performer.make_result(); } /*! * \brief An implementation of main receive actions. * * \since v.5.5.17 */ template< typename Bunch > inline mchain_receive_result_t perform_receive( const mchain_receive_params_t & params, const Bunch & bunch ) { if( !is_infinite_wait_timevalue( params.total_time() ) ) return receive_with_finite_total_time( params, bunch ); else return receive_without_total_time( params, bunch ); } } /* namespace details */ } /* namespace mchain_props */ // // receve (advanced version) // /*! * \brief Advanced version of receive from %mchain. * * \attention It is an error if there are more than one handler for the * same message type in \a handlers. * * \attention * Since v.5.6.0 at least handle_all(), handle_n() or extract_n() should be * called before passing result of from() to receive() function. * * \par Usage examples: \code so_5::mchain_t chain = env.create_mchain(...); // Receive and handle 3 messages. // If there is no 3 messages in the mchain the receive will wait infinitely. // A return from receive will be after handling of 3 messages or // if the mchain is closed explicitely. receive( from(chain).handle_n( 3 ), []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... }, ... ); // Receive and handle 3 messages. // If there is no 3 messages in the mchain the receive will wait // no more that 200ms. // A return from receive will be after handling of 3 messages or // if the mchain is closed explicitely, or if there is no messages // for more than 200ms. receive( from(chain).handle_n( 3 ).empty_timeout( milliseconds(200) ), []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... }, ... ); // Receive all messages from the chain. // If there is no message in the chain then wait no more than 500ms. // A return from receive will be after explicit close of the chain // or if there is no messages for more than 500ms. receive( from(chain).handle_all().empty_timeout( milliseconds(500) ), []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... }, ... ); // Receve any number of messages from the chain but do waiting and // handling for no more than 2s. receive( from(chain).handle_all().total_time( seconds(2) ), []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... }, ... ); // Receve 1000 messages from the chain but do waiting and // handling for no more than 2s. receive( from(chain).extract_n( 1000 ).total_time( seconds(2) ), []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... }, ... ); \endcode * * \par Handlers format examples: * \code receive( from(ch).handle_n(1), // Message instance by const reference. []( const std::string & v ) {...}, // Message instance by value (efficient for small types like int). []( int v ) {...}, // Message instance via mhood_t value. []( so_5::mhood_t< some_message > v ) {...}, // Message instance via const reference to mhood_t. []( const so_5::mhood_t< some_another_message > & v ) {...}, // Explicitly specified signal handler. so_5::handler< some_signal >( []{...} ), // Signal handler via mhood_t value. []( so_5::mhood_t< some_another_signal > ) {...}, // Signal handler via const reference to mhood_t. []( const so_5::mhood_t< yet_another_signal > & ) {...} ); * \endcode * * \note * Since v.5.8.4 the value of empty_timeout (or no_wait_on_empty) is * taken into account when `total_time` is specified. It means that * in the following example the receive() returns immediately if * the mchain is empty: * \code * receive( * from(ch).handle_n(5).no_wait_on_empty().total_time(6s), * [](const my_message & msg) {...}); * \endcode * * \since v.5.5.13 */ template< mchain_props::msg_count_status_t Msg_Count_Status, typename... Handlers > inline mchain_receive_result_t receive( //! Parameters for receive. const mchain_receive_params_t & params, //! Handlers for message processing. Handlers &&... handlers ) { static_assert( Msg_Count_Status == mchain_props::msg_count_status_t::defined, "message count to be processed/extracted should be defined " "by using handle_all()/handle_n()/extract_n() methods" ); using namespace so_5::details; using namespace so_5::mchain_props; using namespace so_5::mchain_props::details; handlers_bunch_t< sizeof...(handlers) > bunch; fill_handlers_bunch( bunch, 0, std::forward< Handlers >(handlers)... ); return perform_receive( params, bunch ); } // // prepared_receive_t // /*! * \brief Special container for holding receive parameters and receive cases. * * \note Instances of that type usually used without specifying the actual * type: * \code auto prepared = so_5::prepare_receive( from(ch).handle_n(10).empty_timeout(10s), some_handlers... ); ... auto r = so_5::receive( prepared ); * \endcode * \note This is a moveable type, not copyable. * * \since v.5.5.17 */ template< std::size_t Handlers_Count > class prepared_receive_t { //! Parameters for receive. mchain_receive_params_t< mchain_props::msg_count_status_t::defined > m_params; //! Cases for receive. so_5::details::handlers_bunch_t< Handlers_Count > m_bunch; public : prepared_receive_t( const prepared_receive_t & ) = delete; prepared_receive_t & operator=( const prepared_receive_t & ) = delete; //! Initializing constructor. template< typename... Handlers > prepared_receive_t( mchain_receive_params_t< mchain_props::msg_count_status_t::defined > params, Handlers &&... cases ) : m_params( std::move(params) ) { static_assert( sizeof...(Handlers) == Handlers_Count, "Handlers_count and sizeof...(Handlers) mismatch" ); fill_handlers_bunch( m_bunch, 0u, std::forward(cases)... ); } //! Move constructor. prepared_receive_t( prepared_receive_t && other ) : m_params( std::move(other.m_params) ) , m_bunch( std::move(other.m_bunch) ) {} //! Move operator. prepared_receive_t & operator=( prepared_receive_t && other ) noexcept { prepared_receive_t tmp( std::move(other) ); swap( &this, tmp ); return *this; } //! Swap operation. friend void swap( prepared_receive_t & a, prepared_receive_t & b ) noexcept { using std::swap; swap( a.m_params, b.m_params ); swap( a.m_bunch, b.m_bunch ); } /*! * \name Getters * \{ */ const auto & params() const noexcept { return m_params; } const auto & handlers() const noexcept { return m_bunch; } /*! * \} */ }; // // prepare_receive // /*! * \brief Create parameters for receive function to be used later. * * \attention * Since v.5.6.0 at least handle_all(), handle_n() or extract_n() should be * called before passing result of from() to prepare_receive() function. * * Accepts all parameters as advanced receive() version. For example: * \code // Receive and handle 3 messages. // If there is no 3 messages in the mchain the receive will wait // no more that 200ms. // A return from receive will be after handling of 3 messages or // if the mchain is closed explicitely, or if there is no messages // for more than 200ms. auto prepared1 = prepare_receive( from(chain).handle_n( 3 ).empty_timeout( milliseconds(200) ), []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... }, ... ); // Receive all messages from the chain. // If there is no message in the chain then wait no more than 500ms. // A return from receive will be after explicit close of the chain // or if there is no messages for more than 500ms. auto prepared2 = prepare_receive( from(chain).handle_all().empty_timeout( milliseconds(500) ), []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... }, ... ); * \endcode * * \note * Since v.5.8.4 the value of empty_timeout (or no_wait_on_empty) is * taken into account when `total_time` is specified. It means that * in the following example the receive() returns immediately if * the mchain is empty: * \code * auto prepared = prepare_receive( * from(ch).handle_n(5).no_wait_on_empty().total_time(6s), * [](const my_message & msg) {...}); * ... * auto r = so_5::receive(prepared); * \endcode * * \since v.5.5.17 */ template< mchain_props::msg_count_status_t Msg_Count_Status, typename... Handlers > prepared_receive_t< sizeof...(Handlers) > prepare_receive( //! Parameters for advanced receive. const mchain_receive_params_t< Msg_Count_Status > & params, //! Handlers Handlers &&... handlers ) { static_assert( Msg_Count_Status == mchain_props::msg_count_status_t::defined, "message count to be processed/extracted should be defined " "by using handle_all()/handle_n()/extract_n() methods" ); return prepared_receive_t< sizeof...(Handlers) >( params, std::forward(handlers)... ); } /*! * \brief A receive operation to be done on previously prepared receive params. * * Usage of ordinary forms of receive() functions inside loops could be * inefficient because of wasting resources on constructions of internal * objects with descriptions of handlers on each receive() call. More * efficient way is preparation of all receive params and reusing them later. A * combination of so_5::prepare_receive() and so_5::receive(prepared_receive_t) * allows to do that. * * Usage example: * \code auto prepared = so_5::prepare_receive( so_5::from(ch).extract_n(10).empty_timeout(200ms), some_handlers... ); ... while( !some_condition ) { auto r = so_5::receive( prepared ); ... } * \endcode * * \since v.5.5.17 */ template< std::size_t Handlers_Count > mchain_receive_result_t receive( const prepared_receive_t< Handlers_Count > & prepared ) { return mchain_props::details::perform_receive( prepared.params(), prepared.handlers() ); } } /* namespace so_5 */ namespace std { // /*! * \brief part of tuple protocol for mchain_receive_result_t. * * \since v.5.8.5 */ template<> struct tuple_size : integral_constant { }; // /*! * \brief part of tuple protocol for mchain_receive_result_t. * * \since v.5.8.5 */ template struct tuple_element : tuple_element> { }; } /* namespace std */ #endif sobjectizer-5.8.6/dev/so_5/mchain_helper_functions.hpp000066400000000000000000000372261523065210100231040ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helper functions for mchains. * \since * v.5.5.16 */ #if !defined( SO_5_MCHAIN_HELPER_FUNCTIONS_HPP ) #define SO_5_MCHAIN_HELPER_FUNCTIONS_HPP #include #include #include namespace so_5 { /*! * \name Helper functions for mchain creation. * \{ */ /*! * \brief Create size-unlimited chain. * * \code so_5::environment_t & env = ...; auto ch = create_mchain( env ); // Or as more verbose alternative: auto ch = env.create_mchain( so_5::make_unlimited_mchain_params() ); * \endcode * * \since * v.5.5.16 */ inline mchain_t create_mchain( environment_t & env ) { return env.create_mchain( make_unlimited_mchain_params() ); } /*! * \brief Create size-unlimited chain. * * \code wrapped_env_t sobj; auto ch = create_mchain( sobj ); // Or as more verbose alternative: auto ch = sobj.environment().create_mchain( make_unlimited_mchain_params() ); * \endcode * * \since * v.5.5.16 */ inline mchain_t create_mchain( wrapped_env_t & sobj ) { return create_mchain( sobj.environment() ); } /*! * \brief Create size-limited chain without waiting on overflow. * * \code so_5::environment_t & env = ...; auto ch = create_mchain( env, // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest ); // Or as more verbose alternative: auto ch = env.create_mchain( so_5::make_limited_without_waiting_mchain_params( // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest ) ); * \endcode * * \since * v.5.5.16 */ inline mchain_t create_mchain( //! Environment for mchain. environment_t & env, //! Max capacity of mchain. std::size_t max_size, //! Type of chain storage. mchain_props::memory_usage_t memory_usage, //! Reaction on chain overflow. mchain_props::overflow_reaction_t overflow_reaction ) { return env.create_mchain( make_limited_without_waiting_mchain_params( max_size, memory_usage, overflow_reaction ) ); } /*! * \brief Create size-limited chain without waiting on overflow. * * \code so_5::wrapped_env_t sobj; auto ch = create_mchain( sobj, // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest ); // Or as more verbose alternative: auto ch = sobj.environment().create_mchain( so_5::make_limited_without_waiting_mchain_params( // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest ) ); * \endcode * * \since * v.5.5.16 */ inline mchain_t create_mchain( //! Environment for mchain. wrapped_env_t & sobj, //! Max capacity of mchain. std::size_t max_size, //! Type of chain storage. mchain_props::memory_usage_t memory_usage, //! Reaction on chain overflow. mchain_props::overflow_reaction_t overflow_reaction ) { return sobj.environment().create_mchain( make_limited_without_waiting_mchain_params( max_size, memory_usage, overflow_reaction ) ); } /*! * \} */ /*! * \brief Create size-limited chain with waiting on overflow. * * \code so_5::environment_t & env = ...; auto ch = create_mchain( env, // Wait for 150ms. std::chrono::milliseconds{150}, // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest ); // Or as more verbose alternative: auto ch = env.create_mchain( so_5::make_limited_with_waiting_mchain_params( // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest, // Wait for 150ms. std::chrono::milliseconds{150} ) ); * \endcode * * \since * v.5.5.16 */ inline mchain_t create_mchain( //! Environment for mchain. environment_t & env, //! Max waiting time in case of overflow. mchain_props::duration_t waiting_time, //! Max capacity of mchain. std::size_t max_size, //! Type of chain storage. mchain_props::memory_usage_t memory_usage, //! Reaction on chain overflow. mchain_props::overflow_reaction_t overflow_reaction ) { return env.create_mchain( make_limited_with_waiting_mchain_params( max_size, memory_usage, overflow_reaction, waiting_time ) ); } /*! * \brief Create size-limited chain without waiting on overflow. * * \code so_5::wrapped_env_t sobj; auto ch = create_mchain( sobj, // Wait for 150ms. std::chrono::milliseconds{150}, // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest ); // Or as more verbose alternative: auto ch = sobj.environment().create_mchain( so_5::make_limited_with_waiting_mchain_params( // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest, // Wait for 150ms. std::chrono::milliseconds{150} ) ); * \endcode * * \since * v.5.5.16 */ inline mchain_t create_mchain( //! Environment for mchain. wrapped_env_t & sobj, //! Max waiting time in case of overflow. mchain_props::duration_t waiting_time, //! Max capacity of mchain. std::size_t max_size, //! Type of chain storage. mchain_props::memory_usage_t memory_usage, //! Reaction on chain overflow. mchain_props::overflow_reaction_t overflow_reaction ) { return sobj.environment().create_mchain( make_limited_with_waiting_mchain_params( max_size, memory_usage, overflow_reaction, waiting_time ) ); } /*! * \} */ namespace mchain_auto_close_details { /*! * \brief Main class for auto close operation. * * \attention * Since v.5.7.3 it uses so_5::terminate_if_throws calling 'close' for * mchains. * * \attention * This is Moveable class, not Copyable. * * \since * v.5.5.16 */ template< std::size_t N > class auto_closer_t { std::array< mchain_t, N > m_chains; so_5::mchain_props::close_mode_t m_close_mode; public : auto_closer_t( const auto_closer_t & ) = delete; auto_closer_t & operator=( const auto_closer_t & ) = delete; auto_closer_t( so_5::mchain_props::close_mode_t close_mode ) : m_close_mode{ close_mode } {} auto_closer_t( auto_closer_t && o ) noexcept : m_close_mode{ o.m_close_mode } { swap( m_chains, o.m_chains ); } ~auto_closer_t() { for( auto & ch : m_chains ) if( ch ) // NOTE: use terminate_if_throws because we're in the // destructor. ch->close( terminate_if_throws, m_close_mode ); } friend inline void swap( auto_closer_t & a, auto_closer_t & b ) noexcept { using std::swap; swap( a.m_close_mode, b.m_close_mode ); swap( a.m_chains, b.m_chains ); } auto_closer_t & operator=( auto_closer_t && o ) noexcept { auto_closer_t tmp{ std::move(o) }; swap( *this, tmp ); return *this; } void set_mchain( std::size_t index, mchain_t ch ) { m_chains[ index ] = std::move(ch); } }; /*! * \brief Helper for filling auto_closer object. * * \since * v.5.5.16 */ template< typename Closer > void add_to_closer( Closer & to, std::size_t index, mchain_t ch ) { to.set_mchain( index, std::move(ch) ); } /*! * \brief Helper for filling auto_closer object. * * \since * v.5.5.16 */ template< typename Closer, typename... Tail > void add_to_closer( Closer & to, std::size_t index, mchain_t ch, Tail &&... tail ) { add_to_closer( to, index, std::move(ch) ); add_to_closer( to, index + 1, std::forward< Tail >(tail)... ); } } /* namespace mchain_auto_close_details */ /*! * \brief Helper function for creation of automatic closer of mchains. * * Usage examples: * \code so_5::environment_t & env = ...; auto command_ch = create_mchain( env ); auto reply_ch = create_mchain( env ); std::thread worker{ [command_ch, reply_ch] { receive( from( command_ch ), handlers... ); } }; // Chains must be closed automatically on any exception... // Content of chains must be dropped. auto channels_closer = so_5::auto_close_mchains( so_5::mchain_props::close_mode_t::drop_content, command_ch, reply_ch ); ... // Some complex code here. // Close command chain for worker thread. Worker will finish its work. close_retain_content( command_ch ); // Ensure that worker finished. worker.join(); * \endcode * * \attention * Since v.5.7.3 it uses so_5::terminate_if_throws calling 'close' for mchains. * * \since * v.5.5.16 */ template< typename... Tail > mchain_auto_close_details::auto_closer_t< sizeof...(Tail) > auto_close_mchains( //! Close mode for all mchains. //! This value will be passed to all close() calls. mchain_props::close_mode_t close_mode, //! Chains to be closed. Tail &&... tail ) { using namespace mchain_auto_close_details; auto_closer_t< sizeof...(tail) > closer{ close_mode }; add_to_closer( closer, 0, std::forward< Tail >(tail)... ); return closer; } /*! * \brief Helper function for automatic closing of mchains with * retaining their content. * * Usage example: * \code so_5::environment_t & env = ...; auto ch1 = create_mchain(env); auto ch2 = create_mchain(env); auto ch_closer = so_5::auto_close_retain_content(ch1, ch2); ... * \endcode * * \note This is just a shorthand for: * \code so_5::auto_close_mchains( so_5::mchain_props::close_mode_t::retain_content, chains... ); * \endcode * * \attention * Since v.5.7.3 it uses so_5::terminate_if_throws calling 'close' for mchains. * * \tparam Tail List of mchains. * * \since * v.5.5.16 */ template< typename... Tail > mchain_auto_close_details::auto_closer_t< sizeof...(Tail) > auto_close_retain_content( Tail &&... tail ) { return auto_close_mchains( mchain_props::close_mode_t::retain_content, std::forward< Tail >(tail)... ); } /*! * \brief Helper function for automatic closing of mchains with * dropping their content. * * Usage example: * \code so_5::environment_t & env = ...; auto ch1 = create_mchain(env); auto ch2 = create_mchain(env); auto ch_closer = so_5::auto_close_drop_content(ch1, ch2); ... * \endcode * * \note This is just a shorthand for: * \code so_5::auto_close_mchains( so_5::mchain_props::close_mode_t::drop_content, chains... ); * \endcode * * \attention * Since v.5.7.3 it uses so_5::terminate_if_throws calling 'close' for mchains. * * \tparam Tail List of mchains. * * \since * v.5.5.16 */ template< typename... Tail > mchain_auto_close_details::auto_closer_t< sizeof...(Tail) > auto_close_drop_content( Tail &&... tail ) { return auto_close_mchains( mchain_props::close_mode_t::drop_content, std::forward< Tail >(tail)... ); } // // mchain_master_handle_t // /*! * \brief Helper class for automatic close of a mchain at * the destruction of master handle instance. * * This class is intended for cases like that: \code auto ch = so_5::create_mchain( env ); auto ch_closer = so_5::auto_close_drop_content( ch ); ... \endcode * The example shown above can be replaced by: * \code so_5::mchain_master_handle_t ch_handle( so_5::create_mchain( env ), so_5::mchain_props::close_mode_t::drop_content ); // Or auto ch_handle = so_5::mchain_master_handle_t::make( so_5::create_mchain( env ), so_5::mchain_props::close_mode_t::drop_content ); // Or auto ch_handle = so_5::mchain_master_handle_t::with_drop_content( so_5::create_mchain( env ) ); * \endcode * * \attention * Since v.5.7.3 it uses so_5::terminate_if_throws calling 'close' for mchains. * * \note This class is moveable but not copyable. * * \since * v.5.5.17 */ class mchain_master_handle_t { public : mchain_master_handle_t( const mchain_master_handle_t & ) = delete; mchain_master_handle_t & operator=( const mchain_master_handle_t & ) = delete; //! Initializing constructor. mchain_master_handle_t( //! A mchain itself. mchain_t chain, //! Close mode for mchain. mchain_props::close_mode_t close_mode ) noexcept : m_chain( std::move(chain) ) , m_close_mode( close_mode ) {} //! Move constructor. mchain_master_handle_t( mchain_master_handle_t && handle ) noexcept : m_chain( std::move(handle.m_chain) ) , m_close_mode( handle.m_close_mode ) {} //! Destructor closes the chain. ~mchain_master_handle_t() { if( m_chain ) m_chain->close( terminate_if_throws, m_close_mode ); } //! Move operator. mchain_master_handle_t & operator=( mchain_master_handle_t && handle ) noexcept { mchain_master_handle_t tmp( std::move(handle) ); swap( tmp, *this ); return *this; } //! Swap operation. friend void swap( mchain_master_handle_t & a, mchain_master_handle_t & b ) noexcept { using std::swap; swap( a.m_chain, b.m_chain ); swap( a.m_close_mode, b.m_close_mode ); } //! Get the mchain. const mchain_t & get() const noexcept { return m_chain; } //! Get the mchain. const mchain_t & operator*() const noexcept { return m_chain; } //! \name Helpers methods for master handle creation. //! \{ /*! * \par Usage example: * \code void demo( so_5::environment_t & env, app_config & config ) { std::thread worker; auto worker_joiner = so_5::auto_join( worker ); auto chain = so_5::mchain_master_handle_t::make( create_mchain( env ), detect_close_mode( config ) ); worker = std::thread( [ch = *chain]{ ... } ); ... } * \endcode */ inline static mchain_master_handle_t make( mchain_t chain, mchain_props::close_mode_t close_mode ) noexcept { return mchain_master_handle_t( std::move(chain), close_mode ); } /*! * \par Usage example: * \code void demo( so_5::environment_t & env ) { std::thread worker; auto worker_joiner = so_5::auto_join( worker ); auto chain = so_5::mchain_master_handle_t::with_drop_content( create_mchain( env ) ); worker = std::thread( [ch = *chain]{ ... } ); ... } * \endcode */ inline static mchain_master_handle_t with_drop_content( mchain_t chain ) noexcept { return mchain_master_handle_t( std::move(chain), mchain_props::close_mode_t::drop_content ); } /*! * \par Usage example: * \code void demo( so_5::environment_t & env ) { std::thread worker; auto worker_joiner = so_5::auto_join( worker ); auto chain = so_5::mchain_master_handle_t::with_retain_content( create_mchain( env ) ); worker = std::thread( [ch = *chain]{ ... } ); ... } * \endcode */ inline static mchain_master_handle_t with_retain_content( mchain_t chain ) noexcept { return mchain_master_handle_t( std::move(chain), mchain_props::close_mode_t::retain_content ); } //! \} private : //! Chain to be hold. mchain_t m_chain; //! Close mode for mchain. mchain_props::close_mode_t m_close_mode; }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/mchain_select.hpp000066400000000000000000001640001523065210100210030ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Various stuff related to multi chain select. * * \since v.5.5.16 */ #if !defined( SO_5_MCHAIN_SELECT_HPP ) #define SO_5_MCHAIN_SELECT_HPP #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif namespace so_5 { namespace mchain_props { namespace details { // // adv_select_data_t // //NOTE: there is no any additional data for select() function. using adv_select_data_t = bulk_processing_basic_data_t; } /* namespace details */ } /* namespace mchain_props */ // // mchain_select_result_t // /*! * \brief A result of select from several mchains. * * \since v.5.7.0 */ class mchain_select_result_t { //! Count of extracted incoming messages. std::size_t m_extracted; //! Count of handled incoming messages. std::size_t m_handled; //! Count of messages sent. std::size_t m_sent; //! Count of closed chains. std::size_t m_closed; public : //! Default constructor. mchain_select_result_t() noexcept : m_extracted{ 0u } , m_handled{ 0u } , m_sent{ 0u } , m_closed{ 0u } {} //! Initializing constructor. mchain_select_result_t( //! Count of extracted incoming messages. std::size_t extracted, //! Count of handled incoming messages. std::size_t handled, //! Count of messages sent. std::size_t sent, //! Count of closed chains. std::size_t closed ) noexcept : m_extracted{ extracted } , m_handled{ handled } , m_sent{ sent } , m_closed{ closed } {} //! Count of extracted incoming messages. [[nodiscard]] std::size_t extracted() const noexcept { return m_extracted; } //! Count of handled incoming messages. [[nodiscard]] std::size_t handled() const noexcept { return m_handled; } //! Count of messages sent. [[nodiscard]] std::size_t sent() const noexcept { return m_sent; } //! Count of closed chains. [[nodiscard]] std::size_t closed() const noexcept { return m_closed; } /*! * \return true if extracted() is not 0. */ [[nodiscard]] bool was_extracted() const noexcept { return 0u != m_extracted; } /*! * \return true if handled() is not 0. */ [[nodiscard]] bool was_handled() const noexcept { return 0u != m_handled; } /*! * \return true if sent() is not 0. */ [[nodiscard]] bool was_sent() const noexcept { return 0u != m_sent; } /*! * \return true if closed() is not 0. */ [[nodiscard]] bool was_closed() const noexcept { return 0u != m_closed; } /*! * \return true if something was sent or received/handled. */ [[nodiscard]] bool was_sent_or_received() const noexcept { return was_extracted() || was_handled() || was_sent(); } }; // // mchain_select_params_t // /*! * \brief Parameters for advanced select from multiple mchains. * * \sa so_5::select(). * * \note Adds nothing to mchain_bulk_processing_params_t. * * \since v.5.5.16 */ template< mchain_props::msg_count_status_t Msg_Count_Status > class mchain_select_params_t final : public mchain_bulk_processing_params_t< mchain_props::details::adv_select_data_t, mchain_select_params_t< Msg_Count_Status > > { using base_type = mchain_bulk_processing_params_t< mchain_props::details::adv_select_data_t, mchain_select_params_t< Msg_Count_Status > >; public : //! Make of clone with different Msg_Count_Status or return //! a reference to the same object. template< mchain_props::msg_count_status_t New_Msg_Count_Status > [[nodiscard]] decltype(auto) so5_clone_if_necessary() noexcept { if constexpr( New_Msg_Count_Status != Msg_Count_Status ) return mchain_select_params_t< New_Msg_Count_Status >{ this->so5_data() }; else return *this; } //! The default constructor. mchain_select_params_t() = default; //! Initializing constructor for the case of cloning. mchain_select_params_t( typename base_type::data_type data ) : base_type{ std::move(data) } {} }; // // from_all // /*! * \brief Helper function for creation of mchain_select_params instance * with default values. * * \attention * Since v.5.6.0 at least handle_all(), handle_n() or extract_n() should be * called before passing result of from_all() to select() or * prepare_select() functions. * * Usage example: * \code select( so_5::from_all().handle_n(3).empty_timeout(3s), ... ); * \endcode */ inline mchain_select_params_t< mchain_props::msg_count_status_t::undefined > from_all() { return {}; } namespace mchain_props { namespace details { // // receive_select_case_t // /*! * \brief A base class for implementations of select_case for the * case of receiving messages. * * This class implements pure virtual try_handle() methods but introduces * a new pure virtual method try_handle_extracted_message() that should * be implemented in a derived class. * * \since v.5.7.0 */ class receive_select_case_t : public select_case_t { public : using select_case_t::select_case_t; [[nodiscard]] handling_result_t try_handle( select_notificator_t & notificator ) override { // Please note that value of m_notificator will be // returned to nullptr if a message extracted or // channel is closed. // // NOTE: if extract() throws we don't know is the pointer // to this select_case stored inside mchain or not. // But is doesn't matter because in the case of an exception // select operation will be aborted and on_select_finish() // will be called for each select_case. // // But we don't reset m_notificator to nullptr in the case // of an exception from extract(). It is for the case if // mchain stores pointer to select_case and calls // select_case's notify() method just before select operation // is aborted (and on_select_finish() is called). // m_notificator = ¬ificator; demand_t demand; const auto status = extract( demand ); // Notificator pointer must retain its value only if // there is no messages in mchain. // In other cases this pointer must be dropped. if( extraction_status_t::no_messages != status ) m_notificator = nullptr; if( extraction_status_t::msg_extracted == status ) return try_handle_extracted_message( demand ); return mchain_receive_result_t{ 0u, 0u, status }; } protected : //! Attempt to handle extracted message. /*! * This method will be overriden in derived classes. */ [[nodiscard]] virtual mchain_receive_result_t try_handle_extracted_message( demand_t & demand ) = 0; }; // // send_select_case_t // /*! * \brief A base class for implementations of select_case for the * case of sending messages. * * This class implements pure virtual try_handle() methods but introduces * a new pure virtual method on_successful_push() that should * be implemented in a derived class. * * \since v.5.7.0 */ class send_select_case_t : public select_case_t { private : //! Type of message to be sent. std::type_index m_msg_type; //! Message to be sent. message_ref_t m_message; public : //! Initializing constructor. send_select_case_t( mchain_t chain, std::type_index msg_type, message_ref_t message ) : select_case_t{ std::move(chain) } , m_msg_type{ std::move(msg_type) } , m_message{ std::move(message) } {} [[nodiscard]] handling_result_t try_handle( select_notificator_t & notificator ) override { // Please note that value of m_notificator will be // returned to nullptr if a message stored into the mchain // or channel is closed. // // NOTE: if push() throws we don't know is the pointer // to this select_case stored inside mchain or not. // But is doesn't matter because in the case of an exception // select operation will be aborted and on_select_finish() // will be called for each select_case. // // But we don't reset m_notificator to nullptr in the case // of an exception from push(). It is for the case if // mchain stores pointer to select_case and calls // select_case's notify() method just before select operation // is aborted (and on_select_finish() is called). // m_notificator = ¬ificator; const auto status = push( m_msg_type, m_message ); // Notificator pointer must retain its value only if // message is deffered. // In other cases this pointer must be dropped. if( push_status_t::deffered != status ) m_notificator = nullptr; if( push_status_t::stored == status ) on_successful_push(); return mchain_send_result_t{ push_status_t::stored == status ? 1u : 0u, status }; } protected : //! Hook for handling successful push attempt. /*! * This method will be overriden in derived classes. */ virtual void on_successful_push() = 0; }; // // actual_receive_select_case_t // /*! * \brief Actual implementation of one multi chain select case. * * \since v.5.5.16 */ template< std::size_t N > class actual_receive_select_case_t : public receive_select_case_t { so_5::details::handlers_bunch_t< N > m_handlers; public : //! Initializing constructor. /*! * \tparam Handlers list of message handlers for messages extracted * from the mchain. */ template< typename... Handlers > actual_receive_select_case_t( //! Chain to be used for select. mchain_t chain, //! Message handlers. Handlers &&... handlers ) : receive_select_case_t( std::move(chain) ) { so_5::details::fill_handlers_bunch( m_handlers, 0, std::forward< Handlers >(handlers)... ); } protected : [[nodiscard]] mchain_receive_result_t try_handle_extracted_message( demand_t & demand ) override { const bool handled = m_handlers.handle( demand.m_msg_type, demand.m_message_ref ); return mchain_receive_result_t{ 1u, handled ? 1u : 0u, extraction_status_t::msg_extracted }; } }; // // actual_send_select_case_t // /*! * \brief The actual implementation of select_case for the case * of sending a message. * * This implementation calls an instance of On_Success_Handler handler * in on_successful_push() method. * * \tparam On_Success_Handler the type of handler to be called when * the message is sent. * * \since v.5.7.0 */ template< typename On_Success_Handler > class actual_send_select_case_t : public send_select_case_t { private : //! Actual handler of successful send attempt. On_Success_Handler m_success_handler; public : //! Initializing constructor for the case when success_handler is a const lvalue actual_send_select_case_t( mchain_t chain, std::type_index msg_type, message_ref_t message, const On_Success_Handler & success_handler ) : send_select_case_t{ std::move(chain), std::move(msg_type), std::move(message) } , m_success_handler{ success_handler } {} //! Initializing constructor for the case when success_handler is a rvalue. actual_send_select_case_t( mchain_t chain, std::type_index msg_type, message_ref_t message, On_Success_Handler && success_handler ) : send_select_case_t{ std::move(chain), std::move(msg_type), std::move(message) } , m_success_handler{ std::move(success_handler) } {} protected : void on_successful_push() override { m_success_handler(); } }; // // select_cases_holder_t // /*! * \brief A holder for serie of select_cases. * * Provides access to select_cases via iterator and begin() and end() methods. * * \note This is moveable class, but not copyable. * * \since v.5.5.16 */ template< std::size_t Cases_Count > class select_cases_holder_t { //! Type of array for holding select_cases. using array_type_t = std::array< select_case_unique_ptr_t, Cases_Count >; //! Storage for select_cases. array_type_t m_cases; public : select_cases_holder_t( const select_cases_holder_t & ) = delete; select_cases_holder_t & operator=( const select_cases_holder_t & ) = delete; //! Default constructor. select_cases_holder_t() {} //! Move constructor. select_cases_holder_t( select_cases_holder_t && o ) noexcept { swap( *this, o ); } //! Move operator. select_cases_holder_t & operator=( select_cases_holder_t && o ) noexcept { select_cases_holder_t tmp( std::move( o ) ); swap( *this, tmp ); return *this; } //! Swap operation. friend void swap( select_cases_holder_t & a, select_cases_holder_t & b ) noexcept { using std::swap; for( std::size_t i = 0; i != Cases_Count; ++i ) swap( a.m_cases[ i ], b.m_cases[ i ] ); } //! Helper method for setting up specific select_case. /*! * This method will be used during creation of select_cases_holder. */ void set_case( std::size_t index, select_case_unique_ptr_t c ) noexcept { m_cases[ index ] = std::move(c); } //! Get count of select_cases in holder. [[nodiscard]] std::size_t size() const noexcept { return Cases_Count; } //! Iterator class for accessing select_cases. /*! * Implements ForwardIterator concept. */ class const_iterator { using actual_it_t = typename array_type_t::const_iterator; actual_it_t m_it; public : using difference_type = std::ptrdiff_t; using value_type = select_case_t; using pointer = const value_type*; using reference = const value_type&; using iterator_category = std::forward_iterator_tag; const_iterator() = default; const_iterator( actual_it_t it ) noexcept : m_it( std::move(it) ) {} const_iterator & operator++() noexcept { ++m_it; return *this; } const_iterator operator++(int) noexcept { const_iterator o{ m_it }; ++m_it; return o; } bool operator==( const const_iterator & o ) const noexcept { return m_it == o.m_it; } bool operator!=( const const_iterator & o ) const noexcept { return m_it != o.m_it; } select_case_t & operator*() const noexcept { return **m_it; } select_case_t * operator->() const noexcept { return m_it->get(); } }; //! Get iterator for the first item in select_cases_holder. [[nodiscard]] const_iterator begin() const noexcept { return const_iterator{ m_cases.begin() }; } //! Get iterator for the item just behind the last item in select_cases_holder. [[nodiscard]] const_iterator end() const noexcept { return const_iterator{ m_cases.end() }; } }; // // fill_select_cases_holder // template< typename Holder > void fill_select_cases_holder( Holder & holder, std::size_t index, select_case_unique_ptr_t c ) { holder.set_case( index, std::move(c) ); } template< typename Holder, typename... Cases > void fill_select_cases_holder( Holder & holder, std::size_t index, select_case_unique_ptr_t c, Cases &&... other_cases ) { fill_select_cases_holder( holder, index, std::move(c)); fill_select_cases_holder( holder, index + 1, std::forward< Cases >(other_cases)... ); } /*! * \brief The current status of prepared-select instance. * * If prepared-select instance is activated (is used in select() call) * then this instance can't be activated yet more time. * * \since v.5.6.1 */ enum class prepared_select_status_t { //! Prepared-select instance is not used in select() call. passive, //! Prepared-select instance is used in select() call now. active }; /*! * \brief A data for prepared-select instance. * * \note * This data is protected by mutex. To get access to data it is * necessary to use instances activation_locker_t class. * * \attention * This class is not Moveable nor Copyable. * * \since v.5.6.1 */ template< std::size_t Cases_Count > class prepared_select_data_t { //! The object's lock. std::mutex m_lock; //! The current status of extensible-select object. prepared_select_status_t m_status{ prepared_select_status_t::passive }; //! Parameters for select. const mchain_select_params_t< mchain_props::msg_count_status_t::defined > m_params; //! A list of cases for extensible-select operation. select_cases_holder_t< Cases_Count > m_cases; public : prepared_select_data_t( const prepared_select_data_t & ) = delete; prepared_select_data_t( prepared_select_data_t && ) = delete; //! Initializing constructor. template< typename... Cases > prepared_select_data_t( mchain_select_params_t< msg_count_status_t::defined > && params, Cases && ...cases ) noexcept : m_params{ std::move(params) } { static_assert( sizeof...(Cases) == Cases_Count, "Cases_Count and sizeof...(Cases) mismatch" ); fill_select_cases_holder( m_cases, 0u, std::forward(cases)... ); } friend class activation_locker_t; /*! * \brief Special class for locking prepared-select instance * for activation inside select() call. * * This class acquires prepared-select instance's mutex for a * short time twice: * * - the first time in the constructor to check the status of * prepared-select instance and switch status to `active`; * - the second time in the destructor to return status to * `passive`. * * This logic allow an instance of activation_locker_t live for * long time but not to block other instances of * activation_locker_t. * * \attention * The constructor of activation_locker_t throws if prepared-select * instance is used in select() call. * * \note * This class is not Moveable nor Copyable. * * \since v.5.6.1 */ class activation_locker_t { outliving_reference_t< prepared_select_data_t > m_data; public : activation_locker_t( const activation_locker_t & ) = delete; activation_locker_t( activation_locker_t && ) = delete; activation_locker_t( outliving_reference_t< prepared_select_data_t > data ) : m_data{ data } { // Lock the data object only for changing the status. std::lock_guard< std::mutex > lock{ m_data.get().m_lock }; if( prepared_select_status_t::active == m_data.get().m_status ) SO_5_THROW_EXCEPTION( rc_prepared_select_is_active_now, "an activate prepared-select " "that is already active" ); m_data.get().m_status = prepared_select_status_t::active; } ~activation_locker_t() noexcept { // Lock the data object only for changing the status. std::lock_guard< std::mutex > lock{ m_data.get().m_lock }; m_data.get().m_status = prepared_select_status_t::passive; } const auto & params() const noexcept { return m_data.get().m_params; } const auto & cases() const noexcept { return m_data.get().m_cases; } }; }; // // extensible_select_cases_holder_t // /*! * \brief A holder for serie of select_cases for the case of extensible select. * * Provides access to select_cases via iterator and begin() and end() methods. * * \note This is moveable class, but not copyable. * * \since v.5.6.1 */ class extensible_select_cases_holder_t { //! Type of array for holding select_cases. using array_type_t = std::vector< select_case_unique_ptr_t >; //! Storage for select_cases. array_type_t m_cases; public : extensible_select_cases_holder_t( const extensible_select_cases_holder_t & ) = delete; extensible_select_cases_holder_t & operator=( const extensible_select_cases_holder_t & ) = delete; //! Swap operation. friend void swap( extensible_select_cases_holder_t & a, extensible_select_cases_holder_t & b ) noexcept { using std::swap; swap( a.m_cases, b.m_cases ); } //! Default constructor. extensible_select_cases_holder_t() {} //! Constructor with initial capacity. extensible_select_cases_holder_t( std::size_t initial_capacity ) { if( 0u != initial_capacity ) m_cases.reserve( initial_capacity ); } //! Move constructor. extensible_select_cases_holder_t( extensible_select_cases_holder_t && o ) noexcept : m_cases{ std::move(o.m_cases) } {} //! Move operator. extensible_select_cases_holder_t & operator=( extensible_select_cases_holder_t && o ) noexcept { extensible_select_cases_holder_t tmp( std::move( o ) ); swap( *this, tmp ); return *this; } //! Helper method for setting up specific select_case. /*! * This method will be used during creation of select_cases_holder. */ void add_case( select_case_unique_ptr_t c ) { m_cases.push_back( std::move(c) ); } //! Get count of select_cases in holder. std::size_t size() const noexcept { return m_cases.size(); } //! Iterator class for accessing select_cases. /*! * Implements ForwardIterator concept. */ class const_iterator { using actual_it_t = typename array_type_t::const_iterator; actual_it_t m_it; public : using difference_type = std::ptrdiff_t; using value_type = select_case_t; using pointer = const value_type*; using reference = const value_type&; using iterator_category = std::forward_iterator_tag; const_iterator() = default; const_iterator( actual_it_t it ) noexcept : m_it( std::move(it) ) {} const_iterator & operator++() noexcept { ++m_it; return *this; } const_iterator operator++(int) noexcept { const_iterator o{ m_it }; ++m_it; return o; } bool operator==( const const_iterator & o ) const noexcept { return m_it == o.m_it; } bool operator!=( const const_iterator & o ) const noexcept { return m_it != o.m_it; } select_case_t & operator*() const noexcept { return **m_it; } select_case_t * operator->() const noexcept { return m_it->get(); } }; //! Get iterator for the first item in select_cases_holder. const_iterator begin() const noexcept { return const_iterator{ m_cases.begin() }; } //! Get iterator for the item just behind the last item in select_cases_holder. const_iterator end() const noexcept { return const_iterator{ m_cases.end() }; } }; inline void fill_select_cases_holder( extensible_select_cases_holder_t & /*holder*/ ) {} template< typename... Cases > void fill_select_cases_holder( extensible_select_cases_holder_t & holder, select_case_unique_ptr_t c, Cases &&... other_cases ) { holder.add_case( std::move(c) ); if constexpr( 0u != sizeof...(other_cases) ) fill_select_cases_holder( holder, std::forward(other_cases)... ); } /*! * \brief The current status of extensible-select instance. * * If extensible-select instance is activated (is used in select() call) * then this instance can't be modified or activated yet more time. * * \since v.5.6.1 */ enum class extensible_select_status_t { //! Extensible-select instance is not used in select() call. passive, //! Extensible-select instance is used in select() call now. active }; /*! * \brief A data for extensible-select instance. * * \note * This data is protected by mutex. To get access to data it is * necessary to use instances of modification_locker_t and * activation_locker_t classes. * * \attention * This class is not Moveable nor Copyable. * * \since v.5.6.1 */ class extensible_select_data_t { //! The object's lock. std::mutex m_lock; //! The current status of extensible-select object. extensible_select_status_t m_status{ extensible_select_status_t::passive }; //! Parameters for select. const mchain_select_params_t< mchain_props::msg_count_status_t::defined > m_params; //! A list of cases for extensible-select operation. extensible_select_cases_holder_t m_cases; public : extensible_select_data_t( const extensible_select_data_t & ) = delete; extensible_select_data_t( extensible_select_data_t && ) = delete; //! Initializing constructor. extensible_select_data_t( mchain_select_params_t< msg_count_status_t::defined > && params, extensible_select_cases_holder_t && cases ) noexcept : m_params{ std::move(params) } , m_cases{ std::move(cases) } {} friend class modification_locker_t; /*! * \brief Special class for locking extensible-select instance * for modification. * * Acquires extensible-select instance's in the constructor and * releases in the destructor. * * It is possible to have several modification_locker_t instances * for one extensible-select instance in different threads at * the same time. All of them except one will be blocked on * extensible-select instance's mutex. * * \attention * The constructor of modification_locker_t throws if extensible-select * instance is used in select() call. * * \note * This class is not Moveable nor Copyable. * * \since v.5.6.1 */ class modification_locker_t { outliving_reference_t< extensible_select_data_t > m_data; std::lock_guard< std::mutex > m_lock; public : modification_locker_t( const modification_locker_t & ) = delete; modification_locker_t( modification_locker_t && ) = delete; modification_locker_t( outliving_reference_t< extensible_select_data_t > data ) : m_data{ data } , m_lock{ m_data.get().m_lock } { if( extensible_select_status_t::active == m_data.get().m_status ) SO_5_THROW_EXCEPTION( rc_extensible_select_is_active_now, "an attempt to modify extensible-select " "that is already active" ); } //! Get access to cases-holder for modification. auto & cases() const noexcept { return m_data.get().m_cases; } }; friend class activation_locker_t; /*! * \brief Special class for locking extensible-select instance * for activation inside select() call. * * This class acquires extensible-select instance's mutex for a * short time twice: * * - the first time in the constructor to check the status of * extensible-select instance and switch status to `active`; * - the second time in the destructor to return status to * `passive`. * * This logic allow an instance of activation_locker_t live for * long time but not to block other instances of * modification_locker_t or activation_locker_t. * * \attention * The constructor of activation_locker_t throws if extensible-select * instance is used in select() call. * * \note * This class is not Moveable nor Copyable. * * \since v.5.6.1 */ class activation_locker_t { outliving_reference_t< extensible_select_data_t > m_data; public : activation_locker_t( const activation_locker_t & ) = delete; activation_locker_t( activation_locker_t && ) = delete; activation_locker_t( outliving_reference_t< extensible_select_data_t > data ) : m_data{ data } { // Lock the data object only for changing the status. std::lock_guard< std::mutex > lock{ m_data.get().m_lock }; if( extensible_select_status_t::active == m_data.get().m_status ) SO_5_THROW_EXCEPTION( rc_extensible_select_is_active_now, "an activate extensible-select " "that is already active" ); m_data.get().m_status = extensible_select_status_t::active; } ~activation_locker_t() noexcept { // Lock the data object only for changing the status. std::lock_guard< std::mutex > lock{ m_data.get().m_lock }; m_data.get().m_status = extensible_select_status_t::passive; } const auto & params() const noexcept { return m_data.get().m_params; } const auto & cases() const noexcept { return m_data.get().m_cases; } }; }; #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnon-virtual-dtor" #endif // // actual_select_notificator_t // /*! * \brief Actual implementation of notificator for multi chain select. * * \since v.5.5.16 */ class actual_select_notificator_t : public select_notificator_t { private : std::mutex m_lock; std::condition_variable m_condition; //! Queue of already notified select_cases. select_case_t * m_tail = nullptr; /*! * \attention This method must be called only on locked object. */ void push_to_notified_chain( select_case_t & what ) noexcept { what.set_next( m_tail ); m_tail = &what; } public : /*! * \brief Initializing constructor. * * Intended to be used with select_cases_holder and its iterators. * * Every select_case is automatically added to the list of notified * select_cases. */ template< typename Fwd_it > actual_select_notificator_t( Fwd_it b, Fwd_it e ) { // All select_cases from range [b,e) must be included in // ready_cases list. while( b != e ) { b->set_next( m_tail ); m_tail = &(*b); ++b; } } void notify( select_case_t & what ) noexcept override { select_case_t * old_tail = nullptr; { std::lock_guard< std::mutex > lock{ m_lock }; old_tail = m_tail; push_to_notified_chain( what ); } if( !old_tail ) m_condition.notify_one(); } /*! * \brief Return specifed select_case object to the chain of * 'notified select_cases'. * * If a message has been read from a mchain then there could be * other messages in that mchain. Because of that the select_case * for that mchain must be seen as 'notified' -- it should be * processed on next call to wait() method. This method must be * used for immediately return of select_case to the chain of * 'notified select_cases'. */ void return_to_ready_chain( select_case_t & what ) noexcept { std::lock_guard< std::mutex > lock{ m_lock }; push_to_notified_chain( what ); } /*! * \brief Wait for any notified select_case. * * Waiting no more than \a wait_time. * * \return nullptr if there is no notified select_cases after * waiting for \a wait_time. */ [[nodiscard]] select_case_t * wait( //! Maximum waiting time for notified select_case. duration_t wait_time ) { std::unique_lock< std::mutex > lock{ m_lock }; if( !m_tail ) ::so_5::details::wait_for_big_interval( lock, m_condition, wait_time, [this]{ return m_tail != nullptr; } ); auto * result = m_tail; m_tail = nullptr; return result; } }; #if defined(__clang__) #pragma clang diagnostic pop #endif // // select_actions_performer_t // /*! * \brief Helper class for performing select-specific operations. * * \tparam Holder type of actual select_cases_holder_t. * * \since v.5.5.16 */ template< typename Holder > class select_actions_performer_t { const mchain_select_params_t< msg_count_status_t::defined > & m_params; const Holder & m_select_cases; actual_select_notificator_t m_notificator; std::size_t m_closed_chains = 0; std::size_t m_extracted_messages = 0; std::size_t m_handled_messages = 0; std::size_t m_sent_messages = 0; /*! * \brief The counter of completed send_cases. * * A send_case is completed if the corresponding message is sent * of if the target mchain is closed. * * \since v.5.7.0 */ std::size_t m_completed_send_cases = 0; extraction_status_t m_last_extraction_status = extraction_status_t::no_messages; bool m_can_continue = true; public : select_actions_performer_t( const mchain_select_params_t< msg_count_status_t::defined > & params, const Holder & select_cases ) : m_params( params ) , m_select_cases( select_cases ) , m_notificator( select_cases.begin(), select_cases.end() ) {} ~select_actions_performer_t() { for( auto & c : m_select_cases ) c.on_select_finish(); } void handle_next( const duration_t & wait_time ) { select_case_t * ready_chain = m_notificator.wait( wait_time ); if( !ready_chain ) { m_last_extraction_status = extraction_status_t::no_messages; update_can_continue_flag(); } else handle_ready_chain( ready_chain ); } extraction_status_t last_extraction_status() const noexcept { return m_last_extraction_status; } bool can_continue() const noexcept { return m_can_continue; } mchain_select_result_t make_result() const noexcept { return { m_extracted_messages, m_handled_messages, m_sent_messages, m_closed_chains, }; } private : friend struct select_result_handler_t; struct select_result_handler_t final { select_actions_performer_t * m_performer; select_case_t * m_current; void operator()( const mchain_receive_result_t & result ) const { m_performer->on_receive_result( m_current, result ); } void operator()( const mchain_send_result_t & result ) const { m_performer->on_send_result( m_current, result ); } }; void handle_ready_chain( select_case_t * ready_chain ) { while( ready_chain && m_can_continue ) { auto * current = ready_chain; ready_chain = current->giveout_next(); std::visit( select_result_handler_t{ this, current }, current->try_handle( m_notificator ) ); update_can_continue_flag(); } } void on_receive_result( select_case_t * current, const mchain_receive_result_t & result ) { m_last_extraction_status = result.status(); if( extraction_status_t::msg_extracted == result.status() ) { m_extracted_messages += result.extracted(); m_handled_messages += result.handled(); // The mchain from 'current' can contain more // messages. We should return this case to 'ready_chain' // of the notificator. m_notificator.return_to_ready_chain( *current ); } else if( extraction_status_t::chain_closed == result.status() ) { react_on_closed_chain( current ); } } void on_send_result( select_case_t * current, const mchain_send_result_t & result ) { // No extracted messages for that case. m_last_extraction_status = extraction_status_t::no_messages; switch( result.status() ) { case push_status_t::stored : ++m_completed_send_cases; m_sent_messages += result.sent(); break; case push_status_t::deffered : // Nothing to do. Another attempt could be performed later. break; case push_status_t::not_stored : // Message wasn't sent but the send_case completed. ++m_completed_send_cases; break; case push_status_t::chain_closed : // Message wasn't sent but the send_case completed. ++m_completed_send_cases; react_on_closed_chain( current ); break; } } void react_on_closed_chain( select_case_t * current ) { ++m_closed_chains; // Since v.5.5.17 chain_closed handler must be // used on chain_closed event. if( const auto & handler = m_params.closed_handler() ) so_5::details::invoke_noexcept_code( [&handler, current] { handler( current->chain() ); } ); } void update_can_continue_flag() { auto fn = [this] { if( m_closed_chains == m_select_cases.size() ) return false; if( m_completed_send_cases >= m_select_cases.size() ) return false; if( m_params.to_handle() && (m_handled_messages + m_completed_send_cases >= m_params.to_handle()) ) return false; if( m_params.to_extract() && m_extracted_messages >= m_params.to_extract() ) return false; if( m_params.stop_on() && m_params.stop_on()() ) return false; return true; }; m_can_continue = fn(); } }; template< typename Holder > mchain_select_result_t do_adv_select_with_total_time( const mchain_select_params_t< msg_count_status_t::defined > & params, const Holder & select_cases ) { using namespace so_5::details; select_actions_performer_t< Holder > performer{ params, select_cases }; remaining_time_counter_t total_time_counter{ params.total_time() }; remaining_time_counter_t wait_incoming_time{ params.empty_timeout() }; do { // Since v.5.8.4 we have to take the empty_timeout value // into the account. const duration_t this_iteration_wait_time = std::min( total_time_counter.remaining(), wait_incoming_time.remaining() ); performer.handle_next( this_iteration_wait_time ); if( extraction_status_t::msg_extracted == performer.last_extraction_status() ) // Becase some message extracted we must restart // wait_incoming_time counting. wait_incoming_time = remaining_time_counter_t{ params.empty_timeout() }; else // Otherwise wait_incoming_time should be updated to // reduce it value. wait_incoming_time.update(); total_time_counter.update(); } while( total_time_counter && wait_incoming_time && performer.can_continue() ); return performer.make_result(); } template< typename Holder > mchain_select_result_t do_adv_select_without_total_time( const mchain_select_params_t< msg_count_status_t::defined > & params, const Holder & select_cases ) { using namespace so_5::details; select_actions_performer_t< Holder > performer{ params, select_cases }; remaining_time_counter_t wait_time{ params.empty_timeout() }; do { performer.handle_next( wait_time.remaining() ); if( extraction_status_t::msg_extracted == performer.last_extraction_status() ) // Becase some message extracted we must restart wait_time // counting. wait_time = remaining_time_counter_t{ params.empty_timeout() }; else // There could be one of two situations: // 1) several threads do select on the same mchain. // Both threads will be awoken when some message is // pushed into the mchain. But only one thread will get // this message. Second thread will receive no_messages // status. In this case we should wait for the next message, // but wait_time must be decremented. // 2) some chain is closed. Wait time should be updated and // next wait attempt must be performed. wait_time.update(); } while( wait_time && performer.can_continue() ); return performer.make_result(); } // // perform_select // /*! * \brief Helper function with implementation of main select action. * * \since v.5.5.17 */ template< typename Cases_Holder > mchain_select_result_t perform_select( //! Parameters for advanced select. const mchain_select_params_t< msg_count_status_t::defined > & params, //! Select cases. const Cases_Holder & cases_holder ) { if( is_infinite_wait_timevalue( params.total_time() ) ) return do_adv_select_without_total_time( params, cases_holder ); else return do_adv_select_with_total_time( params, cases_holder ); } } /* namespace details */ } /* namespace mchain_props */ // // receive_case // /*! * \brief A helper for creation of select_case object for one multi chain * select. * * \attention It is an error if there are more than one handler for the * same message type in \a handlers. * * \sa so_5::select() * * \since v.5.5.16 */ template< typename... Handlers > [[nodiscard]] mchain_props::select_case_unique_ptr_t receive_case( //! Message chain to be used in select. mchain_t chain, //! Message handlers for messages extracted from that chain. Handlers &&... handlers ) { using namespace mchain_props; using namespace mchain_props::details; return select_case_unique_ptr_t{ new actual_receive_select_case_t< sizeof...(handlers) >{ std::move(chain), std::forward< Handlers >(handlers)... } }; } // // send_case // /*! * \brief A helper for creation of select_case object for one send-case * of a multi chain select. * * \sa so_5::select() * * \since v.5.7.0 */ template< typename Msg, message_ownership_t Ownership, typename On_Success_Handler > [[nodiscard]] mchain_props::select_case_unique_ptr_t send_case( //! Message chain to be used in select. mchain_t chain, //! Message instance to be sent. message_holder_t< Msg, Ownership > msg, On_Success_Handler && handler ) { using namespace mchain_props; using namespace mchain_props::details; using actual_handler_type = std::decay_t; using select_case_type = actual_send_select_case_t; return select_case_unique_ptr_t{ new select_case_type{ std::move(chain), message_payload_type::subscription_type_index(), msg.make_reference(), std::forward< On_Success_Handler >(handler) } }; } /*! * \brief An advanced form of multi chain select. * * \attention The behaviour is not defined if a mchain is used in different * select_cases. * * \attention * Since v.5.6.0 at least handle_all(), handle_n() or extract_n() should be * called before passing result of from_all() to select() function. * * \par Usage examples: \code using namespace so_5; mchain_t ch1 = env.create_mchain(...); mchain_t ch2 = env.create_mchain(...); // Receive and handle 3 messages. // It could be 3 messages from ch1. Or 2 messages from ch1 and 1 message // from ch2. Or 1 message from ch1 and 2 messages from ch2. And so on... // // If there is no 3 messages in mchains the select will wait infinitely. // A return from select will be after handling of 3 messages or // if all mchains are closed explicitely. select( from_all().handle_n( 3 ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); // Receive and handle 3 messages. // If there is no 3 messages in chains the select will wait // no more that 200ms. // A return from select will be after handling of 3 messages or // if all mchains are closed explicitely, or if there is no messages // for more than 200ms. select( from_all().handle_n( 3 ).empty_timeout( milliseconds(200) ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); // Receive all messages from mchains. // If there is no message in any of mchains then wait no more than 500ms. // A return from select will be after explicit close of all mchains // or if there is no messages for more than 500ms. select( from_all().handle_all().empty_timeout( milliseconds(500) ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); // Receve any number of messages from mchains but do waiting and // handling for no more than 2s. select( from_all().handle_all().total_time( seconds(2) ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); // Receve 1000 messages from chains but do waiting and // handling for no more than 2s. select( from_all().extract_n( 1000 ).total_time( seconds(2) ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); \endcode * * \note * Since v.5.8.4 the value of empty_timeout (or no_wait_on_empty) is * taken into account when `total_time` is specified. It means that * in the following example the select() returns immediately if * all mchains are empty: * \code * select( * from_all().handle_n(5).no_wait_on_empty().total_time(6s), * receive_case( ch1, ...), * receive_case( ch2, ...) ); * \endcode * * \since v.5.5.16 */ template< mchain_props::msg_count_status_t Msg_Count_Status, typename... Cases > mchain_select_result_t select( //! Parameters for advanced select. const mchain_select_params_t< Msg_Count_Status > & params, //! Select cases. Cases &&... cases ) { static_assert( Msg_Count_Status == mchain_props::msg_count_status_t::defined, "message count to be processed/extracted should be defined " "by using handle_all()/handle_n()/extract_n() methods" ); using namespace mchain_props; using namespace mchain_props::details; select_cases_holder_t< sizeof...(cases) > cases_holder; fill_select_cases_holder( cases_holder, 0, std::forward< Cases >(cases)... ); return perform_select( params, cases_holder ); } // // prepared_select_t // /*! * \brief Special container for holding select parameters and select cases. * * \note Instances of that type usually used without specifying the actual * type: * \code auto prepared = so_5::prepare_select( so_5::from_all().handle_n(10).empty_timeout(10s), receive_case( ch1, some_handlers... ), receive_case( ch2, more_handlers... ), ... ); ... auto r = so_5::select( prepared ); * \endcode * * \note * This is a moveable type, not copyable. It is very like to unique_ptr. * Because of that an instance of prepared_select_t can empty. It means * that the actual content (e.g. prepared-select object) was moved to * another prepared_select_t instance. Usage of empty prepared_select_t * is an error and can lead to null-pointer dereference. SObjectizer doesn't * check the emptiness of prepared_select_t object. * * \since v.5.5.17 */ template< std::size_t Cases_Count > class prepared_select_t { template< mchain_props::msg_count_status_t Msg_Count_Status, typename... Cases > friend prepared_select_t< sizeof...(Cases) > prepare_select( mchain_select_params_t< Msg_Count_Status > params, Cases &&... cases ); //! The actual prepared-select object. /*! * \note * Can be null if the actual content was moved to another * prepared_select_t instance. * * \since v.5.6.1 */ std::unique_ptr< mchain_props::details::prepared_select_data_t > m_data; //! Initializing constructor. /*! * \note * This constructor is private since v.5.6.1 */ template< typename... Cases > prepared_select_t( mchain_select_params_t< mchain_props::msg_count_status_t::defined > params, Cases &&... cases ) : m_data{ std::make_unique< mchain_props::details::prepared_select_data_t >( std::move(params), std::forward(cases)... ) } {} public : prepared_select_t( const prepared_select_t & ) = delete; prepared_select_t & operator=( const prepared_select_t & ) = delete; //! Move constructor. prepared_select_t( prepared_select_t && other ) noexcept : m_data( std::move(other.m_data) ) {} //! Move operator. prepared_select_t & operator=( prepared_select_t && other ) noexcept { prepared_select_t tmp( std::move(other) ); swap( *this, tmp); return *this; } //! Swap operation. friend void swap( prepared_select_t & a, prepared_select_t & b ) noexcept { using std::swap; swap( a.m_data, b.m_data ); } //! Is this handle empty? /*! * \since v.5.6.1 */ bool empty() const noexcept { return !m_data; } /*! * \name Getters * \{ */ auto & data() const noexcept { return *m_data; } /*! * \} */ }; // // prepare_select // /*! * \brief Create prepared select statement to be used later. * * \attention * Since v.5.6.0 at least handle_all(), handle_n() or extract_n() should be * called before passing result of from_all() to prepare_select() function. * * Accepts all parameters as advanced select() version. For example: * \code // Receive and handle 3 messages. // If there is no 3 messages in chains the select will wait // no more that 200ms. // A return from select will be after handling of 3 messages or // if all mchains are closed explicitely, or if there is no messages // for more than 200ms. auto prepared1 = prepare_select( so_5::from_all().handle_n( 3 ).empty_timeout( milliseconds(200) ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); // Receive all messages from mchains. // If there is no message in any of mchains then wait no more than 500ms. // A return from select will be after explicit close of all mchains // or if there is no messages for more than 500ms. auto prepared2 = prepare_select( so_5::from_all().handle_all().empty_timeout( milliseconds(500) ), receive_case( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), receive_case( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); * \endcode * * \note * Since v.5.8.4 the value of empty_timeout (or no_wait_on_empty) is * taken into account when `total_time` is specified. It means that * in the following example the select() returns immediately if * all mchains are empty: * \code * auto prepared = prepare_select( * from_all().handle_n(5).no_wait_on_empty().total_time(6s), * receive_case( ch1, ...), * receive_case( ch2, ...) ); * ... * so_5::select(prepared); * \endcode * * \since v.5.5.17 */ template< mchain_props::msg_count_status_t Msg_Count_Status, typename... Cases > prepared_select_t< sizeof...(Cases) > prepare_select( //! Parameters for advanced select. mchain_select_params_t< Msg_Count_Status > params, //! Select cases. Cases &&... cases ) { static_assert( Msg_Count_Status == mchain_props::msg_count_status_t::defined, "message count to be processed/extracted should be defined " "by using handle_all()/handle_n()/extract_n() methods" ); return prepared_select_t< sizeof...(Cases) >( std::move(params), std::forward(cases)... ); } /*! * \brief A select operation to be done on previously prepared select params. * * Usage of ordinary forms of select() functions inside loops could be * inefficient because of wasting resources on constructions of internal * objects with descriptions of select cases on each select() call. More * efficient way is preparation of all select params and reusing them later. A * combination of so_5::prepare_select() and so_5::select(prepared_select_t) * allows to do that. * * Usage example: * \code auto prepared = so_5::prepare_select( so_5::from_all().extract_n(10).empty_timeout(200ms), receive_case( ch1, some_handlers... ), receive_case( ch2, more_handlers... ), receive_case( ch3, yet_more_handlers... ) ); ... while( !some_condition ) { auto r = so_5::select( prepared ); ... } * \endcode * * \attention * Since v.5.6.1 there is a check for usage of prepared-select object * in parallel/nested calls to select(). If such call detected then * an exception is thrown. * * \since v.5.5.17 */ template< std::size_t Cases_Count > mchain_select_result_t select( const prepared_select_t< Cases_Count > & prepared ) { using namespace mchain_props::details; typename prepared_select_data_t::activation_locker_t locker{ outliving_mutable(prepared.data()) }; return mchain_props::details::perform_select( locker.params(), locker.cases() ); } // // extensible_select_t // /*! * \brief Special container for holding select parameters and select cases. * * This type is a *handle* for extensible-select instance. It's like * unique_ptr. Just one instance of extensible_select_t owns the instance * of extensible-select. * * \attention * Because extensible_select_t is like to unique_ptr it can be in an empty * state (it means that there is no actual extensible-select instance behind * the handle). Usage of empty extensible_select_t object in call to * select() is an error. SObjectizer doesn't check the emptiness of * extensible_select_t object. An attempt to pass an empty extensible_select_t * to select() will lead to null-pointer dereference. * * \note * This is Moveable type but not Copyable. * * \since v.5.6.1 */ class extensible_select_t { template< mchain_props::msg_count_status_t Msg_Count_Status, typename... Cases > friend extensible_select_t make_extensible_select( mchain_select_params_t< Msg_Count_Status > params, Cases &&... cases ); //! Actual data for that extensible-select. std::unique_ptr< mchain_props::details::extensible_select_data_t > m_data; //! Actual initializing constructor. extensible_select_t( std::unique_ptr< mchain_props::details::extensible_select_data_t > data ) : m_data{ std::move(data) } {} public : extensible_select_t( const extensible_select_t & ) = delete; extensible_select_t & operator=( const extensible_select_t & ) = delete; //! Default constructor. /*! * \attention * This constructor is intended for the cases like that: * \code * class some_my_data { * so_5::extensible_select_t m_select_handle; * ... * void on_some_stage() { * m_select_handle = so_5::make_extensible_select(...); * ... * } * void on_another_stage() { * so_5::add_select_cases(m_select_handle, ...); * ... * } * void on_yet_another_stage() { * auto r = so_5::select(m_select_handle); * ... * } * ... * }; * \endcode */ extensible_select_t() = default; //! Move constructor. extensible_select_t( extensible_select_t && other ) noexcept : m_data{ std::move(other.m_data) } {} //! Move operator. extensible_select_t & operator=( extensible_select_t && other ) noexcept { extensible_select_t tmp( std::move(other) ); swap( *this, tmp); return *this; } //! Swap operation. friend void swap( extensible_select_t & a, extensible_select_t & b ) noexcept { using std::swap; swap( a.m_data, b.m_data ); } //! Is this handle empty? bool empty() const noexcept { return !m_data; } /*! * \name Getters * \{ */ auto & data() const noexcept { return *m_data; } /*! * \} */ }; /*! * \brief Creation of extensible-select instance. * * This function creates an instance of extensible-select object that * can be used for subsequent calls to add_select_cases() and * select(). * * Usage examples: * \code * // Creation of extensible-select instance with initial set of cases. * auto sel = so_5::make_extensible_select( * so_5::from_all().handle_n(10), * receive_case(ch1, ...), * receive_case(ch2, ...)); * * // Creation of extensible-select instance without initial set of cases. * auto sel2 = so_5::make_extensible_select( * so_5::from_all().handle_n(20)); * // Cases should be added later. * so_5::add_select_cases(sel2, receive_case(ch1, ...)); * so_5::add_select_cases(sel2, * receive_case(ch2, ...), * receive_case(ch3, ...)); * \endcode * * \note * Since v.5.8.4 the value of empty_timeout (or no_wait_on_empty) is * taken into account when `total_time` is specified. It means that * in the following example the select() returns immediately if * all mchains are empty: * \code * // Creation of extensible-select instance with initial set of cases. * auto sel = so_5::make_extensible_select( * so_5::from_all().handle_n(10).no_wait_on_empty().total_time(6s), * receive_case(ch1, ...), * receive_case(ch2, ...)); * ... * so_5::add_select_cases(sel, ...); * ... * auto r = select(sel); * \endcode * * \since v.5.6.1 */ template< mchain_props::msg_count_status_t Msg_Count_Status, typename... Cases > [[nodiscard]] extensible_select_t make_extensible_select( //! Parameters for advanced select. mchain_select_params_t< Msg_Count_Status > params, //! Select cases. Cases &&... cases ) { static_assert( Msg_Count_Status == mchain_props::msg_count_status_t::defined, "message count to be processed/extracted should be defined " "by using handle_all()/handle_n()/extract_n() methods" ); using namespace mchain_props::details; extensible_select_cases_holder_t cases_holder{ sizeof...(cases) }; fill_select_cases_holder( cases_holder, std::forward(cases)... ); auto data = std::make_unique< extensible_select_data_t >( std::move(params), std::move(cases_holder) ); return { std::move(data) }; } /*! * \brief Add a portion of cases to extensible-select instance. * * Usage examples: * \code * // Creation of extensible-select instance without initial set of cases. * auto sel2 = so_5::make_extensible_select( * so_5::from_all().handle_n(20)); * // Cases should be added later. * so_5::add_select_cases(sel2, receive_case(ch1, ...)); * so_5::add_select_cases(sel2, * receive_case(ch2, ...), * receive_case(ch3, ...)); * \endcode * * \note * An attempt to call this function for extensible-select object that is * used in some select() call will lead to an exception. * * \attention * The \a extensible_select object must not be empty! * * \since v.5.6.1 */ template< typename... Cases > void add_select_cases( //! An instance of extensible-select to be extended. extensible_select_t & extensible_select, //! Select cases. Cases &&... cases ) { using namespace mchain_props::details; extensible_select_data_t::modification_locker_t locker{ outliving_mutable(extensible_select.data()) }; fill_select_cases_holder( locker.cases(), std::forward(cases)... ); } /*! * \brief A select operation to be done on previously prepared * extensible-select object. * * Usage example: * \code * void handle_messages_from(const std::vector & chains) { * auto sel = so_5::make_extensible_select(so_5::from_all().handle_all()); * * for(auto & ch : chains) * so_5::add_select_cases(receive_case(ch, ...)); * * auto r = so_5::select(sel); * ... // Handing of the select() result. * } * \endcode * * \note * An attempt to call this function for extensible-select object that is * used in some select() call will lead to an exception. * * \attention * The \a extensible_select object must not be empty! * * \since v.5.6.1 */ inline mchain_select_result_t select( const extensible_select_t & extensible_select ) { using namespace mchain_props::details; extensible_select_data_t::activation_locker_t locker{ outliving_mutable(extensible_select.data()) }; return mchain_props::details::perform_select( locker.params(), locker.cases() ); } } /* namespace so_5 */ #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic pop #endif #endif sobjectizer-5.8.6/dev/so_5/mchain_select_ifaces.hpp000066400000000000000000000153401523065210100223170ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Various stuff related to multi chain select. * * \note This file contains only publicly visible interfaces. * * \since v.5.5.16 */ #if !defined( SO_5_MCHAIN_SELECT_IFACES_HPP ) #define SO_5_MCHAIN_SELECT_IFACES_HPP #include #include #include namespace so_5 { namespace mchain_props { #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnon-virtual-dtor" #endif // // select_notificator_t // /*! * \brief An interface of select_case notificator. * * \note This class has no virtual destructor becase there is no * intention to create instances of select_notificators dynamically. * * \since v.5.5.16 */ class select_notificator_t { public : virtual void notify( select_case_t & what ) noexcept = 0; }; #if defined(__clang__) #pragma clang diagnostic pop #endif // // select_case_t // /*! * \brief Base class for representation of one case in multi chain select. * * \attention Objects of this class are not copyable nor moveable. * * \since v.5.5.16 */ class select_case_t { protected : //! Message chain to receive message from. mchain_t m_chain; //! Notificator to be used for notify sleeping thread. /*! * Can be null. It means that select_case is not used in * select queue for the mchain at that moment. * * There are just two methods where m_notificator changes its value: * * - try_handle() where m_notificator receives an actual pointer * (m_notificator can become nullptr again in try_handle() if * mchain has messages or was closed); * - on_select_finish() where m_notificator receives nullptr value if * it wasn't null yet. * * In the previous versions of SObjectizer m_notificator received * nullptr value during notification of new messages arrival or * closing of mchain. But this lead to data races and the behaviour * was changed. Now m_notificator can hold an actual pointer even * after notification was initiated. */ select_notificator_t * m_notificator = nullptr; //! Next select_case in queue. /*! * A select_case object can be included in one of two different queues: * - in select queue inside mchain (in this case m_notificator is not * nullptr). Next item in queue belongs to different select(). This * item must be notified in notify() method. * - in ready to use select_case queue. The select_case is added to that * queue when select_case is notified by mchain. The next item in * queue belongs to the same select(). */ select_case_t * m_next = nullptr; /*! * \brief Helper method for calling extract method of the target mchain. * * This method is intended to be called by a derived class. This helper * is necessary because only select_case_t class is declared as * friend for abstract_message_chain_t class. Classes that are * derived from select_case_t are not friends for abstract_message_chain_t * class and can't call protected/private methods of * abstract_message_chain_t. * * \since v.5.7.0 */ [[nodiscard]] auto extract( demand_t & demand ) { return m_chain->extract( demand, *this ); } /*! * \brief Helper method for calling push method of the target mchain. * * This method is intended to be called by a derived class. This helper * is necessary because only select_case_t class is declared as * friend for abstract_message_chain_t class. Classes that are * derived from select_case_t are not friends for abstract_message_chain_t * class and can't call protected/private methods of * abstract_message_chain_t. * * \since v.5.7.0 */ [[nodiscard]] auto push( //! Type of message/signal to be pushed. const std::type_index & msg_type, //! Message/signal to be pushed. const message_ref_t & message ) { return m_chain->push( msg_type, message, *this ); } public : //! The result of attempt of handling this case. using handling_result_t = std::variant< so_5::mchain_receive_result_t, so_5::mchain_send_result_t >; //! Initialized constructor. select_case_t( //! Message chain for that this select_case is created. mchain_t chain ) : m_chain{ std::move(chain) } {} select_case_t( const select_case_t & ) = delete; select_case_t( select_case_t && ) = delete; virtual ~select_case_t() {} //! Simple access to next item in the current queue to which //! select_case object belongs at this moment. /*! * \sa select_case_t::m_next */ [[nodiscard]] select_case_t * query_next() const noexcept { return m_next; } //! Get the next item in the current queue to which select_case belongs //! at this moment and drop this pointer to nullptr value. /*! * This method must be used if select_case object must be extracted from * the current queue. * * \sa select_case_t::m_next. */ [[nodiscard]] select_case_t * giveout_next() noexcept { auto n = m_next; m_next = nullptr; return n; } //! Set the next item in the current queue to which select_case belongs. /*! * \sa select_case_t::m_next. */ void set_next( select_case_t * next ) noexcept { m_next = next; } //! Notification for all waiting select_cases. /*! * This method is called by mchain if empty mchain becomes non-empty * or if it is closed. * * This method does notification for all members of select_case queue. * It means that mchain calls notify() for the head of the queue and * that head does notification for all other queue's members. */ void notify() noexcept { auto c = this; while( c ) { auto next = c->giveout_next(); c->m_notificator->notify( *c ); c = next; } } //! Reaction to the end of select work. /*! * \attention This method must be called before return from select() * function to ensure that mchain do not hold a pointer to non-existent * select_case object. * * \attention This method must not throw because it will be called from * destructor of RAII wrappers. */ void on_select_finish() noexcept { if( m_notificator ) { m_chain->remove_from_select( *this ); m_notificator = nullptr; } } //! An attempt to handle this case. [[nodiscard]] virtual handling_result_t try_handle( select_notificator_t & notificator ) = 0; //! Get the underlying mchain. /*! * \since v.5.5.17 */ [[nodiscard]] const mchain_t & chain() const noexcept { return m_chain; } }; // // select_case_unique_ptr_t // /*! * \brief An alias of unique_ptr for select_case. * \since v5.5.16 */ using select_case_unique_ptr_t = std::unique_ptr< select_case_t >; } /* namespace mchain_props */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/message.cpp000066400000000000000000000015441523065210100176270ustar00rootroot00000000000000/* SObjectizer 5. */ #include namespace so_5 { // // message_t // message_t::message_t() : m_mutability( message_mutability_t::immutable_message ) { } message_t::message_t( const message_t & other ) : atomic_refcounted_t() , m_mutability( other.m_mutability ) { } message_t::message_t( message_t && other ) : atomic_refcounted_t() , m_mutability( other.m_mutability ) { } message_t & message_t::operator=( const message_t & other ) { m_mutability = other.m_mutability; return *this; } message_t & message_t::operator=( message_t && other ) noexcept { m_mutability = other.m_mutability; return *this; } namespace message_limit { // // any_unspecified_message // any_unspecified_message::~any_unspecified_message() {} any_unspecified_message::any_unspecified_message() {} } /* namespace message_limit */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/message.hpp000066400000000000000000000567101523065210100176410ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A base class for the agent messages definition. */ #if !defined( SO_5_MESSAGE_HPP ) #define SO_5_MESSAGE_HPP #include #include #include #include #include #include #include #include #include #include namespace so_5 { // // message_t // //! A base class for agent messages. /*! * All messages for agents must be derived from this class. * * \attention This class should be used for all messages which * have an actual message data. For signals (messages without any data) * a signal_t class should be used as a base class. * * \note * Class message_t is derived from atomic_refcounted_t. * But atomic_refcounted_t is not Copyable or Movable class. * It means that copy/move constructors and operators for message_t * have no influence on the reference counter inside message_t. */ class SO_5_TYPE message_t : public atomic_refcounted_t { friend class impl::internal_message_iface_t; public: //! A short typedef for kind of message. /*! * \since v.5.5.23 */ using kind_t = ::so_5::message_kind_t; message_t(); message_t( const message_t & other ); message_t( message_t && other ); message_t & operator=( const message_t & other ); message_t & operator=( message_t && other ) noexcept; virtual ~message_t() noexcept = default; /*! * \brief Helper method for safe get of message mutability flag. * * Use this method instead of direct call to so5_message_mutability() * because \a what will be nullptr for signals. */ friend message_mutability_t message_mutability( const intrusive_ptr_t & what ) noexcept { message_mutability_t r = message_mutability_t::immutable_message; if( what ) r = what->so5_message_mutability(); return r; } /*! * \brief Helper method for get message mutability flag. */ friend message_mutability_t message_mutability( const message_t & what ) noexcept { return what.so5_message_mutability(); } /*! * \brief Helper method for safe change message mutability flag. * * Use this method instead of direct call to so5_change_mutability() * because \a what will be nullptr for signals. * * \note * This is a very dangerous operation. Don't do it by yourselt. * See message_t::so5_change_mutability() for more details. * * \attention * This function can throw. * For example underlying message object can prohibit changing * of message mutability. */ friend void change_message_mutability( //! Message instance to be modified if it is not a signal. intrusive_ptr_t< message_t> & what, //! New mutability flag for the message. message_mutability_t mutability ) { if( what ) what->so5_change_mutability( mutability ); } /*! * \brief Helper method for change message mutability flag. * * \note * This is a very dangerous operation. Don't do it by yourselt. * See message_t::so5_change_mutability() for more details. * * \attention * This function can throw. * For example underlying message object can prohibit changing * of message mutability. */ friend void change_message_mutability( //! Message instance to be modified. message_t & what, //! New mutability flag for the message. message_mutability_t mutability ) { what.so5_change_mutability( mutability ); } /*! * \brief Helper method for quering kind of the message. * * This helper function is necessary because it correctly * handles case when message is a signal. In that case pointer * to message instance will be null. * * \note * This function is part of internal implementation of SObjectizer. * Don't use it directly. It can be a subject of changes in some * future versions. * * \since v.5.5.23 */ friend message_kind_t message_kind( const so_5::intrusive_ptr_t< message_t > & what ) { if( what ) return what->so5_message_kind(); else return message_kind_t::signal; } /*! * \brief Helper method for quering kind of the message. * * \note * This function is part of internal implementation of SObjectizer. * Don't use it directly. It can be a subject of changes in some * future versions. * * \since v.5.5.23 */ friend message_kind_t message_kind( const message_t & what ) { return what.so5_message_kind(); } private : /*! * \brief Is message mutable or immutable? * * By default the message is immutable. * * \since v.5.5.19 */ message_mutability_t m_mutability; /*! * \brief Get message mutability flag. * * \note * This method is intended to be used by SObjectizer and * low-level SObjectizer extension. Because of that it is not * guaranteed that this method is part of stable SObjectizer API. * It can be changed or even removed in any future versions * of SObjectizer. * * \since v.5.5.19 */ virtual message_mutability_t so5_message_mutability() const noexcept { return m_mutability; } /*! * \brief Change message mutabilty flag. * * \attention * Changing mutability from message_mutability_t::immutable_message * to message_mutability_t::mutable_message is a very bad idea. * Please don't do this until you are know what you are doing. * * \note * This method is intended to be used by SObjectizer and * low-level SObjectizer extension. Because of that it is not * guaranteed that this method is part of stable SObjectizer API. * It can be changed or even removed in any future versions * of SObjectizer. * * \attention * This function can throw. * For example a derived class can prohibit changing * of message mutability. * * \since v.5.5.19 */ virtual void so5_change_mutability( //! New mutability flag for message_mutability_t mutability ) { m_mutability = mutability; } /*! * \brief Detect the kind of the message. * * \note * This method is intended to be used by SObjectizer and * low-level SObjectizer extension. Because of that it is not * guaranteed that this method is part of stable SObjectizer API. * It can be changed or even removed in any future versions * of SObjectizer. * * \since v.5.5.23 */ virtual kind_t so5_message_kind() const noexcept { return kind_t::classical_message; } }; // // message_ref_t // //! A smart reference to the message. /*! * \note Defined as typedef since v.5.2.0 */ using message_ref_t = intrusive_ptr_t< message_t >; // // signal_t // //! A base class for agent signals. /*! * All signals (messages without any data) for agents should be * derived from this class. * * \since v.5.2.0 */ class SO_5_TYPE signal_t : public message_t { private : // Note: clang-3.9 requires this on Windows platform. signal_t( const signal_t & ) = delete; signal_t( signal_t && ) = delete; signal_t & operator=( const signal_t & ) = delete; signal_t & operator=( signal_t && ) = delete; /*! * Private constructor to disable creation of instances of * derived classes. */ signal_t() = default; // Note: this method has no sence in v.5.5, becasue instances // of that class are not created at all. // But that can be changed in v.5.6.0. kind_t so5_message_kind() const noexcept override { return kind_t::classical_message; } public : ~signal_t() noexcept override = default; }; // // user_type_message_t // /*! * \brief Template class for representing object of user type as a message. * * \tparam T type of actual message. This type must have move- or copy * constructor. * * \since v.5.5.9 */ template< typename T > struct user_type_message_t : public message_t { //! Instance of user message. /*! * \note * Since v.5.5.19 it is not a const object anymore. */ T m_payload; //! Initializing constructor. template< typename... Args > user_type_message_t( Args &&... args ) : m_payload{ std::forward< Args >( args )... } {} //! Initialization from const T object. user_type_message_t( const T & o ) : m_payload{ o } {} //! Initialization from non-const T object. user_type_message_t( T & o ) : m_payload{ o } {} //! Initialization from temporary T object. user_type_message_t( T && o ) : m_payload{ std::move(o) } {} private : kind_t so5_message_kind() const noexcept override { return kind_t::user_type_message; } }; // // immutable_msg // /*! * \brief A special marker for immutable message. * * This marker tells that message can be sent to several receivers and * nobody can change the message content. * * \tparam M type of the message. * * \since v.5.5.19 */ template< typename M > struct immutable_msg final {}; // // mutable_msg // /*! * \brief A special marker for mutable message. * * This marker tells that message can be sent only for one receiver. * And that receiver will get an exclusive access to the message content. * It means that receiver can change message's content. * * \tparam M type of the message. * * \since v.5.5.19 */ template< typename M > struct mutable_msg final {}; namespace details { // // message_mutability_traits // /*! * \brief Detector of message type traits in dependency of message immutability * or mutability. * * \since v.5.5.19 */ template< typename T > struct message_mutability_traits { using payload_type = typename std::decay::type; using subscription_type = payload_type; using mhood_param_type = payload_type; static const constexpr message_mutability_t mutability = message_mutability_t::immutable_message; }; template< typename T > struct message_mutability_traits< immutable_msg > { using payload_type = T; using subscription_type = T; using mhood_param_type = T; static const constexpr message_mutability_t mutability = message_mutability_t::immutable_message; }; template< typename T > struct message_mutability_traits< mutable_msg > { using payload_type = T; using subscription_type = mutable_msg; using mhood_param_type = mutable_msg; static const constexpr message_mutability_t mutability = message_mutability_t::mutable_message; }; } /* namespace details */ // // is_user_type_message // /*! * \brief A helper for detection presence of message of user type. * * \tparam M type to test. * * \since v.5.5.9 */ template< typename M > struct is_user_type_message { enum { value = false }; }; template< typename M > struct is_user_type_message< user_type_message_t< M > > { enum { value = true }; }; // // is_signal // /*! * \brief A helper class for checking that message is a signal. * * \since v.5.5.4 */ template< class T > struct is_signal { enum { value = std::is_base_of< signal_t, typename details::message_mutability_traits::payload_type >::value }; }; // // is_classical_message // /*! * \brief A helper class for checking that message is a classical message * derived from %message_t class. * * \since v.5.5.9 */ template< class T > struct is_classical_message { enum { value = std::is_base_of< message_t, typename details::message_mutability_traits::payload_type >::value }; }; // // is_mutable_message // /*! * \brief A helper class for checking that message is a mutable message. * * \since v.5.5.19 */ template< typename T > struct is_mutable_message { enum { value = false }; }; template< typename T > struct is_mutable_message< mutable_msg > { enum { value = true }; }; // // ensure_not_signal // /*! * \brief A special compile-time checker to guarantee that the message * class is not a signal class. * * \since v.5.2.0 */ template< class Msg > void ensure_not_signal() { static_assert( !is_signal< Msg >::value, "message class must be derived from the message_t" ); } // // ensure_message_with_actual_data // /*! * \brief A special checker to guarantee that the message is an instance * of the message_t (not signal_t) and has a not-null pointer * to the message data. * * \note A check for the inheritance from the message_t is done at compile-time. * * \tparam Msg message type to be checked. * * \since v.5.2.0 */ template< class Msg > void ensure_message_with_actual_data( const Msg * m ) { ensure_not_signal< Msg >(); if( !m ) throw so_5::exception_t( "an attempt to send a message via nullptr", so_5::rc_null_message_data ); } // // ensure_not_mutable_signal // /*! * \brief A special compile-time checker to guarantee that S is not a * mutable signal. * * This check is intended to prevent usage of mutable_msg where S is * a signal type. * * \since v.5.5.19 */ template< class S > void ensure_not_mutable_signal() { static_assert( !is_signal::value || message_mutability_t::immutable_message == details::message_mutability_traits::mutability, "usage of mutable_msg where S is a signal is prohibited" ); } // // ensure_signal // /*! * \brief A special compile-time checker to guarantee that the Msg is derived * from the signal_t. * * \tparam Msg signal type to be checked. * * \since v.5.2.0 */ template< class Msg > void ensure_signal() { static_assert( is_signal< Msg >::value, "expected a type derived from the signal_t" ); // Added in v.5.5.19. // Msg must not be a mutable_msg. ensure_not_mutable_signal(); } // // ensure_classical_message // /*! * \brief A special compile-time checker to guarantee that Msg is derived from * %message_t. * * \tparam Msg type to be checked. * * \since v.5.5.9 */ template< typename Msg > void ensure_classical_message() { static_assert( is_classical_message< Msg >::value, "expected a type derived from the message_t" ); } // // message_payload_type_impl // /*! * \brief Implementation details for %message_payload_type. * * \note This specialization is for cases where T is derived from message_t. * In that case payload_type is the same as envelope_type. * * \since v.5.5.9 */ template< typename T, bool is_classical_message > struct message_payload_type_impl { //! Mutability traits for this message type. using mutability_traits = details::message_mutability_traits; //! Type visible to user. using payload_type = typename mutability_traits::payload_type; //! Type for message delivery. using envelope_type = payload_type; //! Type to which subscription must be done. using subscription_type = typename mutability_traits::subscription_type; //! Is it a signal type or message type. static constexpr const bool is_signal = ::so_5::is_signal::value; //! Type ID for subscription. [[nodiscard]] inline static std::type_index subscription_type_index() { // T must not be a mutable_msg. ensure_not_mutable_signal(); return typeid(subscription_type); } //! Helper for extraction of pointer to payload part. /*! * \note This method return non-const pointer because it is * necessary for so_5::mhood_t. */ [[nodiscard]] inline static payload_type * extract_payload_ptr( message_ref_t & msg ) { return dynamic_cast< payload_type * >( msg.get() ); } //! Helper for extraction of pointer to envelope part. /*! * The same implementation as for extract_payload_ptr(). */ [[nodiscard]] inline static envelope_type * extract_envelope_ptr( message_ref_t & msg ) { return dynamic_cast< envelope_type * >( msg.get() ); } //! Helper for getting a const reference to payload part. [[nodiscard]] inline static payload_type & payload_reference( message_t & msg ) { return dynamic_cast< payload_type & >( msg ); } //! Helper for getting message mutability flag. [[nodiscard]] inline static constexpr message_mutability_t mutability() { return mutability_traits::mutability; } }; /*! * \brief Implementation details for %message_payload_type. * * \note This specialization is for cases where T is not derived from message_t. * In that case payload_type is T, but envelope_type is user_type_message_t. * * \since v.5.5.9 */ template< typename T > struct message_payload_type_impl< T, false > { //! Mutability traits for this message type. using mutability_traits = details::message_mutability_traits; //! Type visible to user. using payload_type = typename mutability_traits::payload_type; //! Type for message delivery. using envelope_type = user_type_message_t< payload_type >; //! Type to which subscription must be done. using subscription_type = typename mutability_traits::subscription_type; //! Is it a signal type or message type. /*! * \note * User-defined type can't be a signal. */ static constexpr const bool is_signal = false; //! Type ID for subscription. [[nodiscard]] inline static std::type_index subscription_type_index() { return typeid(subscription_type); } //! Helper for extraction of pointer to payload part. /*! * \note This method return const pointer because payload is * a const object inside user_type_message_t instance. */ [[nodiscard]] inline static payload_type * extract_payload_ptr( message_ref_t & msg ) { auto envelope = dynamic_cast< envelope_type * >( msg.get() ); if( !envelope ) SO_5_THROW_EXCEPTION( so_5::rc_unexpected_error, "nullptr for user_type_message_t instance" ); return &(envelope->m_payload); } //! Helper for extraction of pointer to envelope part. [[nodiscard]] inline static envelope_type * extract_envelope_ptr( message_ref_t & msg ) { return dynamic_cast< envelope_type * >( msg.get() ); } //! Helper for getting a const reference to payload part. [[nodiscard]] inline static payload_type & payload_reference( message_t & msg ) { auto & envelope = dynamic_cast< envelope_type & >( msg ); return envelope.m_payload; } //! Helper for getting message mutability flag. [[nodiscard]] inline static constexpr message_mutability_t mutability() { return mutability_traits::mutability; } }; // // message_payload_type // /*! * \brief A helper class for detection of payload type of message. * * \tparam T type to test. * * \since v.5.5.9 */ template< typename T > struct message_payload_type : public message_payload_type_impl< T, is_classical_message< typename ::so_5::details::message_mutability_traits::payload_type >::value > { }; template< typename T > struct message_payload_type< user_type_message_t< T > > : public message_payload_type_impl< T, false > { }; namespace details { template< bool is_signal, typename Msg > struct make_message_instance_impl { using E = typename message_payload_type< Msg >::envelope_type; template< typename... Args > [[nodiscard]] static std::unique_ptr< E > make( Args &&... args ) { ensure_not_signal< Msg >(); auto r = std::unique_ptr< E >( new E( std::forward< Args >(args)... ) ); if constexpr( message_mutability_t::mutable_message == message_mutability_traits::mutability ) { change_message_mutability( *r, message_mutability_t::mutable_message ); } return r; } }; template< typename Msg > struct make_message_instance_impl< true, Msg > { [[nodiscard]] static std::unique_ptr< Msg > make() { ensure_signal< Msg >(); return std::unique_ptr< Msg >(); } }; /*! * \brief A helper for allocate instance of a message. * * \since v.5.5.4 */ template< typename Msg, typename... Args > [[nodiscard]] auto make_message_instance( Args &&... args ) -> std::unique_ptr< typename message_payload_type< Msg >::envelope_type > { return make_message_instance_impl< is_signal< Msg >::value, Msg >::make( std::forward< Args >( args )... ); } /*! * \brief A signal to be used for switching to another state by timeout. * * \note * It was a so_5::state_t::time_limit_t::msg_timeout in previous versions. * * \since v.5.8.3 */ struct msg_state_timeout final : public so_5::signal_t {}; } /* namespace details */ namespace message_limit { struct control_block_t; // // any_unspecified_message // /*! * \brief A special mark to be used for default limits. * * \since v.5.7.1 */ class SO_5_TYPE any_unspecified_message final { // Instances of that type can't be created. ~any_unspecified_message(); any_unspecified_message(); }; namespace impl { class action_msg_tracer_t; } /* namespace impl */ /*! * \brief Description of context for overlimit action. * * \since v.5.5.4 */ struct overlimit_context_t { //! ID of mbox which is used for message delivery. /*! * Added in v.5.5.23 because it is necessary for * so_5::enveloped_msg::handling_context_t. */ const mbox_id_t m_mbox_id; //! Delivery mode for message delivery attempt. /*! * \since v.5.8.0 */ const message_delivery_mode_t m_delivery_mode; //! Receiver of the message (or enveloped message). const agent_t & m_receiver; //! Control block for message limit. const control_block_t & m_limit; //! The current deep of overlimit reaction recursion. const unsigned int m_reaction_deep; //! Type of message to be delivered. const std::type_index & m_msg_type; //! A message (or enveloped message) to be delivered. const message_ref_t & m_message; /*! * \since v.5.5.9 * * \brief An optional pointer to tracer object for * message delivery tracing. * * \note Value nullptr means that message delivery tracing * is not used. */ const impl::action_msg_tracer_t * m_msg_tracer; //! Initializing constructor. inline overlimit_context_t( mbox_id_t mbox_id, message_delivery_mode_t delivery_mode, const agent_t & receiver, const control_block_t & limit, unsigned int reaction_deep, const std::type_index & msg_type, const message_ref_t & message, const impl::action_msg_tracer_t * msg_tracer ) : m_mbox_id( mbox_id ) , m_delivery_mode{ delivery_mode } , m_receiver( receiver ) , m_limit( limit ) , m_reaction_deep( reaction_deep ) , m_msg_type( msg_type ) , m_message( message ) , m_msg_tracer{ msg_tracer } {} }; // // action_t // /*! * \brief A type for reaction of message overlimit. * * \since v.5.5.4 */ using action_t = std::function< void(const overlimit_context_t&) >; // // control_block_t // /*! * \brief A control block for one message limit. * * \since v.5.5.4 */ struct control_block_t { //! Limit value. unsigned int m_limit; //! The current count of the messages of that type. mutable std::atomic_uint m_count; //! Limit overflow reaction. action_t m_action; //! Initializing constructor. control_block_t( unsigned int limit, action_t action ) : m_limit( limit ) , m_action( std::move( action ) ) { m_count = 0; } //! Copy constructor. control_block_t( const control_block_t & o ) : m_limit( o.m_limit ) , m_action( o.m_action ) { m_count.store( o.m_count.load( std::memory_order_acquire ), std::memory_order_release ); } //! Copy operator. control_block_t & operator=( const control_block_t & o ) { m_limit = o.m_limit; m_count.store( o.m_count.load( std::memory_order_acquire ), std::memory_order_release ); m_action = o.m_action; return *this; } //! A special indicator about absence of control_block. [[nodiscard]] inline static const control_block_t * none() { return nullptr; } //! A safe decrement of message count with respect to absence of limit //! for a message. inline static void decrement( const control_block_t * limit ) { if( limit ) --(limit->m_count); } }; } /* namespace message_limit */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/message_handler_format_detector.hpp000066400000000000000000000142521523065210100245720ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helpers for detection of message type by message handler format. * * \since * v.5.5.19.5 */ #if !defined( SO_5_MESSAGE_HANDLER_FORMAT_DETECTOR_HPP ) #define SO_5_MESSAGE_HANDLER_FORMAT_DETECTOR_HPP #include #include namespace so_5 { namespace details { // // message_handler_format_detector // /*! * \brief A helper template to detect type of message from the * format of an event handler. * * Usage example: * \code * template * void some_method( RET (AGENT::*)(HANDLER_ARG) ) { * using message_type = typename message_handler_format_detector< * HANDLER_ARG>::type; * ... * } * \endcode * * \since * v.5.5.19.5 */ template struct message_handler_format_detector { using type = typename message_payload_type::subscription_type; }; template struct message_handler_format_detector &> { using type = typename message_payload_type::subscription_type; }; template struct message_handler_format_detector> { using type = typename message_payload_type::subscription_type; }; template struct message_handler_format_detector { using type = typename message_payload_type::subscription_type; }; // // method_arity // /*! * \brief A special enumeration to specify arity of lambda-function or method. * * This enumeration is intended to be used in utility tools like * is_agent_method_pointer class. * * \since * v.5.6.0 */ enum class method_arity { //! Method or function has no arguments. nullary, //! Method or function has just one argument. unary }; // // is_agent_method_pointer // /*! * \brief Check whether T is a non-static member function pointer. * * If T is not a pointer to member function then is_agent_method_pointer::value * will be \a false. * * If T is a pointer to method without args then: * * is_agent_method_pointer::value is \a true; * * is_agent_method_pointer::arity is \a method_arity::nullary; * * there is a is_agent_method_pointer::agent_type type name; * * there is a is_agent_method_pointer::result_type type name. * * If T is a pointer to method with one argument then: * * is_agent_method_pointer::value is \a true; * * is_agent_method_pointer::arity is \a method_arity::unary; * * there is a is_agent_method_pointer::agent_type type name; * * there is a is_agent_method_pointer::result_type type name. * * there is a is_agent_method_pointer::argument_type type name. * * \since * v.5.5.20 */ template struct is_agent_method_pointer { static constexpr const bool value = false; }; namespace agent_method_pointer_details { enum class method_noexceptness_t { noexcept_method, not_noexcept_method }; inline constexpr method_noexceptness_t noexcept_method = method_noexceptness_t::noexcept_method; inline constexpr method_noexceptness_t not_noexcept_method = method_noexceptness_t::not_noexcept_method; template< typename Result, typename Agent, method_noexceptness_t NoExcept> struct no_arg { using result_type = Result; using agent_type = Agent; static constexpr const bool value = true; static constexpr const method_arity arity = method_arity::nullary; static constexpr const bool is_noexcept = (noexcept_method == NoExcept); }; template< typename Result, typename Agent, typename Handler_Argument, method_noexceptness_t NoExcept> struct with_arg { using result_type = Result; using agent_type = Agent; using argument_type = Handler_Argument; static constexpr const bool value = true; static constexpr const method_arity arity = method_arity::nullary; static constexpr const bool is_noexcept = (noexcept_method == NoExcept); }; } /* namespace agent_method_pointer_details */ template struct is_agent_method_pointer< method_arity::nullary, Result (Agent::*)() > : public agent_method_pointer_details::no_arg< Result, Agent, agent_method_pointer_details::not_noexcept_method> {}; template struct is_agent_method_pointer< method_arity::nullary, Result (Agent::*)() const > : public agent_method_pointer_details::no_arg< Result, Agent, agent_method_pointer_details::not_noexcept_method> {}; template struct is_agent_method_pointer< method_arity::nullary, Result (Agent::*)() noexcept > : public agent_method_pointer_details::no_arg< Result, Agent, agent_method_pointer_details::noexcept_method> {}; template struct is_agent_method_pointer< method_arity::nullary, Result (Agent::*)() const noexcept > : public agent_method_pointer_details::no_arg< Result, Agent, agent_method_pointer_details::noexcept_method> {}; template struct is_agent_method_pointer : public agent_method_pointer_details::with_arg< Result, Agent, Handler_Argument, agent_method_pointer_details::not_noexcept_method> {}; template struct is_agent_method_pointer : public agent_method_pointer_details::with_arg< Result, Agent, Handler_Argument, agent_method_pointer_details::not_noexcept_method> {}; template struct is_agent_method_pointer : public agent_method_pointer_details::with_arg< Result, Agent, Handler_Argument, agent_method_pointer_details::noexcept_method> {}; template struct is_agent_method_pointer : public agent_method_pointer_details::with_arg< Result, Agent, Handler_Argument, agent_method_pointer_details::noexcept_method> {}; } /* namespace details */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/message_holder.hpp000066400000000000000000000463301523065210100211730ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Definition of the template class message_holder. \since v.5.6.0 */ #if !defined( SO_5_MESSAGE_HOLDER_HPP ) #define SO_5_MESSAGE_HOLDER_HPP #include #include #include namespace so_5 { /*! * \brief Type of ownership of a message instance inside message_holder. * * This type is intended to be used as parameter for message_holder_t * template. * * \since * v.5.6.0 */ enum class message_ownership_t { //! Type of ownership will be automatically detected in dependency //! of message mutability. autodetected, //! An instance of message_holder should be the unique holder of //! the message instance. //! In that case message_holder will be similar to unique_ptr. unique, //! Several instances of message_holder can own the message instance. //! In that case message_holder will be similar to shared_ptr. shared }; namespace details { namespace message_holder_details { //! A helper function to get a const raw pointer from smart pointer. template< typename M > M * get_ptr( const intrusive_ptr_t & msg ) noexcept { return msg.get(); } //! A helper function to get a const raw pointer from smart pointer. /*! * This overload is for case when M is a user type message. */ template< typename M > M * get_ptr( const intrusive_ptr_t< user_type_message_t > & msg ) noexcept { return std::addressof(msg->m_payload); } /*! * \brief Basic part of message_holder implementations. * * Contains method which should be present in all implementations. * * \since * v.5.6.0 */ template< typename Payload, typename Envelope > class basic_message_holder_impl_t { protected : //! Message instance. /*! * Can be empty if the message_holder doens't hold anything * (in that case empty message_holder is an analogue of nullptr). */ intrusive_ptr_t< Envelope > m_msg; public : using payload_type = Payload; using envelope_type = Envelope; basic_message_holder_impl_t() noexcept = default; basic_message_holder_impl_t( intrusive_ptr_t< Envelope > msg ) noexcept : m_msg{ std::move(msg) } {} //! Drops to pointer to the message instance. /*! * The message_holder becomes empty as the result. */ void reset() noexcept { m_msg.reset(); } //! Check for the emptiness of message_holder. [[nodiscard]] bool empty() const noexcept { return !static_cast( m_msg ); } //! Check for the non-emptiness of message_holder. [[nodiscard]] operator bool() const noexcept { return !this->empty(); } //! Check for the emptiness of message_holder. [[nodiscard]] bool operator!() const noexcept { return this->empty(); } }; /*! * \brief A part of implementation of message_holder to be used for * shared ownership of message instances. * * This implementation allows copy and move. * * This implementation provides const make_reference() method that * returns a copy of underlying smart pointer. * * \since * v.5.6.0 */ template< typename Payload, typename Envelope > class shared_message_holder_impl_t : public basic_message_holder_impl_t { using direct_base_type = basic_message_holder_impl_t; public : using direct_base_type::direct_base_type; //! Make an another reference to the message. /*! * Returns empty smart pointer if message_holder is empty. */ [[nodiscard]] intrusive_ptr_t< Envelope > make_reference() const noexcept { return this->m_msg; } }; /*! * \brief A part of implementation of message_holder to be used for * unique ownership of message instances. * * This implementation allows only move operations and disables * copy constructor/operator. * * This implementation provides non-const make_reference() method that * extracts underlying smart pointer and leaves message_holder empty. * * \since * v.5.6.0 */ template< typename Payload, typename Envelope > class unique_message_holder_impl_t : public basic_message_holder_impl_t { using direct_base_type = basic_message_holder_impl_t; public : using direct_base_type::direct_base_type; unique_message_holder_impl_t( const unique_message_holder_impl_t & ) = delete; unique_message_holder_impl_t( unique_message_holder_impl_t && ) = default; unique_message_holder_impl_t & operator=( const unique_message_holder_impl_t & ) = delete; unique_message_holder_impl_t & operator=( unique_message_holder_impl_t && ) noexcept = default; //! Extracts the smart pointer to the message. /*! * Returns empty smart pointer if message_holder is empty. * * Leaves the message_holder instance empty. */ [[nodiscard]] intrusive_ptr_t< Envelope > make_reference() noexcept { return { std::move(this->m_msg) }; } }; /*! * \brief A meta-function for selection a base of message_holder implementation * in compile-time. * * If Ownership is message_ownership_t::autodetect then message's mutability * is examined. If message immutable then shared_message_holder_impl_t * will be selected as the base class. For mutable messages * unique_message_holder_impl_t will be used. * * \since * v.5.6.0 */ template< typename Msg, message_ownership_t Ownership > struct impl_selector { static_assert( !is_signal::value, "Signals can't be used with message_holder" ); using P = typename message_payload_type< Msg >::payload_type; using E = typename message_payload_type< Msg >::envelope_type; using type = std::conditional_t< message_ownership_t::autodetected == Ownership, std::conditional_t< message_mutability_t::immutable_message == message_mutability_traits::mutability, shared_message_holder_impl_t, unique_message_holder_impl_t >, std::conditional_t< message_ownership_t::shared == Ownership, shared_message_holder_impl_t, unique_message_holder_impl_t > >; }; /*! * \brief Just a shortcut for impl_selector meta-function. * * \since * v.5.6.0 */ template< typename Msg, message_ownership_t Ownership > using impl_selector_t = typename impl_selector::type; /*! * \brief An of mixin with getters for message_holder. * * It is assumed that shared_message_holder_impl_t or * unique_message_holder_impl_t will be used as Base template parameter. * * \since * v.5.6.0 */ template< typename Base, typename Return_Type > class msg_accessors_t : public Base { public : using Base::Base; //! Get a pointer to the message inside message_holder. /*! * \attention * Returns nullptr is message_holder is empty. */ [[nodiscard]] Return_Type * get() const noexcept { return get_ptr( this->m_msg ); } //! Get a reference to the message inside message_holder. /*! * \attention * An attempt to use this method on empty message_holder is UB. */ [[nodiscard]] Return_Type & operator * () const noexcept { return *get(); } //! Get a pointer to the message inside message_holder. /*! * \attention * An attempt to use this method on empty message_holder is UB. */ [[nodiscard]] Return_Type * operator->() const noexcept { return get(); } }; /*! * \brief A meta-function for selection of type of accessors mixin. * * \since * v.5.6.0 */ template< message_mutability_t Mutability, typename Base > struct accessor_selector { using type = std::conditional_t< message_mutability_t::immutable_message == Mutability, msg_accessors_t, msg_accessors_t >; }; /*! * \brief Just a shortcut for accessor_selector meta-function. * * \since * v.5.6.0 */ template< message_mutability_t Mutability, typename Base > using accessor_selector_t = typename accessor_selector::type; } /* namespace message_holder_details */ } /* namespace details */ /*! * \brief A class for holding an instance of a message. * * \attention * This class should be used with messages only. Signals are not supported * by that class. * * This class is intended for simplification of holding message instances * for some time and resending them later. For example: * \code * class my_actor final : public so_5::agent_t { * // A stored message. * so_5::message_holder_t stored_; * ... * void on_message(mhood_t cmd) { * // Store message inside the agent. * stored_ = cmd.make_holder(); * ... * // Initiate a delayed message to resend the stored message later. * so_5::send_delayed(*this, 10s); * } * ... * void on_resend_message(mhood_t) { * // Resend the stored message. * so_5::send(some_target, stored_); * // The stored message is no more needed. * stored_.reset(); * * // Or we can write: * // so_5::send(some_target, std::move(stored_)); * } * }; * \endcode * This class is also intended to be used with preallocated messages: * \code * class prealloc_msg_demo final : public so_5::agent_t { * so_5::message_holder_t request_; * ... * prealloc_msg_demo( * context_t ctx, * ... // Some other params. * ) : request_{std::piecewise_construct, ...} // Preallocation of message. * {} * * void on_some_event(...) { * ... * // It is time to send preallocated message. * so_5::send(some_target, request_); * ... * } * }; * \endcode * * The main benefit of that class is the ability to correctly handle * messages of arbitrary user types (e.g. messages not derived from * so_5::message_t class) and mutability flags. For example, the following * cases are correctly handled by message_holder_t: * \code * struct status_data { // This is message that is not derived from so_5::message_t. * ... // Some fields. * }; * * so_5::message_holder_t msg1; * so_5::message_holder_t> msg2; * so_5::message_holder_t> msg3; * \endcode * * This is an example of how immutable and mutable preallocated messages * can be used with message_holder_t: * \code * class preallocated_messages_owner final : public so_5::agent_t { * so_5::message_holder_t first_; * so_5::message_holder_t> second_; * ... * void on_some_event(mhood_t) { * // It is time to send preallocated messages. * * // This message will be sent as immutable message. * so_5::send(dest, first_); * * // This message will be sent as mutable message. * so_5::send(dest, std::move(second_)); * } * }; * \endcode * * \par Methods of message_holder_t class * * Class message_holder_t provides the following methods: * \code * // Default constructor. Creates an empty holder. * message_holder_t(); * * // Constructs holder for holding the specified message instance. * message_holder_t(so_5::intrusive_ptr msg); * * // Creates a new instance of message from 'args' and constructs holder for it. * template * message_holder_t(std::piecewise_construct_t, Args && ...args); * * // Creates a new instance of message from 'args' and constructs holder for it. * template * static message_holder_t make(Args && ...args); * * // Returns true if message_holder is empty. * bool empty() const noexcept; * bool operator!() const noexcept; * * // Returns true if message_holder is not empty. * operator bool() const noexcept; * * // Drops the content of message_holder. * void reset() noexcept; * \endcode * * There are also some more methods which are depend on mutability of * message and the type of ownership. They are described below. * * \par Getters are depend on mutability of message * * If a message_holder holds an immutable message then there are the following * getter methods: * \code * const payload_type * get() const noexcept; * const payload_type & operator*() const noexcept; * const payload_type * operator->() const noexcept; * \endcode * But if message_holder holds a mutable message those getters are still here * but they have non-const return type: * \code * payload_type * get() const noexcept; * payload_type & operator*() const noexcept; * payload_type * operator->() const noexcept; * \endcode * * \par Shared and unique ownership * * A message_holder works like a smart pointer. But what kind of smart pointer? * * It depends on Ownership template parameters. * * But default Ownership is so_5::message_ownership_t::autodetected. * In this case the behavior of a message_holder depends of the mutability * of message. If message is immutable then message_holders is like * std::shared_ptr: several message_holders can hold pointers to the * same message instances. * * If message is mutable then message_holder is like * std::unique_ptr: only one message_holder can hold a pointer to a message * instance. * * For example: * \code * // Immutable message. * so_5::message_holder_t msg1{std::piecewise_construct, ...}; * so_5::message_holder_t msg2{ msg1 }; * assert(msg1.get() == msg2.get()); // Now msg1 and msg2 refer to the same msg. * * // Mutable message. * so_5::message_holder_t> msg3{...}; * so_5::message_holder_t> msg4{ msg3 }; // WON'T COMPILE! * so_5::message_holder_t> msg5{ std::move(msg3) }; * assert(msg3.empty()); // Now msg3 is empty. * assert(!msg5.empty()); // And only msg5 holds the message. * \endcode * * The value of Ownership parameter can be specified manually. * In that case we can have an unique-holder for an immutable message: * \code * so_5::message_holder_t msg1{...}; * // WON'T COMPILE! * so_5::message_holder_t msg2{ msg1 }; * // Will compile but ownership will be moved: * so_5::message_holder_t msg3{ std::move(msg) }; * \endcode * There can also be a shared-holder for a mutable message: * \code * so_5::message_holder_t msg1{...}; * // No problems. * so_5::message_holder_t msg2{ msg1 }; * \endcode * But this approach should be taken with an additional care because it allows * to make several sends of the same mutable message instances at the same time. * * If a message_holder works as std::shared_ptr then there is the following * methods: * \code * // Copy constructor and operator. * message_holder_t(const message_holder_t &) noexcept; * message_holder_t & operator=(const message_holder_t &) noexcept; * // Move constructor and operator. * message_holder_t(message_holder_t &&) noexcept; * message_holder_t & operator=(message_holder_t &&) noexcept; * * // Getter for the underlying smart pointer. * intrusive_ptr_t make_reference() const noexcept; * \endcode * * If a message_holder works as std::unique_ptr then copy operator/constructors * are disabled and make_reference() leaves the message_holder object empty: * \code * // Move constructor and operator. * message_holder_t(message_holder_t &&) noexcept; * message_holder_t & operator=(message_holder_t &&) noexcept; * * // Extracts the underlying smart pointer. * // Leaves the message_holder object empty. * intrusive_ptr_t make_reference() noexcept; * \endcode * * \par Creation of an instance of message to be stored inside a message_holder * * There are several ways of creation of a message to be stored inside * a message_holder object. * * The recommended way is to use the constructor of message_holder with * std::piecewise_construct_t argument. This constructor automatically * creates an underlying message instance: * \code * struct my_msg { * int a_; * std::string b_; * }; * so_5::message_holder_t msg{std::piecewise_construct, * 0, // value for my_msg's a_ field. * "hello" // value for my_msg's b_ field. * }; * \endcode * Sometimes a static method make() can be used for similar purpose: * \code * auto make_message() { * return so_5::message_holder_t::make(0, "hello"); * } * \endcode * * But sometimes an instance of message is present as raw pointer, * std::unique_ptr or so_5::intrusive_ptr_t objects. In that case the * constructor that accepts intrusive_ptr_t can be used: * \code * // Somewhere in 3rd-party library. * std::unique_ptr make_message() { * return std::make_unique(...); * } * * // Somewhere in your code. * so_5::message_holder_t msg{make_message()}; * \endcode * * \since * v.5.6.0 */ template< typename Msg, message_ownership_t Ownership = message_ownership_t::autodetected > class message_holder_t : public details::message_holder_details::accessor_selector_t< details::message_mutability_traits::mutability, details::message_holder_details::impl_selector_t > { using base_type = details::message_holder_details::accessor_selector_t< details::message_mutability_traits::mutability, details::message_holder_details::impl_selector_t >; static_assert( !is_signal< Msg >::value, "message_holder_t can't be used with signals" ); public : using payload_type = typename base_type::payload_type; using envelope_type = typename base_type::envelope_type; using base_type::base_type; //! Special constructor for constructing message_holder with //! a new message instance inside. /*! * Usage example: * \code * struct my_message { * int a_; * std::string b_; * std::chrono::millisecons c_; * }; * * so_5::message_holder_t msg{ std::piecewise_construct, * 0, // value for my_message's a_ field. * "hello", // value for my_message's b_ field. * 15s // value for my_message's c_ field. * }; * \endcode */ template< typename... Args > message_holder_t( std::piecewise_construct_t, Args && ...args ) : base_type{ make_msg_instance( std::forward(args)... ) } {} friend void swap( message_holder_t & a, message_holder_t & b ) noexcept { using std::swap; swap( a.m_msg, b.m_msg ); } //! Create a new instance of message_holder with a new message inside. /*! * Usage example: * \code * struct my_message { * int a_; * std::string b_; * std::chrono::millisecons c_; * }; * * auto make_message() { * return so_5::message_holder_t::make( * 0, // value for my_message's a_ field. * "hello", // value for my_message's b_ field. * 15s ); // value for my_message's c_ field. * } * \endcode */ template< typename... Args > [[nodiscard]] static message_holder_t make( Args && ...args ) { return { make_msg_instance( std::forward(args)... ) }; } private : //! Create a new instance of message. template< typename... Args > [[nodiscard]] static intrusive_ptr_t< envelope_type > make_msg_instance( Args && ...args ) { using namespace details; // Mutability of a message will be changed appropriately // in make_message_instance. intrusive_ptr_t< envelope_type > msg{ make_message_instance< Msg >( std::forward(args)... ) }; return msg; } }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/message_limit.cpp000066400000000000000000000074061523065210100210300ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \brief Public part of message limit implementation. */ #include #include #include #include #include #include #include #include #include namespace so_5 { namespace message_limit { namespace impl { SO_5_FUNC void drop_message_reaction( const overlimit_context_t & ctx ) { if( ctx.m_msg_tracer ) ctx.m_msg_tracer->reaction_drop_message( &ctx.m_receiver ); } SO_5_FUNC void abort_app_reaction( const overlimit_context_t & ctx ) { so_5::details::abort_on_fatal_error( [&] { if( ctx.m_msg_tracer ) ctx.m_msg_tracer->reaction_abort_app( &ctx.m_receiver ); SO_5_LOG_ERROR( ctx.m_receiver.so_environment().error_logger(), logger ) logger << "message limit exceeded, application will be aborted. " << " msg_type: " << ctx.m_msg_type.name() << ", limit: " << ctx.m_limit.m_limit << ", agent: " << &(ctx.m_receiver) << std::endl; } ); } SO_5_FUNC void redirect_reaction( const overlimit_context_t & ctx, const mbox_t & to ) { if( ctx.m_reaction_deep >= max_redirection_deep ) { // NOTE: this fragment can throw but it isn't a problem // because redirect_reaction() is called during message // delivery process and exceptions are expected in that // process. SO_5_LOG_ERROR( ctx.m_receiver.so_environment().error_logger(), logger ) logger << "maximum message redirection deep exceeded on " "overlimit redirect_reaction; message will be ignored; " << " msg_type: " << ctx.m_msg_type.name() << ", limit: " << ctx.m_limit.m_limit << ", agent: " << &(ctx.m_receiver) << ", target_mbox: " << to->query_name(); } else { if( ctx.m_msg_tracer ) ctx.m_msg_tracer->reaction_redirect_message( &ctx.m_receiver, to ); // Since v.5.8.0 nonblocking delivery mode // has to be used for redirection. // Otherwise the timer thread can be blocked if // the destination is a full mchain. to->do_deliver_message( message_delivery_mode_t::nonblocking, ctx.m_msg_type, ctx.m_message, ctx.m_reaction_deep + 1 ); } } SO_5_FUNC void transform_reaction( const overlimit_context_t & ctx, const mbox_t & to, const std::type_index & msg_type, const message_ref_t & message ) { if( ctx.m_reaction_deep >= max_redirection_deep ) { // NOTE: this fragment can throw but it isn't a problem // because transform_reaction() is called during message // delivery process and exceptions are expected in that // process. SO_5_LOG_ERROR( ctx.m_receiver.so_environment().error_logger(), logger ) logger << "maximum message redirection deep exceeded on " "overlimit transform_reaction; message will be ignored;" << " original_msg_type: " << ctx.m_msg_type.name() << ", limit: " << ctx.m_limit.m_limit << ", agent: " << &(ctx.m_receiver) << ", result_msg_type: " << msg_type.name() << ", target_mbox: " << to->query_name(); } else { if( ctx.m_msg_tracer ) ctx.m_msg_tracer->reaction_transform( &ctx.m_receiver, to, msg_type, message ); // Since v.5.8.0 nonblocking delivery mode // has to be used for redirection. // Otherwise the timer thread can be blocked if // the destination is a full mchain. to->do_deliver_message( message_delivery_mode_t::nonblocking, msg_type, message, ctx.m_reaction_deep + 1 ); } } } /* namespace impl */ } /* namespace message_limit */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/message_limit.hpp000066400000000000000000000525411523065210100210350ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Public part of message limit implementation. * * \since v.5.5.4 */ #if !defined( SO_5_MESSAGE_LIMIT_HPP ) #define SO_5_MESSAGE_LIMIT_HPP #include #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace message_limit { // // description_t // /*! * \since * v.5.5.4 * * \brief A description of one message limit. */ struct description_t { //! Type of message. std::type_index m_msg_type; //! Max count of waiting messages. unsigned int m_limit; //! Reaction to overload. action_t m_action; //! Initializing constructor. description_t( std::type_index msg_type, unsigned int limit, action_t action ) : m_msg_type( std::move( msg_type ) ) , m_limit( limit ) , m_action( std::move( action ) ) {} }; // // description_container_t // /*! * \since * v.5.5.4 * * \brief Type of container for holding message limit descriptions. */ using description_container_t = std::vector< description_t >; namespace impl { /*! * \since * v.5.5.9 * * \brief Actual implementation of drop message reaction. */ SO_5_FUNC void drop_message_reaction( const overlimit_context_t & ctx ); } /* namespace impl */ // // drop_indicator_t // /*! * \since * v.5.5.4 * * \brief Message limit with reaction 'drop new message'. */ template< class M > struct drop_indicator_t { //! Max count of waiting messages. const unsigned int m_limit; //! Initializing constructor. drop_indicator_t( unsigned int limit ) : m_limit( limit ) {} }; // // accept_one_indicator // /*! * \since * v.5.5.4 * * \brief Helper function for accepting drop_indicator and storing * the corresponding description into the limits container. */ template< class M > void accept_one_indicator( //! Container for storing new description to. description_container_t & to, //! An instance of drop_indicator. const drop_indicator_t< M > & indicator ) { to.emplace_back( message_payload_type< M >::subscription_type_index(), indicator.m_limit, &impl::drop_message_reaction ); } namespace impl { /*! * \since * v.5.5.4 * * \brief Actual implementation of abort application reaction. */ SO_5_FUNC void abort_app_reaction( const overlimit_context_t & ctx ); } /* namespace impl */ // // abort_app_indicator_t // /*! * \since * v.5.5.4 * * \brief Message limit with reaction 'abort the application'. */ template< class M > struct abort_app_indicator_t { //! Max count of waiting messages. const unsigned int m_limit; //! Initializing constructor. abort_app_indicator_t( unsigned int limit ) : m_limit( limit ) {} }; /*! * \since * v.5.5.4 * * \brief Helper function for accepting abort_app_indicator and storing * the corresponding description into the limits container. */ template< class M > void accept_one_indicator( //! Container for storing new description to. description_container_t & to, //! An instance of abort_app_indicator to store. const abort_app_indicator_t< M > & indicator ) { to.emplace_back( message_payload_type< M >::subscription_type_index(), indicator.m_limit, []( const overlimit_context_t & ctx ) { impl::abort_app_reaction( ctx ); } ); } /*! * \since * v.5.5.8 * * \brief Message limit with reaction 'abort the application' and the * possibility to call additional lambda before aborting the app. */ template< typename M, typename L > struct log_then_abort_app_indicator_t { //! Max count of waiting messages. const unsigned int m_limit; //! Lambda for some last actions. const L m_lambda; //! Initializing constructor. log_then_abort_app_indicator_t( unsigned int limit, L lambda ) : m_limit( limit ) , m_lambda( std::move( lambda ) ) {} }; namespace impl { /*! * \since * v.5.5.8 * * \brief Helper class for calling pre-abort action. */ template< bool is_message, typename M, typename L > struct call_pre_abort_action_impl { static void call( const overlimit_context_t & ctx, L action ) { const auto & m = message_payload_type< M >::payload_reference( *ctx.m_message ); action( ctx.m_receiver, m ); } }; /*! * \since * v.5.5.8 * * \brief Specialization for the case of signal. */ template< typename M, typename L > struct call_pre_abort_action_impl< false, M, L > { static void call( const overlimit_context_t & ctx, L action ) { action( ctx.m_receiver ); } }; /*! * \since * v.5.5.8 * * \brief Helper class for calling pre-abort action. * * \tparam M type of message or signal * \tparam L lambda function to be called before aborting app. * * This lambda function must no throw exceptions. * For the case when M is a message type L should have prototype: * \code void lambda(const agent_t &, const M &); * \endcode * For the case when M is a signal type L should have prototype: * \code void lambda(const agent_t &); * \endcode */ template< typename M, typename L > struct call_pre_abort_action : public call_pre_abort_action_impl< !is_signal< M >::value, M, L > {}; } /* namespace impl */ /*! * \since * v.5.5.8 * * \brief Helper function for accepting log_then_abort_app_indicator and * storing the corresponding description into the limits container. */ template< class M, class L > void accept_one_indicator( //! Container for storing new description to. description_container_t & to, //! An instance of abort_app_indicator to store. const log_then_abort_app_indicator_t< M, L > & indicator ) { auto lambda = indicator.m_lambda; to.emplace_back( message_payload_type< M >::subscription_type_index(), indicator.m_limit, [lambda]( const overlimit_context_t & ctx ) { so_5::details::invoke_noexcept_code( [&] { impl::call_pre_abort_action< M, L >::call( ctx, lambda ); } ); impl::abort_app_reaction( ctx ); } ); } namespace impl { /*! * \since * v.5.5.4 * * \brief Actual implementation of redirect message reaction. */ SO_5_FUNC void redirect_reaction( //! Context on which overlimit must be handled. const overlimit_context_t & ctx, //! Destination for message redirection. const mbox_t & to ); } /* namespace impl */ // // redirect_indicator_t // /*! * \since * v.5.5.4 * * \brief Indication that a message must be redirected on overlimit. * * \tparam Msg Message type of message/signal to be redirected. * \tparam Lambda Type of lambda- or functional object which returns * actual mbox for redirection. */ template< typename Msg, typename Lambda > struct redirect_indicator_t { //! Max count of waiting messages. const unsigned int m_limit; //! A lambda/functional object which returns mbox for redirection. Lambda m_destination_getter; //! Initializing constructor. redirect_indicator_t( unsigned int limit, Lambda destination_getter ) : m_limit( limit ) , m_destination_getter( std::move( destination_getter ) ) {} }; /*! * \since * v.5.5.4 * * \brief Helper function for accepting redirect_indicator and storing * the corresponding description into the limits container. */ template< typename Msg, typename Lambda > void accept_one_indicator( //! Container for storing new description to. description_container_t & to, //! An instance of redirect_indicator to be stored. redirect_indicator_t< Msg, Lambda > indicator ) { Lambda dest_getter = std::move( indicator.m_destination_getter ); to.emplace_back( message_payload_type< Msg >::subscription_type_index(), indicator.m_limit, [dest_getter]( const overlimit_context_t & ctx ) { impl::redirect_reaction( ctx, dest_getter() ); } ); } namespace impl { /*! * \since * v.5.5.4 * * \brief Actual implementation of transform reaction. */ SO_5_FUNC void transform_reaction( //! Context on which overlimit must be handled. const overlimit_context_t & ctx, //! Destination for new message. const mbox_t & to, //! Type of new message. const std::type_index & msg_type, //! An instance of new message. const message_ref_t & message ); /*! * \brief Helper function to make an action that performs message transformation. * * Wraps \a transformator into an instance of type action_t. * * \tparam Message_Type type of message to be transformed. This type should be * a type of payload without so_5::immutable_msg or so_5::mutable_msg * modifications. * * \tparam Message_Mutability mutability of message. It's required to detect * actual type of reference (Message_Type& or const Message_Type&) to be passed * to \a transformator. * * \tparam Lambda type of \a transformator. * * \since v.5.8.1 */ template< typename Message_Type, message_mutability_t Message_Mutability, typename Lambda > [[nodiscard]] action_t make_action_for_message_transformer( Lambda && transformator ) { using msg_payload_t = std::conditional_t< message_mutability_t::mutable_message == Message_Mutability, // No const if message is mutable. typename message_payload_type< Message_Type >::payload_type, // Const if message is immutable. std::add_const_t< typename message_payload_type< Message_Type >::payload_type > >; action_t act = [transformator]( const overlimit_context_t & ctx ) { const auto actual_transform = [&]( const message_ref_t & msg_to_transform ) { msg_payload_t & msg = message_payload_type< Message_Type >::payload_reference( *msg_to_transform.get() ); auto r = transformator( msg ); impl::transform_reaction( ctx, r.mbox(), r.msg_type(), r.message() ); }; // Envelopes should be handled a special way. // Payload must be extrected and checked for presence. if( message_t::kind_t::enveloped_msg == message_kind( ctx.m_message ) ) { const auto opt_payload = ::so_5::enveloped_msg:: extract_payload_for_message_transformation( ctx.m_message ); // Payload can be optional. Se we will perform // transformation only if payload is present. if( opt_payload ) { actual_transform( opt_payload->message() ); } } else { actual_transform( ctx.m_message ); } }; return act; } /*! * \brief Helper function to make an action that performs signal transformation. * * Wraps \a transformator into an instance of type action_t. * * \tparam Source type of signal to be transformed. * * \tparam Lambda type of \a transformator. * * \since v.5.8.1 */ template< typename Source, typename Lambda > [[nodiscard]] action_t make_action_for_signal_transformer( Lambda && transformator ) { action_t act = [transformator]( const overlimit_context_t & ctx ) { const auto actual_transform = [&]() { auto r = transformator(); impl::transform_reaction( ctx, r.mbox(), r.msg_type(), r.message() ); }; // Envelopes should be handled a special way. // Payload must be extrected and checked for presence. if( message_t::kind_t::enveloped_msg == message_kind( ctx.m_message ) ) { const auto opt_payload = ::so_5::enveloped_msg:: extract_payload_for_message_transformation( ctx.m_message ); // Payload can be optional. Se we will perform // transformation only if payload is present. if( opt_payload ) { actual_transform(); } } else { actual_transform(); } }; return act; } } /* namespace impl */ // // transform_indicator_t // /*! * \since * v.5.5.4 * * \brief An indicator of transform reaction on message overlimit. * * \tparam Source Type of message to be transformed. */ template< typename Source > struct transform_indicator_t { //! Limit value. unsigned int m_limit; //! Reaction. action_t m_action; //! Initializing constructor. transform_indicator_t( unsigned int limit, action_t action ) : m_limit( limit ) , m_action( std::move( action ) ) {} }; /*! * \since * v.5.5.4 * * \brief Helper function for accepting transform_indicator and storing * the corresponding description into the limits container. */ template< class M > void accept_one_indicator( //! Container for storing new description to. description_container_t & to, //! An instance of transform_indicator to be stored. transform_indicator_t< M > indicator ) { to.emplace_back( message_payload_type< M >::subscription_type_index(), indicator.m_limit, std::move( indicator.m_action ) ); } // // accept_indicators // /*! * \since * v.5.5.4 * * \brief Helper function for constructing limits description from * a series of limit indicators. */ template< typename I, typename... Args > void accept_indicators( description_container_t & to, I && indicator, Args &&... others ) { accept_one_indicator( to, std::forward< I >( indicator ) ); accept_indicators( to, std::forward< Args >( others )... ); } inline void accept_indicators( description_container_t & ) { } // // message_limit_methods_mixin_t // /*! * \since * v.5.5.4 * * \brief A mixin with message limit definition methods. */ struct message_limit_methods_mixin_t { using any_unspecified_message = ::so_5::message_limit::any_unspecified_message; /*! * \since * v.5.5.4 * * \brief A helper function for creating drop_indicator. */ template< typename Msg > [[nodiscard]] static drop_indicator_t< Msg > limit_then_drop( unsigned int limit ) { return drop_indicator_t< Msg >( limit ); } /*! * \since * v.5.5.4 * * \brief A helper function for creating abort_app_indicator. */ template< typename Msg > [[nodiscard]] static abort_app_indicator_t< Msg > limit_then_abort( unsigned int limit ) { return abort_app_indicator_t< Msg >( limit ); } /*! * \since * v.5.5.8 * * \brief A helper function for creating log_then_abort_app_indicator. * * \tparam M type of message or signal * \tparam L lambda function to be called before aborting app. * * This lambda function must no throw exceptions. * For the case when M is a message type L should have prototype: * \code void lambda(const agent_t &, const M &); * \endcode * For the case when M is a signal type L should have prototype: * \code void lambda(const agent_t &); * \endcode */ template< typename M, typename L > [[nodiscard]] static log_then_abort_app_indicator_t< M, L > limit_then_abort( unsigned int limit, L lambda ) { return log_then_abort_app_indicator_t< M, L >{ limit, std::move(lambda) }; } /*! * \brief A helper function for creating redirect_indicator. * * The \a dest_getter should be a functor (or just function) with the * following prototype: * \code * so_5::mbox_t dest_getter(); * \endcode * * \par Usage example: * \code * class main_message_handler : public so_5::agent_t { * // Destination for message for an overload case. * // NOTE: this mbox isn't known in the constructor. * so_5::mbox_t overload_dest_; * ... * public: * main_message_handler(context_t ctx) * : so_5::agent_t{ ctx * + limit_then_redirect( 10, * [this]() { return overload_dest_; } ) * } * {} * * // Setter for overload_dest. * void set_overload_dest(const so_5::mbox_t & dest) { * overload_dest_ = dest; * } * ... * }; * \endcode * * \since v.5.5.4 */ template< typename Msg, typename Lambda > [[nodiscard]] static redirect_indicator_t< Msg, Lambda > limit_then_redirect( unsigned int limit, Lambda dest_getter ) { return redirect_indicator_t< Msg, Lambda >( limit, std::move( dest_getter ) ); } /*! * \brief A helper function for creating redirect_indicator. * * This helper can be used if the target mbox is already known: * \code * class processor final : public so_5::agent_t * { * public: * processor(context_t ctx, so_5::mbox_t overload_handler) * : so_5::agent_t{ ctx * + limit_then_redirect< some_message >( 10u, overload_handler ) * + limit_then_redirect< another_message >( 20u, overload_handler ) * } * {...} * * ... * }; * \endcode * * \since * v.5.7.1 */ template< typename Msg > [[nodiscard]] static auto limit_then_redirect( unsigned int limit, mbox_t destination ) { return limit_then_redirect< Msg >( limit, [dest = std::move(destination)]() -> const so_5::mbox_t & { return dest; } ); } /*! * \brief A helper function for creating transform_indicator. * * \note Must be used for message transformation, signals cannot be * transformed. Type of message is detected automatically from the type * of transformation lambda argument. * * \par Usage example: * \code class a_request_processor_t : public so_5::agent_t { public : a_request_processor_t( context_t ctx ) : so_5::agent_t( ctx // Limit count of requests in the queue. // If queue is full then request must be transformed // to negative reply. + limit_then_transform( 3, []( const request & evt ) { return make_transformed< reply >( evt.m_reply_to, evt.m_id, false ); } ) ) {...} ... }; * \endcode * * \since v.5.5.4 */ template< typename Lambda > [[nodiscard]] static auto limit_then_transform( unsigned int limit, Lambda && transformator ) { using Message_Type = typename so_5::details::lambda_traits:: argument_type_if_lambda< Lambda >::type; ensure_not_signal< Message_Type >(); static_assert( !std::is_same_v< Message_Type, any_unspecified_message >, "limit_then_transform can't be used for any_unspecified_message" ); action_t act = impl::make_action_for_message_transformer< Message_Type, message_mutability_t::immutable_message, Lambda >( std::forward(transformator) ); return transform_indicator_t< Message_Type >{ limit, std::move( act ) }; } /*! * \brief A helper function for creating transform_indicator. * * This method has to be used in cases when the type of source message/signal * should be specified explicitly. For example: * * \par Usage example: * \code class a_request_processor_t : public so_5::agent_t { public : a_request_processor_t( context_t ctx, const so_5::mbox_t & status_mbox ) : so_5::agent_t( ctx // Limit count of requests in the queue. // If queue is full then request must be transformed // to negative reply. + limit_then_transform< so_5::mutable_msg< request > >( 3, []( request & evt ) { // NOTE: non-const reference to evt. return make_transformed< reply >( evt.m_reply_to, evt.m_id, false ); } ) // Limit count of ask_status in the queue. // The ask_status is a signal. + limit_then_transform< ask_status >( 1, [status_mbox]() { // NOTE: no arguments for the transformation lambda. return make_transformed< busy >( status_mbox ); } ) ) {...} ... }; * \endcode * * \since v.5.5.4, v.5.8.1 */ template< typename Source, typename Lambda > [[nodiscard]] static transform_indicator_t< Source > limit_then_transform( unsigned int limit, Lambda && transformator ) { if constexpr( is_signal< Source >::value ) { action_t act = impl::make_action_for_signal_transformer< Lambda >( std::forward(transformator) ); return transform_indicator_t< Source >{ limit, std::move( act ) }; } else { // Expect that Source is a message, or so_5::mutable_msg, // or so_5::immutable_msg. using payload_t = typename message_payload_type< Source >::payload_type; ensure_not_signal< payload_t >(); static_assert( !std::is_same_v< payload_t, any_unspecified_message >, "limit_then_transform can't be used for any_unspecified_message" ); action_t act = impl::make_action_for_message_transformer< payload_t, message_payload_type< Source >::mutability(), Lambda >( std::forward(transformator) ); return transform_indicator_t< Source >{ limit, std::move( act ) }; } } /*! * \brief Helper method for creating message transformation result. * * \par Usage examples: \code class request_acceptor : public so_5::agent_t { public : request_acceptor( context_t ctx, so_5::mbox_t interaction_mbox, request_scheduling_data & data ) : so_5::agent_t( ctx // If there are to many pending requests then // new requests must be rejected. + limit_then_transform( 10, [this]( const generation_request & req ) { return make_transformed< generation_rejected >( m_interaction_mbox, req.m_id ); } ) ) , m_interaction_mbox( std::move( interaction_mbox ) ) , m_data( data ) {} ... }; \endcode * * \since v.5.5.4 */ template< typename Msg, typename... Args > [[nodiscard]] static transformed_message_t< Msg > make_transformed( mbox_t mbox, Args &&... args ) { return ::so_5::make_transformed< Msg >( std::move( mbox ), std::forward( args )... ); } }; } /* namespace message_limit */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/message_sink.hpp000066400000000000000000000226371523065210100206660ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A base class for message sinks. \since v.5.8.0 */ #if !defined( SO_5_MESSAGE_SINK_HPP ) #define SO_5_MESSAGE_SINK_HPP #include #include #include namespace so_5 { // // max_redirection_deep // /*! * \brief Maximum deep of message redirections. * * Examples for message redirections: * * - message limits are used and so_5::agent_t::limit_then_redirect is * used for pushing a message to another mbox; * - a custom message sink is used for transfering messages from * one mbox to another. * * Such redirections could lead to infinite loops. * SObjectizer cannot detect such loops so it uses a primitive protection * method: the limit for a number of redirections. If message delivery * attempt exceeds this limit then the delivery must be cancelled * (with or without an exception). * * \since v.5.8.0 */ constexpr unsigned int max_redirection_deep = 32; // // abstract_message_sink_t // /*! * \brief Interface for message sink. * * This class is the base for all message sinks. * * The purpose of message sink is to be a subscriber for an mbox. An mbox holds * a list of subscribers and delivers a message to appropriate subscribers * (message sinks). When an mbox receives a message it calls push_event() * method for all message sinks that are subscribed to this message * (and delivery filters permit the delivery to those sinks). * * \note * A message sink in SO-5.8 plays the same role as agent_t in previous * versions, in the sense that message sinks act as a receiver of messages sent * to message boxes. Since an agent_t has priority and this priority is taken * into account during subscriptions and message delivery, the message sink * should also have priority. Because of that there is sink_priority() method. * For cases where a message sink is created for an agent, the sink_priority() * should return the agent's priority. * * \since v.5.8.0 */ class SO_5_TYPE abstract_message_sink_t { public: abstract_message_sink_t() = default; virtual ~abstract_message_sink_t() noexcept = default; abstract_message_sink_t( const abstract_message_sink_t & ) = default; abstract_message_sink_t & operator=( const abstract_message_sink_t & ) = default; abstract_message_sink_t( abstract_message_sink_t && ) noexcept = default; abstract_message_sink_t & operator=( abstract_message_sink_t && ) noexcept = default; //! Get a reference for SObjectizer Environment for that //! the message sink is created. /*! * This method plays the same role as abstract_message_box_t::environment() * or coop_t::environment(). */ [[nodiscard]] virtual environment_t & environment() const noexcept = 0; //! Get the priority for the message sink. [[nodiscard]] virtual priority_t sink_priority() const noexcept = 0; //! Get a message and push it to the appropriate destination. /*! * This is key method for message sink. Its logic depends of message sink * type. For example, ordinary message sink for an agent just pushes the * message to the agent's event_queue. * * \attention * Implementations of message sinks should control the value of * \a redirection_deep. If that value exceeds \a so_5::max_redirection_deep * then the delivery procedure has to be cancelled and the message * (signal) should be dropped (ignored). If an implementation redirects * message to another mbox/msink then the value of \a redirection_deep * has to be incremented. * * \attention * The \a tracer can be nullptr. Moreover, it will be nullptr in most of * the cases when message delivery tracing is off. */ virtual void push_event( //! ID of mbox from that the message is received. mbox_id_t mbox_id, //! Delivery mode for this delivery attempt. message_delivery_mode_t delivery_mode, //! Type of message to be delivered. const std::type_index & msg_type, //! Reference to the message to be delivered. //! NOTE: it can be empty (nullptr) reference in the case of a signal. const message_ref_t & message, //! The current deep of message redirection between mboxes and msinks. unsigned int redirection_deep, //! Message delivery tracer to be used inside overlimit reaction. //! NOTE: it will be nullptr when message delivery tracing if off. const message_limit::impl::action_msg_tracer_t * tracer ) = 0; [[nodiscard]] static bool special_sink_ptr_compare( const abstract_message_sink_t * a, const abstract_message_sink_t * b ) noexcept { // Use std::less for compare pointers. // NOTE: it's UB to compare pointers that do not belong to the same array. using ptr_comparator_t = std::less< const abstract_message_sink_t * >; const auto priority_getter = [](auto * sink) noexcept { return sink == nullptr ? so_5::priority_t::p_min : sink->sink_priority(); }; const auto p1 = priority_getter( a ); const auto p2 = priority_getter( b ); // NOTE: there should be inversion -- sink with higher // priority must be first. return ( p1 > p2 || (p1 == p2 && ptr_comparator_t{}( a, b ) ) ); } }; // // abstract_sink_owner_t // /*! * \brief Interface for holders of message_sink instances. * * There is mbox_t, which is a kind of smart pointer to abstract_message_box_t. * It's a very useful type that allows to hold references to message boxes * safely. But mbox_t exists because all message boxes should be dynamically * allocated objects. The situation with message sinks is a bit more * complicated. Not all message sinks are dynamically allocated, some may be * parts of other objects. But if a message sink is dynamically allocated then * it's good to have something like mbox_t, but for message sinks. * * The type abstract_sink_owner_t is a proxy for a real message sink object. * Instances of abstract_sink_owner_t are always allocated dynamically. They * can hold real message sinks as members (in this case abstract_sink_owner * lives on the heap and the message sink lives inside it), or they can hold * references to message sinks that live elsewhere. * * The presence of abstract_sink_owner_t allows to have msink_t type, which is * very similar to mbox_t. * * Note that the abstract_sink_owner_t is an abstract class. The real * implementations of sink owners must be derived from this class. * * \note * It's expected that one abstract_sink_owner_t holds a reference to * just one message sink. * * \since v.5.8.0 */ class SO_5_TYPE abstract_sink_owner_t : protected atomic_refcounted_t { friend class intrusive_ptr_t< abstract_sink_owner_t >; public: abstract_sink_owner_t() = default; virtual ~abstract_sink_owner_t() noexcept = default; //! Get a reference to the underlying message sink. [[nodiscard]] virtual abstract_message_sink_t & sink() noexcept = 0; //! Get a const reference to the underlying message sink. [[nodiscard]] virtual const abstract_message_sink_t & sink() const noexcept = 0; }; // // msink_t // /*! * \brief Smart reference for abstract_sink_owner. * * \since v.5.8.0 */ using msink_t = intrusive_ptr_t< abstract_sink_owner_t >; namespace impl { // // msink_less_comparator_t // /*! * \brief Helper class to be used as a comparator for msinks. * * Two instances of msink_t can't be compared in a trivial way: they can * be nullptr, if both are not nullptr then their priorities have to be * compared too. * * This class implements the logic of msink_t comparison and can be used * as Comparator template parameter for std::set/map classes. For example: * \code * std::set known_msinks; * \endcode * * \attention * This class is part of the SObjectizer implementation and is subject to * change without notice. * * \since v.5.8.0 */ struct msink_less_comparator_t { [[nodiscard]] static std::pair< const abstract_sink_owner_t *, so_5::priority_t > safe_get_pair( const msink_t & from ) noexcept { if( from ) return { from.get(), from->sink().sink_priority() }; else return { nullptr, so_5::prio::p0 }; } [[nodiscard]] bool operator()( const msink_t & a, const msink_t & b ) const noexcept { const std::less< const abstract_sink_owner_t * > ptr_less; const std::less< so_5::priority_t > prio_less; const auto [a_ptr, a_prio] = safe_get_pair( a ); const auto [b_ptr, b_prio] = safe_get_pair( b ); if( ptr_less( a_ptr, b_ptr ) ) return true; else if( ptr_less( b_ptr, a_ptr ) ) return false; // Assume that a_ptr == b_ptr. else return prio_less( a_prio, b_prio ); } }; } /* namespace impl */ // // simple_sink_owner_t // /*! * \brief Implementation of abstract_sink_owner that owns an instance * of message sink. * * \tparam Sink_Type Type of the message sink to be created as a part of owner object. * * \since v.5.8.0 */ template< typename Sink_Type > class simple_sink_owner_t final : public abstract_sink_owner_t { Sink_Type m_sink; public: /*! * \brief Initializing constructor. * * Forwards all arguments to the m_sink's constructor. */ template< typename... Args > simple_sink_owner_t( Args && ...args ) : m_sink{ std::forward(args)... } {} [[nodiscard]] abstract_message_sink_t & sink() noexcept override { return m_sink; } [[nodiscard]] const abstract_message_sink_t & sink() const noexcept override { return m_sink; } }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/mhood.hpp000066400000000000000000000345051523065210100173210ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Definition of the template class mhood. */ #if !defined( SO_5_MHOOD_HPP ) #define SO_5_MHOOD_HPP #include #include #include #include namespace so_5 { template< typename M > class mhood_t; namespace details { /*! * \since * v.5.5.14 * * \brief A special selector for %mhood_t implementations. */ enum class mhood_type_t { //! Message type is a classical message derived from so_5::message_t. classical_message, //! Message type is a classical signal derived from so_5::signal_t. classical_signal, //! Message type is not related to so_5::message_t. user_type_message }; /*! * \since * v.5.5.14 * * \brief A type to be used as a base for %mhood_t implementation. */ template< typename M, mhood_type_t T, message_mutability_t Mut > class mhood_base_t {}; /*! * \since * v.5.5.14 * * \brief A special base for the case when message type is derived * from %message_t. * * \note * This is specialization for immutable message. * * \tparam M type of message. */ template< typename M > class mhood_base_t< M, mhood_type_t::classical_message, message_mutability_t::immutable_message > { public : using payload_type = typename message_payload_type< M >::payload_type; using envelope_type = typename message_payload_type< M >::envelope_type; mhood_base_t( message_ref_t & mf ) noexcept : m_msg{ message_payload_type< M >::extract_payload_ptr( mf ) } {} //! Access to the message. const payload_type * get() const noexcept { return m_msg; } //! Create a smart pointer for the message envelope. [[nodiscard]] intrusive_ptr_t< envelope_type > make_reference() const noexcept { return intrusive_ptr_t< envelope_type >{m_msg}; } //! Create a holder for this message. /*! * \since * v.5.6.0 */ template< message_ownership_t Ownership = message_ownership_t::autodetected > [[nodiscard]] message_holder_t< M, Ownership > make_holder() const noexcept { return { make_reference() }; } //! Access to the message. const payload_type & operator * () const noexcept { return *get(); } //! Access to the message via pointer. const payload_type * operator->() const noexcept { return get(); } private : /*! * \note * It should be a non-const pointer because of make_reference() method. */ payload_type * m_msg; }; /*! * \since * v.5.5.19 * * \brief A special base for the case when message type is derived * from %message_t. * * \note * This is specialization for mutable message. * * \tparam M type of message. */ template< typename M > class mhood_base_t< M, mhood_type_t::classical_message, message_mutability_t::mutable_message > { public : using payload_type = typename message_payload_type< M >::payload_type; using envelope_type = typename message_payload_type< M >::envelope_type; mhood_base_t( message_ref_t & mf ) noexcept : m_msg{ message_payload_type< M >::extract_payload_ptr( mf ) } {} mhood_base_t( const mhood_base_t & ) = delete; mhood_base_t( mhood_base_t && other ) noexcept : m_msg{ other.m_msg } { other.m_msg = nullptr; } friend void swap( mhood_base_t & a, mhood_base_t & b ) noexcept { using namespace std; swap( a, b ); } mhood_base_t & operator=( const mhood_base_t & ) = delete; mhood_base_t & operator=( mhood_base_t && other ) noexcept { mhood_base_t tmp{ std::move(other) }; swap( *this, tmp ); return *this; } //! Access to the message. payload_type * get() const noexcept { return m_msg; } //! Create a smart pointer for the message envelope. [[nodiscard]] intrusive_ptr_t< envelope_type > make_reference() noexcept { intrusive_ptr_t< envelope_type > result{m_msg}; m_msg = nullptr; return result; } //! Create a holder for this message. /*! * \since * v.5.6.0 */ template< message_ownership_t Ownership = message_ownership_t::autodetected > [[nodiscard]] message_holder_t< M, Ownership > make_holder() noexcept { return { make_reference() }; } //! Access to the message. payload_type & operator*() noexcept { return *get(); } //! Access to the message via pointer. payload_type * operator->() noexcept { return get(); } private : payload_type * m_msg; }; /*! * \since * v.5.5.14 * * \brief A special base for the case when message type is derived * from %message_t. * * \tparam M type of signal. */ template< typename M > class mhood_base_t< M, mhood_type_t::classical_signal, message_mutability_t::immutable_message > { public : mhood_base_t( message_ref_t & ) {} }; /*! * \since * v.5.5.19 * * \brief A special base for the case when message type is derived * from %message_t. * * \tparam M type of signal. */ template< typename M > class mhood_base_t< M, mhood_type_t::classical_signal, message_mutability_t::mutable_message > { public : mhood_base_t( message_ref_t & ) {} }; /*! * \since * v.5.5.14 * * \brief A special base for the case when message type is not * related to %message_t. * * \note * This specialization is for immutable message. * * \tparam M type of message. */ template< typename M > class mhood_base_t< M, mhood_type_t::user_type_message, message_mutability_t::immutable_message > { public : using payload_type = typename message_payload_type< M >::payload_type; using envelope_type = typename message_payload_type< M >::envelope_type; mhood_base_t( message_ref_t & mf ) noexcept : m_payload{ message_payload_type< M >::extract_payload_ptr( mf ) } , m_envelope{ message_payload_type< M >::extract_envelope_ptr( mf ) } {} //! Access to the message. const payload_type * get() const noexcept { return m_payload; } //! Create a smart pointer for the message envelope. intrusive_ptr_t< envelope_type > make_reference() const noexcept { return intrusive_ptr_t< envelope_type >{m_envelope}; } //! Create a holder for this message. /*! * \since * v.5.6.0 */ template< message_ownership_t Ownership = message_ownership_t::autodetected > [[nodiscard]] message_holder_t< M, Ownership > make_holder() const noexcept { return { make_reference() }; } //! Access to the message. const payload_type & operator*() const noexcept { return *get(); } //! Access to the message via pointer. const payload_type * operator->() const noexcept { return get(); } private : const payload_type * m_payload; envelope_type * m_envelope; }; /*! * \since * v.5.5.19 * * \brief A special base for the case when message type is not * related to %message_t. * * \note * This implementation is for mutable message. * * \tparam M type of message. */ template< typename M > class mhood_base_t< M, mhood_type_t::user_type_message, message_mutability_t::mutable_message > { public : using payload_type = typename message_payload_type< M >::payload_type; using envelope_type = typename message_payload_type< M >::envelope_type; mhood_base_t( message_ref_t & mf ) noexcept : m_payload{ message_payload_type< M >::extract_payload_ptr( mf ) } , m_envelope{ message_payload_type< M >::extract_envelope_ptr( mf ) } {} mhood_base_t( const mhood_base_t & ) = delete; mhood_base_t( mhood_base_t && other ) noexcept : m_payload( other.m_payload ) , m_envelope( other.m_envelope ) { other.m_payload = nullptr; other.m_envelope = nullptr; } friend void swap( mhood_base_t & a, mhood_base_t & b ) noexcept { using namespace std; swap( a.m_payload, b.m_payload ); swap( a.m_envelope, b.m_envelope ); } mhood_base_t & operator=( const mhood_base_t & ) = delete; mhood_base_t & operator=( mhood_base_t && other ) noexcept { mhood_base_t tmp{ std::move(other) }; swap( *this, tmp ); return *this; } //! Access to the message. payload_type * get() noexcept { return m_payload; } //! Create a smart pointer for the message envelope. [[nodiscard]] intrusive_ptr_t< envelope_type > make_reference() noexcept { intrusive_ptr_t< envelope_type > result{m_envelope}; m_payload = nullptr; m_envelope = nullptr; return result; } //! Create a holder for this message. /*! * \since * v.5.6.0 */ template< message_ownership_t Ownership = message_ownership_t::autodetected > [[nodiscard]] message_holder_t< M, Ownership > make_holder() noexcept { return { make_reference() }; } //! Access to the message. payload_type & operator*() noexcept { return *get(); } //! Access to the message via pointer. payload_type * operator->() noexcept { return get(); } private : payload_type * m_payload; envelope_type * m_envelope; }; /*! * \since * v.5.5.14 * * \brief A special selector of message hood type. */ template< typename T > struct mhood_type_detector { static constexpr const mhood_type_t mhood_type = is_signal< T >::value ? mhood_type_t::classical_signal : is_classical_message< T >::value ? mhood_type_t::classical_message : mhood_type_t::user_type_message; }; /*! * \brief A special detector of message immutability/mutability. * * \since * v.5.5.19 */ template< typename T > struct mhood_mutability_detector { static constexpr const message_mutability_t mutability = message_mutability_traits::mutability; }; /*! * \brief Just a useful typename for base type of mhood implementation. * * \since * v.5.5.19 */ template< typename Msg > using actual_mhood_base_type = mhood_base_t< Msg, mhood_type_detector::mhood_type, mhood_mutability_detector::mutability >; } /* namespace details */ /*! * \since * v.5.5.14 * * \brief A message wrapped to be used as type of argument for * event handlers. * * \tparam M type of message or signal. * * \note This class provides different sets of methods in dependency of * type \a M. If M is a type derived from so_5::message_t, then there * will be the following methods for immutable message M: * \code const M * get() const; const M * operator->() const; const M & operator*() const; so_5::intrusive_ptr_t< M > make_reference() const; so_5::message_holder_t< M, Ownership > make_holder() const; * \endcode * If M is a type derived from so_5::signal_t, then there will no be methods * at all. It is because there is no actual message object for a signal. * If M is user-type message then there will be the following methods * for immutable message M: * \code const M * get() const; const M * operator->() const; const M & operator*() const; so_5::intrusive_ptr_t< so_5::user_type_message_t > make_reference() const; so_5::message_holder_t< M, Ownership > make_holder() const; * \endcode * For mutable message M there will be the following methods is M is * derived from so_5::message_t: * \code M * get(); M * operator->(); M & operator*(); so_5::intrusive_ptr_t< M > make_reference(); so_5::message_holder_t< M, Ownership > make_holder(); * \endcode * If mutable message M is not derived from so_5::message_t then there * will be the following methods: * \code M * get(); M * operator->(); M & operator*(); so_5::intrusive_ptr_t< so_5::user_type_message_t > make_reference(); so_5::message_holder_t< M, Ownership > make_holder(); * \endcode * * \note * Method make_holder() uses so_5::message_ownership_t::autodetected by * default. * * \attention * Methods make_reference() for mhood_t> will leave mhood object * in nullptr state. It means that mhood must not be used after calling * make_reference(). * * \attention * Methods make_holder() for mhood_t> will leave * the mhood object empty always. Regardless of Ownership parameter. * Methods make_holder() for mhood_t and mhood_t> will * leave value for mhood object regardless of Ownership parameter. * For example: * \code * class demo : public agent_t { * void on_immutable_msg(mhood_t cmd) { * auto unique_holder = cmd.make_holder(); * auto v = cmd->some_value(); // cmd still has its value. * ... * } * ... * void on_mutable_msg(mhood_t< mutable_msg > cmd) { * auto shared_holder = cmd.make_holder(); * // Now cmd is empty! * auto v = cmd->some_value(); // THIS IS UB! * } * }; * \endcode * * \note Class mhood_t can be used for redirection of messages of user types. * For example (since v.5.5.19): \code class redirector : public so_5::agent_t { ... void some_event( mhood_t< std::string > evt ) { // Redirection of message to the different mbox. so_5::send(m_another_mbox, evt); } }; \endcode * If mhood_t> is used then a redirection must be done this * way: \code class redirector : public so_5::agent_t { ... void some_event( mhood_t< mutable_msg > evt ) { // Redirection of message to the different mbox. so_5::send(m_another_mbox, std::move(evt)); // NOTE: evt is nullptr at this point. It must not be used anymore. } }; \endcode */ template< typename M > class mhood_t : public details::actual_mhood_base_type { using base_type_t = details::actual_mhood_base_type; public : mhood_t( message_ref_t & mf ) : base_type_t{ mf } {} }; //! Transform mutable message instance into immutable. /*! * \attention * Moves all content from \a msg into new object and leaves \a msg object in * nullptr state. * * Usage example: * \code void on_hello_mut( mhood_t< so_5::mutable_msg< hello > > cmd ) { cmd->m_content = "bye"; send( another_mbox, to_immutable( std::move(cmd) ) ); } * \endcode * * \since * v.5.5.19 */ template< typename M > typename std::enable_if< !is_signal::value, mhood_t< immutable_msg< M > > >::type to_immutable( mhood_t< mutable_msg > msg ) { message_ref_t ref{ msg.make_reference() }; change_message_mutability( ref, message_mutability_t::immutable_message ); return { ref }; } template< typename M > typename std::enable_if< is_signal::value, mhood_t< immutable_msg< M > > >::type to_immutable( mhood_t< mutable_msg > /*msg*/ ) { message_ref_t ref; return { ref }; } /*! * \since * v.5.5.14 * * \brief A special case for %mhood_t to prevent usage of %mhood_t as wrapper * for %user_type_message_t. */ template< typename M > class mhood_t< user_type_message_t< M > >; /*! * \brief A short name for message hood for mutable message. * * \since * v.5.5.19 */ template< typename M > using mutable_mhood_t = mhood_t< mutable_msg >; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/msg_tracing.cpp000066400000000000000000000025561523065210100205040ustar00rootroot00000000000000/* * SObjectizer-5 */ #include #include #include namespace so_5 { namespace msg_tracing { namespace impl { // // std_stream_tracer_t // /*! * \brief A simple implementation of tracer which uses one of * standard streams. * * \since v.5.5.9 */ class std_stream_tracer_t : public tracer_t { public : //! Main constructor. std_stream_tracer_t( //! Stream to be used for tracing. std::ostream & stream ) : m_stream( stream ) {} virtual void trace( const std::string & what ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; m_stream << what << std::endl; } private : //! Object lock. /*! * It is necessary for synchronization of trace calls from * different threads. */ std::mutex m_lock; //! Stream to be used for tracing. std::ostream & m_stream; }; } /* namespace impl */ // // Standard stream tracers. // SO_5_FUNC tracer_unique_ptr_t std_cout_tracer() { return tracer_unique_ptr_t{ new impl::std_stream_tracer_t{ std::cout } }; } SO_5_FUNC tracer_unique_ptr_t std_cerr_tracer() { return tracer_unique_ptr_t{ new impl::std_stream_tracer_t{ std::cerr } }; } SO_5_FUNC tracer_unique_ptr_t std_clog_tracer() { return tracer_unique_ptr_t{ new impl::std_stream_tracer_t{ std::clog } }; } } /* namespace msg_tracing */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/msg_tracing.hpp000066400000000000000000000305751523065210100205130ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Stuff related to message delivery tracing. * * \since v.5.5.9 */ #if !defined( SO_5_MSG_TRACING_HPP ) #define SO_5_MSG_TRACING_HPP #include #include #include #include #include #include #include #include #include #include namespace so_5 { namespace msg_tracing { /*! * \brief Status of message delivery tracing. * * \since v.5.5.9 */ enum class status_t { //! Message delivery tracing is disabled. disabled, //! Message delivery tracing is enabled. enabled }; // // tracer_t // /*! * \brief Interface of tracer object. * * \since v.5.5.9 */ class SO_5_TYPE tracer_t { // Note: clang-3.9 requires this on Windows platform. tracer_t( const tracer_t & ) = delete; tracer_t( tracer_t && ) = delete; tracer_t & operator=( const tracer_t & ) = delete; tracer_t & operator=( tracer_t && ) = delete; public : tracer_t() = default; virtual ~tracer_t() noexcept = default; //! Store a description of message delivery action to the //! appropriate storage/stream. virtual void trace( const std::string & what ) noexcept = 0; }; // // tracer_unique_ptr_t // /*! * \brief A short alias for unique_ptr to tracer. * * \since v.5.5.9 */ using tracer_unique_ptr_t = std::unique_ptr< tracer_t >; // // Standard stream tracers. // /*! * \brief Factory for tracer which uses std::cout stream. * * \since v.5.5.9 */ SO_5_FUNC tracer_unique_ptr_t std_cout_tracer(); /*! * \brief Factory for tracer which uses std::cerr stream. * * \since v.5.5.9 */ SO_5_FUNC tracer_unique_ptr_t std_cerr_tracer(); /*! * \brief Factory for tracer which uses std::clog stream. * * \since v.5.5.9 */ SO_5_FUNC tracer_unique_ptr_t std_clog_tracer(); /*! * \brief A flag for message/signal dichotomy. * * \since v.5.5.22 */ enum class message_or_signal_flag_t { message, signal }; /*! * \brief Type of message source. * * Message can be sent to a mbox or to a mchain. This mbox/mchain * will be a message source for a subscriber. Sometimes it is necessary * to know what is actual type of message source: mbox or mchain. * * \since v.5.5.22 */ enum class msg_source_type_t { //! The source is a mbox. mbox, //! The source is a mchain. mchain, //! There is no information about a type of actual message source. //! This information can be lost if message was redirected or //! transformed during delivery. //! Or there is no information about message source in the point where //! message is handled. For example, when message arrived to a subscriber //! it is unknown was message sent to mchain or to mbox. unknown }; /*! * \brief An information about message source. * * \since v.5.5.22 */ struct msg_source_t { //! ID of mbox or mchain. mbox_id_t m_id; //! Type of message source. msg_source_type_t m_type; }; /*! * \brief An information about a message instance. * * \since v.5.5.22 */ struct message_instance_info_t { //FIXME: maybe this has to be `const message_t *` in v5.9? //! A pointer to envelope. /*! * Can be null if message is not enveloped into a special wrapper. */ const void * m_envelope; //! Information about message mutability. message_mutability_t m_mutability; }; /*! * \brief An information about compound description of message-related action. * * \since v.5.5.22 */ struct compound_action_description_t { //! The first part of the description. /*! * Can't be null. */ const char * m_first; //! The second part of the description. /*! * Can't be null. */ const char * m_second; }; // // trace_data_t // /*! * \brief An interface of object for accessing trace details. * * A reference to an object with this interface will be passed to * trace filter. This interface provides access to details of a trace message. * * Please note that not all data can be available for every trace message. * Because of that all methods returns optional values. It means that * the value returned must be checked for presense first. For example: * \code * const auto thr_id = so_5::query_current_thread_id(); * so_5::msg_tracing::make_filter( * [thr_id](const so_5::msg_tracing::trace_data_t & td) { * // Try to get ID of thread. * const auto tid = td.tid(); * // Check the presence of value first. * if(tid) { * // And only then we can check the value itself. * return thr_id == *tid; * } * else * return false; * } ); * \endcode * * \note * Version 5.5.22 provides and experimental implementation of * trace filters. Because of that the content of this interface can be * a subject of change in the future versions of SObjectizer. * * \since v.5.5.22 */ class SO_5_TYPE trace_data_t { protected : trace_data_t(const trace_data_t &) = delete; trace_data_t& operator=(const trace_data_t &) = delete; trace_data_t() = default; virtual ~trace_data_t() noexcept = default; public : //! Get the Thread ID from trace message. virtual optional tid() const noexcept = 0; //! Get the information about message type. virtual optional msg_type() const noexcept = 0; //! Get the information about message source. virtual optional msg_source() const noexcept = 0; //! Get a pointer to agent from trace message. virtual optional agent() const noexcept = 0; //! Get a pointer to message_sink from trace message. virtual optional message_sink() const noexcept = 0; //! Get message or signal information. virtual optional message_or_signal() const noexcept = 0; //! Get message instance information. virtual optional message_instance_info() const noexcept = 0; //! Get the description of a compound action. virtual optional compound_action() const noexcept = 0; //! Get pointer to event handler. virtual optional event_handler_data_ptr() const noexcept = 0; }; // // filter_t // /*! * \brief An interface of filter for trace messages. * * This interface must be implemented by all user-defined trace filters. * * \attention * User implementation of a trace filter must be thread-safe. Because * filter() method can be called on several threads at the save time. * * Usage example: * \code * // A filter for messages from specific mboxes/mchains. * class my_filter final : public so_5::msg_tracing::filter_t { * public: * using id_set = std::set; * * my_filter(id_set ids) : ids_{std::move(ids)} {} * * virtual bool filter(const so_5::msg_tracing::trace_data_t & td) noexcept override { * const auto ms = td.msg_source(); * if(ms) { * return 1 == ids_.count(ms->m_id); * } * else * return false; * } * * private: * id_set ids_; * }; * ... * void some_agent::so_evt_start() { * my_filter::id_set ids{ ... }; * so_environment().change_message_delivery_tracer_filter( * new my_filter{std::move(ids)} ); * ... * } * \endcode * * \note * In most cases it is much easier to use make_filter() helper function. * * \since v.5.5.22 */ class SO_5_TYPE filter_t : private so_5::atomic_refcounted_t { friend class so_5::intrusive_ptr_t; public : virtual ~filter_t() noexcept = default; //! Filter the current message. /*! * \return true if message should be placed into the trace. */ virtual bool filter( //! Accessor of trace data. const trace_data_t & data ) noexcept = 0; }; // // filter_shptr_t // /*! * \brief An alias for smart pointer to filter. * * \since v.5.5.22 */ using filter_shptr_t = intrusive_ptr_t< filter_t >; namespace impl { // // filter_from_lambda_t // /*! * \brief A type of implementation of filters created from lambda function. * * \since v.5.5.22 */ template< typename L > class filter_from_lambda_t : public filter_t { L m_lambda; public: filter_from_lambda_t( L lambda ) : m_lambda( std::move(lambda) ) {} bool filter( const trace_data_t & data ) noexcept override { return m_lambda( data ); } }; } /* namespace impl */ // // make_filter // /*! * \brief A helper function for creation of new filter from lambda-function. * * In most cases usage of that function must easier than implementation * of a new class derived from filter_t interface. * * Usage example: * \code * void some_agent_t::so_evt_start() { * // Create a filter which will enable only messages from specific mboxes. * std::set selected_ids{...}; * so_environment().change_message_delivery_tracer_filter( * so_5::msg_tracing::make_filter( * [ids = std::move(selected_ids)](so_5::msg_tracing::trace_data_t & td) { * const auto ms = td.msg_source(); * return ms && 1 == ids.count(ms->m_id); * } ) ); * ... * } * \endcode * * \since v.5.5.22 */ template< typename L > filter_shptr_t make_filter( L && lambda ) { using R = impl::filter_from_lambda_t< typename std::decay::type >; return filter_shptr_t{ new R{ std::forward(lambda) } }; } // // make_enable_all_filter // /*! * \brief A helper function for creation of filter that enables all messages. * * Usage example: * \code * so_5::launch([](so_5::environment_t & env) {...}, * [](so_5::environment_params_t & params) { * // Turn message delivery tracing on. * params.message_delivery_tracer( * so_5::msg_tracing::std_cout_tracer()); * // Enable all trace messages. * params.message_delivery_tracer_filter( * so_5::msg_tracing::make_enable_all_filter()); * ... * } ); * \endcode * * \since v.5.5.22 */ inline filter_shptr_t make_enable_all_filter() { return make_filter( [](const trace_data_t &) { return true; } ); } // // make_disable_all_filter // /*! * \brief A helper function for creation of filter that disables all messages. * * Usage example: * \code * so_5::launch([](so_5::environment_t & env) {...}, * [](so_5::environment_params_t & params) { * // Turn message delivery tracing on. * params.message_delivery_tracer( * so_5::msg_tracing::std_cout_tracer()); * // Disable all trace messages. * // It is expected that trace filter will be changed in the future. * params.message_delivery_tracer_filter( * so_5::msg_tracing::make_disable_all_filter()); * ... * } ); * \endcode * * \since v.5.5.22 */ inline filter_shptr_t make_disable_all_filter() { return make_filter( [](const trace_data_t &) { return false; } ); } // // no_filter // /*! * \brief A helper function to be used when it is necessary to remove * msg_tracing's filter. * * Usage example: * \code * so_5::launch([](so_5::environment_t & env) {...}, * [](so_5::environment_params_t & params) { * // Turn message delivery tracing on. * params.message_delivery_tracer( * so_5::msg_tracing::std_cout_tracer()); * // Disable all trace messages. * // It is expected that trace filter will be changed in the future. * params.message_delivery_tracer_filter( * so_5::msg_tracing::make_disable_all_filter()); * ... * } ); * ... * void some_agent_t::turn_msg_tracing_on() { * // Remove trace filter. As result all trace messages will be printed. * so_environment().change_message_delivery_tracer_filter( * so_5::msg_tracing::no_filter()); * ... * } * \endcode * * \since v.5.5.22 */ inline filter_shptr_t no_filter() { return {}; } // // holder_t // /*! * \brief Interface of holder of message tracer and message trace filter objects. * * \since v.5.5.22 */ class SO_5_TYPE holder_t { public : holder_t() = default; virtual ~holder_t() noexcept = default; holder_t(const holder_t &) = delete; holder_t & operator=(const holder_t &) = delete; //! Is message tracing enabled? virtual bool is_msg_tracing_enabled() const noexcept = 0; //! Get access to the current message trace filter object. /*! * \note * This method should be called only if is_msg_tracing_enabled() * returns true. */ virtual filter_shptr_t take_filter() noexcept = 0; //! Get pointer to the message tracer object. /*! * \note * This method should be called only if is_msg_tracing_enabled() * returns true. */ virtual tracer_t & tracer() const noexcept = 0; }; } /* namespace msg_tracing */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/msg_tracing_individual.cpp000066400000000000000000000065621523065210100227150ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Stuff related to individual message delivery tracing. * * \since v.5.8.3 */ #include #include namespace so_5::msg_tracing { namespace impl { // // special_enveloping_mbox_t // special_enveloping_mbox_t::special_enveloping_mbox_t( mbox_t dest ) : m_dest{ std::move(dest) } {} mbox_id_t special_enveloping_mbox_t::id() const { return m_dest->id(); } void special_enveloping_mbox_t::subscribe_event_handler( const std::type_index & /*type_index*/, abstract_message_sink_t & /*subscriber*/ ) { SO_5_THROW_EXCEPTION( rc_not_implemented, "subscribe_event_handler is not supported for " "msg_tracing::impl::special_enveloping_mbox_t" ); } void special_enveloping_mbox_t::unsubscribe_event_handler( const std::type_index & /*type_index*/, abstract_message_sink_t & /*subscriber*/ ) noexcept { // Nothing to do. } std::string special_enveloping_mbox_t::query_name() const { return m_dest->query_name(); } mbox_type_t special_enveloping_mbox_t::type() const { return m_dest->type(); } void special_enveloping_mbox_t::do_deliver_message( message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) { // The message has to be wrapped into a special envelope. message_ref_t wrapped_msg{ std::make_unique< individual_tracing_envelope_t >( message ) }; m_dest->do_deliver_message( delivery_mode, msg_type, wrapped_msg, redirection_deep ); } void special_enveloping_mbox_t::set_delivery_filter( const std::type_index & /*msg_type*/, const delivery_filter_t & /*filter*/, abstract_message_sink_t & /*subscriber*/ ) { SO_5_THROW_EXCEPTION( rc_not_implemented, "set_delivery_filter is not supported for " "msg_tracing::impl::special_enveloping_mbox_t" ); } void special_enveloping_mbox_t::drop_delivery_filter( const std::type_index & /*msg_type*/, abstract_message_sink_t & /*subscriber*/ ) noexcept { // Nothing to do. } so_5::environment_t & special_enveloping_mbox_t::environment() const noexcept { return m_dest->environment(); } // // individual_tracing_envelope_t // individual_tracing_envelope_t::individual_tracing_envelope_t( message_ref_t message ) : m_message{ std::move(message) } {} void individual_tracing_envelope_t::access_hook( access_context_t /*context*/, handler_invoker_t & invoker ) noexcept { invoker.invoke( payload_info_t{ m_message } ); } } /* namespace impl */ // // make_individual_trace_filter // [[nodiscard]] SO_5_FUNC filter_shptr_t make_individual_trace_filter() { return make_filter( []( const trace_data_t & td ) -> bool { const auto instance_info = td.message_instance_info(); if( instance_info && instance_info->m_envelope ) { // Use the fact that instance_info.m_envelope is a pointer // to message_t. const message_t * msg = //FIXME: it's better to have m_envelope as message_t pointer. static_cast(instance_info->m_envelope); // It has to be an instance of the individual_tracing_envelope_t. if( dynamic_cast< const impl::individual_tracing_envelope_t * >(msg) ) // Only now we can enable tracing. return true; } // In all other cases the tracing has to be suppressed. return false; } ); } } /* namespace so_5::msg_tracing */ sobjectizer-5.8.6/dev/so_5/msg_tracing_individual.hpp000066400000000000000000000152021523065210100227110ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Stuff related to individual message delivery tracing. * * \since v.5.8.3 */ #if !defined( SO_5_MSG_TRACING_INDIVIDUAL_HPP ) #define SO_5_MSG_TRACING_INDIVIDUAL_HPP #include #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5::msg_tracing { namespace impl { /*! * \brief A special mbox that wraps all incoming messages/signals into a * special envelope. * * Instances of that special mbox will be created by * so_5::msg_tracing::individual_trace(). * * This mbox wraps outgoing messages/signals into an envelope of * type individual_tracing_envelope_t. * * \note * This mbox works like a simple proxy and because of that doesn't support * operations like subscriptions and delivery filter management. * * \since v.5.8.3 */ class SO_5_TYPE special_enveloping_mbox_t final : public abstract_message_box_t { //! The real destination for the messages/signals. const mbox_t m_dest; public: //! Initializing constructor. special_enveloping_mbox_t( //! The real destination for the messages/signals. //! It's assumed that this pointer can't be nullptr. mbox_t dest ); /*! * \note * Returns the ID of the actual destination. */ [[nodiscard]] mbox_id_t id() const override; /*! * \note * It throws because this mbox doesn't support subscriptions and can * be used for message delivery only. */ void subscribe_event_handler( const std::type_index & type_index, abstract_message_sink_t & subscriber ) override; /*! * \note * Does nothing because this mbox doesn't support subscriptions and can * be used for message delivery only. */ void unsubscribe_event_handler( const std::type_index & type_index, abstract_message_sink_t & subscriber ) noexcept override; /*! * \note * Returns the name of the actual destination mbox. */ [[nodiscard]] std::string query_name() const override; /*! * \note * Returns the type of the actual destination mbox. */ [[nodiscard]] mbox_type_t type() const override; /*! * \brief Implementation of message delivery. * * Wraps the original message/signal into a special envelope and * calls do_deliver_message on the actual destination mbox. */ void do_deliver_message( message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) override; /*! * \note * It throws because this mbox doesn't support delivery filters and can * be used for message delivery only. */ void set_delivery_filter( const std::type_index & msg_type, const delivery_filter_t & filter, abstract_message_sink_t & subscriber ) override; /*! * \note * Does nothing because this mbox doesn't support delivery filters and can * be used for message delivery only. */ void drop_delivery_filter( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) noexcept override; [[nodiscard]] so_5::environment_t & environment() const noexcept override; }; // // individual_tracing_envelope_t // /*! * \brief Special envelope that just holds an original message/signal. * * This envelope has no own logic. It returns the original message/signal * always. The only purpose of this type is an attempt to make dynamic_cast in * a filter created by so_5::msg_tracing::make_individual_trace_filter(). This * filter tries to perform such a cast, and if this cast succeeds, then the * filter allows the trace. * * \since v.5.8.3 */ class SO_5_TYPE individual_tracing_envelope_t final : public so_5::enveloped_msg::envelope_t { //! The original message/signal. const message_ref_t m_message; public: //! Initializing constructor. explicit individual_tracing_envelope_t( //! The original message/signal. message_ref_t message ); void access_hook( access_context_t context, handler_invoker_t & invoker ) noexcept override; }; // // make_special_enveloping_mbox // /*! * \brief Factory function for special_enveloping_mbox. * * Creates an instance of special_enveloping_mbox_t bound with \a dest and * returns it as mbox. * * \since v.5.8.3 */ [[nodiscard]] inline so_5::mbox_t make_special_enveloping_mbox( //! Actual destination. It's assumed that \a dest != nullptr. so_5::mbox_t dest ) { return { std::make_unique< special_enveloping_mbox_t >( std::move(dest) ) }; } } /* namespace impl */ // // individual // /*! * \brief Indicator that tells that delivery of the message/signal should be traced. * * This indicator has to be used with send(), send_delayed() and send_periodic() functions. * It allows to trace only actions related to a message/signal sent by using * individual_trace(). * * \note * It's necessary to enable message delivery tracing and set a filter returned * by make_individual_trace_filter(). * * Usage example: * \code * // Ordinary message/signal. * so_5::send(so_5::msg_tracing::individual_trace(dest), ...); * * // Delayed message/signal. * so_5::send_delayed(so_5::msg_tracing::individual_trace(dest), 10ms, ...); * * // Periodic message/signal. * auto timer_id = so_5::send_periodic(so_5::msg_tracing::individual_trace(dest), 10ms, 10ms, ...); * \endcode * * \since v.5.8.3 */ template< typename Dest > [[nodiscard]] so_5::mbox_t individual_trace( //! Actual destination for the message/signal. //! It can be a reference to the agent (direct mbox of that agent will be used), //! a mbox, a mchain. Dest && dest ) { return impl::make_special_enveloping_mbox( so_5::send_functions_details::arg_to_mbox( std::forward(dest) ) ); } // // make_individual_trace_filter // /*! * \brief Factory for special message tracing filter for individual tracing. * * This filter allows to trace only actions related to messages/signal sent by * using individual_trace(). * * Usage example: * \code * so_5::launch( * [](so_5::environment_t & env) {...}, * [](so_5::environment_params_t & params) { * // Message delivery tracing has to be turned on. * params.message_delivery_tracer(so_5::msg_tracing::std_cout_tracer()); * // Special filter has to be set to avoid flooding of trace information. * params.message_delivery_tracer_filter(so_5::msg_tracing::make_individual_trace_filter()); * ... * }); * \endcode * * \since v.5.8.3 */ [[nodiscard]] SO_5_FUNC filter_shptr_t make_individual_trace_filter(); } /* namespace so_5::msg_tracing */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/msinks/000077500000000000000000000000001523065210100167775ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/msinks/transform_then_redirect.hpp000066400000000000000000000515171523065210100244330ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Stuff for transform_then_redirect message sink. * * \since v.5.8.1 */ #if !defined( SO_5_MSINKS_TRANSFORM_THEN_REDIRECT_HPP ) #define SO_5_MSINKS_TRANSFORM_THEN_REDIRECT_HPP #include #include #include #include #include #include #include #include namespace so_5 { namespace msinks { namespace transform_then_redirect_impl { /*! * \brief Internal function to be used in %deliver_transformation_result. * * Performs message delivery tracing if msg_tracing is enabled. * * Should be used when transformation result is present and has to be * delivered to the destination mbox. * * \note * This function isn't a template and should minimize code size when * a template deliver_transformation_result is instantiated. */ inline void trace_deliver_transformation_result_if_enabled( environment_t & env, const std::type_index & transformed_msg_type, const abstract_message_box_t & dest_mbox, message_delivery_mode_t delivery_mode, unsigned int redirection_deep ) { so_5::impl::internal_env_iface_t internal_env{ env }; if( internal_env.is_msg_tracing_enabled() ) { namespace tracing = so_5::impl::msg_tracing_helpers::details; tracing::make_trace( // It's safe to use _nonchecked() because // is_msg_tracing_enabled() returns true. internal_env.msg_tracing_stuff_nonchecked(), tracing::composed_action_name{ "msinks::transform_then_redirect", "deliver_transformed" }, tracing::type_of_transformed_msg{ transformed_msg_type }, tracing::mbox_as_msg_destination{ dest_mbox }, delivery_mode, tracing::redirection_deep{ redirection_deep } ); } } /*! * \brief Internal function to be used in %deliver_transformation_result. * * Performs message delivery tracing if msg_tracing is enabled. * * Should be used when transformer returns an empty optional. * * \note * This function isn't a template and should minimize code size when * a template deliver_transformation_result is instantiated. */ inline void trace_no_transformation_result_if_enabled( environment_t & env, message_delivery_mode_t delivery_mode, unsigned int redirection_deep ) { so_5::impl::internal_env_iface_t internal_env{ env }; if( internal_env.is_msg_tracing_enabled() ) { namespace tracing = so_5::impl::msg_tracing_helpers::details; tracing::make_trace( // It's safe to use _nonchecked() because // is_msg_tracing_enabled() returns true. internal_env.msg_tracing_stuff_nonchecked(), tracing::composed_action_name{ "msinks::transform_then_redirect", "no_transformation_result" }, delivery_mode, tracing::redirection_deep{ redirection_deep } ); } } /*! * \brief Helper function for calling do_deliver_message for sending a * transformation result. * * This function is used when tranformation function returns just * so_5::transformed_message_t. * * \attention * \a redirection_deep is not incremented. It's expected that this * value is already processed by the caller. * * \since v.5.8.1 */ template< typename Msg > void deliver_transformation_result( environment_t & env, message_delivery_mode_t delivery_mode, transformed_message_t< Msg > & r, unsigned int redirection_deep ) { trace_deliver_transformation_result_if_enabled( env, r.msg_type(), *(r.mbox()), delivery_mode, redirection_deep ); r.mbox()->do_deliver_message( delivery_mode, r.msg_type(), r.message(), redirection_deep ); } /*! * \brief Helper function for calling do_deliver_message for sending a * transformation result. * * This function is used when tranformation function returns * std::optional. * * \note * Delivery attempt will be performed only if \a r contains a value. * * \attention * \a redirection_deep is not incremented. It's expected that this * value is already processed by the caller. * * \since v.5.8.1 */ template< typename Msg > void deliver_transformation_result( environment_t & env, message_delivery_mode_t delivery_mode, std::optional< transformed_message_t< Msg > > & r, unsigned int redirection_deep ) { if( r.has_value() ) { deliver_transformation_result( env, delivery_mode, *r, redirection_deep ); } else { trace_no_transformation_result_if_enabled( env, delivery_mode, redirection_deep ); } } /*! * \brief Helper function for causing a compilation error. * * This function will be selected by a compiler if transformation * function neither returns so_5::transformed_message_t nor * std::optional. * * \since v.5.8.1 */ template< typename Dummy > void deliver_transformation_result( environment_t & /*env*/, message_delivery_mode_t /*delivery_mode*/, const Dummy &, unsigned int /*redirection_deep*/ ) { static_assert( so_5::details::always_false::value, "Transformer for transform_then_redirect_sink should " "return so_5::transformed_message_t or " "std::optional>" ); } /*! * \brief Base class for all implementation of transform_then_redirect sinks. * * Holds a reference to SObjectizer Environment. * This reference is necessary for implementation of inherited environment() method. * * Holds the tranformer. * * Implements all inherited methods with respect to enveloped messages and so on. * * Introduces a new virtual method call_transformer_then_go_further() that has to * be implemented in derived classes. * * \note * This implementation always returns so_5::prio::p0 as sink priority. * * \tparam Transformer type of message tranformer. * * \since v.5.8.1 */ template< typename Transformer > class basic_transform_then_redirect_sink_t : public abstract_message_sink_t { protected: //! SObjectizer Environment in that we're working. outliving_reference_t< so_5::environment_t > m_env; //! Functor to be used for transformation. Transformer m_transformer; public: /*! * \brief Initializing constructor. * * \tparam Transformer_T type of the transformed. It's expected to be * the same type as \a Transformer, but we're using template constructor * here to have a possibility to use perfect forwarding. */ template< typename Transformer_T > basic_transform_then_redirect_sink_t( //! SObjectizer Environment to work in. outliving_reference_t< so_5::environment_t > env, //! Functor to be used for transformation. Transformer_T && transformer ) : m_env{ env } , m_transformer{ std::forward(transformer) } {} [[nodiscard]] environment_t & environment() const noexcept final { return m_env.get(); } [[nodiscard]] priority_t sink_priority() const noexcept final { return prio::p0; } void push_event( mbox_id_t mbox_id, message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep, const message_limit::impl::action_msg_tracer_t * /*tracer*/ ) final { if( redirection_deep >= max_redirection_deep ) { // NOTE: this fragment can throw but it isn't a problem // because push_event() is called during message // delivery process and exceptions are expected in that // process. SO_5_LOG_ERROR( this->environment().error_logger(), logger ) logger << "maximum message redirection deep exceeded on " "tranform_then_redirect_sink_t::push_event; " "message will be ignored;" << " msg_type: " << msg_type.name() << ", mbox_id: " << mbox_id; } else { this->handle_envelope_then_go_further( delivery_mode, message, // redirection_deep has to be increased. redirection_deep + 1u ); } } private: void handle_envelope_then_go_further( message_delivery_mode_t delivery_mode, const message_ref_t & message, unsigned int redirection_deep ) { // Envelopes should be handled a special way. // Payload must be extrected and checked for presence. if( message_t::kind_t::enveloped_msg == message_kind( message ) ) { const auto opt_payload = ::so_5::enveloped_msg:: extract_payload_for_message_transformation( message ); // Payload can be optional. Se we will perform // transformation only if payload is present. if( opt_payload ) { call_transformer_then_go_further( delivery_mode, opt_payload->message(), redirection_deep ); } } else { call_transformer_then_go_further( delivery_mode, message, redirection_deep ); } } /*! * \brief Method that does actual transformation and redirection * of the message. * * It's called when envelopes already processed and \a message points * to the actual payload (if it's not a signal). * * \note * The value of \a redirection_deep is already checked and incremented. */ virtual void call_transformer_then_go_further( //! Delivery mode to be used. This is the value form push_event() method. message_delivery_mode_t delivery_mode, //! Reference to payload to be transformed. //! It has to be nullptr for a signal. const message_ref_t & message, //! Value of redirection deep to be used for delivery of transformation //! result. unsigned int redirection_deep ) = 0; }; /*! * \brief Implementation of tranformation sink for a case when * a message has to be transformed. * * \tparam Expected_Msg type of source message. It can be M, * so_5::immutable_msg or so_5::mutable_msg. * * \tparam Transformer type of message tranformer. * * \since v.5.8.1 */ template< typename Expected_Msg, typename Transformer > class msg_transform_then_redirect_sink_t final : public basic_transform_then_redirect_sink_t< Transformer > { //! Type of a payload of the message. using payload_t = typename message_payload_type< Expected_Msg >::payload_type; //! Type of a reference to the message payload. /*! * In the case of an immutable message it should be const reference. * In the case of a mutable message it should be non-nonst reference. */ using payload_reference_t = std::conditional_t< is_mutable_message< Expected_Msg >::value, payload_t &, const payload_t & >; //! Short alias for the base type. using base_type_t = basic_transform_then_redirect_sink_t< Transformer >; public: // Just reuse the constructor(s). using base_type_t::base_type_t; private: /*! * \brief Actual implementation of transformation and delivery of the result. * * Extracts the payload from \a message. Calls the transformer and delivers * the result (if result is present). */ void call_transformer_then_go_further( message_delivery_mode_t delivery_mode, const message_ref_t & message, unsigned int redirection_deep ) override { payload_reference_t payload = message_payload_type< Expected_Msg >:: payload_reference( *message.get() ); auto r = this->m_transformer( payload ); deliver_transformation_result( this->m_env.get(), delivery_mode, r, redirection_deep ); } }; /*! * \brief Implementation of tranformation sink for a case when * a message has to be transformed. * * \tparam Signal type of source signal. * * \tparam Transformer type of message tranformer. * * \since v.5.8.1 */ template< typename Signal, typename Transformer > class signal_transform_then_redirect_sink_t final : public basic_transform_then_redirect_sink_t< Transformer > { //! Short alias for the base type. using base_type_t = basic_transform_then_redirect_sink_t< Transformer >; public: // Just reuse the constructor(s). using base_type_t::base_type_t; private: /*! * \brief Actual implementation of transformation and delivery of the result. * * Extracts the payload from \a message. Calls the transformer and delivers * the result (if result is present). */ void call_transformer_then_go_further( message_delivery_mode_t delivery_mode, const message_ref_t & /*message*/, unsigned int redirection_deep ) override { auto r = this->m_transformer(); deliver_transformation_result( this->m_env.get(), delivery_mode, r, redirection_deep ); } }; } /* namespace transform_then_redirect_impl */ /*! * \brief Factory function that creates an instance of transform_then_redirect * msink. * * \attention * It's much simpler to use so_5::bind_transformer() function * that calls this factory under the hood. * * Type of source message is automatically deduced from the type of * \a transformer argument. * * Usage example: * \code * struct part { ... }; * struct compound { * part m_first; * part m_second; * }; * * ... * so_5::mbox_t src_mbox = ...; * so_5::mbox_t dest_mbox = ...; * * so_5::single_sink_binding_t binding; * binding.bind(src_mbox, * so_5::msinks::transform_then_redirect( * src_mbox->environment(), * [dest_mbox](const compound & msg) { * return so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * msg.m_first // Initializer for the new `part` instance. * ); * })); * \endcode * * The \a transformer is expected to return a so_5::transformed_message_t * or std::optional: * \code * binding.bind(src_mbox, * so_5::msinks::transform_then_redirect( * src_mbox->environment(), * [dest_mbox](const compound & msg) * -> std::optional> * { * if(should_message_be_transformed(msg)) { * return { * so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * msg.m_first // Initializer for the new `part` instance. * ) }; * } * else * return std::nullopt; // No transformation. * })); * \endcode * * \note * Because the type of the source message is deduced from \a transformer * argument this factory function can't be used for transforming mutable messages * and signals. * * \attention * The \a transformer can't have an argument in the form of `const auto &`: * \code * // Compilation error is expected here because the type of * // the transformer argument can't be deduced. * so_5::msinks::transform_then_redirect(env, [](const auto & msg) {...}); * \endcode * * \tparam Transformer_Lambda type of transformer functor (a lambda or free function). * \since v.5.8.1 */ template< typename Transformer_Lambda > [[nodiscard]] msink_t transform_then_redirect( //! SObjectizer Environment for that a new msink will be created. so_5::environment_t & env, //! Transformer that produced so_5::transformed_message_t instance or //! std::optional instance. Transformer_Lambda && transformer ) { using namespace transform_then_redirect_impl; using transformer_t = std::decay_t< Transformer_Lambda >; using lambda_traits_t = so_5::details::lambda_traits::traits< transformer_t >; using transformer_arg_t = std::remove_cv_t< typename lambda_traits_t::argument_type >; using sink_t = msg_transform_then_redirect_sink_t< transformer_arg_t, // As expected msg type. transformer_t >; using sink_owner_t = simple_sink_owner_t< sink_t >; return { std::make_unique< sink_owner_t >( outliving_mutable( env ), std::forward(transformer) ) }; } /*! * \brief Factory function that creates an instance of transform_then_redirect * msink. * * \attention * It's much simpler to use so_5::bind_transformer() function that calls this factory under the hood. * * Type of source message is specified explicitly and because of * that this factory function can be used for mutable messages too. * * Usage example: * \code * struct part { ... }; * struct compound { * part m_first; * part m_second; * }; * * ... * so_5::mbox_t src_mbox = ...; * so_5::mbox_t dest_mbox = ...; * * so_5::single_sink_binding_t binding; * binding.bind< so_5::mutable_msg >(src_mbox, * so_5::msinks::transform_then_redirect< so_5::mutable_msg >( * src_mbox->environment(), * [dest_mbox](compound & msg) { // or [dest_mbox](auto & msg) * return so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * std::move(msg.m_first) // Initializer for the new `part` instance. * ); * })); * \endcode * * The \a transformer is expected to return a so_5::transformed_message_t * or std::optional: * \code * binding.bind< so_5::mutable_msg >(src_mbox, * so_5::msinks::transform_then_redirect< so_5::mutable_msg >( * src_mbox->environment(), * [dest_mbox](compound & msg) * -> std::optional> * { * if(should_message_be_transformed(msg)) { * return { * so_5::make_transformed( * dest_mbox, // Destination for the transformed message. * std::move(msg.m_first) // Initializer for the new `part` instance. * ) }; * } * else * return std::nullopt; // No transformation. * })); * \endcode * * \attention * This factory function can't be used for signals. * * \tparam Expected_Msg type of the source message or signal. When `Msg` is the * source message type, then `Msg`, `so_5::immutable_msg` or * `so_5::mutable_msg` can be used. * * \tparam Transformer_Lambda type of transformer functor (a lambda or free function). * \since v.5.8.1 */ template< typename Expected_Msg, typename Transformer_Lambda > [[nodiscard]] std::enable_if_t< !is_signal::value, msink_t > transform_then_redirect( //! SObjectizer Environment for that a new msink will be created. so_5::environment_t & env, //! Transformer that produced so_5::transformed_message_t instance or //! std::optional instance. Transformer_Lambda && transformer ) { // Just for a case (it's a bit paranoid). ensure_not_signal< Expected_Msg >(); using namespace transform_then_redirect_impl; using transformer_t = std::decay_t< Transformer_Lambda >; using sink_t = msg_transform_then_redirect_sink_t< Expected_Msg, // As expected msg type. transformer_t >; using sink_owner_t = simple_sink_owner_t< sink_t >; return { std::make_unique< sink_owner_t >( outliving_mutable( env ), std::forward(transformer) ) }; } /*! * \brief Factory function that creates an instance of transform_then_redirect * msink. * * \attention * It's much simpler to use so_5::bind_transformer() function that calls this factory under the hood. * * \attention * This overload of %transform_then_redirect factory function can * only be used with signals. * * Usage example: * \code * struct first_signal final : public so_5::signal_t {}; * struct second_signal final : public so_5::signal_t {}; * * ... * so_5::mbox_t src_mbox = ...; * so_5::mbox_t dest_mbox = ...; * * so_5::single_sink_binding_t binding; * binding.bind< first_signal >(src_mbox, * so_5::msinks::transform_then_redirect< first_signal >( * src_mbox->environment(), * [dest_mbox]() { * return so_5::make_transformed( * dest_mbox // Destination for the transformed message. * ); * })); * \endcode * * The \a transformer is expected to return a so_5::transformed_message_t * or std::optional. * * \tparam Expected_Msg type of the source signal. When `Expected_Msg` is the * source signal type then `Expected_Msg` or * `so_5::immutable_msg` is allowed. * * \tparam Transformer_Lambda type of transformer functor (a lambda or free function). * * \since v.5.8.1 */ template< typename Signal, typename Transformer_Lambda > [[nodiscard]] std::enable_if_t< is_signal::value, msink_t > transform_then_redirect( //! SObjectizer Environment for that a new msink will be created. so_5::environment_t & env, //! Transformer that produced so_5::transformed_message_t instance or //! std::optional instance. Transformer_Lambda && transformer ) { // Just for a case (it's a bit paranoid). ensure_signal< Signal >(); using namespace transform_then_redirect_impl; using transformer_t = std::decay_t< Transformer_Lambda >; using sink_t = signal_transform_then_redirect_sink_t< Signal, transformer_t >; using sink_owner_t = simple_sink_owner_t< sink_t >; return { std::make_unique< sink_owner_t >( outliving_mutable( env ), std::forward(transformer) ) }; } } /* namespace msinks */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/multi_sink_binding.hpp000066400000000000000000000422001523065210100220520ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Stuff for multi_sink_binding implementation. * * \since v.5.8.0 */ #if !defined( SO_5_MULTI_SINK_BINDING_HPP ) #define SO_5_MULTI_SINK_BINDING_HPP #include #include #include #include #include #include #include namespace so_5 { namespace multi_sink_binding_impl { /*! * \brief Type of container for bindings for messages. * * There could be just one binding for one message type. * * \since v.5.8.0 */ using one_sink_bindings_t = std::map< std::type_index, single_sink_binding_t >; /*! * \brief Type of container for bindings for one msink. * * There could be bindings for several message types for the same msink. * * \since v.5.8.0 */ using one_mbox_bindings_t = std::map< msink_t, one_sink_bindings_t, so_5::impl::msink_less_comparator_t >; /*! * \brief Type of container for bindings for messages from mboxes. * * Several msinks can be bound to one mbox. * * \since v.5.8.0 */ using bindings_map_t = std::map< mbox_id_t, one_mbox_bindings_t >; /*! * \brief Helper type for insertion into a std::map with automatic rollback. * * It's expected that \a Container is a std::map. It's expected that * Container::mapped_type is DefaultConstructible. * * If commit() isn't called and a new item was inserted to the map * in the constructor then that item will be erased in the destructor. * * See actual_binding_handler_t::do_actual_bind() for usage examples. * * \since v.5.8.0 */ template< class Container > class insertion_it_with_auto_erase_if_not_committed_t final { using iterator_type = typename Container::iterator; Container & m_container; iterator_type m_it; bool m_modified; bool m_commited{ false }; public: /*! * \brief Initializing constructor. * * Tries to find an item with \a k in \a container. * If item isn't found then a new item with \a k as key will be inserted * into \a container. */ insertion_it_with_auto_erase_if_not_committed_t( Container & container, typename Container::key_type const & k ) : m_container{ container } , m_it{ container.find( k ) } { if( container.end() == m_it ) { m_it = m_container.emplace( k, typename Container::mapped_type{} ).first; m_modified = true; } else m_modified = false; } insertion_it_with_auto_erase_if_not_committed_t( const insertion_it_with_auto_erase_if_not_committed_t & ) = delete; insertion_it_with_auto_erase_if_not_committed_t( insertion_it_with_auto_erase_if_not_committed_t && ) = delete; ~insertion_it_with_auto_erase_if_not_committed_t() noexcept { if( m_modified && !m_commited ) m_container.erase( m_it ); } void commit() noexcept { m_commited = true; } [[nodiscard]] iterator_type operator->() const { return m_it; } /*! * \retval true if new item was inserted in the constructor. * \retval false if item was found in the container. */ [[nodiscard]] bool modified() const noexcept { return m_modified; } }; // Deduction guide for insertion_it_with_auto_erase_if_not_committed_t. template insertion_it_with_auto_erase_if_not_committed_t(C &, const K&) -> insertion_it_with_auto_erase_if_not_committed_t; /*! * \brief Class that actually holds multiple sinks bindings. * * \attention * It's not a thread-safe class. Thread-safety has to be provided by * an owner of actual_binding_handler_t instance. * * \since v.5.8.0 */ class actual_binding_handler_t { //! All bindings. bindings_map_t m_bindings; /*! * \brief Implementation of bind procedure. * * \tparam Single_Sink_Modificator Type of functor that will be called * for modification of the corresponding single_sink_binding_t instance. * This functor should have a prototype like: * \code * void(const std::type_index & msg_type, single_sink_binding_t & binding); * \endcode */ template< typename Single_Sink_Modificator > void do_actual_bind( const std::type_index & msg_type, const mbox_t & from, const msink_t & dest, Single_Sink_Modificator && single_sink_modificator ) { insertion_it_with_auto_erase_if_not_committed_t it_mbox{ m_bindings, from->id() }; insertion_it_with_auto_erase_if_not_committed_t it_msink{ it_mbox->second, dest }; insertion_it_with_auto_erase_if_not_committed_t it_msg{ it_msink->second, msg_type }; // If new item wasn't inserted then it's an error. if( !it_msg.modified() ) { SO_5_THROW_EXCEPTION( rc_evt_handler_already_provided, std::string{ "msink already subscribed to a message" } + "(mbox:'" + from->query_name() + "', msg_type:'" + msg_type.name() + "'" ); } single_sink_modificator( msg_type, it_msg->second ); it_msg.commit(); it_msink.commit(); it_mbox.commit(); } public: /*! * This method can be used for messages and signals. */ template< typename Msg > void do_bind( const mbox_t & from, const msink_t & dest ) { do_actual_bind( message_payload_type< Msg >::subscription_type_index(), from, dest, [&]( const std::type_index & msg_type, single_sink_binding_t & binding ) { binding.bind_for_msg_type( msg_type, from, dest ); } ); } /*! * This method can only be used if \a Msg isn't a signal. * * It's expected that \a delivery_filter isn't nullptr. */ template< typename Msg > void do_bind( const mbox_t & from, const msink_t & dest, delivery_filter_unique_ptr_t delivery_filter ) { // Msg can't be a signal! ensure_not_signal< Msg >(); do_actual_bind( message_payload_type< Msg >::subscription_type_index(), from, dest, [&]( const std::type_index & msg_type, single_sink_binding_t & binding ) { binding.bind_for_msg_type( msg_type, from, dest, std::move(delivery_filter) ); } ); } /*! * This method can be used for messages and signals. */ template< typename Msg > void do_unbind( const mbox_t & from, const msink_t & dest ) noexcept { auto it_mbox = m_bindings.find( from->id() ); if( it_mbox == m_bindings.end() ) return; auto & msinks = it_mbox->second; auto it_msink = msinks.find( dest ); if( it_msink == msinks.end() ) return; const auto & msg_type = message_payload_type< Msg >::subscription_type_index(); auto & msgs = it_msink->second; msgs.erase( msg_type ); if( msgs.empty() ) { msinks.erase( it_msink ); if( msinks.empty() ) { m_bindings.erase( it_mbox ); } } } /*! * Remove binding for all types of messages/signals that were * made for \a dest from \a from. */ void do_unbind_all_for( const mbox_t & from, const msink_t & dest ) noexcept { auto it_mbox = m_bindings.find( from->id() ); if( it_mbox == m_bindings.end() ) return; auto & msinks = it_mbox->second; msinks.erase( dest ); if( msinks.empty() ) m_bindings.erase( it_mbox ); } /*! * Removes all bindings. */ void do_clear() noexcept { m_bindings.clear(); } }; } /* namespace multi_sink_binding_impl */ /*! * \brief Helper class for managing multiple sink bindings. * * An instance of multi_sink_binding_t drops all binding in the destructor. * If it's necessary to drop all binding manually then clear() method * can be used. * * Usage examples: * \code * // Use as a part of an agent. * class coordinator final : public so_5::agent_t * { * const so_5::mbox_t broadcasting_mbox_; * so_5::multi_sink_binding_t<> bindings_; * ... * void on_some_event(mhood_t cmd) { * // Create a child coop and bind agents to broadcasting mbox. * so_5::introduce_child_coop(*this, [](so_5::coop_t & coop) { * auto * first = coop.make_agent(...); * auto first_worker_msink = so_5::wrap_to_msink(first->so_direct_mbox()); * * auto * second = coop.make_agent(...); * auto second_worker_msink = so_5::wrap_to_msink(second->so_direct_mbox()); * * bindings_.bind(broadcasting_mbox_, first_worker_msink); * bindings_.bind(broadcasting_mbox_, second_worker_msink); * bindings_.bind(broadcasting_mbox_, first_worker_msink); * bindings_.bind(broadcasting_mbox_, second_worker_msink); * ... * }); * } * }; * * // Use as object controlled by a coop. * so_5::environment_t & env = ...; * env.introduce_coop([](so_5::coop_t & coop) { * const auto broadcasting_mbox = coop.environment().create_mbox(); * auto * bindings = coop.take_under_control( * std::make_unique>()); * * auto * first = coop.make_agent(...); * auto first_worker_msink = so_5::wrap_to_msink(first->so_direct_mbox()); * * auto * second = coop.make_agent(...); * auto second_worker_msink = so_5::wrap_to_msink(second->so_direct_mbox()); * * bindings.bind(broadcasting_mbox, first_worker_msink); * bindings.bind(broadcasting_mbox, second_worker_msink); * bindings.bind(broadcasting_mbox, first_worker_msink); * bindings.bind(broadcasting_mbox, second_worker_msink); * ... * }); * \endcode * * The instance of multi_sink_binding_t is thread safe by the default (if * the default value is used for template parameter \a Lock_Type). * In single-threaded environments this can be unnecessary, so_5::null_mutex_t * can be used in those cases: * \code * // It's assumed that code works in single-threaded environment. * so_5::environment_t & env = ...; * env.introduce_coop([](so_5::coop_t & coop) { * const auto broadcasting_mbox = coop.environment().create_mbox(); * auto * bindings = coop.take_under_control( * std::make_unique>()); * \endcode * * \note * This class isn't Copyable, not Moveable. Once created an instance of * that type can't be copied or moved. * * \tparam Lock_Type Type to be used for thread-safety. * It should be a std::mutex-like class. If thread-safety isn't needed * then so_5::null_mutex_t can be used. * * \since v.5.8.0 */ template< typename Lock_Type = std::mutex > class multi_sink_binding_t : protected so_5::details::lock_holder_detector< Lock_Type >::type , protected multi_sink_binding_impl::actual_binding_handler_t { public: multi_sink_binding_t() = default; multi_sink_binding_t( const multi_sink_binding_t & ) = delete; multi_sink_binding_t & operator=( const multi_sink_binding_t & ) = delete; multi_sink_binding_t( multi_sink_binding_t && ) = delete; multi_sink_binding_t & operator=( multi_sink_binding_t && ) = delete; /*! * Create a binding for message/signal of type \a Msg from mbox \a from * to the destination \a dest. * * This binding won't use a delivery filter. * * An exception will be thrown if such a binding already exists. * * Usage example: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * auto binding = std::make_unique< so_5::multi_sink_binding_t<> >(); * * binding->bind(source, dest); * binding->bind(source, dest); * \endcode * * It it's required to make a binding for a mutable message then * so_5::mutable_msg marker has to be used: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * auto binding = std::make_unique< so_5::multi_sink_binding_t<> >(); * * binding->bind< so_5::mutable_msg >(source, dest); * \endcode */ template< typename Msg > void bind( //! The source mbox. const mbox_t & from, //! The destination for messages. const msink_t & dest ) { this->lock_and_perform( [&]() { this->template do_bind< Msg >( from, dest ); } ); } /*! * Create a binding for message of type \a Msg from mbox \a from * to the destination \a dest. * * This binding should use delivery filter \a delivery_filter. * * An exception will be thrown if such a binding already exists. * * \note * This method can't be used for binding signals. */ template< typename Msg > void bind( //! The source mbox. const mbox_t & from, //! The destination for messages. const msink_t & dest, //! Delivery filter to be used. It shouldn't be nullptr. delivery_filter_unique_ptr_t delivery_filter ) { so_5::low_level_api::ensure_not_null( delivery_filter ); this->lock_and_perform( [&]() { this->template do_bind< Msg >( from, dest, std::move(delivery_filter) ); } ); } /*! * Create a binding for message of type \a Msg from mbox \a from * to the destination \a dest. * * The lambda (or functor) \a filter will be used as delivery filter * for messages. * * An exception will be thrown if such a binding already exists. * * \note * This method can't be used for binding signals. * * Usage example: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * auto binding = std::make_unique< so_5::multi_sink_binding_t<> >(); * * binding->bind(source, dest, * [](const my_message & msg) { * ... // should return `true` or `false`. * }); * \endcode * * It it's required to make a binding for a mutable message then * so_5::mutable_msg marker has to be used, but note the type of * delivery filter argument: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * auto binding = std::make_unique< so_5::multi_sink_binding_t<> >(); * * binding->bind< so_5::mutable_msg >(source, dest, * [](const my_message & msg) { * ... // should return `true` or `false`. * }); * \endcode */ template< typename Msg, typename Lambda > void bind( //! The source mbox. const mbox_t & from, //! The destination for messages. const msink_t & dest, //! Delivery filter to be used. Lambda && filter ) { using lambda_type = std::remove_reference_t< Lambda >; using detectable_arg_type = sink_bindings_details::lambda_with_detectable_arg_type_t< lambda_type >; delivery_filter_unique_ptr_t filter_holder; if constexpr( detectable_arg_type::value ) { // Type of filter lambda can be checked by a static_assert. using argument_type = typename detectable_arg_type::argument_t; // Try to check delivery filter lambda argument type // at the compile time. sink_bindings_details::ensure_valid_argument_for_delivery_filter< typename so_5::message_payload_type::payload_type, argument_type >(); filter_holder.reset( new low_level_api::lambda_as_filter_t< lambda_type, argument_type >( std::move(filter) ) ); } else { // Assume that filter lambda is in form: // // [](const auto & msg) -> bool {...} // // so we don't know the type of the argument. using argument_type = typename message_payload_type< Msg >::payload_type; filter_holder.reset( new low_level_api::lambda_as_filter_t< lambda_type, argument_type >( std::move(filter) ) ); } this->bind< Msg >( from, dest, std::move(filter_holder) ); } /*! * Remove binding for message/signal of type \a Msg from mbox \a from * to the destination \a dest. * * It is safe to call this method if such a binding doesn't exist. * * Usage example: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * so_5::multi_sink_binding_t & bindings = ...; * * binding->unbind(source, dest); * binding->unbind(source, dest); * \endcode */ template< typename Msg > void unbind( //! The source mbox. const mbox_t & from, //! The destination for messages. const msink_t & dest ) noexcept { this->lock_and_perform( [&]() { this->template do_unbind< Msg >( from, dest ); } ); } /*! * Remove binding for all message/signal types from mbox \a from * to the destination \a dest. * * It is safe to call this method if there is no any binding for * (\a form, \a dest) pair. * * Usage example: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * so_5::multi_sink_binding_t & bindings = ...; * * binding->unbind_all_for(source, dest); * \endcode */ void unbind_all_for( //! The source mbox. const mbox_t & from, //! The destination for messages. const msink_t & dest ) noexcept { this->lock_and_perform( [&]() { this->do_unbind_all_for( from, dest ); } ); } /*! * Remove all exising bindings. */ void clear() noexcept { this->lock_and_perform( [&]() { this->do_clear(); } ); } }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/nonempty_name.hpp000066400000000000000000000036431523065210100210630ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A nonempty_name class definition. */ #if !defined( SO_5_NONEMPTY_NAME_HPP ) #define SO_5_NONEMPTY_NAME_HPP #include #include #include namespace so_5 { // // nonempty_name_t // //! A class for the name which cannot be empty. /*! * Checks value in the constructor. Throws an exception if the name is empty. */ class nonempty_name_t { // Note: clang-3.9 requires this on Windows platform. nonempty_name_t( const nonempty_name_t & ) = delete; nonempty_name_t & operator=( const nonempty_name_t & ) = delete; private: //! Value. std::string m_nonempty_name; public: //! Constructor with check for the empty value. nonempty_name_t( const char * name ) : nonempty_name_t( std::string(name) ) {} //! Constructor with check for the empty value. nonempty_name_t( std::string name ) : m_nonempty_name( std::move(name) ) { if( m_nonempty_name.empty() ) SO_5_THROW_EXCEPTION( rc_empty_name, "empty string as argument" ); } nonempty_name_t( nonempty_name_t && o ) noexcept : m_nonempty_name( std::move(o.m_nonempty_name) ) {} friend void swap( nonempty_name_t & a, nonempty_name_t & b ) noexcept { swap( a.m_nonempty_name, b.m_nonempty_name ); } nonempty_name_t & operator=( nonempty_name_t && o ) noexcept { nonempty_name_t tmp( std::move(o) ); swap( *this, o ); return *this; } //! Get the value. [[nodiscard]] inline const std::string & query_name() const noexcept { return m_nonempty_name; } /*! * \brief Get the value away from the object. * * \attention * After calling that method m_nonempty_name will become empty! * * \since * v.5.5.19 */ [[nodiscard]] std::string giveout_value() noexcept( noexcept( std::string{ std::move(m_nonempty_name) } ) ) { return std::move(m_nonempty_name); } }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/optional.hpp000066400000000000000000000004051523065210100200300ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.22 * * \brief Definition of optional type. */ #if !defined( SO_5_OPTIONAL_HPP ) #define SO_5_OPTIONAL_HPP #include namespace so_5 { using std::optional; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/outliving.hpp000066400000000000000000000072521523065210100202320ustar00rootroot00000000000000/*! * \file * \brief outliving_reference_t and related stuff. * * \note * This code is borrowed from cpp_util_3 project * (https://bitbucket.org/sobjectizerteam/cpp_util-3.0) * * \since * v.5.5.19 */ #if !defined( SO_5_OUTLIVING_HPP ) #define SO_5_OUTLIVING_HPP #include #include namespace so_5 { /*! * \brief Helper class for indication of long-lived reference via its type. * * Sometimes it is necessary to store a reference to an object that lives * longer that a reference holder. For example: * \code class config {...}; class config_consumer { config & cfg_; public : config_consumer(config & cfg) : cfg_(cfg) {...} ... }; void f() { config cfg = load_config(); // cfg outlives all other objects in f(). config_consumer consumer(cfg); // It is safe to store a reference // for all lifetime of consumer object. ... } * \endcode * * The problem there is: when we see consumer::consumer(cfg) we can * say is it safe to pass a reference to short-lived object to it or not. * Helper class outliving_reference_t can be used as indicator that * consumer::consumer expects a reference to long-lived object: * \code class config_consumer { so_5::outliving_reference_t cfg_; public : config_consumer(so_5::outliving_reference_t cfg) : cfg_(cfg) {...} ... }; void f() { config cfg = load_config(); // An explicit sign that cfg will outlive consumer object. config_consumer consumer(so_5::outliving_mutable(cfg)); ... } * \endcode * * If it is necessary to store a const reference to long-lived object * then outliving_reference_t and outliving_const() should be used: * \code class data_processor { so_5::outliving_reference_t cfg_; public : data_processor(so_5::outliving_reference_t cfg) : cfg_(cfg) {...} ... }; void f() { config cfg = load_config(); config_consumer consumer(so_5::outliving_mutable(cfg)); data_processor processor(so_5::outliving_const(cfg)); ... } * \endcode * * \attention * outliving_reference_t has no copy operator! It is CopyConstructible, * but not CopyAssingable class. * * \note * There is no implicit type conversion operator since v.5.8.0. * * \tparam T type for reference. * * \since v.5.5.19 */ template< typename T > class outliving_reference_t { T * m_ptr; public : using type = T; explicit outliving_reference_t(T & r) noexcept : m_ptr(std::addressof(r)) {} outliving_reference_t(T &&) = delete; outliving_reference_t(outliving_reference_t const & o) noexcept : m_ptr(std::addressof(o.get())) {} template outliving_reference_t(outliving_reference_t const & o) noexcept : m_ptr(std::addressof(o.get())) {} outliving_reference_t & operator=(outliving_reference_t const &o) = delete; T & get() const noexcept { return *m_ptr; } }; /*! * \brief Make outliving_reference wrapper for mutable reference. * * \sa outliving_reference_t * * \since * v.5.5.19 */ template< typename T > outliving_reference_t< T > outliving_mutable( T & r ) { return outliving_reference_t(r); } /*! * \brief Make outliving_reference wrapper for const reference. * * \sa outliving_reference_t * * \since * v.5.5.19 */ template< typename T > outliving_reference_t< const T > outliving_const( T const & r ) { return outliving_reference_t(r); } /*! * \brief Make outliving_reference wrapper for const reference. * * \sa outliving_reference_t * * \since * v.5.5.22 */ template< typename T > outliving_reference_t< const T > outliving_const( outliving_reference_t r ) { return outliving_reference_t(r.get()); } } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/priority.hpp000066400000000000000000000077121523065210100200740ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.8 * * \brief Definition of priority type. */ #if !defined( SO_5_PRIORITY_HPP ) #define SO_5_PRIORITY_HPP #include #include #include namespace so_5 { /*! * \since * v.5.5.8 * * \brief Definition of supported priorities. */ enum class priority_t : unsigned char { p_min = 0, p0 = p_min, p1, p2, p3, p4, p5, p6, p7, p_max = p7 }; /*! * \since * v.5.5.8 * * \brief Helper function for conversion from priority to size_t. */ inline std::size_t to_size_t( priority_t priority ) { return static_cast< std::size_t >( priority ); } /*! * \since * v.5.5.8 * * \brief Helper function for conversion from size_t to priority. * * \throw std::invalid_argument if \a v is out of range. */ inline priority_t to_priority_t( std::size_t v ) { if( !( v >= to_size_t(priority_t::p_min) && v <= to_size_t(priority_t::p_max) ) ) throw std::invalid_argument( "value to be converted to " "priority_t is out of range: " + std::to_string( v ) ); return static_cast< priority_t >( v ); } namespace prio { /*! * \name Constants with priority values. * \{ */ const priority_t p0 = priority_t::p0; const priority_t p1 = priority_t::p1; const priority_t p2 = priority_t::p2; const priority_t p3 = priority_t::p3; const priority_t p4 = priority_t::p4; const priority_t p5 = priority_t::p5; const priority_t p6 = priority_t::p6; const priority_t p7 = priority_t::p7; /*! * \} */ /*! * \since * v.5.5.8 * * \brief Default priority value. */ const priority_t default_priority = p0; /*! * \since * v.5.5.8 * * \brief Total count of priorities. */ const unsigned int total_priorities_count = static_cast< unsigned int >( p7 ) - static_cast< unsigned int >( p0 ) + 1; /*! * \since * v.5.5.8 * * \brief Is there higher priority? */ inline bool has_next( priority_t p ) { return p != priority_t::p_max; } /*! * \since * v.5.5.8 * * \brief Get the next priority value. * * \return next priority value if \a p is less than priority_t::p_max or * priority_t::p_max instead. */ inline priority_t next( priority_t p ) { using ut = std::underlying_type< priority_t >::type; return p < priority_t::p_max ? (static_cast< priority_t >( static_cast< ut >(p) + 1)) : priority_t::p_max; } /*! * \since * v.5.5.8 * * \brief Is there lower priority? */ inline bool has_prev( priority_t p ) { return p != priority_t::p_min; } /*! * \since * v.5.5.8 * * \brief Get the previous priority value. * * \return previous priority value if \a p is greater than priority_t::p_min or * priority_t::p_min instead. */ inline priority_t prev( priority_t p ) { using ut = std::underlying_type< priority_t >::type; return p > priority_t::p_min ? (static_cast< priority_t >(static_cast< ut >(p) - 1 )) : priority_t::p_min; } /*! * \since * v.5.5.8 * * \brief Does enumeration of all priorities. * * \note Performs enumeration from priority_t::p_min to priority_t::p_max * (inclusive). * * \tparam Lambda a lambda with the prototype: * \code return_type lambda(so_5::priority_t); * \endcode * Return value from that lambda will be ignored and discarded. * * \par Example: \code // Fill the new cooperation with agents with different priorities. env.introduce_coop( so_5::disp::prio_one_thread::strictly_ordered::make_dispatcher(env).binder(), [&]( so_5::coop_t & coop ) { so_5::prio::for_each_priority( [&]( so_5::priority_t prio ) { // Creation of new agent with next priority. coop.make_agent< my_prio_agent >( prio, ... ); } ); } ); \endcode */ template< typename Lambda > void for_each_priority( Lambda l ) { using ut = std::underlying_type< priority_t >::type; const auto min = static_cast< ut >( priority_t::p_min ); const auto max = static_cast< ut >( priority_t::p_max ); for( auto i = min; i <= max; ++i ) l( static_cast< priority_t >( i ) ); } } /* namespace prio */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/queue_locks_defaults_manager.cpp000066400000000000000000000031351523065210100241010ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Interface of manager for default locks for event queues. * * \since * v.5.5.18 */ #include namespace so_5 { namespace { // // manager_for_simple_locks_t // class manager_for_simple_locks_t : public queue_locks_defaults_manager_t { public : virtual so_5::disp::mpsc_queue_traits::lock_factory_t mpsc_queue_lock_factory() override { return so_5::disp::mpsc_queue_traits::simple_lock_factory(); } virtual so_5::disp::mpmc_queue_traits::lock_factory_t mpmc_queue_lock_factory() override { return so_5::disp::mpmc_queue_traits::simple_lock_factory(); } }; // // manager_for_combined_locks_t // class manager_for_combined_locks_t : public queue_locks_defaults_manager_t { public : virtual so_5::disp::mpsc_queue_traits::lock_factory_t mpsc_queue_lock_factory() override { return so_5::disp::mpsc_queue_traits::combined_lock_factory(); } virtual so_5::disp::mpmc_queue_traits::lock_factory_t mpmc_queue_lock_factory() override { return so_5::disp::mpmc_queue_traits::combined_lock_factory(); } }; } /* namespace anonymous */ // // make_defaults_manager_for_simple_locks // SO_5_FUNC queue_locks_defaults_manager_unique_ptr_t make_defaults_manager_for_simple_locks() { return std::make_unique< manager_for_simple_locks_t >(); } // // make_defaults_manager_for_combined_locks // SO_5_FUNC queue_locks_defaults_manager_unique_ptr_t make_defaults_manager_for_combined_locks() { return std::make_unique< manager_for_combined_locks_t >(); } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/queue_locks_defaults_manager.hpp000066400000000000000000000042111523065210100241020ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Interface of manager for default locks for event queues. * * \since * v.5.5.18 */ #if !defined( SO_5_QUEUE_LOCKS_DEFAULTS_MANAGER_HPP ) #define SO_5_QUEUE_LOCKS_DEFAULTS_MANAGER_HPP #include #include #include #include namespace so_5 { // // queue_locks_defaults_manager_t // /*! * \brief A base class for manager of default locks for event queues. * * \since * v.5.5.18 */ class SO_5_TYPE queue_locks_defaults_manager_t { public : queue_locks_defaults_manager_t( const queue_locks_defaults_manager_t & ) = delete; queue_locks_defaults_manager_t( queue_locks_defaults_manager_t && ) = delete; queue_locks_defaults_manager_t & operator=( const queue_locks_defaults_manager_t & ) = delete; queue_locks_defaults_manager_t & operator=( queue_locks_defaults_manager_t && ) = delete; queue_locks_defaults_manager_t() = default; virtual ~queue_locks_defaults_manager_t() noexcept = default; //! Get default lock_factory for MPSC queues. virtual so_5::disp::mpsc_queue_traits::lock_factory_t mpsc_queue_lock_factory() = 0; //! Get default lock_factory for MPMC queues. virtual so_5::disp::mpmc_queue_traits::lock_factory_t mpmc_queue_lock_factory() = 0; }; // // queue_locks_defaults_manager_unique_ptr_t // /*! * \brief An alias of unique_ptr for queue_locks_defaults_manager. * * \since * v.5.5.18 */ using queue_locks_defaults_manager_unique_ptr_t = std::unique_ptr< queue_locks_defaults_manager_t >; // // make_defaults_manager_for_simple_locks // /*! * \brief A factory for queue_locks_defaults_manager with * generators for simple locks. * * \since * v.5.5.18 */ SO_5_FUNC queue_locks_defaults_manager_unique_ptr_t make_defaults_manager_for_simple_locks(); // // make_defaults_manager_for_combined_locks // /*! * \brief A factory for queue_locks_defaults_manager with * generators for combined locks. * * \since * v.5.5.18 */ SO_5_FUNC queue_locks_defaults_manager_unique_ptr_t make_defaults_manager_for_combined_locks(); } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/ret_code.hpp000066400000000000000000000333611523065210100177760ustar00rootroot00000000000000/* SObjectizer 5 */ /*! \file \brief Error codes. */ #if !defined( SO_5_RET_CODE_HPP ) #define SO_5_RET_CODE_HPP #include #include namespace so_5 { // // Standard error codes. // //! so_environment launch is failed. const int rc_environment_error = 1; //! \name Error codes for agent's methods. //! \{ //! Trying to switch to the unknown state. const int rc_agent_unknown_state = 10; //! Agent is not bound to a cooperation. const int rc_agent_has_no_cooperation = 14; //! It is impossible to make a cast to that type. const int rc_agent_incompatible_type_conversion = 15; /*! * \brief An attempt to perform an operation which is enabled only * on agent's working thread. * * \since v.5.4.0 */ const int rc_operation_enabled_only_on_agent_working_thread = 16; /*! * \brief An attempt to change agent state to a new composite state which * have no initial state defined. * * \since v.5.5.15 */ const int rc_no_initial_substate = 17; /*! * \brief Nesting of agent states is too deep. * * \since v.5.5.15 */ const int rc_state_nesting_is_too_deep = 18; /*! * \brief Initial substate for a composite state is already defined. * * \since v.5.5.15 */ const int rc_initial_substate_already_defined = 19; //! \} //! \name Error codes for cooperations of agents. //! \{ //! Cooperation couldn't be registered. const int rc_coop_define_agent_failed = 24; /*! * \brief Binding of agent to dispatcher failed. * * \since v.5.4.0 */ const int rc_agent_to_disp_binding_failed = 27; /*! * \brief It is impossible to register cooperation during * SObjectizer Environment shutdown. * * \since v.5.4.0.1 */ const int rc_unable_to_register_coop_during_shutdown = 28; //! \} //! \name Error codes for dispatchers. //! \{ //! Unable to create a dispatcher. const int rc_disp_create_failed = 31; //! \} //! \name Error codes for event handlers and message interceptors registration. //! \{ //! A handler for that event/mbox/state is already registered. const int rc_evt_handler_already_provided = 40; //! Agent doesn't own this state. const int rc_agent_is_not_the_state_owner = 43; /*! * \brief An attempt to create illegal subscription to mpsc_mbox. * * \since v.5.4.0 */ const int rc_illegal_subscriber_for_mpsc_mbox = 47; /*! * \brief An attempt to create subscription to message without * predefined limit for that message type. * * \since v.5.5.4 */ const int rc_message_has_no_limit_defined = 48; /*! * \brief An attempt to define several limits for one message type. * * \since v.5.5.4 */ const int rc_several_limits_for_one_message_type = 49; //! \} //! \name Error codes for mboxes. //! \{ //! Null message data. /*! * An attempt to deliver message_t by nullptr pointer to it is an error. * A signal should be used for this. * * \since v.5.2.0 */ const int rc_null_message_data = 82; //! \} //! \name Error codes for layers. //! \{ //! The layer is not bound to the SObjectizer Environment. const int rc_layer_not_binded_to_so_env = 100; //! Unable to bind a layer by the null pointer to it. const int rc_trying_to_add_nullptr_extra_layer = 101; //! The layer is already bound to the SObjectizer Environment as a default layer. const int rc_trying_to_add_extra_layer_that_already_exists_in_default_list = 102; //! The layer is already bound to the SObjectizer Environment as an extra layer. const int rc_trying_to_add_extra_layer_that_already_exists_in_extra_list = 103; //! Layer initialization is failed. const int rc_unable_to_start_extra_layer = 104; //! A layer with the specified type doesn't exist. const int rc_layer_does_not_exist = 105; //! \} //! \name Error codes for priorities. //! \{ //! Illegal value of quote for a priority. const int rc_priority_quote_illegal_value = 120; //! \} //! \name Error codes for SObjectizer Environment related errors. //! \{ //! Message delivery tracing is disabled and cannot be used. const int rc_msg_tracing_disabled = 140; //! \} //! \name Error codes for message chains. //! \{ //! Attempt to get message from empty message queue. const int rc_msg_chain_is_empty = 160; //! Attempt to push a message to full message queue. const int rc_msg_chain_is_full = 161; //! Attempt to make subscription for message chain. const int rc_msg_chain_doesnt_support_subscriptions = 162; //! Attempt to set delivery_filter for message chain. const int rc_msg_chain_doesnt_support_delivery_filters = 163; //! Attempt to added a message to full message chain when //! throw_exception overflow reaction is used. const int rc_msg_chain_overflow = 164; //! Attempt to define several handlers for one msg_type. const int rc_several_handlers_for_one_message_type = 165; //! \} /*! * \brief An attempt to switch agent state when another switch operation * is in progress. * * \since v.5.5.18 */ const int rc_another_state_switch_in_progress = 170; /*! * \brief An attempt to launch environment with autoshutdown disabled in * conditions where autoshutdown must be enabled. * * For example so_5::env_infrastructures::simple_not_mtsafe requires * autoshutdown feature. * * \since v.5.5.19 */ const int rc_autoshutdown_must_be_enabled = 171; /*! * \brief An attempt to deliver mutable message via MPMC mbox. * * \since v.5.5.19 */ const int rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox = 172; /*! * \brief An attempt to send mutable message as a periodic message * * \since v.5.5.19 */ const int rc_mutable_msg_cannot_be_periodic = 173; /*! * \brief An attempt to make subscription on mutable message from * MPMC mbox. * * Delivery of mutable messages via MPMC mboxes is prohibited. * * \since v.5.5.19 */ const int rc_subscription_to_mutable_msg_from_mpmc_mbox = 174; /*! * \brief An attempt to set up a new stop_guard when the stop operation * is already in progress. * * \since v.5.5.19.2 */ const int rc_cannot_set_stop_guard_when_stop_is_started = 175; /*! * \brief An attempt to use negative value for pause argument for * delayed or periodic message/signal. * * A value of `pause` argument for so_5::send_delayed() and * so_5::send_periodic() must be non-negative. * * \since v.5.5.21 */ const int rc_negative_value_for_pause = 176; /*! * \brief An attempt to use negative value for period argument for * periodic message/signal. * * A value of `pause` argument for so_5::send_periodic() must be non-negative. * * \since v.5.5.21 */ const int rc_negative_value_for_period = 177; /*! * \brief A loop in transfer_to_state detected. * * There could be a case when usage of transfer_to_state leads to a loop. * For a very simple example: * \code * class a_two_state_loop_t final : public so_5::agent_t * { * state_t st_one{ this, "one" }; * state_t st_two{ this, "two" }; * * struct message {}; * * public : * a_two_state_loop_t(context_t ctx) : so_5::agent_t{ctx} { * this >>= st_one; * * st_one.transfer_to_state(st_two); * st_two.transfer_to_state(st_one); * } * * virtual void so_evt_start() override { * so_5::send(*this); * } * }; * \endcode * Since v.5.5.22.1 an attempt to handle `message` in the example above * will lead to an exception with this error code. * * Note that there could be more tricky examples when hierarchical states * are used: * \code * class a_hsm_case_t final : public so_5::agent_t * { * state_t st_base{ this, "base" }; * state_t st_disconnected{ initial_substate_of{st_base}, "disconnected" }; * state_t st_connected{ substate_of{st_base}, "connected" }; * * struct message {}; * * public : * a_hsm_case_t(context_t ctx) : so_5::agent_t{ctx} { * this >>= st_base; * * st_base.transfer_to_state(st_disconnected); * } * * virtual void so_evt_start() override { * so_5::send(*this); * } * }; * \endcode * * \since v.5.5.22.1 */ const int rc_transfer_to_state_loop = 178; /*! * \brief An attempt to cast message to message envelope when * a pointer to message is NULL. * * Envelopes were introduced in v.5.5.23. Envelopes are sent and * delivered as ordinary messages (it means that pointer to envelope * will be sent as pointer to message_t). For processing of an envelope * it is necessary to cast a pointer from message_t to envelope_t. * But it is impossible if a pointer to message_t is null. * * \since v.5.5.23 */ const int rc_attempt_to_cast_to_envelope_on_nullptr = 180; /*! * \brief New step can't be defined if testing scenario is already * started or finished. * * \since v.5.5.24 */ const int rc_unable_to_define_new_step = 181; /*! * \brief Testing scenario must be completed before an attempt * to do the current operation. * * \since v.5.5.24 */ const int rc_scenario_must_be_completed = 182; /*! * \brief There is no stored state name in the testing scenario. * * \since v.5.5.24 */ const int rc_stored_state_name_not_found = 183; /*! * \brief An attempt to get a pointer to already destroyed cooperation. * * This error can be reported when a coop_handle instance for already * destroyed coop is used. * * \since v.5.6.0 */ const int rc_coop_already_destroyed = 184; /*! * \brief An attempt to do something with coop that is not in registered * state. * * For example if can be an attempt to add a new child coop when * the parent coop is being deregistered. * * \since v.5.6.0 */ const int rc_coop_is_not_in_registered_state = 185; /*! * \brief An attempt to call join() from the joinable thread itself. * * \since v.5.6.0 */ const int rc_unable_to_join_thread_by_itself = 186; /*! * \brief An attempt to modify or activate extensible-select when an operation * on that extensible-select object is already active. * * \since v.5.6.1 */ const int rc_extensible_select_is_active_now = 187; /*! * \brief An attempt to activate prepared-select when an operation * on that prepared-select object is already active. * * \since v.5.6.1 */ const int rc_prepared_select_is_active_now = 188; /*! * \brief Agent can't change state because the agent is already deactivated. * * There is a special state for an agent in that agent can't handle events and * only waits for the deregistration. An agent is switched to that state * due to unhandled exception (with exception reaction set to * so_5::deregister_coop_on_exception) or after by a call to * agent_t::so_deactivate_agent(). It is impossible to change the state of * the agent if the agent is in that state already. * * \since v.5.7.3 */ const int rc_agent_deactivated = 189; /*! * \brief An instance of MPSC mbox is expected as custom direct mbox. * * Version 5.7.4 allows to set a custom mbox as the direct mbox for * an agent. That mbox has to be MPSC mbox. An attempt to set MPMC * mbox as the direct mbox will lead to this error. * * \since v.5.7.4 */ const int rc_mpsc_mbox_expected = 190; /*! * \brief nullptr can't be passed as delivery_filter. * * An attempt to pass nullptr pointer to a delivery_filter (or empty * delivery_filter_unique_ptr_t) to a method/function that expects * an actual instance of delivery_filter. * * \since v.5.8.0 */ const int rc_nullptr_as_delivery_filter_pointer = 191; /*! * \brief nullptr returned by user-provided mbox factory. * * The so_5::environment_t::introduce_named_mbox() gets a user-provided * mbox factory. This factory is called by SObjectizer Environment * to create a new instance of the named mbox. This factory must return * a valid so_5::mbox_t. If the returned so_5::mbox_t contains * nullptr it's an error and this error code will be used in an * exception thrown by so_5::environment_t::introduce_named_mbox(). * * \since v.5.8.0 */ const int rc_nullptr_as_result_of_user_mbox_factory = 192; /*! * \brief There are no resources that must have been in place for an agent in * advance. * * Some dispatchers (like so_5::disp::nef_one_thread) preallocates * resources necessary for normal agent work. If those resource are not * found for an agent for some reason it's an error and exception with * this error code is thrown. * * \since v.5.8.0 */ const int rc_no_preallocated_resources_for_agent = 193; /*! * \brief The dispatcher binder is already set for the agent. * * Since v.5.7.5 disp_binder is stored inside the agent. * Only one disp_binder can be set for an agent. An attempt to set * disp_binder when the agent already has one is an error. * * \since v.5.7.5 */ const int rc_disp_binder_already_set_for_agent = 194; /*! * \brief The dispatcher binder is not set for the agent yet. * * Since v.5.7.5 disp_binder is stored inside the agent. * A disp_binder must be set for an agent during addition of the agent * to a coop. It means that if agent is added to coop the agent should have * non-null pointer to the disp_binder. If this pointer is still null then * it's an error. * * \since v.5.7.5 */ const int rc_no_disp_binder_for_agent = 195; /*! * \brief Name for an agent can't be empty. * * \since v.5.8.2 */ const int rc_empty_agent_name = 196; /*! * \brief Length of an agent name is too large. * * \since v.5.8.2 */ const int rc_agent_name_too_long = 197; /*! * \brief There is no stored msg inspection result in the testing scenario. * * \since v.5.8.3 */ const int rc_stored_msg_inspection_result_not_found = 198; //! \name Common error codes. //! \{ //! The empty name doesn't allowed. const int rc_empty_name = 500; /*! * \brief Invalid value of time limit for an agent's state. * * \since v.5.5.15 */ const int rc_invalid_time_limit_for_state = 501; /*! * \brief Feature or method has no implementation yet. * * \since v.5.5.16 */ const int rc_not_implemented = 502; /*! * \brief An exception of unknown type is caught. * * SObjectizer expects that all exceptions are derived from std::exception * class. * * \since v.5.5.24.3 */ const int rc_unknown_exception_type = 503; //! Unclassified error. const int rc_unexpected_error = 0xFFFFFF; //! \} } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/rt.hpp000066400000000000000000000007701523065210100166350ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A wrapper for all major SO Run-Time includes. */ #if !defined( SO_5_RT_HPP ) #include #include #include #include #include #include #include #include #include #include #include #include #endif sobjectizer-5.8.6/dev/so_5/send_functions.hpp000066400000000000000000000544441523065210100212400ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.1 * * \brief Implementation of free functions send/send_delayed. */ #if !defined( SO_5_SEND_FUNCTIONS_HPP ) #define SO_5_SEND_FUNCTIONS_HPP #include #include namespace so_5 { namespace impl { /* * This is helpers for so_5::send implementation. */ template< class Message, bool Is_Signal > struct instantiator_and_sender_base { private : // Helper method for message instance creation and // mutability flag handling. template< typename... Args > static auto make_instance( Args &&... args ) { // it will be std::unique_ptr, where Envelope // can be a different type. But Envelope is derived from // so_5::message_t. // Mutability of a message will be changed appropriately // in make_message_instance. auto msg_instance = so_5::details::make_message_instance< Message >( std::forward< Args >( args )...); return msg_instance; } public : template< typename... Args > static void send( const so_5::mbox_t & to, Args &&... args ) { so_5::low_level_api::deliver_message( message_delivery_mode_t::ordinary, *to, message_payload_type< Message >::subscription_type_index(), make_instance( std::forward(args)... ) ); } template< typename... Args > static void send_delayed( const so_5::mbox_t & to, std::chrono::steady_clock::duration pause, Args &&... args ) { so_5::low_level_api::single_timer( message_payload_type< Message >::subscription_type_index(), message_ref_t{ make_instance( std::forward(args)... ) }, to, pause ); } template< typename... Args > [[nodiscard]] static timer_id_t send_periodic( const so_5::mbox_t & to, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period, Args &&... args ) { return so_5::low_level_api::schedule_timer( message_payload_type< Message >::subscription_type_index(), message_ref_t{ make_instance( std::forward(args)... ) }, to, pause, period ); } }; template< class Message > struct instantiator_and_sender_base< Message, true > { //! Type of signal to be delivered. using actual_signal_type = typename message_payload_type< Message >::subscription_type; static void send( const so_5::mbox_t & to ) { using namespace so_5::low_level_api; deliver_signal< actual_signal_type >( message_delivery_mode_t::ordinary, *to ); } static void send_delayed( const so_5::mbox_t & to, std::chrono::steady_clock::duration pause ) { so_5::low_level_api::single_timer( message_payload_type::subscription_type_index(), message_ref_t{}, to, pause ); } [[nodiscard]] static timer_id_t send_periodic( const so_5::mbox_t & to, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) { return so_5::low_level_api::schedule_timer( message_payload_type< Message >::subscription_type_index(), message_ref_t{}, to, pause, period ); } }; template< class Message > struct instantiator_and_sender : public instantiator_and_sender_base< Message, is_signal< typename message_payload_type< Message >::payload_type >::value > {}; } /* namespace impl */ /*! * \since * v.5.5.9 * * \brief Implementation details for send-family and request_future/value helper functions. */ namespace send_functions_details { inline const so_5::mbox_t & arg_to_mbox( const so_5::mbox_t & mbox ) { return mbox; } inline const so_5::mbox_t & arg_to_mbox( const so_5::agent_t & agent ) { return agent.so_direct_mbox(); } inline so_5::mbox_t arg_to_mbox( const so_5::mchain_t & chain ) { return chain->as_mbox(); } inline so_5::environment_t & arg_to_env( const so_5::mbox_t & mbox ) { return mbox->environment(); } inline so_5::environment_t & arg_to_env( const so_5::agent_t & agent ) { return agent.so_environment(); } inline so_5::environment_t & arg_to_env( const so_5::mchain_t & chain ) { return chain->environment(); } } /* namespace send_functions_details */ /*! * \since * v.5.5.1 * * \brief A utility function for creating and delivering a message or a signal. * * \note Since v.5.5.13 can send also a signal. * * \tparam Message type of message to be sent. * \tparam Target identification of request processor. Could be reference to * so_5::mbox_t, to so_5::agent_t (the later case agent's direct * mbox will be used). * \tparam Args arguments for Message's constructor. * * \par Usage samples: * \code struct hello_msg { std::string greeting; std::string who }; // Send to mbox. so_5::send< hello_msg >( env.create_mbox( "hello" ), "Hello", "World!" ); // Send to agent. class demo_agent : public so_5::agent_t { public : ... virtual void so_evt_start() override { ... so_5::send< hello_msg >( *this, "Hello", "World!" ); } }; struct turn_on : public so_5::signal_t {}; // Send to mbox. so_5::send< turn_on >( env.create_mbox( "engine" ) ); // Send to agent. class engine_agent : public so_5::agent_t { public : ... virtual void so_evt_start() override { ... so_5::send< turn_on >( *this ); } }; * \endcode */ template< typename Message, typename Target, typename... Args > void send( Target && to, Args&&... args ) { so_5::impl::instantiator_and_sender< Message >::send( send_functions_details::arg_to_mbox( std::forward(to) ), std::forward(args)... ); } /*! * \brief A version of %send function for redirection of a message * from exising message hood. * * \tparam Message a type of message to be redirected (it can be * in form of Msg, so_5::immutable_msg or so_5::mutable_msg). * * Usage example: * \code class redirector : public so_5::agent_t { ... void on_some_immutable_message(mhood_t cmd) { so_5::send(another_mbox, cmd); ... } void on_some_mutable_message(mhood_t> cmd) { so_5::send(another_mbox, std::move(cmd)); // Note: cmd is nullptr now, it can't be used anymore. ... } }; * \endcode * * \since * v.5.5.19 */ template< typename Target, typename Message > typename std::enable_if< !is_signal< Message >::value >::type send( Target && to, mhood_t< Message > what ) { using namespace so_5::low_level_api; deliver_message( message_delivery_mode_t::ordinary, *send_functions_details::arg_to_mbox( std::forward(to) ), message_payload_type::subscription_type_index(), what.make_reference() ); } /*! * \brief A version of %send function for redirection of a signal * from exising message hood. * * \tparam Message a type of signal to be redirected (it can be * in form of Sig or so_5::immutable_msg). * * Usage example: * \code class redirector : public so_5::agent_t { ... void on_some_immutable_signal(mhood_t cmd) { so_5::send(another_mbox, cmd); ... } }; * \endcode * * \since * v.5.5.19 */ template< typename Target, typename Message > typename std::enable_if< is_signal< Message >::value >::type send( Target && to, mhood_t< Message > /*what*/ ) { so_5::impl::instantiator_and_sender< Message >::send( send_functions_details::arg_to_mbox( std::forward(to) ) ); } /*! * \brief A version of %send function for redirection of a message * from exising message_holder instance. * * Usage example: * \code class preallocated_messages_owner final : public so_5::agent_t { so_5::message_holder_t first_; so_5::message_holder_t> second_; ... void on_some_event(mhood_t) { // It is time to send preallocated messages. // This message will be sent as immutable message. so_5::send(dest, first_); // This message will be sent as mutable message. so_5::send(dest, std::move(second_)); } }; * \endcode * * \attention * An attempt to call this function for empty message_holder object is UB. * * \since * v.5.6.0 */ template< typename Target, typename Message, message_ownership_t Ownership> void send( //! Destination for the message. Target && to, //! Message to be sent. message_holder_t what ) { using namespace so_5::low_level_api; deliver_message( message_delivery_mode_t::ordinary, *send_functions_details::arg_to_mbox( std::forward(to) ), message_payload_type::subscription_type_index(), what.make_reference() ); } /*! * \brief A utility function for creating and delivering a delayed message * to the specified destination. * * Agent or mchain can be used as \a target. * * \attention * Value of \a pause should be non-negative. * * \tparam Message type of message or signal to be sent. * \tparam Target can be so_5::agent_t or so_5::mchain_t. * \tparam Args list of arguments for Message's constructor. * * \since * v.5.5.19 */ template< typename Message, typename Target, typename... Args > void send_delayed( //! A target for delayed message. Target && target, //! Pause for message delaying. std::chrono::steady_clock::duration pause, //! Message constructor parameters. Args&&... args ) { using namespace send_functions_details; so_5::impl::instantiator_and_sender< Message >::send_delayed( arg_to_mbox( target ), pause, std::forward< Args >(args)... ); } /*! * \brief A utility function for delayed redirection of a message * from existing message hood. * * \tparam Target a type of destination of the message. It can be an agent, * a mbox or mchain. * \tparam Message a type of message to be redirected (it can be * in form of Msg, so_5::immutable_msg or so_5::mutable_msg). * * Usage example: * \code class redirector : public so_5::agent_t { ... void on_some_immutable_message(mhood_t cmd) { so_5::send_delayed(another_mbox, std::chrono::seconds(1), cmd); ... } void on_some_mutable_message(mhood_t> cmd) { so_5::send_delayed(another_mbox, std::chrono::seconds(1), std::move(cmd)); // Note: cmd is nullptr now, it can't be used anymore. ... } }; * \endcode * A redirection to the direct mbox of some agent can looks like: * \code * void some_agent::on_some_message(mhood_t cmd) { * // Redirect to itself but with a pause. * so_5::send_delayed(*this, std::chrono::seconds(2), cmd); * } * // For the case of mutable message. * void another_agent::on_another_msg(mutable_mhood_t cmd) { * // Redirect to itself but with a pause. * so_5::send_delayed(*this, std::chrono::seconds(2), std::move(cmd)); * // Note: cmd is nullptr now, it can't be used anymore. * } * \endcode * A redirection to a mchain can looks like: * \code * so_5::mchain_t first = ...; * so_5::mchain_t second = ...; * so_5::receive(so_5::from(first).handle_n(1), * [second](so_5::mhood_t cmd) { * so_5::send_delayed(second, std::chrono::seconds(1), cmd); * }, * [second](so_5::mutable_mhood_t cmd) { * so_5::send_delayed(second, std::chrono::seconds(1), std::move(cmd)); * // Note: cmd is nullptr now, it can't be used anymore. * }); * \endcode * * \attention * Value of \a pause should be non-negative. * * \since * v.5.5.19 */ template< typename Target, typename Message > typename std::enable_if< !message_payload_type::is_signal >::type send_delayed( //! Destination for the message. Target && to, //! Pause for message delaying. std::chrono::steady_clock::duration pause, //! Message instance owner. mhood_t< Message > msg ) { using namespace send_functions_details; so_5::low_level_api::single_timer( message_payload_type< Message >::subscription_type_index(), msg.make_reference(), arg_to_mbox( to ), pause ); } /*! * \brief A utility function for delayed redirection of a signal * from existing message hood. * * \tparam Target a type of destination of the signal. It can be an agent, * a mbox or mchain. * \tparam Message a type of signal to be redirected (it can be * in form of Sig or so_5::immutable_msg). * * Usage example: * \code class redirector : public so_5::agent_t { ... void on_some_immutable_signal(mhood_t cmd) { so_5::send_delayed(another_mbox, std::chrono::seconds(1), cmd); ... } }; * \endcode * A redirection to the direct mbox of some agent can looks like: * \code * void some_agent::on_some_signal(mhood_t cmd) { * // Redirect to itself but with a pause. * so_5::send_delayed(*this, std::chrono::seconds(2), cmd); * } * \endcode * A redirection to a mchain can looks like: * \code * so_5::mchain_t first = ...; * so_5::mchain_t second = ...; * so_5::receive(so_5::from(first).handle_n(1), * [second](so_5::mhood_t cmd) { * so_5::send_delayed(second, std::chrono::seconds(1), cmd); * }); * \endcode * * \attention * Value of \a pause should be non-negative. * * \since * v.5.5.19 */ template< typename Target, typename Message > typename std::enable_if< message_payload_type::is_signal >::type send_delayed( //! Destination for the message. Target && to, //! Pause for message delaying. std::chrono::steady_clock::duration pause, //! Message instance owner. mhood_t< Message > /*msg*/ ) { using namespace send_functions_details; so_5::low_level_api::single_timer( message_payload_type< Message >::subscription_type_index(), message_ref_t{}, arg_to_mbox( to ), pause ); } /*! * \brief A version of %send_delayed function for redirection of a message * from exising message_holder instance. * * Usage example: * \code class preallocated_messages_owner final : public so_5::agent_t { so_5::message_holder_t first_; so_5::message_holder_t> second_; ... void on_some_event(mhood_t) { // It is time to send preallocated messages. // This message will be sent as immutable message. so_5::send_delayed(dest, 15s, first_); // This message will be sent as mutable message. so_5::send_delayed(dest, 15s, std::move(second_)); } }; * \endcode * * \attention * An attempt to call this function for empty message_holder object is UB. * * \since * v.5.6.0 */ template< typename Target, typename Message, message_ownership_t Ownership > void send_delayed( //! Destination for the message. Target && to, //! Pause for message delaying. std::chrono::steady_clock::duration pause, //! Message to be sent message_holder_t msg ) { using namespace send_functions_details; so_5::low_level_api::single_timer( message_payload_type< Message >::subscription_type_index(), msg.make_reference(), arg_to_mbox( to ), pause ); } /*! * \brief A utility function for creating and delivering a periodic message * to the specified destination. * * Agent or mchain can be used as \a target. * * \note * Message chains with overload control must be used for periodic messages * with additional care: \ref so_5_5_18__overloaded_mchains_and_timers. * * \attention * Values of \a pause and \a period should be non-negative. * * \tparam Message type of message or signal to be sent. * \tparam Target can be so_5::agent_t or so_5::mchain_t. * \tparam Args list of arguments for Message's constructor. * * \since * v.5.5.19 */ template< typename Message, typename Target, typename... Args > [[nodiscard]] timer_id_t send_periodic( //! A destination for the periodic message. Target && target, //! Pause for message delaying. std::chrono::steady_clock::duration pause, //! Period of message repetitions. std::chrono::steady_clock::duration period, //! Message constructor parameters. Args&&... args ) { using namespace send_functions_details; return so_5::impl::instantiator_and_sender< Message >::send_periodic( arg_to_mbox( target ), pause, period, std::forward< Args >(args)... ); } /*! * \brief A utility function for delivering a periodic * from an existing message hood. * * \attention Message must not be a mutable message if \a period is not 0. * Otherwise an exception will be thrown. * * \tparam Target a type of destination of the message. It can be an agent, * a mbox or mchain. * \tparam Message a type of message to be redirected (it can be * in form of Msg, so_5::immutable_msg or so_5::mutable_msg). * * Usage example: * \code class redirector : public so_5::agent_t { ... void on_some_immutable_message(mhood_t cmd) { timer_id = so_5::send_periodic(another_mbox, std::chrono::seconds(1), std::chrono::seconds(15), cmd); ... } void on_some_mutable_message(mhood_t> cmd) { timer_id = so_5::send_periodic(another_mbox, std::chrono::seconds(1), std::chrono::seconds::zero(), // Note: period is 0! std::move(cmd)); // Note: cmd is nullptr now, it can't be used anymore. ... } }; * \endcode * A redirection to the direct mbox of some agent can looks like: * \code * void some_agent::on_some_message(mhood_t cmd) { * // Redirect to itself but with a pause and a period. * timer_id = so_5::send_periodic(*this, * std::chrono::seconds(2), * std::chrono::seconds(5), * cmd); * } * \endcode * A redirection to a mchain can looks like: * \code * so_5::mchain_t first = ...; * so_5::mchain_t second = ...; * so_5::timer_id_t timer_id; * so_5::receive(so_5::from(first).handle_n(1), * [&](so_5::mhood_t cmd) { * timer_id = so_5::send_periodic( * second, * std::chrono::seconds(2), * std::chrono::seconds(5), * cmd); * }); * \endcode * \note * Message chains with overload control must be used for periodic messages * with additional care: \ref so_5_5_18__overloaded_mchains_and_timers. * * \attention * Values of \a pause and \a period should be non-negative. * * \since * v.5.5.19 */ template< typename Target, typename Message > [[nodiscard]] typename std::enable_if< !is_signal< Message >::value, timer_id_t >::type send_periodic( //! A destination for the periodic message. Target && target, //! Pause for message delaying. std::chrono::steady_clock::duration pause, //! Period of message repetitions. std::chrono::steady_clock::duration period, //! Existing message hood for message to be sent. mhood_t< Message > mhood ) { using namespace send_functions_details; return so_5::low_level_api::schedule_timer( message_payload_type< Message >::subscription_type_index(), mhood.make_reference(), arg_to_mbox( target ), pause, period ); } /*! * \brief A utility function for periodic redirection of a signal * from existing message hood. * * \tparam Target a type of destination of the signal. It can be an agent, * a mbox or mchain. * \tparam Message a type of signal to be redirected (it can be * in form of Sig or so_5::immutable_msg). * * Usage example: * \code class redirector : public so_5::agent_t { ... void on_some_immutable_signal(mhood_t cmd) { timer_id = so_5::send_periodic(another_mbox, std::chrono::seconds(1), std::chrono::seconds(10), cmd); ... } }; * \endcode * A redirection to the direct mbox of some agent can looks like: * \code * void some_agent::on_some_message(mhood_t cmd) { * // Redirect to itself but with a pause and a period. * timer_id = so_5::send_periodic(*this, * std::chrono::seconds(2), * std::chrono::seconds(5), * cmd); * } * \endcode * A redirection to a mchain can looks like: * \code * so_5::mchain_t first = ...; * so_5::mchain_t second = ...; * so_5::timer_id_t timer_id; * so_5::receive(so_5::from(first).handle_n(1), * [&](so_5::mhood_t cmd) { * timer_id = so_5::send_periodic( * second, * std::chrono::seconds(2), * std::chrono::seconds(5), * cmd); * }); * \endcode * \note * Message chains with overload control must be used for periodic messages * with additional care: \ref so_5_5_18__overloaded_mchains_and_timers. * * \attention * Values of \a pause and \a period should be non-negative. * * \since * v.5.5.19 */ template< typename Target, typename Message > [[nodiscard]] typename std::enable_if< is_signal< Message >::value, timer_id_t >::type send_periodic( //! A destination for the periodic message. Target && target, //! Pause for message delaying. std::chrono::steady_clock::duration pause, //! Period of message repetitions. std::chrono::steady_clock::duration period, //! Existing message hood for message to be sent. mhood_t< Message > /*mhood*/ ) { using namespace send_functions_details; return so_5::low_level_api::schedule_timer( message_payload_type< Message >::subscription_type_index(), message_ref_t{}, arg_to_mbox( target ), pause, period ); } /*! * \brief A version of %send_periodic function for redirection of a message * from exising message_holder instance. * * \note * Note that an attempt to call with function with non-zero \a period * argument for a mutable messages will lead to an exception. * It is because mutable message can't be sent as periodic message. * * Usage example: * \code class preallocated_messages_owner final : public so_5::agent_t { so_5::message_holder_t msg_; so_5::timer_id_t msg_timer_; ... void on_some_event(mhood_t) { // It is time to send preallocated message. // This message will be sent as immutable message. msg_timer_ = so_5::send_periodic(dest, 15s, 20s, msg_); } }; * \endcode * * \attention * An attempt to call this function for empty message_holder object is UB. * * \since * v.5.6.0 */ template< typename Target, typename Message, message_ownership_t Ownership > [[nodiscard]] timer_id_t send_periodic( //! A destination for the periodic message. Target && target, //! Pause for message delaying. std::chrono::steady_clock::duration pause, //! Period of message repetitions. std::chrono::steady_clock::duration period, //! Message to be sent. message_holder_t what ) { using namespace send_functions_details; return so_5::low_level_api::schedule_timer( message_payload_type< Message >::subscription_type_index(), what.make_reference(), arg_to_mbox( target ), pause, period ); } } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/single_sink_binding.hpp000066400000000000000000000376771523065210100222270ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Stuff for single_sink_binding implementation. * * \since v.5.8.0 */ #if !defined( SO_5_SINGLE_SINK_BINDING_HPP ) #define SO_5_SINGLE_SINK_BINDING_HPP #include #include #include #include namespace so_5 { namespace sink_bindings_details { /*! * \brief Helper metafunction to check delivery filter lambda. * * A delivery filter lambda can be specified in two forms: * \code * // The first: explicit message type as argument. * [](const some_msg & msg) -> bool {...} * * // The second: auto detection of a message type. * [](const auto & msg) -> bool {...} * \endcode * * In the first case the type of message can be checked and error * can be raised at the compile-time if a delivery filter is specified * for a different message type. For example: * \code * struct msg_A {...}; * struct msg_B {...}; * * // NOTE: it's an error! * binding.bind(source, dest, [](const msg_B & msg) {...}); * \endcode * * This helper metafunction allows to check if delivery filter lambda * have an argument with explicitly specified type. * * \since v.5.8.1 */ template< typename Lambda, typename = void > struct lambda_with_detectable_arg_type_t : public std::false_type {}; /*! * \brief Specialization for a case when delivery filter lambda has an argument * with explicitly specified type. * * \since v.5.8.1 */ template< typename Lambda > struct lambda_with_detectable_arg_type_t< Lambda, typename std::void_t< typename so_5::details::lambda_traits::argument_type_if_lambda< Lambda >::type > > : public std::true_type { using argument_t = typename so_5::details::lambda_traits::argument_type_if_lambda< Lambda >::type; }; /*! * @brief Helper to have more information in compiler output if static_assert * fails. * * If static_assert fails compiler will show actual names of Subscription_Type * and Delivery_Filter_Arg_Type in an error message. */ template< typename Subscription_Type, typename Delivery_Filter_Arg_Type> void ensure_valid_argument_for_delivery_filter() { static_assert( std::is_same_v< Subscription_Type, Delivery_Filter_Arg_Type >, "delivery filter lambda expects a different message type" ); } } /* namespace sink_bindings_details */ // // single_sink_binding_t // /*! * \brief Helper class for managing single sink bindings. * * An instance of single_sink_binding_t drops the binding in the destructor. * If it's necessary to drop the binding manually then clear()/unbind() methods * can be used. * * Usage examples: * \code * // Use as a part of an agent. * class coordinator final : public so_5::agent_t * { * const so_5::mbox_t broadcasting_mbox_; * so_5::single_sink_binding_t bindings_; * ... * void on_some_event(mhood_t cmd) { * // Create a child coop and bind an agent to broadcasting mbox. * so_5::introduce_child_coop(*this, [](so_5::coop_t & coop) { * auto * worker = coop.make_agent(...); * auto worker_msink = so_5::wrap_to_msink(worker->so_direct_mbox()); * * bindings_.bind(broadcasting_mbox_, worker_msink); * ... * }); * } * }; * * // Use as object controlled by a coop. * so_5::environment_t & env = ...; * env.introduce_coop([](so_5::coop_t & coop) { * const auto broadcasting_mbox = coop.environment().create_mbox(); * auto * first = coop.make_agent(...); * auto * first_binding = coop.take_under_control( * std::make_unique() ); * first_binding->bind(broadcasting_mbox, * so_5::wrap_to_msink(first->so_direct_mbox())); * * auto * second = coop.make_agent(...); * auto * second_binding = coop.take_under_control( * std::make_unique() ); * second_binding->bind(broadcasting_mbox, * so_5::wrap_to_msink(second->so_direct_mbox())); * ... * }); * \endcode * * There is a principial difference between single_sink_binding_t and * multi_sink_binding_t: if bind() is called for single_sink_binding_t when * the binding is exists, then old binding will be dropped and new binding will * be created. For example, this is a valid behavior for single_sink_binding_t: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * binding.bind(source, dest); // New binding created. * ... * binding.bind(source, dest); // Old binding will be removed * // and new binding will be created (despite the fact that source and dest * // are the same). * \endcode * Contrary, multi_sink_binding_t::bind() throws if a binding for triplet * (message, source, dest) already exists. * * \attention * The instance of single_sink_binding_t is not thread safe. If a user wants * to work with an instance of single_sink_binding_t from different * threads then the user has to protect the instance by her/himself. * * \note * This class is Moveable, but is not Copyable. * * \since v.5.8.0 */ class single_sink_binding_t { /*! * Actual information about binding. */ struct binding_info_t { //! The source mbox. mbox_t m_source; //! Type of message/signal. std::type_index m_msg_type; //! The destination for messages/signals. msink_t m_sink_owner; //! Optional delivery filter. /*! * May be nullptr. Can't be used for signals. */ delivery_filter_unique_ptr_t m_delivery_filter{}; binding_info_t( const mbox_t & source, const std::type_index & msg_type, const msink_t & sink_owner, delivery_filter_unique_ptr_t delivery_filter ) noexcept : m_source{ source } , m_msg_type{ msg_type } , m_sink_owner{ sink_owner } , m_delivery_filter{ std::move(delivery_filter) } {} }; /*! * Information about the current binding. * * Empty value means that there is no binding at the moment. */ std::optional< binding_info_t > m_info; public: friend void swap( single_sink_binding_t & a, single_sink_binding_t & b ) noexcept { using std::swap; swap( a.m_info, b.m_info ); } single_sink_binding_t() noexcept = default; // NOTE: this pragma was added because of a problem with GCC-13/14 with // -Werror, -Wall, -Wpedantic. GCC-13/14 complained sometimes // (test/so_5/mbox/sink_binding/single_sink_too_deep/main.cpp) about the use of // uninitialized pointer somewhere inside implementation of std::unique_ptr. // It seems to be a bug in the compiler. #if defined(__GNUG__) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif ~single_sink_binding_t() noexcept { clear(); } #if defined(__GNUG__) && !defined(__clang__) #pragma GCC diagnostic pop #endif single_sink_binding_t( const single_sink_binding_t & ) = delete; single_sink_binding_t & operator=( const single_sink_binding_t & ) = delete; single_sink_binding_t( single_sink_binding_t && other ) noexcept : m_info{ std::exchange( other.m_info, std::nullopt ) } {} single_sink_binding_t & operator=( single_sink_binding_t && other ) noexcept { single_sink_binding_t tmp{ std::move(other) }; swap( *this, tmp ); return *this; } /*! * \retval true if binding exists * \retval false if there is no binding at the moment. */ [[nodiscard]] bool has_value() const noexcept { return m_info.has_value(); } /*! * \retval false if binding exists * \retval true if there is no binding at the moment. */ [[nodiscard]] bool empty() const noexcept { return !has_value(); } /*! * Remove the current binding. * * It's safe to call this method even if there is no binding * at the moment. * * The object can be used for creation of a new binding after calling * clear() method. For example: * \code * so_5::single_sink_binding_t & binding = ...; * * binding.clear(); // Object is empty now. * * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * binding.bind(source, dest); // New binding created. * \endcode */ void clear() noexcept { if( m_info.has_value() ) { if( m_info->m_delivery_filter ) { m_info->m_source->drop_delivery_filter( m_info->m_msg_type, m_info->m_sink_owner->sink() ); m_info->m_delivery_filter.reset(); } m_info->m_source->unsubscribe_event_handler( m_info->m_msg_type, m_info->m_sink_owner->sink() ); m_info.reset(); } } /*! * A synonym for the clear() method. */ void unbind() noexcept { clear(); } /*! * Helper method for creation of a new binding for case when * the type of message/signal is represented as std::type_index. * * \note * This method is intended to be used for internal use. It's not * guaranteed that it won't be changed (or removed) in future * versions of SObjectizer. */ void bind_for_msg_type( //! Type of the message/signal. const std::type_index & msg_type, //! The source mbox. const mbox_t & source, //! The destination for messages/signals. const msink_t & sink_owner ) { // Previous binding has to be dropped. clear(); source->subscribe_event_handler( msg_type, sink_owner->sink() ); // Don't expect that this action can throw! so_5::details::invoke_noexcept_code( [&] { m_info = binding_info_t{ source, msg_type, sink_owner, delivery_filter_unique_ptr_t{} }; } ); } /*! * Create a binding for message/signal of type \a Msg from mbox \a source * to the destination \a sink_owner. * * This binding won't use a delivery filter. * * If the object already holds a binding the current binding will be * removed before the creation of a new one. * * Usage example: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * auto binding = std::make_unique< so_5::single_sink_binding_t >(); * * binding->bind(source, dest); * \endcode * * It it's required to make a binding for a mutable message then * so_5::mutable_msg marker has to be used: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * auto binding = std::make_unique< so_5::single_sink_binding_t >(); * * binding->bind< so_5::mutable_msg >(source, dest); * \endcode */ template< typename Msg > void bind( //! The source mbox. const mbox_t & source, //! The destination for messages/signals. const msink_t & sink_owner ) { this->bind_for_msg_type( message_payload_type::subscription_type_index(), source, sink_owner ); } /*! * Helper method for creation of a new binding for case when * the type of message is represented as std::type_index. * * If the object already holds a binding the current binding will be * removed before the creation of a new one. * * \note * This method is intended to be used for internal use. It's not * guaranteed that it won't be changed (or removed) in future * versions of SObjectizer. * * \note * This method can't be used for binding signals. */ void bind_for_msg_type( //! The type of the message. const std::type_index & msg_type, //! The source mbox. const mbox_t & source, //! The destination for messages. const msink_t & sink_owner, //! Delivery filter to be used. It shouldn't be nullptr. delivery_filter_unique_ptr_t delivery_filter ) { so_5::low_level_api::ensure_not_null( delivery_filter ); // Previous binding has to be dropped. clear(); // Assume that this check was performed by caller. // ensure_not_signal< Msg >(); source->set_delivery_filter( msg_type, *delivery_filter, sink_owner->sink() ); so_5::details::do_with_rollback_on_exception( [&]() { source->subscribe_event_handler( msg_type, sink_owner->sink() ); }, [&]() { source->drop_delivery_filter( msg_type, sink_owner->sink() ); } ); // Don't expect that this action can throw! so_5::details::invoke_noexcept_code( [&] { m_info = binding_info_t{ source, msg_type, sink_owner, std::move(delivery_filter) }; } ); } /*! * Create a binding for message of type \a Msg from mbox \a source * to the destination \a sink_owner. * * This binding should use delivery filter \a delivery_filter. * * If the object already holds a binding the current binding will be * removed before the creation of a new one. * * \note * This method can't be used for binding signals. */ template< typename Msg > void bind( //! The source mbox. const mbox_t & source, //! The destination for messages. const msink_t & sink_owner, //! Delivery filter to be used. It shouldn't be nullptr. delivery_filter_unique_ptr_t delivery_filter ) { ensure_not_signal< Msg >(); so_5::low_level_api::ensure_not_null( delivery_filter ); bind_for_msg_type( message_payload_type< Msg >::subscription_type_index(), source, sink_owner, std::move(delivery_filter) ); } /*! * Create a binding for message of type \a Msg from mbox \a source * to the destination \a sink_owner. * * The lambda (or functor) \a filter will be used as delivery filter * for messages. * * If the object already holds a binding the current binding will be * removed before the creation of a new one. * * \note * This method can't be used for binding signals. * * Usage example: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * auto binding = std::make_unique< so_5::single_sink_binding_t >(); * * binding->bind(source, dest, * [](const my_message & msg) { * ... // should return `true` or `false`. * }); * \endcode * * It it's required to make a binding for a mutable message then * so_5::mutable_msg marker has to be used, but note the type of * delivery filter argument: * \code * const so_5::mbox_t & source = ...; * const so_5::msink_t & dest = ...; * auto binding = std::make_unique< so_5::single_sink_binding_t >(); * * binding->bind< so_5::mutable_msg >(source, dest, * [](const my_message & msg) { * ... // should return `true` or `false`. * }); * \endcode */ template< typename Msg, typename Lambda > void bind( //! The source mbox. const mbox_t & source, //! The destination for messages. const msink_t & sink_owner, //! Filter to be used. Lambda && filter ) { using lambda_type = std::remove_reference_t< Lambda >; using detectable_arg_type = sink_bindings_details::lambda_with_detectable_arg_type_t< lambda_type >; delivery_filter_unique_ptr_t filter_holder; if constexpr( detectable_arg_type::value ) { // Type of filter lambda can be checked by a static_assert. using argument_type = typename detectable_arg_type::argument_t; // Try to check delivery filter lambda argument type // at the compile time. sink_bindings_details::ensure_valid_argument_for_delivery_filter< typename so_5::message_payload_type::payload_type, argument_type >(); filter_holder.reset( new low_level_api::lambda_as_filter_t< lambda_type, argument_type >( std::move(filter) ) ); } else { // Assume that filter lambda is in form: // // [](const auto & msg) -> bool {...} // // so we don't know the type of the argument. using argument_type = typename message_payload_type< Msg >::payload_type; filter_holder.reset( new low_level_api::lambda_as_filter_t< lambda_type, argument_type >( std::move(filter) ) ); } this->bind< Msg >( source, sink_owner, std::move(filter_holder) ); } }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/so_layer.cpp000066400000000000000000000007571523065210100200250ustar00rootroot00000000000000/* SObjectizer 5. */ #include #include namespace so_5 { void layer_t::start() { } void layer_t::shutdown() { } void layer_t::wait() { } environment_t & layer_t::so_environment() const { if( nullptr == m_env ) { throw so_5::exception_t( "so_environment isn't bound to this layer", rc_layer_not_binded_to_so_env ); } return *m_env; } void layer_t::bind_to_environment( environment_t * env ) { m_env = env; } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/so_layer.hpp000066400000000000000000000037601523065210100200270ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief An addition layer for the SObjectizer Environment definition. */ #if !defined( SO_5_SO_LAYER_HPP ) #define SO_5_SO_LAYER_HPP #include #include #include #include #include #include #include namespace so_5 { // // so_layer_t // //! An interface of the additional SObjectizer Environment layer. class SO_5_TYPE layer_t { friend class impl::layer_core_t; // Note: clang-3.9 requires this on Windows platform. layer_t( const layer_t & ) = delete; layer_t( layer_t && ) = delete; layer_t & operator=( const layer_t & ) = delete; layer_t & operator=( layer_t && ) = delete; public: layer_t() = default; virtual ~layer_t() noexcept = default; //! Start hook. /*! * The default implementation do nothing. */ virtual void start(); //! Shutdown signal hook. /*! * The default implementation do nothing. */ virtual void shutdown(); //! Waiting for the complete shutdown of a layer. /*! * The default implementation do nothing and returned immediately. */ virtual void wait(); protected: //! Access to the SObjectizer Environment. /*! * Throws an exception if a layer is not bound to * the SObjectizer Environment. */ environment_t & so_environment() const; private: //! Bind layer to the SObjectizer Environment. void bind_to_environment( environment_t * env ); //! SObjectizer Environment to which layer is bound. /*! * Pointer has the actual value only after binding * to the SObjectizer Environment. */ environment_t * m_env{}; }; //! Typedef for the layer's autopointer. using layer_unique_ptr_t = std::unique_ptr< layer_t >; //! Typedef for the layer's smart pointer. using layer_ref_t = std::shared_ptr< layer_t >; //! Typedef for the map from a layer typeid to the layer. using layer_map_t = std::map< std::type_index, layer_ref_t >; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/spinlocks.hpp000066400000000000000000000115371523065210100202200ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.4.0 * * \file * \brief Definition of various types of spinlocks. */ #if !defined( SO_5_SPINLOCKS_HPP ) #define SO_5_SPINLOCKS_HPP #include #include #include #if defined(_MSC_VER) && defined(__SSE2__) #define SO_5_ARCH_MSC_WITH_SSE2 #include #endif namespace so_5 { // // yield_backoff_t // /*! * \since * v.5.4.0 * * \brief An implementation of backoff object with usage of std::yield. */ class yield_backoff_t { public : inline void operator()() { std::this_thread::yield(); } }; // // pause_backoff_t // /*! * \since * v.5.5.22.2 * * \brief An implementation of backoff object using assembly instruction. * * \note * This implementation is provided by Pavel Begunkov. */ class pause_backoff_t { public : inline void operator()() { #if (defined(__GNUC__) || defined(__clang__)) && \ (defined(_M_X64) || defined(_M_AMD64) || defined(__amd64__) || \ defined(__amd64) || \ defined(_M_IX86) || defined(__i386__) || defined(__i386)) asm( "pause;" ); #elif defined(SO_5_ARCH_MSC_WITH_SSE2) _mm_pause(); #else ; #endif } }; // // spinlock_t // /*! * \since * v.5.4.0 * * \brief A simple spinlock (analog of std::mutex). * * \note * Since v.5.5.22.2 a TATAS spinlock implementation is used. * Implementation is provided by Pavel Begunkov. */ template< class Backoff > class spinlock_t { public : spinlock_t() { m_flag.store( false, std::memory_order_release ); } spinlock_t( const spinlock_t & ) = delete; spinlock_t( spinlock_t && ) = delete; spinlock_t & operator=( const spinlock_t & ) = delete; spinlock_t & operator=( spinlock_t && ) = delete; //! Lock object. void lock() { Backoff backoff; do { while( m_flag.load( std::memory_order_relaxed ) ) backoff(); } while( m_flag.exchange( true, std::memory_order_acquire ) ); } //! Unlock object. void unlock() { m_flag.store( false, std::memory_order_release ); } private : //! Atomic flag which is used as actual lock. std::atomic_bool m_flag; }; // // default_spinlock_t // using default_spinlock_t = spinlock_t< pause_backoff_t >; // // rw_spinlock_t // /*! * \since * v.5.4.0 * * \brief A simple multi-readers/single-writer spinlock * (analog of std::shared_mutex). * * This implementation is based on Dmitry Vyukov implementation * from LLVM code base: * http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/tsan/rtl/tsan_mutex.cc?revision=210345&view=markup */ template< class Backoff > class rw_spinlock_t { private : std::atomic_uint_fast32_t m_counters; static constexpr const std::uint_fast32_t unlocked = 0; static constexpr const std::uint_fast32_t write_lock = 1; static constexpr const std::uint_fast32_t read_lock = 2; public : rw_spinlock_t() { m_counters.store( unlocked, std::memory_order_release ); } rw_spinlock_t( const rw_spinlock_t & ) = delete; rw_spinlock_t & operator=( const rw_spinlock_t & ) = delete; //! Lock object in shared mode. inline void lock_shared() { Backoff backoff; std::uint_fast32_t previous = m_counters.fetch_add( read_lock, std::memory_order_acquire ); while( previous & write_lock ) { backoff(); previous = m_counters.load( std::memory_order_acquire ); } } //! Unlock object locked in shared mode. inline void unlock_shared() { m_counters.fetch_sub( read_lock, std::memory_order_release ); } //! Lock object in exclusive mode. inline void lock() { std::uint_fast32_t expected = unlocked; const std::uint_fast32_t desired = write_lock; if( m_counters.compare_exchange_strong( expected, desired, std::memory_order_acquire, std::memory_order_relaxed ) ) return; Backoff backoff; while( true ) { if( unlocked == m_counters.load( std::memory_order_relaxed ) ) { expected = unlocked; if( m_counters.compare_exchange_weak( expected, desired, std::memory_order_acquire, std::memory_order_relaxed ) ) break; } backoff(); } } //! Unlock object locked in exclusive mode. inline void unlock() { m_counters.fetch_sub( write_lock, std::memory_order_release ); } }; using default_rw_spinlock_t = rw_spinlock_t< pause_backoff_t >; // // read_lock_guard_t // /*! * \since * v.5.4.0 * * \brief Scoped guard for shared locks. */ template< class Lock > class read_lock_guard_t { private : Lock & m_lock; public : read_lock_guard_t( Lock & l ) : m_lock( l ) { m_lock.lock_shared(); } ~read_lock_guard_t() { m_lock.unlock_shared(); } read_lock_guard_t( const read_lock_guard_t & ) = delete; read_lock_guard_t & operator=( const read_lock_guard_t & ) = delete; }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/state.hpp000066400000000000000000001321121523065210100173240ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief A class for the agent state definition. */ #if !defined( SO_5_STATE_HPP ) #define SO_5_STATE_HPP #include #include #include #include #include #include #include #include #include #include #include namespace so_5 { #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif // // initial_substate_of // /*! * \brief Helper for marking initial substate of composite state. * * Usage example: * \code class demo : public so_5::agent_t { state_t active{ *this, "active" }; state_t wait_input{ initial_substate_of{ active }, "wait_input" }; state_t dialog{ substate_of{ active }, "dialog" }; ... }; * \endcode * * \note A composite state can have only one initial substate. * * \since v.5.5.15 */ struct initial_substate_of { state_t * m_parent_state; initial_substate_of( state_t & parent_state ) : m_parent_state{ &parent_state } {} }; // // substate_of // /*! * \brief Helper for marking a substate of composite state. * * Usage example: * \code class demo : public so_5::agent_t { state_t active{ *this, "active" }; state_t wait_input{ initial_substate_of{ active }, "wait_input" }; state_t dialog{ substate_of{ active }, "dialog" }; state_t shutting_down{ substate_of{ active }, "shutdown" }; ... }; * \endcode * * \note A composite state can have any number of substates but * only one of them must be marked as initial substate. * * \since v.5.5.15 */ struct substate_of { state_t * m_parent_state; substate_of( state_t & parent_state ) : m_parent_state{ &parent_state } {} }; // // state_t // //! Class for the representing agent state. /*! * \attention This class is not thread safe. It is designed to be used inside * an owner agent only. For example: * \code class my_agent : public so_5::agent_t { state_t first_state{ this, "first" }; state_t second_state{ this, "second" }; ... public : my_agent( context_t ctx ) : so_5::agent_t{ ctx } { // It is a safe usage of state. first_state.on_enter( &my_agent::first_on_enter ); second_state.on_exit( &my_agent::second_on_exit ); ... } virtual void so_define_agent() override { // It is a safe usage of state. first_state.event( &my_agent::some_event_handler ); second_state.time_limit( std::chrono::seconds{20}, first_state ); second_state.event( [this]( const some_message & msg ) { // It is also safe usage of state because event handler // will be called on the context of agent's working thread. second_state.drop_time_limit(); ... } ); } void some_public_method() { // It is a safe usage if this method is called by the agent itself. // This will be unsafe usage if this method is called from outside of // the agent: a data damage or something like that can happen. second_state.time_limit( std::chrono::seconds{30}, first_state ); } ... }; * \endcode * Because of that be very careful during manipulation of agent's states outside * of agent's event handlers. */ class SO_5_TYPE state_t final { class time_limit_t; class state_path_t; friend class state_path_t; friend class agent_t; state_t( const state_t & ) = delete; state_t & operator =( const state_t & ) = delete; public: /*! * \brief Max deep of nested states. * * \since v.5.5.15 */ static constexpr const std::size_t max_deep = 16; /*! * \brief Type of history for state. * * \since v.5.5.15 */ enum class history_t { //! State has no history. none, //! State has shallow history. shallow, //! State has deep history. deep }; /*! * \brief A type for representation of state's path. * * \note Path contains pointer to the state itself (always the * last item in the path) and pointers to all superstates. * If state has no superstate the path will contains just one * pointer to the state itself. * * \since v.5.5.15 */ using path_t = std::array< const state_t *, max_deep >; /*! * \brief Type of function to be called on enter to the state. * * \attention Handler must be noexcept function. * * \since v.5.5.15 */ using on_enter_handler_t = std::function< void() >; /*! * \brief Type of function to be called on exit from the state. * * \attention Handler must be noexcept function. * * \since v.5.5.15 */ using on_exit_handler_t = std::function< void() >; /*! * \brief Alias for duration type. * * \since v.5.5.15 */ using duration_t = std::chrono::high_resolution_clock::duration; /*! * \note State name will be generated automaticaly. */ state_t( //! State owner. agent_t * agent ); /*! * \note State name will be generated automaticaly. * * \since v.5.5.15 * */ state_t( //! State owner. agent_t * agent, //! Type of state history. history_t state_history ); state_t( //! State owner. agent_t * agent, //! State name. std::string state_name ); /*! * \since v.5.5.15 */ state_t( //! State owner. agent_t * agent, //! State name. std::string state_name, //! Type of state history. history_t state_history ); /*! * \brief Constructor for the case when state is the initial * substate of some parent state. * * \since v.5.5.15 */ state_t( //! Parent state. initial_substate_of parent ); /*! * \brief Constructor for the case when state is the initial * substate of some parent state. * * \since v.5.5.15 */ state_t( //! Parent state. initial_substate_of parent, //! Type of state history. history_t state_history ); /*! * \brief Constructor for the case when state is the initial * substate of some parent state. * * \since v.5.5.15 */ state_t( //! Parent state. initial_substate_of parent, //! State name. std::string state_name ); /*! * \brief Constructor for the case when state is the initial * substate of some parent state. * * \since v.5.5.15 */ state_t( //! Parent state. initial_substate_of parent, //! State name. std::string state_name, //! Type of state history. history_t state_history ); /*! * \brief Constructor for the case when state is a substate of some * parent state. * * \since v.5.5.15 */ state_t( //! Parent state. substate_of parent ); /*! * \brief Constructor for the case when state is a substate of some * parent state. * * \since v.5.5.15 */ state_t( //! Parent state. substate_of parent, //! Type of state history. history_t state_history ); /*! * \brief Constructor for the case when state is a substate of some * parent state. * * \since v.5.5.15 */ state_t( //! Parent state. substate_of parent, //! State name. std::string state_name ); /*! * \brief Constructor for the case when state is a substate of some * parent state. * * \since v.5.5.15 * */ state_t( //! Parent state. substate_of parent, //! State name. std::string state_name, //! Type of state history. history_t state_history ); /*! * \brief Move constructor. * * \since v.5.4.0 */ state_t( state_t && other ); // Explicitely defined destructor is necessary because // time_limit_t is incomplete type at this point and // compiler can't generate the destructor for unique_ptr. ~state_t(); bool operator==( const state_t & state ) const noexcept; /*! * \since * v.5.5.20.1 */ bool operator!=( const state_t & state ) const noexcept { return !(*this == state); } //! Get textual name of the state. /*! * \note The return type is changed in v.5.5.15: now it is a std::string * object, not a const reference to some value inside state_t object. */ std::string query_name() const; //! Is agent owner of this state? bool is_target( const agent_t * agent ) const noexcept; /*! * \brief Switch agent to that state. * * \since v.5.5.1 */ void activate() const; /*! * \brief Is this state or any of its substates activated? * * \note See so_5::agent_t::so_is_active_state() for more details. * This method is just a thin wrapper around agent_t::so_is_active_state(). * * \since v.5.5.15 */ bool is_active() const noexcept; /*! * \brief Clear state history. * * \note Clears the history for this state only. History for any * substates remains intact. For example: * \code class demo : public so_5::agent_t { state_t A{ this, "A", deep_history }; state_t B{ initial_substate_of{ A }, "B", shallow_history }; state_t C{ initial_substate_of{ B }, "C" }; state_t D{ substate_of{ B }, "D" }; state_t E{ this, "E" }; ... void some_event() { this >>= A; // The current state is "A.B.C" // Because B is initial substate of A, and // C is initial substate of B. this >>= D; // The current state is "A.B.D". this >>= E; // The current state is "E". this >>= A; // The current state is "A.B.D" because deep history of A. this >>= E; A.clear_history(); this >>= A; // The current state is "A.B.D" because: // B is the initial substate of A and B has shallow history; // D is the last active substate of B. } }; * \endcode * * * \attention This method is not thread safe. Be careful calling * this method from outside of agent's working thread. * * \since v.5.5.15 */ void clear_history() noexcept { m_last_active_substate = nullptr; } /*! * \brief Helper for subscription of event handler in this state. * * \note This method must be used for messages which are * sent to agent's direct mbox. * * \par Usage example \code class my_agent : public so_5::agent_t { const so_5::state_t st_normal = so_make_state(); public : ... virtual void so_define_agent() override { st_normal.event( [=]( const msg_reconfig & evt ) { ... } ); st_normal.event( &my_agent::evt_shutdown ); ... } }; \endcode * * \since v.5.5.1 */ template< typename... Args > const state_t & event( Args&&... args ) const; /*! * \since v.5.5.15 */ template< typename... Args > state_t & event( Args&&... args ) { const state_t & t = *this; t.event( std::forward(args)... ); return *this; } /*! * \brief Helper for subscription of event handler in this state. * * \note This method must be used for messages which are * sent to \a from message-box. * * \par Usage example \code class my_agent : public so_5::agent_t { const so_5::state_t st_normal = so_make_state(); public : ... virtual void so_define_agent() override { st_normal.event( m_owner, [=]( const msg_reconfig & evt ) { ... } ); st_normal.event( m_owner, &my_agent::evt_shutdown ); ... } private : so_5::mbox_t m_owner; }; \endcode * * \since v.5.5.1 */ template< typename... Args > const state_t & event( mbox_t from, Args&&... args ) const; /*! * \since v.5.5.15 */ template< typename... Args > state_t & event( mbox_t from, Args&&... args ) { const state_t & t = *this; t.event( std::move(from), std::forward(args)... ); return *this; } /*! * \brief Check the presence of a subscription. * * \return true if subscription is present. * * Usage example: * \code * void my_agent::evt_create_new_subscription(mhood_t cmd) * { * // cmd can contain mbox we have already subscribed to. * // If we just call so_subscribe() then an exception can be thrown. * // Because of that check the presence of subscription first. * if(!some_state.has_subscription(cmd->mbox())) * { * // There is no subscription yet. New subscription can be * // created. * some_state.event(cmd->mbox(), ...); * } * } * \endcode * * \sa so_5::agent_t::so_has_subscription * * \since v.5.5.19.5 */ template< typename Msg > bool has_subscription( const mbox_t & from ) const; /*! * \brief Check the presence of a subscription. * * Type is deduced from the signature of event_handler. * * \return true if subscription is present. * * Usage example: * \code * void my_agent::evt_create_new_subscription(mhood_t cmd) * { * // cmd can contain mbox we have already subscribed to. * // If we just call so_subscribe() then an exception can be thrown. * // Because of that check the presence of subscription first. * if(!some_state.has_subscription(cmd->mbox(), &my_agent::some_event)) * { * // There is no subscription yet. New subscription can be * // created. * some_state.event(cmd->mbox(), ...); * } * } * \endcode * * \sa so_5::agent_t::so_has_subscription * * \since v.5.5.19.5 */ template< typename Method_Pointer > bool has_subscription( const mbox_t & from, Method_Pointer && pfn ) const; /*! * \brief Drop subscription. * * Drops the subscription for the message/signal of type \a Msg for * the state (if this subscription is present). Do nothing if subscription * is not exists. * * Usage example: * \code * class my_agent : public so_5::agent_t * { * state_t some_state{ this }; * ... * virtual void so_define_agent() override * { * some_state.event(some_mbox, &my_agent::some_handler); * ... * } * ... * void evt_some_another_handler() * { * // A subscription is no more needed. * some_state.drop_subscription(some_mbox); * } * }; * \endcode * * \note * This method should be called only from the working context of the agent. * * \since v.5.5.19.5 */ template< typename Msg > void drop_subscription( const mbox_t & from ) const; /*! * \brief Drop subscription. * * Drops the subscription for the message/signal for the state (if this * subscription is present). Do nothing if subscription is not exists. * * Type of a message is deduced from event handler signature. * * Usage example: * \code * class my_agent : public so_5::agent_t * { * state_t some_state{ this }; * ... * virtual void so_define_agent() override * { * some_state.event(some_mbox, &my_agent::some_handler); * ... * } * ... * void some_another_handler() * { * // A subscription is no more needed. * some_state.drop_subscription(some_mbox, &my_agent::some_handler); * } * void some_handler(mhood_t cmd) * { * ... * } * }; * \endcode * * \note * This method should be called only from the working context of the agent. * * \since v.5.5.19.5 */ template< typename Method_Pointer > void drop_subscription( const mbox_t & from, Method_Pointer && pfn ) const; /*! * \brief An instruction for switching agent to the specified * state and transfering event proceessing to new state. * * \note Transfers processing of message/signal which is going * from message box \a from. * * \par Usage example: * \code class device : public so_5::agent_t { const state_t off{ this, "off" }; const state_t on{ this, "on" }; public : virtual void so_define_agent() override { off .transfer_to_state< key_on >( some_mbox, on ) .transfer_to_state< key_info >( some_mbox, on ); ... } ... }; * \endcode * * \note For more details see subscription_bind_t::transfer_to_state(). * * \note Since v.5.5.22.1 actual execution of transfer_to_state operation * can raise so_5::exception_t with so_5::rc_transfer_to_state_loop * error code if a loop in transfer_to_state is detected. * * \since v.5.5.15 * */ template< typename Msg > const state_t & transfer_to_state( mbox_t from, const state_t & target_state ) const; /*! * \brief An instruction for switching agent to the specified * state and transfering event proceessing to new state. * * \note Transfers processing of message/signal which is going * from agent's direct mbox. * * \par Usage example: * \code class device : public so_5::agent_t { const state_t off{ this, "off" }; const state_t on{ this, "on" }; public : virtual void so_define_agent() override { off .transfer_to_state< key_on >( on ) .transfer_to_state< key_info >( on ); ... } ... }; * \endcode * * \note For more details see subscription_bind_t::transfer_to_state(). * * \note Since v.5.5.22.1 actual execution of transfer_to_state operation * can raise so_5::exception_t with so_5::rc_transfer_to_state_loop * error code if a loop in transfer_to_state is detected. * * \since v.5.5.15 */ template< typename Msg > const state_t & transfer_to_state( const state_t & target_state ) const; /*! * \brief An instruction for switching agent to the specified * state and transfering event proceessing to new state. * * \note Transfers processing of message/signal which is going * from message box \a from. * * \par Usage example: * \code class device : public so_5::agent_t { state_t off{ this, "off" }; state_t on{ this, "on" }; public : virtual void so_define_agent() override { off .transfer_to_state< key_on >( some_mbox, on ) .transfer_to_state< key_info >( some_mbox, on ); ... } ... }; * \endcode * * \note For more details see subscription_bind_t::transfer_to_state(). * * \note Since v.5.5.22.1 actual execution of transfer_to_state operation * can raise so_5::exception_t with so_5::rc_transfer_to_state_loop * error code if a loop in transfer_to_state is detected. * * \since v.5.5.15 */ template< typename Msg > state_t & transfer_to_state( mbox_t from, const state_t & target_state ) { const state_t & t = *this; t.transfer_to_state< Msg >( std::move(from), target_state ); return *this; } /*! * \brief An instruction for switching agent to the specified * state and transfering event proceessing to new state. * * \note Transfers processing of message/signal which is going * from agent's direct mbox. * * \par Usage example: * \code class device : public so_5::agent_t { state_t off{ this, "off" }; state_t on{ this, "on" }; public : virtual void so_define_agent() override { off .transfer_to_state< key_on >( on ) .transfer_to_state< key_info >( on ); ... } ... }; * \endcode * * \note For more details see subscription_bind_t::transfer_to_state(). * * \note Since v.5.5.22.1 actual execution of transfer_to_state operation * can raise so_5::exception_t with so_5::rc_transfer_to_state_loop * error code if a loop in transfer_to_state is detected. * * \since v.5.5.15 */ template< typename Msg > state_t & transfer_to_state( const state_t & target_state ) { const state_t & t = *this; t.transfer_to_state< Msg >( target_state ); return *this; } /*! * \brief Define handler which only switches agent to the specified * state. * * \note Defines a reaction to message/signal which is going from * message box \a from. * * \note This method differes from transfer_to_state() method: * just_switch_to() changes state of the agent, but there will not be a * look for event handler for message/signal in the new state. It means * that just_switch_to() is just a shorthand for: * \code virtual void demo::so_define_agent() override { some_state.event< some_signal >( from, [this]{ this >>= S2; } ); } * \endcode * With just_switch_to() this code can looks like: * \code virtual void demo::so_define_agent() override { some_state.just_switch_to< some_signal >( from, S2 ); } * \endcode * * \since v.5.5.15 */ template< typename Msg > const state_t & just_switch_to( mbox_t from, const state_t & target_state ) const; /*! * \brief Define handler which only switches agent to the specified * state. * * \note Defines a reaction to message/signal which is going from * agent's direct mbox. * * \note This method differes from transfer_to_state() method: * just_switch_to() changes state of the agent, but there will not be a * look for event handler for message/signal in the new state. It means * that just_switch_to() is just a shorthand for: * \code virtual void demo::so_define_agent() override { some_state.event< some_signal >( [this]{ this >>= S2; } ); } * \endcode * With just_switch_to() this code can looks like: * \code virtual void demo::so_define_agent() override { some_state.just_switch_to< some_signal >( S2 ); } * \endcode * * \since v.5.5.15 */ template< typename Msg > const state_t & just_switch_to( const state_t & target_state ) const; /*! * \brief Define handler which only switches agent to the specified * state. * * \note Defines a reaction to message/signal which is going from * message box \a from. * * \note This method differes from transfer_to_state() method: * just_switch_to() changes state of the agent, but there will not be a * look for event handler for message/signal in the new state. It means * that just_switch_to() is just a shorthand for: * \code virtual void demo::so_define_agent() override { some_state.event< some_signal >( from, [this]{ this >>= S2; } ); } * \endcode * With just_switch_to() this code can looks like: * \code virtual void demo::so_define_agent() override { some_state.just_switch_to< some_signal >( from, S2 ); } * \endcode * * \since v.5.5.15 */ template< typename Msg > state_t & just_switch_to( mbox_t from, const state_t & target_state ) { const state_t & t = *this; t.just_switch_to< Msg >( std::move(from), target_state ); return *this; } /*! * \brief Define handler which only switches agent to the specified * state. * * \note Defines a reaction to message/signal which is going from * agent's direct mbox. * * \note This method differes from transfer_to_state() method: * just_switch_to() changes state of the agent, but there will not be a * look for event handler for message/signal in the new state. It means * that just_switch_to() is just a shorthand for: * \code virtual void demo::so_define_agent() override { some_state.event< some_signal >( [this]{ this >>= S2; } ); } * \endcode * With just_switch_to() this code can looks like: * \code virtual void demo::so_define_agent() override { some_state.just_switch_to< some_signal >( S2 ); } * \endcode * * \since v.5.5.15 */ template< typename Msg > state_t & just_switch_to( const state_t & target_state ) { const state_t & t = *this; t.just_switch_to< Msg >( target_state ); return *this; } /*! * \brief Suppress processing of event in this state. * * \note Suppresses message/signal which is going from agent's * direct mbox. * * \note This method allows to disable passing of event to * event handlers from parent states. For example: * \code class demo : public so_5::agent_t { const state_t S1{ this, "1" }; const state_t S2{ initial_substate_of{ S1 }, "2" }; const state_t S3{ initial_substate_of{ S2 }, "3" }; public : virtual void so_define_agent() override { S1 // Default event handler which will be inherited by states S2 and S3. .event< msg1 >(...) .event< msg2 >(...) .event< msg3 >(...); S2 // A special handler for msg1. // For msg2 and msg3 event handlers from state S1 will be used. .event< msg1 >(...); S3 // Message msg1 will be suppressed. It will be simply ignored. // No events from states S1 and S2 will be called. .suppress< msg1 >() // The same for msg2. .suppress< msg2 >() // A special handler for msg3. Overrides handler from state S1. .event< msg3 >(...); } }; * \endcode * * \since v.5.5.15 */ template< typename Msg > const state_t & suppress() const; /*! * \brief Suppress processing of event in this state. * * \note Suppresses message/signal which is going from * message box \a from. * * \note This method allows to disable passing of event to * event handlers from parent states. For example: * \code class demo : public so_5::agent_t { const state_t S1{ this, "1" }; const state_t S2{ initial_substate_of{ S1 }, "2" }; const state_t S3{ initial_substate_of{ S2 }, "3" }; public : virtual void so_define_agent() override { S1 // Default event handler which will be inherited by states S2 and S3. .event< msg1 >( some_mbox, ...) .event< msg2 >( some_mbox, ...) .event< msg3 >( some_mbox, ...); S2 // A special handler for msg1. // For msg2 and msg3 event handlers from state S1 will be used. .event< msg1 >( some_mbox, ...); S3 // Message msg1 will be suppressed. It will be simply ignored. // No events from states S1 and S2 will be called. .suppress< msg1 >( some_mbox ) // The same for msg2. .suppress< msg2 >( some_mbox ) // A special handler for msg3. Overrides handler from state S1. .event< msg3 >( some_mbox ); } }; * \endcode * * \since v.5.5.15 */ template< typename Msg > const state_t & suppress( mbox_t from ) const; /*! * \brief Suppress processing of event in this state. * * \note Suppresses message/signal which is going from agent's * direct mbox. * * \note This method allows to disable passing of event to * event handlers from parent states. For example: * \code class demo : public so_5::agent_t { state_t S1{ this, "1" }; state_t S2{ initial_substate_of{ S1 }, "2" }; state_t S3{ initial_substate_of{ S2 }, "3" }; public : virtual void so_define_agent() override { S1 // Default event handler which will be inherited by states S2 and S3. .event< msg1 >(...) .event< msg2 >(...) .event< msg3 >(...); S2 // A special handler for msg1. // For msg2 and msg3 event handlers from state S1 will be used. .event< msg1 >(...); S3 // Message msg1 will be suppressed. It will be simply ignored. // No events from states S1 and S2 will be called. .suppress< msg1 >() // The same for msg2. .suppress< msg2 >() // A special handler for msg3. Overrides handler from state S1. .event< msg3 >(...); } }; * \endcode * * \since v.5.5.15 */ template< typename Msg > state_t & suppress() { const state_t & t = *this; t.suppress< Msg >(); return *this; } /*! * \brief Suppress processing of event in this state. * * \note Suppresses message/signal which is going from * message box \a from. * * \note This method allows to disable passing of event to * event handlers from parent states. For example: * \code class demo : public so_5::agent_t { state_t S1{ this, "1" }; state_t S2{ initial_substate_of{ S1 }, "2" }; state_t S3{ initial_substate_of{ S2 }, "3" }; public : virtual void so_define_agent() override { S1 // Default event handler which will be inherited by states S2 and S3. .event< msg1 >( some_mbox, ...) .event< msg2 >( some_mbox, ...) .event< msg3 >( some_mbox, ...); S2 // A special handler for msg1. // For msg2 and msg3 event handlers from state S1 will be used. .event< msg1 >( some_mbox, ...); S3 // Message msg1 will be suppressed. It will be simply ignored. // No events from states S1 and S2 will be called. .suppress< msg1 >( some_mbox ) // The same for msg2. .suppress< msg2 >( some_mbox ) // A special handler for msg3. Overrides handler from state S1. .event< msg3 >( some_mbox ); } }; * \endcode * * \since v.5.5.15 */ template< typename Msg > state_t & suppress( mbox_t from ) { const state_t & t = *this; t.suppress< Msg >( std::move(from) ); return *this; } /*! * \name Method for manupulation of enter/exit handlers. * \{ */ /*! * \brief Set on enter handler. * * \attention Handler must be noexcept function. If handler * throws an exception the whole application will be aborted. * * \par Usage example: * \code class demo : public so_5::agent_t { state_t number_accumulation{ this, "accumulation" }; ... public : virtual void so_define_agent() override { number_accumulation .on_enter( [this] { m_number.clear(); } ) .event( [this]( const next_digit & msg ) { m_number += msg.m_digit; } ); ... } ... private : std::string m_number; }; * \endcode * * \since v.5.5.15 */ state_t & on_enter( on_enter_handler_t handler ) { m_on_enter = std::move(handler); return *this; } /*! * \brief Set on enter handler. * * \attention Handler must be noexcept function. If handler * throws an exception the whole application will be aborted. * * \par Usage example: * \code class demo : public so_5::agent_t { state_t number_accumulation{ this, "accumulation" }; ... public : virtual void so_define_agent() override { number_accumulation .on_enter( &demo::accumulation_on_enter ) .event( [this]( const next_digit & msg ) { m_number += msg.m_digit; } ); ... } ... private : std::string m_number; ... void accumulation_on_enter() { m_number.clear(); ... } }; * \endcode * * \since v.5.5.15 * */ template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::nullary, Method_Pointer>::value, state_t & >::type on_enter( Method_Pointer pfn ); /*! * \brief Query on enter handler. * * \note This method can be useful if there is a need to refine * handler defined in the base class: * \code class parent : public so_5::agent_t { protected : state_t some_state{ this, ... }; public : virtual void so_define_agent() override { some_state.on_enter( [this] { ... // Some action. } ); } ... }; class child : public parent { public : virtual void so_define_agent() override { // Calling so_define_agent from base class. parent::so_define_agent(); // Refine on_enter handler for state from parent class. auto old_handler = some_state.on_enter(); some_state.on_enter( [this, old_handler] { old_handler(); // Calling the old handler. ... // Some addition action. } ); } ... }; * \endcode * * \since v.5.5.15 */ const on_enter_handler_t & on_enter() const { return m_on_enter; } /*! * \brief Set on exit handler. * * \attention Handler must be noexcept function. If handler * throws an exception the whole application will be aborted. * * \par Usage example: * \code class demo : public so_5::agent_t { state_t dialog{ this, "dialog" }; ... public : virtual void so_define_agent() override { dialog .on_exit( [this] { m_display.turn_off(); } ) .event( [this]( const user_input & msg ) {...} ); ... } ... private : display m_display; }; * \endcode * * \since v.5.5.15 */ state_t & on_exit( on_exit_handler_t handler ) { m_on_exit = std::move(handler); return *this; } /*! * \brief Set on exit handler. * * \attention Handler must be noexcept function. If handler * throws an exception the whole application will be aborted. * * \par Usage example: * \code class demo : public so_5::agent_t { state_t dialog{ this, "dialog" }; ... public : virtual void so_define_agent() override { dialog .on_exit( &demo::dialog_on_exit ) .event( [this]( const user_input & msg ) {...} ); ... } ... private : display m_display; ... void dialog_on_exit() { m_display.turn_off(); ... } }; * \endcode * * \since v.5.5.15 */ template< typename Method_Pointer > typename std::enable_if< details::is_agent_method_pointer< details::method_arity::nullary, Method_Pointer>::value, state_t & >::type on_exit( Method_Pointer pfn ); /*! * \brief Query on enter handler. * * \note This method can be useful if there is a need to refine * handler defined in the base class: * \code class parent : public so_5::agent_t { protected : state_t some_state{ this, ... }; public : virtual void so_define_agent() override { some_state.on_exit( [this] { ... // Some action. } ); } ... }; class child : public parent { public : virtual void so_define_agent() override { // Calling so_define_agent from base class. parent::so_define_agent(); // Refine on_enter handler for state from parent class. auto old_handler = some_state.on_exit(); some_state.on_exit( [this, old_handler] { ... // Some addition action. old_handler(); // Calling the old handler. } ); } ... }; * \endcode * * \since v.5.5.15 */ const on_exit_handler_t & on_exit() const { return m_on_exit; } /*! * \} */ /*! * \name Methods for dealing with state's time limit. * \{ */ /*! * \brief Set up a time limit for the state. * * \note Agent will automatically switched to \a state_to_switch after * \a timeout spent in the current state. * * \par Usage example: * \code class demo : public so_5::agent_t { state_t dialog{ this, "dialog" }; state_t show_err{ this, "err" }; ... public : virtual void so_define_agent() override { dialog .on_enter( [this] { m_display.clear(); } ) .event( [this]( const user_input & msg ) { try_handle_input( msg ); if( some_error() ) { m_error = error_description(); this >>= show_err; } } ) ...; // Other handlers. show_err .on_enter( [this] { m_display.show( m_error ); } ) .time_limit( std::chrono::seconds{2}, dialog ); } ... private : display m_display; std::string m_error; ... }; * \endcode * * \note If S.time_limit() is called when S is active state then * time_limit for that state will be reset and time for the state S * will be counted from zero. * * \attention * This method provides only basic exception guarantee. * For example: * \code * void some_agent::so_define_agent() * { * some_state * .time_limit( std::chrono::seconds{2}, another_state ) * .event( [this]( const some_message & msg ) { * // An attempt to change time_limit for * // some_state. NOTE: this state is already * // activated and SObjectizer controls time * // spent in this state. * some_state( std::chrono::seconds{5}, another_state ); // (1) * ... * } ) * ; * } * \endcode * If an exception thrown at (1) then the previous 2s timeout will be * cancelled, but new timeout won't be started due to exception. It means * that the agent will stay in `some_state`, but time limit for this * state won't be controlled anymore. * * \since v.5.5.15 */ state_t & time_limit( //! Max duration of time for staying in this state. duration_t timeout, //! A new state to be switched to. const state_t & state_to_switch ); /*! * \brief Drop time limit for the state if defined. * * \note Do nothing if a time limit is not defined. * * \note This method can be useful if there is need to refine state * defined in the parent class: * \code class parent : public so_5::agent_t { protected : state_t dialog{ this, "dialog" }; state_t show_err{ this, "err" }; ... public : virtual void so_define_agent() override { dialog .on_enter( [this] { m_display.clear(); } ) .event( [this]( const user_input & msg ) { try_handle_input( msg ); if( some_error() ) { m_error = error_description(); this >>= show_err; } } ) ...; // Other handlers. show_err .on_enter( [this] { m_display.show( m_error ); } ) .time_limit( std::chrono::seconds{2}, dialog ); } ... protected : display m_display; std::string m_error; ... }; class child : public parent { public : virtual void so_define_agent() override { parent::so_define_agent(); show_err // Switching from show_err to dialog must be done // only by pressing "cancel" or "ok" buttons. // Automatic switching after 2s must be disabled. .drop_time_limit() .just_switch_to< key_cancel >( dialog ) .just_switch_to< key_on >( dialog ); } ... }; * \endcode * * \note This method can be called when agents is already in that * state. Automatic switching will be disabled. To reenable automatic * switching time_limit() method must be called again: * \code class demo : public so_5::agent_t { state_t dialog{ this, "dialog" }; state_t show_err{ this, "err" }; ... public : virtual void so_define_agent() override { dialog .on_enter( [this] { m_display.clear(); } ) .event( [this]( const user_input & msg ) { try_handle_input( msg ); if( some_error() ) { m_error = error_description(); this >>= show_err; } } ) ...; // Other handlers. show_err .on_enter( [this] { m_display.show( m_error ); } ) // By default state will be switched after 2s. .time_limit( std::chrono::seconds{2}, dialog ) .event< key_pause >( [this] { // Disable automatic switching. show_err.drop_time_limit(); } ) .event< key_continue >( [this] { // Automatic switching is reenabled. show_err.time_limit( std::chrono::seconds{2}, dialog ); ... } ); } ... private : display m_display; std::string m_error; ... }; * \endcode * * \since v.5.5.15 */ state_t & drop_time_limit(); /*! * \} */ private: //! Fully initializing constructor. /*! * \since v.5.5.15 */ state_t( //! Owner of this state. agent_t * target_agent, //! Name for this state. std::string state_name, //! Parent state. nullptr means that there is no parent state. state_t * parent_state, //! Nesting deep for this state. Value 0 means this state is //! a top-level state. std::size_t nested_level, //! Type of state history. history_t state_history ); //! Owner of this state. agent_t * const m_target_agent; //! State name. /*! * \note Since v.5.5.15 has empty value for anonymous state. */ std::string m_state_name; /*! * \brief Parent state. * * \note Value nullptr means that state is a top-level state and * has no parent state. * * \note This pointer is not const because some modification of * parent state must be performed via that pointer. * * \since v.5.5.15 */ state_t * m_parent_state; /*! * \brief The initial substate. * * \note Value nullptr means that state has no initial substate. * If m_substate_count == 0 it is normal. It means that state is * not a composite state. But if m_substate_count != 0 the value * nullptr means that state description is incorrect. * * \since v.5.5.15 */ const state_t * m_initial_substate; /*! * \brief Type of state history. * * \since v.5.5.15 */ history_t m_state_history; /*! * \brief Last active substate. * * \note This attribute is used only if * m_state_history != history_t::none. It holds a pointer to last * active substate of this composite state. * * \since v.5.5.15 */ const state_t * m_last_active_substate; /*! * \brief Nesting level for state. * * \note Value 0 means that state is a top-level state. * * \since v.5.5.15 */ std::size_t m_nested_level; /*! * \brief Number of substates. * * \note Value 0 means that state is not composite state and has no * any substates. * * \since v.5.5.15 */ size_t m_substate_count; /*! * \brief Handler for the enter to the state. * * \since v.5.5.15 */ on_enter_handler_t m_on_enter; /*! * \brief Handler for the exit from the state. * * \since v.5.5.15 */ on_exit_handler_t m_on_exit; /*! * \brief A definition of time limit for the state. * * \note Value nullptr means that time limit is not set. * * \since v.5.5.15 */ std::unique_ptr< time_limit_t > m_time_limit; /*! * \brief A helper for handle-methods implementation. * * \since v.5.5.1 */ template< typename... Args > const state_t & subscribe_message_handler( const mbox_t & from, Args&&... args ) const; /*! * \name Methods to be used by agents. * \{ */ /*! * \brief Get a parent state if exists. * * \since v.5.5.15 */ const state_t * parent_state() const noexcept { return m_parent_state; } /*! * \brief Find actual state to be activated for agent. * * \note If (*this) is a composite state then actual state to * enter will be its m_initial_substate (if m_initial_substate is * a composite state then actual state to enter will be its * m_initial_substate and so on). * * \note If this state is a composite state with history then * m_last_active_substate value will be used (if it is not nullptr). * * \throw exception_t if (*this) is a composite state but m_initial_substate * is not defined. * * \since v.5.5.15 */ const state_t * actual_state_to_enter() const; /*! * \brief Query nested level for the state. * * \since v.5.5.15 */ std::size_t nested_level() const noexcept { return m_nested_level; } /*! * \brief A helper method for building a path from top-level * state to this state. * * \since v.5.5.15 */ void fill_path( path_t & path ) const noexcept { path[ m_nested_level ] = this; if( m_parent_state ) m_parent_state->fill_path( path ); } /*! * \brief A helper method which is used during state change for * update state with history. * * \since v.5.5.15 */ void update_history_in_parent_states() const; /*! * \brief A special handler of time limit to be used on entering into state. * \attention This method must be called only if m_time_limit is not null. * * \since v.5.5.15 */ void handle_time_limit_on_enter() const; /*! * \brief A special handler of time limit to be used on exiting from state. * \attention This method must be called only if m_time_limit is not null. * * \since v.5.5.15 */ void handle_time_limit_on_exit() const; /*! * \brief Call for on enter handler if defined. * * \since v.5.5.15 */ void call_on_enter() const noexcept { if( m_on_enter ) m_on_enter(); if( m_time_limit ) handle_time_limit_on_enter(); } /*! * \brief Call for on exit handler if defined. * * \since v.5.5.15 */ void call_on_exit() const noexcept { if( m_time_limit ) handle_time_limit_on_exit(); if( m_on_exit ) m_on_exit(); } /*! * \} */ }; /*! * \brief Helper class for simplify iteration on state's path. * * Usage example: * \code * state_t::state_path_t path{ *m_current_state_ptr }; * for( const state_t * st : path ) * { * ... // use of `st` * } * \endcode * * \attention * This class is not Copyable, nor Moveable. * * \since v.5.8.5 */ class state_t::state_path_t { /// Path for the state. state_t::path_t m_path; /// Past-the-end iterator for the m_path. /// /// Will be calculated in the constructor and won't be /// changed after that. state_t::path_t::const_iterator m_past_the_end_it; public: /// Initializing constructor. explicit state_path_t( const state_t & state ) noexcept { state.fill_path( m_path ); m_past_the_end_it = static_cast(m_path).begin(); std::advance( m_past_the_end_it, state.nested_level() + 1u ); } state_path_t( const state_path_t & ) = delete; state_path_t & operator=( const state_path_t & ) = delete; state_path_t( state_path_t && ) = delete; state_path_t & operator=( state_path_t && ) = delete; /// Get iterator for the very first item of the path. [[nodiscard]] state_t::path_t::const_iterator begin() const noexcept { return m_path.begin(); } /// Get the past-the-end iterator for the path. [[nodiscard]] state_t::path_t::const_iterator end() const noexcept { return m_past_the_end_it; } }; #if defined( SO_5_MSVC ) #pragma warning(pop) #endif } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/000077500000000000000000000000001523065210100166315ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/stats/controller.hpp000066400000000000000000000030471523065210100215310ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.4 * * \brief A public interface for control SObjectizer monitoring options. */ #if !defined( SO_5_STATS_CONTROLLER_HPP ) #define SO_5_STATS_CONTROLLER_HPP #include #include #include namespace so_5 { namespace stats { /*! * \since * v.5.5.4 * * \brief A public interface for control SObjectizer monitoring options. */ class SO_5_TYPE controller_t { protected : // Note: clang-3.9 requires this on Windows platform. controller_t( const controller_t & ) = delete; controller_t( controller_t && ) = delete; controller_t & operator=( const controller_t & ) = delete; controller_t & operator=( controller_t && ) = delete; controller_t() = default; ~controller_t() = default; public : //! Get the mbox for receiving monitoring information. virtual const mbox_t & mbox() const = 0; //! Turn the monitoring on. virtual void turn_on() = 0; //! Turn the monitoring off. virtual void turn_off() = 0; //! Set distribution period. /*! * \return Old distribution period value. */ virtual std::chrono::steady_clock::duration set_distribution_period( //! New period value. std::chrono::steady_clock::duration period ) = 0; protected : /*! * \brief Default distribution period. * * \since * v.5.5.19 */ inline static std::chrono::steady_clock::duration default_distribution_period() { return std::chrono::seconds{ 2 }; } }; } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/impl/000077500000000000000000000000001523065210100175725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/so_5/stats/impl/activity_tracking.hpp000066400000000000000000000144431523065210100240270ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Details for implementation of activity tracking. * * \since v.5.5.18 */ #if !defined( SO_5_STATS_IMPL_ACTIVITY_TRACKING_HPP ) #define SO_5_STATS_IMPL_ACTIVITY_TRACKING_HPP #include #include #include #include #include #include namespace so_5 { namespace stats { namespace activity_tracking_stuff { /*! * \brief Various traits of activity tracking implementation. * * \since v.5.5.18 */ struct traits { //! Type of lock object to be used for synchronization of //! activity tracking data. using lock_t = default_spinlock_t; }; /*! * \brief An analog of std::lock_guard but without actual locking actions. * * This class is inteded to be used with different lock policies for * stats_collector_t. * * \since v.5.5.18 */ template< typename L > struct no_actual_lock { no_actual_lock( L & ) { /* Do nothing. */ } }; /*! * \brief Default locking policy for stats_collector_t. * * Performs actual locking on start/stop and take_activity operations. * * \since v.5.5.18 */ template< typename Lock_Holder > struct default_lock_policy { using start_stop_lock_t = std::lock_guard< Lock_Holder >; using take_stats_lock_t = std::lock_guard< Lock_Holder >; }; /*! * \brief A custom locking policy for stats_collector_t. * * Performs actual locking only on take_activity operation. * * \since v.5.5.18 */ template< typename Lock_Holder > struct no_lock_at_start_stop_policy { using start_stop_lock_t = no_actual_lock< Lock_Holder >; using take_stats_lock_t = std::lock_guard< Lock_Holder >; }; /*! * \brief Base for the case of internal stats lock. * * \since v.5.5.18 */ class internal_lock { traits::lock_t m_lock; public : using start_stop_lock_t = std::lock_guard< internal_lock >; using take_stats_lock_t = std::lock_guard< internal_lock >; internal_lock() {} void lock() { m_lock.lock(); } void unlock() { m_lock.unlock(); } }; /*! * \brief Base for the case of externals stats lock. * * \since v.5.5.18 */ template< typename Lock_Type = traits::lock_t, template class Lock_Policy = default_lock_policy > class external_lock { Lock_Type & m_lock; public : using start_stop_lock_t = typename Lock_Policy< external_lock >::start_stop_lock_t; using take_stats_lock_t = typename Lock_Policy< external_lock >::take_stats_lock_t; external_lock( Lock_Type & lock ) : m_lock( lock ) {} void lock() { m_lock.lock(); } void unlock() { m_lock.unlock(); } }; /*! * \brief A special class for cases where lock is not needed at all. * * Usage example: * \code class real_activity_tracker_t final { so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::null_lock > m_waiting{}; so_5::stats::activity_tracking_stuff::stats_collector_t< so_5::stats::activity_tracking_stuff::null_lock > m_working{}; ... }; * \endcode * * \since v.5.5.19 */ struct null_lock { public : using start_stop_lock_t = no_actual_lock< null_lock >; using take_stats_lock_t = no_actual_lock< null_lock >; null_lock() {} }; /*! * \brief Helper for collecting activity stats. * * \since v.5.5.18 */ template< typename Lock_Holder > class stats_collector_t : protected Lock_Holder { Lock_Holder & lock_holder() { return *this; } public : template< typename... Args > stats_collector_t( Args && ...args ) : Lock_Holder( std::forward(args)... ) {} void start() { typename Lock_Holder::start_stop_lock_t lock{ lock_holder() }; do_start(); } /*! * \brief A helper method for safe start if start method hasn't been * called yet. * * \since v.5.5.19 */ void start_if_not_started() { typename Lock_Holder::start_stop_lock_t lock{ lock_holder() }; if( !m_is_in_working ) do_start(); } void stop() { typename Lock_Holder::start_stop_lock_t lock{ lock_holder() }; m_is_in_working = false; so_5::stats::details::update_stats_from_current_time( m_work_activity, m_work_started_at ); } so_5::stats::activity_stats_t take_stats() { so_5::stats::activity_stats_t result; bool is_in_working{ false }; so_5::stats::clock_type_t::time_point work_started_at; { typename Lock_Holder::take_stats_lock_t lock{ lock_holder() }; result = m_work_activity; if( true == (is_in_working = m_is_in_working) ) work_started_at = m_work_started_at; } if( is_in_working ) so_5::stats::details::update_stats_from_current_time( result, work_started_at ); return result; } private : //! A flag for indicating work activity. bool m_is_in_working{ false }; //! A time point when current activity started. so_5::stats::clock_type_t::time_point m_work_started_at; //! A statistics for work activity. so_5::stats::activity_stats_t m_work_activity{}; void do_start() { m_is_in_working = true; m_work_started_at = so_5::stats::clock_type_t::now(); m_work_activity.m_count += 1; } }; /*! * \brief Helper function for creation of dispatcher with respect * to activity tracking flag in dispatcher params and in Environment's * params. * * \since v.5.5.18 */ template< typename Common_Disp_Iface_Type, typename Disp_No_Tracking, typename Disp_With_Tracking, typename Env, typename Disp_Params, typename... Args > std::unique_ptr< Common_Disp_Iface_Type > create_appropriate_disp( outliving_reference_t< Env > env, const std::string_view name_base, Disp_Params disp_params, Args && ...args ) { std::unique_ptr< Common_Disp_Iface_Type > disp; auto tracking = disp_params.work_thread_activity_tracking(); if( work_thread_activity_tracking_t::unspecified == tracking ) tracking = env.get().work_thread_activity_tracking(); if( work_thread_activity_tracking_t::on == tracking ) disp = std::make_unique< Disp_With_Tracking >( env, name_base, std::move(disp_params), std::forward(args)... ); else disp = std::make_unique< Disp_No_Tracking >( env, name_base, std::move(disp_params), std::forward(args)... ); return disp; } } /* namespace activity_tracking_stuff */ } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/impl/ds_agent_core_stats.cpp000066400000000000000000000022571523065210100243160ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief A data source class for run-time monitoring of agent_core. */ #include #include #include #include namespace so_5 { namespace stats { namespace impl { // // ds_agent_core_stats_t // ds_agent_core_stats_t::ds_agent_core_stats_t( so_5::environment_infrastructure_t & what ) : m_what( what ) {} void ds_agent_core_stats_t::distribute( const mbox_t & distribution_mbox ) { auto stats = m_what.query_coop_repository_stats(); send< messages::quantity< std::size_t > >( distribution_mbox, prefixes::coop_repository(), suffixes::coop_count(), stats.m_total_coop_count ); send< messages::quantity< std::size_t > >( distribution_mbox, prefixes::coop_repository(), suffixes::agent_count(), stats.m_total_agent_count ); send< messages::quantity< std::size_t > >( distribution_mbox, prefixes::coop_repository(), suffixes::coop_final_dereg_count(), stats.m_final_dereg_coop_count ); } } /* namespace impl */ } /* namespace stats */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/stats/impl/ds_agent_core_stats.hpp000066400000000000000000000017271523065210100243240ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief A data source class for run-time monitoring of agent_core. */ #if !defined( SO_5_STATS_IMPL_DS_AGENT_CORE_STATS_HPP ) #define SO_5_STATS_IMPL_DS_AGENT_CORE_STATS_HPP #include #include namespace so_5 { namespace stats { namespace impl { // // ds_agent_core_stats_t // /*! * \since * v.5.5.4 * * \brief A data source for distributing information about agent_core. */ class ds_agent_core_stats_t final : public source_t { public : ds_agent_core_stats_t( //! What to watch. //! This reference must stay valid during all lifetime of //! the data source object. so_5::environment_infrastructure_t & what ); void distribute( const mbox_t & distribution_mbox ) override; private : so_5::environment_infrastructure_t & m_what; }; } /* namespace impl */ } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/impl/ds_mbox_core_stats.cpp000066400000000000000000000015041523065210100241570ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief A data source class for run-time monitoring of mbox_core. */ #include #include #include #include namespace so_5 { namespace stats { namespace impl { // // ds_mbox_core_stats_t // ds_mbox_core_stats_t::ds_mbox_core_stats_t( so_5::impl::mbox_core_t & what ) : m_what( what ) {} void ds_mbox_core_stats_t::distribute( const mbox_t & distribution_mbox ) { auto stats = m_what.query_stats(); send< messages::quantity< std::size_t > >( distribution_mbox, prefixes::mbox_repository(), suffixes::named_mbox_count(), stats.m_named_mbox_count ); } } /* namespace impl */ } /* namespace stats */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/stats/impl/ds_mbox_core_stats.hpp000066400000000000000000000016501523065210100241660ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief A data source class for run-time monitoring of mbox_core. */ #if !defined( SO_5_STATS_IMPL_DS_MBOX_CORE_STATS_HPP ) #define SO_5_STATS_IMPL_DS_MBOX_CORE_STATS_HPP #include #include namespace so_5 { namespace stats { namespace impl { // // ds_mbox_core_stats_t // /*! * \since * v.5.5.4 * * \brief A data source for distributing information about mbox_core. */ class ds_mbox_core_stats_t : public source_t { public : ds_mbox_core_stats_t( //! What to watch. //! This reference must stay valid during all lifetime of //! the data source object. so_5::impl::mbox_core_t & what ); void distribute( const mbox_t & distribution_mbox ) override; private : so_5::impl::mbox_core_t & m_what; }; } /* namespace impl */ } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/impl/ds_timer_thread_stats.cpp000066400000000000000000000020341523065210100246500ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief A data source class for run-time monitoring of timer_thread. */ #include #include #include #include namespace so_5 { namespace stats { namespace impl { // // ds_timer_thread_stats_t // ds_timer_thread_stats_t::ds_timer_thread_stats_t( so_5::environment_infrastructure_t & what ) : m_what( what ) {} void ds_timer_thread_stats_t::distribute( const mbox_t & distribution_mbox ) { const auto stats = m_what.query_timer_thread_stats(); send< messages::quantity< std::size_t > >( distribution_mbox, prefixes::timer_thread(), suffixes::timer_single_shot_count(), stats.m_single_shot_count ); send< messages::quantity< std::size_t > >( distribution_mbox, prefixes::timer_thread(), suffixes::timer_periodic_count(), stats.m_periodic_count ); } } /* namespace impl */ } /* namespace stats */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/stats/impl/ds_timer_thread_stats.hpp000066400000000000000000000017371523065210100246660ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief A data source class for run-time monitoring of timer_thread. */ #if !defined( SO_5_STATS_IMPL_DS_TIMER_THREAD_STATS_HPP ) #define SO_5_STATS_IMPL_DS_TIMER_THREAD_STATS_HPP #include #include namespace so_5 { namespace stats { namespace impl { // // ds_timer_thread_stats_t // /*! * \since * v.5.5.4 * * \brief A data source for distributing information about timer_thread. */ class ds_timer_thread_stats_t : public source_t { public : ds_timer_thread_stats_t( //! What to watch. //! This reference must stay valid during all lifetime of //! the data source object. so_5::environment_infrastructure_t & what ); void distribute( const mbox_t & distribution_mbox ) override; private : so_5::environment_infrastructure_t & m_what; }; } /* namespace impl */ } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/impl/st_env_stuff.hpp000066400000000000000000000105711523065210100230140ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Various stuff dedicated for single-threaded environments. * * \since * v.5.5.19 */ #if !defined( SO_5_STATS_IMPL_ST_ENV_STUFF_HPP ) #define SO_5_STATS_IMPL_ST_ENV_STUFF_HPP #include #include #include namespace so_5 { namespace stats { namespace impl { namespace st_env_stuff { // // next_turn_handler_t // /*! * \brief An interface for initiation of next turn in stats distribution. * * \since * v.5.5.19 */ class next_turn_handler_t { public : next_turn_handler_t() {} virtual ~next_turn_handler_t() {} virtual void on_next_turn( //! ID of stats distribution. int run_id ) = 0; struct next_turn : public message_t { //! Who must do next turn. outliving_reference_t< next_turn_handler_t > m_handler; //! ID of stats distribution. int m_run_id; next_turn( outliving_reference_t< next_turn_handler_t > handler, int run_id ) : m_handler( std::move(handler) ) , m_run_id( run_id ) {} }; }; // // next_turn_mbox_t // /*! * \since A special implementation of abstract_message_box for handling * stats distribution in single-threaded environments. * * A call to next_turn_handler_t::on_next_turn is performed directly * in do_deliver_message() method. This is done in assumption that * do_deliver_message() is called on the context on the main environment's * thread. * * \since * v.5.5.19 */ class next_turn_mbox_t final : public abstract_message_box_t { //! Environment for which that mbox is created. /*! * \note * This attribute is necessary for correct implementation of * inherited environment() method. * * \since * v.5.6.0 */ environment_t & m_env; next_turn_mbox_t( environment_t & env ) : m_env{ env } {} public: // NOTE: this method should never be used. mbox_id_t id() const override { return 0; } void subscribe_event_handler( const std::type_index & /*type_index*/, abstract_message_sink_t & /*subscriber*/ ) override { SO_5_THROW_EXCEPTION( rc_not_implemented, "call to subscribe_event_handler() is illegal for " "next_turn_mbox_t" ); } void unsubscribe_event_handler( const std::type_index & /*type_index*/, abstract_message_sink_t & /*subscriber*/ ) noexcept override { // Do nothing because it's a noexcept method since v.5.8.0. } std::string query_name() const override { return ""; } mbox_type_t type() const override { return mbox_type_t::multi_producer_single_consumer; } void do_deliver_message( message_delivery_mode_t /*delivery_mode*/, const std::type_index & msg_type, const message_ref_t & message, unsigned int /*redirection_deep*/ ) override { static const auto & next_turn_msg_type = typeid(next_turn_handler_t::next_turn); if( msg_type != next_turn_msg_type ) SO_5_THROW_EXCEPTION( rc_unexpected_error, "only next_turn_handler_t::next_turn expected in " "next_turn_mbox_t::do_deliver_message" ); const auto & actual_message = dynamic_cast< const next_turn_handler_t::next_turn & >( *message.get() ); actual_message.m_handler.get().on_next_turn( actual_message.m_run_id ); } void set_delivery_filter( const std::type_index & /*msg_type*/, const delivery_filter_t & /*filter*/, abstract_message_sink_t & /*subscriber*/ ) override { SO_5_THROW_EXCEPTION( rc_not_implemented, "call to set_delivery_filter() is illegal for " "next_turn_mbox_t" ); } void drop_delivery_filter( const std::type_index & /*msg_type*/, abstract_message_sink_t & /*subscriber*/ ) noexcept override { SO_5_THROW_EXCEPTION( rc_not_implemented, "call to drop_delivery_filter() is illegal for " "next_turn_mbox_t" ); } /*! * \note * It seems that this method should never be called. * But it is safer to provide an actual implementation for it * than relying on wrong assumption. */ environment_t & environment() const noexcept override { return m_env; } //! Helper for simplify creation of that mboxes of that type. static mbox_t make( environment_t & env ) { return { new next_turn_mbox_t{env} }; } }; } /* namespace st_env_stuff */ } /* namespace impl */ } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/impl/std_controller.cpp000066400000000000000000000054511523065210100233400ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.4 * * \brief A standard implementation of controller for run-time monitoring. */ #include #include #include namespace so_5 { namespace stats { namespace impl { // // std_controller_t // std_controller_t::std_controller_t( mbox_t mbox ) : m_mbox( std::move( mbox ) ) {} const mbox_t & std_controller_t::mbox() const { return m_mbox; } void std_controller_t::turn_on() { std::lock_guard< std::mutex > lock{ m_start_stop_lock }; if( !m_distribution_thread ) { // Distribution thread must be started. m_shutdown_initiated = false; m_distribution_thread.reset( new std::thread( [this] { body(); } ) ); } } void std_controller_t::turn_off() { std::lock_guard< std::mutex > lock{ m_start_stop_lock }; if( m_distribution_thread ) { { // Send shutdown signal to work thread. std::lock_guard< std::mutex > cond_lock{ m_data_lock }; m_shutdown_initiated = true; m_wake_up_cond.notify_one(); } // Wait for work thread termination. m_distribution_thread->join(); // Pointer to work thread must be dropped. // This allows to start new working thread. m_distribution_thread.reset(); } } std::chrono::steady_clock::duration std_controller_t::set_distribution_period( std::chrono::steady_clock::duration period ) { std::lock_guard< std::mutex > lock{ m_data_lock }; auto ret_value = m_distribution_period; m_distribution_period = period; return ret_value; } void std_controller_t::add( source_t & what ) { std::lock_guard< std::mutex > lock{ m_data_lock }; source_list_add( what, m_head, m_tail ); } void std_controller_t::remove( source_t & what ) noexcept { std::lock_guard< std::mutex > lock{ m_data_lock }; source_list_remove( what, m_head, m_tail ); } void std_controller_t::body() { while( true ) { std::unique_lock< std::mutex > lock{ m_data_lock }; if( m_shutdown_initiated ) return; const auto actual_duration = distribute_current_data(); if( actual_duration < m_distribution_period ) // There is some time to sleep. m_wake_up_cond.wait_for( lock, m_distribution_period - actual_duration ); } } std::chrono::steady_clock::duration std_controller_t::distribute_current_data() { auto started_at = std::chrono::steady_clock::now(); send< so_5::stats::messages::distribution_started >( m_mbox ); source_t * s = m_head; while( s ) { s->distribute( m_mbox ); s = source_list_next( *s ); } send< so_5::stats::messages::distribution_finished >( m_mbox ); return std::chrono::steady_clock::now() - started_at; } } /* namespace impl */ } /* namespace stats */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/stats/impl/std_controller.hpp000066400000000000000000000052611523065210100233440ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.4 * * \brief A standard implementation of controller for run-time monitoring. */ #if !defined( SO_5_STATS_IMPL_STD_CONTROLLER_HPP ) #define SO_5_STATS_IMPL_STD_CONTROLLER_HPP #include #include #include #include #include #include #include namespace so_5 { namespace stats { namespace impl { #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnon-virtual-dtor" #endif /*! * \since * v.5.5.4 * * \brief A standard implementation of controller for run-time monitoring. */ class std_controller_t final : public controller_t , public repository_t { public : std_controller_t( mbox_t mbox ); // Implementation of controller_t interface. virtual const mbox_t & mbox() const override; virtual void turn_on() override; virtual void turn_off() override; virtual std::chrono::steady_clock::duration set_distribution_period( std::chrono::steady_clock::duration period ) override; // Implementation of repository_t interface. virtual void add( source_t & what ) override; virtual void remove( source_t & what ) noexcept override; private : //! Mbox for sending monitoring data. const mbox_t m_mbox; //! Object lock for start/stop operations. std::mutex m_start_stop_lock; //! Object lock for data-related operations. std::mutex m_data_lock; //! Condition for wake-up data-distribution thread. std::condition_variable m_wake_up_cond; //! Main data-distribution thread. /*! * This thread is created in turn_on() and is destroyed in turn_off(). * It is not exists if run-time monitoring is switched off. */ std::unique_ptr< std::thread > m_distribution_thread; //! Shutdown signal. /*! * It is set to true in turn_off(). */ bool m_shutdown_initiated = { false }; /*! * \name Data sources-related part of controller's data. * \{ */ //! Head of data sources list. source_t * m_head = { nullptr }; //! Tail of data sources list. source_t * m_tail = { nullptr }; //! Data-distribution period. std::chrono::steady_clock::duration m_distribution_period = { default_distribution_period() }; /*! * \} */ //! Main body of data distribution thread. void body(); //! Initiates distribution of current values for all data sources. /*! * \return A time spent for data distribution. */ std::chrono::steady_clock::duration distribute_current_data(); }; #if defined(__clang__) #pragma clang diagnostic pop #endif } /* namespace impl */ } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/messages.hpp000066400000000000000000000056351523065210100211620ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * * \brief Messages with monitoring information. * * \since v.5.5.4 */ #if !defined( SO_5_STATS_MESSAGES_HPP ) #define SO_5_STATS_MESSAGES_HPP #include #include #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { namespace stats { namespace messages { /* * Implementation note: this type has to be marked as SO_5_TYPE for * clang on FreeBSD/macOS. But SO_5_TYPE shouldn't be used on Windows. */ #if defined(SO_5_OS_WINDOWS) #define SO_5_EXPORT_IMPORT_FOR_QUANTITY_MSG /* empty */ #else #define SO_5_EXPORT_IMPORT_FOR_QUANTITY_MSG SO_5_TYPE #endif /*! * \brief A message with value of some quantity. * * This message can be used for monitoring things like queue sizes, * count of delayed messages, count of cooperations and so on. * * \tparam T type for representing quantity. * * \since v.5.5.4 */ template< typename T > struct SO_5_EXPORT_IMPORT_FOR_QUANTITY_MSG quantity : public message_t { //! Prefix of data_source name. prefix_t m_prefix; //! Suffix of data_source name. suffix_t m_suffix; //! Actual quantity value. T m_value; //! Initializing constructor. quantity( const prefix_t & prefix, const suffix_t & suffix, T value ) : m_prefix( prefix ) , m_suffix( suffix ) , m_value( std::move( value ) ) {} }; #undef SO_5_EXPORT_IMPORT_FOR_QUANTITY_MSG /*! * \brief Notification about start of new stats distribution. * * \note This message is empty now but it is not declared as a signal. * It is for a possibility of expansion of the message in the future. * * \since v.5.5.18 */ struct SO_5_TYPE distribution_started : public message_t {}; /*! * \brief Notification about finish of stats distribution. * * \note This message is empty now but it is not declared as a signal. * It is for a possibility of expansion of the message in the future. * * \since v.5.5.18 */ struct SO_5_TYPE distribution_finished : public message_t {}; /*! * \brief Information about one work thread activity. * * \since v.5.5.18 */ struct SO_5_TYPE work_thread_activity : public message_t { //! Prefix of data_source name. prefix_t m_prefix; //! Suffix of data_source name. suffix_t m_suffix; //! ID of the thread. so_5::current_thread_id_t m_thread_id; //! Actual value. work_thread_activity_stats_t m_stats; work_thread_activity( const prefix_t & prefix, const suffix_t & suffix, const so_5::current_thread_id_t & thread_id, work_thread_activity_stats_t stats ) : m_prefix( prefix ) , m_suffix( suffix ) , m_thread_id( thread_id ) , m_stats( stats ) {} }; } /* namespace messages */ } /* namespace stats */ } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/stats/prefix.hpp000066400000000000000000000106641523065210100206460ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.4 * * \brief A type for storing prefix of data_source name. */ #if !defined( SO_5_STATS_PREFIX_HPP ) #define SO_5_STATS_PREFIX_HPP #include #include #include #include namespace so_5 { namespace stats { /*! * \brief A type for storing prefix of data_source name. * * \since v.5.5.4 */ class prefix_t { public : //! Max length of prefix (not including 0-symbol at the end). static constexpr const std::size_t max_length = 47; //! Max size of buffer for prefix value (including 0-symbol at the end). static constexpr const std::size_t max_buffer_size = max_length + 1; //! Default constructor creates empty prefix. constexpr prefix_t() noexcept : m_value{ 0 } {} // clang++ complaints about unsafe pointer arithmetics. // Attempt to use strncpy leads to warnings in VC++. #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif //! Initializing constructor. /*! * Gets no more than max_length symbols. */ constexpr prefix_t( const char * value ) noexcept : m_value{} { char * last = m_value + max_length; char * pos = m_value; while( *value && pos != last ) { *(pos++) = *(value++); } *pos = 0; } #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic pop #endif //! Initializing constructor. /*! * Gets no more than max_length symbols. */ prefix_t( const std::string & value ) noexcept( noexcept(value.c_str()) ) : prefix_t( value.c_str() ) {} //! Access to prefix value. [[nodiscard]] constexpr const char * c_str() const noexcept { return m_value; } //! Access to prefix value as string_view. /*! * \since v.5.8.2 */ [[nodiscard]] constexpr std::string_view as_string_view() const noexcept( noexcept(std::string_view{std::declval()}) ) { return { m_value }; } //! Is prefix empty? [[nodiscard]] constexpr bool empty() const noexcept { return 0 == m_value[ 0 ]; } //! Is equal? [[nodiscard]] bool operator==( const prefix_t & o ) const noexcept { return 0 == strcmp( c_str(), o.c_str() ); } //! Is not equal? [[nodiscard]] bool operator!=( const prefix_t & o ) const noexcept { return 0 != strcmp( c_str(), o.c_str() ); } //! Is less than? [[nodiscard]] bool operator<( const prefix_t & o ) const noexcept { return 0 < strcmp( c_str(), o.c_str() ); } private : //! Actual value. char m_value[ max_buffer_size ]; }; /*! * \brief Just a helper operator. * * \since v.5.5.4 */ inline std::ostream & operator<<( std::ostream & to, const prefix_t & what ) { return (to << what.c_str()); } /*! * \brief A type for representing the suffix of data_source name. * * \note This is just a wrapper around `const char *`. * * \attention * It's assumed that suffix_t holds a pointer to a string in static memory * (it's a pointer to a string literal). * * \since v.5.5.4 */ class suffix_t { public : //! Initializing constructor. constexpr suffix_t( //! Value. It's expected to be a pointer to a null-terminated string. const char * value ) noexcept : m_value( value ) {} //! Access to suffix value. [[nodiscard]] constexpr const char * c_str() const noexcept { return m_value; } //! Access to prefix value as string_view. /*! * \since v.5.8.2 */ [[nodiscard]] constexpr std::string_view as_string_view() const noexcept( noexcept(std::string_view{std::declval()}) ) { return { m_value }; } //! Compares suffixes by pointer values. [[nodiscard]] constexpr bool operator==( const suffix_t & o ) const noexcept { return m_value == o.m_value; } //! Compares suffixes by pointer value. [[nodiscard]] constexpr bool operator!=( const suffix_t & o ) const noexcept { return m_value != o.m_value; } //! Compares suffixes by pointer value. [[nodiscard]] constexpr bool operator<( const suffix_t & o ) const noexcept { return m_value < o.m_value; } private : //! Actual value. const char * m_value; }; /*! * \brief Just a helper operator. * * \since v.5.5.4 */ inline std::ostream & operator<<( std::ostream & to, const suffix_t & what ) { return (to << what.c_str()); } } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/repository.cpp000066400000000000000000000017631523065210100215630ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.4 * * \brief Interfaces of data source and data sources repository. */ #include namespace so_5 { namespace stats { // // repository_t // void repository_t::source_list_add( source_t & what, source_t *& head, source_t *& tail ) noexcept { if( !tail ) { // Addition to the empty list. what.m_prev = what.m_next = nullptr; head = &what; } else { tail->m_next = &what; what.m_prev = tail; what.m_next = nullptr; } tail = &what; } void repository_t::source_list_remove( source_t & what, source_t *& head, source_t *& tail ) noexcept { if( what.m_prev ) what.m_prev->m_next = what.m_next; else head = what.m_next; if( what.m_next ) what.m_next->m_prev = what.m_prev; else tail = what.m_prev; } source_t * repository_t::source_list_next( const source_t & what ) noexcept { return what.m_next; } } /* namespace stats */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/stats/repository.hpp000066400000000000000000000151071523065210100215650ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \since * v.5.5.4 * * \brief Interfaces of data source and data sources repository. */ #if !defined( SO_5_STATS_REPOSITORY_HPP ) #define SO_5_STATS_REPOSITORY_HPP #include #include #include #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace so_5 { namespace stats { /*! * \since * v.5.5.4 * * \brief An interface of data source. */ class SO_5_TYPE source_t { friend class repository_t; protected : // Note: clang-3.9 requires this on Windows platform. source_t( const source_t & ) = delete; source_t( source_t && ) = delete; source_t & operator=( const source_t & ) = delete; source_t & operator=( source_t && ) = delete; source_t() = default; virtual ~source_t() noexcept = default; public : //! Send appropriate notification about the current value. virtual void distribute( //! Target mbox for the appropriate message. const mbox_t & /*distribution_mbox*/ ) = 0; private : //! Previous item in the data sources list. source_t * m_prev{}; //! Next item in the data sources list. source_t * m_next{}; }; /*! * \since * v.5.5.4 * * \brief An interface of data sources repository. */ class SO_5_TYPE repository_t { protected : // Note: clang-3.9 requires this on Windows platform. repository_t( const repository_t & ) = delete; repository_t( repository_t && ) = delete; repository_t & operator=( const repository_t & ) = delete; repository_t & operator=( repository_t && ) = delete; repository_t() = default; virtual ~repository_t() noexcept = default; public : //! Registration of new data source. /*! * Caller must guarantee that the data source will live until * it is registered in the repository. */ virtual void add( source_t & what ) = 0; //! Deregistration of previously registered data source. virtual void remove( source_t & what ) noexcept = 0; protected : //! Helper method for adding data source to existing list. static void source_list_add( //! A new data source to be added to the list. source_t & what, //! Marker of the list head. //! Will be modified if the list is empty. source_t *& head, //! Marker of the list tail. //! Will be modified. source_t *& tail ) noexcept; //! Helper method for removing data source from existing list. static void source_list_remove( //! An item to be removed. source_t & what, //! Marker of the list head. //! Will be modified if the list becomes empty. source_t *& head, //! Marker of the list tail. //! Will be modified if the item at the end of the list. source_t *& tail ) noexcept; //! Helper method for accessing next data source in the list. static source_t * source_list_next( //! The current item. const source_t & what ) noexcept; }; /*! * \brief A holder for data-souce that should be automatically * registered and deregistered in registry. * * This class is necessary because data-source can't * register and deregister itself in the constructor/destructor. * It can lead to errors when `distribute()` method is called * during object's destruction. * * To avoid that problem data-source is created inside that * holder. It means that data-source is still live and fully * constructed when the destructor of holder starts its work. * It allows to deregister data-source before the destructor * of data source will be called. * * \since * v.5.6.0 */ template< typename Data_Source > class auto_registered_source_holder_t { public : // This class isn't Copyable nor Moveable. auto_registered_source_holder_t( const auto_registered_source_holder_t & ) = delete; auto_registered_source_holder_t( auto_registered_source_holder_t && ) = delete; //! Initializing constructor. template< typename... Args > auto_registered_source_holder_t( outliving_reference_t< repository_t > repo, Args && ...args ) : m_repo{ repo } , m_ds{ std::forward(args)... } { m_repo.get().add( m_ds ); } ~auto_registered_source_holder_t() noexcept { m_repo.get().remove( m_ds ); } Data_Source & get() noexcept { return m_ds; } const Data_Source & get() const noexcept { return m_ds; } private : //! Repository for data source. outliving_reference_t< repository_t > m_repo; //! Data source itself. Data_Source m_ds; }; /*! * \brief An addition to auto_registered_source_holder for the * cases where manual registration of data_source should be used * instead of automatic one. * * \attention * This is a simple wrapped that doesn't control the correctness * of start() and stop() method calls. For example: * \code * environment_t & env = ...; * manually_registered_source_holder_t< my_data_source > my_source; * ... * my_source.start( outliving_mutable(env.stats_repository()) ); * // Repeated call to start() may lead to UB. * my_source.start( outliving_mutable(env.stats_repository()) ); // Oops! * ... * my_source.stop(); // The first call to stop() is OK. * // Repeated call to stop() may lead to UB (null pointer dereference, for example. * my_source.stop(); // Oops! * \endcode * * \since v.5.6.0 */ template< typename Data_Source > class manually_registered_source_holder_t { public : // This class isn't Copyable nor Moveable. manually_registered_source_holder_t( const manually_registered_source_holder_t & ) = delete; manually_registered_source_holder_t( manually_registered_source_holder_t && ) = delete; //! Initializing constructor. template< typename... Args > manually_registered_source_holder_t( Args && ...args ) : m_ds{ std::forward(args)... } {} ~manually_registered_source_holder_t() noexcept { if( m_repo ) stop(); } /*! * \note * It's safe to call start() only if start() haven't been called yet. * Repeated call to start() leads to UB. */ void start( outliving_reference_t< repository_t > repo ) { repo.get().add( m_ds ); m_repo = &(repo.get()); } /*! * \note * It's safe to call stop() only if start() has been called earlier. * Repeated call to stop() leads to UB. */ void stop() noexcept { m_repo->remove( m_ds ); m_repo = nullptr; } Data_Source & get() noexcept { return m_ds; } const Data_Source & get() const noexcept { return m_ds; } private : //! Repository for data source. repository_t * m_repo{ nullptr }; //! Data source itself. Data_Source m_ds; }; } /* namespace stats */ } /* namespace so_5 */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif #endif sobjectizer-5.8.6/dev/so_5/stats/std_names.cpp000066400000000000000000000031261523065210100213140ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief Standard data sources prefixes and suffixes used by SObjectizer. */ #include namespace so_5 { namespace stats { namespace prefixes { SO_5_FUNC prefix_t coop_repository() { return prefix_t( "coop_repository" ); } SO_5_FUNC prefix_t mbox_repository() { return prefix_t( "mbox_repository" ); } SO_5_FUNC prefix_t timer_thread() { return prefix_t( "timer_thread" ); } } /* namespace prefixes */ namespace suffixes { #define IMPL_SUFFIX(val) \ static constexpr const char s[] = val;\ return suffix_t{ s }; SO_5_FUNC suffix_t coop_count() { IMPL_SUFFIX( "/coop.count" ) } SO_5_FUNC suffix_t coop_final_dereg_count() { IMPL_SUFFIX( "/coop.final.dereg.count" ) } SO_5_FUNC suffix_t named_mbox_count() { IMPL_SUFFIX( "/named_mbox.count" ) } SO_5_FUNC suffix_t agent_count() { IMPL_SUFFIX( "/agent.count" ) } SO_5_FUNC suffix_t disp_active_group_count() { IMPL_SUFFIX( "/group.count" ) } SO_5_FUNC suffix_t work_thread_queue_size() { IMPL_SUFFIX( "/demands.count" ) } SO_5_FUNC suffix_t work_thread_activity() { IMPL_SUFFIX( "/thread.activity" ) } SO_5_FUNC suffix_t disp_thread_count() { IMPL_SUFFIX( "/threads.count" ) } SO_5_FUNC suffix_t timer_single_shot_count() { IMPL_SUFFIX( "/single_shot.count" ) } SO_5_FUNC suffix_t timer_periodic_count() { IMPL_SUFFIX( "/periodic.count" ) } SO_5_FUNC suffix_t demand_quote() { IMPL_SUFFIX( "/demands.quote" ) } #undef IMPL_SUFFIX } /* namespace suffixes */ } /* namespace stats */ } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/stats/std_names.hpp000066400000000000000000000051671523065210100213300ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.4 * * \file * \brief Standard data sources prefixes and suffixes used by SObjectizer. */ #if !defined( SO_5_STATS_STD_NAMES_HPP ) #define SO_5_STATS_STD_NAMES_HPP #include #include namespace so_5 { namespace stats { namespace prefixes { /*! * \since * v.5.5.4 * * \brief Prefix of data sources with statistics for cooperations and * agents repository. */ SO_5_FUNC prefix_t coop_repository(); /*! * \since * v.5.5.4 * * \brief Prefix of data sources with statistics for mboxes repository. */ SO_5_FUNC prefix_t mbox_repository(); /*! * \since * v.5.5.4 * * \brief Prefix of data sources with statistics for timer thread. */ SO_5_FUNC prefix_t timer_thread(); } /* namespace prefixes */ namespace suffixes { /*! * \since * v.5.6.0 * * \brief Suffix for data source with count of cooperations. */ SO_5_FUNC suffix_t coop_count(); /*! * \since * v.5.5.12 * * \brief Suffix for data source with count of cooperations waiting * the final deregistration step and removement from coops registry. */ SO_5_FUNC suffix_t coop_final_dereg_count(); /*! * \since * v.5.5.4 * * \brief Suffix for data source with count of named mboxes. */ SO_5_FUNC suffix_t named_mbox_count(); /*! * \since * v.5.5.4 * * \brief Suffix for data source with count of agents bound to some entity. */ SO_5_FUNC suffix_t agent_count(); /*! * \since * v.5.5.4 * * \brief Suffix for data source with count of active groups in * an active_group dispatcher. */ SO_5_FUNC suffix_t disp_active_group_count(); /*! * \since * v.5.5.4 * * \brief Suffix for data source with count of demands in a working thread event queue. */ SO_5_FUNC suffix_t work_thread_queue_size(); /*! * \since * v.5.5.18 * * \brief Suffix for data source with work thread activity statistics. */ SO_5_FUNC suffix_t work_thread_activity(); /*! * \since * v.5.5.4 * * \brief Suffix for data source with count of work threads for dispatcher. */ SO_5_FUNC suffix_t disp_thread_count(); /*! * \since * v.5.5.4 * * \brief Suffix for data source with count of single-shot timers. */ SO_5_FUNC suffix_t timer_single_shot_count(); /*! * \since * v.5.5.4 * * \brief Suffix for data source with count of periodic timers. */ SO_5_FUNC suffix_t timer_periodic_count(); /*! * \since * v.5.5.8 * * \brief Suffix for data source with size of quote for demands processing. * * This suffix is used in prio_one_thread::quoted_round_robin dispatcher. */ SO_5_FUNC suffix_t demand_quote(); } /* namespace suffixes */ } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stats/work_thread_activity.hpp000066400000000000000000000102261523065210100235700ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Data types for work thread's activity stats. * * \since v.5.5.18 */ #if !defined( SO_5_STATS_WORK_THREAD_ACTIVITY_HPP ) #define SO_5_STATS_WORK_THREAD_ACTIVITY_HPP #include #include #include #include #include namespace so_5 { namespace stats { /*! * \brief An alias for clock type to be used for statistics. * * \since v.5.5.18 */ using clock_type_t = std::conditional< std::chrono::high_resolution_clock::is_steady, std::chrono::high_resolution_clock, std::chrono::steady_clock >::type; static_assert( clock_type_t::is_steady, "clock_type_t must be steady clock!" ); /*! * \brief An alias for type for representing time duration. * * \since v.5.5.18 */ using duration_t = clock_type_t::duration; /*! * \brief Statistics of some activity. * * \since v.5.5.18 */ struct activity_stats_t { //! Count of events in that period of time. std::uint_fast64_t m_count{}; //! Total time spent for events in that period of time. duration_t m_total_time{}; //! Average time for one event. duration_t m_avg_time{}; //! Duration of the current activity. //! //! This value is defined only if the current activity is present. //! Otherwise it will be nullopt. //! //! When it isn't nullopt it contains duration of the current //! activity. For example, the total execution time of the current //! event handler. If an instance of activity_stats_t describes //! waiting, then this field shows duration of the current waiting //! period. //! //! \since v.5.8.5 std::optional< duration_t > m_current_activity_time; }; /*! * \brief Helper for printing value of activity_stats. * * \since v.5.5.18 */ inline std::ostream & operator<<( std::ostream & to, const activity_stats_t & what ) { auto to_ms = []( const duration_t & d ) { return double(std::chrono::duration_cast< std::chrono::nanoseconds >( d ) .count()) / 1000000.0; }; to << "[count=" << what.m_count << ";total=" << to_ms(what.m_total_time) << "ms;avg=" << to_ms(what.m_avg_time) << "ms"; if( what.m_current_activity_time ) to << ";current=" << to_ms(*what.m_current_activity_time) << "ms"; to << ']'; return to; } /*! * \brief Stats for a work thread activity. * * \since v.5.5.18 */ struct work_thread_activity_stats_t { //! Stats for processed events. activity_stats_t m_working_stats{}; //! Stats for waiting periods. /*! * \note * Not all dispatchers can provide this information. In such a case * there will be an object without actual information (all fields of * m_waiting_stats will have default zero values). */ activity_stats_t m_waiting_stats{}; }; namespace details { /*! * \brief A function for calculating average value. * * Uses modified moving average (also known as running moving average) * which is calculated as: MMA(t) = (last + (N-1)*MMA(t-1))/N. * * \since v.5.5.18 */ inline duration_t calc_avg_time( //! Total count of samples. std::uint_fast64_t count, //! Previous average value. duration_t previous, //! Duration of last activity (or waiting). duration_t last ) { const int N = count > 100u ? 100 : (count ? static_cast< int >(count) : 1); return (last + (N-1) * previous) / N; } /*! * \brief Helper function for simplification of current stats update. * * \since v.5.5.18 */ inline void update_stats_from_duration( activity_stats_t & value_to_update, clock_type_t::duration last_duration ) { value_to_update.m_total_time += last_duration; value_to_update.m_avg_time = calc_avg_time( value_to_update.m_count, value_to_update.m_avg_time, last_duration ); } /*! * \brief Helper function for simplification of current stats update. * * \since v.5.5.18 */ inline void update_stats_from_current_time( activity_stats_t & value_to_update, clock_type_t::time_point activity_started_at ) { const auto current_activity_time = clock_type_t::now() - activity_started_at; value_to_update.m_current_activity_time = current_activity_time; update_stats_from_duration( value_to_update, current_activity_time ); } } /* namespace details */ } /* namespace stats */ } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/stop_guard.hpp000066400000000000000000000035751523065210100203650ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief An iterface of stop_guard. * * \since * v.5.5.19.2 */ #if !defined( SO_5_STOP_GUARD_HPP ) #define SO_5_STOP_GUARD_HPP #include #include #include namespace so_5 { // // stop_guard // /*! * \brief An interface of stop_guard entity. * * All implementations of stop_guards must implement this interface. * * \since * v.5.5.19.2 */ class SO_5_FUNC stop_guard_t { public : //! Type for result of setting up a new stop_guard. enum class setup_result_t { //! New stop_guard has be set successfully. ok, //! New stop_guard has not been set because stop operation //! is already in progress. stop_already_in_progress }; //! What to do if attempt to setup a new stop_guard is performed //! when the stop operation is in progress. enum class what_if_stop_in_progress_t { //! An exception must be thrown. throw_exception, //! Return negative setup result. return_negative_result }; stop_guard_t() = default; stop_guard_t( const stop_guard_t & ) = delete; stop_guard_t( stop_guard_t && ) = delete; virtual ~stop_guard_t() noexcept = default; stop_guard_t & operator=( const stop_guard_t & ) = delete; stop_guard_t & operator=( stop_guard_t && ) = delete; //! Perform stop-related actions. /*! * This method will be called by SObjectizer Environment * when an user called environment_t::stop(). * * Stop_guard must start stop-related activities. When all these * activities will be finished the stop_guard must remove itself * from the list of stop_guards. */ virtual void stop() noexcept = 0; }; // // stop_guard_shptr_t // /*! * \brief An alias of shared_ptr for stop_guard. * * \since * v.5.5.19.2 */ using stop_guard_shptr_t = std::shared_ptr< stop_guard_t >; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/subscription_storage_fwd.hpp000066400000000000000000000144611523065210100233220ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.3 * * \file * \brief Forward definitions for subscription storage related stuff. */ #if !defined( SO_5_SUBSCRIPTION_STORAGE_FWD_HPP ) #define SO_5_SUBSCRIPTION_STORAGE_FWD_HPP #include #include #include #include namespace so_5 { namespace impl { class subscription_storage_t; /*! * \since * v.5.5.3 * * \brief Typedef for unique pointer to subscription_storage object. */ using subscription_storage_unique_ptr_t = std::unique_ptr< subscription_storage_t >; } /* namespace impl */ /*! * \since * v.5.5.3 * * \brief Type of subscription_storage factory. */ using subscription_storage_factory_t = std::function< impl::subscription_storage_unique_ptr_t() >; /*! * \brief Factory for default subscription storage object. * * \note Creates adaptive storage with vector-based storage for * small amount of subscriptions and map-based storage for * large amount of subscriptions. * * \par More about subscription storage tuning * See \ref so_5_5_3__subscr_storage_selection for more details about selection * of appropriate subscription storage type. * * \since v.5.5.3 */ [[nodiscard]] SO_5_FUNC subscription_storage_factory_t default_subscription_storage_factory(); /*! * \brief Factory for default subscription storage based on std::unordered_map. * * \note This storage is efficient only in the cases of very large amount * of subscriptions (from several hundreds to thousands). For smaller amounts * map-based and vector-based storages are more appropriate. * * \par More about subscription storage tuning * See \ref so_5_5_3__subscr_storage_selection for more details about selection * of appropriate subscription storage type. * * \since v.5.5.3 */ [[nodiscard]] SO_5_FUNC subscription_storage_factory_t hash_table_based_subscription_storage_factory(); /*! * \brief Factory for subscription storage based on unsorted std::vector. * * \note Uses very simple working scheme: all subscriptions are stored in the * linear vector and simpliest linear search is used for seaching and * manipulating of subscriptions. Because of that vector-based storage must be * used only with very small amount of subscriptions. * * \par More about subscription storage tuning * See \ref so_5_5_3__subscr_storage_selection for more details about selection * of appropriate subscription storage type. * * \since v.5.5.3 */ [[nodiscard]] SO_5_FUNC subscription_storage_factory_t vector_based_subscription_storage_factory( //! Initial storage capacity. std::size_t initial_capacity ); /*! * \brief Factory for subscription storage based on std::map. * * \note Uses std::map as an underlying storage. And very efficient when count * of subscription in measured in dozens. For very large amount of * subscriptions the hash-table-based storage is more efficient. * * \par More about subscription storage tuning * See \ref so_5_5_3__subscr_storage_selection for more details about selection * of appropriate subscription storage type. * * \since v.5.5.3 */ [[nodiscard]] SO_5_FUNC subscription_storage_factory_t map_based_subscription_storage_factory(); /*! * \brief Factory for adaptive subscription storage. * * \par Description * This storage will use vector-based storage for small amount * of subscriptions. And map-based storage for large amount * of subscriptions. * * \par More about subscription storage tuning * See \ref so_5_5_3__subscr_storage_selection for more details about selection * of appropriate subscription storage type. * * \since v.5.5.3 */ [[nodiscard]] SO_5_FUNC subscription_storage_factory_t adaptive_subscription_storage_factory( //! Threshold for switching from small storage to the large one //! (and back from the large to the small in the case of subscription //! deletion). std::size_t threshold ); /*! * \brief Factory for adaptive subscription storage. * * \par Description * This storage will use storage created by \a small_storage_factory for * small amount of subscriptions. And storage created by * \a large_storage_factory for large amount of subscriptions. * * \par Cascading of storages * This function can be used for creating more complex cascades of * several subscription storages. For example, the following code creates * adaptive storage with three underlying objects. The first will be used * for 0..10 subscriptions. The seconds will be used for 11..100 subscriptions. * And the third for 100+ subscriptions: \code so_5::adaptive_subscription_storage_factory( // First threshold for switching from the first storage to the second. 10, // First storage -- simple vector-based. so_5::vector_based_subscription_storage_factory(10), // There will be another adaptive storage. so_5::adaptive_subscription_storage_factory( // Second threshold for switching from the second storage to the third. 100, // Second storage. so_5::map_based_subscription_storage_factory(), // Third storage. so_5::hash_table_based_subscription_storage_factory() ) ); \endcode * * * \par More about subscription storage tuning * See \ref so_5_5_3__subscr_storage_selection for more details about selection * of appropriate subscription storage type. * * \since v.5.5.3 */ [[nodiscard]] SO_5_FUNC subscription_storage_factory_t adaptive_subscription_storage_factory( //! Threshold for switching from small storage to the large one //! (and back from the large to the small in the case of subscription //! deletion). std::size_t threshold, //! A factory for creating small storage. const subscription_storage_factory_t & small_storage_factory, //! A factory for creating large storage. const subscription_storage_factory_t & large_storage_factory ); /*! * \brief Factory for subscription storage based on sorted std::vector. * * The capacity of the storage is not fixed, the underlaying vector will * grow as needed (note that the current implementation never shrinks it down). * * \note * Uses very simple working scheme: all subscriptions are stored in a * sorted vector (aka "flat_set") and binary search is used for seaching and * manipulating of subscriptions. * * \since v.5.8.2 */ [[nodiscard]] SO_5_FUNC subscription_storage_factory_t flat_set_based_subscription_storage_factory( //! Initial storage capacity. std::size_t initial_capacity ); } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/thread_helper_functions.hpp000066400000000000000000000052641523065210100231110ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Helper functions for working with threads. * \since * v.5.5.16 */ #if !defined( SO_5_THREAD_HELPER_FUNCTIONS_HPP ) #define SO_5_THREAD_HELPER_FUNCTIONS_HPP #include #include namespace so_5 { namespace thread_auto_join_details { /*! * \brief Main class for auto join operation. * * \attention This is Moveable class, not Copyable. * * \since * v.5.5.16 */ template< std::size_t N > class auto_joiner_t { std::array< std::thread *, N > m_threads; void nullify() { m_threads.fill( nullptr ); } public : auto_joiner_t( const auto_joiner_t & ) = delete; auto_joiner_t & operator=( const auto_joiner_t & ) = delete; auto_joiner_t() { nullify(); } auto_joiner_t( auto_joiner_t && o ) noexcept { nullify(); m_threads.swap( o.m_threads ); } ~auto_joiner_t() { for( auto t : m_threads ) if( t && t->joinable() ) t->join(); } friend inline void swap( auto_joiner_t & a, auto_joiner_t & b ) noexcept { using std::swap; swap( a.m_threads, b.m_threads ); } auto_joiner_t & operator=( auto_joiner_t && o ) noexcept { auto_joiner_t tmp{ std::move(o) }; swap( *this, tmp ); return *this; } void set_thread( std::size_t index, std::thread & t ) { m_threads[ index ] = &t; } }; /*! * \brief Helper for filling auto_joiner object. * * \since * v.5.5.16 */ template< typename Joiner > void add_to_joiner( Joiner & to, std::size_t index, std::thread & t ) { to.set_thread( index, t ); } /*! * \brief Helper for filling auto_joiner object. * * \since * v.5.5.16 */ template< typename Joiner, typename... Tail > void add_to_joiner( Joiner & to, std::size_t index, std::thread & t, Tail &&... tail ) { add_to_joiner( to, index, t ); add_to_joiner( to, index + 1, std::forward< Tail >(tail)... ); } } /* namespace thread_auto_join_details */ /*! * \brief Helper function for creation of automatic joiner of std::threads. * * Usage examples: * \code std::thread first_worker; std::thread second_worker; auto worker_joiner = so_5::auto_join( first_worker, second_worker ); first_worker = thread{ ... }; second_worker = thread{ ... }; ... * \endcode * * \since * v.5.5.16 */ template< typename... Tail > thread_auto_join_details::auto_joiner_t< 1 + sizeof...(Tail) > auto_join( //! The first thread to be closed. std::thread & first_thread, //! Other threads to be closed. Tail &&... tail ) { using namespace thread_auto_join_details; auto_joiner_t< 1 + sizeof...(tail) > joiner; add_to_joiner( joiner, 0, first_thread, std::forward< Tail >(tail)... ); return joiner; } } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/timers.cpp000066400000000000000000000366321523065210100175140ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \since v.5.5.0 \brief Timers and tools for working with timers. */ #include #include #include #include namespace so_5 { namespace timers_details { // // actual_timer_t // /*! * \brief An actual implementation of timer interface. * * \note * Since v.5.5.19 this template can be used with timer_thread and * with timer_manager. * * \tparam Timer A type of timertt-based thread/manager which implements timers. * * \since v.5.5.0 */ template< class Timer > class actual_timer_t : public timer_t { public : //! The actual type of timer holder for timertt. using timer_holder_t = timertt::timer_object_holder< typename Timer::thread_safety >; //! Initialized constructor. actual_timer_t( Timer * thread ) : m_thread( thread ) , m_timer( thread->allocate() ) {} virtual ~actual_timer_t() noexcept override { release(); } timer_holder_t & timer_holder() noexcept { return m_timer; } virtual bool is_active() const noexcept override { return (m_thread != nullptr); } virtual void release() noexcept override { if( m_thread ) { m_thread->deactivate( m_timer ); m_thread = nullptr; m_timer.reset(); } } private : //! Timer thread for the timer. /*! * nullptr means that timer is deactivated. */ Timer * m_thread; //! Underlying timer object reference. timer_holder_t m_timer; }; // // timer_action_for_timer_thread_t // /*! * \brief A functor to be used as timer action in implementation * of timer thread. * * \since * v.5.5.20 */ class timer_action_for_timer_thread_t { std::type_index m_type_index; mbox_t m_mbox; message_ref_t m_msg; public: timer_action_for_timer_thread_t( std::type_index type_index, mbox_t mbox, message_ref_t msg ) : m_type_index( std::move(type_index) ) , m_mbox( std::move(mbox) ) , m_msg( std::move(msg) ) {} void operator()() noexcept { ::so_5::impl::mbox_iface_for_timers_t{ m_mbox } .deliver_message_from_timer( m_type_index, m_msg ); } }; // // actual_thread_t // /*! * \since * v.5.5.0 * * \brief An actual implementation of timer thread. * * \tparam Timer_Thread A type of timertt-based thread which implements timers. */ template< class Timer_Thread > class actual_thread_t : public timer_thread_t { using timer_demand_t = actual_timer_t< Timer_Thread >; public : //! Initializing constructor. actual_thread_t( //! Real timer thread. std::unique_ptr< Timer_Thread > thread ) : m_thread( std::move( thread ) ) {} virtual void start() override { m_thread->start(); } virtual void finish() override { m_thread->shutdown_and_join(); } virtual timer_id_t schedule( const std::type_index & type_index, const mbox_t & mbox, const message_ref_t & msg, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) override { auto timer = std::make_unique< timer_demand_t >( m_thread.get() ); m_thread->activate( timer->timer_holder(), pause, period, timer_action_for_timer_thread_t( type_index, mbox, msg ) ); return timer_id_t( timer.release() ); } virtual void schedule_anonymous( const std::type_index & type_index, const mbox_t & mbox, const message_ref_t & msg, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) override { m_thread->activate( pause, period, timer_action_for_timer_thread_t( type_index, mbox, msg ) ); } virtual timer_thread_stats_t query_stats() override { auto d = m_thread->get_timer_quantities(); return timer_thread_stats_t{ d.m_single_shot_count, d.m_periodic_count }; } private : std::unique_ptr< Timer_Thread > m_thread; }; // // timer_action_for_timer_manager_t // /*! * \brief A functor to be used as timer action in implementation * of timer thread. * * \since * v.5.5.20 */ class timer_action_for_timer_manager_t { outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > m_collector; std::type_index m_type_index; mbox_t m_mbox; message_ref_t m_msg; public: timer_action_for_timer_manager_t( outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector, std::type_index type_index, mbox_t mbox, message_ref_t msg ) : m_collector( collector ) , m_type_index( std::move(type_index) ) , m_mbox( std::move(mbox) ) , m_msg( std::move(msg) ) {} void operator()() noexcept { m_collector.get().accept( m_type_index, m_mbox, m_msg ); } }; // // actual_manager_t // /*! * \brief An actual implementation of timer_manager. * * \tparam Timer_Manager A type of timertt-based manager which implements timers. * * \since * v.5.5.19 */ template< class Timer_Manager > class actual_manager_t : public timer_manager_t { using timer_demand_t = actual_timer_t< Timer_Manager >; public : //! Initializing constructor. actual_manager_t( //! Real timer thread. std::unique_ptr< Timer_Manager > manager, //! Collector for elapsed timers. outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector ) : m_manager( std::move( manager ) ) , m_collector( std::move( collector ) ) {} virtual void process_expired_timers() override { m_manager->process_expired_timers(); } virtual std::chrono::steady_clock::duration timeout_before_nearest_timer( std::chrono::steady_clock::duration default_timer ) override { return m_manager->timeout_before_nearest_timer( default_timer ); } virtual timer_id_t schedule( const std::type_index & type_index, const mbox_t & mbox, const message_ref_t & msg, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) override { auto timer = std::make_unique< timer_demand_t >( m_manager.get() ); m_manager->activate( timer->timer_holder(), pause, period, timer_action_for_timer_manager_t( m_collector, type_index, mbox, msg ) ); return timer_id_t( timer.release() ); } virtual void schedule_anonymous( const std::type_index & type_index, const mbox_t & mbox, const message_ref_t & msg, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period ) override { m_manager->activate( pause, period, timer_action_for_timer_manager_t( m_collector, type_index, mbox, msg ) ); } virtual bool empty() override { return m_manager->empty(); } virtual timer_thread_stats_t query_stats() override { auto d = m_manager->get_timer_quantities(); return timer_thread_stats_t{ d.m_single_shot_count, d.m_periodic_count }; } private : std::unique_ptr< Timer_Manager > m_manager; outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > m_collector; }; // // error_logger_for_timertt_t // /*! * \since * v.5.5.0 * * \brief Type of error_logger for timertt stuff. */ using error_logger_for_timertt_t = std::function< void(const std::string &) >; // // create_error_logger_for_timertt // #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmissing-prototypes" #endif error_logger_for_timertt_t create_error_logger_for_timertt( const error_logger_shptr_t & logger ) { // NOTE: if this logger throws then timertt will catch the exception // and terminate the application. // It's normal because timertt calls this logger right before calling // std::abort() in the cases when timertt can't recover from // previous failures. return [logger]( const std::string & msg ) { SO_5_LOG_ERROR( *logger, stream ) { stream << "error inside timer_thread: " << msg; } }; } #if defined(__clang__) #pragma clang diagnostic pop #endif // // exception_handler_for_timertt_t // /*! * \since * v.5.5.0 * * \brief Type of actor_exception_handler for timertt stuff. */ using exception_handler_for_timertt_t = std::function< void(const std::exception &) >; // // create_exception_handler_for_timertt // #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmissing-prototypes" #endif exception_handler_for_timertt_t create_exception_handler_for_timertt_thread( const error_logger_shptr_t & logger ) { return [logger]( const std::exception & x ) { so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( *logger, stream ) { stream << "exception has been thrown and caught inside " "timer_thread, application will be aborted. " "Exception: " << x.what(); } } ); }; } exception_handler_for_timertt_t create_exception_handler_for_timertt_manager( const error_logger_shptr_t & logger ) { return [logger]( const std::exception & x ) { so_5::details::abort_on_fatal_error( [&] { SO_5_LOG_ERROR( *logger, stream ) { stream << "exception has been thrown and caught inside " "timer_manager, application will be aborted. " "Exception: " << x.what(); } } ); }; } #if defined(__clang__) #pragma clang diagnostic pop #endif /*! * \name Short synonyms for timertt templates. * \{ */ //! timer_wheel thread type. using timer_wheel_thread_t = timertt::timer_wheel_thread_template< timer_action_for_timer_thread_t, error_logger_for_timertt_t, exception_handler_for_timertt_t >; //! timer_heap thread type. using timer_heap_thread_t = timertt::timer_heap_thread_template< timer_action_for_timer_thread_t, error_logger_for_timertt_t, exception_handler_for_timertt_t >; //! timer_list thread type. using timer_list_thread_t = timertt::timer_list_thread_template< timer_action_for_timer_thread_t, error_logger_for_timertt_t, exception_handler_for_timertt_t >; //! timer_wheel manager type. using timer_wheel_manager_t = timertt::timer_wheel_manager_template< timertt::thread_safety::unsafe, timer_action_for_timer_manager_t, error_logger_for_timertt_t, exception_handler_for_timertt_t >; //! timer_heap manager type. using timer_heap_manager_t = timertt::timer_heap_manager_template< timertt::thread_safety::unsafe, timer_action_for_timer_manager_t, error_logger_for_timertt_t, exception_handler_for_timertt_t >; //! timer_list manager type. using timer_list_manager_t = timertt::timer_list_manager_template< timertt::thread_safety::unsafe, timer_action_for_timer_manager_t, error_logger_for_timertt_t, exception_handler_for_timertt_t >; /*! * \} */ } /* namespace timers_details */ SO_5_FUNC timer_thread_unique_ptr_t create_timer_wheel_thread( error_logger_shptr_t logger ) { using timertt_thread_t = timers_details::timer_wheel_thread_t; return create_timer_wheel_thread( std::move(logger), timertt_thread_t::default_wheel_size(), timertt_thread_t::default_granularity() ); } SO_5_FUNC timer_thread_unique_ptr_t create_timer_wheel_thread( error_logger_shptr_t logger, unsigned int wheel_size, std::chrono::steady_clock::duration granuality ) { using timertt_thread_t = timers_details::timer_wheel_thread_t; using namespace timers_details; std::unique_ptr< timertt_thread_t > thread( new timertt_thread_t( wheel_size, granuality, create_error_logger_for_timertt( logger ), create_exception_handler_for_timertt_thread( logger ) ) ); return timer_thread_unique_ptr_t( new actual_thread_t< timertt_thread_t >( std::move( thread ) ) ); } SO_5_FUNC timer_thread_unique_ptr_t create_timer_heap_thread( error_logger_shptr_t logger ) { using timertt_thread_t = timers_details::timer_heap_thread_t; return create_timer_heap_thread( std::move(logger), timertt_thread_t::default_initial_heap_capacity() ); } SO_5_FUNC timer_thread_unique_ptr_t create_timer_heap_thread( error_logger_shptr_t logger, std::size_t initial_heap_capacity ) { using timertt_thread_t = timers_details::timer_heap_thread_t; using namespace timers_details; std::unique_ptr< timertt_thread_t > thread( new timertt_thread_t( initial_heap_capacity, create_error_logger_for_timertt( logger ), create_exception_handler_for_timertt_thread( logger ) ) ); return timer_thread_unique_ptr_t( new actual_thread_t< timertt_thread_t >( std::move( thread ) ) ); } SO_5_FUNC timer_thread_unique_ptr_t create_timer_list_thread( error_logger_shptr_t logger ) { using timertt_thread_t = timers_details::timer_list_thread_t; using namespace timers_details; std::unique_ptr< timertt_thread_t > thread( new timertt_thread_t( create_error_logger_for_timertt( logger ), create_exception_handler_for_timertt_thread( logger ) ) ); return timer_thread_unique_ptr_t( new actual_thread_t< timertt_thread_t >( std::move( thread ) ) ); } SO_5_FUNC timer_manager_unique_ptr_t create_timer_wheel_manager( error_logger_shptr_t logger, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector ) { using timertt_manager_t = timers_details::timer_wheel_manager_t; return create_timer_wheel_manager( std::move(logger), collector, timertt_manager_t::default_wheel_size(), timertt_manager_t::default_granularity() ); } SO_5_FUNC timer_manager_unique_ptr_t create_timer_wheel_manager( error_logger_shptr_t logger, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector, unsigned int wheel_size, std::chrono::steady_clock::duration granuality ) { using timertt_manager_t = timers_details::timer_wheel_manager_t; using namespace timers_details; auto manager = std::make_unique< timertt_manager_t >( wheel_size, granuality, create_error_logger_for_timertt( logger ), create_exception_handler_for_timertt_manager( logger ) ); return std::make_unique< actual_manager_t< timertt_manager_t > >( std::move( manager ), collector ); } SO_5_FUNC timer_manager_unique_ptr_t create_timer_heap_manager( error_logger_shptr_t logger, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector ) { using timertt_manager_t = timers_details::timer_heap_manager_t; return create_timer_heap_manager( std::move(logger), collector, timertt_manager_t::default_initial_heap_capacity() ); } SO_5_FUNC timer_manager_unique_ptr_t create_timer_heap_manager( error_logger_shptr_t logger, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector, std::size_t initial_heap_capacity ) { using timertt_manager_t = timers_details::timer_heap_manager_t; using namespace timers_details; auto manager = std::make_unique< timertt_manager_t >( initial_heap_capacity, create_error_logger_for_timertt( logger ), create_exception_handler_for_timertt_manager( logger ) ); return std::make_unique< actual_manager_t< timertt_manager_t > >( std::move( manager ), collector ); } SO_5_FUNC timer_manager_unique_ptr_t create_timer_list_manager( error_logger_shptr_t logger, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector ) { using timertt_manager_t = timers_details::timer_list_manager_t; using namespace timers_details; auto manager = std::make_unique< timertt_manager_t >( create_error_logger_for_timertt( logger ), create_exception_handler_for_timertt_manager( logger ) ); return std::make_unique< actual_manager_t< timertt_manager_t > >( std::move( manager ), collector ); } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/timers.hpp000066400000000000000000000451311523065210100175130ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \since v.5.5.0 \brief Timers and tools for working with timers. */ #if !defined( SO_5_TIMERS_HPP ) #define SO_5_TIMERS_HPP #include #include #include #include #include #include #include #include #include namespace so_5 { #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif // // timer_t // /*! * \brief A base class for timer identificator. * * \since v.5.5.0 */ class SO_5_TYPE timer_t : private so_5::atomic_refcounted_t { friend class intrusive_ptr_t< timer_t >; public : virtual ~timer_t() noexcept = default; //! Is this timer event is active? virtual bool is_active() const noexcept = 0; //! Release the timer event. virtual void release() noexcept = 0; }; // // timer_id_t // /*! * \brief An indentificator for the timer. * * This type is Copyable and Moveable. * * Instances of this type work like smart pointers: a reference counter * for a timer event is used. When a timer_id is copied this counter is * incremented. When a timer_id dies this counter is decremented. When * the last timer_id dies then the timer event if cancelled (released). * * The timer event can be cancelled explicitly by calling release() method. * * The method is_active() allows to check if timer event is not cancelled yet. * * \since v.5.5.0 */ class SO_5_TYPE timer_id_t { public : //! Default constructor. timer_id_t() noexcept = default; //! Initializing constructor. timer_id_t( so_5::intrusive_ptr_t< timer_t > && timer ) noexcept : m_timer( std::move(timer) ) {} //! Swapping. friend void swap( timer_id_t & a, timer_id_t & b ) noexcept { swap( a.m_timer, b.m_timer ); } //! Is this timer event is active? //! //! \retval true if timer event wasn't cancelled yet. //! \retval false if timer event was cancelled by release() call. //! //! \note //! This method can return `true` even if timer event is already fired. //! The `false` will be returned only if release() was called explicitly. bool is_active() const noexcept { return ( m_timer && m_timer->is_active() ); } //! Release the timer event. void release() noexcept { if( m_timer ) m_timer->release(); } private : //! Actual timer. so_5::intrusive_ptr_t< timer_t > m_timer; }; // // timer_thread_stats_t // /*! * \since * v.5.5.4 * * \brief Statistics for run-time monitoring. */ struct timer_thread_stats_t { //! Quantity of single-shot timers. std::size_t m_single_shot_count; //! Quantity of periodic timers. std::size_t m_periodic_count; }; // // timer_thread_t // //! Timer thread interface. /*! All timer threads for SObjectizer must be derived from this class. A real timer may not be implemented as a thread. The name of this class is just a consequence of some historic reasons. A timer is started by timer_thread_t::start() method. To stop timer the timer_thread_t::finish() method is used. The finish() method should block caller until all timer resources will be released and all dedicated timer threads (if any) are completelly stopped. */ class SO_5_TYPE timer_thread_t { timer_thread_t( const timer_thread_t & ) = delete; timer_thread_t & operator=( const timer_thread_t & ) = delete; public: timer_thread_t() = default; virtual ~timer_thread_t() = default; //! Launch timer. virtual void start() = 0; //! Finish timer and wait for full stop. virtual void finish() = 0; //! Push delayed/periodic message to the timer queue. /*! * A timer can be deactivated later by using returned timer_id. */ virtual timer_id_t schedule( //! Type of message to be sheduled. const std::type_index & type_index, //! Mbox for message delivery. const mbox_t & mbox, //! Message to be sent. const message_ref_t & msg, //! Pause before first message delivery. std::chrono::steady_clock::duration pause, //! Period for message repetition. //! Zero value means single shot delivery. std::chrono::steady_clock::duration period ) = 0; //! Push anonymous delayed/periodic message to the timer queue. /*! * A timer cannot be deactivated later. */ virtual void schedule_anonymous( //! Type of message to be sheduled. const std::type_index & type_index, //! Mbox for message delivery. const mbox_t & mbox, //! Message to be sent. const message_ref_t & msg, //! Pause before first message delivery. std::chrono::steady_clock::duration pause, //! Period for message repetition. //! Zero value means single shot delivery. std::chrono::steady_clock::duration period ) = 0; /*! * \since * v.5.5.4 * * \brief Get statistics for run-time monitoring. */ virtual timer_thread_stats_t query_stats() = 0; }; //! Auxiliary typedef for timer_thread autopointer. using timer_thread_unique_ptr_t = std::unique_ptr< timer_thread_t >; // // timer_thread_factory_t // /*! * \since * v.5.5.0 * * \brief Type of factory for creating timer_thread objects. */ using timer_thread_factory_t = std::function< timer_thread_unique_ptr_t( error_logger_shptr_t ) >; /*! * \name Tools for creating timer threads. * \{ */ /*! * \since * v.5.5.0 * * \brief Create timer thread based on timer_wheel mechanism. * \note Default parameters will be used for timer thread. */ SO_5_FUNC timer_thread_unique_ptr_t create_timer_wheel_thread( //! A logger for handling error messages inside timer_thread. error_logger_shptr_t logger ); /*! * \since * v.5.5.0 * * \brief Create timer thread based on timer_wheel mechanism. * \note Parameters must be specified explicitely. */ SO_5_FUNC timer_thread_unique_ptr_t create_timer_wheel_thread( //! A logger for handling error messages inside timer_thread. error_logger_shptr_t logger, //! Size of the wheel. unsigned int wheel_size, //! A size of one time step for the wheel. std::chrono::steady_clock::duration granuality ); /*! * \since * v.5.5.0 * * \brief Create timer thread based on timer_heap mechanism. * \note Default parameters will be used for timer thread. */ SO_5_FUNC timer_thread_unique_ptr_t create_timer_heap_thread( //! A logger for handling error messages inside timer_thread. error_logger_shptr_t logger ); /*! * \since * v.5.5.0 * * \brief Create timer thread based on timer_heap mechanism. * \note Parameters must be specified explicitely. */ SO_5_FUNC timer_thread_unique_ptr_t create_timer_heap_thread( //! A logger for handling error messages inside timer_thread. error_logger_shptr_t logger, //! Initical capacity of heap array. std::size_t initial_heap_capacity ); /*! * \since * v.5.5.0 * * \brief Create timer thread based on timer_list mechanism. */ SO_5_FUNC timer_thread_unique_ptr_t create_timer_list_thread( //! A logger for handling error messages inside timer_thread. error_logger_shptr_t logger ); /*! * \} */ /*! * \name Standard timer thread factories. * \{ */ /*! * \since * v.5.5.0 * * \brief Factory for timer_wheel thread with default parameters. */ inline timer_thread_factory_t timer_wheel_factory() { // Use this trick because create_timer_wheel_thread is overloaded. timer_thread_unique_ptr_t (*f)( error_logger_shptr_t ) = create_timer_wheel_thread; return f; } /*! * \since * v.5.5.0 * * \brief Factory for timer_wheel thread with explicitely specified parameters. */ inline timer_thread_factory_t timer_wheel_factory( //! Size of the wheel. unsigned int wheel_size, //! A size of one time step for the wheel. std::chrono::steady_clock::duration granularity ) { // Use this trick because create_timer_wheel_thread is overloaded. timer_thread_unique_ptr_t (*f)( error_logger_shptr_t, unsigned int, std::chrono::steady_clock::duration ) = create_timer_wheel_thread; return std::bind( f, std::placeholders::_1, wheel_size, granularity ); } /*! * \since * v.5.5.0 * * \brief Factory for timer_heap thread with default parameters. */ inline timer_thread_factory_t timer_heap_factory() { // Use this trick because create_timer_heap_thread is overloaded. timer_thread_unique_ptr_t (*f)( error_logger_shptr_t ) = create_timer_heap_thread; return f; } /*! * \since * v.5.5.0 * * \brief Factory for timer_heap thread with explicitely specified parameters. */ inline timer_thread_factory_t timer_heap_factory( //! Initial capacity of heap array. std::size_t initial_heap_capacity ) { // Use this trick because create_timer_heap_thread is overloaded. timer_thread_unique_ptr_t (*f)( error_logger_shptr_t, std::size_t ) = create_timer_heap_thread; return std::bind( f, std::placeholders::_1, initial_heap_capacity ); } /*! * \since * v.5.5.0 * * \brief Factory for timer_list thread with default parameters. */ inline timer_thread_factory_t timer_list_factory() { return &create_timer_list_thread; } /*! * \} */ // // timer_manager_t // //! Timer manager interface. /*! * Interface for all implementations of timer_managers. * * Timer managers do not create externals threads and must not use * any mutexs/spinlocks inside. All work must be done only on the context * of the caller thread. * * \since * v.5.5.19 */ class SO_5_TYPE timer_manager_t { timer_manager_t( const timer_manager_t & ) = delete; timer_manager_t & operator=( const timer_manager_t & ) = delete; public: /*! * \brief An interface for collector of elapsed timers. * * Handling of elapsed timers in single threaded environments * differs from handling in multi-threaded environments. * When timers are handled by separated timer thread then that * thread can send delayed/periodic messages directly to target * mboxes. But in single-threaded environment this is impossible * if some receiver is bound to the default dispatcher. In that * case a deadlock is possible. * * Because of that timer_managers uses different schemes: main * working thread periodically calls process_expired_timers() method * and collects elapsed timers. Then the main working thread handles * all elapsed timers. * * To collect elapsed timer some container is necessary. * The class elapsed_timers_collector_t describes interface of * that container. */ class SO_5_TYPE elapsed_timers_collector_t { elapsed_timers_collector_t( const elapsed_timers_collector_t & ) = delete; elapsed_timers_collector_t & operator=( const elapsed_timers_collector_t & ) = delete; public : elapsed_timers_collector_t() noexcept = default; virtual ~elapsed_timers_collector_t() noexcept = default; //! Accept and store info about elapsed timer. virtual void accept( //! A type of message to be sent. std::type_index type_index, //! Target mbox for the message. mbox_t mbox, //! A message to be sent. message_ref_t msg ) = 0; }; timer_manager_t() noexcept = default; virtual ~timer_manager_t() noexcept = default; //! Translation of expired timers into message sends. virtual void process_expired_timers() = 0; //! Calculate time before the nearest timer (if any). /*! * Return timeout before the nearest timer or \c default_timeout * if there is no any timer. */ virtual std::chrono::steady_clock::duration timeout_before_nearest_timer( //! Default timeout if there is no any timer. std::chrono::steady_clock::duration default_timer ) = 0; //! Push delayed/periodic message to the timer queue. /*! * A timer can be deactivated later by using returned timer_id. */ virtual timer_id_t schedule( //! Type of message to be sheduled. const std::type_index & type_index, //! Mbox for message delivery. const mbox_t & mbox, //! Message to be sent. const message_ref_t & msg, //! Pause before first message delivery. std::chrono::steady_clock::duration pause, //! Period for message repetition. //! Zero value means single shot delivery. std::chrono::steady_clock::duration period ) = 0; //! Push anonymous delayed/periodic message to the timer queue. /*! * A timer cannot be deactivated later. */ virtual void schedule_anonymous( //! Type of message to be sheduled. const std::type_index & type_index, //! Mbox for message delivery. const mbox_t & mbox, //! Message to be sent. const message_ref_t & msg, //! Pause before first message delivery. std::chrono::steady_clock::duration pause, //! Period for message repetition. //! Zero value means single shot delivery. std::chrono::steady_clock::duration period ) = 0; /*! * \return true if there is no any pending timers. */ virtual bool empty() = 0; /*! * \since * v.5.5.4 * * \brief Get statistics for run-time monitoring. */ virtual timer_thread_stats_t query_stats() = 0; }; //! Auxiliary typedef for timer_manager autopointer. /*! * \since * v.5.5.19 */ using timer_manager_unique_ptr_t = std::unique_ptr< timer_manager_t >; // // timer_manager_factory_t // /*! * \brief Type of factory for creating timer_manager objects. * * \since * v.5.5.19 */ using timer_manager_factory_t = std::function< timer_manager_unique_ptr_t( error_logger_shptr_t, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > ) >; /*! * \name Tools for creating timer managers. * \{ */ /*! * \brief Create timer manager based on timer_wheel mechanism. * \note Default parameters will be used for timer manager. * * \since * v.5.5.19 */ SO_5_FUNC timer_manager_unique_ptr_t create_timer_wheel_manager( //! A logger for handling error messages inside timer_manager. error_logger_shptr_t logger, //! A collector for elapsed timers. outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector ); /*! * \since * v.5.5.0 * * \brief Create timer manager based on timer_wheel mechanism. * \note Parameters must be specified explicitely. */ SO_5_FUNC timer_manager_unique_ptr_t create_timer_wheel_manager( //! A logger for handling error messages inside timer_manager. error_logger_shptr_t logger, //! A collector for elapsed timers. outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector, //! Size of the wheel. unsigned int wheel_size, //! A size of one time step for the wheel. std::chrono::steady_clock::duration granuality ); /*! * \since * v.5.5.0 * * \brief Create timer manager based on timer_heap mechanism. * \note Default parameters will be used for timer manager. */ SO_5_FUNC timer_manager_unique_ptr_t create_timer_heap_manager( //! A logger for handling error messages inside timer_manager. error_logger_shptr_t logger, //! A collector for elapsed timers. outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector ); /*! * \since * v.5.5.0 * * \brief Create timer manager based on timer_heap mechanism. * \note Parameters must be specified explicitely. */ SO_5_FUNC timer_manager_unique_ptr_t create_timer_heap_manager( //! A logger for handling error messages inside timer_manager. error_logger_shptr_t logger, //! A collector for elapsed timers. outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector, //! Initical capacity of heap array. std::size_t initial_heap_capacity ); /*! * \since * v.5.5.0 * * \brief Create timer thread based on timer_list mechanism. */ SO_5_FUNC timer_manager_unique_ptr_t create_timer_list_manager( //! A logger for handling error messages inside timer_manager. error_logger_shptr_t logger, //! A collector for elapsed timers. outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > collector ); /*! * \} */ /*! * \name Standard timer manager factories. * \{ */ /*! * \since * v.5.5.0 * * \brief Factory for timer_wheel manager with default parameters. */ inline timer_manager_factory_t timer_wheel_manager_factory() { // Use this trick because create_timer_wheel_thread is overloaded. timer_manager_unique_ptr_t (*f)( error_logger_shptr_t, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > ) = create_timer_wheel_manager; return f; } /*! * \since * v.5.5.0 * * \brief Factory for timer_wheel manager with explicitely specified parameters. */ inline timer_manager_factory_t timer_wheel_manager_factory( //! Size of the wheel. unsigned int wheel_size, //! A size of one time step for the wheel. std::chrono::steady_clock::duration granularity ) { // Use this trick because create_timer_wheel_thread is overloaded. timer_manager_unique_ptr_t (*f)( error_logger_shptr_t, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t >, unsigned int, std::chrono::steady_clock::duration ) = create_timer_wheel_manager; return std::bind( f, std::placeholders::_1, std::placeholders::_2, wheel_size, granularity ); } /*! * \since * v.5.5.0 * * \brief Factory for timer_heap manager with default parameters. */ inline timer_manager_factory_t timer_heap_manager_factory() { // Use this trick because create_timer_heap_thread is overloaded. timer_manager_unique_ptr_t (*f)( error_logger_shptr_t, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t > ) = create_timer_heap_manager; return f; } /*! * \since * v.5.5.0 * * \brief Factory for timer_heap manager with explicitely specified parameters. */ inline timer_manager_factory_t timer_heap_manager_factory( //! Initial capacity of heap array. std::size_t initial_heap_capacity ) { // Use this trick because create_timer_heap_thread is overloaded. timer_manager_unique_ptr_t (*f)( error_logger_shptr_t, outliving_reference_t< timer_manager_t::elapsed_timers_collector_t >, std::size_t ) = create_timer_heap_manager; return std::bind( f, std::placeholders::_1, std::placeholders::_2, initial_heap_capacity ); } /*! * \since * v.5.5.0 * * \brief Factory for timer_list manager with default parameters. */ inline timer_manager_factory_t timer_list_manager_factory() { return &create_timer_list_manager; } /*! * \} */ namespace internal_timer_helpers { /*! * \brief Helper function for timer_thread creation. * * \since * v.5.5.0 */ inline timer_thread_unique_ptr_t create_appropriate_timer_thread( error_logger_shptr_t error_logger, const timer_thread_factory_t & user_factory ) { if( user_factory ) return user_factory( std::move( error_logger ) ); else return create_timer_heap_thread( std::move( error_logger ) ); } } /* namespace internal_timer_helpers */ #if defined( SO_5_MSVC ) #pragma warning(pop) #endif } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/transformed_message.hpp000066400000000000000000000173701523065210100222440ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Stuff related to template type transformed_message. * * \since v.5.8.1 */ #if !defined( SO_5_TRANSFORMED_MESSAGE_HPP ) #define SO_5_TRANSFORMED_MESSAGE_HPP #include #include #include namespace so_5 { namespace impl::transformed_message_impl { // // message_maker // /*! * \brief A helper function to call appropriate constructor of * resulting type in dependence of message or signal. * * \tparam is_signal true if Msg is a signal. * \tparam Result Result type object of that will be created by make() method. * \tparam Msg Type of message to be instantiated. * \tparam Args Type of parameters to message constructor. * * \since v.5.5.4, v.5.8.1 */ template< bool is_signal, typename Result, typename Msg, typename... Args > struct message_maker { //! Create a message instance. static Result make( mbox_t mbox, Args &&... args ) { return Result( std::move( mbox ), so_5::details::make_message_instance< Msg >( std::forward( args )... ) ); } }; /*! * \brief A specialization of signal. * * \since v.5.5.4, v.5.8.1 */ template< typename Result, typename Msg > struct message_maker< true, Result, Msg > { static Result make( mbox_t mbox ) { return Result( std::move( mbox ) ); } }; } /* namespace impl::transformed_message_impl */ // // transformed_message_t // /*! * \brief A result of message transformation. * * This template class is intended to store the result of * message transformation and redirection procedure performed by * so_5::agent_t::limit_then_transform, so_5::bind_transformer and * so_5::msinks::transform_then_redirect. * * An instance of this class is usually created by the static member * function make() or by a free function so_5::make_transformed. * * \tparam Msg Type of result (transformed) message. If `Msg` is the * message type, then `Msg`, `so_5::immutable_msg` or * `so_5::mutable_msg` can be used. If `Msg` is the signal type then * `Msg` or `so_5::immutable_msg` is allowed. * * \since v.5.8.1 */ template< typename Msg > class transformed_message_t { public : //! Initializing constructor for the case when Msg is a message type. transformed_message_t( //! Message box to which transformed message to be sent. mbox_t mbox, //! New message instance. std::unique_ptr< typename message_payload_type< Msg >::envelope_type > msg ) : m_mbox( std::move( mbox ) ) { ensure_message_with_actual_data( msg.get() ); m_message = message_ref_t( msg.release() ); } //! Initializing constructor for the case when Msg is a signal type. transformed_message_t( //! Message box to which signal to be sent. mbox_t mbox ) : m_mbox( std::move( mbox ) ) { ensure_signal< Msg >(); } //! Initializing constructor for the case when Msg is already //! exists as a %message_holder_t instance. /*! * \since v.5.8.1 */ template< typename Msg_In_Holder, message_ownership_t Ownership > transformed_message_t( mbox_t mbox, message_holder_t< Msg_In_Holder, Ownership > msg_holder ) : m_mbox{ std::move(mbox) } , m_message{ msg_holder.make_reference() } { static_assert( std::is_same_v< typename message_payload_type< Msg >::envelope_type, typename message_payload_type< Msg_In_Holder >::envelope_type >, "message_holder has to hold a message of the same type" ); } //! Destination message box. const mbox_t & mbox() const { return m_mbox; } //! Type of the transformed message. std::type_index msg_type() const { return message_payload_type< Msg >::subscription_type_index(); } //! Instance of transformed message. /*! * \note Will be nullptr for signal. */ const message_ref_t & message() const { return m_message; } //! A helper method for transformed_message construction. template< typename... Args > static transformed_message_t< Msg > make( mbox_t mbox, Args &&... args ) { return impl::transformed_message_impl::message_maker< is_signal< Msg >::value, transformed_message_t, Msg, Args... >::make( std::move( mbox ), std::forward( args )... ); } private : //! Destination message box. mbox_t m_mbox; //! Instance of transformed message. /*! * \note Will be nullptr for signal. */ message_ref_t m_message; }; // // make_transformed // /*! * \brief Helper function for creation of an instance of %transformed_message_t. * * It's just a simple way of calling transformed_message_t::make(). * * Usage example: * \code * // For a case of messages. * struct my_source_msg { ... }; * struct my_result_msg { ... }; * * so_5::bind_transformer< my_source_msg >(binding, src_mbox, * [dest_mbox](const auto & msg) { * // We can make a mutable message. * return so_5::make_transformed< so_5::mutable_msg< my_result_msg > >( * dest_mbox, // The first parameter is the destination mbox. * ... // Remaining parameters will be forwarded to * // the so_5::transformed_message_t< so_5::mutable_msg > constructor. * ); * }); * * // For a case of signals. * struct my_source_sig { ... }; * struct my_result_sig { ... }; * * so_5::bind_transformer< my_source_sig >(binding, src_mbox, * [dest_mbox]() { // NOTE: no args for transformer lambda! * return so_5::make_transformed< my_result_sig >( * dest_mbox // The first parameter is the destination mbox. * // No more parameters for a signal. * ); * }); * \endcode * * \tparam Msg type of the transformed (result) message. * * \tparam Args type of arguments to be passed to the * transformed_message_t::make(). If \a Msg is a signal type * then \a Args has to be an empty type list. */ template< typename Msg, typename... Args > [[nodiscard]] transformed_message_t< Msg > make_transformed( //! The destination mbox. mbox_t mbox, //! Parameters to be used for constructing an instance of \a Msg type. //! This can be an empty parameters list. Args &&... args ) { return transformed_message_t< Msg >::make( std::move( mbox ), std::forward( args )... ); } /*! * \brief Helper function for creation of an instance of %transformed_message_t. * * It's just a simple way of calling transformed_message_t::make(). * * \note * This function has to be used when the result message is * represented as so_5::message_holder_t. * * Usage example: * \code * // For a case of messages. * struct my_source_msg { ... }; * struct my_result_msg { ... }; * * so_5::bind_transformer< my_source_msg >(binding, src_mbox, * [dest_mbox](const auto & msg) { * // We can make a mutable message. * auto result = so_5::message_holder_t< so_5::mutable_msg >::make( * ... // Parameters to be used for my_result_msg construction. * ); * return so_5::make_transformed( * dest_mbox, // The first parameter is the destination mbox. * std::move(result) // The transformed message. * ); * }); * \endcode * * \attention * This function can't be used for signals because signals are not * supported by so_5::message_holder_t. * * \tparam Msg type of the transformed (result) message. * * \tparam Ownership ownership of message instance in \a msg_holder (see * so_5::message_ownership_t for more details). */ template< typename Msg, message_ownership_t Ownership > [[nodiscard]] transformed_message_t< Msg > make_transformed( //! The destination mbox. mbox_t mbox, //! Already constructed message. //! It is expected that \a msg_holder isn't an empty holder. message_holder_t< Msg, Ownership > msg_holder ) { return transformed_message_t< Msg >{ std::move( mbox ), std::move( msg_holder ) }; } } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/types.hpp000066400000000000000000000076571523065210100173670ustar00rootroot00000000000000/* SObjectizer 5. */ /*! \file \brief Various typedefs. */ #if !defined( SO_5_TYPES_HPP ) #define SO_5_TYPES_HPP #include #include // It is necessary that version.hpp will be almost always automatically // included. To do that we include it in that header file because // that file seems to be necessary for all SObjectizer's definintions. #include namespace so_5 { //! Atomic counter type. using atomic_counter_t = std::atomic_ulong; //! Atomic flag type. using atomic_flag_t = std::atomic_ulong; //! A type for mbox indentifier. using mbox_id_t = unsigned long long; /*! * \brief Default value for null mbox_id. * \since * v.5.5.4.1 */ inline mbox_id_t null_mbox_id() { return 0ull; } /*! * \brief Thread safety indicator. * \since * v.5.4.0 */ enum class thread_safety_t : std::uint8_t { //! Not thread safe. unsafe = 0, //! Thread safe. safe = 1 }; /*! * \brief Shorthand for thread unsafety indicator. * \since * v.5.4.0 */ const thread_safety_t not_thread_safe = thread_safety_t::unsafe; /*! * \brief Shorthand for thread safety indicator. * \since * v.5.4.0 */ const thread_safety_t thread_safe = thread_safety_t::safe; /*! * \brief Values for dispatcher's work thread activity tracking. */ enum class work_thread_activity_tracking_t { //! Tracking mode is specified elsewhere. unspecified, //! Tracking is disabled. off, //! Tracking is enabled. on }; // // message_mutability_t // /*! * \brief A enum with variants of message mutability or immutability. * * \since * v.5.5.19 */ enum class message_mutability_t { immutable_message, mutable_message }; // // message_kind_t // /*! * \brief A enum with variants of message kinds. * * \since * v.5.5.23 */ enum class message_kind_t { //! Message is a signal. It means there is no data associated //! with the message instance. signal, //! Message is a classical message. It means that message is //! an instance of class derived from message_t. classical_message, //! Message is an user type message. user_type_message, //! Message is an envelope with some other message inside. enveloped_msg }; // // coop_id_t // /*! * \brief ID of cooperation. * * \since * v.5.6.0 */ using coop_id_t = std::uint_fast64_t; // // event_handler_kind_t // /*! * \brief Kind of an event handler. * * Since v.5.7.0 event handlers of an agent are devided into the following * categories: * * - final handler. That is an ordinary handler that takes a message and * handles it. In the case of an enveloped message a final handler should * take the payload of a message; * - intermediate handler. That is a special handler that doesn't handle * a message by itself but delegates the handling to some other event * handler or just suppresses the message. In the case of enveloped message * an intermediate handler should take the whole envelope. * * \since * v.5.7.0 */ enum class event_handler_kind_t : char { //! Ordinary, final event handler. //! That event handler should take the payload from an eveloped message. final_handler, //! Intermediate event handler. //! Doesn't handle a message itself but delegates actual processing //! to some other event handler. Or just skips the processing of //! the message. //! That event handler should take the whole envelope. intermediate_handler }; /*! * \brief Possible modes of message/signal delivery. * * \since v.5.8.0 */ enum class message_delivery_mode_t { //! Ordinary delivery. The send operation can block (for //! example on an attempt to send a message to a full mchain). ordinary, //! Delivery that prohibit blocking. For example a delivery //! of a delayed/periodic can't block the current thread //! (because it's the timer thread and the timer thread //! can't be blocked). //! //! NOTE. The current version also prohibit throwing of //! exceptions. nonblocking }; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/unique_subscribers_mbox.hpp000066400000000000000000000227141523065210100231530ustar00rootroot00000000000000/*! * \file * \brief Implementation of unique_subscribers mbox. * * \since so5extra-1.5.0, v.5.8.0 */ #if !defined( SO_5_UNIQUE_SUBSCRIBERS_MBOX_HPP ) #define SO_5_UNIQUE_SUBSCRIBERS_MBOX_HPP #include #include #include #include #include #include #include #include #include namespace so_5 { namespace unique_subscribers_mbox_impl { // // subscriber_info_t // /*! * \brief Description of a subscriber. */ using subscriber_info_t = so_5::impl::local_mbox_details::subscription_info_with_sink_t; // // data_t // /*! * \brief A coolection of data required for local mbox implementation. * * \since so5extra-1.5.0 */ struct data_t { data_t( mbox_id_t id, environment_t & env ) : m_id{ id } , m_env{ env } {} //! ID of this mbox. const mbox_id_t m_id; //! Environment for which the mbox is created. environment_t & m_env; /*! * \brief Map from message type to subscribers. */ using messages_table_t = std::map< std::type_index, subscriber_info_t >; //! Map of subscribers to messages. messages_table_t m_subscribers; }; // // actual_mbox_t // //! Actual implementation of unique_subscribers mbox. /*! * \tparam Mutex type of lock to be used for thread safety. * \tparam Tracing_Base base class with implementation of message * delivery tracing methods. * * \since so5extra-1.5.0, v.5.8.0 */ template< typename Mutex, typename Tracing_Base > class actual_mbox_t final : public abstract_message_box_t , private ::so_5::details::lock_holder_detector< Mutex >::type , private data_t , private Tracing_Base { public: template< typename... Tracing_Args > actual_mbox_t( //! ID of this mbox. mbox_id_t id, //! Environment for which the mbox is created. outliving_reference_t< environment_t > env, //! Optional parameters for Tracing_Base's constructor. Tracing_Args &&... args ) : data_t{ id, env.get() } , Tracing_Base{ std::forward< Tracing_Args >(args)... } {} mbox_id_t id() const override { return this->m_id; } void subscribe_event_handler( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) override { insert_or_modify_subscriber( msg_type, subscriber, [&subscriber] { return subscriber_info_t{ subscriber }; }, [&subscriber]( subscriber_info_t & info ) { info.set_sink( subscriber ); } ); } void unsubscribe_event_handler( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) noexcept override { modify_and_remove_subscriber_if_needed( msg_type, subscriber, []( subscriber_info_t & info ) { info.drop_sink(); } ); } std::string query_name() const override { std::ostringstream s; s << ""; return s.str(); } mbox_type_t type() const override { return mbox_type_t::multi_producer_single_consumer; } void do_deliver_message( message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) override { typename Tracing_Base::deliver_op_tracer tracer{ *this, // as Tracing_base *this, // as abstract_message_box_t "deliver_message", delivery_mode, msg_type, message, redirection_deep }; do_deliver_message_impl( tracer, delivery_mode, msg_type, message, redirection_deep ); } void set_delivery_filter( const std::type_index & msg_type, const delivery_filter_t & filter, abstract_message_sink_t & subscriber ) override { insert_or_modify_subscriber( msg_type, subscriber, [&] { return subscriber_info_t{ subscriber, filter }; }, [&filter]( subscriber_info_t & info ) { info.set_filter( filter ); } ); } void drop_delivery_filter( const std::type_index & msg_type, abstract_message_sink_t & subscriber ) noexcept override { modify_and_remove_subscriber_if_needed( msg_type, subscriber, []( subscriber_info_t & info ) { info.drop_filter(); } ); } environment_t & environment() const noexcept override { return m_env; } private : template< typename Info_Maker, typename Info_Changer > void insert_or_modify_subscriber( const std::type_index & msg_type, abstract_message_sink_t & subscriber, Info_Maker maker, Info_Changer changer ) { this->lock_and_perform( [&] { auto it = this->m_subscribers.find( msg_type ); if( it == this->m_subscribers.end() ) { // There isn't such message type yet. m_subscribers.emplace( msg_type, maker() ); } else { // If subscription or delivery filter is already set by // a different agent then we can't continue. if( it->second.sink_pointer() != std::addressof(subscriber) ) SO_5_THROW_EXCEPTION( rc_evt_handler_already_provided, std::string{ "subscription is already exists " "for message type '" } + msg_type.name() + "'" ); else changer( it->second ); } } ); } template< typename Info_Changer > void modify_and_remove_subscriber_if_needed( const std::type_index & msg_type, abstract_message_sink_t & subscriber, Info_Changer changer ) { this->lock_and_perform( [&] { auto it = this->m_subscribers.find( msg_type ); if( it != this->m_subscribers.end() ) { auto & subscriber_info = it->second; // Skip all other actions if the subscription is // made for a different agent. if( std::addressof(subscriber) == subscriber_info.sink_pointer() ) { // Subscriber is found and must be modified. changer( subscriber_info ); // If info about subscriber becomes empty after // modification then subscriber info must be removed. if( subscriber_info.empty() ) this->m_subscribers.erase( it ); } } } ); } void do_deliver_message_impl( typename Tracing_Base::deliver_op_tracer const & tracer, message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) { this->lock_and_perform( [&] { auto it = this->m_subscribers.find( msg_type ); if( it != this->m_subscribers.end() ) { do_deliver_message_to_subscriber( it->second, tracer, delivery_mode, msg_type, message, redirection_deep ); } else tracer.no_subscribers(); } ); } void do_deliver_message_to_subscriber( const subscriber_info_t & subscriber_info, typename Tracing_Base::deliver_op_tracer const & tracer, message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const message_ref_t & message, unsigned int redirection_deep ) const { const auto delivery_status = subscriber_info.must_be_delivered( message, []( const message_ref_t & msg ) -> message_t & { return *msg; } ); if( delivery_possibility_t::must_be_delivered == delivery_status ) { using namespace so_5::message_limit::impl; subscriber_info.sink_reference().push_event( this->m_id, delivery_mode, msg_type, message, redirection_deep, tracer.overlimit_tracer() ); } else tracer.message_rejected( subscriber_info.sink_pointer(), delivery_status ); } }; } /* namespace unique_subscribers_mbox_impl */ // // make_unique_subscribers_mbox // /*! * \brief Factory function for creation of a new instance of unique_subscribers * mbox. * * Usage examples: * * Create a mbox with std::mutex as Lock_Type (this mbox can safely be * used in multi-threaded environments): * \code * so_5::environment_t & env = ...; * auto mbox = so_5::make_unique_subscribers_mbox(env); * \endcode * * Create a mbox with so_5::null_mutex_t as Lock_Type (this mbox can only * be used in single-threaded environments): * \code * so_5::environment_t & env = ...; * auto mbox = so_5::make_unique_subscribers_mbox(env); * \endcode * * \tparam Lock_Type type of lock to be used for thread safety. It can be * std::mutex or so_5::null_mutex_t (or any other type which can be used * with std::lock_quard). * * \since v.5.8.0 */ template< typename Lock_Type = std::mutex > [[nodiscard]] mbox_t make_unique_subscribers_mbox( so_5::environment_t & env ) { return env.make_custom_mbox( [&]( const mbox_creation_data_t & data ) { mbox_t result; if( data.m_tracer.get().is_msg_tracing_enabled() ) { using T = unique_subscribers_mbox_impl::actual_mbox_t< Lock_Type, ::so_5::impl::msg_tracing_helpers::tracing_enabled_base >; result = mbox_t{ new T{ data.m_id, data.m_env, data.m_tracer } }; } else { using T = unique_subscribers_mbox_impl::actual_mbox_t< Lock_Type, ::so_5::impl::msg_tracing_helpers::tracing_disabled_base >; result = mbox_t{ new T{ data.m_id, data.m_env } }; } return result; } ); } } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/version.dox000066400000000000000000001117011523065210100176750ustar00rootroot00000000000000/*! \page so_5__version so_5: Version History \section so_5__5_8_5 5.8.5 "San Fransisco" Implementation of so_5::state_t::time_limit has been changed. Now it is more effective and doesn't recreate a special mbox for internal delayed messages. A new method so_5::agent_t::so_drop_all_subscriptions_and_filters(). It's now possible to skip all pending demands for agent when deregistration of agent's coop starts. A new agent tuning option so_5::skip_demands_on_dereg has to be used. A new method so_5::mchain_params_t::empty_notificator(). It allows to do something when a mchain becomes empty. A new field so_5::stats::activity_stats_t::m_current_activity_time. When it isn't nullopt it contains a duration of the current activity (for example total execution time of the current event handler). The result of so_5::receive() can now be deconstructed by structured binding, for example: \code const auto [extracted, handled, status] = so_5::receive(...); \endcode \section so_5__5_8_4 5.8.4 "Uturuncu" More advanced version of so_5::experimental::testing::receives(). New method so_5::experimental::testing::scenario_proxy_t::has_stored_state_name(). New method so_5::experimental::testing::scenario_proxy_t::has_stored_msg_inspection_result(). The `no_wait_on_empty` and `empty_timeout` parameters are now handled in so_5::receive() and so_5::select() if `total_time` is specified. The previous versions of SObjectizer ignored `no_wait_on_empty`/`empty_timeout` if `total_time` was set. \section so_5__5_8_3 5.8.3 "Volcan Tacora" A fix for a case when message limits are specified for an agent and time_limit is used for the agent's state(s). See the issue 75 for more details. The lock_shared/unlock_shared methods have been added to so_5::null_mutext_t class. Now the so_5::null_mutex_t class can be used with std::shared_lock. New facilities for testing of agents: so_5::experimental::testing::inspect_msg(), so_5::experimental::testing::receives(), and so_5::experimental::testing::wait_event_handler_completion(). New facilities for message delivery tracing: so_5::msg_tracing::individual_trace() and so_5::msg_tracing::make_individual_trace_filter(). \section so_5__5_8_2 5.8.2 "Sabancaya" New synchronous mode for execution of init-function in the constructor of so_5::wrapped_env_t. A new type of subscription storage: flat-set based storage. It can be specified by using a new so_5::flat_set_based_subscription_storage_factory() function. A new method so_5::environment_params_t::default_subscription_storage_factory(). New methods so_5::stats::prefix_t::as_string_view() and so_5::stats::suffix_t::as_string_view(). Several methods of so_5::stats::prefix_t and so_5::stats::suffix_t are now constexpr. A possibility to set an optional name for agent. New types so_5::name_for_agent_t, so_5::agent_identity_t. New methods so_5::agent_t::name_for_agent(), so_5::agent_t::so_agent_name(). \section so_5__5_8_1 5.8.1 "Alpamayo" New functions so_5::msinks::transform_then_redirect() and new helpers so_5::bind_transformer(). New methods so_5::agent_t::so_this_agent_disp_binder() and so_5::agent_t::so_this_coop_disp_binder(). BugFix: the so_5::agent_t::limit_then_transform() now can be used with mutable messages. \section so_5__5_8_0 5.8.0 "Nevado de Chani" \attention This version breaks compatibility with SObjectizer-5.7 in several places. An introduction of so_5::abstract_message_sink_t interface. Format of so_5::abstract_message_box_t::subscribe_event_handler() changed. Method so_5::abstract_message_box_t::unsubscribe_event_handler() instead of unsubscribe_event_handlers in previous versions. Several method of so_5::abstract_message_box_t (like unsubscribe_event_handler() and drop_delivery_filter()) are now noexcept. New helpers so_5::single_sink_binding_t, so_5::multi_sink_binding_t and so_5::wrap_to_msink(). New method so_5::environment_t::introduce_named_mbox(). Unique-subcribers mbox is now a part of SObjectizer core (it's borrowed from companion so5extra project). See so_5::make_unique_subscribers_mbox() helper function for more details. There were several member functions `swap` in different classes. Since v.5.8 they have been transformed into more standard-like friend freestanding `swap` functions (receive two references as parameters and are noexcept). New method so_5::agent_t::so_low_level_exec_as_event_handler(). The standard implementations of environment infrastructures are no longer use dynamic memory allocation to handle the final deregistration of coops. An intrusive list of coop_t objects is now used to hold a list of coops ready to be removed. Changed the interface so_5::event_queue_t: new methods so_5::event_queue_t::push_evt_start() and so_5::event_queue_t::push_evt_finish() are introduced. Two new dispatchers added: so_5::disp::nef_one_thread and so_5::disp::nef_thread_pool. The so_5::disp::nef_one_thread dispatcher can be used as the default dispatcher if the standard multi-theaded environment-infrastructure is used. The so_5::environment_params_t::default_disp_params(void) getter now returns a sum type (parameters for so_5::disp::one_thread or so_5::disp::nef_one_thread dispatchers). \section so_5__5_7_5 5.7.5 "Licancabur" The so_5::abstract_message_box_t::environment() method for MPSC mboxes now returns a valid reference to the environment even if the owner of the MPSC mbox has already deregistered (note that mbox can't be used if the environment is stopped). Changed the order in which of agents and disp_binders are destroyed: now disp_binders are destroyed after the destruction of agents. This was achieved by moving the ownership of the disp_binder to an agent. \section so_5__5_7_4 5.7.4 "Cerro Miniques" The standard MPSC mboxes now support delivery filters. Delivery filters for mutable messages should be set via a new method so_5::agent_t::so_set_delivery_filter_for_mutable_msg(). A posibility to set a custom mbox as the direct mbox for an agent added. See so_5::agent_t::custom_direct_mbox_factory(). \section so_5__5_7_3 5.7.3 "Cotopaxi" Old forms of so_5::abstract_message_chain_t::close(), so_5::close_drop_content() and so_5::close_retain_content() are deprecated. New forms that accept so_5::exceptions_enabled/so_5::terminate_if_throws have to be used now. A user can now introduce own worker threads to SObjectizer dispatchers via implementing new interfaces so_5::disp::abstract_work_thread_t and so_5::disp::abstract_work_thread_factory_t. New method so_5::agent_t::so_deactivate_agent deactivates the agent: - drops all agent's subscriptions (including deadletter handlers) and delivery filters; - switches the agent to a special state in that the agent does nothing and just waits for the deregistration. Being in a deactivated state an agent can't make new subscriptions and change its state. Since v.5.7.3 actions performed in a thread-safe event-handler are checked regardless of the dispatcher type. It means that the state of an agent can't be changed in thread-safe handler even if the agent is bound to one_thread dispatcher. Changes to agent's subscriptions can't be made in a thread-safe event-handler too. \section so_5__5_7_2 5.7.2 "Putana" `operator==` and `operator!=` added to so_5::coop_handle_t. Fixes for several issues reported by users. \section so_5__5_7_1 5.7.1 "Nevados Jotabeche" New function template so_5::make_agent_ref(). A possibility to define default (generic) message limits. For example: \code class demo final : public so_5::agent_t { public: demo(context_t ctx) : so_5::agent_t{ctx + limit_then_drop(100u) + limit_then_abort(10u) // That limit will be used for all other messages. + limit_then_drop(50u) } {} \endcode Fix for supported formats of on_enter/on_exit handlers for agent states. Now `noexcept` methods/lambdas can be used as those handlers. Changes in the procedure of message delivery to MPSC mboxes. Now the presence of a subscription to the message type is performed during `send` operation. If there is no such subscriptions the message isn't stored into the receiver's queue. New examples: default_message_limits, make_agent_ref, named_dispatchers_layer. \section so_5__5_7_0 5.7.0 "Volcan Ollague" Support of so_5::send_case() in so_5::select(). There is no more so_5::case_() function, so_5::receive_case() should be used instead. so_5::select() returns so_5::mchain_select_result_t instead of so_5::mchain_receive_result_t. The behavior of enveloped messages with `transfer_to_state()` and `suppress()` helpers. Since v.5.7.0 an envelope doesn't receive a notification if the payload is ignoread as the result of `transfer_to_state()`/`suppress()` actions. New enum so_5::event_handler_kind_t is introduced and the format of so_5::agent_t::so_create_event_subscription() changed. \section so_5__5_6_1 5.6.1 "Cerro Mururata" Extensible-select added. See functions so_5::make_extensible_select() and so_5::add_select_cases() for more details. Implementation of prepared-select changed. The parallel/nested calls to select() with prepared-select are now checked and an exception is thrown if such call is detected. \section so_5__5_6_0 5.6.0 "Volcan San Jose" A list of changes in v.5.6.0 can be found here. \attention This version is not compatible with SObjectizer-5.5. \note Since v.5.6.0 some descriptions made for previous versions of SObjectizer can contain obsolete information. \section so_5__5_24_1 5.5.24.1 "El Misti" Flags -fvisibitity=hidden and -fvisibility-inlines-hidden are now used for Unix builds (Linux, FreeBSD, MacOS). This reduces count of public symbols in SObjectizer's .so library. \section so_5__5_24 5.5.24 "El Misti" Mechanism of so_5::event_queue_hook_t added. See \ref so_5_5_24__event_queue_hook for more details. The first version of experimental support for unit-testing of agents added. \section so_5__5_23 5.5.23 "Volcan Cayambe" Some internal refactoring and optimizations. so_5::message_kind_t, so_5::message_t::kind_t, so_5::message_t::so5_message_kind() and related stuff are introduced. A new namespace so_5::enveloped_msg is introduced. See \ref so_5_5_23__enveloped_msg for more details. so_5::abstract_message_box_t::do_deliver_enveloped_msg() introduced. so_5::abstract_message_box_t::delegate_deliver_message_from_timer() introduced. \section so_5__5_22_1 5.5.22.1 "Pico Cristobal Colon" An error code so_5::rc_transfer_to_state_loop added and run-time detection of transfer_to_state loops is implemented. \section so_5__5_22 5.5.22 "Pico Cristobal Colon" A new experimental mechanism for filtering msg_tracing messages is introduced. It includes new types so_5::msg_tracing::trace_data_t, so_5::msg_tracing::filter_t (and some more related types in so_5::msg_tracing namespace), new methods so_5::environment_t::change_message_delivery_tracer_filter() and so_5::environment_params_t::message_delivery_tracer_filter(), and new free functions like so_5::msg_tracing::make_filter(), so_5::msg_tracing::make_disable_all_filter(), so_5::msg_tracing::make_enable_all_filter(). Free functions can be used as handlers for so_5::receive() and so_5::select() functions. For example it is possible to write something like: \code void first_handler(const some_msg & msg) {...} void second_handler(another_msg msg) {...} void third_handler(so_5::mhood_t cmd) {...} void forth_handler(so_5::mutable_mhood_t cmd) {...} ... auto ch = so_5::create_mchain(env); so_5::receive(ch, so_5::infinite_wait, first_handler, second_handler, third_handler, forth_handler); \endcode Method so_5::agent_t::so_current_state() now returns expected value in on_enter/on_exit state's handlers. For example, the following asserts will work correctly since v.5.5.22: \code class demo final : public so_5::agent_t { state_t st_1{ this }; state_t st_1_1{ initial_substate_of{st_1} }; state_t st_1_2{ substate_of{st_1}}; ... virtual void so_define_agent() override { st_1.on_enter([this]{ assert(st_1 == so_current_state()); ... }); st_1_1.on_enter([this]{ assert(st_1_1 == so_current_state()); ... }); ... } }; \endcode New examples selective_msg_tracing and nohandler_msg_tracing added. \section so_5__5_21 5.5.21 "Cerro de La Isla" Deadletter handlers are introduced. See \ref so_5_5_21__deadletter_handlers for more details. Negative values of `pause` and `period` for so_5::send_delayed(), so_5::send_periodic(), so_5::environment_t::schedule_timer() and so_5::environment_t::single_timer() are now explicitely disabled. Values of `pause` and `period` are checked at run-time and an exception is thrown if a value is negative. There are new error codes so_5::rc_negative_value_for_pause and so_5::rc_negative_value_for_period for such cases. \section so_5__5_20_1 5.5.20.1 "Nevado Aguas Blancas" Operator so_5::state_t::operator!=() added. `noexcept` marks are set for so_5::intrusive_ptr_t and so_5::timer_id_t classes. \section so_5__5_20 5.5.20 "Nevado Aguas Blancas" Const methods of agents can now be used as event handlers. Const methods of agents can now be used as on_enter/on_exit handlers for agent's states. Support for CMake has been enhanced. New overloads for so_5::send_delayed(target, pause, mhood) and so_5::send_periodic(target, pause, period, mhood) have been added. Timers' implementation has been switched to timertt-1.2.2. If a compiler supports `[[nodiscard]]` attribute then this attribute will be used for `so_5::send_periodic` functions. \section so_5__5_19_5 5.5.19.5 "Nevado de Poquis" Now it is possible to check presense of a subscription to a message. See \ref so_5_5_19__has_subscription for more details. New methods are introduced: so_5::agent_t::so_has_subscription(), so_5::state_t::has_subscription(), so_5::state_t::drop_subscription(). Fix implementation of so_5::agent_t::so_drop_subscription() and so_5::agent_t::so_drop_subscription_for_all_states() for cases when an event-handler returns non-void. \section so_5__5_19_4 5.5.19.4 "Nevado de Poquis" Fixes for warnings detected by clang-5.0.0 on high warning levels. \section so_5__5_19_3 5.5.19.3 "Nevado de Poquis" Some version-specific macros are introduced: SO_5_VERSION_MAJOR, SO_5_VERSION_MINOR, SO_5_VERSION_PATCH, SO_5_VERSION, SO_5_VERSION_MAKE. Method make_trace in msg_tracing-implementation tracers is now public. It simplifies implementation of custom mboxes. \section so_5__5_19_2 5.5.19.2 "Nevado de Poquis" A new entity so_5::stop_guard_t is added. See \ref so_5_5_19__stop_guards for more details. New methods: so_5::environment_t::setup_stop_guard() and so_5::environment_t::remove_stop_guard(). New example stop_guard is added. Method so_5::environment_t::make_custom_mbox() and new type so_5::mbox_creation_data_t. See \ref so_5_5_19__custom_mbox for more details. Class so_5::null_mutex_t added. Helper template so_5::details::lock_holder_detector added to simplify development of SObjectizer's extensions and additional libraries. \section so_5__5_19 5.5.19 "Nevado de Poquis" New format of send-functions, see \ref so_5_5_19__new_format_of_send_functions for more details. Mutable messages are supported now. See \ref so_5_5_19__mutable_messages for more details. An ability to run SObjectizer Environment in a single threaded mode is implemented. See \ref so_5_5_19__environment_infrastructures for more details. Function so_5::create_default_disp_binder() is deprecated. New function so_5::make_default_disp_binder() must be used instead. New examples: hello_world_simple_not_mtsafe, modify_resend_as_immutable, mutable_msg_agents, wrapped_env_demo_3, two_handlers. \section so_5__5_18 5.5.18 "Nevado Pisco" New behavior for delivery of delayed and periodic messages to overloaded message chains. See \ref so_5_5_18__overloaded_mchains_and_timers for details. New messages so_5::stats::messages::distribution_started and so_5::stats::messages::distribution_finished. See also \ref so_5_5_18__new_msg_distrib_started_finished for more details. Support for global lock_factories. Class so_5::queue_locks_defaults_manager_t and method so_5::environment_params_t::queue_locks_defaults_manager(). See also \ref so_5_5_18__global_lock_factories for more details. Nested state switch is explicitely disabled. See \ref so_5_5_18__nested_state_switch for more details. Work thread activity tracking. See \ref so_5_5_18__work_thread_activity_tracking for more details. \section so_5__5_17 5.5.17 "Nevado del Huila" SObjectizer can be built as shared or static library. See \ref so_5_5_17__so_as_static_lib for more details. New functions so_5::prepare_receive(), so_5::receive(const prepared_receive_t< HANDLERS_COUNT > &), so_5::prepare_select(), so_5::select(const prepared_select_t< CASES_COUNT > &). See \ref so_5_5_17__prepare_receive for more details. New method so_5::mchain_bulk_processing_params_t::on_close() to specify a handler for chain close event. See \ref so_5_5_17__on_close_handlers_for_adv_receive for more details. Helper class so_5::mchain_master_handle_t added. See \ref so_5_5_17__mchain_master_handle for more details. \section so_5__5_16_1 5.5.16.1 Support for GCC v.4.8.2. \section so_5__5_16 5.5.16 "Cerro Barroso" New method so_5::disp::mpmc_queue_traits::queue_params_t::next_thread_wakeup_threshold() for tuning the behaviour of thread_pool and adv_thread_pool dispatchers. Helper functions so_5::create_mchain() added. They simplify creation of size-limited and size-unlimited mchains. Helper function so_5::auto_join() added. It helps to work with raw std::thread. Helper function so_5::auto_close_mchains() added. There are also helper functions so_5::auto_close_retain_content() and so_5::auto_close_drop_content(). New functions so_5::select() for handling messages from several mchains added. New example *mchain_select* added. mchain can now be used as MPMC chains. It means that several consumer threads can call so_5::receive() on the same mchain. New example *mchain_multi_consumers* added. New methods so_5::coop_t::size(), so_5::coop_t::capacity() and so_5::coop_t::reserve(). \section so_5__5_15 5.5.15 "Cerro Paroma" A possibility to implement agent as a hierachical state machine added. See \ref so_5_5__in_depth__agent_states for more details. New examples added: *blinking_led*, *intercom_statechart* and *state_deep_history*. \section so_5__5_14 5.5.14 "Nevado de Antisana" Unification of event handler formats for method-style and lambda-style handlers. See \ref so_5_5_14__mhood for more details. New method so_5::mchain_receive_params_t::no_wait_on_empty(). New example producer_consumer_mchain added. \section so_5__5_13 5.5.13 "Cerro Dona Ana" Breaking change: namespace so_5::rt is deprecated! See \ref so_5_5_13__so_rt_ns_deprecated for more details. Functions send_delayed_to_agent and send_periodic_to_agent are deprecated. See \ref so_5_5_13__timed_send_deprecated for more details. Another constructor implemented for class so_5::wrapped_env_t. It receives just one argument: an instance of so_5::environment_params_t. Type name so_5::agent_t::state_t is added. It just an alias for so_5::state_t. Methods so_5::environment_t::create_local_mbox() are deprecated. Methods so_5::environment_t::create_mbox() must be used instead. A new mechanism named message chain added. See \ref so_5_5__in_depth__mchains for more details. \section so_5__5_12 5.5.12 "Cerro Araral" A performance degradation for local mboxes with large amount of subscribers for one message type is fixed (that degradation was accidentally introduced some versions ago). New data source for showing quantity of coops waiting for final deregistration added to run-time statistics. Small modification to output format of msg_tracing messages (format of pointers doesn't depends of compiler now). \section so_5__5_11 5.5.11 "Cerro Nelly" A possibility to tune some options for event queue added for dispatchers so_5::disp::thread_pool and so_5::disp::adv_thread_pool. See \ref so_5_5_12__event_queue_lock_factories for more details. Types for parameters for dispatchers now have name disp_params_t (for example so_5::disp::one_thread::disp_params_t or so_5::disp::adv_thread_pool::disp_params_t). Type for event queues' parameters now have name queue_params_t (for example so_5::disp::one_thread::queue_traits::queue_params_t or so_5::disp::adv_thread_pool::queue_traits::queue_params_t). \section so_5__5_10 5.5.10 "Pichu Pichu" A possibility to tune some options for event queue added for dispatchers so_5::disp::one_thread, so_5::disp::active_obj, so_5::disp::active_group, so_5::disp::prio_one_thread::strictly_ordered, so_5::disp::prio_one_thread::quoted_round_robin, so_5::disp::prio_dedicated_threads::one_per_prio. A type of event queue lock can be specified by providing appropriate lock factory (such as so_5::disp::mpsc_queue_traits::combined_lock_factory() and so_5::disp::mpsc_queue_traits::simple_lock_factory()); A possibility to tune options for event queue of the default dispatcher is implemented via so_5::rt::environment_params_t::default_disp_params() method. \section so_5__5_9 5.5.9 "Cerro Zapaleri" A possibility to trace details of message delivery process is added. See \ref so_5_5__in_depth_msg_tracing for more details. A possibility to send messages of arbitrary types is added. See \ref so_5_5__in_depth__arbitrary_msg_type for more details. New format of event handlers is supported now. It is `R event_handler(T)` for methods of agents or `R(T)` for lambda-functions. Event handlers of such format are very useful with messages of arbitrary types. New helper functions for synchronous agents interaction: so_5::request_future() and so_5::request_value(). See \ref so_5_5_9__request_future for more details. New format of so_5::send(). Now so_5::send() can accept const reference to so_5::rt::mbox_t, so_5::rt::agent_t and so_5::rt::adhoc_agent_definition_proxy_t. See \ref so_5_5_9__new_send_format for more details. New class so_5::wrapped_env_t added. See \ref so_5_5_9__wrapped_env for more details. A fix for serious bug: usage of MPSC mbox after the deregistration of the mbox's owner will lead to use of dangling pointer (with unpredicted consequences like application crash). \section so_5__5_8 5.5.8 "Elbrus" Pririties for agents and several new dispatchers with priorities support have been added. See \ref so_5_5__in_depth_priority_of_agents for details. Another variant of introduce_child_coop and create_child_coop for simplification of child coop creation: \ref so_5_5_8__new_form_introduce_child_coop. New methods of so_5::rt::agent_coop_t for simplification of coop deregistration: \ref so_5_5_8__new_method_agent_coop_deregister. An ability to define context object for ad-hoc agents added. NOTE. This feature was removed in SObjectizer-5.6.0. New version of send-family functions. NOTE. This feature was removed in SObjectizer-5.6.0. More short way for subscribing ad-hoc agents. NOTE. This feature was removed in SObjectizer-5.6.0. A new variant of so_5::rt::agent_t::limit_then_abort() with pre-abort action: \ref so_5_5_8__new_limit_then_abort. \section so_5__5_7 5.5.7 Support for Visual C++ 14.0 (Visual Studio 2015) added. A small fix for collecting runtime monitoring info in so_5::disp::reuse::work_thread. \section so_5__5_6 5.5.6 A small refactoring of SObjectizer-5 to guarantee that `std::abort` will be called when appropriate event in presence of exceptions during error logging. A new description is added to Doxygen-generated documentation: \ref so_5_5__in_depth_exceptions. A new example make_pipeline is added. \section so_5__5_5 5.5.5 A mechanism of message delivery filters is introduced. See \ref so_5_5__in_depth_message_delivery_filters for more details. New templates so_5::rt::tuple_as_message_t, so_5::rt::mtag and so_5::rt::typed_mtag added. NOTE. These features were removed in SObjectizer-5.6.0. New helpers so_5::rt::environment_t::introduce_coop() and so_5::rt::introduce_child_coop(). See \ref so_5_5_5__introduce_coop for more details. \section so_5__5_4 5.5.4 "Koshtan-Tau" A possibility to receive run-time statistics from SObjectizer's internals are introduced. See \ref so_5_5__in_depth_runtime_monitoring for more details. Message limits are supported now. See \ref so_5_5_4__message_limits for more details. Another constructor has been added to so_5::rt::agent_t. This constructor receives argument of type so_5::rt::agent_t::context_t. See \ref so_5_5_4__agent_context for more details. A new conception of *private dispatchers* has been introduced. See \ref so_5_5_4__private_dispatchers for more details. Methods so_5::rt::environment_t::make_agent(), so_5::rt::agent_coop_t::make_agent() and so_5::rt::agent_coop_t::make_agent_with_binder() have been introduced. See \ref so_5_5_4__make_agent_methods for more details. Switching to timertt-1.1.1. Switching to utest_helpers-1.0.2. \section so_5__5_3 5.5.3 "Pik Pushkin" Support for CMake expanded also to the SObjectizer's tests. Switching to timertt-1.1.0. A possibility to get ad-hoc agent's direct mbox added via so_5::rt::adhoc_agent_definition_proxy_t::direct_mbox() method. NOTE. This feature was removed in SObjectizer-5.6.0. A free function so_5::rt::create_child_coop() has been added for simplification of creation of child cooperations. A method for tuning subscription handling mechanism has been added (via so_5::rt::agent_t::tuning_options() and so_5::rt::agent_tuning_options_t data structure). See \ref so_5_5_3__subscr_storage_selection for more details. Several new samples have been added (collector_performer_pair, collector_many_performers, simple_message_deadline, ping_pong_with_owner). \section so_5__5_2 5.5.2 "Jangi-Tau" Initial support for CMake added. Some warnings which are detected at high warning level fixed. \section so_5__5_1 5.5.1 "Shkhara" The old name so_5::rt::mbox_ref_t is marked as deprecated. Name mbox_t must be used instead. The old name will remain valid for several future versions of SObjectizer. But it is strongly recommended to use so_5::rt::mbox_t in the new code. The old name so_5::rt::smart_atomic_reference_t is marked as deprecated. Name so_5::intrusive_ptr_t must be used instead. The old name will remain valid for several future versions of SObjectizer. But it is strongly recommended to use so_5::intrusive_ptr_t in the new code. Also the so_5::atomic_refcounted_t is now moved from namespace so_5::rt to so_5. A method so_5::rt::agent_t::so_subscribe_self() has been introduced. It is just a shorthand for so_subscrive(so_direct_mbox()). A state of an agent can now be changed by several methods. See \ref so_5_5_1__change_agent_state. A new approach for subscribing an agent's event via so_5::rt::state_t::event() method chain. See \ref so_5_5_1__subscribe_event_via_state. New overload to simplify subscription for signals. See \ref so_5_5_1__new_event_method_overloads. New family of free functions introduced to simplify messages and signal sending. See \ref so_5_5_1__new_send_function_family. Names for new cooperations may be generated automatically. See \ref so_5_5_1__autoname_for_cooperations. \section so_5__5_0 5.5.0 "Kazbek" Timer-handling methods so_5::rt::environment_t::schedule_timer() and so_5::rt::environment_t::single_timer() now accept std::chrono::duration as arguments. See \ref so_5_5_0__native_stdchrono_support. Two new functions so_5::launch() are implemeted to be used instead of bunch of so_5::api::run_so_environment() functions. SObjectizer Environment class renamed to so_5::rt::environment_t. Class with parameters for SObjectizer Environment renamed to so_5::rt::environment_params_t. Old names (so_5::rt::so_environment_t, so_5::rt::so_environment_params_t) kept for backward compatibility. timertt library is used for timers support. Because of that user has a possibility to select the most appropriate timer mechanism for its application. See \ref so_5_5_0__timer_thread_factories for more details. Because ACE_Logging is no more used new interface, so_5::error_logger_t is introduced. See \ref so_5_5_0__error_logger for more details. ACE Framework is no more used in SObjectizer core. \section so_5__4_0 5.4.0 "Katyn-Tau" New helper header file so_5/api.hpp added. See \ref so_5_4_0__new_header_all_hpp. New method so_5::rt::so_environment_t::add_dispatcher_if_not_exists() is implemeted. It allows to add new dispatcher on running SO Environment. Added dispatcher cannot be removed. See \ref so_5_4_0__add_dispatcher_if_not_exists. New helper methods so_5::rt::agent_t::so_deregister_agent_coop() and so_5::rt::agent_t::so_deregister_agent_coop_normally(). See \ref so_5_4_0__coop_dereg_helper_methods. A new feature for SObjectizer Environment: SO Environment is shutting down automatically if there is no more live cooperations. This feature is on by default. To disable it new method so_5::rt::so_environment_params_t::disable_autoshutdown() must be used. See \ref so_5_4_0__autoshutdown_mode. New methods so_5::rt::agent_t::so_make_state() introduced to simplify initialization of so_5::rt::state_t objects. See \ref so_5_4_0__method_so_make_state. Two new dispatchers: so_5::disp::thread_pool and so_5::disp::adv_thread_pool. The dispatcher adv_thread_pool can use event handler thread safety information for launching several thread-safe event handlers of one agent on different threads at the same time. See \ref so_5_4_0__thread_pool_dispatcher and \ref so_5_4_0__adv_thread_pool_dispatcher. A possibility to specify thread safety for event handler. By default all event handlers are not thread safe and SObjectizer is guarantees that those handler will not be called from different threads at the same time. But if event is declared as thread safe then dispatcher is free to launch several thread-safe event handlers at different threads at the same time. See \ref so_5_4_0__thread_safety_for_events. New sequence of agent to dispatcher binding. Now it is two stage procedure which guaratees that no so_evt_start() is called until all cooperation's agents are bound to dispatchers. New format of so_5::rt::agent_coop_t::add_agent(). Those methods now return a pointer to agent which has been added to cooperation. See \ref so_5_4_0__new_format_of_add_agent. There is no more anonymous agent states. If user doesn't specify name for the state it will be generated automatically. See \ref so_5_4_0__no_anonymous_states. A new type of mbox is introduced: this is multi-producer/single-consumer mbox. That mbox is belonging to the agent. A reference to that mbox can be obtained only by agent_t::so_direct_mbox() method. Anyone can send messages/signals to such mbox, but only the owner can be subscribed to messages from such mbox. See \ref so_5_4_0__new_mpsc_mbox. Format of so_5::rt::mbox_t::query_name() is changed. Now the std::string object is returned, not the const reference as in previous versions. A working thread identifier is now stored for every agent and controlled by some operations (like changing agent's state and managing agent's subscriptions). A new mechanism for event queues are implemented. Prototypes of methods of so_5::rt::disp_binder_t are changed: now they are received reference to so_5::rt::so_environment_t. There is no more local event queue for agents. Method so_5::rt::so_environment_params_t::agent_event_queue_mutex_pool_size() is obsolete and marked as deprecated. \section so_5__3_0 5.3.0 "Shota Rustaveli Peak" New formats and forms of event- and signal-handlers are supported. See \ref so_5_3_0__new_event_handlers_formats for details. A form of synchronous interaction with agents is introduced. See \ref so_5_3_0__synchronous_interaction for details. Ad-hoc agents implemented. NOTE. This feature was removed in SObjectizer-5.6.0. Exception reaction for agents and agent cooperation could be inhreited now. See \ref so_5_3_0__exception_reaction for details. Several new forms of so_5::api::run_so_environment() added. See \ref so_5_3_0__new_run_so_environment for details. Namespace so_5::chrono_helpers and the function so_5::chrono_helpers::to_ms() added to simplify usage of C++11 std::chrono::duration values. \section so_5__2_3 5.2.3 "Mijirgi" Methods so_5::rt::agent_t::so_subscribe(), so_5::rt::agent_t::so_drop_subscription() and so_5::rt::agent_t::so_drop_subscription_for_all_states() are made public since v.5.2.3.2. Class so_5::rt::so_environment_params_t is now moveable (but not copyable). New exception handling mechanism implemented. An enumeration so_5::rt::exception_reaction_t and method so_5::rt::agent_t::so_exception_reaction() added. Old event_exception_handler_t and related stuff removed. For more details see \ref so_5_2_3__exception_reaction. A class so_5::rt::coop_dereg_reason_t added. New namespace with cooperation deregistration reasons enumeration added: so_5::rt::dereg_reason. Format of so_5::rt::so_environment_t::deregisted_coop() changed. It is necessary to specify reason of cooperation deregistration. New sample 'coop_user_resources' added. An ability to store some dynamically allocated user resource inside cooperation is added via so_5::rt::agent_coop_t::take_under_control() method. For more details see \ref so_5_2_3__cooperation_controlled_resources. The so_5::rt::agent_coop_t contructor is now declared as public. It means that it is possible to create classess which are derived from agent_coop_t. The agent_coop_t destructor is protected now. So the only way to work with agent cooperations is create them as dynamically allocated objects and pass pointers to them to so_5::rt::agent_coop_unique_ptr_t wrappers. New sample 'coop_notification' added. Demands queues now store agent pointers, not smart references as in previous versions. so_5::rt::msg_coop_registered and so_5::rt::msg_coop_deregistered added. so_5::rt::make_coop_reg_notificator() and so_5::rt::make_coop_dereg_notificator() added. so_5::rt::coop_reg_notificator_t and so_5::rt::coop_dereg_notificator_t so_5::rt::agent_coop_t::add_reg_notificator() and so_5::rt::agent_coop_t::add_dereg_notificator() are introduced. See \ref so_5_2_3__cooperation_notificators for more details. Parent-child relationship added for cooperations. For the details see \ref so_5_2_3__parent_child_coops. Cooperation mutex pool no more used and removed from SObjectizer code. Some related method marked as deprecated and will be removed in future releases. \section so_5__2_2 5.2.2 "Tetnuld" Method so_5::rt::event_data_t::make_reference added. It returns an instance of so_5::rt::smart_atomic_reference_t where MSG is the same type as in event_data_t. Another overloaded method deliver_message() added to so_5::rt::mbox_t. It accepts so_5::rt::smart_atomic_reference_t. Those changes make possible to resending or storing the same message without copying a message. Method so_5::rt::smart_atomic_reference_t::reset() added. \section so_5__2_1 5.2.1 "Tetnuld" Methods so_5::rt::so_environment_t::register_agent_as_coop() added. \section so_5__2_0 5.2.0 "Tetnuld" Return codes no more used for error reporting in public methods of SObjectizer classes. In case of error an exception is thrown. All stuff related to return codes (e.g. throwing_strategy_t and so on) have been removed. Only exceptions are used now. Format of so_5::rt::agent_coop_t::add_agent() methods has been changed. Now add_agent() accepts std::unique_ptr and raw so_5::rt::agent_t pointers, not agent_ref_t as in previous versions. An instance of so_5::rt::so_environment_params_t now is passed to so_5::rt::so_environment_t constructor by rvalue reference, not by const reference as in previous versions. A template not_null_event_data_t and all related stuff have been removed. A new base class for all signals (e.g. messages without any data) introduced. Since then all signals classes should be derived from so_5::rt::signal_t. Special checks for inheritance from so_5::rt::message_t and so_5::rt::signal_t inserted into deliver_message/deliver_signal and schedule_timer methods. Those checks work in compile-time. Special checks for not-null message pointers inserted into deliver_message and schedult_timer methods. So it is impossible to use those methods to send null messages. Signals should be used for this purpose. A version of deliver_message for raw message pointer is added (since v.5.2.0.2). Some changes to message dispatching scheme. Some refactoring of code inside SObjectizer internals. \section so_5__1_2 5.1.2 All comments in code have been translated to English. Some small code improvements and refactoring. \section so_5__1_0 5.1.0 "Jimara" Transport layer has been rewritten. An exception handler/logger facility has been added. A cooperation listener has been added. \section so_5__0_0 5.0.0 "Ushba" A first version of new generation of SObjectizer. */ // vim:ft=cpp.doxygen sobjectizer-5.8.6/dev/so_5/version.hpp000066400000000000000000000035001523065210100176670ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \file * \brief Definition of SO_5_VERSION macro * * \since v.5.5.19.3 */ #if !defined( SO_5_VERSION_HPP ) #define SO_5_VERSION_HPP // The current version is 5.8.6 // /*! * A number next to generation number in SObjectizer's version. * * If SObjectizer's version is 5.6.20.8 then SO_5_VERSION_MAJOR==6. * * When major version is changed some incompatibilities are introduced * in SObjectizer API or working principles. */ #define SO_5_VERSION_MAJOR 8ull /*! * A number next to major version in SObjectizer's version. * * If SObjectizer's version is 5.6.20.8 then SO_5_VERSION_MINOR==20. * * When minor version is changed some new features are added to SObjectizer * but SObjectizer API is compatible on source-code level. */ #define SO_5_VERSION_MINOR 6ull /*! * A number next to minor version in SObjectizer's version. * * If SObjectizer's version is 5.6.20.8 then SO_5_VERSION_PATCH==8. * * When patch number is changed some bug-fixes are made or some minor features * or API fixes are added to SObjectizer but SObjectizer API is compatible on * source-code level. */ #define SO_5_VERSION_PATCH 0ull /*! * Helper macro for make single number representation of SObjectizer's version. * * It can be used that way: * \code * // Some feature is available only from 5.5.20.0. * #if SO_5_VERSION >= SO_5_VERSION_MAKE(5, 20, 0) * ... // Some 5.5.20.0 (or above) specific code. * #endif * \endcode */ #define SO_5_VERSION_MAKE(major, minor, patch) \ (((major) * 1000000ull) + \ ((minor) * 1000ull) + \ (patch)) /*! * A single number representation of SObjectizer-5 version. * * For example it can be 5019003ull for SO-5.5.19.3. * Or 6001023ull for SO-5.6.1.23. */ #define SO_5_VERSION SO_5_VERSION_MAKE( \ SO_5_VERSION_MAJOR,\ SO_5_VERSION_MINOR,\ SO_5_VERSION_PATCH) #endif sobjectizer-5.8.6/dev/so_5/wait_indication.hpp000066400000000000000000000020261523065210100213510ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.13 * * \file * \brief Definition of various types of waiting indicators. */ #if !defined( SO_5_WAIT_INDICATION_HPP ) #define SO_5_WAIT_INDICATION_HPP namespace so_5 { /*! * \since * v.5.5.13 * * \brief A type for special marker for infitite waiting on service request * or on receive from %mchain. */ enum class infinite_wait_indication { infinite_wait }; /*! * \since * v.5.5.13 * * \brief A special indicator for infinite waiting on service request or * on receive from %mchain. */ const infinite_wait_indication infinite_wait = infinite_wait_indication::infinite_wait; /*! * \since * v.5.5.13 * * \brief A type for special marker for no waiting on service request * or on receive from %mchain. */ enum class no_wait_indication { no_wait }; /*! * \since * v.5.5.13 * * \brief A special indicator for no waiting on service request or * on receive from %mchain. */ const no_wait_indication no_wait = no_wait_indication::no_wait; } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/so_5/wrapped_env.cpp000066400000000000000000000225751523065210100205240ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.9 * * \file * \brief Class wrapped_env and its details. */ #include #include #include #include #include #include namespace so_5 { using wrapped_env_details::init_style_t; namespace { /*! * \since * v.5.5.9 * * \brief Implementation of environment to be used inside wrapped_env. */ class actual_environment_t : public environment_t { public: //! Initializing constructor. actual_environment_t( //! Initialization routine. so_5::generic_simple_init_t init, //! SObjectizer Environment parameters. environment_params_t && env_params, //! How initialization is performed. init_style_t init_style ) : environment_t( std::move( env_params ) ) , m_init( std::move(init) ) , m_init_style{ init_style } {} void init() override { // Don't expect that this code throws, but if it does // we can't complete our work correctly, so it's better // to crash the application. so_5::details::invoke_noexcept_code( [this]{ { std::lock_guard< std::mutex > lock{ m_status_lock }; m_status = status_t::started; m_status_cond.notify_all(); } switch( m_init_style ) { case init_style_t::async: call_init_functor_async_style(); break; case init_style_t::sync: call_init_functor_sync_style(); break; } // If an exception was caught then the whole // environment has to be stopped after changing // the status. bool should_stop = false; { std::lock_guard< std::mutex > lock{ m_status_lock }; // If exception was caught then we have to stop // the environment. should_stop = static_cast( m_exception_from_init_functor ); m_status = status_t::init_functor_completed; m_status_cond.notify_all(); } if( should_stop ) { // The environment has to be stopped because // of an exception. this->stop(); } } ); } /*! * @brief Wait for change of the status. * * This method blocks the caller thread while m_status is * status_t::not_started. * * Since v.5.8.2 this method throws if: * * - init_style_t::sync is used; * - an exception is throw from init-functor. * * In that case an exception is caught and then rethrown from * ensure_started() method. */ void ensure_started() { /*! * \note This method is necessary because stop() can be * called before run(). In that case there will be an * infinite waiting on join() in wrapped_env_t. */ std::unique_lock< std::mutex > lock{ m_status_lock }; if( status_t::not_started == m_status ) { m_status_cond.wait( lock, [this]{ return status_t::not_started != m_status; } ); } // If sync mode is used that we have to wait while // m_status becomes init_functor_completed. if( init_style_t::sync == m_init_style ) { if( status_t::init_functor_completed != m_status ) { m_status_cond.wait( lock, [this]{ return status_t::init_functor_completed == m_status; } ); } // If exception was thrown from init-functor then it // has to be rethrown now. if( m_exception_from_init_functor ) { std::rethrow_exception( std::move( m_exception_from_init_functor ) ); } } } private: //! Initialization routine. so_5::generic_simple_init_t m_init; //! Style of initialization (asynchronous or synchronous). /*! * \since v.5.8.2 */ const init_style_t m_init_style; //! Status of environment. enum class status_t { //! init() is not called yet. not_started, //! init() is called and is about to call init-functor. started, //! init-functor completed its work. init_functor_completed }; //! Status of environment. status_t m_status = status_t::not_started; //! Lock object for defending status. std::mutex m_status_lock; //! Condition for waiting on status. std::condition_variable m_status_cond; /*! * \brief Exception from init-functor. * * In sync mode an exception throw from init-functor has to be * rethrown in ensure_started() method. To do this it's necessary * to store an instance of that exception. This member is * intended to hold an exception caught from init-functor. * * If this member is empty after completion of the * call_init_functor_sync_style() then it's assumed an exception * wasn't thrown. * * \since v.5.8.2 */ std::exception_ptr m_exception_from_init_functor; /*! * \brief Helper for calling init-functor in asynchronous mode. * * Doesn't handle exceptions. * * \since v.5.8.2 */ void call_init_functor_async_style() { m_init( *this ); } /*! * \brief Helper for calling init-functor in synchronous mode. * * Catches an exception if it's thrown from init-functor. * * \since v.5.8.2 */ void call_init_functor_sync_style() noexcept { try { m_init( *this ); } catch( ... ) { // Assume that these actions won't throw. std::lock_guard< std::mutex > lock{ m_status_lock }; m_exception_from_init_functor = std::current_exception(); } } }; } /* namespace anonymous */ /*! * \since * v.5.5.9 * * \brief Implementation details for wrapped_env. */ struct wrapped_env_t::details_t { //! Actual environment object. actual_environment_t m_env; //! Helper thread for calling run method. std::thread m_env_thread; //! Initializing constructor. details_t( so_5::generic_simple_init_t init_func, environment_params_t && params, init_style_t init_style ) : m_env{ std::move( init_func ), std::move( params ), init_style } {} ~details_t() { // Since v.5.8.2 it may be necessary to call join() in the destructor // in the case when init_style_t::sync is used and an exception // is thrown from init-functor. join(); } void start() { m_env_thread = std::thread{ [this]{ m_env.run(); } }; // Since v.5.8.2 this call may block the current thread // until init-functor completes. It also may throw. m_env.ensure_started(); } void stop() noexcept { m_env.stop(); } void join() noexcept { if( m_env_thread.joinable() ) m_env_thread.join(); } }; namespace { std::unique_ptr< wrapped_env_t::details_t > make_details_object( so_5::generic_simple_init_t init_func, environment_params_t && params, init_style_t init_style ) { return std::unique_ptr< wrapped_env_t::details_t >( new wrapped_env_t::details_t{ std::move( init_func ), std::move( params ), init_style } ); } environment_params_t make_necessary_tuning( environment_params_t && params ) { params.disable_autoshutdown(); return std::move( params ); } environment_params_t make_params_via_tuner( so_5::generic_simple_so_env_params_tuner_t tuner ) { environment_params_t params; tuner( params ); return params; } } /* namespace anonymous */ wrapped_env_t::wrapped_env_t( so_5::generic_simple_init_t init_func, environment_params_t && params, wrapped_env_details::init_style_t init_style ) : m_impl{ make_details_object( std::move( init_func ), make_necessary_tuning( std::move( params ) ), init_style ) } { m_impl->start(); } wrapped_env_t::wrapped_env_t() : wrapped_env_t{ []( environment_t & ) {}, environment_params_t{} } {} wrapped_env_t::wrapped_env_t( so_5::generic_simple_init_t init_func ) : wrapped_env_t{ std::move( init_func ), environment_params_t{} } {} wrapped_env_t::wrapped_env_t( so_5::generic_simple_init_t init_func, so_5::generic_simple_so_env_params_tuner_t params_tuner ) : wrapped_env_t{ std::move( init_func ), make_params_via_tuner( std::move( params_tuner ) ) } {} wrapped_env_t::wrapped_env_t( so_5::generic_simple_init_t init_func, environment_params_t && params ) : wrapped_env_t{ std::move( init_func ), std::move( params ), init_style_t::async } {} wrapped_env_t::wrapped_env_t( wait_init_completion_t wait_init_completion_indicator, so_5::generic_simple_init_t init_func ) : wrapped_env_t{ wait_init_completion_indicator, std::move( init_func ), environment_params_t{} } {} wrapped_env_t::wrapped_env_t( wait_init_completion_t /*wait_init_completion_indicator*/, so_5::generic_simple_init_t init_func, environment_params_t && params ) : wrapped_env_t{ std::move( init_func ), std::move( params ), init_style_t::sync } {} wrapped_env_t::wrapped_env_t( wait_init_completion_t wait_init_completion_indicator, so_5::generic_simple_init_t init_func, so_5::generic_simple_so_env_params_tuner_t params_tuner ) : wrapped_env_t{ wait_init_completion_indicator, std::move( init_func ), make_params_via_tuner( std::move( params_tuner ) ) } {} wrapped_env_t::wrapped_env_t( environment_params_t && params ) : wrapped_env_t{ []( environment_t & ) {}, std::move( params ) } {} wrapped_env_t::~wrapped_env_t() { stop_then_join(); } environment_t & wrapped_env_t::environment() const { return m_impl->m_env; } void wrapped_env_t::stop() { m_impl->stop(); } void wrapped_env_t::join() { m_impl->join(); } void wrapped_env_t::stop_then_join() { stop(); join(); } } /* namespace so_5 */ sobjectizer-5.8.6/dev/so_5/wrapped_env.hpp000066400000000000000000000314461523065210100205260ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since * v.5.5.9 * * \file * \brief Class wrapped_env and its details. */ #if !defined( SO_5_WRAPPED_ENV_HPP ) #define SO_5_WRAPPED_ENV_HPP #include #include #include namespace so_5 { #if defined( SO_5_MSVC ) #pragma warning(push) #pragma warning(disable: 4251) #endif namespace wrapped_env_details { /*! * \brief Style of handling init-functor in the constructor of wrapped_env. * * \since v.5.8.2 */ enum class init_style_t { //! The init-functor has to be handled synchronously. The constructor //! of the wrapped_env_t will block the caller thread until the //! init-functor completes it's work. sync, //! The init-functor has to be handled asynchronously. The constructor //! of the wrapped_env_t may complete its work before the completion //! of the init-functor. async }; } /* namespace wrapped_env_details */ /*! * \brief A wrapped environment. * * \note Starts a SObjectizer Environment in the constructor. Automatically * stops it in the destructor (via stop_then_join() call). An Environment * will be started on the context of new thread. This thread is also * created in the constructor of wrapped_env_t. * * \note SObjectizer Environment is started with autoshutdown disabled. * It means that the Environment won't be stopped when the last coop will * be deregistered. Autoshutdown will be disabled even if a constructor * with custom Environment's params will be used. * * The wrapped_env_t may handle init-function (if it's passed to the constructor) * in two modes: * * - asynchronous (the default). In that case the wrapped_env_t constructor * may complete its work even before the start of init-function; * - synchronous. In that case the wrapped_env_t constructor will block * the caller thread until the init-function completes. * * \par Usage examples for the default asynchronous mode * \code // Start Environment without initialization function. int main() { so_5::wrapped_env_t env; ... // Some user code. // Add a cooperation to environment. env.environment().introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< some_agent >(...); ... } ); ... // Some user code. return 0; // env.stop_then_join() will be called in env destructor. } // Start Environment with initialization function but with // default parameters. int main() { so_5::wrapped_env_t env{ []( so_5::environment_t & env ) { ... // Some initialization stuff. } }; ... // Some user code. return 0; // env.stop_then_join() will be called in env destructor. } // Start Environment with initialization function and custom // parameters. so_5::environment_params_t make_params() { so_5::environment_params_t params; params.exception_reaction( so_5::shutdown_sobjectizer_on_exception ); ... return params; } int main() { so_5::wrapped_env_t env{ []( so_5::environment_t & env ) { ... // Some initialization stuff. }, make_params() }; ... // Some user code. return 0; // env.stop_then_join() will be called in env destructor. } // Start Environment with initialization function and custom // parameters tuner function. int main() { so_5::wrapped_env_t env{ []( so_5::environment_t & env ) { ... // Some initialization stuff. }, []( so_5::environment_params_t & params ) { params.exception_reaction( so_5::shutdown_sobjectizer_on_exception ); ... } }; ... // Some user code. return 0; // env.stop_then_join() will be called in env destructor. } // Explicit stop and join. int main() { so_5::wrapped_env_t env{ ... }; ... // Some user code. // Stopping environment. env.stop(); ... // Some user code. // Waiting for finish of environment's work. env.join(); ... // Some user code. } * \endcode * * \attention * Please note that if an init function is passed to the constructor * of wrapped_env_t objects then it is possible that this init function * will work longer than lifetime of wrapped_env_t object. For example: * \code * int some_func() { * so_5::wrapped_env_t env{ * [](so_5::environment_t & env) { ... } // Some long-running code inside. * }; * ... // Some very fast actions. * return 42; // It is possible that init-function is not finished yet. * } * \endcode * This can lead to some nasty surprises. For example: * \code * so_5::wrapped_env_t env{ * [](so_5::environment & env) { * env.introduce_coop(...); // Creation of one coop. * env.introduce_coop(...); // Creation of another coop. * ... * env.introduce_coop(...); // Creation of yet another coop. * } * }; * ... // Some very fact actions. * env.stop(); // Several coops could not be registered yet. * \endcode * Another example that may lead to null-pointer dereference: * \code * so_5::mbox_t target; // null by default. * so_5::wrapped_env_t env{ * [&](so_5::environment_t & env) { * env.introduce_coop([&](so_5::coop_t & coop) { * target = coop.make_agent(...)->so_direct_mbox(); * }); * } * }; * so_5::send(target); // target may still be null! * \endcode * * \attention * If init-function throws an exception in synchronous mode then the whole * application will be terminated because this exception won't be handled. * * \par Usage examples for the default synchronous mode * Please note that there is no overloads of the wrapped_env_t contructor * without the init-function parameter. * \code * // Start Environment with initialization function but with * // default parameters. * int main() * { * so_5::wrapped_env_t env{ * so_5::wrapped_env_t::wait_init_completion, * []( so_5::environment_t & env ) { * ... // Some initialization stuff. * } * }; * ... // Some user code. * * return 0; // env.stop_then_join() will be called in env destructor. * } * * // Start Environment with initialization function and custom * // parameters. * so_5::environment_params_t make_params() * { * so_5::environment_params_t params; * params.exception_reaction( so_5::shutdown_sobjectizer_on_exception ); * ... * return params; * } * * int main() * { * so_5::wrapped_env_t env{ * so_5::wrapped_env_t::wait_init_completion, * []( so_5::environment_t & env ) { * ... // Some initialization stuff. * }, * make_params() * }; * ... // Some user code. * * return 0; // env.stop_then_join() will be called in env destructor. * } * * // Start Environment with initialization function and custom * // parameters tuner function. * int main() * { * so_5::wrapped_env_t env{ * so_5::wrapped_env_t::wait_init_completion, * []( so_5::environment_t & env ) { * ... // Some initialization stuff. * }, * []( so_5::environment_params_t & params ) { * params.exception_reaction( so_5::shutdown_sobjectizer_on_exception ); * ... * } * }; * ... // Some user code. * * return 0; // env.stop_then_join() will be called in env destructor. * } * \endcode * * \attention * If the init-function throws in synchronous mode then the exception will * be rethrown from the constructor of the wrapped_env_t: * \code * struct my_exception final : public std::exception {...}; * ... * try { * so_5::wrapped_env_t sobjectizer{ * so_5::wrapped_env_t::wait_init_completion, * [](so_5::environment_t & env) { * ... * if(some_condition) * // In the synchronous mode we can throw from init-function. * throw my_exception{...}; * } * } * ... * } * catch( const my_exception & x ) { * ... // exception handling. * } * \endcode * * \since v.5.5.9 */ class SO_5_TYPE wrapped_env_t { /*! * \brief The main initializing constructor. * * All other constructors just delegate work to this constructor. * * \since v.5.8.2 */ wrapped_env_t( //! Init-function to be called. so_5::generic_simple_init_t init_func, //! Parameters for SOEnv to be used. environment_params_t && params, //! Asynchronous or synchronous mode. wrapped_env_details::init_style_t init_style ); public : /*! * \brief Helper type to be used as indicator of synchronous mode. * * \since v.5.8.2. */ enum class wait_init_completion_t { sync }; /*! * \brief Special indicator that tells that synchronous mode * has to be used for calling init-function. * * Usage example: * \code * so_5::wrapped_env_t sobjectizer{ * so_5::wrapped_env_t::wait_init_completion, * ... * }; * ... * \endcode * * \since v.5.8.2 */ static constexpr wait_init_completion_t wait_init_completion{ wait_init_completion_t::sync }; wrapped_env_t( const wrapped_env_t & ) = delete; wrapped_env_t( wrapped_env_t && ) = delete; /*! * \brief Default constructor. * * Starts environment without any initialization actions. */ wrapped_env_t(); /*! * \brief A constructor which receives only initialization function. * * Default environment parameters will be used. * * \attention * This constructor runs \a init_func in asynchronous mode. */ wrapped_env_t( //! Initialization function. so_5::generic_simple_init_t init_func ); /*! * \brief A constructor which receives initialization function and a * function for environment's params tuning. * * \attention * This constructor runs \a init_func in asynchronous mode. */ wrapped_env_t( //! Initialization function. so_5::generic_simple_init_t init_func, //! Function for environment's params tuning. so_5::generic_simple_so_env_params_tuner_t params_tuner ); //! A constructor which receives initialization function and //! already prepared environment's params. /*! * \attention * This constructor runs \a init_func in asynchronous mode. */ wrapped_env_t( //! Initialization function. so_5::generic_simple_init_t init_func, //! Environment's params. environment_params_t && params ); /*! * \brief A constructor for synchronous mode which receives only * initialization function. * * Default environment parameters will be used. * * \attention * This constructor runs \a init_func in synchronous mode. * * \note * This construtor rethrows an exception thrown in \a init_func. * * \since v.5.8.2 */ wrapped_env_t( //! Indicator of the synchronous mode. wait_init_completion_t wait_init_completion_indicator, //! Initialization function. so_5::generic_simple_init_t init_func ); /*! * \brief A constructor for synchronous mode which receives * initialization function and already prepared environment's params. * * \attention * This constructor runs \a init_func in synchronous mode. * * \note * This construtor rethrows an exception thrown in \a init_func. * * \since v.5.8.2 */ wrapped_env_t( //! Indicator of the synchronous mode. wait_init_completion_t wait_init_completion_indicator, //! Initialization function. so_5::generic_simple_init_t init_func, //! Environment's params. environment_params_t && params ); /*! * \brief A constructor for synchronous mode which receives * initialization function and a function for environment's params * tuning. * * \attention * This constructor runs \a init_func in synchronous mode. * * \note * This construtor rethrows an exception thrown in \a init_func. * * \since v.5.8.2 */ wrapped_env_t( wait_init_completion_t wait_init_completion_indicator, //! Initialization function. so_5::generic_simple_init_t init_func, //! Function for environment's params tuning. so_5::generic_simple_so_env_params_tuner_t params_tuner ); /*! * \brief A constructor which receives already prepared * environment's params. * * Usage example: * \code * #include * * so_5::environment_params_t make_params() { * so_5::environment_params_t result; * ... // Parameters' tuning. * return result; * } * * int main() { * so_5::wrapped_env_t sobj{ make_params() }; // SObjectizer is started on separate thread here. * ... // Some actions. * return 0; // SObjectizer will be stopped automatically here. * } * \endcode * * \since v.5.5.13 */ wrapped_env_t( //! Environment's params. environment_params_t && params ); //! Destructor. /*! * Stops the environment and waits it. */ ~wrapped_env_t(); //! Access to wrapped environment. environment_t & environment() const; //! Send stop signal to environment. void stop(); //! Wait for complete finish of environment's work. void join(); //! Send stop signal and wait for complete finish of environment's work. void stop_then_join(); struct details_t; private : //! Implementation details. std::unique_ptr< details_t > m_impl; }; #if defined( SO_5_MSVC ) #pragma warning(pop) #endif } /* namespace so_5 */ #endif sobjectizer-5.8.6/dev/test/000077500000000000000000000000001523065210100156055ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/3rd_party/000077500000000000000000000000001523065210100175145ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/3rd_party/utest_helper/000077500000000000000000000000001523065210100222175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/3rd_party/utest_helper/helper.hpp000066400000000000000000000222421523065210100242110ustar00rootroot00000000000000/*! Вспомогательная функциональность для тестирования. \file utest_helper_1/h/helper.hpp */ #if !defined( UTEST_HELPER_1_HELPER_HELPER_HPP ) #define UTEST_HELPER_1_HELPER_HELPER_HPP #include #include #include #include #include namespace utest_helper_1 { //! Исключение выбрасываемое утверждениями. class check_ex_t : public std::runtime_error { public: check_ex_t( const std::string & err ) : std::runtime_error( err ) {} }; #define UT_CHECK_THROW_IMPL( file, line, exception_class, body )\ do \ { \ std::ostringstream err_stream; \ err_stream << file << "(" << line << "): "; \ bool exception_thrown_impl_detail_ = false; \ try { \ body; \ } \ catch( const exception_class & ) { \ exception_thrown_impl_detail_ = true; \ } \ catch( const std::exception & x ) { \ err_stream << "unit test error: check throw failed: expected " \ << #exception_class " but caught: std::exception: " << x.what() << "\n"; \ } \ catch( ... ) { \ err_stream << "unit test error: check throw failed: expected " \ << #exception_class << " but caught unknown exception\n"; \ } \ if( !exception_thrown_impl_detail_ ) \ { \ throw ::utest_helper_1::check_ex_t( err_stream.str() ); \ } \ } \ while( false ) /*! Проверка на выбрасывание исключения. \code UT_CHECK_THROW( connection_ex_t, m_db.open( connection_params ) ); \endcode */ #define UT_CHECK_THROW( exception_class, body ) \ UT_CHECK_THROW_IMPL(__FILE__, __LINE__, exception_class, body ) /*! Функция для проверки условия. */ inline void check_and_throw( //! Результат проверки условия. bool check_result, //! Условие. const std::string & condition, //! Файл. const std::string & file, //! Номер строки. int line ) { if( !check_result ) { std::ostringstream err_stream; err_stream << file << "(" << line << "): " "unit test error: check condition failed: '" << condition << "'\n"; throw check_ex_t( err_stream.str() ); } } #define UT_CHECK_CONDITION( condition ) \ ::utest_helper_1::check_and_throw( (condition), #condition, __FILE__, __LINE__ ) //! Вспомогательная функция для формированияописания ошибки //! в случае если сравнение не прошло проверку. template inline void cmp_failed( const std::string & cmp_str, const LEFT & left, const RIGHT & right, const std::string & left_str, const std::string & right_str, const std::string & file, int line ) { std::ostringstream err_stream; err_stream << file << "(" << line << "): " "unit test error: comparison failed: '" << left_str << cmp_str << right_str << "' where\n" " [\n" " " << left_str << " is " << left << ",\n" " " << right_str << " is " << right << "\n" " ]\n"; throw check_ex_t( err_stream.str() ); } //! Функция для проверки на равенство. template inline void check_eq_and_throw( const LEFT & left, const RIGHT & right, const std::string & left_str, const std::string & right_str, const std::string & file, int line ) { if( !( left == right) ) { cmp_failed( " == ", left, right, left_str, right_str, file, line ); } } #define UT_CHECK_EQ( left, right ) \ ::utest_helper_1::check_eq_and_throw(\ (left), (right), #left, #right, __FILE__, __LINE__ ) //! Функция для проверки на неравенство. template inline void check_ne_and_throw( const LEFT & left, const RIGHT & right, const std::string & left_str, const std::string & right_str, const std::string & file, int line ) { if( !( left != right) ) { cmp_failed( " != ", left, right, left_str, right_str, file, line ); } } #define UT_CHECK_NE( left, right ) \ ::utest_helper_1::check_ne_and_throw(\ (left), (right), #left, #right, __FILE__, __LINE__ ) //! Функция для проверки на >=. template inline void check_ge_and_throw( const LEFT & left, const RIGHT & right, const std::string & left_str, const std::string & right_str, const std::string & file, int line ) { if( !( left >= right) ) { cmp_failed( " >= ", left, right, left_str, right_str, file, line ); } } #define UT_CHECK_GE( left, right ) \ ::utest_helper_1::check_ge_and_throw(\ (left), (right), #left, #right, __FILE__, __LINE__ ) //! Функция для проверки на >. template inline void check_gt_and_throw( const LEFT & left, const RIGHT & right, const std::string & left_str, const std::string & right_str, const std::string & file, int line ) { if( !( left > right) ) { cmp_failed( " > ", left, right, left_str, right_str, file, line ); } } #define UT_CHECK_GT( left, right ) \ ::utest_helper_1::check_gt_and_throw(\ (left), (right), #left, #right, __FILE__, __LINE__ ) //! Функция для проверки на <=. template inline void check_le_and_throw( const LEFT & left, const RIGHT & right, const std::string & left_str, const std::string & right_str, const std::string & file, int line ) { if( !( left <= right) ) { cmp_failed( " <= ", left, right, left_str, right_str, file, line ); } } #define UT_CHECK_LE( left, right ) \ ::utest_helper_1::check_le_and_throw(\ (left), (right), #left, #right, __FILE__, __LINE__ ) //! Функция для проверки на <. template inline void check_lt_and_throw( const LEFT & left, const RIGHT & right, const std::string & left_str, const std::string & right_str, const std::string & file, int line ) { if( !( left < right) ) { cmp_failed( " < ", left, right, left_str, right_str, file, line ); } } #define UT_CHECK_LT( left, right ) \ ::utest_helper_1::check_lt_and_throw(\ (left), (right), #left, #right, __FILE__, __LINE__ ) typedef std::stack< std::string > context_stack_t; //! Вспомогательный класс вывода контекста. class context_streamer_t { public: inline context_streamer_t( context_stack_t & context_stack, const std::string & name, const std::string & file, unsigned int line ) : m_context_stack( context_stack ) { m_sout << "---- ---- ---- ---- " << name << "\n"; m_header = m_sout.str(); m_sout.str( "" ); m_sout << "file: " << file << "\n" "line: " << line << "\n"; m_footer = m_sout.str(); m_sout.str( "" ); } inline ~context_streamer_t() { std::string final = m_header; const std::string extra = m_sout.str(); if( !extra.empty() ) final += "info: " + extra + "\n"; final += m_footer + "\n"; m_context_stack.push( final ); } inline std::ostream & query_stream() const { return m_sout; } private: std::string m_header; std::string m_footer; context_stack_t & m_context_stack; mutable std::ostringstream m_sout; }; //! A macro for simplification of unit-test definition. #define UT_UNIT_TEST( test_name ) \ struct test_name ## _internal_unique_name_class_ { \ ::utest_helper_1::context_stack_t test_context_impl_detail_; \ void run_test(); \ }; \ int test_name() \ { \ test_name ## _internal_unique_name_class_ instance; \ bool has_exceptions = false; \ try{ \ instance.run_test(); \ std::cout << "Unit test: " #test_name " OK" << std::endl;\ } catch( const ::utest_helper_1::check_ex_t & ex ) { \ std::cerr << ex.what() \ << "Unit test '" << #test_name << "' failed\n"; \ has_exceptions = true; \ } catch( const ::std::exception & ex ) { \ std::cerr << "Unit test '" << #test_name << "' failed\n" \ "Exception: " << ex.what(); \ has_exceptions = true; \ } catch( ... ) { \ std::cerr << "\n\nUnit test '" << #test_name << "' failed\n" \ "Unknown exception"; \ has_exceptions = true; \ } \ \ if( has_exceptions && !instance.test_context_impl_detail_.empty() ) \ { \ std::cerr << "\nContext:\n"; \ while( !instance.test_context_impl_detail_.empty() ) \ { \ std::cerr << instance.test_context_impl_detail_.top();\ instance.test_context_impl_detail_.pop(); \ } \ } \ return has_exceptions ? -1 : 0; \ } \ void test_name ## _internal_unique_name_class_::run_test() //! Добавить еденицу описания контекста. #define UT_PUSH_CONTEXT( name ) \ ::utest_helper_1::context_streamer_t( \ test_context_impl_detail_, \ name, \ __FILE__, \ __LINE__ ).query_stream() //! Извлечь еденицу описания из контекста. #define UT_POP_CONTEXT() \ test_context_impl_detail_.pop() //! Для передачи контекста в другие функции. #define UT_CONTEXT_DECL ::utest_helper_1::context_stack_t & test_context_impl_detail_ //! Передать вызываемой функции контекст #define UT_CONTEXT test_context_impl_detail_ //! Вспомогательный макрос для вызова юнит теста из функции main. #define UT_RUN_UNIT_TEST( test_name ) \ if( 0 != test_name() ) \ {\ return 1; \ } } /* namespace utest_helper_1 */ #endif sobjectizer-5.8.6/dev/test/3rd_party/various_helpers/000077500000000000000000000000001523065210100227265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/3rd_party/various_helpers/benchmark_helpers.hpp000066400000000000000000000052601523065210100271160ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since v.5.4.0 * \file * \brief A simple helpers for building benchmarks. */ #pragma once #include #include #include #include namespace benchmarks_details { //! Helper class for changing and restoring ostream precision settings. class precision_settings_t { std::ios_base & m_what; std::streamsize m_old_value; public : precision_settings_t( std::ios_base & what, std::streamsize new_value ) : m_what( what ) , m_old_value( what.precision( new_value ) ) {} ~precision_settings_t() { m_what.precision( m_old_value ); } }; } /* namespace benchmarks_details */ //! A helper for fixing starting and finishing time points and //! calculate events processing time and events throughtput. class benchmarker_t { public : //! Fix starting time. inline void start() { m_start = std::chrono::high_resolution_clock::now(); } //! Fix finish time and show stats. inline void finish_and_show_stats( unsigned long long events, const std::string & title ) { if( !events ) throw std::invalid_argument( "events cannot be 0" ); auto finish_time = std::chrono::high_resolution_clock::now(); const double duration = double( std::chrono::duration_cast< std::chrono::milliseconds >( finish_time - m_start ).count() ) / 1000.0; const double price = duration / double(events); const double throughtput = 1 / price; benchmarks_details::precision_settings_t precision{ std::cout, 10 }; std::cout << title << ": " << events << ", total_time: " << duration << "s" << "\n""price: " << price << "s" << "\n""throughtput: " << throughtput << " " << title << "/s" << std::endl; } private : std::chrono::high_resolution_clock::time_point m_start; }; //! A helper for showing duration between constructor and destructor calls. /*! * Usage example: \code { duration_meter_t meter( "creating some objects" ); ... // Some code here } // Duration of the code above will be shown here. \endcode */ class duration_meter_t { public : duration_meter_t( std::string name ) : m_name( std::move( name ) ) , m_start( std::chrono::high_resolution_clock::now() ) {} ~duration_meter_t() { auto finish = std::chrono::high_resolution_clock::now(); benchmarks_details::precision_settings_t precision{ std::cout, 10 }; std::cout << m_name << ": " << double( std::chrono::duration_cast< std::chrono::milliseconds >( finish - m_start ).count() ) / 1000.0 << "s" << std::endl; } private : const std::string m_name; const std::chrono::high_resolution_clock::time_point m_start; }; sobjectizer-5.8.6/dev/test/3rd_party/various_helpers/cmd_line_args_helpers.hpp000066400000000000000000000020561523065210100277520ustar00rootroot00000000000000/* * SObjectizer-5 */ #pragma once #include #include #include inline bool is_arg( const char * cmd_line_arg, const char * v1, const char * v2 ) { return (0 == std::strcmp( cmd_line_arg, v1 ) || 0 == std::strcmp( cmd_line_arg, v2 )); } template< class T > void arg_to_value( T & receiver, const char * cmd_line_arg_value, const char * name, const char * description ) { std::stringstream ss; ss << cmd_line_arg_value; ss.seekg(0); T r; ss >> r; if( !ss || !ss.eof() ) throw std::runtime_error( std::string( "unable to parse value for argument '" ) + name + "' (" + description + "): " + cmd_line_arg_value ); receiver = r; } template< class T > void mandatory_arg_to_value( T & receiver, char ** arg, char ** last_arg, const char * name, const char * description ) { if( arg == last_arg ) throw std::runtime_error( std::string( "argument '" ) + name + "' requires value (" + description + ")" ); return arg_to_value( receiver, *arg, name, description ); } sobjectizer-5.8.6/dev/test/3rd_party/various_helpers/ensure.hpp000066400000000000000000000045371523065210100247510ustar00rootroot00000000000000/* * SObjectizer-5 * various_helpers-1 */ /*! * \since v.1.0.2 * \file * \brief Ensure family of helper functions. */ #pragma once #include #include #include #include /*! * \since v.1.0.2 * \brief Very simple form of ensure helper function. * * \par Purpose * Checks the \a p argument and throws an exception if \a p is not `true`. * * \par Usage sample: * \code ensure( demands_count != 0, "demands_count must be greater than 0" ); * \endcode */ inline void ensure( bool p, const char * explanation_msg ) { if( !p ) throw std::runtime_error( explanation_msg ); } /*! * \since v.1.0.2 * \brief Very simple form of ensure helper function. * * \par Purpose * Checks the \a p argument and throws an exception if \a p is not `true`. * * \par Usage sample: * \code ensure( demands_count <= max_demands, "demands_count(" + to_string( demands_count ) + ") must be less or " "equal to max_demands(" + to_string( demands_count ) + ")" ); * \endcode */ inline void ensure( bool p, const std::string & explanation_msg ) { if( !p ) throw std::runtime_error( explanation_msg ); } /*! * \brief Ensure helper function which allows to throw an exception of * the specified type. * * \par Purpose * Checks the \a p argument and throws an exception if \a p is not `true`. * * \par Usage sample: * \code ensure< invalid_argument >( demands_count <= max_demands, "demands_count(" + to_string( demands_count ) + ") must be less or " "equal to max_demands(" + to_string( demands_count ) + ")" ); * \endcode * * \since * v.1.0.2 */ template< typename EXCEPTION, typename EXPLANATION > inline void ensure( bool p, EXPLANATION explanation_msg ) { if( !p ) throw EXCEPTION( explanation_msg ); } /*! * \brief A helper function which calls std::abort if some condition is not meet. * * \note Prints \a explanation_msg to std::cerr before calling std::abort(). * * \par Usage sample: * \code ensure_or_die( demands_count <= max_demands, "demands_count(" + to_string( demands_count ) + ") must be less or " "equal to max_demands(" + to_string( demands_count ) + ")" ); * \endcode * * \since * v.1.0.3 */ inline void ensure_or_die( bool p, const std::string & explanation_msg ) { if( !p ) { std::cerr << "ensure_or_die: '" << explanation_msg << "'" << std::endl; std::abort(); } } sobjectizer-5.8.6/dev/test/3rd_party/various_helpers/time_limited_execution.hpp000066400000000000000000000023161523065210100301710ustar00rootroot00000000000000/* * SObjectizer-5 */ /*! * \since v.5.4.0 * \brief A helper for limit execution of code block to some time. */ #pragma once #include #include #include #include #include #include template< typename FUNCTOR > inline void run_with_time_limit( FUNCTOR action, unsigned int seconds, std::string block_name = std::string() ) { std::promise< void > finish; std::thread sentinel( [&finish, seconds, block_name]() { auto f = finish.get_future(); auto status = f.wait_for( std::chrono::seconds( seconds ) ); if( std::future_status::timeout == status ) { if( !block_name.empty() ) std::cerr << block_name << ": " << std::flush; std::cerr << "block of code not finished within " << seconds << " seconds. Aborting..." << std::endl; std::abort(); } } ); struct sentinel_finisher { std::thread m_s; std::promise< void > & m_p; sentinel_finisher( std::thread && s, std::promise< void > & p ) : m_s( std::move(s) ), m_p( p ) {} ~sentinel_finisher() { m_p.set_value(); m_s.join(); } } sentinel_finisher( std::move(sentinel), finish ); action(); } sobjectizer-5.8.6/dev/test/so_5/000077500000000000000000000000001523065210100164525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/CMakeLists.txt000066400000000000000000000030341523065210100212120ustar00rootroot00000000000000project(tests) if( NOT CYGWIN ) add_subdirectory(spinlocks/llvm_inspired_test) endif() add_subdirectory(details) add_subdirectory(environment) add_subdirectory(wrapped_env) add_subdirectory(execution_hint/basic_checks) add_subdirectory(timer_thread) add_subdirectory(mpsc_queue_traits) add_subdirectory(agent) add_subdirectory(disp) add_subdirectory(event_handler) add_subdirectory(messages) add_subdirectory(enveloped_msg) add_subdirectory(state) add_subdirectory(coop) add_subdirectory(mbox) add_subdirectory(mchain) add_subdirectory(msg_tracing) add_subdirectory(message_limits) add_subdirectory(layer/layer_init) add_subdirectory(layer/layer_query) add_subdirectory(layer/extra_layer_query) add_subdirectory(layer/extra_layer_errors) add_subdirectory(api/run_so_environment) add_subdirectory(mutable_msg) add_subdirectory(internal_stats) add_subdirectory(env_infrastructure) add_subdirectory(event_queue_hook) add_subdirectory(testing) add_subdirectory(bench/ping_pong) add_subdirectory(bench/same_msg_in_different_states) add_subdirectory(bench/parallel_send_to_same_mbox) add_subdirectory(bench/change_state) add_subdirectory(bench/many_mboxes) add_subdirectory(bench/thread_pool_disp) add_subdirectory(bench/no_workload) add_subdirectory(bench/agent_ring) add_subdirectory(bench/coop_dereg) add_subdirectory(bench/skynet1m) add_subdirectory(bench/prepared_receive) add_subdirectory(bench/prepared_select) add_subdirectory(bench/named_mboxes) add_subdirectory(bench/subscribe_unsubscribe) add_subdirectory(bench/state_time_limit) sobjectizer-5.8.6/dev/test/so_5/agent/000077500000000000000000000000001523065210100175505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/agent/CMakeLists.txt000066400000000000000000000000351523065210100223060ustar00rootroot00000000000000add_subdirectory(agent_name) sobjectizer-5.8.6/dev/test/so_5/agent/agent_name/000077500000000000000000000000001523065210100216465ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/agent/agent_name/CMakeLists.txt000066400000000000000000000001341523065210100244040ustar00rootroot00000000000000set(UNITTEST _unit.test.agent.agent_name) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/agent/agent_name/main.cpp000066400000000000000000000034511523065210100233010ustar00rootroot00000000000000/* * A unit-test for agent_t::so_agent_name(). */ #include #include #include namespace test { class anonymous_agent_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { const auto id = so_agent_name(); std::cout << "anonymous_agent_t: " << id << std::endl; ensure_or_die( !id.has_actual_name(), "agent should not have a name!" ); const auto str_id = id.to_string(); ensure_or_die( "() ); env.register_agent_as_coop( env.make_agent< named_agent_t >() ); } } /* namespace test */ int main() { try { run_with_time_limit( []{ so_5::launch( test::init ); }, 5 ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/api/000077500000000000000000000000001523065210100172235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/api/run_so_environment/000077500000000000000000000000001523065210100231545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/api/run_so_environment/CMakeLists.txt000066400000000000000000000001421523065210100257110ustar00rootroot00000000000000set(UNITTEST _unit.test.api.run_so_environment) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/api/run_so_environment/main.cpp000066400000000000000000000034511523065210100246070ustar00rootroot00000000000000/* Test of so_5::launch() routines. Tests both: compilation of template routines specialization and params passin correctnes. */ #include #include #include void init( so_5::environment_t & env ) { env.stop(); } UT_UNIT_TEST( launch_with_free_function_pointer ) { so_5::launch( &init ); } const std::string test_str_param = "Hello!"; void init_with_string_param( so_5::environment_t & env, const std::string & param ) { if( param != test_str_param ) throw std::runtime_error( "paramaters error" ); env.stop(); } const int test_int_param = 42; void init_with_int_param( so_5::environment_t & env, int param ) { if( param != test_int_param ) throw std::runtime_error( "paramaters error" ); env.stop(); } UT_UNIT_TEST( launch_with_parameter ) { using namespace std; using namespace std::placeholders; { const std::string param = test_str_param; UT_PUSH_CONTEXT( "string parameter"); so_5::launch( bind( &init_with_string_param, _1, param ) ); UT_POP_CONTEXT(); } { const int param = test_int_param; UT_PUSH_CONTEXT( "int paramater"); so_5::launch( bind( &init_with_int_param, _1, param ) ); UT_POP_CONTEXT(); } } struct so_init_tester_t { so_init_tester_t() {} void init( so_5::environment_t & env ) { env.stop(); } private: so_init_tester_t( const so_init_tester_t & ); void operator = ( const so_init_tester_t & ); }; UT_UNIT_TEST( launch_on_object ) { using namespace std; using namespace std::placeholders; so_init_tester_t so_init_tester; so_5::launch( bind( &so_init_tester_t::init, &so_init_tester, _1 ) ); } int main() { UT_RUN_UNIT_TEST( launch_with_free_function_pointer ) UT_RUN_UNIT_TEST( launch_with_parameter ) UT_RUN_UNIT_TEST( launch_on_object ) return 0; } sobjectizer-5.8.6/dev/test/so_5/api/several_dlls/000077500000000000000000000000001523065210100217025ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/api/several_dlls/first.cpp000066400000000000000000000015701523065210100235400ustar00rootroot00000000000000#include "first.hpp" #include namespace first { namespace { class demo_t final : public so_5::agent_t { struct tick final : public so_5::signal_t {}; so_5::timer_id_t m_timer; unsigned m_received{}; void on_tick( mhood_t ) { ++m_received; if( 3 == m_received ) so_deregister_agent_coop_normally(); } public: demo_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( &demo_t::on_tick ); } void so_evt_start() override { m_timer = so_5::send_periodic< tick >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds(50) ); } }; } /* namespace anonymous */ FIRST_FUNC void make_coop(void * env) { auto & actual_env = *(reinterpret_cast(env)); actual_env.register_agent_as_coop( actual_env.make_agent() ); } } /* namespace first */ sobjectizer-5.8.6/dev/test/so_5/api/several_dlls/first.hpp000066400000000000000000000003451523065210100235440ustar00rootroot00000000000000#pragma once #include #if defined(FIRST_PRJ) #define FIRST_FUNC SO_5_EXPORT #else #define FIRST_FUNC SO_5_IMPORT #endif namespace first { FIRST_FUNC void make_coop(void * env); } /* namespace first */ sobjectizer-5.8.6/dev/test/so_5/api/several_dlls/main.cpp000066400000000000000000000005131523065210100233310ustar00rootroot00000000000000#include "first.hpp" #include "second.hpp" #include "third.hpp" #include int main() { run_with_time_limit( []{ third::func_container_t funcs; funcs.push_back( &first::make_coop ); funcs.push_back( &second::make_coop ); third::run( funcs ); }, 5 ); } sobjectizer-5.8.6/dev/test/so_5/api/several_dlls/second.cpp000066400000000000000000000015741523065210100236700ustar00rootroot00000000000000#include "second.hpp" #include namespace second { namespace { class demo_t final : public so_5::agent_t { struct tick final : public so_5::signal_t {}; so_5::timer_id_t m_timer; unsigned m_received{}; void on_tick( mhood_t ) { ++m_received; if( 3 == m_received ) so_deregister_agent_coop_normally(); } public: demo_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( &demo_t::on_tick ); } void so_evt_start() override { m_timer = so_5::send_periodic< tick >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds(50) ); } }; } /* namespace anonymous */ SECOND_FUNC void make_coop(void * env) { auto & actual_env = *(reinterpret_cast(env)); actual_env.register_agent_as_coop( actual_env.make_agent() ); } } /* namespace second */ sobjectizer-5.8.6/dev/test/so_5/api/several_dlls/second.hpp000066400000000000000000000003531523065210100236670ustar00rootroot00000000000000#pragma once #include #if defined(SECOND_PRJ) #define SECOND_FUNC SO_5_EXPORT #else #define SECOND_FUNC SO_5_IMPORT #endif namespace second { SECOND_FUNC void make_coop(void * env); } /* namespace second */ sobjectizer-5.8.6/dev/test/so_5/api/several_dlls/third.cpp000066400000000000000000000010011523065210100235100ustar00rootroot00000000000000#include "third.hpp" #include namespace third { THIRD_FUNC void run(const func_container_t & funcs) { try { so_5::launch( [&](so_5::environment_t & env) { for( auto f : funcs ) { (*f)( &env ); } }, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); } catch( const std::exception & x ) { std::cout << "Exception caught: " << x.what() << std::endl; std::abort(); } } } /* namespace third */ sobjectizer-5.8.6/dev/test/so_5/api/several_dlls/third.hpp000066400000000000000000000005271523065210100235310ustar00rootroot00000000000000#pragma once #include #include #if defined(THIRD_PRJ) #define THIRD_FUNC SO_5_EXPORT #else #define THIRD_FUNC SO_5_IMPORT #endif namespace third { using func_t = void (*)(void *); using func_container_t = std::vector; THIRD_FUNC void run(const func_container_t & funcs); } /* namespace third */ sobjectizer-5.8.6/dev/test/so_5/bench/000077500000000000000000000000001523065210100175315ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/agent_ring/000077500000000000000000000000001523065210100216465ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/agent_ring/CMakeLists.txt000066400000000000000000000001771523065210100244130ustar00rootroot00000000000000add_executable(_test.bench.so_5.agent_ring main.cpp) target_link_libraries(_test.bench.so_5.agent_ring sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/agent_ring/main.cpp000066400000000000000000000320261523065210100233010ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif using namespace std::chrono; enum class dispatcher_type_t { one_thread, nef_one_thread, thread_pool, adv_thread_pool, prio_ot_strictly_ordered }; enum class queue_lock_type_t { combined, simple }; enum class pool_fifo_t { cooperation, individual }; struct cfg_t { unsigned int m_ring_size = 50000; unsigned int m_rounds = 1000; bool m_direct_mboxes = false; dispatcher_type_t m_dispatcher_type = dispatcher_type_t::one_thread; queue_lock_type_t m_queue_lock_type = queue_lock_type_t::combined; pool_fifo_t m_fifo = pool_fifo_t::individual; std::size_t m_next_thread_wakeup_threshold = 0; }; cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last_arg = argv + argc; current != last_arg; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.agent_ring \n" "\noptions:\n" "-s, --ring-size size of agent's ring\n" "-r, --rounds count of full rounds around the ring\n" "-d, --direct-mboxes use direct(mpsc) mboxes for agents\n" "-D, --dispatcher type of dispatcher to be used:\n" " one_thread,\n" " nef_one_thread,\n" " thread_pool,\n" " adv_thread_pool,\n" " prio_ot_strictly_ordered\n" "-L, --queue-lock type of queue lock to be used:\n" " combined, simple\n" "-f, --fifo type of fifo for dispatcher with " "thread pool:\n" " cooperation, individual (default)\n" "-T, --threshold value of next_thread_wakeup_threshold for\n" " thread_pool and adv_thread_pool dispatchers\n" " (defaule value: 0)\n" "-h, --help show this help" << std::endl; std::exit( 1 ); } else if( is_arg( *current, "-d", "--direct-mboxes" ) ) tmp_cfg.m_direct_mboxes = true; else if( is_arg( *current, "-s", "--ring-size" ) ) mandatory_arg_to_value( tmp_cfg.m_ring_size, ++current, last_arg, "-s", "size of agent's ring" ); else if( is_arg( *current, "-r", "--rounds" ) ) mandatory_arg_to_value( tmp_cfg.m_rounds, ++current, last_arg, "-r", "count of full rounds around the ring" ); else if( is_arg( *current, "-D", "--dispatcher" ) ) { std::string name; mandatory_arg_to_value( name, ++current, last_arg, "-D", "dispatcher type" ); if( "one_thread" == name ) tmp_cfg.m_dispatcher_type = dispatcher_type_t::one_thread; else if( "nef_one_thread" == name ) tmp_cfg.m_dispatcher_type = dispatcher_type_t::nef_one_thread; else if( "thread_pool" == name ) tmp_cfg.m_dispatcher_type = dispatcher_type_t::thread_pool; else if( "adv_thread_pool" == name ) tmp_cfg.m_dispatcher_type = dispatcher_type_t::adv_thread_pool; else if( "prio_ot_strictly_ordered" == name ) tmp_cfg.m_dispatcher_type = dispatcher_type_t::prio_ot_strictly_ordered; else throw std::runtime_error( "unsupported dispatcher type: " + name ); } else if( is_arg( *current, "-L", "--queue-lock" ) ) { std::string name; mandatory_arg_to_value( name, ++current, last_arg, "-L", "queue lock type" ); if( "combined" == name ) tmp_cfg.m_queue_lock_type = queue_lock_type_t::combined; else if( "simple" == name ) tmp_cfg.m_queue_lock_type = queue_lock_type_t::simple; else throw std::runtime_error( "unsupported queue lock type: " + name ); } else if( is_arg( *current, "-f", "--fifo" ) ) { std::string name; mandatory_arg_to_value( name, ++current, last_arg, "-f", "FIFO type" ); if( "cooperation" == name ) tmp_cfg.m_fifo = pool_fifo_t::cooperation; else if( "individual" == name ) tmp_cfg.m_fifo = pool_fifo_t::individual; else throw std::runtime_error( "unsupported FIFO: " + name ); } else if( is_arg( *current, "-T", "--threshold" ) ) mandatory_arg_to_value( tmp_cfg.m_next_thread_wakeup_threshold, ++current, last_arg, "-T", "value of next_thread_wakeup_threshold param" ); else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } return tmp_cfg; } struct measure_result_t { steady_clock::time_point m_start_time; steady_clock::time_point m_finish_time; }; class a_ring_member_t : public so_5::agent_t { public : struct msg_start : public so_5::signal_t {}; struct msg_your_turn : public so_5::message_t { unsigned long long m_request_number; msg_your_turn( unsigned long long number ) : m_request_number( number ) {} }; a_ring_member_t( so_5::environment_t & env, const cfg_t & cfg, measure_result_t & measure_result ) : so_5::agent_t( env ) , m_cfg( cfg ) , m_measure_result( measure_result ) {} void set_self_mbox( const so_5::mbox_t & mbox ) { m_self_mbox = mbox; } void set_next_mbox( const so_5::mbox_t & mbox ) { m_next_mbox = mbox; } void so_define_agent() override { so_default_state() .event( m_self_mbox, &a_ring_member_t::evt_start ) .event( m_self_mbox, &a_ring_member_t::evt_your_turn ); } void evt_start(mhood_t< msg_start >) { m_measure_result.m_start_time = steady_clock::now(); so_5::send< msg_your_turn >( m_next_mbox, 0ull ); } void evt_your_turn( const msg_your_turn & evt ) { ++m_rounds_passed; if( m_rounds_passed < m_cfg.m_rounds ) so_5::send< msg_your_turn >( m_next_mbox, evt.m_request_number + 1 ); else { m_measure_result.m_finish_time = steady_clock::now(); so_environment().stop(); } } private : so_5::mbox_t m_self_mbox; so_5::mbox_t m_next_mbox; const cfg_t m_cfg; measure_result_t & m_measure_result; unsigned int m_rounds_passed = 0; }; [[nodiscard]] const char * dispatcher_type_name( dispatcher_type_t t ) { const char * result = ""; switch( t ) { case dispatcher_type_t::one_thread: return "one_thread"; case dispatcher_type_t::nef_one_thread: return "nef_one_thread"; case dispatcher_type_t::thread_pool: return "thread_pool"; case dispatcher_type_t::adv_thread_pool: return "adv_thread_pool"; case dispatcher_type_t::prio_ot_strictly_ordered: return "prio_ot_strictly_ordered"; } return result; } const char * queue_lock_type_name( queue_lock_type_t t ) { if( queue_lock_type_t::combined == t ) return "combined"; else return "simple"; } const char * fifo_name( pool_fifo_t fifo ) { if( pool_fifo_t::cooperation == fifo ) return "cooperation"; else return "individual"; } void show_cfg( const cfg_t & cfg ) { std::cout << "Configuration:" << "\n\t" "ring size: " << cfg.m_ring_size << "\n\t" "rounds: " << cfg.m_rounds << "\n\t" "direct mboxes: " << ( cfg.m_direct_mboxes ? "yes" : "no" ) << "\n\t" "disp: " << dispatcher_type_name( cfg.m_dispatcher_type ) << "\n\t" "queue_lock: " << queue_lock_type_name( cfg.m_queue_lock_type ); if( dispatcher_type_t::thread_pool == cfg.m_dispatcher_type || dispatcher_type_t::adv_thread_pool == cfg.m_dispatcher_type ) std::cout << "\n\t" "fifo: " << fifo_name( cfg.m_fifo ) << "\n\t" "threshold: " << cfg.m_next_thread_wakeup_threshold; std::cout << std::endl; } void show_result( const cfg_t & cfg, const measure_result_t & result ) { auto total_msec = duration_cast< milliseconds >( result.m_finish_time - result.m_start_time ).count(); const unsigned long long total_msg_count = static_cast< unsigned long long >( cfg.m_ring_size ) * cfg.m_rounds; double price = static_cast< double >( total_msec ) / double(total_msg_count) / 1000.0; double throughtput = 1 / price; benchmarks_details::precision_settings_t precision{ std::cout, 10 }; std::cout << "total time: " << double(total_msec) / 1000.0 << ", messages sent: " << total_msg_count << ", price: " << price << ", throughtput: " << throughtput << std::endl; } template< typename Disp_Params, typename Combined_Factory, typename Simple_Factory, typename Queue_Params_Tuner > Disp_Params make_disp_params( const cfg_t & cfg, Combined_Factory combined_factory, Simple_Factory simple_factory, Queue_Params_Tuner queue_params_tuner ) { Disp_Params disp_params; using queue_params_t = typename std::decay< decltype(disp_params.queue_params()) >::type; disp_params.tune_queue_params( [&]( queue_params_t & p ) { if( queue_lock_type_t::simple == cfg.m_queue_lock_type ) p.lock_factory( simple_factory() ); else p.lock_factory( combined_factory() ); queue_params_tuner( p ); } ); return disp_params; } so_5::disp_binder_shptr_t create_disp_binder( so_5::environment_t & env, const cfg_t & cfg ) { const auto t = cfg.m_dispatcher_type; if( dispatcher_type_t::one_thread == t ) { using namespace so_5::disp::one_thread; auto disp_params = make_disp_params< disp_params_t >( cfg, []{ return queue_traits::combined_lock_factory(); }, []{ return queue_traits::simple_lock_factory(); }, []( queue_traits::queue_params_t & ) {} ); return make_dispatcher( env, "disp", disp_params ).binder(); } else if( dispatcher_type_t::nef_one_thread == t ) { using namespace so_5::disp::nef_one_thread; auto disp_params = make_disp_params< disp_params_t >( cfg, []{ return queue_traits::combined_lock_factory(); }, []{ return queue_traits::simple_lock_factory(); }, []( queue_traits::queue_params_t & ) {} ); return make_dispatcher( env, "disp", disp_params ).binder(); } else if( dispatcher_type_t::thread_pool == t ) { using namespace so_5::disp::thread_pool; auto disp_params = make_disp_params< disp_params_t >( cfg, []{ return queue_traits::combined_lock_factory(); }, []{ return queue_traits::simple_lock_factory(); }, [&cfg]( queue_traits::queue_params_t & qp ) { qp.next_thread_wakeup_threshold( cfg.m_next_thread_wakeup_threshold ); } ); return make_dispatcher( env, "disp", disp_params ).binder( [&cfg]( bind_params_t & p ) { if( pool_fifo_t::individual == cfg.m_fifo ) p.fifo( fifo_t::individual ); } ); } else if( dispatcher_type_t::adv_thread_pool == t ) { using namespace so_5::disp::adv_thread_pool; auto disp_params = make_disp_params< disp_params_t >( cfg, []{ return queue_traits::combined_lock_factory(); }, []{ return queue_traits::simple_lock_factory(); }, [&cfg]( queue_traits::queue_params_t & qp ) { qp.next_thread_wakeup_threshold( cfg.m_next_thread_wakeup_threshold ); } ); return make_dispatcher( env, "disp", disp_params ).binder( [cfg]( bind_params_t & p ) { if( pool_fifo_t::individual == cfg.m_fifo ) p.fifo( fifo_t::individual ); } ); } else { using namespace so_5::disp::prio_one_thread::strictly_ordered; auto disp_params = make_disp_params< disp_params_t >( cfg, []{ return queue_traits::combined_lock_factory(); }, []{ return queue_traits::simple_lock_factory(); }, []( queue_traits::queue_params_t & ) {} ); return make_dispatcher( env, "disp", disp_params ).binder(); } } void create_coop( const cfg_t & cfg, measure_result_t & result, so_5::environment_t & env ) { so_5::mbox_t first_agent_mbox = env.introduce_coop( create_disp_binder( env, cfg ), [&]( so_5::coop_t & coop ) { std::vector< a_ring_member_t * > agents; agents.reserve( cfg.m_ring_size ); std::vector< so_5::mbox_t > mboxes; mboxes.reserve( cfg.m_ring_size ); for( unsigned int i = 0; i != cfg.m_ring_size; ++i ) { auto member = coop.make_agent< a_ring_member_t >( cfg, result ); agents.push_back( member ); if( cfg.m_direct_mboxes ) mboxes.push_back( member->so_direct_mbox() ); else mboxes.push_back( env.create_mbox() ); } for( unsigned int i = 0; i != cfg.m_ring_size; ++i ) { agents[ i ]->set_self_mbox( mboxes[ i ] ); agents[ i ]->set_next_mbox( mboxes[ (i + 1) % cfg.m_ring_size ] ); } return mboxes[ 0 ]; } ); so_5::send< a_ring_member_t::msg_start >( first_agent_mbox ); } int main( int argc, char ** argv ) { try { cfg_t cfg = try_parse_cmdline( argc, argv ); show_cfg( cfg ); measure_result_t result; so_5::launch( [&]( so_5::environment_t & env ) { create_coop( cfg, result, env ); } ); show_result( cfg, result ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/bench/change_state/000077500000000000000000000000001523065210100221565ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/change_state/CMakeLists.txt000066400000000000000000000002031523065210100247110ustar00rootroot00000000000000add_executable(_test.bench.so_5.change_state main.cpp) target_link_libraries(_test.bench.so_5.change_state sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/change_state/main.cpp000066400000000000000000000051121523065210100236050ustar00rootroot00000000000000/* * A simple benchmark for so_change_state() performance. */ #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif struct msg_dummy : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env, unsigned int iterations ) : so_5::agent_t( env ) , m_iterations( iterations ) { m_states.push_back( &st_0 ); m_states.push_back( &st_1 ); m_states.push_back( &st_2 ); m_states.push_back( &st_3 ); m_states.push_back( &st_4 ); m_states.push_back( &st_5 ); m_states.push_back( &st_6 ); m_states.push_back( &st_7 ); m_states.push_back( &st_8 ); m_states.push_back( &st_9 ); } void so_define_agent() override { so_subscribe( so_direct_mbox() ) .in( st_0 ) .in( st_1 ) .in( st_2 ) .in( st_3 ) .in( st_4 ) .in( st_5 ) .in( st_6 ) .in( st_7 ) .in( st_8 ) .in( st_9 ).event( &a_test_t::evt_dummy ); } void so_evt_start() override { benchmarker_t bench; bench.start(); unsigned long long changes = 0; for( unsigned int i = 0; i != m_iterations; ++i ) { for( auto sp : m_states ) { so_change_state( *sp ); ++changes; } } bench.finish_and_show_stats( changes, "changes" ); so_environment().stop(); } void evt_dummy( mhood_t< msg_dummy > ) { } private : const so_5::state_t st_0{ this, "0" }; const so_5::state_t st_1{ this, "1" }; const so_5::state_t st_2{ this, "2" }; const so_5::state_t st_3{ this, "3" }; const so_5::state_t st_4{ this, "4" }; const so_5::state_t st_5{ this, "5" }; const so_5::state_t st_6{ this, "6" }; const so_5::state_t st_7{ this, "7" }; const so_5::state_t st_8{ this, "8" }; const so_5::state_t st_9{ this, "9" }; unsigned int m_iterations; std::vector< const so_5::state_t * > m_states; }; int main( int argc, char ** argv ) { try { const unsigned int tick_count = static_cast< unsigned int >( 2 == argc ? std::atoi( argv[1] ) : 1000); so_5::launch( [tick_count]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( tick_count ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/bench/coop_dereg/000077500000000000000000000000001523065210100216375ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/coop_dereg/CMakeLists.txt000066400000000000000000000002141523065210100243740ustar00rootroot00000000000000set(BENCHMARK _test.bench.so_5.coop_dereg) add_executable(${BENCHMARK} main.cpp) target_link_libraries(${BENCHMARK} sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/coop_dereg/main.cpp000066400000000000000000000167651523065210100233060ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif using namespace std::chrono; enum class dispatcher_type_t { one_thread, thread_pool, }; struct cfg_t { unsigned int m_coop_count = 1000; unsigned int m_coop_size = 10; dispatcher_type_t m_dispatcher_type = dispatcher_type_t::one_thread; }; cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last_arg = argv + argc; current != last_arg; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.coop_dereg \n" "\noptions:\n" "-c, --coop-count count of coops to be created\n" "-a, --coop-size size of every coop\n" "-D, --dispatcher type of dispatcher to be used:\n" " one_thread, thread_pool\n" "-h, --help show this help" << std::endl; std::exit( 1 ); } else if( is_arg( *current, "-c", "--coop-count" ) ) mandatory_arg_to_value( tmp_cfg.m_coop_count, ++current, last_arg, "-c", "count of coops to be created" ); else if( is_arg( *current, "-a", "--coop-size" ) ) mandatory_arg_to_value( tmp_cfg.m_coop_size, ++current, last_arg, "-a", "count of agents in every coop" ); else if( is_arg( *current, "-D", "--dispatcher" ) ) { std::string name; mandatory_arg_to_value( name, ++current, last_arg, "-D", "dispatcher type" ); if( "one_thread" == name ) tmp_cfg.m_dispatcher_type = dispatcher_type_t::one_thread; else if( "thread_pool" == name ) tmp_cfg.m_dispatcher_type = dispatcher_type_t::thread_pool; else throw std::runtime_error( "unsupported dispatcher type: " + name ); } else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } return tmp_cfg; } using binder_generator_t = std::function< so_5::disp_binder_shptr_t() >; class a_benchmarker_t : public so_5::agent_t { struct ping : public so_5::signal_t {}; struct pong : public so_5::signal_t {}; public : a_benchmarker_t( context_t ctx, cfg_t cfg, binder_generator_t binder_generator ) : so_5::agent_t{ ctx } , m_cfg{ std::move(cfg) } , m_binder_generator{ std::move(binder_generator) } { m_child_mboxes.reserve( cfg.m_coop_count * cfg.m_coop_size ); } virtual void so_define_agent() override { so_subscribe_self() .event( &a_benchmarker_t::evt_root_deregistered ) .event( &a_benchmarker_t::evt_child_registered ) .event( &a_benchmarker_t::evt_pong ); } virtual void so_evt_start() override { class empty_agent_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; }; // Root for children coops must be registered first. so_5::introduce_child_coop( *this, m_binder_generator(), [this]( so_5::coop_t & coop ) { m_root_coop = coop.handle(); coop.add_dereg_notificator( so_5::make_coop_dereg_notificator( so_direct_mbox() ) ); // Empty agent. coop.make_agent(); } ); // Now all children coops must be registered. m_reg_bench.start(); for( unsigned int c = 0; c != m_cfg.m_coop_count; ++c ) register_child_coop(); } private : const cfg_t m_cfg; const binder_generator_t m_binder_generator; so_5::coop_handle_t m_root_coop; std::vector< so_5::mbox_t > m_child_mboxes; benchmarker_t m_reg_bench; benchmarker_t m_ping_pong_bench; benchmarker_t m_dereg_bench; unsigned int m_child_coop_reg_count = { 0u }; unsigned int m_pongs_received = { 0u }; void evt_root_deregistered( const so_5::msg_coop_deregistered & ) { m_dereg_bench.finish_and_show_stats( m_cfg.m_coop_count + 1, "deregistrations" ); so_environment().stop(); } void evt_child_registered( const so_5::msg_coop_registered & ) { ++m_child_coop_reg_count; if( m_child_coop_reg_count == m_cfg.m_coop_count ) { // Registration finished. m_reg_bench.finish_and_show_stats( m_cfg.m_coop_count, "registrations" ); m_ping_pong_bench.start(); benchmarker_t ping_send_bench; ping_send_bench.start(); for( const auto & c : m_child_mboxes ) so_5::send< ping >( c ); ping_send_bench.finish_and_show_stats( m_child_mboxes.size(), "pings" ); } } void evt_pong(mhood_t< pong >) { ++m_pongs_received; if( m_pongs_received == (m_cfg.m_coop_count * m_cfg.m_coop_size) ) { // Ping-pong stage finished. m_ping_pong_bench.finish_and_show_stats( m_pongs_received, "ping-pongs" ); // Initiate deregistration of all children. m_dereg_bench.start(); so_environment().deregister_coop( m_root_coop, so_5::dereg_reason::normal ); } } void register_child_coop() { class child_agent_t final : public so_5::agent_t { public : child_agent_t( context_t ctx, const so_5::mbox_t & parent ) : so_5::agent_t{ std::move(ctx) } { // Child agent should respond to ping signal. so_subscribe_self().event( [parent](mhood_t) { so_5::send< pong >( parent ); } ); } }; so_5::introduce_child_coop( m_root_coop, m_binder_generator(), [this]( so_5::coop_t & coop ) { coop.add_reg_notificator( so_5::make_coop_reg_notificator( so_direct_mbox() ) ); for( unsigned int a = 0; a != m_cfg.m_coop_size; ++a ) { // Ad-hoc agent will respond to ping signal. auto child = coop.make_agent< child_agent_t >( so_direct_mbox() ); m_child_mboxes.push_back( child->so_direct_mbox() ); } } ); } }; const char * dispatcher_type_name( dispatcher_type_t t ) { if( dispatcher_type_t::one_thread == t ) return "one_thread"; return "thread_pool"; } binder_generator_t make_binder_generator( so_5::environment_t & env, dispatcher_type_t t ) { if( dispatcher_type_t::one_thread == t ) { using namespace so_5::disp::one_thread; auto disp = make_dispatcher( env ); return [disp] { return disp.binder(); }; } else { using namespace so_5::disp::thread_pool; auto disp = make_dispatcher( env ); return [disp] { return disp.binder(); }; } } void show_cfg( const cfg_t & cfg ) { std::cout << "Configuration: " << "coops: " << cfg.m_coop_count << ", agents_per_coop: " << cfg.m_coop_size << ", disp: " << dispatcher_type_name( cfg.m_dispatcher_type ) << std::endl; } void run_sobjectizer( const cfg_t & cfg ) { so_5::launch( [&cfg]( so_5::environment_t & env ) { env.introduce_coop( [&cfg]( so_5::coop_t & coop ) { coop.make_agent< a_benchmarker_t >( cfg, make_binder_generator( coop.environment(), cfg.m_dispatcher_type ) ); } ); } ); } int main( int argc, char ** argv ) { try { cfg_t cfg = try_parse_cmdline( argc, argv ); show_cfg( cfg ); run_sobjectizer( cfg ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/bench/many_mboxes/000077500000000000000000000000001523065210100220525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/many_mboxes/CMakeLists.txt000066400000000000000000000002011523065210100246030ustar00rootroot00000000000000add_executable(_test.bench.so_5.many_mboxes main.cpp) target_link_libraries(_test.bench.so_5.many_mboxes sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/many_mboxes/main.cpp000066400000000000000000000313331523065210100235050ustar00rootroot00000000000000/* * A benchmark for sending messages to M mboxes and to N agents. */ #include #include #include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif enum class subscr_storage_type_t { vector_based, map_based, hash_table_based, flat_set_based }; const char * subscr_storage_name( subscr_storage_type_t type ) { if( subscr_storage_type_t::vector_based == type ) return "vector_based"; else if( subscr_storage_type_t::map_based == type ) return "map_based"; else if( subscr_storage_type_t::hash_table_based == type ) return "hash_table_based"; else return "flat_set_based"; } struct cfg_t { std::size_t m_mboxes = 1024; std::size_t m_agents = 512; std::size_t m_msg_types = 4; std::size_t m_iterations = 10; subscr_storage_type_t m_subscr_storage = subscr_storage_type_t::map_based; std::size_t m_vector_subscr_storage_capacity = 8; }; cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last = argv + argc; current != last; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.many_mboxes \n" "\noptions:\n" "-m, --mboxes count of mboxes\n" "-a, --agents count of agents\n" "-t, --types count of message types\n" "-i, --iterations count of iterations for every " "message type\n" "-s, --storage-type type of subscription storage\n" " allowed values: vector, map, hash, flat_set\n" "-V, --vector-capacity initial capacity of vector-based and " "flat-set-based subscription storage\n" "-h, --help show this description\n" << std::endl; std::exit(1); } else if( is_arg( *current, "-m", "--mboxes" ) ) mandatory_arg_to_value( tmp_cfg.m_mboxes, ++current, last, "-m", "count of mboxes" ); else if( is_arg( *current, "-a", "--agents" ) ) mandatory_arg_to_value( tmp_cfg.m_agents, ++current, last, "-a", "count of agents" ); else if( is_arg( *current, "-t", "--types" ) ) mandatory_arg_to_value( tmp_cfg.m_msg_types, ++current, last, "-t", "count of message types" ); else if( is_arg( *current, "-i", "--iterations" ) ) mandatory_arg_to_value( tmp_cfg.m_iterations, ++current, last, "-i", "count of iterations for every message type" ); else if( is_arg( *current, "-V", "--vector-capacity" ) ) mandatory_arg_to_value( tmp_cfg.m_vector_subscr_storage_capacity, ++current, last, "-V", "initial capacity on vector-based and flat-set-based" "subscription storage" ); else if( is_arg( *current, "-s", "--storage-type" ) ) { std::string type; mandatory_arg_to_value( type, ++current, last, "-s", "type of subscription storage" ); if( "vector" == type ) tmp_cfg.m_subscr_storage = subscr_storage_type_t::vector_based; else if( "map" == type ) tmp_cfg.m_subscr_storage = subscr_storage_type_t::map_based; else if( "hash" == type ) tmp_cfg.m_subscr_storage = subscr_storage_type_t::hash_table_based; else if( "flat_set" == type ) tmp_cfg.m_subscr_storage = subscr_storage_type_t::flat_set_based; else throw std::runtime_error( std::string( "unsupported subscription storage type: " ) + type ); } else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } return tmp_cfg; } #define DECLARE_SIGNAL_TYPE(I) \ struct msg_signal_##I : public so_5::signal_t {} DECLARE_SIGNAL_TYPE(0); DECLARE_SIGNAL_TYPE(1); DECLARE_SIGNAL_TYPE(2); DECLARE_SIGNAL_TYPE(3); DECLARE_SIGNAL_TYPE(4); DECLARE_SIGNAL_TYPE(5); DECLARE_SIGNAL_TYPE(6); DECLARE_SIGNAL_TYPE(7); DECLARE_SIGNAL_TYPE(8); DECLARE_SIGNAL_TYPE(9); DECLARE_SIGNAL_TYPE(10); DECLARE_SIGNAL_TYPE(11); DECLARE_SIGNAL_TYPE(12); DECLARE_SIGNAL_TYPE(13); DECLARE_SIGNAL_TYPE(14); DECLARE_SIGNAL_TYPE(15); DECLARE_SIGNAL_TYPE(16); DECLARE_SIGNAL_TYPE(17); DECLARE_SIGNAL_TYPE(18); DECLARE_SIGNAL_TYPE(19); DECLARE_SIGNAL_TYPE(20); DECLARE_SIGNAL_TYPE(21); DECLARE_SIGNAL_TYPE(22); DECLARE_SIGNAL_TYPE(23); DECLARE_SIGNAL_TYPE(24); DECLARE_SIGNAL_TYPE(25); DECLARE_SIGNAL_TYPE(26); DECLARE_SIGNAL_TYPE(27); DECLARE_SIGNAL_TYPE(28); DECLARE_SIGNAL_TYPE(29); DECLARE_SIGNAL_TYPE(30); DECLARE_SIGNAL_TYPE(31); #undef DECLARE_SIGNAL_TYPE struct msg_start : public so_5::signal_t {}; struct msg_shutdown : public so_5::signal_t {}; struct msg_next_iteration : public so_5::signal_t {}; class a_worker_t : public so_5::agent_t { public : a_worker_t( so_5::environment_t & env, so_5::subscription_storage_factory_t subscr_storage_factory ) : so_5::agent_t( env + subscr_storage_factory ) , m_signals_received( 0 ) { } template< typename Signal > void make_subscription( const so_5::mbox_t & from ) { so_subscribe( from ).event( [this](mhood_t) { ++m_signals_received; } ); } private : unsigned long m_signals_received; }; template< class Signal > class a_sender_t : public so_5::agent_t { public : a_sender_t( so_5::environment_t & env, so_5::subscription_storage_factory_t subscr_storage_factory, const so_5::mbox_t & common_mbox, std::size_t iterations, const std::vector< so_5::mbox_t > & mboxes, const std::vector< a_worker_t * > & workers ) : so_5::agent_t( env + subscr_storage_factory ) , m_common_mbox( common_mbox ) , m_iterations_left( iterations ) , m_mboxes( mboxes ) { for( auto a : workers ) for( auto & m : m_mboxes ) a->make_subscription< Signal >( m ); } void so_define_agent() override { so_subscribe( m_common_mbox ).event( &a_sender_t::evt_start ); so_subscribe_self().event( &a_sender_t::evt_next_iteration ); } void evt_start(mhood_t< msg_start >) { try_start_next_iteration(); } void evt_next_iteration(mhood_t< msg_next_iteration >) { try_start_next_iteration(); } private : const so_5::mbox_t m_common_mbox; std::size_t m_iterations_left; const std::vector< so_5::mbox_t > & m_mboxes; void try_start_next_iteration() { if( m_iterations_left ) { for( auto & m : m_mboxes ) so_5::send< Signal >( m ); initiate_next_iteration(); --m_iterations_left; } else so_5::send< msg_shutdown >( m_common_mbox ); } void initiate_next_iteration() { so_5::send< msg_next_iteration >( *this ); } }; class a_starter_stopper_t : public so_5::agent_t { public : a_starter_stopper_t( so_5::environment_t & env, so_5::subscription_storage_factory_t subscr_storage_factory, const cfg_t & cfg ) : so_5::agent_t( env + subscr_storage_factory ) , m_subscr_storage_factory( subscr_storage_factory ) , m_common_mbox( env.create_mbox() ) , m_cfg( cfg ) , m_agents_finished( 0 ) { create_sender_factories(); } static const std::size_t max_msg_types = 32; void so_define_agent() override { so_subscribe( m_common_mbox ) .event( &a_starter_stopper_t::evt_shutdown ); } void so_evt_start() override { std::cout << "* mboxes: " << m_cfg.m_mboxes << "\n" << "* agents: " << m_cfg.m_agents << "\n" << "* msg_types: " << m_cfg.m_msg_types << "\n" << "* iterations: " << m_cfg.m_iterations << "\n" << "* subscr_storage: " << subscr_storage_name( m_cfg.m_subscr_storage ) << std::endl; if( subscr_storage_type_t::vector_based == m_cfg.m_subscr_storage ) std::cout << "* vector_initial_capacity: " << m_cfg.m_vector_subscr_storage_capacity << std::endl; create_child_coop(); m_benchmark.start(); so_5::send< msg_start >( m_common_mbox ); } void evt_shutdown(mhood_t< msg_shutdown >) { ++m_agents_finished; if( m_agents_finished == m_cfg.m_msg_types ) { auto messages = static_cast< unsigned long long >( m_cfg.m_agents ) * m_cfg.m_mboxes * m_cfg.m_msg_types * m_cfg.m_iterations; m_benchmark.finish_and_show_stats( messages, "messages" ); so_environment().stop(); } } private : const so_5::subscription_storage_factory_t m_subscr_storage_factory; const so_5::mbox_t m_common_mbox; const cfg_t m_cfg; std::size_t m_agents_finished; benchmarker_t m_benchmark; std::vector< so_5::mbox_t > m_mboxes; std::vector< a_worker_t * > m_workers; typedef std::function< so_5::agent_t *() > sender_factory_t; std::vector< sender_factory_t > m_sender_factories; void create_sender_factories() { m_sender_factories.reserve( m_cfg.m_msg_types ); #define MAKE_SENDER_FACTORY(I)\ m_sender_factories.emplace_back( \ [this]() \ { \ return new a_sender_t< msg_signal_##I >( so_environment(), \ m_subscr_storage_factory, \ m_common_mbox, \ m_cfg.m_iterations, \ m_mboxes, \ m_workers ); \ } ) MAKE_SENDER_FACTORY(0); MAKE_SENDER_FACTORY(1); MAKE_SENDER_FACTORY(2); MAKE_SENDER_FACTORY(3); MAKE_SENDER_FACTORY(4); MAKE_SENDER_FACTORY(5); MAKE_SENDER_FACTORY(6); MAKE_SENDER_FACTORY(7); MAKE_SENDER_FACTORY(8); MAKE_SENDER_FACTORY(9); MAKE_SENDER_FACTORY(10); MAKE_SENDER_FACTORY(11); MAKE_SENDER_FACTORY(12); MAKE_SENDER_FACTORY(13); MAKE_SENDER_FACTORY(14); MAKE_SENDER_FACTORY(15); MAKE_SENDER_FACTORY(16); MAKE_SENDER_FACTORY(17); MAKE_SENDER_FACTORY(18); MAKE_SENDER_FACTORY(19); MAKE_SENDER_FACTORY(20); MAKE_SENDER_FACTORY(21); MAKE_SENDER_FACTORY(22); MAKE_SENDER_FACTORY(23); MAKE_SENDER_FACTORY(24); MAKE_SENDER_FACTORY(25); MAKE_SENDER_FACTORY(26); MAKE_SENDER_FACTORY(27); MAKE_SENDER_FACTORY(28); MAKE_SENDER_FACTORY(29); MAKE_SENDER_FACTORY(30); MAKE_SENDER_FACTORY(31); #undef MAKE_SENDER_FACTORY } void create_child_coop() { std::cout << "creating child coop..." << std::endl; { duration_meter_t meter( "creating mboxes" ); m_mboxes.reserve( m_cfg.m_mboxes ); for( std::size_t i = 0; i != m_cfg.m_mboxes; ++i ) m_mboxes.emplace_back( so_environment().create_mbox() ); } auto coop = so_5::create_child_coop( *this ); { duration_meter_t meter( "creating workers" ); m_workers.reserve( m_cfg.m_agents ); for( std::size_t i = 0; i != m_cfg.m_agents; ++i ) { m_workers.push_back( coop->make_agent< a_worker_t >( m_subscr_storage_factory ) ); } } { duration_meter_t meter( "creating senders and subscribe workers" ); for( std::size_t i = 0; i != m_cfg.m_msg_types; ++i ) { std::unique_ptr< so_5::agent_t > sender( m_sender_factories[i]() ); coop->add_agent( std::move( sender ) ); } } so_environment().register_coop( std::move( coop ) ); std::cout << "child coop created..." << std::endl; } }; so_5::subscription_storage_factory_t factory_by_cfg( const cfg_t & cfg ) { using namespace so_5; const auto type = cfg.m_subscr_storage; if( subscr_storage_type_t::vector_based == type ) return vector_based_subscription_storage_factory( cfg.m_vector_subscr_storage_capacity ); else if( subscr_storage_type_t::map_based == type ) return map_based_subscription_storage_factory(); else if( subscr_storage_type_t::hash_table_based == type ) return hash_table_based_subscription_storage_factory(); else return flat_set_based_subscription_storage_factory( cfg.m_vector_subscr_storage_capacity ); } int main( int argc, char ** argv ) { try { cfg_t cfg = try_parse_cmdline( argc, argv ); if( cfg.m_msg_types > a_starter_stopper_t::max_msg_types ) { std::ostringstream ss; ss << "too many msg_types specified: " << cfg.m_msg_types << ", max avaliable msg_types: " << a_starter_stopper_t::max_msg_types; throw std::logic_error( ss.str() ); } so_5::launch( [cfg]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_starter_stopper_t >( factory_by_cfg( cfg ), cfg ) ); }, []( so_5::environment_params_t & params ) { // This timer thread doesn't consume resources without // actual delayed/periodic messages. params.timer_thread( so_5::timer_list_factory() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/bench/named_mboxes/000077500000000000000000000000001523065210100221725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/named_mboxes/CMakeLists.txt000066400000000000000000000002031523065210100247250ustar00rootroot00000000000000add_executable(_test.bench.so_5.named_mboxes main.cpp) target_link_libraries(_test.bench.so_5.named_mboxes sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/named_mboxes/main.cpp000066400000000000000000000103601523065210100236220ustar00rootroot00000000000000/* * A test for creation of named mboxes. */ #include #include #include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif namespace named_mbox_benchmark { struct cfg_t { std::size_t m_count = 30000; }; cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last = argv + argc; current != last; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.named_mboxes \n" "\noptions:\n" "-c, --count number of mboxes to be created\n" "-h, --help show this description\n" << std::endl; std::exit(1); } else if( is_arg( *current, "-c", "--count" ) ) mandatory_arg_to_value( tmp_cfg.m_count, ++current, last, "-c", "number of mboxes to be created" ); else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } return tmp_cfg; } struct msg_child_started final : public so_5::message_t { const so_5::mbox_t m_reply_to; explicit msg_child_started( so_5::mbox_t reply_to ) : m_reply_to{ std::move(reply_to) } {} }; struct msg_ack final : public so_5::signal_t {}; struct msg_destroy_child final : public so_5::signal_t {}; class a_child_t final : public so_5::agent_t { const so_5::mbox_t m_parent; const so_5::mbox_t m_self_mbox; [[nodiscard]] static so_5::mbox_t make_self_mbox( so_5::environment_t & env, std::size_t ordinal ) { return env.create_mbox( "child-mbox-with-ordinal-number=" + std::to_string( ordinal ) ); } public: a_child_t( context_t ctx, so_5::mbox_t parent, std::size_t ordinal ) : so_5::agent_t{ std::move(ctx) } , m_parent{ std::move(parent) } , m_self_mbox{ make_self_mbox( so_environment(), ordinal ) } {} void so_define_agent() override { so_subscribe( m_self_mbox ) .event( &a_child_t::evt_ack ) ; } void so_evt_start() override { so_5::send< msg_child_started >( m_parent, m_self_mbox ); } private: void evt_ack( mhood_t ) { so_5::send< msg_destroy_child >( m_parent ); } }; class a_contoller_t : public so_5::agent_t { public : a_contoller_t( so_5::environment_t & env, cfg_t cfg ) : so_5::agent_t( env ) , m_cfg( std::move( cfg ) ) , m_ordinal( 0 ) {} void so_define_agent() override { so_subscribe( so_direct_mbox() ) .event( &a_contoller_t::evt_child_started ) .event( &a_contoller_t::evt_destroy_child ) ; } void so_evt_start() override { try_create_new_child_or_shut_down(); } private : const cfg_t m_cfg; std::size_t m_ordinal; so_5::coop_handle_t m_child_coop; void evt_child_started( mhood_t cmd ) { so_5::send< msg_ack >( cmd->m_reply_to ); } void evt_destroy_child( mhood_t ) { so_environment().deregister_coop( std::move(m_child_coop), so_5::dereg_reason::normal ); try_create_new_child_or_shut_down(); } void try_create_new_child_or_shut_down() { if( m_ordinal >= m_cfg.m_count ) { so_deregister_agent_coop_normally(); } else { auto coop_holder = so_environment().make_coop( so_coop() ); coop_holder->make_agent< a_child_t >( so_direct_mbox(), ++m_ordinal ); m_child_coop = so_environment().register_coop( std::move(coop_holder) ); } } }; } /* namespace named_mbox_benchmark */ int main( int argc, char ** argv ) { using namespace named_mbox_benchmark; try { cfg_t cfg = try_parse_cmdline( argc, argv ); so_5::launch( [cfg]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_contoller_t >( cfg ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/bench/no_workload/000077500000000000000000000000001523065210100220475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/no_workload/CMakeLists.txt000066400000000000000000000002011523065210100246000ustar00rootroot00000000000000add_executable(_test.bench.so_5.no_workload main.cpp) target_link_libraries(_test.bench.so_5.no_workload sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/no_workload/main.cpp000066400000000000000000000102321523065210100234750ustar00rootroot00000000000000/* * A test for showing overhead of working threads in abcense of * any workload. */ #include #include #include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif std::size_t default_thread_pool_size() { auto c = std::thread::hardware_concurrency(); if( !c ) c = 4; return c; } enum class dispatcher_t { thread_pool, adv_thread_pool }; struct cfg_t { std::size_t m_pause = 30; std::size_t m_threads = 0; dispatcher_t m_dispatcher = dispatcher_t::thread_pool; }; cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last = argv + argc; current != last; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.no_workload \n" "\noptions:\n" "-d, --dispatcher type of dispatcher\n" "-t, --threads size of thread pool\n" "-p, --pause timeout before exit (in seconds)\n" "-h, --help show this description\n" << std::endl; std::exit(1); } else if( is_arg( *current, "-d", "--dispatcher" ) ) { std::string v; mandatory_arg_to_value( v, ++current, last, "-d", "type of dispacther " "[thread_pool, adv_thread_pool" ); if( "thread_pool" == v ) tmp_cfg.m_dispatcher = dispatcher_t::thread_pool; else if( "adv_thread_pool" == v ) tmp_cfg.m_dispatcher = dispatcher_t::adv_thread_pool; else throw std::runtime_error( "unsupported dispacther type: " + v ); } else if( is_arg( *current, "-t", "--threads" ) ) mandatory_arg_to_value( tmp_cfg.m_threads, ++current, last, "-t", "size of thread pool" ); else if( is_arg( *current, "-p", "--pause" ) ) mandatory_arg_to_value( tmp_cfg.m_pause, ++current, last, "-p", "timeout before exit (in seconds)" ); else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } return tmp_cfg; } struct msg_shutdown : public so_5::signal_t {}; class a_contoller_t : public so_5::agent_t { public : a_contoller_t( so_5::environment_t & env, cfg_t cfg ) : so_5::agent_t( env ) , m_cfg( std::move( cfg ) ) , m_remaining( m_cfg.m_pause ) {} void so_define_agent() override { so_subscribe( so_direct_mbox() ) .event( &a_contoller_t::evt_shutdown ); } void so_evt_start() override { m_timer = so_5::send_periodic< msg_shutdown >( *this, std::chrono::seconds(1), std::chrono::seconds(1) ); create_dispatcher(); } void evt_shutdown(mhood_t< msg_shutdown >) { --m_remaining; std::cout << "remaining: " << m_remaining << "s \r" << std::flush; if( !m_remaining ) so_environment().stop(); } private : const cfg_t m_cfg; std::size_t m_remaining; so_5::timer_id_t m_timer; so_5::disp_binder_shptr_t m_binder; void create_dispatcher() { const auto threads = m_cfg.m_threads ? m_cfg.m_threads : default_thread_pool_size(); if( dispatcher_t::adv_thread_pool == m_cfg.m_dispatcher ) m_binder = so_5::disp::adv_thread_pool::make_dispatcher( so_environment(), threads ).binder(); else m_binder = so_5::disp::thread_pool::make_dispatcher( so_environment(), threads ).binder(); } }; int main( int argc, char ** argv ) { try { cfg_t cfg = try_parse_cmdline( argc, argv ); so_5::launch( [cfg]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_contoller_t >( cfg ) ); }, []( so_5::environment_params_t & params ) { // This timer thread doesn't consume resources without // actual delayed/periodic messages. params.timer_thread( so_5::timer_list_factory() ); }); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/bench/parallel_parent_child/000077500000000000000000000000001523065210100240415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/parallel_parent_child/CMakeLists.txt000066400000000000000000000002271523065210100266020ustar00rootroot00000000000000set(BENCHMARK _test.bench.so_5.parallel_parent_child) add_executable(${BENCHMARK} main.cpp) target_link_libraries(${BENCHMARK} sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/parallel_parent_child/main.cpp000066400000000000000000000147651523065210100255060ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif using namespace std::chrono; struct cfg_t { unsigned int m_root_count = 2; unsigned int m_levels = 5; unsigned int m_level_size = 5; }; cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last_arg = argv + argc; current != last_arg; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.parallel_parent_child \n" "\noptions:\n" "-r, --root-count count of roots (parallel parents)\n" "-l, --levels count of levels\n" "-s, --level-size count of coop on each level\n" "-h, --help show this help" << std::endl; std::exit( 1 ); } else if( is_arg( *current, "-r", "--root-count" ) ) mandatory_arg_to_value( tmp_cfg.m_root_count, ++current, last_arg, "-r", "count of roots (parallel parents)" ); else if( is_arg( *current, "-l", "--levels" ) ) mandatory_arg_to_value( tmp_cfg.m_levels, ++current, last_arg, "-l", "count of levels" ); else if( is_arg( *current, "-s", "--level-size" ) ) mandatory_arg_to_value( tmp_cfg.m_level_size, ++current, last_arg, "-s", "level size" ); else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } return tmp_cfg; } void create_and_register_agent( const so_5::coop_handle_t & parent_coop, const so_5::mbox_t & parent_mbox, const so_5::disp_binder_shptr_t & binder, unsigned int current_level, unsigned int total_levels, unsigned int level_size ); struct msg_child_completed final { unsigned int m_children_agents; }; class a_child_t final : public so_5::agent_t { public : a_child_t( context_t ctx, so_5::mbox_t parent_mbox, so_5::disp_binder_shptr_t binder, unsigned int current_level, unsigned int total_levels, unsigned int level_size ) : so_5::agent_t{ std::move(ctx) } , m_parent_mbox{ std::move(parent_mbox) } , m_binder{ std::move(binder) } , m_current_level{ current_level } , m_total_levels{ total_levels } , m_level_size{ level_size } {} void so_define_agent() override { so_subscribe_self() .event( &a_child_t::evt_child_completed ); } void so_evt_start() override { if( m_current_level != m_total_levels ) for( unsigned int i = 0; i != m_level_size; ++i ) create_and_register_agent( so_coop(), so_direct_mbox(), m_binder, m_current_level + 1u, m_total_levels, m_level_size ); else complete_work(); } void evt_child_completed( mhood_t< msg_child_completed > cmd ) { m_children_agents += cmd->m_children_agents + 1u; ++m_children_completed; if( m_children_completed == m_level_size ) complete_work(); } private : const so_5::mbox_t m_parent_mbox; const so_5::disp_binder_shptr_t m_binder; const unsigned int m_current_level; const unsigned int m_total_levels; const unsigned int m_level_size; unsigned int m_children_agents{}; unsigned int m_children_completed{}; void complete_work() { so_5::send< msg_child_completed >( m_parent_mbox, m_children_agents ); } }; void create_and_register_agent( const so_5::coop_handle_t & parent_coop, const so_5::mbox_t & parent_mbox, const so_5::disp_binder_shptr_t & binder, unsigned int current_level, unsigned int total_levels, unsigned int level_size ) { so_5::introduce_child_coop( parent_coop, [&]( so_5::coop_t & coop ) { coop.make_agent< a_child_t >( parent_mbox, binder, current_level, total_levels, level_size ); } ); } class a_root_t final : public so_5::agent_t { public : a_root_t( context_t ctx, so_5::disp_binder_shptr_t binder, std::atomic_uint & result_receiver, unsigned int total_levels, unsigned int level_size ) : so_5::agent_t{ std::move(ctx) } , m_binder{ std::move(binder) } , m_result_receiver{ result_receiver } , m_total_levels{ total_levels } , m_level_size{ level_size } {} void so_define_agent() override { so_subscribe_self() .event( &a_root_t::on_child_completed ); } void so_evt_start() override { for( unsigned int i = 0; i != m_level_size; ++i ) create_and_register_agent( so_coop(), so_direct_mbox(), m_binder, 0u, m_total_levels, m_level_size ); } private : const so_5::disp_binder_shptr_t m_binder; std::atomic_uint & m_result_receiver; const unsigned int m_total_levels; const unsigned int m_level_size; unsigned int m_children_agents{}; unsigned int m_children_completed{}; void on_child_completed( mhood_t cmd ) { m_children_agents += cmd->m_children_agents + 1u; ++m_children_completed; if( m_children_completed == m_level_size ) { m_result_receiver = m_children_agents; so_deregister_agent_coop_normally(); } } }; void show_cfg( const cfg_t & cfg ) { std::cout << "Configuration: " << "roots: " << cfg.m_root_count << ", levels: " << cfg.m_levels << ", level-size: " << cfg.m_level_size << std::endl; } void run_sobjectizer( const cfg_t & cfg ) { std::vector< std::atomic_uint > results( cfg.m_root_count ); for( auto & v : results ) v = 0u; { duration_meter_t meter{ "parallel_parent_child" }; so_5::launch( [&]( so_5::environment_t & env ) { for( unsigned int i = 0; i != cfg.m_root_count; ++i ) { auto binder = so_5::disp::one_thread::make_dispatcher( env ).binder(); env.introduce_coop( binder, [&]( so_5::coop_t & coop ) { coop.make_agent< a_root_t >( binder, std::ref(results[i]), cfg.m_levels, cfg.m_level_size ); } ); } } ); } unsigned int total{}; for( auto & v : results ) { const auto c = v.load(); std::cout << c << std::endl; total += c; } std::cout << "Total: " << total << std::endl; } int main( int argc, char ** argv ) { try { cfg_t cfg = try_parse_cmdline( argc, argv ); show_cfg( cfg ); run_sobjectizer( cfg ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/bench/parallel_send_to_same_mbox/000077500000000000000000000000001523065210100250725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/parallel_send_to_same_mbox/CMakeLists.txt000066400000000000000000000002371523065210100276340ustar00rootroot00000000000000add_executable(_test.bench.so_5.parallel_send_to_same_mbox main.cpp) target_link_libraries(_test.bench.so_5.parallel_send_to_same_mbox sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/parallel_send_to_same_mbox/main.cpp000066400000000000000000000066671523065210100265410ustar00rootroot00000000000000/* * A benchmark of parallel send from different agents to the same mbox. * * There is no a receiver for the messages. Benchmark shows only * the price of parallel access to the mbox. */ #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif struct msg_send : public so_5::signal_t {}; struct msg_complete : public so_5::signal_t {}; class a_sender_t : public so_5::agent_t { public : a_sender_t( so_5::environment_t & env, const so_5::mbox_t & mbox, unsigned int send_count ) : so_5::agent_t( env ) , m_mbox( mbox ) , m_send_count( send_count ) {} void so_evt_start() override { for( unsigned int i = 0; i != m_send_count; ++i ) so_5::send< msg_send >( m_mbox ); so_5::send< msg_complete >( m_mbox ); } private : const so_5::mbox_t m_mbox; unsigned int m_send_count; }; class a_shutdowner_t : public so_5::agent_t { public : a_shutdowner_t( so_5::environment_t & env, const so_5::mbox_t & mbox, unsigned int sender_count ) : so_5::agent_t( env ) , m_sender_count( sender_count ) { so_subscribe( mbox ).event( [this](mhood_t< msg_complete >) { m_sender_count -= 1; if( !m_sender_count ) so_environment().stop(); } ); } private : unsigned int m_sender_count; }; void init( so_5::environment_t & env, unsigned int agent_count, unsigned int send_count ) { auto mbox = env.create_mbox(); auto coop = env.make_coop( so_5::disp::active_obj::make_dispatcher( env, "active_obj" ).binder() ); for( unsigned int i = 0; i != agent_count; ++i ) coop->make_agent< a_sender_t >( mbox, send_count ); coop->make_agent_with_binder< a_shutdowner_t >( so_5::make_default_disp_binder( env ), mbox, agent_count ); env.register_coop( std::move( coop ) ); } void print_usage() { std::cout << "Usage: parallel_sent_to_same_mbox \n\n" " and must not be 0" << std::endl; } struct cmd_line_exception : public std::invalid_argument { cmd_line_exception( const char * what ) : std::invalid_argument( what ) {} }; int main( int argc, char ** argv ) { try { auto ensure_args_validity = []( bool p, const char * msg ) { if( !p ) throw cmd_line_exception( msg ); }; ensure_args_validity( 3 == argc, "wrong number of arguments" ); const unsigned int agent_count = static_cast< unsigned int >(std::atoi( argv[1] )); ensure_args_validity( agent_count != 0, "agent_count must not be 0" ); const unsigned int send_count = static_cast< unsigned int >(std::atoi( argv[2] )); ensure_args_validity( send_count != 0, "send_count must not be 0" ); benchmarker_t benchmark; benchmark.start(); so_5::launch( [agent_count, send_count]( so_5::environment_t & env ) { init( env, agent_count, send_count ); } ); benchmark.finish_and_show_stats( static_cast< unsigned long long >(agent_count) * send_count, "sends" ); } catch( const cmd_line_exception & ex ) { std::cerr << "Command line argument(s) error: " << ex.what() << "\n\n" << std::flush; print_usage(); return 1; } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/bench/ping_pong/000077500000000000000000000000001523065210100215115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/ping_pong/CMakeLists.txt000066400000000000000000000001751523065210100242540ustar00rootroot00000000000000add_executable(_test.bench.so_5.ping_pong main.cpp) target_link_libraries(_test.bench.so_5.ping_pong sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/ping_pong/main.cpp000066400000000000000000000235161523065210100231500ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif using namespace std::chrono; enum class env_type_t { default_mt, simple_mtsafe, simple_not_mtsafe }; struct cfg_t { unsigned int m_request_count = 1000; bool m_use_messages = false; bool m_active_objects = false; bool m_simple_lock = false; bool m_direct_mboxes = false; bool m_message_limits = false; bool m_track_activity = false; env_type_t m_env = env_type_t::default_mt; }; cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last_arg = argv + argc; current != last_arg; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.ping_pong \n" "\noptions:\n" "-a, --active-objects agents should be active objects\n" "-r, --requests count of requests to send\n" "-d, --direct-mboxes use direct(mpsc) mboxes for agents\n" "-l, --message-limits use message limits for agents\n" "-s, --simple-lock use simple lock factory for event queue\n" "-T, --track-activity turn work thread activity tracking on\n" "-e, --env environment infrastructure to be used:\n" " default_mt (default),\n" " simple_mtsafe,\n" " simple_not_mtsafe\n" "-M, --use-messages use messages for interaction " "(signals are used by default)\n" "-h, --help show this help" << std::endl; std::exit( 1 ); } else if( is_arg( *current, "-a", "--active-objects" ) ) tmp_cfg.m_active_objects = true; else if( is_arg( *current, "-d", "--direct-mboxes" ) ) tmp_cfg.m_direct_mboxes = true; else if( is_arg( *current, "-l", "--message-limits" ) ) tmp_cfg.m_message_limits = true; else if( is_arg( *current, "-s", "--simple-lock" ) ) tmp_cfg.m_simple_lock = true; else if( is_arg( *current, "-r", "--requests" ) ) mandatory_arg_to_value( tmp_cfg.m_request_count, ++current, last_arg, "-r", "count of requests to send" ); else if( is_arg( *current, "-T", "--track-activity" ) ) tmp_cfg.m_simple_lock = true; else if( is_arg( *current, "-e", "--env" ) ) { std::string env_type_literal; mandatory_arg_to_value( env_type_literal, ++current, last_arg, "-e", "type of environment infrastructure" ); if( "default_mt" == env_type_literal ) tmp_cfg.m_env = env_type_t::default_mt; else if( "simple_mtsafe" == env_type_literal ) tmp_cfg.m_env = env_type_t::simple_mtsafe; else if( "simple_not_mtsafe" == env_type_literal ) { tmp_cfg.m_env = env_type_t::simple_not_mtsafe; } else throw std::runtime_error( "unknown type of " "environment infrastructure: " + env_type_literal ); } else if( is_arg( *current, "-M", "--use-messages" ) ) tmp_cfg.m_use_messages = true; else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } return tmp_cfg; } struct measure_result_t { steady_clock::time_point m_start_time; steady_clock::time_point m_finish_time; }; struct impact_signal final : public so_5::signal_t {}; struct impact_message final : public so_5::message_t { impact_message() = default; }; template< typename Impact > class a_pinger_t final : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_pinger_t( context_t ctx, const cfg_t & cfg, measure_result_t & measure_result ) : base_type_t( prepare_context( ctx, cfg ) ) , m_cfg( cfg ) , m_measure_result( measure_result ) , m_requests_sent( 0 ) {} void set_self_mbox( const so_5::mbox_t & mbox ) { m_self_mbox = mbox; } void set_ponger_mbox( const so_5::mbox_t & mbox ) { m_ponger_mbox = mbox; } void so_define_agent() override { so_subscribe( m_self_mbox ).event( &a_pinger_t::evt_pong ); } void so_evt_start() override { m_measure_result.m_start_time = steady_clock::now(); send_ping(); } void evt_pong( mhood_t< Impact > ) { ++m_requests_sent; if( m_requests_sent < m_cfg.m_request_count ) send_ping(); else { m_measure_result.m_finish_time = steady_clock::now(); so_environment().stop(); } } private : so_5::mbox_t m_self_mbox; so_5::mbox_t m_ponger_mbox; const cfg_t m_cfg; measure_result_t & m_measure_result; unsigned int m_requests_sent; void send_ping() { so_5::send< Impact >( m_ponger_mbox ); } static context_t prepare_context( context_t ctx, const cfg_t & cfg ) { if( cfg.m_message_limits ) return ctx + limit_then_abort< Impact >( 1 ); else return ctx; } }; template< typename Impact > class a_ponger_t final : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_ponger_t( context_t ctx, const cfg_t & cfg ) : base_type_t( prepare_context( ctx, cfg ) ) {} void set_self_mbox( const so_5::mbox_t & mbox ) { m_self_mbox = mbox; } void set_pinger_mbox( const so_5::mbox_t & mbox ) { m_pinger_mbox = mbox; } void so_define_agent() override { so_subscribe( m_self_mbox ).event( &a_ponger_t::evt_ping ); } void evt_ping( mhood_t< Impact > ) { so_5::send< Impact >( m_pinger_mbox ); } private : so_5::mbox_t m_self_mbox; so_5::mbox_t m_pinger_mbox; static context_t prepare_context( context_t ctx, const cfg_t & cfg ) { if( cfg.m_message_limits ) return ctx + limit_then_abort< Impact >( 1 ); else return ctx; } }; void show_cfg( const cfg_t & cfg ) { std::cout << "Configuration: " << "active objects: " << ( cfg.m_active_objects ? "yes" : "no" ) << ", direct mboxes: " << ( cfg.m_direct_mboxes ? "yes" : "no" ) << ", limits: " << ( cfg.m_message_limits ? "yes" : "no" ) << ", locks: " << ( cfg.m_simple_lock ? "simple" : "combined" ) << ", requests: " << cfg.m_request_count << ", activity tracking: " << ( cfg.m_track_activity ? "on" : "off" ) << ", env: " << ( env_type_t::default_mt == cfg.m_env ? "mt" : ( env_type_t::simple_mtsafe == cfg.m_env ? "mtsafe" : "not_mtsafe" ) ) << ", " << ( cfg.m_use_messages ? "messages" : "signals" ) << std::endl; } void show_result( const cfg_t & cfg, const measure_result_t & result ) { auto total_msec = duration_cast< milliseconds >( result.m_finish_time - result.m_start_time ).count(); const unsigned int total_msg_count = cfg.m_request_count * 2; double price = static_cast< double >( total_msec ) / double(total_msg_count) / 1000.0; double throughtput = 1 / price; benchmarks_details::precision_settings_t precision{ std::cout, 10 }; std::cout << "total time: " << double(total_msec) / 1000.0 << ", messages sent: " << total_msg_count << ", price: " << price << ", throughtput: " << throughtput << std::endl; } void ensure_valid_cfg( const cfg_t & cfg ) { if( cfg.m_active_objects && env_type_t::simple_not_mtsafe == cfg.m_env ) throw std::runtime_error( "invalid config: active objects can't be" " used with simple_not_mtsafe environment infrastructure" ); } class test_env_t { public : test_env_t( const cfg_t & cfg ) : m_cfg( cfg ) {} void init( so_5::environment_t & env ) { auto binder = ( m_cfg.m_active_objects ? so_5::disp::active_obj::make_dispatcher( env ).binder() : so_5::make_default_disp_binder( env ) ); auto coop = env.make_coop( std::move(binder) ); if( m_cfg.m_use_messages ) fill_coop_with< impact_message >( *coop ); else fill_coop_with< impact_signal >( *coop ); env.register_coop( std::move( coop ) ); } void process_results() const { show_result( m_cfg, m_result ); } private : const cfg_t m_cfg; measure_result_t m_result; template< typename Impact > void fill_coop_with( so_5::coop_t & coop ) { auto a_pinger = coop.make_agent< a_pinger_t< Impact > >( m_cfg, m_result ); auto a_ponger = coop.make_agent< a_ponger_t< Impact > >( m_cfg ); auto pinger_mbox = m_cfg.m_direct_mboxes ? a_pinger->so_direct_mbox() : coop.environment().create_mbox(); auto ponger_mbox = m_cfg.m_direct_mboxes ? a_ponger->so_direct_mbox() : coop.environment().create_mbox(); a_pinger->set_self_mbox( pinger_mbox ); a_pinger->set_ponger_mbox( ponger_mbox ); a_ponger->set_self_mbox( ponger_mbox ); a_ponger->set_pinger_mbox( pinger_mbox ); } }; int main( int argc, char ** argv ) { try { cfg_t cfg = try_parse_cmdline( argc, argv ); ensure_valid_cfg( cfg ); show_cfg( cfg ); test_env_t test_env( cfg ); so_5::launch( [&]( so_5::environment_t & env ) { test_env.init( env ); }, [&]( so_5::environment_params_t & params ) { if( env_type_t::simple_mtsafe == cfg.m_env ) { using namespace so_5::env_infrastructures::simple_mtsafe; params.infrastructure_factory( factory() ); } else if( env_type_t::simple_not_mtsafe == cfg.m_env ) { using namespace so_5::env_infrastructures::simple_not_mtsafe; params.infrastructure_factory( factory() ); } if( cfg.m_track_activity ) params.turn_work_thread_activity_tracking_on(); if( cfg.m_simple_lock ) params.queue_locks_defaults_manager( so_5::make_defaults_manager_for_simple_locks() ); } ); test_env.process_results(); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/bench/prepared_receive/000077500000000000000000000000001523065210100230355ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/prepared_receive/CMakeLists.txt000066400000000000000000000002221523065210100255710ustar00rootroot00000000000000set(BENCHMARK _test.bench.so_5.prepared_receive) add_executable(${BENCHMARK} main.cpp) target_link_libraries(${BENCHMARK} sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/prepared_receive/main.cpp000066400000000000000000000037071523065210100244740ustar00rootroot00000000000000/* * A simple benchmark for select() and prepare_select() performance. */ #include #include #include #include #include #include #include const unsigned long long max_iterations = 50000u; struct one {}; struct two {}; struct three {}; so_5::mchain_t make_mchain( so_5::environment_t & env ) { return so_5::create_mchain( env, 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::throw_exception ); } void raw_receive_case( so_5::environment_t & env ) { auto ch1 = make_mchain( env ); unsigned long long iterations = 0u; so_5::send< one >( ch1 ); benchmarker_t bench; bench.start(); while( iterations < max_iterations ) { so_5::receive( from( ch1 ).extract_n( 1 ).no_wait_on_empty(), [&ch1]( one ) { so_5::send< two >( ch1 ); }, [&ch1]( two ) { so_5::send< three >( ch1 ); }, [&ch1]( three ) { so_5::send< one >( ch1 ); } ); ++iterations; } bench.finish_and_show_stats( iterations, "raw_receive_case" ); } void prepared_receive_case( so_5::environment_t & env ) { auto ch1 = make_mchain( env ); unsigned long long iterations = 0u; const auto prepared = so_5::prepare_receive( from( ch1 ).extract_n( 1 ).no_wait_on_empty(), [&ch1]( one ) { so_5::send< two >( ch1 ); }, [&ch1]( two ) { so_5::send< three >( ch1 ); }, [&ch1]( three ) { so_5::send< one >( ch1 ); } ); so_5::send< one >( ch1 ); benchmarker_t bench; bench.start(); while( iterations < max_iterations ) { so_5::receive( prepared ); ++iterations; } bench.finish_and_show_stats( iterations, "prepared_receive_case" ); } int main() { try { so_5::launch( []( so_5::environment_t & env ) { raw_receive_case( env ); prepared_receive_case( env ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/bench/prepared_select/000077500000000000000000000000001523065210100226725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/prepared_select/CMakeLists.txt000066400000000000000000000002211523065210100254250ustar00rootroot00000000000000set(BENCHMARK _test.bench.so_5.prepared_select) add_executable(${BENCHMARK} main.cpp) target_link_libraries(${BENCHMARK} sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/prepared_select/main.cpp000066400000000000000000000043271523065210100243300ustar00rootroot00000000000000/* * A simple benchmark for select() and prepare_select() performance. */ #include #include #include #include #include #include #include so_5::mchain_t make_mchain( so_5::environment_t & env ) { return so_5::create_mchain( env, 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::throw_exception ); } void raw_select_case( so_5::environment_t & env ) { auto ch1 = make_mchain( env ); auto ch2 = make_mchain( env ); auto ch3 = make_mchain( env ); unsigned long long iterations = 0u; const unsigned long long max_iterations = 10000u; so_5::send< int >( ch1, 0 ); benchmarker_t bench; bench.start(); while( iterations < max_iterations ) { so_5::select( so_5::from_all().handle_n( 1 ).no_wait_on_empty(), receive_case( ch1, [&ch2]( int v ) { so_5::send< int >(ch2, v+1); } ), receive_case( ch2, [&ch3]( int v ) { so_5::send< int >(ch3, v+1); } ), receive_case( ch3, [&ch1]( int v ) { so_5::send< int >(ch1, v+1); } ) ); ++iterations; } bench.finish_and_show_stats( iterations, "raw_select_case" ); } void prepared_select_case( so_5::environment_t & env ) { auto ch1 = make_mchain( env ); auto ch2 = make_mchain( env ); auto ch3 = make_mchain( env ); unsigned long long iterations = 0u; const unsigned long long max_iterations = 10000u; auto prepared = so_5::prepare_select( so_5::from_all().handle_n( 1 ).no_wait_on_empty(), receive_case( ch1, [&ch2]( int v ) { so_5::send< int >(ch2, v+1); } ), receive_case( ch2, [&ch3]( int v ) { so_5::send< int >(ch3, v+1); } ), receive_case( ch3, [&ch1]( int v ) { so_5::send< int >(ch1, v+1); } ) ); so_5::send< int >( ch1, 0 ); benchmarker_t bench; bench.start(); while( iterations < max_iterations ) { select( prepared ); ++iterations; } bench.finish_and_show_stats( iterations, "prepared_select_case" ); } int main() { try { so_5::launch( []( so_5::environment_t & env ) { raw_select_case( env ); prepared_select_case( env ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/bench/same_msg_in_different_states/000077500000000000000000000000001523065210100254235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/same_msg_in_different_states/CMakeLists.txt000066400000000000000000000002431523065210100301620ustar00rootroot00000000000000add_executable(_test.bench.so_5.same_msg_in_different_states main.cpp) target_link_libraries(_test.bench.so_5.same_msg_in_different_states sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/same_msg_in_different_states/main.cpp000066400000000000000000000055301523065210100270560ustar00rootroot00000000000000/* */ #include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif struct msg_tick : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env, std::size_t states_count, int tick_count ) : so_5::agent_t( env ) , m_self_mbox( env.create_mbox() ) , m_tick_count( tick_count ) , m_messages_received( 0 ) { for( size_t i = 0; i != states_count; ++i ) m_states.emplace_back( std::make_shared< so_5::state_t >( self_ptr(), "noname" ) ); m_it_current_state = m_states.begin(); } void so_define_agent() override { for( auto s : m_states ) so_subscribe( m_self_mbox ) .in( *s ) .event( &a_test_t::evt_tick ); } void so_evt_start() override { m_benchmarker.start(); so_change_state( *(m_states.front()) ); so_5::send< msg_tick >( m_self_mbox ); } void evt_tick( mhood_t< msg_tick > ) { ++m_messages_received; ++m_it_current_state; if( m_it_current_state == m_states.end() ) { --m_tick_count; m_it_current_state = m_states.begin(); } if( m_tick_count > 0 ) { so_change_state( **m_it_current_state ); so_5::send< msg_tick >( m_self_mbox ); } else { m_benchmarker.finish_and_show_stats( m_messages_received, "messages" ); so_environment().stop(); } } private : const so_5::mbox_t m_self_mbox; int m_tick_count; std::uint_fast64_t m_messages_received; std::vector< std::shared_ptr< so_5::state_t > > m_states; std::vector< std::shared_ptr< so_5::state_t > >::iterator m_it_current_state; benchmarker_t m_benchmarker; }; int main( int argc, char ** argv ) { try { std::size_t max_states = 16; int tick_count = 100000; if( 3 == argc ) { max_states = static_cast< std::size_t >(std::atoi( argv[1] )); ensure( max_states > 0, "max_states must be >= 1" ); tick_count = std::atoi( argv[2] ); ensure( tick_count > 0, "tick_count must be >= 1" ); } for( std::size_t states = 1; states <= max_states; states *= 2 ) { std::cout << "*** benchmark for " << states << " state(s) ***" << std::endl; so_5::launch( [states, tick_count]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( states, tick_count ) ); } ); tick_count /= 2; if( tick_count < 10 ) tick_count = 10; } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/bench/skynet1m/000077500000000000000000000000001523065210100213045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/skynet1m/CMakeLists.txt000066400000000000000000000002121523065210100240370ustar00rootroot00000000000000set(BENCHMARK _test.bench.so_5.skynet1m) add_executable(${BENCHMARK} main.cpp) target_link_libraries(${BENCHMARK} sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/skynet1m/main.cpp000066400000000000000000000047551523065210100227470ustar00rootroot00000000000000#include #include #include using namespace std; using namespace so_5; const unsigned int divider = 10; using number = unsigned long long; using disp_handle = disp::thread_pool::dispatcher_handle_t; disp::thread_pool::bind_params_t bind_params() { return disp::thread_pool::bind_params_t{} .fifo( disp::thread_pool::fifo_t::cooperation ) .max_demands_at_once( divider * 3 ) ; } class skynet final : public agent_t { public : skynet( context_t ctx, disp_handle disp, mbox_t parent, number num, unsigned int size ) : agent_t{ ctx } , m_disp{ std::move(disp) } , m_parent{ std::move(parent) } , m_num{ num } , m_size{ size } {} virtual void so_evt_start() override { if( 1u == m_size ) send< number >( m_parent, m_num ); else { so_subscribe_self().event( &skynet::on_number ); create_agents(); } } private : const disp_handle m_disp; const mbox_t m_parent; const number m_num; const unsigned int m_size; number m_sum = 0; unsigned int m_received = 0; so_5::coop_handle_t m_child; void on_number( number v ) { m_sum += v; if( ++m_received == divider ) { so_environment().deregister_coop( m_child, dereg_reason::normal ); send< number >( m_parent, m_sum ); } } void create_agents() { so_environment().introduce_coop( m_disp.binder( bind_params() ), [&]( coop_t & coop ) { m_child = coop.handle(); const auto subsize = m_size / divider; coop.reserve( divider ); for( unsigned int i = 0; i != divider; ++i ) coop.make_agent< skynet >( m_disp, so_direct_mbox(), m_num + i * subsize, subsize ); } ); } }; size_t pool_size() { const auto c = thread::hardware_concurrency(); return c > 1 ? c - 1 : 1; } int main() { number result = 0; using clock_type = std::chrono::high_resolution_clock; const auto start_at = clock_type::now(); so_5::launch( [&result]( environment_t & env ) { auto tp_disp = disp::thread_pool::make_dispatcher( env, pool_size() ); auto result_ch = env.create_mchain( make_unlimited_mchain_params() ); env.introduce_coop( tp_disp.binder(), [&]( coop_t & coop ) { coop.make_agent< skynet >( tp_disp, result_ch->as_mbox(), 0u, 1000000u ); } ); receive( from(result_ch).handle_n(1), [&result]( number v ) { result = v; } ); env.stop(); } ); const auto finish_at = clock_type::now(); std::cout << "result: " << result << ", time: " << chrono::duration_cast< chrono::milliseconds >( finish_at - start_at ).count() << "ms" << std::endl; } sobjectizer-5.8.6/dev/test/so_5/bench/state_time_limit/000077500000000000000000000000001523065210100230655ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/state_time_limit/CMakeLists.txt000066400000000000000000000002221523065210100256210ustar00rootroot00000000000000set(BENCHMARK _test.bench.so_5.state_time_limit) add_executable(${BENCHMARK} main.cpp) target_link_libraries(${BENCHMARK} sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/state_time_limit/main.cpp000066400000000000000000000056721523065210100245270ustar00rootroot00000000000000#include #include #include #include #include #include #include #include struct cfg_t { unsigned int m_iterations = 50000u; int m_time_limit_ms = 100; }; [[nodiscard]] cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last_arg = argv + argc; current != last_arg; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.state_time_limit \n" "\noptions:\n" "-i, --iterations how many times time_limit has to be called\n" "-t, --time-limit value of time_limit to be used (in milliseconds)\n" "-h, --help show this help" << std::endl; std::exit( 1 ); } else if( is_arg( *current, "-i", "--iterations" ) ) mandatory_arg_to_value( tmp_cfg.m_iterations, ++current, last_arg, "-i", "number of iterations" ); else if( is_arg( *current, "-t", "--time-limit" ) ) mandatory_arg_to_value( tmp_cfg.m_time_limit_ms, ++current, last_arg, "-t", "number of milliseconds for time_limit" ); else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } return tmp_cfg; } class a_benchmarker_t : public so_5::agent_t { state_t st_working{ this, "working" }; public : a_benchmarker_t( context_t ctx, cfg_t cfg ) : so_5::agent_t{ ctx } , m_cfg{ std::move(cfg) } {} void so_evt_start() override { this >>= st_working; benchmarker_t bench; bench.start(); for( unsigned int i = 0u; i != m_cfg.m_iterations; ++i) { st_working.time_limit( std::chrono::milliseconds{ m_cfg.m_time_limit_ms }, so_default_state() ); } bench.finish_and_show_stats( m_cfg.m_iterations, "st_working.time_limit" ); so_deregister_agent_coop_normally(); } private : const cfg_t m_cfg; }; void show_cfg( const cfg_t & cfg ) { std::cout << "Configuration: " << "iterations: " << cfg.m_iterations << ", time_limit: " << cfg.m_time_limit_ms << "ms" << std::endl; } void run_sobjectizer( const cfg_t & cfg ) { so_5::launch( [&cfg]( so_5::environment_t & env ) { env.introduce_coop( [&cfg]( so_5::coop_t & coop ) { coop.make_agent< a_benchmarker_t >( cfg ); } ); }, []( so_5::environment_params_t & params ) { // This timer thread doesn't consume resources without // actual delayed/periodic messages. params.timer_thread( so_5::timer_list_factory() ); } ); } int main( int argc, char ** argv ) { try { const cfg_t cfg = try_parse_cmdline( argc, argv ); show_cfg( cfg ); run_sobjectizer( cfg ); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/bench/subscribe_unsubscribe/000077500000000000000000000000001523065210100241165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/subscribe_unsubscribe/CMakeLists.txt000066400000000000000000000002251523065210100266550ustar00rootroot00000000000000add_executable(_test.bench.so_5.subscribe_unsubscribe main.cpp) target_link_libraries(_test.bench.so_5.subscribe_unsubscribe sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/subscribe_unsubscribe/main.cpp000066400000000000000000000402311523065210100255460ustar00rootroot00000000000000/* * A benchmark for subscription/unsubscription operations. */ #include #include #include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif namespace benchmark { enum class subscr_storage_type_t { vector_based, map_based, hash_table_based, flat_set_based }; const char * subscr_storage_name( subscr_storage_type_t type ) { if( subscr_storage_type_t::vector_based == type ) return "vector_based"; else if( subscr_storage_type_t::map_based == type ) return "map_based"; else if( subscr_storage_type_t::hash_table_based == type ) return "hash_table_based"; else return "flat_set_based"; } struct cfg_t { std::size_t m_agents = 32; std::size_t m_iterations = 10; std::size_t m_loops = 20; subscr_storage_type_t m_subscr_storage = subscr_storage_type_t::map_based; }; cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last = argv + argc; current != last; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.subscribe_unsubscribe \n" "\noptions:\n" "-a, --agents count of agents\n" "-i, --iterations count of iterations for subscribe/unsubscribe\n" " operations for every agent\n" "-l, --loops loops to be done\n" "-s, --storage-type type of subscription storage\n" " allowed values: vector, map, hash, flat_set\n" "-h, --help show this description\n" << std::endl; std::exit(1); } else if( is_arg( *current, "-a", "--agents" ) ) mandatory_arg_to_value( tmp_cfg.m_agents, ++current, last, "-a", "count of agents" ); else if( is_arg( *current, "-i", "--iterations" ) ) mandatory_arg_to_value( tmp_cfg.m_iterations, ++current, last, "-i", "count of iterations for subscribe/unsubscribe" ); else if( is_arg( *current, "-l", "--loops" ) ) mandatory_arg_to_value( tmp_cfg.m_loops, ++current, last, "-l", "loops to be done" ); else if( is_arg( *current, "-s", "--storage-type" ) ) { std::string type; mandatory_arg_to_value( type, ++current, last, "-s", "type of subscription storage" ); if( "vector" == type ) tmp_cfg.m_subscr_storage = subscr_storage_type_t::vector_based; else if( "map" == type ) tmp_cfg.m_subscr_storage = subscr_storage_type_t::map_based; else if( "hash" == type ) tmp_cfg.m_subscr_storage = subscr_storage_type_t::hash_table_based; else if( "flat_set" == type ) tmp_cfg.m_subscr_storage = subscr_storage_type_t::flat_set_based; else throw std::runtime_error( std::string( "unsupported subscription storage type: " ) + type ); } else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } return tmp_cfg; } // Types to be used for subscription/unsubscription operations. struct olivia_msg final : public so_5::signal_t {}; struct emma_msg final : public so_5::signal_t {}; struct charlotte_msg final : public so_5::signal_t {}; struct amelia_msg final : public so_5::signal_t {}; struct sophia_msg final : public so_5::signal_t {}; struct isabella_msg final : public so_5::signal_t {}; struct ava_msg final : public so_5::signal_t {}; struct mia_msg final : public so_5::signal_t {}; struct evelyn_msg final : public so_5::signal_t {}; struct luna_msg final : public so_5::signal_t {}; struct harper_msg final : public so_5::signal_t {}; struct camila_msg final : public so_5::signal_t {}; struct sofia_msg final : public so_5::signal_t {}; struct scarlett_msg final : public so_5::signal_t {}; struct elizabeth_msg final : public so_5::signal_t {}; struct eleanor_msg final : public so_5::signal_t {}; struct emily_msg final : public so_5::signal_t {}; struct chloe_msg final : public so_5::signal_t {}; struct mila_msg final : public so_5::signal_t {}; struct violet_msg final : public so_5::signal_t {}; struct penelope_msg final : public so_5::signal_t {}; struct gianna_msg final : public so_5::signal_t {}; struct aria_msg final : public so_5::signal_t {}; struct abigail_msg final : public so_5::signal_t {}; struct ella_msg final : public so_5::signal_t {}; struct avery_msg final : public so_5::signal_t {}; struct hazel_msg final : public so_5::signal_t {}; struct nora_msg final : public so_5::signal_t {}; struct layla_msg final : public so_5::signal_t {}; struct lily_msg final : public so_5::signal_t {}; struct aurora_msg final : public so_5::signal_t {}; struct nova_msg final : public so_5::signal_t {}; struct ellie_msg final : public so_5::signal_t {}; struct madison_msg final : public so_5::signal_t {}; struct grace_msg final : public so_5::signal_t {}; struct isla_msg final : public so_5::signal_t {}; struct willow_msg final : public so_5::signal_t {}; struct zoe_msg final : public so_5::signal_t {}; struct riley_msg final : public so_5::signal_t {}; struct stella_msg final : public so_5::signal_t {}; struct eliana_msg final : public so_5::signal_t {}; struct ivy_msg final : public so_5::signal_t {}; struct victoria_msg final : public so_5::signal_t {}; struct emilia_msg final : public so_5::signal_t {}; struct zoey_msg final : public so_5::signal_t {}; struct naomi_msg final : public so_5::signal_t {}; struct hannah_msg final : public so_5::signal_t {}; struct lucy_msg final : public so_5::signal_t {}; struct elena_msg final : public so_5::signal_t {}; struct lillian_msg final : public so_5::signal_t {}; struct maya_msg final : public so_5::signal_t {}; struct leah_msg final : public so_5::signal_t {}; struct paisley_msg final : public so_5::signal_t {}; struct addison_msg final : public so_5::signal_t {}; struct natalie_msg final : public so_5::signal_t {}; struct valentina_msg final : public so_5::signal_t {}; struct everly_msg final : public so_5::signal_t {}; struct delilah_msg final : public so_5::signal_t {}; struct leilani_msg final : public so_5::signal_t {}; struct madelyn_msg final : public so_5::signal_t {}; struct kinsley_msg final : public so_5::signal_t {}; struct ruby_msg final : public so_5::signal_t {}; struct sophie_msg final : public so_5::signal_t {}; struct alice_msg final : public so_5::signal_t {}; struct msg_next_loop : public so_5::signal_t {}; class a_worker_t : public so_5::agent_t { public : a_worker_t( context_t ctx, std::size_t iterations, so_5::subscription_storage_factory_t subscr_storage_factory ) : so_5::agent_t{ ctx + subscr_storage_factory } , m_iterations{ iterations } {} void set_next( so_5::mbox_t next ) { m_next = std::move(next); } void so_define_agent() override { so_subscribe_self().event( &a_worker_t::evt_next_loop ); } protected : const std::size_t m_iterations; so_5::mbox_t m_next; template< typename Signal > void make_subscription() { so_subscribe_self().event( [](mhood_t) { /* Nothing to do */ } ); } template< typename Signal > void drop_subscription() { so_drop_subscription( so_direct_mbox() ); } void evt_next_loop( mhood_t< msg_next_loop > ) { for( std::size_t i = 0; i != m_iterations; ++i ) { // Subscription part. make_subscription< olivia_msg >(); make_subscription< emma_msg >(); make_subscription< charlotte_msg >(); make_subscription< amelia_msg >(); make_subscription< sophia_msg >(); make_subscription< isabella_msg >(); make_subscription< ava_msg >(); make_subscription< mia_msg >(); make_subscription< evelyn_msg >(); make_subscription< luna_msg >(); make_subscription< harper_msg >(); make_subscription< camila_msg >(); make_subscription< sofia_msg >(); make_subscription< scarlett_msg >(); make_subscription< elizabeth_msg >(); make_subscription< eleanor_msg >(); make_subscription< emily_msg >(); make_subscription< chloe_msg >(); make_subscription< mila_msg >(); make_subscription< violet_msg >(); make_subscription< penelope_msg >(); make_subscription< gianna_msg >(); make_subscription< aria_msg >(); make_subscription< abigail_msg >(); make_subscription< ella_msg >(); make_subscription< avery_msg >(); make_subscription< hazel_msg >(); make_subscription< nora_msg >(); make_subscription< layla_msg >(); make_subscription< lily_msg >(); make_subscription< aurora_msg >(); make_subscription< nova_msg >(); make_subscription< ellie_msg >(); make_subscription< madison_msg >(); make_subscription< grace_msg >(); make_subscription< isla_msg >(); make_subscription< willow_msg >(); make_subscription< zoe_msg >(); make_subscription< riley_msg >(); make_subscription< stella_msg >(); make_subscription< eliana_msg >(); make_subscription< ivy_msg >(); make_subscription< victoria_msg >(); make_subscription< emilia_msg >(); make_subscription< zoey_msg >(); make_subscription< naomi_msg >(); make_subscription< hannah_msg >(); make_subscription< lucy_msg >(); make_subscription< elena_msg >(); make_subscription< lillian_msg >(); make_subscription< maya_msg >(); make_subscription< leah_msg >(); make_subscription< paisley_msg >(); make_subscription< addison_msg >(); make_subscription< natalie_msg >(); make_subscription< valentina_msg >(); make_subscription< everly_msg >(); make_subscription< delilah_msg >(); make_subscription< leilani_msg >(); make_subscription< madelyn_msg >(); make_subscription< kinsley_msg >(); make_subscription< ruby_msg >(); make_subscription< sophie_msg >(); make_subscription< alice_msg >(); // Unsubscription part. drop_subscription< zoey_msg >(); drop_subscription< hannah_msg >(); drop_subscription< lillian_msg >(); drop_subscription< eleanor_msg >(); drop_subscription< madelyn_msg >(); drop_subscription< leilani_msg >(); drop_subscription< emma_msg >(); drop_subscription< paisley_msg >(); drop_subscription< madison_msg >(); drop_subscription< violet_msg >(); drop_subscription< alice_msg >(); drop_subscription< maya_msg >(); drop_subscription< ava_msg >(); drop_subscription< riley_msg >(); drop_subscription< eliana_msg >(); drop_subscription< elizabeth_msg >(); drop_subscription< charlotte_msg >(); drop_subscription< elena_msg >(); drop_subscription< gianna_msg >(); drop_subscription< chloe_msg >(); drop_subscription< mia_msg >(); drop_subscription< victoria_msg >(); drop_subscription< willow_msg >(); drop_subscription< kinsley_msg >(); drop_subscription< grace_msg >(); drop_subscription< sophia_msg >(); drop_subscription< mila_msg >(); drop_subscription< sophie_msg >(); drop_subscription< amelia_msg >(); drop_subscription< isabella_msg >(); drop_subscription< natalie_msg >(); drop_subscription< everly_msg >(); drop_subscription< emilia_msg >(); drop_subscription< layla_msg >(); drop_subscription< naomi_msg >(); drop_subscription< ruby_msg >(); drop_subscription< lucy_msg >(); drop_subscription< sofia_msg >(); drop_subscription< stella_msg >(); drop_subscription< nora_msg >(); drop_subscription< penelope_msg >(); drop_subscription< camila_msg >(); drop_subscription< ella_msg >(); drop_subscription< aria_msg >(); drop_subscription< ivy_msg >(); drop_subscription< aurora_msg >(); drop_subscription< ellie_msg >(); drop_subscription< emily_msg >(); drop_subscription< leah_msg >(); drop_subscription< zoe_msg >(); drop_subscription< valentina_msg >(); drop_subscription< isla_msg >(); drop_subscription< harper_msg >(); drop_subscription< avery_msg >(); drop_subscription< nova_msg >(); drop_subscription< abigail_msg >(); drop_subscription< hazel_msg >(); drop_subscription< evelyn_msg >(); drop_subscription< olivia_msg >(); drop_subscription< addison_msg >(); drop_subscription< lily_msg >(); drop_subscription< scarlett_msg >(); drop_subscription< luna_msg >(); drop_subscription< delilah_msg >(); } so_5::send< msg_next_loop >( m_next ); } }; class a_first_worker_t final : public a_worker_t { public: a_first_worker_t( context_t ctx, std::size_t loops, std::size_t iterations, so_5::subscription_storage_factory_t subscr_storage_factory ) : a_worker_t{ std::move(ctx), iterations, std::move(subscr_storage_factory) } , m_loops{ loops } {} void so_define_agent() override { so_subscribe_self() .event( &a_first_worker_t::evt_custom_next_loop ) ; } void so_evt_start() override { so_5::send< msg_next_loop >( *this ); } private: const std::size_t m_loops; std::size_t m_loop_index{}; benchmarker_t m_benchmark; void evt_custom_next_loop( mhood_t< msg_next_loop > cmd ) { const bool do_actions = [this]() { if( 0u == m_loop_index ) { // Start of the benchmark. m_benchmark.start(); return true; } else if( m_loops == m_loop_index ) { // Benchmark has to be completed. m_benchmark.finish_and_show_stats( static_cast( m_loops ) * m_iterations, "iterations" ); so_deregister_agent_coop_normally(); return false; } else return true; }(); if( do_actions ) { ++m_loop_index; // Let the base class do the main work. evt_next_loop( cmd ); } } }; so_5::subscription_storage_factory_t factory_by_cfg( const cfg_t & cfg ) { using namespace so_5; constexpr std::size_t default_initial_capacity = 65; const auto type = cfg.m_subscr_storage; if( subscr_storage_type_t::vector_based == type ) return vector_based_subscription_storage_factory( default_initial_capacity ); else if( subscr_storage_type_t::map_based == type ) return map_based_subscription_storage_factory(); else if( subscr_storage_type_t::hash_table_based == type ) return hash_table_based_subscription_storage_factory(); else return flat_set_based_subscription_storage_factory( default_initial_capacity ); } void run_benchmark( so_5::environment_t & env, const cfg_t & cfg ) { env.introduce_coop( [&cfg]( so_5::coop_t & coop ) { auto factory = factory_by_cfg( cfg ); std::vector< a_worker_t * > workers; workers.reserve( cfg.m_agents ); workers.push_back( coop.make_agent< a_first_worker_t >( cfg.m_loops, cfg.m_iterations, factory ) ); for( std::size_t i = 1; i != cfg.m_agents; ++i ) workers.push_back( coop.make_agent< a_worker_t >( cfg.m_iterations, factory ) ); for( std::size_t i = 0; i != cfg.m_agents; ++i ) workers[ i ]->set_next( workers[ (i + 1) % cfg.m_agents ]->so_direct_mbox() ); } ); } } /* namespace benchmark */ using namespace benchmark; int main( int argc, char ** argv ) { try { cfg_t cfg = try_parse_cmdline( argc, argv ); if( cfg.m_agents < 2u ) { std::ostringstream ss; ss << "number of agents is too small: " << cfg.m_agents; throw std::logic_error( ss.str() ); } std::cout << "* agents: " << cfg.m_agents << "\n" << "* iterations: " << cfg.m_iterations << "\n" << "* loops: " << cfg.m_loops << "\n" << "* subscr_storage: " << subscr_storage_name( cfg.m_subscr_storage ) << std::endl; so_5::launch( [cfg]( so_5::environment_t & env ) { run_benchmark( env, cfg ); }, []( so_5::environment_params_t & params ) { // This timer thread doesn't consume resources without // actual delayed/periodic messages. params.timer_thread( so_5::timer_list_factory() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/bench/thread_pool_disp/000077500000000000000000000000001523065210100230505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/bench/thread_pool_disp/CMakeLists.txt000066400000000000000000000002131523065210100256040ustar00rootroot00000000000000add_executable(_test.bench.so_5.thread_pool_disp main.cpp) target_link_libraries(_test.bench.so_5.thread_pool_disp sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/bench/thread_pool_disp/main.cpp000066400000000000000000000257631523065210100245150ustar00rootroot00000000000000/* * A benchmark for thread_pool dispatcher. */ #include #include #include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif std::size_t default_thread_pool_size() { auto c = std::thread::hardware_concurrency(); if( !c ) c = 4; return c; } enum class dispatcher_t { thread_pool, adv_thread_pool }; enum class lock_type_t { combined_lock, simple_lock }; struct cfg_t { std::size_t m_cooperations = 1024; std::size_t m_agents = 512; std::size_t m_messages = 100; std::size_t m_demands_at_once = 0; std::size_t m_threads = 0; bool m_individual_fifo = false; dispatcher_t m_dispatcher = dispatcher_t::thread_pool; std::size_t m_messages_to_send_at_start = 1; lock_type_t m_lock_type = lock_type_t::combined_lock; bool m_track_activity = false; }; cfg_t try_parse_cmdline( int argc, char ** argv ) { cfg_t tmp_cfg; for( char ** current = &argv[ 1 ], **last = argv + argc; current != last; ++current ) { if( is_arg( *current, "-h", "--help" ) ) { std::cout << "usage:\n" "_test.bench.so_5.thread_pool_disp \n" "\noptions:\n" "-c, --cooperations count of cooperations\n" "-a, --agents count of agents in cooperation\n" "-m, --messages count of messages for every agent\n" "-d, --demands-at-once count consequently processed demands\n" "-S, --messages-at-start count of messages to be sent at start\n" "-t, --threads size of thread pool\n" "-i, --individual-fifo use individual FIFO for agents\n" "-P, --adv-thread-pool use adv_thread_pool dispatcher\n" "-s, --simple-lock use simple_lock_factory for MPMC queue\n" "-T, --track-activity turn work thread activity tracking on\n" "-h, --help show this description\n" << std::endl; std::exit(1); } else if( is_arg( *current, "-c", "--cooperations" ) ) mandatory_arg_to_value( tmp_cfg.m_cooperations, ++current, last, "-c", "count of cooperations" ); else if( is_arg( *current, "-a", "--agents" ) ) mandatory_arg_to_value( tmp_cfg.m_agents, ++current, last, "-a", "count of agents in cooperation" ); else if( is_arg( *current, "-m", "--messages" ) ) mandatory_arg_to_value( tmp_cfg.m_messages, ++current, last, "-m", "count of messages for every agent" ); else if( is_arg( *current, "-d", "--demands-at-once" ) ) mandatory_arg_to_value( tmp_cfg.m_demands_at_once, ++current, last, "-d", "count of consequently processed demands" ); else if( is_arg( *current, "-S", "--messages-at-start" ) ) mandatory_arg_to_value( tmp_cfg.m_messages_to_send_at_start, ++current, last, "-S", "count of messages to be sent at start" ); else if( is_arg( *current, "-t", "--threads" ) ) mandatory_arg_to_value( tmp_cfg.m_threads, ++current, last, "-t", "size of thread pool" ); else if( is_arg( *current, "-i", "--individual-fifo" ) ) tmp_cfg.m_individual_fifo = true; else if( is_arg( *current, "-P", "--adv-thread-pool" ) ) tmp_cfg.m_dispatcher = dispatcher_t::adv_thread_pool; else if( is_arg( *current, "-s", "--simple-lock" ) ) tmp_cfg.m_lock_type = lock_type_t::simple_lock; else if( is_arg( *current, "-T", "--track-activity" ) ) tmp_cfg.m_track_activity = true; else throw std::runtime_error( std::string( "unknown argument: " ) + *current ); } if( tmp_cfg.m_messages_to_send_at_start >= tmp_cfg.m_messages ) throw std::runtime_error( "invalid number of messages to be sent at start: " + std::to_string( tmp_cfg.m_messages_to_send_at_start ) + " (total messages to send: " + std::to_string( tmp_cfg.m_messages ) + ")" ); return tmp_cfg; } inline std::size_t total_messages( const cfg_t & cfg ) { const auto total_agents = cfg.m_agents * cfg.m_cooperations; return total_agents + total_agents + total_agents * cfg.m_messages; } struct msg_start : public so_5::signal_t {}; struct msg_shutdown : public so_5::signal_t {}; struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env, const so_5::mbox_t & controller_mbox, std::size_t total_messages_to_send, std::size_t messages_at_start ) : so_5::agent_t( env ) , m_controller_mbox( controller_mbox ) , m_messages_to_send( total_messages_to_send ) , m_messages_at_start( messages_at_start ) { } void so_define_agent() override { so_subscribe( m_controller_mbox ) .event( &a_test_t::evt_start ); so_subscribe_self().event( &a_test_t::evt_hello ); } void evt_start(mhood_t< msg_start >) { for( m_messages_sent = 0; m_messages_sent != m_messages_at_start; ++m_messages_sent ) so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { ++m_messages_received; if( m_messages_received >= m_messages_to_send ) so_5::send< msg_shutdown >( m_controller_mbox ); else if( m_messages_sent < m_messages_to_send ) { so_5::send< msg_hello >( *this ); ++m_messages_sent; } } private : const so_5::mbox_t m_controller_mbox; const std::size_t m_messages_to_send; const std::size_t m_messages_at_start; std::size_t m_messages_sent = 0; std::size_t m_messages_received = 0; }; class a_contoller_t : public so_5::agent_t { public : a_contoller_t( so_5::environment_t & env, cfg_t cfg ) : so_5::agent_t( env ) , m_cfg( std::move( cfg ) ) , m_working_agents( cfg.m_cooperations * cfg.m_agents ) , m_self_mbox( env.create_mbox() ) {} void so_define_agent() override { so_subscribe( m_self_mbox ) .event( &a_contoller_t::evt_shutdown ); } void so_evt_start() override { create_cooperations(); m_benchmarker.start(); so_5::send< msg_start >( m_self_mbox ); } void evt_shutdown(mhood_t< msg_shutdown >) { --m_working_agents; if( !m_working_agents ) { m_benchmarker.finish_and_show_stats( total_messages( m_cfg ), "messages" ); m_shutdown_duration.reset( new duration_meter_t( "shutdown" ) ); so_environment().stop(); } } private : const cfg_t m_cfg; std::size_t m_working_agents; const so_5::mbox_t m_self_mbox; so_5::disp_binder_shptr_t m_binder; benchmarker_t m_benchmarker; std::unique_ptr< duration_meter_t > m_shutdown_duration; void create_cooperations() { duration_meter_t duration( "creating cooperations" ); for( std::size_t i = 0; i != m_cfg.m_cooperations; ++i ) { std::ostringstream ss; ss << "coop_" << i; auto c = so_5::create_child_coop( *this, create_binder_if_necessary() ); for( std::size_t a = 0; a != m_cfg.m_agents; ++a ) { c->make_agent< a_test_t >( m_self_mbox, m_cfg.m_messages, m_cfg.m_messages_to_send_at_start ); } so_environment().register_coop( std::move( c ) ); } } so_5::disp_binder_shptr_t create_binder_if_necessary() { if( m_binder ) return m_binder; const std::size_t threads = m_cfg.m_threads ? m_cfg.m_threads : default_thread_pool_size(); if( dispatcher_t::thread_pool == m_cfg.m_dispatcher ) { using namespace so_5::disp::thread_pool; const auto disp_params = [&] { disp_params_t params; params.thread_count( threads ); if( lock_type_t::simple_lock == m_cfg.m_lock_type ) params.set_queue_params( queue_traits::queue_params_t{} .lock_factory( queue_traits::simple_lock_factory() ) ); return params; }; const auto bind_params = [&] { bind_params_t params; if( m_cfg.m_individual_fifo ) params.fifo( fifo_t::individual ); if( m_cfg.m_demands_at_once ) params.max_demands_at_once( m_cfg.m_demands_at_once ); return params; }; m_binder = make_dispatcher( so_environment(), "thread_pool", disp_params() ) .binder( bind_params() ); } else { using namespace so_5::disp::adv_thread_pool; const auto disp_params = [&] { disp_params_t params; params.thread_count( threads ); if( lock_type_t::simple_lock == m_cfg.m_lock_type ) params.set_queue_params( queue_traits::queue_params_t{} .lock_factory( queue_traits::simple_lock_factory() ) ); return params; }; const auto bind_params = [&] { bind_params_t params; if( m_cfg.m_individual_fifo ) params.fifo( fifo_t::individual ); return params; }; m_binder = make_dispatcher( so_environment(), "thread_pool", disp_params() ) .binder( bind_params() ); } return m_binder; } }; void show_cfg( const cfg_t & cfg ) { std::cout << "coops: " << cfg.m_cooperations << ", agents in coop: " << cfg.m_agents << ", msg per agent: " << cfg.m_messages << " (at start: " << cfg.m_messages_to_send_at_start << ")" << ", total msgs: " << total_messages( cfg ) << std::endl; std::cout << "\n" "dispatcher: " << (dispatcher_t::thread_pool == cfg.m_dispatcher ? "thread_pool" : "adv_thread_pool") << std::endl; std::cout << " MPMC queue lock: " << (lock_type_t::combined_lock == cfg.m_lock_type ? "combined" : "simple") << std::endl; if( dispatcher_t::thread_pool == cfg.m_dispatcher ) { std::cout << "\n*** demands_at_once: "; if( cfg.m_demands_at_once ) std::cout << cfg.m_demands_at_once; else std::cout << "default (" << so_5::disp::thread_pool::bind_params_t().query_max_demands_at_once() << ")"; } std::cout << "\n*** threads in pool: "; if( cfg.m_threads ) std::cout << cfg.m_threads; else std::cout << "default (" << default_thread_pool_size() << ")"; std::cout << "\n*** FIFO: "; if( cfg.m_individual_fifo ) std::cout << "individual"; else { std::cout << "default ("; if( so_5::disp::thread_pool::fifo_t::cooperation == so_5::disp::thread_pool::bind_params_t().query_fifo() ) std::cout << "cooperation"; else std::cout << "individual"; std::cout << ")"; } std::cout << "\n*** activity tracking: " << (cfg.m_track_activity ? "on" : "off"); std::cout << std::endl; } int main( int argc, char ** argv ) { try { cfg_t cfg = try_parse_cmdline( argc, argv ); show_cfg( cfg ); so_5::launch( [cfg]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_contoller_t >( cfg ) ); }, [cfg]( so_5::environment_params_t & params ) { if( cfg.m_track_activity ) params.turn_work_thread_activity_tracking_on(); // This timer thread doesn't consume resources without // actual delayed/periodic messages. params.timer_thread( so_5::timer_list_factory() ); }); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/000077500000000000000000000000001523065210100174125ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/CMakeLists.txt000066400000000000000000000020431523065210100221510ustar00rootroot00000000000000add_subdirectory(dereg_empty_coop) add_subdirectory(dereg_already_deregistered) add_subdirectory(reg_some_and_stop_1) add_subdirectory(reg_some_and_stop_2) add_subdirectory(reg_some_and_stop_3) add_subdirectory(throw_on_define_agent) add_subdirectory(unknown_exception_define_agent) add_subdirectory(throw_on_bind_to_disp) add_subdirectory(throw_on_bind_to_disp_2) add_subdirectory(coop_notify_1) add_subdirectory(coop_notify_2) add_subdirectory(coop_notify_3) add_subdirectory(parent_child_1) add_subdirectory(parent_child_2) add_subdirectory(parent_child_3) add_subdirectory(parent_child_4) add_subdirectory(user_resource) add_subdirectory(introduce_coop) add_subdirectory(introduce_coop_2) add_subdirectory(introduce_coop_3) add_subdirectory(introduce_coop_4) add_subdirectory(create_child_coop_5_5_8) add_subdirectory(shutdown_while_reg) add_subdirectory(child_coop_map) add_subdirectory(issue_28) add_subdirectory(msg_loss_at_registration) add_subdirectory(destruction_order_1) add_subdirectory(this_agent_disp_binder) add_subdirectory(coop_disp_binder) sobjectizer-5.8.6/dev/test/so_5/coop/child_coop_map/000077500000000000000000000000001523065210100223525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/child_coop_map/CMakeLists.txt000066400000000000000000000001371523065210100251130ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.child_coop_map) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/child_coop_map/main.cpp000066400000000000000000000035111523065210100240020ustar00rootroot00000000000000/* * A unit-test for testing deregistration of empty coop. */ #include #include #include class a_child_t final : public so_5::agent_t { struct die_t final : public so_5::signal_t {}; const std::chrono::milliseconds m_work_time; public: a_child_t( context_t ctx, std::chrono::milliseconds work_time ) : so_5::agent_t{ std::move(ctx) } , m_work_time{ work_time } {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send_delayed< die_t >( *this, m_work_time ); } }; class a_supervisor_t final : public so_5::agent_t { std::set< so_5::coop_handle_t > m_children; public : using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( [this](mhood_t cmd) { m_children.erase( cmd->m_coop ); if( m_children.empty() ) { so_deregister_agent_coop_normally(); } } ); } void so_evt_start() override { for( int i = 0; i != 10; ++i ) { so_5::introduce_child_coop( *this, [this, i]( so_5::coop_t & coop ) { m_children.insert( coop.handle() ); coop.make_agent< a_child_t >( std::chrono::milliseconds{ i+1 } * 5 ); coop.add_dereg_notificator( so_5::make_coop_dereg_notificator( so_direct_mbox() ) ); } ); } } }; void init( so_5::environment_t & env ) { // Supervison coop. env.introduce_coop( []( so_5::coop_t & parent ) { parent.make_agent< a_supervisor_t >(); } ); } int main() { try { run_with_time_limit( []{ so_5::launch( init ); }, 20 ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/coop/coop_disp_binder/000077500000000000000000000000001523065210100227145ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/coop_disp_binder/CMakeLists.txt000066400000000000000000000001411523065210100254500ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.coop_disp_binder) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/coop_disp_binder/main.cpp000066400000000000000000000060431523065210100243470ustar00rootroot00000000000000/* * A unit-test for agent_t::so_this_agent_disp_binder(). */ #include #include #include namespace test { struct msg_done final : public so_5::signal_t {}; enum class who_t { parent, child }; struct msg_take_tid final : public so_5::message_t { so_5::current_thread_id_t m_tid; who_t m_who; explicit msg_take_tid( so_5::current_thread_id_t tid, who_t who ) : m_tid{ tid } , m_who{ who } {} }; class a_collector_t final : public so_5::agent_t { so_5::current_thread_id_t m_self_tid; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_collector_t::evt_done ) .event( &a_collector_t::evt_take_tid ) ; } void so_evt_start() override { m_self_tid = so_5::query_current_thread_id(); } private: void evt_done( mhood_t< msg_done > ) { so_deregister_agent_coop_normally(); } void evt_take_tid( mhood_t< msg_take_tid > cmd ) { switch( cmd->m_who ) { case who_t::parent: ensure_or_die( m_self_tid != cmd->m_tid, "parent has to work on a separate thread!" ); break; case who_t::child: ensure_or_die( m_self_tid == cmd->m_tid, "child has to work on the same thread!" ); break; } } }; class a_child_t final : public so_5::agent_t { const so_5::mbox_t m_collector_mbox; public: a_child_t( context_t ctx, so_5::mbox_t collector_mbox ) : so_5::agent_t{ std::move(ctx) } , m_collector_mbox{ std::move(collector_mbox) } {} void so_evt_start() override { so_5::send< msg_take_tid >( m_collector_mbox, so_5::query_current_thread_id(), who_t::child ); so_5::send< msg_done >( m_collector_mbox ); } }; class a_parent_t final : public so_5::agent_t { const so_5::mbox_t m_collector_mbox; public: a_parent_t( context_t ctx, so_5::mbox_t collector_mbox ) : so_5::agent_t{ std::move(ctx) } , m_collector_mbox{ std::move(collector_mbox) } {} void so_evt_start() override { so_5::send< msg_take_tid >( m_collector_mbox, so_5::query_current_thread_id(), who_t::parent ); so_5::introduce_child_coop( *this, so_this_coop_disp_binder(), [this]( so_5::coop_t & coop ) { coop.make_agent< a_child_t >( m_collector_mbox ); } ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( so_5::disp::one_thread::make_dispatcher( env ).binder(), [&]( so_5::coop_t & coop ) { auto mbox = coop.make_agent< a_collector_t >()->so_direct_mbox(); // The parent agent will use active_obj dispatcher. coop.make_agent_with_binder< a_parent_t >( so_5::disp::active_obj::make_dispatcher( env ).binder(), mbox ); } ); } } /* namespace test */ int main() { try { run_with_time_limit( []{ so_5::launch( test::init ); }, 5 ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/coop/coop_notify_1/000077500000000000000000000000001523065210100221625ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/coop_notify_1/CMakeLists.txt000066400000000000000000000001361523065210100247220ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.coop_notify_1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/coop_notify_1/main.cpp000066400000000000000000000062041523065210100236140ustar00rootroot00000000000000/* * A test for coop reg/dereg notifications. */ #include #include #include #include struct msg_child_deregistered : public so_5::signal_t {}; class a_child_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_child_t( so_5::environment_t & env ) : base_type_t( env ) { } void so_evt_start() override { so_deregister_agent_coop_normally(); } }; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, const so_5::coop_reg_notificator_t & reg_notificator, const so_5::coop_dereg_notificator_t & dereg_notificator ) : base_type_t( env ) , m_reg_notificator( reg_notificator ) , m_dereg_notificator( dereg_notificator ) , m_mbox( env.create_mbox() ) {} void so_define_agent() override { so_subscribe( m_mbox ).event( &a_test_t::evt_child_deregistered ); } void so_evt_start() override { auto child_coop = so_environment().make_coop( so_coop() ); child_coop->add_reg_notificator( [this]( auto & env, const auto & handle ) noexcept { m_reg_notificator( env, handle ); } ); child_coop->add_dereg_notificator( [this]( auto & env, const auto & handle, const auto & reason ) noexcept { m_dereg_notificator( env, handle, reason ); } ); child_coop->add_dereg_notificator( [this]( so_5::environment_t &, const so_5::coop_handle_t &, const so_5::coop_dereg_reason_t &) noexcept { so_5::send< msg_child_deregistered >( m_mbox ); } ); child_coop->make_agent< a_child_t >(); so_environment().register_coop( std::move( child_coop ) ); } void evt_child_deregistered( mhood_t< msg_child_deregistered > ) { so_environment().stop(); } private : const so_5::coop_reg_notificator_t m_reg_notificator; const so_5::coop_dereg_notificator_t m_dereg_notificator; const so_5::mbox_t m_mbox; }; class test_env_t { public : test_env_t() : m_reg_notify_received( false ) , m_dereg_notify_received( false ) {} void init( so_5::environment_t & env ) { auto on_reg = [this]( so_5::environment_t &, const so_5::coop_handle_t & ) { m_reg_notify_received = true; }; auto on_dereg = [this]( so_5::environment_t &, const so_5::coop_handle_t &, const so_5::coop_dereg_reason_t &) { m_dereg_notify_received = true; }; env.register_agent_as_coop( env.make_agent< a_test_t >( on_reg, on_dereg ) ); } void check_result() const { if( !m_reg_notify_received ) throw std::runtime_error( "registration notify not received" ); if( !m_dereg_notify_received ) throw std::runtime_error( "deregistration notify not received" ); } private : bool m_reg_notify_received; bool m_dereg_notify_received; }; int main() { run_with_time_limit( [] { test_env_t test_env; so_5::launch( [&test_env]( so_5::environment_t & env ) { test_env.init( env ); } ); test_env.check_result(); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/coop_notify_2/000077500000000000000000000000001523065210100221635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/coop_notify_2/CMakeLists.txt000066400000000000000000000001361523065210100247230ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.coop_notify_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/coop_notify_2/main.cpp000066400000000000000000000102461523065210100236160ustar00rootroot00000000000000/* * A test for coop reg/dereg notifications order. */ #include #include #include #include #include struct msg_child_deregistered : public so_5::signal_t {}; class a_child_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_child_t( so_5::environment_t & env ) : base_type_t( env ) { } void so_evt_start() override { so_deregister_agent_coop_normally(); } }; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, const so_5::coop_reg_notificator_t & reg_notificator, const so_5::coop_dereg_notificator_t & dereg_notificator ) : base_type_t( env ) , m_reg_notificator( reg_notificator ) , m_dereg_notificator( dereg_notificator ) , m_mbox( env.create_mbox() ) {} void so_define_agent() override { so_subscribe( m_mbox ).event( &a_test_t::evt_child_deregistered ); } void so_evt_start() override { auto child_coop = so_environment().make_coop( so_coop(), so_5::disp::active_obj::make_dispatcher( so_environment() ).binder() ); child_coop->add_reg_notificator( [this](auto & env, const auto & handle) noexcept { m_reg_notificator(env, handle); } ); child_coop->add_dereg_notificator( [this]( auto & env, const auto & handle, const auto & reason ) noexcept { m_dereg_notificator( env, handle, reason ); } ); child_coop->add_dereg_notificator( [this]( so_5::environment_t &, const so_5::coop_handle_t &, const so_5::coop_dereg_reason_t &) noexcept { so_5::send< msg_child_deregistered >( m_mbox ); } ); child_coop->make_agent< a_child_t >(); so_environment().register_coop( std::move( child_coop ) ); } void evt_child_deregistered( mhood_t< msg_child_deregistered > ) { so_environment().stop(); } private : const so_5::coop_reg_notificator_t m_reg_notificator; const so_5::coop_dereg_notificator_t m_dereg_notificator; const so_5::mbox_t m_mbox; }; class sequence_holder_t { public : typedef std::vector< std::string > seq_t; void add( const std::string & msg ) { std::lock_guard< std::mutex > lock( m_lock ); m_seq.push_back( msg ); } const seq_t sequence() const { return m_seq; } private : std::mutex m_lock; seq_t m_seq; }; std::string sequence_to_string( const sequence_holder_t::seq_t & what ) { std::string result; for( auto i = what.begin(); i != what.end(); ++i ) { if( i != what.begin() ) result += ","; result += *i; } return result; } class test_env_t { public : test_env_t() {} void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( create_on_reg_notificator(), create_on_dereg_notificator() ) ); } void check_result() const { sequence_holder_t::seq_t expected; expected.push_back( "on_reg_1" ); expected.push_back( "on_reg_2" ); expected.push_back( "on_dereg" ); if( m_sequence.sequence() != expected ) throw std::runtime_error( "Wrong notification sequence! " "actual: '" + sequence_to_string( m_sequence.sequence() ) + "', expected: '" + sequence_to_string( expected ) ); } private : sequence_holder_t m_sequence; so_5::coop_reg_notificator_t create_on_reg_notificator() { return [this]( so_5::environment_t &, const so_5::coop_handle_t & ) { m_sequence.add( "on_reg_1" ); // A timeout for child coop deregistration initiation. std::this_thread::sleep_for( std::chrono::seconds(1) ); m_sequence.add( "on_reg_2" ); }; } so_5::coop_dereg_notificator_t create_on_dereg_notificator() { return [this]( so_5::environment_t &, const so_5::coop_handle_t &, const so_5::coop_dereg_reason_t &) { m_sequence.add( "on_dereg" ); }; } }; int main() { run_with_time_limit( [] { test_env_t test_env; so_5::launch( [&test_env]( so_5::environment_t & env ) { test_env.init( env ); } ); test_env.check_result(); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/coop_notify_3/000077500000000000000000000000001523065210100221645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/coop_notify_3/CMakeLists.txt000066400000000000000000000001361523065210100247240ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.coop_notify_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/coop_notify_3/main.cpp000066400000000000000000000045561523065210100236260ustar00rootroot00000000000000/* * A test for stardard coop reg/dereg notificators. */ #include #include #include #include struct msg_child_deregistered : public so_5::signal_t {}; class a_child_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_child_t( so_5::environment_t & env ) : base_type_t( env ) { } }; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env ) : base_type_t( env ) , m_mbox( env.create_mbox() ) , m_cycle( 0 ) {} void so_define_agent() override { so_subscribe( m_mbox ).in( st_wait_registration ) .event( &a_test_t::evt_coop_registered ); so_subscribe( m_mbox ).in( st_wait_deregistration ) .event( &a_test_t::evt_coop_deregistered ); } void so_evt_start() override { so_change_state( st_wait_registration ); create_next_coop(); } void evt_coop_registered( mhood_t< so_5::msg_coop_registered > evt ) { std::cout << "registered: " << evt->m_coop << std::endl; so_change_state( st_wait_deregistration ); so_environment().deregister_coop( evt->m_coop, so_5::dereg_reason::normal ); } void evt_coop_deregistered( mhood_t< so_5::msg_coop_deregistered > evt ) { std::cout << "deregistered: " << evt->m_coop << std::endl; if( 5 == m_cycle ) so_environment().stop(); else { ++m_cycle; so_change_state( st_wait_registration ); create_next_coop(); } } private : const so_5::mbox_t m_mbox; int m_cycle; so_5::state_t st_wait_registration{ this }; so_5::state_t st_wait_deregistration{ this }; void create_next_coop() { auto child_coop = so_environment().make_coop( so_coop(), so_5::disp::active_obj::make_dispatcher( so_environment() ).binder() ); child_coop->add_reg_notificator( so_5::make_coop_reg_notificator( m_mbox ) ); child_coop->add_dereg_notificator( so_5::make_coop_dereg_notificator( m_mbox ) ); child_coop->make_agent< a_child_t >(); so_environment().register_coop( std::move( child_coop ) ); } }; int main() { run_with_time_limit( [] { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/create_child_coop_5_5_8/000077500000000000000000000000001523065210100237375ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/create_child_coop_5_5_8/CMakeLists.txt000066400000000000000000000001501523065210100264730ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.create_child_coop_5_5_8) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/create_child_coop_5_5_8/main.cpp000066400000000000000000000035261523065210100253750ustar00rootroot00000000000000/* * A unit-test for testing new create_child_coop and introduce_child_coop * functions added in v.5.5.8. */ #include #include struct msg_started : public so_5::signal_t {}; class child_agent_t final : public so_5::agent_t { const so_5::mbox_t m_owner; public: child_agent_t( context_t ctx, so_5::mbox_t owner ) : so_5::agent_t{ std::move(ctx) } , m_owner{ std::move(owner) } {} void so_evt_start() override { so_5::send< msg_started >( m_owner ); } }; class create_child_coop_agent_t final : public so_5::agent_t { public: create_child_coop_agent_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { auto child = so_5::create_child_coop( *this ); child->make_agent< child_agent_t >( so_direct_mbox() ); so_environment().register_coop( std::move(child) ); } }; class introduce_child_coop_agent_t final : public so_5::agent_t { public: introduce_child_coop_agent_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::introduce_child_coop( *this, [this]( so_5::coop_t & coop ) { coop.make_agent< child_agent_t >( so_direct_mbox() ); } ); } }; void init( so_5::environment_t & env ) { // First cooperation. env.introduce_coop( []( so_5::coop_t & parent ) { parent.make_agent< create_child_coop_agent_t >(); } ); // Second cooperation. env.introduce_coop( []( so_5::coop_t & parent ) { parent.make_agent< introduce_child_coop_agent_t >(); } ); } int main() { run_with_time_limit( [] { so_5::launch( init ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/dereg_already_deregistered/000077500000000000000000000000001523065210100247275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/dereg_already_deregistered/CMakeLists.txt000066400000000000000000000001531523065210100274660ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.dereg_already_deregistered) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/dereg_already_deregistered/main.cpp000066400000000000000000000071551523065210100263670ustar00rootroot00000000000000/* * A unit-test for testing deregistration of already deregistered * coops (reference to environment is being got from direct mbox). */ #include #include #include namespace test { class a_child_t final : public so_5::agent_t { const std::vector< std::string > m_head; std::vector< std::string > m_tail; public: a_child_t( context_t ctx, std::vector< std::string > head ) : so_5::agent_t::agent_t{ std::move(ctx) } , m_head{ std::move(head) } {} void set_tail( std::vector< std::string > tail ) { m_tail = std::move(tail); } void so_evt_start() override { so_deregister_agent_coop_normally(); } }; struct msg_child_destroyed final : public so_5::signal_t {}; class destruction_notificator_t { const so_5::mbox_t m_target; public: destruction_notificator_t( so_5::mbox_t target ) : m_target{ std::move(target) } {} ~destruction_notificator_t() { so_5::send< msg_child_destroyed >( m_target ); } }; class a_test_performer_t final : public so_5::agent_t { struct info_t { so_5::mbox_t m_direct_mbox; so_5::coop_handle_t m_coop_handle; info_t( so_5::mbox_t direct_mbox, so_5::coop_handle_t coop_handle ) : m_direct_mbox{ std::move(direct_mbox) } , m_coop_handle{ std::move(coop_handle) } {} }; std::vector< info_t > m_infos; public: a_test_performer_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe_self() .event( &a_test_performer_t::evt_child_destroyed ) ; } void so_evt_start() override { register_next_coop(); } private: void evt_child_destroyed( mhood_t< msg_child_destroyed > /*cmd*/ ) { if( m_infos.size() < 100u ) register_next_coop(); else try_deregister_again(); } void register_next_coop() { // This code is written in hope that reallocation of // memory will rewrite the old value of deregistered // agents. auto c = so_environment().make_coop(); c->take_under_control( std::make_unique< destruction_notificator_t >( so_direct_mbox() ) ); auto * a = c->make_agent< a_child_t >( make_payload() ); a->set_tail( make_payload() ); auto direct_mbox = a->so_direct_mbox(); auto coop_handle = so_environment().register_coop( std::move(c) ); m_infos.emplace_back( std::move(direct_mbox), std::move(coop_handle) ); } void try_deregister_again() { for( const auto & info : m_infos ) { info.m_direct_mbox->environment().deregister_coop( info.m_coop_handle, so_5::dereg_reason::normal ); } so_deregister_agent_coop_normally(); } [[nodiscard]] std::vector< std::string > make_payload() const { std::vector< std::string > result; auto s = 50 + static_cast< std::size_t >( std::rand() % 200 ); if( !s ) s = 1u; for( std::size_t i = 0; i < s; ++i ) { auto l = 50u + static_cast< std::size_t >( std::rand() % 500 ); const auto ch = static_cast< char >( std::rand() & 0xff ); result.push_back( std::string( l, ch ) ); } return result; } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & parent ) { parent.make_agent< a_test_performer_t >(); } ); } } /* namespace test */ using namespace test; int main() { try { run_with_time_limit( []{ so_5::launch( init ); }, 20 ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/coop/dereg_empty_coop/000077500000000000000000000000001523065210100227365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/dereg_empty_coop/CMakeLists.txt000066400000000000000000000001411523065210100254720ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.dereg_empty_coop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/dereg_empty_coop/main.cpp000066400000000000000000000025461523065210100243750ustar00rootroot00000000000000/* * A unit-test for testing deregistration of empty coop. */ #include #include void init( so_5::environment_t & env ) { class a_supervisor_t final : public so_5::agent_t { so_5::coop_handle_t m_child; public : using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( [this](mhood_t) { so_environment().deregister_coop( m_child, so_5::dereg_reason::normal ); } ) .event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::introduce_child_coop( *this, [this]( so_5::coop_t & coop ) { m_child = coop.handle(); coop.add_reg_notificator( so_5::make_coop_reg_notificator( so_direct_mbox() ) ); coop.add_dereg_notificator( so_5::make_coop_dereg_notificator( so_direct_mbox() ) ); } ); } }; // Supervison coop. env.introduce_coop( []( so_5::coop_t & parent ) { parent.make_agent< a_supervisor_t >(); } ); } int main() { try { run_with_time_limit( []{ so_5::launch( init ); }, 20, "dereg_empty_coop" ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/coop/destruction_order_1/000077500000000000000000000000001523065210100233705ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/destruction_order_1/CMakeLists.txt000066400000000000000000000001441523065210100261270ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.destruction_order_1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/destruction_order_1/main.cpp000066400000000000000000000036311523065210100250230ustar00rootroot00000000000000/* * A unit-test for testing order of destruction of binders and agents. */ #include #include namespace test { std::atomic g_value{ 0 }; class custom_dispatcher_t final : public so_5::disp_binder_t { so_5::disp_binder_shptr_t m_actual_binder; public: custom_dispatcher_t( so_5::environment_t & env ) : m_actual_binder{ so_5::disp::one_thread::make_dispatcher(env).binder() } {} ~custom_dispatcher_t() noexcept override { m_actual_binder.reset(); g_value = 1; } void preallocate_resources( so_5::agent_t & agent ) override { m_actual_binder->preallocate_resources( agent ); } void undo_preallocation( so_5::agent_t & agent ) noexcept override { m_actual_binder->undo_preallocation( agent ); } void bind( so_5::agent_t & agent ) noexcept override { m_actual_binder->bind( agent ); } void unbind( so_5::agent_t & agent ) noexcept override { m_actual_binder->unbind( agent ); } }; class a_test_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; ~a_test_t() noexcept override { const auto v = g_value.load( std::memory_order_acquire ); if( 0 != v ) { std::cerr << "Unexpected value of g_value: " << v << std::endl; std::abort(); } } void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent_with_binder< a_test_t >( std::make_shared< custom_dispatcher_t >( coop.environment() ) ); } ); } } /* namespace test */ using namespace test; int main() { try { run_with_time_limit( []{ so_5::launch( init ); }, 20 ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop/000077500000000000000000000000001523065210100224265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop/CMakeLists.txt000066400000000000000000000001371523065210100251670ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.introduce_coop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop/main.cpp000066400000000000000000000055161523065210100240650ustar00rootroot00000000000000/* * Test for various variants of environment_t::introduce_coop. */ #include #include #include #include #include so_5::mbox_t manager_mbox( so_5::environment_t & env ) { return env.create_mbox( "manager" ); } struct msg_started : public so_5::signal_t {}; class a_manager_t : public so_5::agent_t { public : a_manager_t( context_t ctx, unsigned int expected ) : so_5::agent_t( ctx ) , m_expected( expected ) {} virtual void so_define_agent() override { so_default_state().event( manager_mbox( so_environment() ), [this](mhood_t< msg_started >) { ++m_received; if( m_received == m_expected ) so_environment().stop(); } ); } private : const unsigned int m_expected; unsigned int m_received = { 0 }; }; void define_agent( so_5::coop_t & coop ) { class a_starter_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_5::send< msg_started >( manager_mbox( so_environment() ) ); } }; coop.make_agent< a_starter_t >(); } class a_child_owner_t : public so_5::agent_t { public : a_child_owner_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_evt_start() override { using namespace so_5; auto & env = so_environment(); // Special check for C++14 introduce_child_coop( *this, []( auto & coop ) { std::cout << "introduce_child_coop in C++14 or later." << std::endl; define_agent( coop ); } ); introduce_child_coop( *this, so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) { define_agent( coop ); } ); introduce_child_coop( *this, []( coop_t & coop ) { define_agent( coop ); } ); introduce_child_coop( *this, so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) { define_agent( coop ); } ); } }; void init( so_5::environment_t & env ) { using namespace so_5; env.register_agent_as_coop( env.make_agent< a_manager_t >( 8u ) ); env.register_agent_as_coop( env.make_agent< a_child_owner_t >() ); env.introduce_coop( []( auto & coop ) { std::cout << "introduce_coop in C++14 or later." << std::endl; define_agent( coop ); } ); env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) { define_agent( coop ); } ); env.introduce_coop( []( coop_t & coop ) { define_agent( coop ); } ); env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) { define_agent( coop ); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "introduce_coop test" ); } catch( const std::exception & ex ) { std::cerr << "error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop_2/000077500000000000000000000000001523065210100226475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop_2/CMakeLists.txt000066400000000000000000000001411523065210100254030ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.introduce_coop_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop_2/main.cpp000066400000000000000000000056571523065210100243140ustar00rootroot00000000000000/* * Test for various variants of environment_t::introduce_coop. */ #include #include #include #include #include #include using namespace std::string_literals; so_5::mbox_t manager_mbox( so_5::environment_t & env ) { return env.create_mbox( "manager" ); } struct msg_started : public so_5::signal_t {}; class a_manager_t : public so_5::agent_t { public : a_manager_t( context_t ctx, unsigned int expected ) : so_5::agent_t( ctx ) , m_expected( expected ) {} virtual void so_define_agent() override { so_default_state().event( manager_mbox( so_environment() ), [this](mhood_t< msg_started >) { ++m_received; if( m_received == m_expected ) so_environment().stop(); } ); } private : const unsigned int m_expected; unsigned int m_received = { 0 }; }; void define_agent( so_5::coop_t & coop ) { class a_starter_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_5::send< msg_started >( manager_mbox( so_environment() ) ); } }; coop.make_agent< a_starter_t >(); } void ensure_valid_value( const std::string & actual, const std::string & expected ) { ensure_or_die( actual == expected, "values mismatch! actual='" + actual + "', expected='" + expected + "'" ); } class a_child_owner_t : public so_5::agent_t { public : a_child_owner_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_evt_start() override { using namespace so_5; auto & env = so_environment(); // Special check for C++14 ensure_valid_value( introduce_child_coop( *this, []( auto & coop ) { std::cout << "introduce_child_coop in C++14 or later." << std::endl; define_agent( coop ); return "first"s; } ), "first"s ); ensure_valid_value( introduce_child_coop( *this, so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) { define_agent( coop ); return "second"s; } ), "second"s ); } }; void init( so_5::environment_t & env ) { using namespace so_5; env.register_agent_as_coop( env.make_agent< a_manager_t >( 4u ) ); env.register_agent_as_coop( env.make_agent< a_child_owner_t >() ); ensure_valid_value( env.introduce_coop( []( auto & coop ) { std::cout << "introduce_coop in C++14 or later." << std::endl; define_agent( coop ); return "first"s; } ), "first"s ); ensure_valid_value( env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) { define_agent( coop ); return "second"s; } ), "second"s ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "introduce_coop test" ); } catch( const std::exception & ex ) { std::cerr << "error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop_3/000077500000000000000000000000001523065210100226505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop_3/CMakeLists.txt000066400000000000000000000001411523065210100254040ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.introduce_coop_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop_3/main.cpp000066400000000000000000000062341523065210100243050ustar00rootroot00000000000000/* * Test for various variants of environment_t::introduce_coop. */ #include #include #include #include #include #include using namespace std::string_literals; // This is non-copyable and non-movable type. class test_object_t { public: test_object_t() = default; test_object_t( const test_object_t & ) = delete; test_object_t( test_object_t && ) = delete; }; test_object_t g_test_object; so_5::mbox_t manager_mbox( so_5::environment_t & env ) { return env.create_mbox( "manager" ); } struct msg_started : public so_5::signal_t {}; class a_manager_t : public so_5::agent_t { public : a_manager_t( context_t ctx, unsigned int expected ) : so_5::agent_t( ctx ) , m_expected( expected ) {} virtual void so_define_agent() override { so_default_state().event( manager_mbox( so_environment() ), [this](mhood_t< msg_started >) { ++m_received; if( m_received == m_expected ) so_environment().stop(); } ); } private : const unsigned int m_expected; unsigned int m_received = { 0 }; }; void define_agent( so_5::coop_t & coop ) { class a_starter_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_5::send< msg_started >( manager_mbox( so_environment() ) ); } }; coop.make_agent< a_starter_t >(); } void ensure_valid_value( test_object_t & actual ) { ensure_or_die( std::addressof(actual) == std::addressof(g_test_object), "invalid address received!" ); } class a_child_owner_t : public so_5::agent_t { public : a_child_owner_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_evt_start() override { using namespace so_5; auto & env = so_environment(); // Special check for C++14 ensure_valid_value( introduce_child_coop( *this, []( auto & coop ) -> test_object_t & { std::cout << "introduce_child_coop in C++14 or later." << std::endl; define_agent( coop ); return g_test_object; } ) ); ensure_valid_value( introduce_child_coop( *this, so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) -> test_object_t & { define_agent( coop ); return g_test_object; } ) ); } }; void init( so_5::environment_t & env ) { using namespace so_5; env.register_agent_as_coop( env.make_agent< a_manager_t >( 4u ) ); env.register_agent_as_coop( env.make_agent< a_child_owner_t >() ); ensure_valid_value( env.introduce_coop( []( auto & coop ) -> test_object_t & { std::cout << "introduce_coop in C++14 or later." << std::endl; define_agent( coop ); return g_test_object; } ) ); ensure_valid_value( env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) -> test_object_t & { define_agent( coop ); return g_test_object; } ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "introduce_coop test" ); } catch( const std::exception & ex ) { std::cerr << "error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop_4/000077500000000000000000000000001523065210100226515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop_4/CMakeLists.txt000066400000000000000000000001411523065210100254050ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.introduce_coop_4) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/introduce_coop_4/main.cpp000066400000000000000000000063151523065210100243060ustar00rootroot00000000000000/* * Test for various variants of environment_t::introduce_coop. */ #include #include #include #include #include #include using namespace std::string_literals; // This is non-copyable and non-movable type. class test_object_t { public: test_object_t() = default; test_object_t( const test_object_t & ) = delete; test_object_t( test_object_t && ) = delete; }; test_object_t g_test_object; so_5::mbox_t manager_mbox( so_5::environment_t & env ) { return env.create_mbox( "manager" ); } struct msg_started : public so_5::signal_t {}; class a_manager_t : public so_5::agent_t { public : a_manager_t( context_t ctx, unsigned int expected ) : so_5::agent_t( ctx ) , m_expected( expected ) {} virtual void so_define_agent() override { so_default_state().event( manager_mbox( so_environment() ), [this](mhood_t< msg_started >) { ++m_received; if( m_received == m_expected ) so_environment().stop(); } ); } private : const unsigned int m_expected; unsigned int m_received = { 0 }; }; void define_agent( so_5::coop_t & coop ) { class a_starter_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_5::send< msg_started >( manager_mbox( so_environment() ) ); } }; coop.make_agent< a_starter_t >(); } void ensure_valid_value( test_object_t && actual ) { ensure_or_die( std::addressof(actual) == std::addressof(g_test_object), "invalid address received!" ); } class a_child_owner_t : public so_5::agent_t { public : a_child_owner_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_evt_start() override { using namespace so_5; auto & env = so_environment(); // Special check for C++14 ensure_valid_value( introduce_child_coop( *this, []( auto & coop ) -> test_object_t && { std::cout << "introduce_child_coop in C++14 or later." << std::endl; define_agent( coop ); return std::move(g_test_object); } ) ); ensure_valid_value( introduce_child_coop( *this, so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) -> test_object_t && { define_agent( coop ); return std::move(g_test_object); } ) ); } }; void init( so_5::environment_t & env ) { using namespace so_5; env.register_agent_as_coop( env.make_agent< a_manager_t >( 4u ) ); env.register_agent_as_coop( env.make_agent< a_child_owner_t >() ); ensure_valid_value( env.introduce_coop( []( auto & coop ) -> test_object_t && { std::cout << "introduce_coop in C++14 or later." << std::endl; define_agent( coop ); return std::move(g_test_object); } ) ); ensure_valid_value( env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), []( coop_t & coop ) -> test_object_t && { define_agent( coop ); return std::move(g_test_object); } ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "introduce_coop test" ); } catch( const std::exception & ex ) { std::cerr << "error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/issue_28/000077500000000000000000000000001523065210100210535ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/issue_28/CMakeLists.txt000066400000000000000000000001311523065210100236060ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.issue_28) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/issue_28/main.cpp000066400000000000000000000037341523065210100225120ustar00rootroot00000000000000/* * A unit-test for testing deregistration child coop bound to active_group * dispatcher. * * See more: https://github.com/Stiffstream/sobjectizer/issues/28#issuecomment-840638292 */ #include #include struct completed final : public so_5::signal_t {}; class a_child_t final : public so_5::agent_t { struct die_t final : public so_5::signal_t {}; so_5::mchain_t m_final_ch; public: a_child_t( context_t ctx, so_5::mchain_t final_ch ) : so_5::agent_t{ std::move(ctx) } , m_final_ch{ std::move(final_ch) } {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send< die_t >( *this ); } void so_evt_finish() override { so_5::send< completed >( m_final_ch ); } }; void run_test_case( unsigned int n_children ) { std::cout << "n_children=" << n_children << std::flush; so_5::launch( [n_children]( so_5::environment_t & env ) { auto final_ch = so_5::create_mchain( env ); const auto binder = so_5::disp::active_group::make_dispatcher( env ) .binder( "my_group" ); auto parent = env.register_coop( env.make_coop(binder) ); for( unsigned int i{}; i != n_children; ++i ) { auto child = env.make_coop( parent, binder ); child->make_agent< a_child_t >( final_ch ); env.register_coop( std::move(child) ); } so_5::receive( so_5::from( final_ch ).handle_n( n_children ), []( so_5::mhood_t< completed > ) {} ); env.stop(); } ); } int main() { try { run_with_time_limit( []{ const auto n_children = { 2u, 4u, 8u, 16u, 32u, 48u, 64u }; for( auto n : n_children ) { run_test_case( n ); std::cout << "\r" << std::flush; } std::cout << "test completed" << std::endl; }, 20 ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/coop/msg_loss_at_registration/000077500000000000000000000000001523065210100245165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/msg_loss_at_registration/CMakeLists.txt000066400000000000000000000001511523065210100272530ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.msg_loss_at_registration) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/msg_loss_at_registration/main.cpp000066400000000000000000000144211523065210100261500ustar00rootroot00000000000000/* * A unit-test for testing a possible message loss at the registration * of an agent. * * Special binder is used for one agent. That binder pauses bind() operation * (that pause allows other agents from the same coop to start processing * their evt_start demands). */ #include #include namespace so5_test { struct msg_first final : public so_5::signal_t {}; struct msg_second final : public so_5::signal_t {}; struct msg_third final : public so_5::signal_t {}; class a_first_t final : public so_5::agent_t { bool m_first_received{ false }; so_5::mbox_t m_second; public: a_first_t(context_t ctx, so_5::priority_t priority ) : so_5::agent_t{ ctx + priority } {} void set_second_mbox( const so_5::mbox_t & mbox ) { m_second = mbox; } void so_define_agent() override { std::cout << "a_first_t::so_define_agent {" << std::endl; so_subscribe_self() .event( &a_first_t::evt_first ) .event( &a_first_t::evt_third ) ; std::cout << "a_first_t::so_define_agent }" << std::endl; } void so_evt_start() override { std::cout << "a_first_t::so_evt_start {" << std::endl; so_5::send< msg_second >( m_second ); std::cout << "a_first_t::so_evt_start }" << std::endl; } private: void evt_first( mhood_t ) { m_first_received = true; std::cout << "a_first_t::evt_first!" << std::endl; } void evt_third( mhood_t ) { if( m_first_received ) so_deregister_agent_coop_normally(); else throw std::runtime_error{ "msg_first was missed!" }; } }; class a_second_t final : public so_5::agent_t { so_5::mbox_t m_first; public: a_second_t(context_t ctx, so_5::priority_t priority) : so_5::agent_t{ ctx + priority } {} void set_first_mbox( const so_5::mbox_t & mbox ) { m_first = mbox; } void so_define_agent() override { std::cout << "a_second_t::so_define_agent {" << std::endl; so_subscribe_self() .event( &a_second_t::evt_second ) ; std::cout << "a_second_t::so_define_agent }" << std::endl; } void so_evt_start() override { std::cout << "a_second_t::so_evt_start {" << std::endl; so_5::send< msg_first >( m_first ); std::cout << "a_second_t::so_evt_start }" << std::endl; } private: void evt_second( mhood_t ) { so_5::send< msg_third >( m_first ); } }; class custom_dispatcher_t final : public so_5::event_queue_t { std::thread m_worker_thread; so_5::mchain_t m_queue; public: custom_dispatcher_t( so_5::environment_t & env ) : m_queue{ so_5::create_mchain( env ) } {} ~custom_dispatcher_t() override { if( m_worker_thread.joinable() ) { so_5::close_retain_content( so_5::terminate_if_throws, m_queue ); m_worker_thread.join(); } } void start() { m_worker_thread = std::thread{ [this]() { this->body(); } }; } void push( so_5::execution_demand_t demand ) override { so_5::send< so_5::execution_demand_t >( m_queue, std::move(demand) ); } void push_evt_start( so_5::execution_demand_t demand ) override { this->push( std::move(demand) ); } void push_evt_finish( so_5::execution_demand_t demand ) noexcept override { this->push( std::move(demand) ); } private: void body() { const auto worker_id = so_5::query_current_thread_id(); so_5::receive( so_5::from( m_queue ).handle_all(), [worker_id]( so_5::execution_demand_t demand ) { demand.call_handler( worker_id ); } ); } }; class problematic_dispatcher_binder_t final : public so_5::disp_binder_t { std::unique_ptr< custom_dispatcher_t > m_disp; public: problematic_dispatcher_binder_t( std::unique_ptr< custom_dispatcher_t > disp ) : m_disp{ std::move(disp) } {} void preallocate_resources( so_5::agent_t & /*agent*/ ) override { // Nothing to do. } void undo_preallocation( so_5::agent_t & /*agent*/ ) noexcept override { // Nothing to do. } void bind( so_5::agent_t & agent ) noexcept override { std::cout << "*** pausing the binding ***" << std::endl; std::this_thread::sleep_for( std::chrono::seconds{1} ); agent.so_bind_to_dispatcher( *m_disp ); } void unbind( so_5::agent_t & /*agent*/ ) noexcept override { // Nothing to do. } }; class normal_dispatcher_binder_t final : public so_5::disp_binder_t { std::unique_ptr< custom_dispatcher_t > m_disp; public: normal_dispatcher_binder_t( std::unique_ptr< custom_dispatcher_t > disp ) : m_disp{ std::move(disp) } {} void preallocate_resources( so_5::agent_t & /*agent*/ ) override { // Nothing to do. } void undo_preallocation( so_5::agent_t & /*agent*/ ) noexcept override { // Nothing to do. } void bind( so_5::agent_t & agent ) noexcept override { std::cout << "--- binding without a pause ---" << std::endl; agent.so_bind_to_dispatcher( *m_disp ); } void unbind( so_5::agent_t & /*agent*/ ) noexcept override { // Nothing to do. } }; void run() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { // A separate dispatcher for the first agent. auto first_disp = std::make_unique< custom_dispatcher_t >( coop.environment() ); // The dispatcher has to be started manually. first_disp->start(); // Problematic binder will be used for the first agent. auto first_binder = std::make_shared< problematic_dispatcher_binder_t >( std::move(first_disp) ); auto * first = coop.make_agent_with_binder< a_first_t >( first_binder, so_5::prio::p0 ); // A separate dispatcher for the second agent. auto second_disp = std::make_unique< custom_dispatcher_t >( coop.environment() ); // The dispatcher has to be started manually. second_disp->start(); // Normal binder will be used for the second agent. auto second_binder = std::make_shared< normal_dispatcher_binder_t >( std::move(second_disp) ); auto * second = coop.make_agent_with_binder< a_second_t >( second_binder, so_5::prio::p1 ); first->set_second_mbox( second->so_direct_mbox() ); second->set_first_mbox( first->so_direct_mbox() ); } ); } ); } } /* namespace so5_test */ using namespace so5_test; int main() { try { run_with_time_limit( []{ run(); }, 5 ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_1/000077500000000000000000000000001523065210100222665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_1/CMakeLists.txt000066400000000000000000000001371523065210100250270ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.parent_child_1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_1/main.cpp000066400000000000000000000116101523065210100237150ustar00rootroot00000000000000/* * A test for sequence of child cooperation deregistration. */ #include #include #include #include struct msg_child_started : public so_5::signal_t {}; // We just know that the first coop will have this ID in the current // version of SO-5. constexpr so_5::coop_id_t starter_coop_id = 2u; void create_and_register_agent( const so_5::coop_handle_t & parent, so_5::environment_t & env, int ordinal, int max_deep ); class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, int ordinal, int max_deep ) : base_type_t( env ) , m_ordinal( ordinal ) , m_max_deep( max_deep ) , m_self_mbox( env.create_mbox( mbox_name( ordinal ) ) ) { } void so_define_agent() override { so_subscribe( m_self_mbox ) .event( &a_test_t::evt_child_started ); } void so_evt_start() override { if( m_ordinal != m_max_deep ) create_and_register_agent( so_coop(), so_environment(), m_ordinal + 1, m_max_deep ); else notify_parent(); } void evt_child_started( mhood_t< msg_child_started > ) { if( m_ordinal ) notify_parent(); else so_deregister_agent_coop_normally(); } private : const int m_ordinal; const int m_max_deep; so_5::mbox_t m_self_mbox; static std::string mbox_name( int ordinal ) { std::ostringstream s; s << "agent_" << ordinal; return s.str(); } void notify_parent() { so_5::send< msg_child_started >( so_environment().create_mbox( mbox_name( m_ordinal - 1 ) ) ); } }; void create_and_register_agent( const so_5::coop_handle_t & parent, so_5::environment_t & env, int ordinal, int max_deep ) { auto coop = env.make_coop( parent ); coop->make_agent< a_test_t >( ordinal, max_deep ); env.register_coop( std::move( coop ) ); } class a_test_starter_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_starter_t( so_5::environment_t & env ) : base_type_t( env ) {} void so_evt_start() override { create_and_register_agent( so_coop(), so_environment(), 0, 5 ); } }; struct init_deinit_data_t { std::vector< so_5::coop_id_t > m_init_sequence; std::vector< so_5::coop_id_t > m_deinit_sequence; }; class test_coop_listener_t : public so_5::coop_listener_t { public : test_coop_listener_t( init_deinit_data_t & data ) : m_data( data ) , m_active_coops( 0 ) {} void on_registered( so_5::environment_t &, const so_5::coop_handle_t & coop ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; std::cout << "registered: " << coop << std::endl; if( starter_coop_id != coop.id() ) { m_data.m_init_sequence.push_back( coop.id() ); ++m_active_coops; } } void on_deregistered( so_5::environment_t & env, const so_5::coop_handle_t & coop, const so_5::coop_dereg_reason_t & reason ) noexcept override { bool need_stop = false; { std::lock_guard< std::mutex > lock{ m_lock }; std::cout << "deregistered: " << coop << ", reason: " << reason.reason() << std::endl; if( starter_coop_id != coop.id() ) { m_data.m_deinit_sequence.insert( m_data.m_deinit_sequence.begin(), coop.id() ); --m_active_coops; if( !m_active_coops ) need_stop = true; } } if( need_stop ) env.stop(); } static so_5::coop_listener_unique_ptr_t make( init_deinit_data_t & data ) { return so_5::coop_listener_unique_ptr_t( new test_coop_listener_t( data ) ); } private : std::mutex m_lock; init_deinit_data_t & m_data; int m_active_coops; }; std::string sequence_to_string( const std::vector< so_5::coop_id_t > & s ) { std::ostringstream ss; for( auto i = s.begin(); i != s.end(); ++i ) { if( i != s.begin() ) ss << ", "; ss << *i; } return ss.str(); } class test_env_t { public : void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_starter_t >() ); } so_5::coop_listener_unique_ptr_t make_listener() { return test_coop_listener_t::make( m_data ); } void check_result() const { if( m_data.m_init_sequence != m_data.m_deinit_sequence ) throw std::runtime_error( "Wrong deinit sequence: init_seq: " + sequence_to_string( m_data.m_init_sequence ) + ", deinit_seq: " + sequence_to_string( m_data.m_deinit_sequence ) ); } private : init_deinit_data_t m_data; }; int main() { run_with_time_limit( [] { test_env_t test_env; so_5::launch( [&test_env]( so_5::environment_t & env ) { test_env.init( env ); }, [&test_env]( so_5::environment_params_t & params ) { params.coop_listener( test_env.make_listener() ); params.disable_autoshutdown(); } ); test_env.check_result(); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_2/000077500000000000000000000000001523065210100222675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_2/CMakeLists.txt000066400000000000000000000001371523065210100250300ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.parent_child_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_2/main.cpp000066400000000000000000000105411523065210100237200ustar00rootroot00000000000000/* * A test for sequence of child cooperation deregistration. */ #include #include #include #include #include struct msg_child_started : public so_5::signal_t {}; void create_and_register_agent( const so_5::coop_handle_t & parent, so_5::environment_t & env, const std::string & parent_coop_name, int ordinal, int max_children, int level, int max_deep ); class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, const std::string & self_name, const std::string & parent_coop_name, int max_children, int level, int max_deep ) : base_type_t( env ) , m_self_name( self_name ) , m_parent_coop_name( parent_coop_name ) , m_max_children( max_children ) , m_level( level ) , m_max_deep( max_deep ) , m_started_children( 0 ) , m_self_mbox( env.create_mbox( self_name ) ) { } void so_define_agent() override { so_subscribe( m_self_mbox ) .event( &a_test_t::evt_child_started ); } void so_evt_start() override { if( m_level != m_max_deep ) { for( int i = 0; i != m_max_children; ++i ) create_and_register_agent( so_coop(), so_environment(), m_self_name, i, m_max_children, m_level + 1, m_max_deep ); } else notify_parent(); } void evt_child_started( mhood_t< msg_child_started > ) { m_started_children += 1; if( m_started_children == m_max_children ) { if( m_level ) notify_parent(); else so_deregister_agent_coop_normally(); } } private : const std::string m_self_name; const std::string m_parent_coop_name; const int m_max_children; const int m_level; const int m_max_deep; int m_started_children; so_5::mbox_t m_self_mbox; void notify_parent() { so_5::send< msg_child_started >( so_environment().create_mbox( m_parent_coop_name ) ); } }; std::string create_coop_name( const std::string & parent_coop_name, int level, int ordinal ) { std::ostringstream s; if( parent_coop_name.empty() ) s << "a_" << level << "_" << ordinal; else s << parent_coop_name << "_" << level << "_" << ordinal; return s.str(); } void create_and_register_agent( const so_5::coop_handle_t & parent, so_5::environment_t & env, const std::string & parent_coop_name, int ordinal, int max_children, int level, int max_deep ) { auto coop = env.make_coop( parent ); coop->make_agent< a_test_t >( create_coop_name( parent_coop_name, level, ordinal ), parent_coop_name, max_children, level, max_deep ); env.register_coop( std::move( coop ) ); } class test_coop_listener_t : public so_5::coop_listener_t { public : test_coop_listener_t( std::set< so_5::coop_id_t > & ids ) : m_ids( ids ) {} void on_registered( so_5::environment_t &, const so_5::coop_handle_t & coop ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; m_ids.insert( coop.id() ); } void on_deregistered( so_5::environment_t & env, const so_5::coop_handle_t & coop, const so_5::coop_dereg_reason_t &) noexcept override { { std::lock_guard< std::mutex > lock{ m_lock }; m_ids.erase( coop.id() ); } if( 2u == coop.id() ) env.stop(); } static so_5::coop_listener_unique_ptr_t make( std::set< so_5::coop_id_t > & names ) { return so_5::coop_listener_unique_ptr_t( new test_coop_listener_t( names ) ); } private : std::mutex m_lock; std::set< so_5::coop_id_t > & m_ids; }; int main() { try { std::set< so_5::coop_id_t > ids; run_with_time_limit( [&] { so_5::launch( [&]( so_5::environment_t & env ) { create_and_register_agent( so_5::coop_handle_t{}, env, "", 0, 4, 0, 8 ); }, [&]( so_5::environment_params_t & params ) { params.disable_autoshutdown(); params.coop_listener( test_coop_listener_t::make( ids ) ); } ); }, 240 ); if( !ids.empty() ) { for( const auto & n : ids ) std::cout << "unregistered coop: '" << n << "'" << std::endl; throw std::runtime_error( "There are some not deregistered cooperations (" + std::to_string( ids.size() ) + ")" ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_3/000077500000000000000000000000001523065210100222705ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_3/CMakeLists.txt000066400000000000000000000001371523065210100250310ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.parent_child_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_3/main.cpp000066400000000000000000000117561523065210100237320ustar00rootroot00000000000000/* * A test for sequence of child cooperation deregistration. */ #include #include #include #include struct msg_child_started : public so_5::signal_t {}; void create_and_register_agent( const so_5::coop_handle_t & parent, so_5::environment_t & env, int ordinal, int max_deep ); class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, int ordinal, int max_deep ) : base_type_t( env ) , m_ordinal( ordinal ) , m_max_deep( max_deep ) , m_self_mbox( env.create_mbox( mbox_name( ordinal ) ) ) { } void so_define_agent() override { so_subscribe( m_self_mbox ) .event( &a_test_t::evt_child_started ); } void so_evt_start() override { if( m_ordinal != m_max_deep ) create_and_register_agent( so_coop(), so_environment(), m_ordinal + 1, m_max_deep ); else notify_parent(); } void evt_child_started( mhood_t< msg_child_started > ) { if( m_ordinal ) notify_parent(); else so_environment().stop(); } private : const int m_ordinal; const int m_max_deep; so_5::mbox_t m_self_mbox; static std::string mbox_name( int ordinal ) { std::ostringstream s; s << "agent_" << ordinal; return s.str(); } void notify_parent() { so_5::send< msg_child_started >( so_environment().create_mbox( mbox_name( m_ordinal - 1 ) ) ); } }; std::string create_coop_name( int ordinal ) { std::ostringstream s; s << "coop_" << ordinal; return s.str(); } void create_and_register_agent( const so_5::coop_handle_t & parent, so_5::environment_t & env, int ordinal, int max_deep ) { auto coop = env.make_coop( parent ); coop->make_agent< a_test_t >( ordinal, max_deep ); env.register_coop( std::move( coop ) ); } class a_test_starter_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_starter_t( so_5::environment_t & env ) : base_type_t( env ) {} void so_evt_start() override { create_and_register_agent( so_coop(), so_environment(), 0, 5 ); } }; // We just know that the first coop will have this ID in the current // version of SO-5. constexpr so_5::coop_id_t starter_coop_id = 2u; struct init_deinit_data_t { std::vector< so_5::coop_id_t > m_init_sequence; std::vector< so_5::coop_id_t > m_deinit_sequence; }; class test_coop_listener_t : public so_5::coop_listener_t { public : test_coop_listener_t( init_deinit_data_t & data ) : m_data( data ) , m_active_coops( 0 ) {} void on_registered( so_5::environment_t &, const so_5::coop_handle_t & coop ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; std::cout << "registered: " << coop << std::endl; if( starter_coop_id != coop.id() ) { m_data.m_init_sequence.push_back( coop.id() ); ++m_active_coops; } } void on_deregistered( so_5::environment_t & env, const so_5::coop_handle_t & coop, const so_5::coop_dereg_reason_t & reason ) noexcept override { bool need_stop = false; { std::lock_guard< std::mutex > lock{ m_lock }; std::cout << "deregistered: " << coop << ", reason: " << reason.reason() << std::endl; if( starter_coop_id != coop.id() ) { m_data.m_deinit_sequence.insert( m_data.m_deinit_sequence.begin(), coop.id() ); --m_active_coops; if( !m_active_coops ) need_stop = true; } } if( need_stop ) env.stop(); } static so_5::coop_listener_unique_ptr_t make( init_deinit_data_t & data ) { return so_5::coop_listener_unique_ptr_t( new test_coop_listener_t( data ) ); } private : std::mutex m_lock; init_deinit_data_t & m_data; int m_active_coops; }; std::string sequence_to_string( const std::vector< so_5::coop_id_t > & s ) { std::ostringstream ss; for( auto i = s.begin(); i != s.end(); ++i ) { if( i != s.begin() ) ss << ", "; ss << *i; } return ss.str(); } class test_env_t { public : void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_starter_t >() ); } so_5::coop_listener_unique_ptr_t make_listener() { return test_coop_listener_t::make( m_data ); } void check_result() const { if( m_data.m_init_sequence != m_data.m_deinit_sequence ) throw std::runtime_error( "Wrong deinit sequence: init_seq: " + sequence_to_string( m_data.m_init_sequence ) + ", deinit_seq: " + sequence_to_string( m_data.m_deinit_sequence ) ); } private : init_deinit_data_t m_data; }; int main() { run_with_time_limit( [] { test_env_t test_env; so_5::launch( [&test_env]( so_5::environment_t & env ) { test_env.init( env ); }, [&test_env]( so_5::environment_params_t & params ) { params.coop_listener( test_env.make_listener() ); params.disable_autoshutdown(); } ); test_env.check_result(); }, 60 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_4/000077500000000000000000000000001523065210100222715ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_4/CMakeLists.txt000066400000000000000000000001371523065210100250320ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.parent_child_4) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/parent_child_4/main.cpp000066400000000000000000000071771523065210100237350ustar00rootroot00000000000000/* * A test for inability to deregister parent cooperation during * child cooperation registration. */ #include #include #include #include #include struct msg_parent_started final : public so_5::signal_t {}; struct msg_initiate_dereg final : public so_5::message_t { std::promise m_completion; msg_initiate_dereg() = default; }; class a_child_t : public so_5::agent_t { public : a_child_t( so_5::environment_t & env, const so_5::mbox_t & parent_mbox ) : so_5::agent_t( env ) , m_parent_mbox( parent_mbox ) {} void so_define_agent() override { auto original_msg_uptr = std::make_unique< msg_initiate_dereg >(); auto completion = original_msg_uptr->m_completion.get_future(); so_5::message_ref_t original_msg{ std::move(original_msg_uptr) }; so_5::send( m_parent_mbox, mutable_mhood_t< msg_initiate_dereg >{ original_msg } ); completion.wait(); } void so_evt_finish() override { std::cout << "a_child_t::so_evt_finish is called!" << std::endl; std::abort(); } private : const so_5::mbox_t m_parent_mbox; }; class a_parent_t : public so_5::agent_t { public : a_parent_t( so_5::environment_t & env, const so_5::mbox_t & mbox ) : so_5::agent_t( env ) , m_mbox( mbox ) {} void so_define_agent() override { so_subscribe_self().event( &a_parent_t::evt_initiate_dereg ); } void so_evt_start() override { so_5::send< msg_parent_started >( m_mbox ); } void evt_initiate_dereg( mutable_mhood_t< msg_initiate_dereg > cmd ) { so_deregister_agent_coop_normally(); cmd->m_completion.set_value(); } private : const so_5::mbox_t m_mbox; }; class a_driver_t : public so_5::agent_t { public : a_driver_t( so_5::environment_t & env ) : so_5::agent_t( env ) {} void so_define_agent() override { so_subscribe_self().event( &a_driver_t::evt_parent_started ); } void so_evt_start() override { auto coop = so_environment().make_coop( so_5::disp::active_obj::make_dispatcher( so_environment() ).binder() ); m_parent_mbox = coop->make_agent< a_parent_t >( so_direct_mbox() )-> so_direct_mbox(); m_parent = so_environment().register_coop( std::move(coop) ); } void evt_parent_started(mhood_t< msg_parent_started >) { auto coop = so_environment().make_coop( m_parent, so_5::disp::active_obj::make_dispatcher( so_environment() ).binder() ); coop->make_agent< a_child_t >( m_parent_mbox ); try { so_environment().register_coop( std::move( coop ) ); std::cout << "An exception is expected in register_coop!" << std::endl; std::abort(); } catch( const so_5::exception_t & x ) { ensure_or_die( x.error_code() == so_5::rc_coop_is_not_in_registered_state || x.error_code() == so_5::rc_coop_already_destroyed, "rc_coop_is_not_in_registered_state or " "rc_coop_already_destroyed is expected, got: " + std::to_string(x.error_code()) ); } so_environment().stop(); } private : so_5::coop_handle_t m_parent; so_5::mbox_t m_parent_mbox; }; void init( so_5::environment_t & env ) { auto coop = env.make_coop( so_5::disp::active_obj::make_dispatcher( env ).binder() ); coop->make_agent< a_driver_t >(); env.register_coop( std::move( coop ) ); } int main() { run_with_time_limit( [] { for( int i = 0; i < 500; ++i ) { std::cout << i << "\r" << std::flush; so_5::launch( &init ); } std::cout << "Done" << std::endl; }, 600 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/reg_some_and_stop_1/000077500000000000000000000000001523065210100233215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/reg_some_and_stop_1/CMakeLists.txt000066400000000000000000000001441523065210100260600ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.reg_some_and_stop_1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/reg_some_and_stop_1/main.cpp000066400000000000000000000025641523065210100247600ustar00rootroot00000000000000/* * Testing a cooperation registration/deregistration. * * Several cooperations are registered. Then part of them are deregistered. */ #include #include #include #include #include #include #include // A dummy agent to be placed into test cooperations. class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_t( so_5::environment_t & env ) : base_type_t( env ) {} }; so_5::coop_handle_t reg_coop( so_5::environment_t & env ) { auto coop = env.make_coop(); coop->make_agent< test_agent_t >(); coop->make_agent< test_agent_t >(); return env.register_coop( std::move( coop ) ); } void init( so_5::environment_t & env ) { auto coop_1 = reg_coop( env ); reg_coop( env ); auto coop_3 = reg_coop( env ); reg_coop( env ); reg_coop( env ); auto coop_6 = reg_coop( env ); std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) ); env.deregister_coop( coop_1, so_5::dereg_reason::normal ); env.deregister_coop( coop_6, so_5::dereg_reason::normal ); env.deregister_coop( coop_3, so_5::dereg_reason::normal ); std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) ); env.stop(); } int main() { run_with_time_limit( [] { so_5::launch( init ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/reg_some_and_stop_2/000077500000000000000000000000001523065210100233225ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/reg_some_and_stop_2/CMakeLists.txt000066400000000000000000000001441523065210100260610ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.reg_some_and_stop_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/reg_some_and_stop_2/main.cpp000066400000000000000000000065001523065210100247530ustar00rootroot00000000000000/* * Another test of registering and deregistering cooperations. * * Several cooperations are registered. * A message is sent to agents. * Part of cooperations are deregistered. * Another message is sent to agents. */ #include #include #include #include #include #include const char * g_test_mbox_name = "test_mbox"; struct test_message : public so_5::signal_t {}; class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_t( so_5::environment_t & env, const so_5::mbox_t & test_mbox ) : base_type_t( env ), m_test_mbox( test_mbox ) { ++m_agent_count; } ~test_agent_t() override { --m_agent_count; } void so_define_agent() override; void evt_test( mhood_t< test_message > msg ); // Count of life agents. static so_5::atomic_counter_t m_agent_count; // Count of evt_test calls. static so_5::atomic_counter_t m_message_rec_cnt; private: so_5::mbox_t m_test_mbox; }; so_5::atomic_counter_t test_agent_t::m_agent_count; so_5::atomic_counter_t test_agent_t::m_message_rec_cnt; void test_agent_t::so_define_agent() { so_subscribe( m_test_mbox ) .in( so_default_state() ) .event( &test_agent_t::evt_test ); } void test_agent_t::evt_test( mhood_t< test_message > ) { ++m_message_rec_cnt; } auto reg_coop( const so_5::mbox_t & test_mbox, so_5::environment_t & env ) { auto coop = env.make_coop(); coop->make_agent< test_agent_t >( test_mbox ); coop->make_agent< test_agent_t >( test_mbox ); return env.register_coop( std::move( coop ) ); } void init( so_5::environment_t & env ) { so_5::mbox_t test_mbox = env.create_mbox( g_test_mbox_name ); auto coop_1 = reg_coop( test_mbox, env ); reg_coop( test_mbox, env ); auto coop_3 = reg_coop( test_mbox, env ); reg_coop( test_mbox, env ); reg_coop( test_mbox, env ); auto coop_6 = reg_coop( test_mbox, env ); // Give time to subscription. std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); // Initiate message. so_5::send< test_message >( env.create_mbox( g_test_mbox_name ) ); // Give time to process message. std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); auto x = test_agent_t::m_agent_count.load(); x -= test_agent_t::m_message_rec_cnt; if( 0 != x ) throw std::runtime_error( "check 1: test_agent_t::m_agent_count != " "test_agent_t::m_message_rec_cnt" ); // Deregister some cooperations. env.deregister_coop( coop_1, so_5::dereg_reason::normal ); env.deregister_coop( coop_6, so_5::dereg_reason::normal ); env.deregister_coop( coop_3, so_5::dereg_reason::normal ); test_agent_t::m_message_rec_cnt = 0; // Give time to finish deregistration. std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); // Send another message. so_5::send< test_message >( env.create_mbox( g_test_mbox_name ) ); // Give time to process message. std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); x = test_agent_t::m_agent_count; x -= test_agent_t::m_message_rec_cnt; if( 0 != x ) throw std::runtime_error( "check 2: test_agent_t::m_agent_count != " "test_agent_t::m_message_rec_cnt" ); env.stop(); } int main() { run_with_time_limit( [] { so_5::launch( &init ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/reg_some_and_stop_3/000077500000000000000000000000001523065210100233235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/reg_some_and_stop_3/CMakeLists.txt000066400000000000000000000001441523065210100260620ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.reg_some_and_stop_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/reg_some_and_stop_3/main.cpp000066400000000000000000000030031523065210100247470ustar00rootroot00000000000000/* * A test for register cooperation from another cooperation. */ #include #include #include #include #include #include so_5::nonempty_name_t g_test_mbox_name( "test_mbox" ); struct slave_coop_finished_signal : public so_5::signal_t {}; class a_slave_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: a_slave_t( so_5::environment_t & env, so_5::mbox_t master_mbox ) : base_type_t( env ), m_master_mbox( std::move(master_mbox) ) {} void so_evt_start() override { so_deregister_agent_coop_normally(); so_5::send< slave_coop_finished_signal >( m_master_mbox ); } private: const so_5::mbox_t m_master_mbox; }; class a_master_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: a_master_t( so_5::environment_t & env ) : base_type_t( env ) {} void so_evt_start() override { so_subscribe_self() .event( &a_master_t::evt_slave_finished ); so_environment().register_agent_as_coop( so_environment().make_agent< a_slave_t >( so_direct_mbox() ) ); } void evt_slave_finished( mhood_t< slave_coop_finished_signal > ) { std::cout << "Shutdown\n"; so_environment().stop(); } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_master_t >() ); } int main() { run_with_time_limit( [] { so_5::launch( &init ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/shutdown_while_reg/000077500000000000000000000000001523065210100233125ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/shutdown_while_reg/CMakeLists.txt000066400000000000000000000001431523065210100260500ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.shutdown_while_reg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/shutdown_while_reg/main.cpp000066400000000000000000000041531523065210100247450ustar00rootroot00000000000000/* * A unit-test for checking shutdown of SOEnv while coop registration * is in progress. */ #include #include #include struct check_context_t { std::promise m_hello_received; }; class small_actor_t final : public so_5::agent_t { struct hello final : public so_5::signal_t {}; bool m_evt_start_completed{ false }; bool m_hello_received{ false }; check_context_t & m_check_context; public : small_actor_t( context_t ctx, check_context_t & check_context ) : so_5::agent_t{ std::move(ctx) } , m_check_context{ check_context } {} virtual ~small_actor_t() override { ensure_or_die( m_evt_start_completed, "!m_evt_start_completed" ); ensure_or_die( m_hello_received, "!m_hello_received" ); } void so_define_agent() override { so_subscribe_self().event( &small_actor_t::on_hello ); so_environment().stop(); } void so_evt_start() override { m_evt_start_completed = true; so_5::send_delayed< hello >( *this, std::chrono::milliseconds{125} ); } private : void on_hello( mhood_t ) { m_hello_received = true; m_check_context.m_hello_received.set_value(); } }; class driver_t final : public so_5::agent_t { public : driver_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_evt_start() override { so_environment().introduce_coop( so_5::disp::one_thread::make_dispatcher( so_environment() ) .binder(), []( so_5::coop_t & coop ) { auto ctx = coop.take_under_control( std::make_unique< check_context_t >() ); coop.make_agent< small_actor_t >( std::ref(*ctx) ); coop.add_reg_notificator( [ctx]( so_5::environment_t &, const so_5::coop_handle_t & ) noexcept { // Wait while small_actor receive hello message. ctx->m_hello_received.get_future().wait(); } ); } ); } }; int main() { run_with_time_limit( [] { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< driver_t >() ); } ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/this_agent_disp_binder/000077500000000000000000000000001523065210100241015ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/this_agent_disp_binder/CMakeLists.txt000066400000000000000000000001471523065210100266430ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.this_agent_disp_binder) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/this_agent_disp_binder/main.cpp000066400000000000000000000056261523065210100255420ustar00rootroot00000000000000/* * A unit-test for agent_t::so_this_agent_disp_binder(). */ #include #include #include namespace test { struct msg_done final : public so_5::signal_t {}; struct msg_take_tid final : public so_5::message_t { so_5::current_thread_id_t m_tid; explicit msg_take_tid( so_5::current_thread_id_t tid ) : m_tid{ tid } {} }; class a_collector_t final : public so_5::agent_t { std::set< so_5::current_thread_id_t > m_tids; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_collector_t::evt_done ) .event( &a_collector_t::evt_take_tid ) ; } void so_evt_start() override { so_5::send< msg_take_tid >( *this, so_5::query_current_thread_id() ); } void so_evt_finish() override { ensure_or_die( 3u == m_tids.size(), "unexpected number of collected TIDs: " + std::to_string( m_tids.size() ) ); } private: void evt_done( mhood_t< msg_done > ) { so_deregister_agent_coop_normally(); } void evt_take_tid( mhood_t< msg_take_tid > cmd ) { m_tids.insert( cmd->m_tid ); } }; class a_child_t final : public so_5::agent_t { const so_5::mbox_t m_collector_mbox; public: a_child_t( context_t ctx, so_5::mbox_t collector_mbox ) : so_5::agent_t{ std::move(ctx) } , m_collector_mbox{ std::move(collector_mbox) } {} void so_evt_start() override { so_5::send< msg_take_tid >( m_collector_mbox, so_5::query_current_thread_id() ); so_5::send< msg_done >( m_collector_mbox ); } }; class a_parent_t final : public so_5::agent_t { const so_5::mbox_t m_collector_mbox; public: a_parent_t( context_t ctx, so_5::mbox_t collector_mbox ) : so_5::agent_t{ std::move(ctx) } , m_collector_mbox{ std::move(collector_mbox) } {} void so_evt_start() override { so_5::send< msg_take_tid >( m_collector_mbox, so_5::query_current_thread_id() ); so_5::introduce_child_coop( *this, so_this_agent_disp_binder(), [this]( so_5::coop_t & coop ) { coop.make_agent< a_child_t >( m_collector_mbox ); } ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { // The collector will use the default dispatcher // (because it's automatically selected for the coop). auto mbox = coop.make_agent< a_collector_t >()->so_direct_mbox(); // The parent agent will use active_obj dispatcher. coop.make_agent_with_binder< a_parent_t >( so_5::disp::active_obj::make_dispatcher( env ).binder(), mbox ); } ); } } /* namespace test */ int main() { try { run_with_time_limit( []{ so_5::launch( test::init ); }, 5 ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/coop/throw_on_bind_to_disp/000077500000000000000000000000001523065210100237665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/throw_on_bind_to_disp/CMakeLists.txt000066400000000000000000000001461523065210100265270ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.throw_on_bind_to_disp) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/throw_on_bind_to_disp/main.cpp000066400000000000000000000062371523065210100254260ustar00rootroot00000000000000/* * A test of handling an exception during binding to dispatcher. */ #include #include #include #include #include #include #include #include #include so_5::atomic_counter_t g_agents_count; so_5::atomic_counter_t g_evt_count; const char * const g_test_mbox_name = "test_mbox"; struct some_message : public so_5::signal_t {}; class a_ordinary_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: a_ordinary_t( so_5::environment_t & env ) : base_type_t( env ) { ++g_agents_count; } ~a_ordinary_t() override { --g_agents_count; } void so_define_agent() override { so_5::mbox_t mbox = so_environment() .create_mbox( g_test_mbox_name ); so_subscribe( mbox ) .in( so_default_state() ) .event( &a_ordinary_t::some_handler ); // Give time to test message sender. std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); } void so_evt_start() override; void some_handler( mhood_t< some_message > ); }; void a_ordinary_t::so_evt_start() { ++g_evt_count; } void a_ordinary_t::some_handler( mhood_t< some_message > ) { ++g_evt_count; } class a_throwing_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: a_throwing_t( so_5::environment_t & env ) : base_type_t( env ) { ++g_agents_count; } ~a_throwing_t() override { --g_agents_count; } void so_evt_start() override; }; class throwing_disp_binder_t : public so_5::disp_binder_t { public: void preallocate_resources( so_5::agent_t & /*agent*/ ) override { throw std::runtime_error( "throwing while binding agent to disp" ); } void undo_preallocation( so_5::agent_t & /*agent*/ ) noexcept override {} void bind( so_5::agent_t & /*agent*/ ) noexcept override {} virtual void unbind( so_5::agent_t & /*agent*/ ) noexcept override {} }; void a_throwing_t::so_evt_start() { // This method should not be called. std::cerr << "error: a_throwing_t::so_evt_start called."; std::abort(); } void reg_coop( so_5::environment_t & env ) { auto coop = env.make_coop(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); // This agent will throw an exception during binding for dispatcher. coop->make_agent_with_binder< a_throwing_t >( std::make_shared< throwing_disp_binder_t >() ); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); try { env.register_coop( std::move( coop ) ); } catch(...) {} } void init( so_5::environment_t & env ) { reg_coop( env ); env.stop(); } int main() { run_with_time_limit( [] { so_5::launch( &init ); if( 0 != g_agents_count ) { std::cerr << "g_agents_count: " << g_agents_count << "\n"; throw std::runtime_error( "g_agents_count != 0" ); } std::cout << "event handled: " << g_evt_count << "\n"; }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/throw_on_bind_to_disp_2/000077500000000000000000000000001523065210100242075ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/throw_on_bind_to_disp_2/CMakeLists.txt000066400000000000000000000001501523065210100267430ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.throw_on_bind_to_disp_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/throw_on_bind_to_disp_2/main.cpp000066400000000000000000000053561523065210100256500ustar00rootroot00000000000000/* * A test of handling an exception during binding to dispatcher. */ #include #include #include #include #include #include #include #include #include so_5::atomic_counter_t g_agents_count; struct some_message : public so_5::signal_t {}; class a_ordinary_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: a_ordinary_t( so_5::environment_t & env ) : base_type_t( env ) { ++g_agents_count; } ~a_ordinary_t() override { --g_agents_count; } void so_define_agent() override { so_subscribe( so_direct_mbox() ) .in( so_default_state() ) .event( &a_ordinary_t::some_handler ); so_5::send< some_message >( so_direct_mbox() ); } void so_evt_start() override; void some_handler( mhood_t< some_message > ); }; void a_ordinary_t::so_evt_start() { // This method should not be called. std::cerr << "error: a_ordinary_t::so_evt_start called."; std::abort(); } void a_ordinary_t::some_handler( mhood_t< some_message > ) { // This method should not be called. std::cerr << "error: a_ordinary_t::some_handler called."; std::abort(); } class throwing_disp_binder_t : public so_5::disp_binder_t { public: void preallocate_resources( so_5::agent_t & /*agent*/ ) override { std::this_thread::sleep_for( std::chrono::milliseconds( 300 ) ); throw std::runtime_error( "throwing while binding agent to disp" ); } void undo_preallocation( so_5::agent_t & /*agent*/ ) noexcept override {} void bind( so_5::agent_t & /*agent*/ ) noexcept override {} void unbind( so_5::agent_t & /*agent*/ ) noexcept override {} }; void reg_coop( so_5::environment_t & env ) { auto coop = env.make_coop( so_5::disp::active_obj::make_dispatcher( env ).binder() ); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); // This agent will throw an exception during binding for dispatcher. coop->make_agent_with_binder< a_ordinary_t >( std::make_shared< throwing_disp_binder_t >() ); try { env.register_coop( std::move( coop ) ); } catch( const std::exception & x ) { std::cout << "throw_on_bind_to_disp_2, expected exception: " << x.what() << std::endl; } } void init( so_5::environment_t & env ) { reg_coop( env ); env.stop(); } int main() { run_with_time_limit( [] { so_5::launch( &init ); if( 0 != g_agents_count ) { std::cerr << "g_agents_count: " << g_agents_count << "\n"; throw std::runtime_error( "g_agents_count != 0" ); } }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/throw_on_define_agent/000077500000000000000000000000001523065210100237415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/throw_on_define_agent/CMakeLists.txt000066400000000000000000000001461523065210100265020ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.throw_on_define_agent) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/throw_on_define_agent/main.cpp000066400000000000000000000047661523065210100254060ustar00rootroot00000000000000/* * A test for handling of exception during so_define_agent() calling. */ #include #include #include #include #include #include #include const char * const g_test_mbox_name = "test_mbox"; struct some_message : public so_5::signal_t {}; class a_ordinary_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: a_ordinary_t( so_5::environment_t & env ) : base_type_t( env ) {} void so_define_agent() override { so_5::mbox_t mbox = so_environment() .create_mbox( g_test_mbox_name ); so_subscribe( mbox ) .in( so_default_state() ) .event( &a_ordinary_t::some_handler ); // Give some time to agent which sends messages. std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); } void so_evt_start() override; void some_handler( mhood_t< some_message > ); }; void a_ordinary_t::so_evt_start() { // This method should not be called. std::cerr << "error: a_ordinary_t::so_evt_start called."; std::abort(); } void a_ordinary_t::some_handler( mhood_t< some_message > ) { // This method should not be called. std::cerr << "error: a_ordinary_t::some_handler called."; std::abort(); } class a_throwing_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: a_throwing_t( so_5::environment_t & env ) : base_type_t( env ) {} void so_define_agent() override { throw std::runtime_error( "test throwing while defining agent" ); } void so_evt_start() override; }; void a_throwing_t::so_evt_start() { // This method should not be called. std::cerr << "error: a_throwing_t::so_evt_start called."; std::abort(); } void reg_coop( so_5::environment_t & env ) { auto coop = env.make_coop(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); // An agent which will throw an exception. coop->make_agent< a_throwing_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); try { env.register_coop( std::move( coop ) ); } catch(...) {} } void init( so_5::environment_t & env ) { reg_coop( env ); env.stop(); } int main() { run_with_time_limit( [] { so_5::launch( &init ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/unknown_exception_define_agent/000077500000000000000000000000001523065210100256575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/unknown_exception_define_agent/CMakeLists.txt000066400000000000000000000001571523065210100304220ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.unknown_exception_define_agent) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/unknown_exception_define_agent/main.cpp000066400000000000000000000022061523065210100273070ustar00rootroot00000000000000/* * Test for unknown exception from so_define_agent. */ #include #include class test_agent_t final : public so_5::agent_t { public : test_agent_t( context_t ctx ) : agent_t{ std::move(ctx) } {} void so_define_agent() override { throw( "boom!" ); } void so_evt_start() override { // This method should never be called! std::cout << "so_evt_start() is called after an exception from " "so_define_agent()!" << std::endl; std::abort(); } }; int main() { try { run_with_time_limit( []() { try { so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch"; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/coop/user_resource/000077500000000000000000000000001523065210100222775ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/coop/user_resource/CMakeLists.txt000066400000000000000000000001361523065210100250370ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.user_resource) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/coop/user_resource/main.cpp000066400000000000000000000074661523065210100237440ustar00rootroot00000000000000/* * A test for deallocating user resources. */ #include #include #include #include struct sequence_holder_t { typedef std::vector< int > vec_t; vec_t m_sequence; }; const int ID_COOP = 1; const int ID_RESOURCE = 2; const int ID_AGENT = 3; struct resource_t { sequence_holder_t & m_holder; resource_t( sequence_holder_t & holder ) : m_holder( holder ) {} ~resource_t() { m_holder.m_sequence.push_back( ID_RESOURCE ); } }; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, resource_t & resource ) : base_type_t( env ) , m_resource( resource ) { } ~a_test_t() override { m_resource.m_holder.m_sequence.push_back( ID_AGENT ); } void so_evt_start() override { so_deregister_agent_coop_normally(); } private : resource_t & m_resource; }; class test_agent_coop_t : public so_5::coop_t { typedef so_5::coop_t base_type_t; public : test_agent_coop_t( so_5::coop_id_t id, so_5::coop_handle_t parent_coop, so_5::disp_binder_shptr_t coop_disp_binder, so_5::environment_t & env, sequence_holder_t & sequence ) : base_type_t{ id, std::move(parent_coop), std::move(coop_disp_binder), so_5::outliving_mutable(env) } , m_sequence( sequence ) {} ~test_agent_coop_t() override { m_sequence.m_sequence.push_back( ID_COOP ); } private : sequence_holder_t & m_sequence; }; class a_test_starter_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_starter_t( so_5::environment_t & env, sequence_holder_t & sequence ) : base_type_t( env ) , m_sequence( sequence ) , m_self_mbox( env.create_mbox() ) {} void so_define_agent() override { so_subscribe( m_self_mbox ).event( &a_test_starter_t::evt_child_destroyed ); } void so_evt_start() override { so_5::coop_unique_holder_t coop( so_5::coop_shptr_t{ new test_agent_coop_t( 1234567u, so_coop(), so_5::make_default_disp_binder( so_environment() ), so_environment(), m_sequence ) } ); coop->add_dereg_notificator( so_5::make_coop_dereg_notificator( m_self_mbox ) ); resource_t * r = coop->take_under_control( std::make_unique< resource_t >( m_sequence ) ); coop->make_agent< a_test_t >( *r ); so_environment().register_coop( std::move( coop ) ); } void evt_child_destroyed( mhood_t< so_5::msg_coop_deregistered > ) { so_environment().stop(); } private : sequence_holder_t & m_sequence; const so_5::mbox_t m_self_mbox; }; std::string sequence_to_string( const std::vector< int > & s ) { std::ostringstream out; for( auto i = s.begin(); i != s.end(); ++i ) { if( i != s.begin() ) out << ", "; out << *i; } return out.str(); } class test_env_t { public : void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_starter_t >( m_sequence ) ); } void check_result() const { sequence_holder_t::vec_t expected1{ ID_COOP, ID_AGENT, ID_RESOURCE }; sequence_holder_t::vec_t expected2{ ID_AGENT, ID_RESOURCE, ID_COOP }; if( m_sequence.m_sequence != expected1 && m_sequence.m_sequence != expected2 ) throw std::runtime_error( "Wrong deinit sequence:\n" "actual: " + sequence_to_string( m_sequence.m_sequence ) + "\n" "expected1: " + sequence_to_string( expected1 ) + "\n" "expected2: " + sequence_to_string( expected2 ) ); } private : sequence_holder_t m_sequence; }; int main() { run_with_time_limit( [] { test_env_t test_env; so_5::launch( [&]( so_5::environment_t & env ) { test_env.init( env ); } ); test_env.check_result(); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/details/000077500000000000000000000000001523065210100200775ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/details/CMakeLists.txt000066400000000000000000000003111523065210100226320ustar00rootroot00000000000000add_subdirectory(lock_holder_detector) add_subdirectory(null_mutex_lock_shared) add_subdirectory(remaining_time_counter) add_subdirectory(invoke_noexcept_code) add_subdirectory(invoke_noexcept_code_2) sobjectizer-5.8.6/dev/test/so_5/details/invoke_noexcept_code/000077500000000000000000000000001523065210100242715ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/details/invoke_noexcept_code/CMakeLists.txt000066400000000000000000000002051523065210100270260ustar00rootroot00000000000000set(UNITTEST _unit.test.details.invoke_noexcept_code) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/details/invoke_noexcept_code/main.cpp000066400000000000000000000005171523065210100257240ustar00rootroot00000000000000/* * A test for so_5::details::invoke_noexcept_code. * * Test must crash. */ #include #include void function_with_throw() { throw std::runtime_error( "unexcpected_error" ); } int main() { so_5::details::invoke_noexcept_code( [] { function_with_throw(); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/details/invoke_noexcept_code_2/000077500000000000000000000000001523065210100245125ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/details/invoke_noexcept_code_2/CMakeLists.txt000066400000000000000000000001521523065210100272500ustar00rootroot00000000000000set(UNITTEST _unit.test.details.invoke_noexcept_code_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/details/invoke_noexcept_code_2/main.cpp000066400000000000000000000004401523065210100261400ustar00rootroot00000000000000/* * A test for so_5::details::invoke_noexcept_code. */ #include struct functor_with_tricky_call_operator { void operator()() && {} }; int main() { so_5::details::invoke_noexcept_code( functor_with_tricky_call_operator{} ); return 0; } sobjectizer-5.8.6/dev/test/so_5/details/lock_holder_detector/000077500000000000000000000000001523065210100242555ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/details/lock_holder_detector/CMakeLists.txt000066400000000000000000000001501523065210100270110ustar00rootroot00000000000000set(UNITTEST _unit.test.details.lock_holder_detector) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/details/lock_holder_detector/main.cpp000066400000000000000000000015021523065210100257030ustar00rootroot00000000000000/* * A test for so_5::details::lock_holder_detector. */ #include #include #include template< typename LOCK_TYPE > class test : private ::so_5::details::lock_holder_detector::type { int i = 0; public : void inc() { this->lock_and_perform( [&]{ ++i; } ); } int val() const { return this->lock_and_perform( [&]{ return i; } ); } }; int main() { static_assert( sizeof(std::mutex) > sizeof(so_5::null_mutex_t), "sizeof of std::mutex is expected to be sizeof of so_5::null_mutex_t" ); test< std::mutex > t_real_mutex; test< so_5::null_mutex_t > t_null_mutex; ensure_or_die( sizeof(t_real_mutex) > sizeof(t_null_mutex), "sizeof of t_real_mutex is expected to be sizeof of t_null_mutex" ); return 0; } sobjectizer-5.8.6/dev/test/so_5/details/null_mutex_lock_shared/000077500000000000000000000000001523065210100246315ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/details/null_mutex_lock_shared/CMakeLists.txt000066400000000000000000000001521523065210100273670ustar00rootroot00000000000000set(UNITTEST _unit.test.details.null_mutex_lock_shared) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/details/null_mutex_lock_shared/main.cpp000066400000000000000000000007001523065210100262560ustar00rootroot00000000000000/* * A test for so_5::null_mutex_t and std::shared_lock. */ #include #include #include #include template< typename Mutex > void lock_then_unlock_shared( Mutex && mtx ) { std::shared_lock< std::decay_t > lock{ mtx }; std::cout << "lock_then_unlock_shared" << std::endl; } int main() { so_5::null_mutex_t mtx; lock_then_unlock_shared( mtx ); return 0; } sobjectizer-5.8.6/dev/test/so_5/details/remaining_time_counter/000077500000000000000000000000001523065210100246255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/details/remaining_time_counter/CMakeLists.txt000066400000000000000000000001521523065210100273630ustar00rootroot00000000000000set(UNITTEST _unit.test.details.remaining_time_counter) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/details/remaining_time_counter/main.cpp000066400000000000000000000014341523065210100262570ustar00rootroot00000000000000/* * A test for so_5::details::remaining_time_counter. * */ #include #include #include #include #include int main() { const std::chrono::seconds pause(2); const auto started_at = std::chrono::steady_clock::now(); so_5::details::remaining_time_counter_t counter( pause ); for( int i = 0; i < 5 && counter; ++i ) { std::cout << "sleeping #" << i << std::endl; std::this_thread::sleep_for( std::chrono::milliseconds(500) ); counter.update(); } const auto finished_at = std::chrono::steady_clock::now(); ensure_or_die( pause <= (finished_at - started_at), "at least 2s must be passed " ); ensure_or_die( !counter, "no more time must left" ); return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/000077500000000000000000000000001523065210100174115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/CMakeLists.txt000066400000000000000000000006411523065210100221520ustar00rootroot00000000000000add_subdirectory(binder) add_subdirectory(one_thread) add_subdirectory(nef_one_thread) add_subdirectory(active_obj) add_subdirectory(active_group) add_subdirectory(thread_pool) add_subdirectory(adv_thread_pool) add_subdirectory(nef_thread_pool) add_subdirectory(private_dispatchers) add_subdirectory(prio_ot_strictly_ordered) add_subdirectory(prio_ot_quoted_round_robin) add_subdirectory(prio_dt_one_per_prio) sobjectizer-5.8.6/dev/test/so_5/disp/active_group/000077500000000000000000000000001523065210100221005ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/active_group/CMakeLists.txt000066400000000000000000000000461523065210100246400ustar00rootroot00000000000000add_subdirectory(custom_work_thread) sobjectizer-5.8.6/dev/test/so_5/disp/active_group/custom_work_thread/000077500000000000000000000000001523065210100260035ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/active_group/custom_work_thread/CMakeLists.txt000066400000000000000000000001601523065210100305400ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.active_group.custom_work_thread) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/active_group/custom_work_thread/main.cpp000066400000000000000000000033661523065210100274430ustar00rootroot00000000000000/* * Check for the usage of custom work thread factory. */ #include #include #include #include "../../custom_work_thread.hpp" class a_test_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void run_test() { auto factory = std::make_shared< disp_tests::custom_work_thread_factory_t >(); so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp_params = so_5::disp::active_group::disp_params_t{}; disp_params.work_thread_factory( factory ); auto disp = so_5::disp::active_group::make_dispatcher( env, "demo_custom_factory", disp_params ); coop.make_agent_with_binder< a_test_t >( disp.binder("1") ); coop.make_agent_with_binder< a_test_t >( disp.binder("2") ); coop.make_agent_with_binder< a_test_t >( disp.binder("1") ); } ); }, []( so_5::environment_params_t & /*params*/ ) { #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); ensure_or_die( 2 == factory->started(), "unexpected number of started threads" ); ensure_or_die( 2 == factory->finished(), "unexpected number of finished threads" ); ensure_or_die( 2 == factory->created(), "unexpected number of created threads" ); ensure_or_die( 2 == factory->destroyed(), "unexpected number of destroyed threads" ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/active_obj/000077500000000000000000000000001523065210100215165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/active_obj/CMakeLists.txt000066400000000000000000000000461523065210100242560ustar00rootroot00000000000000add_subdirectory(custom_work_thread) sobjectizer-5.8.6/dev/test/so_5/disp/active_obj/custom_work_thread/000077500000000000000000000000001523065210100254215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/active_obj/custom_work_thread/CMakeLists.txt000066400000000000000000000001561523065210100301630ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.active_obj.custom_work_thread) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/active_obj/custom_work_thread/main.cpp000066400000000000000000000033511523065210100270530ustar00rootroot00000000000000/* * Check for the usage of custom work thread factory. */ #include #include #include #include "../../custom_work_thread.hpp" class a_test_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void run_test() { auto factory = std::make_shared< disp_tests::custom_work_thread_factory_t >(); so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp_params = so_5::disp::active_obj::disp_params_t{}; disp_params.work_thread_factory( factory ); auto disp = so_5::disp::active_obj::make_dispatcher( env, "demo_custom_factory", disp_params ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); } ); }, []( so_5::environment_params_t & /*params*/ ) { #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); ensure_or_die( 3 == factory->started(), "unexpected number of started threads" ); ensure_or_die( 3 == factory->finished(), "unexpected number of finished threads" ); ensure_or_die( 3 == factory->created(), "unexpected number of created threads" ); ensure_or_die( 3 == factory->destroyed(), "unexpected number of destroyed threads" ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/000077500000000000000000000000001523065210100225435ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/CMakeLists.txt000066400000000000000000000005561523065210100253110ustar00rootroot00000000000000add_subdirectory(chstate_in_safe) add_subdirectory(cooperation_fifo) add_subdirectory(cooperation_fifo_2) add_subdirectory(individual_fifo) add_subdirectory(simple) add_subdirectory(subscr_in_safe) add_subdirectory(unsafe_after_safe) add_subdirectory(custom_work_thread) add_subdirectory(exception_from_safe_handler) add_subdirectory(exception_from_safe_handler_2) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/chstate_in_safe/000077500000000000000000000000001523065210100256625ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/chstate_in_safe/CMakeLists.txt000066400000000000000000000001601523065210100304170ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.chstate_in_safe) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/chstate_in_safe/main.cpp000066400000000000000000000052641523065210100273210ustar00rootroot00000000000000/* * A test for adv_thread_pool dispatcher: so_change_state * must throw exception in thread safe event handlers. */ #include #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace atp_disp = so_5::disp::adv_thread_pool; struct msg_shutdown : public so_5::signal_t {}; struct msg_safe_signal : public so_5::signal_t {}; struct msg_unsafe_signal : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env ) : so_5::agent_t( env ) { } void so_define_agent() override { so_change_state( st_safe ); so_subscribe( so_direct_mbox() ).in( st_unsafe ) .event( &a_test_t::evt_shutdown ); so_subscribe( so_direct_mbox() ).in( st_safe ) .event( &a_test_t::evt_safe_signal, so_5::thread_safe ) .event( &a_test_t::evt_unsafe_signal ); } void so_evt_start() override { so_5::send< msg_safe_signal >( *this ); so_5::send< msg_unsafe_signal >( *this ); so_5::send< msg_shutdown >( *this ); } void evt_shutdown(mhood_t< msg_shutdown >) { so_environment().stop(); } void evt_safe_signal(mhood_t< msg_safe_signal >) { bool exception_thrown = true; try { so_change_state( st_unsafe ); exception_thrown = false; } catch( const so_5::exception_t & x ) { std::cout << "expected exception: " << x.what() << std::endl; } if( !exception_thrown ) throw std::runtime_error( "an exception on so_change_state expected" ); } void evt_unsafe_signal(mhood_t< msg_unsafe_signal >) { so_change_state( st_unsafe ); } private : const so_5::state_t st_safe{ this, "safe" }; const so_5::state_t st_unsafe{ this, "unsafe" }; }; void run_sobjectizer( atp_disp::queue_traits::lock_factory_t factory ) { so_5::launch( [&]( so_5::environment_t & env ) { using namespace atp_disp; env.register_agent_as_coop( env.make_agent< a_test_t >(), make_dispatcher( env, "thread_pool", disp_params_t{} .set_queue_params( queue_traits::queue_params_t{} .lock_factory( factory ) ) ) .binder() ); } ); } int main() { try { for_each_lock_factory( []( atp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { run_sobjectizer( factory ); }, 20, "chstate_in_safe test" ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/cooperation_fifo/000077500000000000000000000000001523065210100260705ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/cooperation_fifo/CMakeLists.txt000066400000000000000000000001611523065210100306260ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.cooperation_fifo) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/cooperation_fifo/main.cpp000066400000000000000000000055011523065210100275210ustar00rootroot00000000000000/* * A simple test for adv_thread_pool dispatcher. */ #include #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace atp_disp = so_5::disp::adv_thread_pool; struct msg_shutdown : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env, const so_5::mbox_t & shutdowner_mbox ) : so_5::agent_t( env ) , m_shutdowner_mbox( shutdowner_mbox ) { } void so_evt_start() override { auto w = ++m_workers; if( w > 1 ) { std::cerr << "too many workers: " << w << std::endl; } std::this_thread::sleep_for( std::chrono::milliseconds( 250 ) ); w = --m_workers; if( w != 0 ) { std::cout << "expected no more workers, but: " << w << std::endl; } so_5::send< msg_shutdown >( m_shutdowner_mbox ); } private : static std::atomic_uint m_workers; const so_5::mbox_t m_shutdowner_mbox; }; std::atomic_uint a_test_t::m_workers = ATOMIC_VAR_INIT( 0 ); class a_shutdowner_t : public so_5::agent_t { public : a_shutdowner_t( so_5::environment_t & env, std::size_t working_agents ) : so_5::agent_t( env ) , m_working_agents( working_agents ) {} void so_define_agent() override { so_subscribe( so_direct_mbox() ) .event( [this](mhood_t< msg_shutdown >) { --m_working_agents; if( !m_working_agents ) so_environment().stop(); } ); } private : std::size_t m_working_agents; }; const std::size_t thread_count = 4; void run_sobjectizer( atp_disp::queue_traits::lock_factory_t factory ) { so_5::launch( [&]( so_5::environment_t & env ) { so_5::mbox_t shutdowner_mbox; { auto c = env.make_coop(); auto a = c->make_agent< a_shutdowner_t >( thread_count ); shutdowner_mbox = a->so_direct_mbox(); env.register_coop( std::move( c ) ); } auto disp = atp_disp::make_dispatcher( env, "thread_pool", atp_disp::disp_params_t{} .thread_count( thread_count ) .set_queue_params( atp_disp::queue_traits::queue_params_t{} .lock_factory( factory ) ) ); auto c = env.make_coop( disp.binder() ); for( std::size_t i = 0; i != thread_count; ++i ) { c->make_agent< a_test_t >( shutdowner_mbox ); } env.register_coop( std::move( c ) ); } ); } int main() { try { for_each_lock_factory( []( atp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { run_sobjectizer( factory ); }, 20, "cooperation_fifo test" ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/cooperation_fifo_2/000077500000000000000000000000001523065210100263115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/cooperation_fifo_2/CMakeLists.txt000066400000000000000000000001631523065210100310510ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.cooperation_fifo_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/cooperation_fifo_2/main.cpp000066400000000000000000000100311523065210100277340ustar00rootroot00000000000000/* * A test for adv_thread_pool dispatcher when a single binder with * cooperative_fifo is used for several coops. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace tp_disp = so_5::disp::adv_thread_pool; typedef std::set< so_5::current_thread_id_t > thread_id_set_t; class thread_id_collector_t { public : void add_current_thread() { std::lock_guard< so_5::default_spinlock_t > l( m_lock ); m_set.insert( so_5::query_current_thread_id() ); } std::size_t set_size() const { return m_set.size(); } const thread_id_set_t & query_set() const { return m_set; } private : so_5::default_spinlock_t m_lock; thread_id_set_t m_set; }; struct msg_shutdown : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env, thread_id_collector_t & collector, const so_5::mbox_t & shutdowner_mbox ) : so_5::agent_t( env ) , m_collector( collector ) , m_shutdowner_mbox( shutdowner_mbox ) { } void so_evt_start() override { // Block the current thread for some time. // Because of that so_evt_start for an agent from a different // coop has to be started on a separate thread. std::this_thread::sleep_for( std::chrono::milliseconds{ 250 } ); m_collector.add_current_thread(); so_5::send< msg_shutdown >( m_shutdowner_mbox ); } private : thread_id_collector_t & m_collector; const so_5::mbox_t m_shutdowner_mbox; }; class a_shutdowner_t : public so_5::agent_t { public : a_shutdowner_t( so_5::environment_t & env, std::size_t working_agents ) : so_5::agent_t( env ) , m_working_agents( working_agents ) {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< msg_shutdown >) { --m_working_agents; if( !m_working_agents ) so_environment().stop(); } ); } private : std::size_t m_working_agents; }; const std::size_t cooperation_count = 4; void run_sobjectizer( tp_disp::queue_traits::lock_factory_t factory, thread_id_collector_t & collector ) { duration_meter_t duration( "running of test cooperations" ); so_5::launch( [&]( so_5::environment_t & env ) { so_5::mbox_t shutdowner_mbox; { auto c = env.make_coop(); auto a = c->make_agent< a_shutdowner_t >( cooperation_count ); shutdowner_mbox = a->so_direct_mbox(); env.register_coop( std::move( c ) ); } auto disp = tp_disp::make_dispatcher( env, "adv_thread_pool", tp_disp::disp_params_t{} .thread_count( cooperation_count ) .set_queue_params( tp_disp::queue_traits::queue_params_t{} .lock_factory( factory ) ) ); auto params = tp_disp::bind_params_t{} .fifo( tp_disp::fifo_t::cooperation ); auto the_same_binder = disp.binder( params ); for( std::size_t i = 0; i != cooperation_count; ++i ) { auto c = env.make_coop( the_same_binder ); c->make_agent< a_test_t >( collector, shutdowner_mbox ); env.register_coop( std::move( c ) ); } } ); } void analyze_results( const thread_id_collector_t & collector ) { if( cooperation_count != collector.set_size() ) { throw std::runtime_error{ "there is a set with size: " + std::to_string( collector.set_size() ) }; } } void run_and_check( tp_disp::queue_traits::lock_factory_t factory ) { thread_id_collector_t collector; run_sobjectizer( factory, collector ); analyze_results( collector ); } int main() { try { for_each_lock_factory( []( tp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { run_and_check( factory ); }, 240 ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/custom_work_thread/000077500000000000000000000000001523065210100264465ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/custom_work_thread/CMakeLists.txt000066400000000000000000000001631523065210100312060ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.custom_work_thread) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/custom_work_thread/main.cpp000066400000000000000000000031411523065210100300750ustar00rootroot00000000000000/* * Check for the usage of custom work thread factory. */ #include #include #include #include "../../custom_work_thread.hpp" class a_test_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void run_test() { auto factory = std::make_shared< disp_tests::custom_work_thread_factory_t >(); so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp = so_5::disp::adv_thread_pool::make_dispatcher( env, "demo_custom_factory", so_5::disp::adv_thread_pool::disp_params_t{} .thread_count( 5 ) .work_thread_factory( factory ) ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); } ); }, []( so_5::environment_params_t & /*params*/ ) { #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); ensure_or_die( 5 == factory->started(), "unexpected number of started threads" ); ensure_or_die( 5 == factory->finished(), "unexpected number of finished threads" ); ensure_or_die( 5 == factory->created(), "unexpected number of created threads" ); ensure_or_die( 5 == factory->destroyed(), "unexpected number of destroyed threads" ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/exception_from_safe_handler/000077500000000000000000000000001523065210100302575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/exception_from_safe_handler/CMakeLists.txt000066400000000000000000000002311523065210100330130ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.exception_from_safe_handler) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/exception_from_safe_handler/main.cpp000066400000000000000000000026161523065210100317140ustar00rootroot00000000000000/* * Check for an exception from thread_safe event handler. * Test test should fail. */ #include #include #include class a_test_t final : public so_5::agent_t { struct check_it final : public so_5::signal_t {}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_check_it, so_5::thread_safe ); } void so_evt_start() override { so_5::send< check_it >( *this ); } so_5::exception_reaction_t so_exception_reaction() const noexcept override { return so_5::deregister_coop_on_exception; } private: void evt_check_it( mhood_t ) { throw std::runtime_error{ "Oops!" }; } }; void run_test() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp = so_5::disp::adv_thread_pool::make_dispatcher( env ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); } ); }, []( so_5::environment_params_t & /*params*/ ) { #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/exception_from_safe_handler_2/000077500000000000000000000000001523065210100305005ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/exception_from_safe_handler_2/CMakeLists.txt000066400000000000000000000001761523065210100332440ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.exception_from_safe_handler_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/exception_from_safe_handler_2/main.cpp000066400000000000000000000031371523065210100321340ustar00rootroot00000000000000/* * Check for an exception from thread_safe event handler. * Test test should fail. */ #include #include #include class a_test_t final : public so_5::agent_t { struct check_it final : public so_5::signal_t {}; struct quit final : public so_5::signal_t {}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_test_t::evt_check_it, so_5::thread_safe ) .event( &a_test_t::evt_quit, so_5::not_thread_safe ) ; } void so_evt_start() override { so_5::send< check_it >( *this ); so_5::send< quit >( *this ); } so_5::exception_reaction_t so_exception_reaction() const noexcept override { return so_5::ignore_exception; } private: void evt_check_it( mhood_t ) { throw std::runtime_error{ "Oops!" }; } void evt_quit( mhood_t ) { so_deregister_agent_coop_normally(); } }; void run_test() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp = so_5::disp::adv_thread_pool::make_dispatcher( env ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); } ); }, []( so_5::environment_params_t & /*params*/ ) { #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/for_each_lock_factory.hpp000066400000000000000000000015161523065210100275640ustar00rootroot00000000000000#pragma once #include #include template< typename L > void run_with_lock_factory( const char * factory_name, so_5::disp::adv_thread_pool::queue_traits::lock_factory_t factory, L && action ) { std::cout << "=== " << factory_name << " ===" << std::endl; action( factory ); std::cout << "=======" << std::endl; } template< typename L > void for_each_lock_factory( L && action ) { using namespace so_5::disp::adv_thread_pool::queue_traits; run_with_lock_factory( "combined_lock()", combined_lock_factory(), std::forward(action) ); run_with_lock_factory( "combined_lock(250us)", combined_lock_factory( std::chrono::microseconds(250) ), std::forward(action) ); run_with_lock_factory( "simple_lock", simple_lock_factory(), std::forward(action) ); } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/individual_fifo/000077500000000000000000000000001523065210100256765ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/individual_fifo/CMakeLists.txt000066400000000000000000000001601523065210100304330ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.individual_fifo) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/individual_fifo/main.cpp000066400000000000000000000126541523065210100273360ustar00rootroot00000000000000/* * A test for adv_thread_pool dispatcher (individual_fifo mechanism). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace atp_disp = so_5::disp::adv_thread_pool; typedef std::set< so_5::current_thread_id_t > thread_id_set_t; class thread_id_collector_t { public : void add_current_thread() { std::lock_guard< so_5::default_spinlock_t > l( m_lock ); m_set.insert( so_5::query_current_thread_id() ); } std::size_t set_size() const { return m_set.size(); } const thread_id_set_t & query_set() const { return m_set; } private : so_5::default_spinlock_t m_lock; thread_id_set_t m_set; }; typedef std::shared_ptr< thread_id_collector_t > thread_id_collector_ptr_t; typedef std::vector< thread_id_collector_ptr_t > collector_container_t; struct msg_shutdown : public so_5::signal_t {}; struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env, thread_id_collector_t & collector, const so_5::mbox_t & shutdowner_mbox ) : so_5::agent_t( env ) , m_collector( collector ) , m_shutdowner_mbox( shutdowner_mbox ) , m_messages_sent( 0 ) { } void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_hello ); } void so_evt_start() override { m_collector.add_current_thread(); so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { m_collector.add_current_thread(); ++m_messages_sent; if( m_messages_sent >= 20 ) so_5::send< msg_shutdown >( m_shutdowner_mbox ); else so_5::send< msg_hello >( *this ); } private : thread_id_collector_t & m_collector; const so_5::mbox_t m_shutdowner_mbox; std::size_t m_messages_sent; }; class a_shutdowner_t : public so_5::agent_t { public : a_shutdowner_t( so_5::environment_t & env, std::size_t working_agents ) : so_5::agent_t( env ) , m_working_agents( working_agents ) {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< msg_shutdown >) { --m_working_agents; if( !m_working_agents ) so_environment().stop(); } ); } private : std::size_t m_working_agents; }; const std::size_t cooperation_count = 64; // 1000; const std::size_t cooperation_size = 128; // 100; const std::size_t thread_count = 8; const std::size_t total_agent_count = cooperation_count * cooperation_size; collector_container_t create_collectors() { collector_container_t collectors; collectors.reserve( total_agent_count ); for( std::size_t i = 0; i != total_agent_count; ++i ) collectors.emplace_back( std::make_shared< thread_id_collector_t >() ); return collectors; } void run_sobjectizer( atp_disp::queue_traits::lock_factory_t factory, collector_container_t & collectors ) { duration_meter_t duration( "running of test cooperations" ); so_5::launch( [&]( so_5::environment_t & env ) { so_5::mbox_t shutdowner_mbox; { auto c = env.make_coop(); auto a = c->make_agent< a_shutdowner_t >( total_agent_count ); shutdowner_mbox = a->so_direct_mbox(); env.register_coop( std::move( c ) ); } std::size_t collector_index = 0; auto disp = atp_disp::make_dispatcher( env, "thread_pool", atp_disp::disp_params_t{} .thread_count( thread_count ) .set_queue_params( atp_disp::queue_traits::queue_params_t{} .lock_factory( factory ) ) ); atp_disp::bind_params_t params; params.fifo( atp_disp::fifo_t::individual ); for( std::size_t i = 0; i != cooperation_count; ++i ) { auto c = env.make_coop( disp.binder( params ) ); for( std::size_t a = 0; a != cooperation_size; ++a, ++collector_index ) { c->make_agent< a_test_t >( *(collectors[ collector_index ]), shutdowner_mbox ); } env.register_coop( std::move( c ) ); } } ); } void analyze_results( const collector_container_t & collectors ) { thread_id_set_t all_threads; std::size_t total_threads_id = 0; std::size_t agents_with_only_one_thread = 0; for( auto & c : collectors ) { if( 1 == c->set_size() ) ++agents_with_only_one_thread; total_threads_id += c->set_size(); all_threads.insert( c->query_set().begin(), c->query_set().end() ); } if( agents_with_only_one_thread == total_agent_count ) { std::cerr << "all agents have worked only on one thread!" << std::endl; std::abort(); } double avg_thread_count = double(total_threads_id) / total_agent_count; std::cout << "all_threads size: " << all_threads.size() << std::endl; std::cout << "avg threads size: " << avg_thread_count << std::endl; } void run_and_check( atp_disp::queue_traits::lock_factory_t factory ) { auto collectors = create_collectors(); run_sobjectizer( factory, collectors ); analyze_results( collectors ); } int main() { try { for_each_lock_factory( []( atp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { run_and_check( factory ); }, 240, "individual_fifo test" ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/simple/000077500000000000000000000000001523065210100240345ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/simple/CMakeLists.txt000066400000000000000000000001471523065210100265760ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/simple/main.cpp000066400000000000000000000031341523065210100254650ustar00rootroot00000000000000/* * A simple test for thread_pool dispatcher. */ #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace atp_disp = so_5::disp::adv_thread_pool; struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env ) : so_5::agent_t( env ) {} void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_hello ); } void so_evt_start() override { so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { so_environment().stop(); } }; void do_test( atp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { so_5::launch( [&]( so_5::environment_t & env ) { using namespace atp_disp; auto disp = make_dispatcher( env, "thread_pool", disp_params_t{} .thread_count( 4 ) .set_queue_params( queue_traits::queue_params_t{} .lock_factory( factory ) ) ); env.register_agent_as_coop( env.make_agent< a_test_t >(), disp.binder() ); } ); }, 20, "simple adv_thread_pool dispatcher test" ); } int main() { try { for_each_lock_factory( []( atp_disp::queue_traits::lock_factory_t factory ) { do_test( factory ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/subscr_in_safe/000077500000000000000000000000001523065210100255305ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/subscr_in_safe/CMakeLists.txt000066400000000000000000000001571523065210100302730ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.subscr_in_safe) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/subscr_in_safe/main.cpp000066400000000000000000000071721523065210100271670ustar00rootroot00000000000000/* * A test for adv_thread_pool dispatcher: subscription * management methods must throw exception in thread safe event handlers. */ #include #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace atp_disp = so_5::disp::adv_thread_pool; struct msg_shutdown : public so_5::signal_t {}; struct msg_safe_signal : public so_5::signal_t {}; struct msg_unsafe_signal : public so_5::signal_t {}; void expect_throw( const std::string & description, int error_code, std::function< void() > f ) { bool exception_thrown = true; try { f(); exception_thrown = false; } catch( const so_5::exception_t & x ) { if( error_code == x.error_code() ) std::cout << description << ", expected exception: " << x.what() << std::endl; else { std::ostringstream ss; ss << description << " expect exception with error_code(" << error_code << ", but has: " << x.what(); throw std::runtime_error( ss.str() ); } } if( !exception_thrown ) throw std::runtime_error( "an exception expected for " + description ); } class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env ) : so_5::agent_t( env ) { } void so_define_agent() override { so_change_state( st_safe ); so_subscribe_self().in( st_unsafe ) .event( &a_test_t::evt_shutdown ); so_subscribe_self().in( st_safe ) .event( &a_test_t::evt_safe_signal, so_5::thread_safe ) .event( &a_test_t::evt_unsafe_signal ); } void so_evt_start() override { so_5::send< msg_safe_signal >( *this ); so_5::send< msg_unsafe_signal >( *this ); so_5::send< msg_shutdown >( *this ); } void evt_shutdown(mhood_t< msg_shutdown >) { so_environment().stop(); } void evt_safe_signal(mhood_t< msg_safe_signal >) { expect_throw( "so_drop_subscription", so_5::rc_operation_enabled_only_on_agent_working_thread, [this]() { so_drop_subscription( so_direct_mbox(), st_safe, &a_test_t::evt_safe_signal ); } ); expect_throw( "so_drop_subscription_for_all_states", so_5::rc_operation_enabled_only_on_agent_working_thread, [this]() { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::evt_safe_signal ); } ); expect_throw( "so_subscribe", so_5::rc_operation_enabled_only_on_agent_working_thread, [this]() { so_subscribe_self().event( &a_test_t::evt_safe_signal ); } ); } void evt_unsafe_signal(mhood_t< msg_unsafe_signal >) { so_change_state( st_unsafe ); } private : const so_5::state_t st_safe{ this, "safe" }; const so_5::state_t st_unsafe{ this, "unsafe" }; }; void run_sobjectizer( atp_disp::queue_traits::lock_factory_t factory ) { so_5::launch( [&]( so_5::environment_t & env ) { using namespace atp_disp; env.register_agent_as_coop( env.make_agent< a_test_t >(), make_dispatcher( env, "thread_pool", disp_params_t{}.set_queue_params( queue_traits::queue_params_t{} .lock_factory( factory ) ) ) .binder() ); } ); } int main() { try { for_each_lock_factory( []( atp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { run_sobjectizer( factory ); }, 20, "subscr_in_safe test" ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/unsafe_after_safe/000077500000000000000000000000001523065210100262035ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/unsafe_after_safe/CMakeLists.txt000066400000000000000000000001621523065210100307420ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.adv_thread_pool.unsafe_after_safe) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/adv_thread_pool/unsafe_after_safe/main.cpp000066400000000000000000000056031523065210100276370ustar00rootroot00000000000000/* * A test for adv_thread_pool dispatcher: all thread safe handlers * must be finished before any thread unsafe handler. */ #include #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace atp_disp = so_5::disp::adv_thread_pool; const unsigned int thread_count = 4; struct msg_shutdown : public so_5::signal_t {}; struct msg_safe_signal : public so_5::signal_t {}; struct msg_unsafe_signal : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env ) : so_5::agent_t( env ) { m_workers = 0; } void so_define_agent() override { so_subscribe_self() .event( &a_test_t::evt_shutdown ) .event( &a_test_t::evt_safe_signal, so_5::thread_safe ) .event( &a_test_t::evt_unsafe_signal ); } void so_evt_start() override { for( size_t i = 0; i < 100; ++i ) { for( size_t j = 0; j != thread_count; ++j ) so_5::send< msg_safe_signal >( *this ); so_5::send< msg_unsafe_signal >( *this ); } so_5::send< msg_shutdown >( *this ); } void evt_shutdown(mhood_t< msg_shutdown >) { so_environment().stop(); } void evt_safe_signal(mhood_t< msg_safe_signal >) { ++m_workers; while( thread_count != m_workers.load( std::memory_order_acquire ) ) std::this_thread::yield(); } void evt_unsafe_signal(mhood_t< msg_unsafe_signal >) { if( thread_count != m_workers ) throw std::runtime_error( "m_workers != thread_count" ); m_workers = 1; using hrt = std::chrono::high_resolution_clock; auto f = hrt::now() + std::chrono::milliseconds( 5 ); do { if( 1 != m_workers ) throw std::runtime_error( "m_workers != 1" ); std::this_thread::sleep_for( std::chrono::microseconds( 100 ) ); } while( f > hrt::now() ); m_workers = 0; } private : std::atomic_uint m_workers; }; void run_sobjectizer( atp_disp::queue_traits::lock_factory_t factory ) { so_5::launch( [&]( so_5::environment_t & env ) { using namespace atp_disp; env.register_agent_as_coop( env.make_agent< a_test_t >(), make_dispatcher( env, "thread_pool", disp_params_t{} .thread_count( thread_count ) .set_queue_params( queue_traits::queue_params_t{} .lock_factory( factory ) ) ) .binder() ); } ); } int main() { try { for_each_lock_factory( []( atp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { run_sobjectizer( factory ); }, 20, "unsafe_after_safe test" ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/binder/000077500000000000000000000000001523065210100206545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/binder/CMakeLists.txt000066400000000000000000000002311523065210100234100ustar00rootroot00000000000000add_subdirectory(bind_to_disp_1) add_subdirectory(bind_to_disp_2) add_subdirectory(bind_to_disp_3) add_subdirectory(correct_unbind_after_throw_on_bind) sobjectizer-5.8.6/dev/test/so_5/disp/binder/bind_to_disp_1/000077500000000000000000000000001523065210100235315ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/binder/bind_to_disp_1/CMakeLists.txt000066400000000000000000000001461523065210100262720ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.binder.bind_to_disp_1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/binder/bind_to_disp_1/main.cpp000066400000000000000000000065731523065210100251740ustar00rootroot00000000000000/* * A test for dispatcher binders. * * A cooperation is registered. Agents of cooperation is * bound to different dispatchers. */ #include #include #include #include #include // Count of messages to be sent at once. const unsigned int g_send_at_once = 10; // A count of message bunches. const unsigned int g_send_session_count = 100; struct test_message : public so_5::message_t { test_message( bool is_last = false ): m_is_last(is_last) {} ~test_message() override {} bool m_is_last; }; // A signal to start sending. struct send_message_signal : public so_5::signal_t {}; class test_agent_sender_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_sender_t( so_5::environment_t & env, const so_5::mbox_t & mbox ) : base_type_t( env ), m_send_session_complited( 0 ), m_mbox_receiver( mbox ), m_notification_mbox( so_environment().create_mbox() ) {} ~test_agent_sender_t() override {} void so_define_agent() override; void so_evt_start() override; void evt_send_messages( mhood_t< send_message_signal > msg ); private: // A counter of bunch sent. unsigned int m_send_session_complited; // Receiver mbox. so_5::mbox_t m_mbox_receiver; // Self mbox. so_5::mbox_t m_notification_mbox; }; void test_agent_sender_t::so_define_agent() { so_subscribe( m_notification_mbox ) .event( &test_agent_sender_t::evt_send_messages ); } void test_agent_sender_t::so_evt_start() { so_5::send< send_message_signal >( m_notification_mbox ); } void test_agent_sender_t::evt_send_messages( mhood_t< send_message_signal > ) { for( unsigned int i = 0; i < g_send_at_once; ++i ) { so_5::send< test_message >( m_mbox_receiver ); } ++m_send_session_complited; // If all bunches are sent then final message should be sent. if( g_send_session_count <= m_send_session_complited ) { so_5::send< test_message >( m_mbox_receiver, true ); } else { so_5::send< send_message_signal >( m_notification_mbox ); } } class test_agent_receiver_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_receiver_t( so_5::environment_t & env, const so_5::mbox_t & mbox ) : base_type_t( env ), m_source_mbox( mbox ) {} ~test_agent_receiver_t() override {} void so_define_agent() override; void so_evt_start() override {} void evt_test( mhood_t< test_message > msg ); private: // A source of messages. so_5::mbox_t m_source_mbox; }; void test_agent_receiver_t::so_define_agent() { so_subscribe( m_source_mbox ) .event( &test_agent_receiver_t::evt_test ); } void test_agent_receiver_t::evt_test( mhood_t< test_message > msg ) { // Stop if this is the last message. if( msg->m_is_last ) so_environment().stop(); } void init( so_5::environment_t & env ) { so_5::mbox_t mbox = env.create_mbox(); auto coop = env.make_coop(); coop->make_agent_with_binder< test_agent_sender_t >( so_5::disp::one_thread::make_dispatcher( env ).binder(), mbox ); coop->make_agent_with_binder< test_agent_receiver_t >( so_5::disp::one_thread::make_dispatcher( env ).binder(), mbox ); env.register_coop( std::move( coop ) ); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/binder/bind_to_disp_2/000077500000000000000000000000001523065210100235325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/binder/bind_to_disp_2/CMakeLists.txt000066400000000000000000000001461523065210100262730ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.binder.bind_to_disp_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/binder/bind_to_disp_2/main.cpp000066400000000000000000000037551523065210100251740ustar00rootroot00000000000000/* * A test of binding of agent to active object dispatcher. */ #include #include #include #include #include #include class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_t( so_5::environment_t & env ) : base_type_t( env ) {} ~test_agent_t() override {} void so_evt_start() override; static std::size_t agents_cout() { return 20; } static bool ok() { return m_test_ok && agents_cout() == m_threads.size(); } private: static std::mutex m_lock; static std::set< std::thread::id > m_threads; static bool m_test_ok; }; std::mutex test_agent_t::m_lock; std::set< std::thread::id > test_agent_t::m_threads; bool test_agent_t::m_test_ok = true; void test_agent_t::so_evt_start() { auto tid = std::this_thread::get_id(); std::lock_guard< std::mutex > lock( m_lock ); if( m_threads.end() != m_threads.find( tid ) ) m_test_ok = false; m_threads.insert( tid ); } class test_agent_finisher_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_finisher_t( so_5::environment_t & env ) : base_type_t( env ) {} ~test_agent_finisher_t() override {} void so_evt_start() override { std::this_thread::sleep_for( std::chrono::milliseconds( 200 ) ); so_environment().stop(); } }; void init( so_5::environment_t & env ) { auto coop = env.make_coop(); auto disp = so_5::disp::active_obj::make_dispatcher( env ); for( std::size_t i = 0; i < test_agent_t::agents_cout(); ++i ) { coop->make_agent_with_binder< test_agent_t >( disp.binder() ); } coop->make_agent< test_agent_finisher_t >(); env.register_coop( std::move( coop ) ); } int main() { try { so_5::launch( &init ); if( !test_agent_t::ok() ) throw std::runtime_error( "!test_agent_t::ok()" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/binder/bind_to_disp_3/000077500000000000000000000000001523065210100235335ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/binder/bind_to_disp_3/CMakeLists.txt000066400000000000000000000001461523065210100262740ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.binder.bind_to_disp_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/binder/bind_to_disp_3/main.cpp000066400000000000000000000050161523065210100251650ustar00rootroot00000000000000/* * A test of binding agents to active group dispatchers. */ #include #include #include #include #include #include #include #include typedef std::map< std::thread::id, unsigned int > threads_count_map_t; class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_t( so_5::environment_t & env ) : base_type_t( env ) {} ~test_agent_t() override {} void so_evt_start() override; static std::size_t agents_cout() { return 10; } static bool ok() { threads_count_map_t::const_iterator it = m_threads.begin(); for(; it != m_threads.end(); ++it ) if( agents_cout() != it->second ) { std::cerr << it->first << " => " << it->second << std::endl; return false; } return true; } private: static std::mutex m_lock; static threads_count_map_t m_threads; static bool m_test_ok; }; std::mutex test_agent_t::m_lock; std::map< std::thread::id, unsigned int > test_agent_t::m_threads; void test_agent_t::so_evt_start() { auto tid = std::this_thread::get_id(); std::lock_guard< std::mutex > lock( m_lock ); if( m_threads.end() == m_threads.find( tid ) ) m_threads[ tid ] = 1; else ++m_threads[ tid ]; } class test_agent_finisher_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_finisher_t( so_5::environment_t & env ) : base_type_t( env ) {} ~test_agent_finisher_t() override {} void so_evt_start() override { std::this_thread::sleep_for( std::chrono::milliseconds( 200 ) ); so_environment().stop(); } }; void push_group( so_5::coop_t & coop, const std::string & group_name, so_5::environment_t & env ) { auto disp = so_5::disp::active_group::make_dispatcher( env ); for( std::size_t i = 0; i < test_agent_t::agents_cout(); ++i ) { coop.make_agent_with_binder< test_agent_t >( disp.binder( group_name ) ); } } void init( so_5::environment_t & env ) { auto coop = env.make_coop(); push_group( *coop, "grp_1", env ); push_group( *coop, "grp_2", env ); push_group( *coop, "grp_3", env ); push_group( *coop, "grp_4", env ); coop->make_agent< test_agent_finisher_t >(); env.register_coop( std::move( coop ) ); } int main() { try { so_5::launch( &init ); if( !test_agent_t::ok() ) throw std::runtime_error( "!test_agent_t::ok()" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/binder/correct_unbind_after_throw_on_bind/000077500000000000000000000000001523065210100277505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/binder/correct_unbind_after_throw_on_bind/CMakeLists.txt000066400000000000000000000001721523065210100325100ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.binder.correct_unbind_after_throw_on_bind) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/binder/correct_unbind_after_throw_on_bind/main.cpp000066400000000000000000000077561523065210100314170ustar00rootroot00000000000000/* * A test of handling an exception during binding to dispatcher. */ #include #include #include #include #include #include #include #include so_5::atomic_counter_t g_agents_count; struct some_message : public so_5::signal_t {}; class a_ordinary_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: a_ordinary_t( so_5::environment_t & env ) : base_type_t( env ) { ++g_agents_count; } ~a_ordinary_t() override { --g_agents_count; } void so_define_agent() override { so_subscribe( so_direct_mbox() ) .in( so_default_state() ) .event( &a_ordinary_t::some_handler ); so_5::send< some_message >( *this ); } void so_evt_start() override; void some_handler( mhood_t< some_message > ); }; void a_ordinary_t::so_evt_start() { // This method should not be called. std::cerr << "error: a_ordinary_t::so_evt_start called."; std::abort(); } void a_ordinary_t::some_handler( mhood_t< some_message > ) { // This method should not be called. std::cerr << "error: a_ordinary_t::some_handler called."; std::abort(); } class dispatcher_t { public : dispatcher_t() = default; ~dispatcher_t() { if( !m_agents.empty() ) { std::cerr << "error: there must not be any agents in " "dispatcher_t destructor; agents.size()=" << m_agents.size(); std::abort(); } if( m_bind_calls != m_unbind_calls ) { std::cerr << "error: bind and unbind calls mismatch, bind: " << m_bind_calls << ", unbind: " << m_unbind_calls << std::endl; std::abort(); } } void bind_agent( so_5::agent_t & agent ) { m_agents.insert( &agent ); ++m_bind_calls; } void unbind_agent( so_5::agent_t & agent ) { if( m_agents.empty() ) { std::cerr << "error: m_agents must not be empty in unbind_agent"; std::abort(); } auto it = m_agents.find( &agent ); if( it == m_agents.end() ) { std::cerr << "error: unknown agent in unbind_agent: " << "agent: " << &agent; std::abort(); } m_agents.erase( it ); m_unbind_calls++; } private : std::set< so_5::agent_t * > m_agents; unsigned int m_bind_calls{}; unsigned int m_unbind_calls{}; }; class throwing_disp_binder_t : public so_5::disp_binder_t { public: throwing_disp_binder_t( dispatcher_t & disp ) : m_disp{ disp } {} void preallocate_resources( so_5::agent_t & agent ) override { if( m_agents_bound < 3 ) { m_agents_bound++; m_disp.bind_agent( agent ); return; } throw std::runtime_error( "test exception from disp_binder" ); } void undo_preallocation( so_5::agent_t & agent ) noexcept override { m_disp.unbind_agent( agent ); } void bind( so_5::agent_t & /*agent*/ ) noexcept override {} void unbind( so_5::agent_t & agent ) noexcept override { m_disp.unbind_agent( agent ); } private : dispatcher_t & m_disp; unsigned int m_agents_bound{}; }; void reg_coop( so_5::environment_t & env, dispatcher_t & disp ) { auto coop = env.make_coop( std::make_shared< throwing_disp_binder_t >( std::ref(disp) ) ); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); coop->make_agent< a_ordinary_t >(); try { env.register_coop( std::move( coop ) ); } catch( const std::exception & x ) { std::cout << "correct_unbind_after_throw_on_bind, expected exception: " << x.what() << std::endl; } } void init( so_5::environment_t & env ) { dispatcher_t disp; reg_coop( env, disp ); env.stop(); } int main() { try { so_5::launch( &init ); if( 0 != g_agents_count ) { std::cerr << "g_agents_count: " << g_agents_count << "\n"; throw std::runtime_error( "g_agents_count != 0" ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/custom_new_operator.hpp000066400000000000000000000032441523065210100242230ustar00rootroot00000000000000/* * Implementation of a custom new/delete operators for testing purposes. */ #pragma once #include #include #include namespace so_5::test::disp::custom_new { std::atomic< bool > g_should_throw_on_allocation{ false }; [[nodiscard]] bool should_throw() noexcept { return g_should_throw_on_allocation.load( std::memory_order_acquire ); } void turn_should_throw_on() noexcept { g_should_throw_on_allocation = true; } } /* namespace so_5::test::disp::custom_new */ // // NOTE: the following code was borrowed from cppreference.com // https://en.cppreference.com/w/cpp/memory/new/operator_new // // no inline, required by [replacement.functions]/3 void* operator new(std::size_t sz) { if( so_5::test::disp::custom_new::should_throw() ) throw std::bad_alloc{}; if (sz == 0) ++sz; // avoid std::malloc(0) which may return nullptr on success if (void *ptr = std::malloc(sz)) return ptr; throw std::bad_alloc{}; // required by [new.delete.single]/3 } // no inline, required by [replacement.functions]/3 void* operator new[](std::size_t sz) { if( so_5::test::disp::custom_new::should_throw() ) throw std::bad_alloc{}; if (sz == 0) ++sz; // avoid std::malloc(0) which may return nullptr on success if (void *ptr = std::malloc(sz)) return ptr; throw std::bad_alloc{}; // required by [new.delete.single]/3 } void operator delete(void* ptr) noexcept { std::free(ptr); } void operator delete(void* ptr, std::size_t /*size*/) noexcept { std::free(ptr); } void operator delete[](void* ptr) noexcept { std::free(ptr); } void operator delete[](void* ptr, std::size_t /*size*/) noexcept { std::free(ptr); } sobjectizer-5.8.6/dev/test/so_5/disp/custom_work_thread.hpp000066400000000000000000000040161523065210100240260ustar00rootroot00000000000000/* * SObjectizer-5 */ #pragma once #include #include #include #include namespace disp_tests { class custom_work_thread_t final : public so_5::disp::abstract_work_thread_t { std::atomic & m_started_counter; std::atomic & m_finished_counter; std::thread m_thread; public: custom_work_thread_t( std::atomic & started_counter, std::atomic & finished_counter ) : m_started_counter{ started_counter } , m_finished_counter{ finished_counter } {} void start( body_func_t thread_body ) override { m_thread = std::thread{ [this, tb = std::move(thread_body)]() { ++m_started_counter; tb(); ++m_finished_counter; } }; } void join() override { m_thread.join(); } }; class custom_work_thread_factory_t final : public so_5::disp::abstract_work_thread_factory_t { std::atomic m_started_count{}; std::atomic m_finished_count{}; std::atomic m_created_count{}; std::atomic m_destroyed_count{}; public: custom_work_thread_factory_t() = default; so_5::disp::abstract_work_thread_t & acquire( so_5::environment_t & /*env*/ ) override { auto * thr = new custom_work_thread_t{ m_started_count, m_finished_count }; ++m_created_count; return *thr; } void release( so_5::disp::abstract_work_thread_t & thread ) noexcept override { ++m_destroyed_count; delete &thread; } [[nodiscard]] auto started() const noexcept { return m_started_count.load( std::memory_order_acquire ); } [[nodiscard]] auto finished() const noexcept { return m_finished_count.load( std::memory_order_acquire ); } [[nodiscard]] auto created() const noexcept { return m_created_count.load( std::memory_order_acquire ); } [[nodiscard]] auto destroyed() const noexcept { return m_destroyed_count.load( std::memory_order_acquire ); } }; } /* disp_tests */ sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/000077500000000000000000000000001523065210100223515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/CMakeLists.txt000066400000000000000000000000771523065210100251150ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(custom_work_thread) sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/custom_work_thread/000077500000000000000000000000001523065210100262545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/custom_work_thread/CMakeLists.txt000066400000000000000000000001621523065210100310130ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.nef_one_thread.custom_work_thread) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/custom_work_thread/main.cpp000066400000000000000000000032541523065210100277100ustar00rootroot00000000000000/* * Check for the usage of custom work thread factory. */ #include #include #include #include "../../custom_work_thread.hpp" class a_test_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void run_test() { auto factory = std::make_shared< disp_tests::custom_work_thread_factory_t >(); so_5::launch( [&]( so_5::environment_t & env ) { for( int i = 0; i < 3; ++i ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp_params = so_5::disp::nef_one_thread::disp_params_t{}; disp_params.work_thread_factory( factory ); auto disp = so_5::disp::nef_one_thread::make_dispatcher( env, "demo_" + std::to_string(i), disp_params ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); } ); } }, []( so_5::environment_params_t & /*params*/ ) { #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); ensure_or_die( 3 == factory->started(), "unexpected number of started threads" ); ensure_or_die( 3 == factory->finished(), "unexpected number of finished threads" ); ensure_or_die( 3 == factory->created(), "unexpected number of created threads" ); ensure_or_die( 3 == factory->destroyed(), "unexpected number of destroyed threads" ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/shutdown_on_bad_alloc/000077500000000000000000000000001523065210100267005ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/shutdown_on_bad_alloc/main.cpp000066400000000000000000000017011523065210100303270ustar00rootroot00000000000000/* * Test for a normal shutdown after std::bad_alloc. */ #include #include #include "../../custom_new_operator.hpp" #include "../../shutdown_on_bad_alloc_common_stuff.cpp" using namespace test; int main() { try { run_with_time_limit( []() { using namespace so_5::disp::nef_one_thread; so_5::launch( []( so_5::environment_t & env ) { auto binder = make_dispatcher( env ).binder(); env.register_agent_as_coop( env.make_agent< a_test_t >( binder ), binder ); }, []( so_5::environment_params_t & params ) { params.exception_reaction( so_5::exception_reaction_t::shutdown_sobjectizer_on_exception ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/shutdown_on_bad_alloc_as_default_disp/000077500000000000000000000000001523065210100321065ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/shutdown_on_bad_alloc_as_default_disp/main.cpp000066400000000000000000000017121523065210100335370ustar00rootroot00000000000000/* * Test for a normal shutdown after std::bad_alloc. */ #include #include #include "../../custom_new_operator.hpp" #include "../../shutdown_on_bad_alloc_common_stuff.cpp" using namespace test; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( env.so_make_default_disp_binder() ) ); }, []( so_5::environment_params_t & params ) { params.default_disp_params( so_5::disp::nef_one_thread::disp_params_t{} ); params.exception_reaction( so_5::exception_reaction_t::shutdown_sobjectizer_on_exception ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/simple/000077500000000000000000000000001523065210100236425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/simple/CMakeLists.txt000066400000000000000000000001461523065210100264030ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.nef_one_thread.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/nef_one_thread/simple/main.cpp000066400000000000000000000017571523065210100253040ustar00rootroot00000000000000/* * A simple test for nef_one_thread dispatcher. */ #include #include struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_hello ); } void so_evt_start() override { so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { so_environment().stop(); } }; int main() { try { run_with_time_limit( []() { using namespace so_5::disp::nef_one_thread; so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >(), make_dispatcher( env ).binder() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/000077500000000000000000000000001523065210100225415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/CMakeLists.txt000066400000000000000000000002101523065210100252720ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(unique_thread_id) add_subdirectory(max_demands_at_once) add_subdirectory(custom_work_thread) sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/custom_work_thread/000077500000000000000000000000001523065210100264445ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/custom_work_thread/CMakeLists.txt000066400000000000000000000001631523065210100312040ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.nef_thread_pool.custom_work_thread) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/custom_work_thread/main.cpp000066400000000000000000000034731523065210100301030ustar00rootroot00000000000000/* * Check for the usage of custom work thread factory. */ #include #include #include #include "../../custom_work_thread.hpp" namespace test { class a_test_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void run_test() { auto factory = std::make_shared< disp_tests::custom_work_thread_factory_t >(); so_5::launch( [&]( so_5::environment_t & env ) { auto disp_params = so_5::disp::nef_thread_pool::disp_params_t{}; disp_params.thread_count( 3u ); disp_params.work_thread_factory( factory ); auto disp = so_5::disp::nef_thread_pool::make_dispatcher( env, "demo", disp_params ); so_5::disp::nef_thread_pool::bind_params_t bind_params; for( int i = 0; i < 3; ++i ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent_with_binder< a_test_t >( disp.binder( bind_params ) ); } ); } }, []( so_5::environment_params_t & /*params*/ ) { #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); ensure_or_die( 3 == factory->started(), "unexpected number of started threads" ); ensure_or_die( 3 == factory->finished(), "unexpected number of finished threads" ); ensure_or_die( 3 == factory->created(), "unexpected number of created threads" ); ensure_or_die( 3 == factory->destroyed(), "unexpected number of destroyed threads" ); } } /* namespace test */ using namespace test; int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/for_each_lock_factory.hpp000066400000000000000000000015061523065210100275610ustar00rootroot00000000000000#pragma once #include #include template< typename L > void run_with_lock_factory( const char * factory_name, so_5::disp::thread_pool::queue_traits::lock_factory_t factory, L && action ) { std::cout << "=== " << factory_name << " ===" << std::endl; action( factory ); std::cout << "=======" << std::endl; } template< typename L > void for_each_lock_factory( L && action ) { using namespace so_5::disp::thread_pool::queue_traits; run_with_lock_factory( "combined_lock()", combined_lock_factory(), std::forward(action) ); run_with_lock_factory( "combined_lock(250us)", combined_lock_factory( std::chrono::microseconds(250) ), std::forward(action) ); run_with_lock_factory( "simple_lock", simple_lock_factory(), std::forward(action) ); } sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/max_demands_at_once/000077500000000000000000000000001523065210100265115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/max_demands_at_once/CMakeLists.txt000066400000000000000000000001641523065210100312520ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.nef_thread_pool.max_demands_at_once) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/max_demands_at_once/main.cpp000066400000000000000000000113141523065210100301410ustar00rootroot00000000000000/* * A test for nef_thread_pool dispatcher that checks max_demands_at_once * parameter. */ #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace test { struct msg_notice final : public so_5::message_t { std::string m_info; msg_notice( std::string info ) : m_info{ std::move(info) } {} }; struct msg_completed final : public so_5::signal_t {}; class a_supervisor_t final : public so_5::agent_t { public: a_supervisor_t( context_t ctx, std::size_t agents_count ) : so_5::agent_t{ std::move(ctx) } , m_agents_count{ agents_count } {} void so_define_agent() override { so_subscribe_self() .event( &a_supervisor_t::evt_info ) .event( &a_supervisor_t::evt_completed ) ; } private: const std::size_t m_agents_count; std::string m_log; std::size_t m_msg_completed_received{}; void evt_info( mhood_t cmd ) { m_log += cmd->m_info; m_log += ";"; } void evt_completed( mhood_t ) { ++m_msg_completed_received; if( m_msg_completed_received == m_agents_count ) { const std::string expected_log = "a-1;a-2;a-3;b-1;b-2;b-3;c-1;c-2;c-3;" "a-4;a-5;b-4;b-5;c-4;c-5;"; ensure_or_die( expected_log == m_log, "unexpected log: '" + m_log + "'; expected log: '" + expected_log + "'" ); so_deregister_agent_coop_normally(); } } }; class a_test_t : public so_5::agent_t { struct msg_1 final : public so_5::signal_t {}; struct msg_2 final : public so_5::signal_t {}; struct msg_3 final : public so_5::signal_t {}; struct msg_4 final : public so_5::signal_t {}; struct msg_5 final : public so_5::signal_t {}; const std::string m_name; const so_5::mbox_t m_target_mbox; public: a_test_t( context_t ctx, so_5::priority_t priority, std::string name, so_5::mbox_t target_mbox ) : so_5::agent_t( ctx + priority ) , m_name{ std::move(name) } , m_target_mbox{ std::move(target_mbox) } {} void so_define_agent() override { so_subscribe_self() .event( [this](mhood_t) { handle_evt( "1" ); } ) .event( [this](mhood_t) { handle_evt( "2" ); } ) .event( [this](mhood_t) { handle_evt( "3" ); } ) .event( [this](mhood_t) { handle_evt( "4" ); } ) .event( [this](mhood_t) { handle_evt( "5" ); so_5::send< msg_completed >( m_target_mbox ); } ) ; } void so_evt_start() override { so_5::send< msg_1 >( *this ); so_5::send< msg_2 >( *this ); so_5::send< msg_3 >( *this ); so_5::send< msg_4 >( *this ); so_5::send< msg_5 >( *this ); } private: void handle_evt( const std::string & msg_name ) { so_5::send< msg_notice >( m_target_mbox, m_name + "-" + msg_name ); } }; void do_test() { using namespace so_5::disp::nef_thread_pool; for_each_lock_factory( []( queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&factory]( so_5::coop_t & coop ) { auto disp = make_dispatcher( coop.environment(), std::string_view{}, disp_params_t{} .thread_count( 1u ) .set_queue_params( queue_traits::queue_params_t{} .lock_factory( factory ) ) ); // Will work on the default dispatcher. auto * supervisor = coop.make_agent< a_supervisor_t >( 3u ); bind_params_t bind_params; bind_params.max_demands_at_once( 4u ); // Use the fact that agents are sorted inside // the coop with respect to the priority. // If all agents have the same priority then // agent 'c' can receive evt_start before // agent 'a', and agent 'b' can receive evt_start // before agent 'c'. coop.make_agent_with_binder< a_test_t >( disp.binder( bind_params ), so_5::prio::p3, "a", supervisor->so_direct_mbox() ); coop.make_agent_with_binder< a_test_t >( disp.binder( bind_params ), so_5::prio::p2, "b", supervisor->so_direct_mbox() ); coop.make_agent_with_binder< a_test_t >( disp.binder( bind_params ), so_5::prio::p1, "c", supervisor->so_direct_mbox() ); } ); } ); }, 20 ); } ); } } /* namespace test */ using namespace test; int main() { try { do_test(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/shutdown_on_bad_alloc/000077500000000000000000000000001523065210100270705ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/shutdown_on_bad_alloc/main.cpp000066400000000000000000000017021523065210100305200ustar00rootroot00000000000000/* * Test for a normal shutdown after std::bad_alloc. */ #include #include #include "../../custom_new_operator.hpp" #include "../../shutdown_on_bad_alloc_common_stuff.cpp" using namespace test; int main() { try { run_with_time_limit( []() { using namespace so_5::disp::nef_thread_pool; so_5::launch( []( so_5::environment_t & env ) { auto binder = make_dispatcher( env ).binder(); env.register_agent_as_coop( env.make_agent< a_test_t >( binder ), binder ); }, []( so_5::environment_params_t & params ) { params.exception_reaction( so_5::exception_reaction_t::shutdown_sobjectizer_on_exception ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/simple/000077500000000000000000000000001523065210100240325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/simple/CMakeLists.txt000066400000000000000000000001471523065210100265740ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.nef_thread_pool.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/simple/main.cpp000066400000000000000000000027321523065210100254660ustar00rootroot00000000000000/* * A simple test for nef_thread_pool dispatcher. */ #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) {} void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_hello ); } void so_evt_start() override { so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { so_environment().stop(); } }; void do_test() { using namespace so_5::disp::nef_thread_pool; for_each_lock_factory( []( queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { so_5::launch( [&]( so_5::environment_t & env ) { auto disp = make_dispatcher( env, std::string_view{}, disp_params_t{} .thread_count(4) .set_queue_params( queue_traits::queue_params_t{} .lock_factory( factory ) ) ); env.register_agent_as_coop( env.make_agent< a_test_t >(), disp.binder() ); } ); }, 20 ); } ); } int main() { try { do_test(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/unique_thread_id/000077500000000000000000000000001523065210100260525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/unique_thread_id/CMakeLists.txt000066400000000000000000000001611523065210100306100ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.nef_thread_pool.unique_thread_id) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/nef_thread_pool/unique_thread_id/main.cpp000066400000000000000000000064401523065210100275060ustar00rootroot00000000000000/* * A test for nef_thread_pool dispatcher that checks that all threads * in pool are used. */ #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace test { struct msg_thread_info final : public so_5::message_t { so_5::current_thread_id_t m_thread_id; msg_thread_info( so_5::current_thread_id_t thread_id ) : m_thread_id{ thread_id } {} }; struct msg_completed final : public so_5::signal_t {}; class a_supervisor_t final : public so_5::agent_t { public: a_supervisor_t( context_t ctx, std::size_t agents_count ) : so_5::agent_t{ std::move(ctx) } , m_agents_count{ agents_count } {} void so_define_agent() override { so_subscribe_self() .event( &a_supervisor_t::evt_thread_info ) .event( &a_supervisor_t::evt_completed ) ; } private: const std::size_t m_agents_count; std::set< so_5::current_thread_id_t > m_threads; std::size_t m_msg_completed_received{}; void evt_thread_info( mhood_t cmd ) { m_threads.insert( cmd->m_thread_id ); } void evt_completed( mhood_t ) { ++m_msg_completed_received; if( m_msg_completed_received == m_agents_count ) { ensure_or_die( m_agents_count == m_threads.size(), "unexpected number of unique thread IDs: " + std::to_string( m_threads.size() ) + "; expected value: " + std::to_string( m_agents_count ) ); so_deregister_agent_coop_normally(); } } }; class a_test_t : public so_5::agent_t { const so_5::mbox_t m_target_mbox; public: a_test_t( context_t ctx, so_5::mbox_t target_mbox ) : so_5::agent_t( std::move(ctx) ) , m_target_mbox{ std::move(target_mbox) } {} void so_evt_start() override { so_5::send< msg_thread_info >( m_target_mbox, so_5::query_current_thread_id() ); std::this_thread::sleep_for( std::chrono::milliseconds{ 250 } ); so_5::send< msg_completed >( m_target_mbox ); } }; void do_test() { using namespace so_5::disp::nef_thread_pool; for_each_lock_factory( []( queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&factory]( so_5::coop_t & coop ) { constexpr std::size_t agents_count = 4u; auto disp = make_dispatcher( coop.environment(), std::string_view{}, disp_params_t{} .thread_count( agents_count ) .set_queue_params( queue_traits::queue_params_t{} .lock_factory( factory ) ) ); // Will work on the default dispatcher. auto * supervisor = coop.make_agent< a_supervisor_t >( agents_count ); for( std::size_t i = 0; i < agents_count; ++i ) coop.make_agent_with_binder< a_test_t >( disp.binder( bind_params_t{} ), supervisor->so_direct_mbox() ); } ); } ); }, 20 ); } ); } } /* namespace test */ using namespace test; int main() { try { do_test(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/one_thread/000077500000000000000000000000001523065210100215215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/one_thread/CMakeLists.txt000066400000000000000000000001151523065210100242560ustar00rootroot00000000000000add_subdirectory(custom_work_thread) add_subdirectory(custom_work_thread_2) sobjectizer-5.8.6/dev/test/so_5/disp/one_thread/custom_work_thread/000077500000000000000000000000001523065210100254245ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/one_thread/custom_work_thread/CMakeLists.txt000066400000000000000000000001561523065210100301660ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.one_thread.custom_work_thread) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/one_thread/custom_work_thread/main.cpp000066400000000000000000000032441523065210100270570ustar00rootroot00000000000000/* * Check for the usage of custom work thread factory. */ #include #include #include #include "../../custom_work_thread.hpp" class a_test_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void run_test() { auto factory = std::make_shared< disp_tests::custom_work_thread_factory_t >(); so_5::launch( [&]( so_5::environment_t & env ) { for( int i = 0; i < 3; ++i ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp_params = so_5::disp::one_thread::disp_params_t{}; disp_params.work_thread_factory( factory ); auto disp = so_5::disp::one_thread::make_dispatcher( env, "demo_" + std::to_string(i), disp_params ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); } ); } }, []( so_5::environment_params_t & /*params*/ ) { #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); ensure_or_die( 3 == factory->started(), "unexpected number of started threads" ); ensure_or_die( 3 == factory->finished(), "unexpected number of finished threads" ); ensure_or_die( 3 == factory->created(), "unexpected number of created threads" ); ensure_or_die( 3 == factory->destroyed(), "unexpected number of destroyed threads" ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/one_thread/custom_work_thread_2/000077500000000000000000000000001523065210100256455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/one_thread/custom_work_thread_2/CMakeLists.txt000066400000000000000000000001601523065210100304020ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.one_thread.custom_work_thread_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/one_thread/custom_work_thread_2/main.cpp000066400000000000000000000053721523065210100273040ustar00rootroot00000000000000/* * Check for the usage of custom work thread factory. */ #include #include #include #include "../../custom_work_thread.hpp" class a_test_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void run_test() { auto global_factory = std::make_shared< disp_tests::custom_work_thread_factory_t >(); auto separate_factory = std::make_shared< disp_tests::custom_work_thread_factory_t >(); so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp_params = so_5::disp::one_thread::disp_params_t{}; disp_params.work_thread_factory( global_factory ); auto disp = so_5::disp::one_thread::make_dispatcher( env, "demo_global_factory", disp_params ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); } ); env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp_params = so_5::disp::one_thread::disp_params_t{}; disp_params.work_thread_factory( separate_factory ); auto disp = so_5::disp::one_thread::make_dispatcher( env, "demo_separate_factory", disp_params ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); } ); }, [global_factory]( so_5::environment_params_t & params ) { params.work_thread_factory( global_factory ); #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); // 2 because of default dispacther and demo_global_factory dispatcher. ensure_or_die( 2 == global_factory->started(), "unexpected number of started threads (global_factory)" ); ensure_or_die( 2 == global_factory->finished(), "unexpected number of finished threads (global_factory)" ); ensure_or_die( 2 == global_factory->created(), "unexpected number of created threads (global_factory)" ); ensure_or_die( 2 == global_factory->destroyed(), "unexpected number of destroyed threads (global_factory)" ); ensure_or_die( 1 == separate_factory->started(), "unexpected number of started threads (separate_factory)" ); ensure_or_die( 1 == separate_factory->finished(), "unexpected number of finished threads (separate_factory)" ); ensure_or_die( 1 == separate_factory->created(), "unexpected number of created threads (separate_factory)" ); ensure_or_die( 1 == separate_factory->destroyed(), "unexpected number of destroyed threads (separate_factory)" ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/000077500000000000000000000000001523065210100236115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/CMakeLists.txt000066400000000000000000000001411523065210100263450ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(contexts) add_subdirectory(dereg_when_queue_not_empty) sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/contexts/000077500000000000000000000000001523065210100254605ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/contexts/CMakeLists.txt000066400000000000000000000001561523065210100302220ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_dt_one_per_prio.contexts) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/contexts/main.cpp000066400000000000000000000047501523065210100271160ustar00rootroot00000000000000/* * A simple test for prio_dedicated_threads::one_per_prio dispatcher. * Contexts for all priorities must be different. */ #include #include #include #include struct msg_context_info : public so_5::message_t { so_5::current_thread_id_t m_thread_id; msg_context_info( so_5::current_thread_id_t thread_id ) : m_thread_id( thread_id ) {} }; class a_supervisor_t : public so_5::agent_t { public: a_supervisor_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_subscribe_self().event( &a_supervisor_t::evt_context_info ); } private : std::set< so_5::current_thread_id_t > m_contexts; void evt_context_info( const msg_context_info & evt ) { auto r = m_contexts.insert( evt.m_thread_id ); if( !r.second ) { std::ostringstream ss; ss << "thread_id is not unique! id=" << evt.m_thread_id << ", already registered ids: "; for( const auto & s : m_contexts ) ss << s << ","; throw std::runtime_error( ss.str() ); } else if( so_5::prio::total_priorities_count == m_contexts.size() ) { std::cout << "contexts: "; for( const auto & s : m_contexts ) std::cout << s << " "; std::cout << std::endl; so_environment().stop(); } } }; class a_child_t final : public so_5::agent_t { const so_5::mbox_t m_supervisor_mbox; public: a_child_t( context_t ctx, so_5::priority_t priority, so_5::mbox_t supervisor_mbox ) : so_5::agent_t{ ctx + priority } , m_supervisor_mbox{ std::move(supervisor_mbox) } {} void so_evt_start() override { so_5::send< msg_context_info >( m_supervisor_mbox, so_5::query_current_thread_id() ); } }; void init( so_5::environment_t & env ) { so_5::mbox_t supervisor_mbox = env.introduce_coop( [&]( so_5::coop_t & coop ) { return coop.make_agent< a_supervisor_t >()->so_direct_mbox(); } ); using namespace so_5::disp::prio_dedicated_threads::one_per_prio; env.introduce_coop( make_dispatcher( env ).binder(), [&]( so_5::coop_t & coop ) { so_5::prio::for_each_priority( [&]( so_5::priority_t p ) { coop.make_agent< a_child_t >( p, supervisor_mbox ); } ); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( init ); }, 20, "prio_dedicated_threads::one_per_prio dispatcher test for " "context uniquness" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/dereg_when_queue_not_empty/000077500000000000000000000000001523065210100312225ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/dereg_when_queue_not_empty/CMakeLists.txt000066400000000000000000000002001523065210100337520ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_dt_one_per_prio.dereg_when_queue_not_empty) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/dereg_when_queue_not_empty/main.cpp000066400000000000000000000026401523065210100326540ustar00rootroot00000000000000/* * A test of deregistration of cooperation when demand queue for * dispatcher is not empty. */ #include #include struct send_next : public so_5::signal_t {}; struct stop : public so_5::signal_t {}; void fill_coop( so_5::coop_t & coop ) { using namespace so_5; class a_test_t final : public agent_t { public : a_test_t( context_t ctx ) : agent_t{ ctx + agent_t::limit_then_drop< send_next >( 100 ) + agent_t::limit_then_drop< stop >( 1 ) + prio::p0 } { so_subscribe_self() .event( [this](mhood_t) { send< send_next >( *this ); send< send_next >( *this ); } ) .event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { send< send_next >( *this ); send_delayed< stop >( *this, std::chrono::milliseconds( 350 ) ); } }; coop.make_agent< a_test_t >(); } int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { using namespace so_5::disp::prio_dedicated_threads::one_per_prio; env.introduce_coop( make_dispatcher( env ).binder(), fill_coop ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/simple/000077500000000000000000000000001523065210100251025ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/simple/CMakeLists.txt000066400000000000000000000001541523065210100276420ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_dt_one_per_prio.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_dt_one_per_prio/simple/main.cpp000066400000000000000000000023151523065210100265330ustar00rootroot00000000000000/* * A simple test for prio_dedicated_threads::one_per_prio dispatcher. */ #include #include struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( context_t ctx ) : so_5::agent_t( ctx + so_5::prio::p7 ) {} void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_hello ); } void so_evt_start() override { so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { so_environment().stop(); } }; int main() { try { run_with_time_limit( []() { static_assert( 8 == so_5::prio::total_priorities_count, "total_priorities_count must be 8" ); using namespace so_5::disp::prio_dedicated_threads::one_per_prio; so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >(), make_dispatcher( env ).binder() ); } ); }, 20, "simple prio_dedicated_threads::one_per_prio dispatcher test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/000077500000000000000000000000001523065210100250455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/CMakeLists.txt000066400000000000000000000002031523065210100276000ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(simple_seq1) add_subdirectory(simple_quote) add_subdirectory(dereg_when_queue_not_empty) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/dereg_when_queue_not_empty/000077500000000000000000000000001523065210100324565ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002061523065210100351350ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/dereg_when_queue_not_emptyset(UNITTEST _unit.test.disp.prio_ot_quoted_round_robin.dereg_when_queue_not_empty) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/dereg_when_queue_not_empty/main.cpp000066400000000000000000000026601523065210100341120ustar00rootroot00000000000000/* * A test of deregistration of cooperation when demand queue for * dispatcher is not empty. */ #include #include struct send_next : public so_5::signal_t {}; struct stop : public so_5::signal_t {}; void fill_coop( so_5::coop_t & coop ) { using namespace so_5; class a_test_t final : public agent_t { public : a_test_t( context_t ctx ) : agent_t{ ctx + agent_t::limit_then_drop< send_next >( 100 ) + agent_t::limit_then_drop< stop >( 1 ) + prio::p0 } { so_subscribe_self() .event( [this](mhood_t) { send< send_next >( *this ); send< send_next >( *this ); } ) .event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { send< send_next >( *this ); send_delayed< stop >( *this, std::chrono::milliseconds( 350 ) ); } }; coop.make_agent< a_test_t >(); } int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { using namespace so_5::disp::prio_one_thread::quoted_round_robin; env.introduce_coop( make_dispatcher( env, quotes_t{ 100 } ).binder(), fill_coop ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/simple/000077500000000000000000000000001523065210100263365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/simple/CMakeLists.txt000066400000000000000000000001621523065210100310750ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_ot_quoted_round_robin.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/simple/main.cpp000066400000000000000000000023231523065210100277660ustar00rootroot00000000000000/* * A simple test for prio_one_thread::quoted_round_robin dispatcher. */ #include #include struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( context_t ctx ) : so_5::agent_t( ctx + so_5::prio::p7 ) {} void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_hello ); } void so_evt_start() override { so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { so_environment().stop(); } }; int main() { try { run_with_time_limit( []() { using namespace so_5::disp::prio_one_thread::quoted_round_robin; static_assert( 8 == so_5::prio::total_priorities_count, "total_priorities_count must be 8" ); so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >(), make_dispatcher( env, "prio_dispatcher", quotes_t{ 10 } ) .binder() ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/simple_quote/000077500000000000000000000000001523065210100275535ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/simple_quote/CMakeLists.txt000066400000000000000000000001701523065210100323110ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_ot_quoted_round_robin.simple_quote) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/simple_quote/main.cpp000066400000000000000000000120411523065210100312010ustar00rootroot00000000000000/* * A test of simple quoted processing of sequence of messages for * prio_one_thread::quoted_round_robin dispatcher. */ #include #include struct msg_receiver_started : public so_5::signal_t {}; struct msg_send_messages : public so_5::signal_t {}; struct msg_request : public so_5::signal_t {}; struct msg_reply : public so_5::message_t { so_5::priority_t m_priority; msg_reply( so_5::priority_t priority ) : m_priority( priority ) {} }; void define_receiver_agent( so_5::coop_t & coop, so_5::disp::prio_one_thread::quoted_round_robin::dispatcher_handle_t & disp, so_5::priority_t priority, const so_5::mbox_t & common_mbox ) { class actor_t final : public so_5::agent_t { const so_5::mbox_t m_common_mbox; public : actor_t( context_t ctx, so_5::priority_t priority, so_5::mbox_t common_mbox ) : so_5::agent_t{ ctx + priority } , m_common_mbox{ std::move(common_mbox) } { so_subscribe( m_common_mbox ).event( [this, priority](mhood_t) { so_5::send< msg_reply >( m_common_mbox, priority ); } ); } void so_evt_start() override { so_5::send< msg_receiver_started >( m_common_mbox ); } }; coop.make_agent_with_binder< actor_t >( disp.binder(), priority, common_mbox ); } void define_message_sender( so_5::coop_t & coop, so_5::disp::prio_one_thread::quoted_round_robin::dispatcher_handle_t & disp, const so_5::mbox_t & common_mbox ) { class actor_t final : public so_5::agent_t { public : actor_t( context_t ctx, const so_5::mbox_t & common_mbox ) : so_5::agent_t{ ctx + so_5::prio::p0 } { so_subscribe( common_mbox ).event( [common_mbox](mhood_t) { for( int i = 0; i != 20; ++i ) so_5::send< msg_request >( common_mbox ); } ); } }; coop.make_agent_with_binder< actor_t >( disp.binder(), std::cref(common_mbox) ); } void define_supervison_agent( so_5::coop_t & coop, const so_5::mbox_t & common_mbox ) { class a_supervisor_t final : public so_5::agent_t { const so_5::mbox_t m_common_mbox; const std::string m_expected_value; std::string m_accumulator; const std::size_t m_expected_receivers; std::size_t m_started_receivers = 0; const std::size_t m_expected_replies; std::size_t m_replies = 0; public: a_supervisor_t( context_t ctx, so_5::mbox_t common_mbox, std::string expected_value, std::size_t expected_receivers, std::size_t expected_replies ) : so_5::agent_t{ std::move(ctx) } , m_common_mbox{ std::move(common_mbox) } , m_expected_value{ std::move( expected_value ) } , m_expected_receivers{ expected_receivers } , m_expected_replies{ expected_replies } { so_subscribe( m_common_mbox ) .event( &a_supervisor_t::on_receiver_started ) .event( &a_supervisor_t::on_reply ); } private: void on_receiver_started( mhood_t ) { m_started_receivers += 1; if( m_started_receivers == m_expected_receivers ) so_5::send< msg_send_messages >( m_common_mbox ); } void on_reply( mhood_t cmd ) { m_replies += 1; m_accumulator += std::to_string( so_5::to_size_t( cmd->m_priority ) ); if( m_replies >= m_expected_replies ) { if( m_expected_value != m_accumulator ) throw std::runtime_error( "values mismatch: " "expected: " + m_expected_value + ", actual: " + m_accumulator ); else so_environment().stop(); } } }; coop.make_agent< a_supervisor_t >( common_mbox, "777775555333" "777775555333" "777775555333" "777775555333" "5555333" "333" "33", 3u, 3u * 20u ); } void fill_coop( so_5::coop_t & coop ) { using namespace so_5::disp::prio_one_thread::quoted_round_robin; using namespace so_5::prio; auto common_mbox = coop.environment().create_mbox(); auto rr_disp = make_dispatcher( coop.environment(), quotes_t{2} .set( p7, 5 ) .set( p5, 4 ) .set( p3, 3 ) ); define_supervison_agent( coop, common_mbox ); define_message_sender( coop, rr_disp, common_mbox ); define_receiver_agent( coop, rr_disp, p7, common_mbox ); define_receiver_agent( coop, rr_disp, p5, common_mbox ); define_receiver_agent( coop, rr_disp, p3, common_mbox ); } int main() { try { // Do several iterations to increase probability of errors detection. std::cout << "runing iterations" << std::flush; for( int i = 0; i != 100; ++i ) { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( fill_coop ); } ); }, 20, "simple sequence prio_one_thread::quoted_round_robin dispatcher test" ); std::cout << "." << std::flush; } std::cout << "done" << std::endl; } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/simple_seq1/000077500000000000000000000000001523065210100272675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/simple_seq1/CMakeLists.txt000066400000000000000000000001671523065210100320330ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_ot_quoted_round_robin.simple_seq1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_quoted_round_robin/simple_seq1/main.cpp000066400000000000000000000100141523065210100307130ustar00rootroot00000000000000/* * A test of simple sequence of messages for * prio_one_thread::quoted_round_robin dispatcher. */ #include #include struct msg_hello : public so_5::signal_t {}; void define_receiver_agent( so_5::coop_t & coop, so_5::disp::prio_one_thread::quoted_round_robin::dispatcher_handle_t & disp, so_5::priority_t priority, const so_5::mbox_t & common_mbox, std::string & sequence ) { class actor_t final : public so_5::agent_t { std::string & m_seq; public: actor_t( context_t ctx, so_5::priority_t priority, const so_5::mbox_t & common_mbox, std::string & seq ) : so_5::agent_t{ ctx + priority } , m_seq{ seq } { so_subscribe( common_mbox ).event( [this, priority](mhood_t) { m_seq += std::to_string( so_5::to_size_t( priority ) ); } ); } }; coop.make_agent_with_binder< actor_t >( disp.binder(), priority, std::cref(common_mbox), std::ref(sequence) ); } std::string & define_main_agent( so_5::coop_t & coop, so_5::disp::prio_one_thread::quoted_round_robin::dispatcher_handle_t & disp, const so_5::mbox_t & common_mbox ) { class actor_t final : public so_5::agent_t { std::string m_seq; public: actor_t( context_t ctx, const so_5::mbox_t & common_mbox ) : so_5::agent_t{ ctx + so_5::prio::p0 } { so_subscribe( common_mbox ).event( [this]( mhood_t ) { m_seq += "0"; if( "76543210" != m_seq ) throw std::runtime_error( "Unexpected value of sequence: " + m_seq ); else so_environment().stop(); } ); } std::string & sequence() noexcept { return m_seq; } }; return coop.make_agent_with_binder< actor_t >( disp.binder(), std::cref(common_mbox) )->sequence(); } void define_starter_agent( so_5::coop_t & coop, so_5::disp::prio_one_thread::quoted_round_robin::dispatcher_handle_t disp ) { using disp_t = so_5::disp::prio_one_thread::quoted_round_robin::dispatcher_handle_t; class actor_t final : public so_5::agent_t { disp_t m_disp; public : actor_t( context_t ctx, disp_t disp ) : so_5::agent_t{ ctx + so_5::prio::p0 } , m_disp{ std::move(disp) } {} void so_evt_start() override { auto common_mbox = so_environment().create_mbox(); so_environment().introduce_coop( [&]( so_5::coop_t & child ) { using namespace so_5::prio; std::string & sequence = define_main_agent( child, m_disp, common_mbox ); define_receiver_agent( child, m_disp, p1, common_mbox, sequence ); define_receiver_agent( child, m_disp, p2, common_mbox, sequence ); define_receiver_agent( child, m_disp, p3, common_mbox, sequence ); define_receiver_agent( child, m_disp, p4, common_mbox, sequence ); define_receiver_agent( child, m_disp, p5, common_mbox, sequence ); define_receiver_agent( child, m_disp, p6, common_mbox, sequence ); define_receiver_agent( child, m_disp, p7, common_mbox, sequence ); } ); so_5::send< msg_hello >( common_mbox ); } }; coop.make_agent_with_binder< actor_t >( disp.binder(), disp ); } void fill_coop( so_5::coop_t & coop ) { using namespace so_5::disp::prio_one_thread::quoted_round_robin; define_starter_agent( coop, make_dispatcher( coop.environment(), quotes_t{1} ) ); } int main() { try { // Do several iterations to increase probability of errors detection. std::cout << "runing iterations" << std::flush; for( int i = 0; i != 100; ++i ) { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( fill_coop ); } ); }, 20 ); std::cout << "." << std::flush; } std::cout << "done" << std::endl; } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/000077500000000000000000000000001523065210100245255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/CMakeLists.txt000066400000000000000000000002401523065210100272610ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(simple_seq1) add_subdirectory(simple_seq2) add_subdirectory(simple_seq3) add_subdirectory(dereg_when_queue_not_empty) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/dereg_when_queue_not_empty/000077500000000000000000000000001523065210100321365ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002041523065210100346130ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/dereg_when_queue_not_emptyset(UNITTEST _unit.test.disp.prio_ot_strictly_ordered.dereg_when_queue_not_empty) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/dereg_when_queue_not_empty/main.cpp000066400000000000000000000026351523065210100335740ustar00rootroot00000000000000/* * A test of deregistration of cooperation when demand queue for * dispatcher is not empty. */ #include #include struct send_next : public so_5::signal_t {}; struct stop : public so_5::signal_t {}; void fill_coop( so_5::coop_t & coop ) { using namespace so_5; class a_test_t final : public agent_t { public : a_test_t( context_t ctx ) : agent_t{ ctx + agent_t::limit_then_drop< send_next >( 100 ) + agent_t::limit_then_drop< stop >( 1 ) + prio::p0 } { so_subscribe_self() .event( [this](mhood_t) { send< send_next >( *this ); send< send_next >( *this ); } ) .event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { send< send_next >( *this ); send_delayed< stop >( *this, std::chrono::milliseconds( 350 ) ); } }; coop.make_agent< a_test_t >(); } int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { using namespace so_5::disp::prio_one_thread::strictly_ordered; env.introduce_coop( make_dispatcher( env ).binder(), fill_coop ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/shutdown_on_bad_alloc/000077500000000000000000000000001523065210100310545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/shutdown_on_bad_alloc/CMakeLists.txt000066400000000000000000000002351523065210100336140ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_ot_strictly_ordered.shutdown_on_bad_alloc) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/shutdown_on_bad_alloc/main.cpp000066400000000000000000000017241523065210100325100ustar00rootroot00000000000000/* * Test for a normal shutdown after std::bad_alloc. */ #include #include #include "../../custom_new_operator.hpp" #include "../../shutdown_on_bad_alloc_common_stuff.cpp" using namespace test; int main() { try { run_with_time_limit( []() { using namespace so_5::disp::prio_one_thread::strictly_ordered; so_5::launch( []( so_5::environment_t & env ) { auto binder = make_dispatcher( env ).binder(); env.register_agent_as_coop( env.make_agent< a_test_t >( binder ), binder ); }, []( so_5::environment_params_t & params ) { params.exception_reaction( so_5::exception_reaction_t::shutdown_sobjectizer_on_exception ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple/000077500000000000000000000000001523065210100260165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple/CMakeLists.txt000066400000000000000000000001601523065210100305530ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_ot_strictly_ordered.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple/main.cpp000066400000000000000000000022071523065210100274470ustar00rootroot00000000000000/* * A simple test for prio_one_thread::strictly_ordered dispatcher. */ #include #include struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( context_t ctx ) : so_5::agent_t( ctx + so_5::prio::p7 ) {} void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_hello ); } void so_evt_start() override { so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { so_environment().stop(); } }; int main() { try { run_with_time_limit( []() { static_assert( 8 == so_5::prio::total_priorities_count, "total_priorities_count must be 8" ); so_5::launch( []( so_5::environment_t & env ) { using namespace so_5::disp::prio_one_thread::strictly_ordered; env.register_agent_as_coop( env.make_agent< a_test_t >(), make_dispatcher( env ).binder() ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple_seq1/000077500000000000000000000000001523065210100267475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple_seq1/CMakeLists.txt000066400000000000000000000001651523065210100315110ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_ot_strictly_ordered.simple_seq1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple_seq1/main.cpp000066400000000000000000000077561523065210100304160ustar00rootroot00000000000000/* * A test of simple sequence of messages for * prio_one_thread::strictly_ordered dispatcher. */ #include #include struct msg_hello : public so_5::signal_t {}; void define_receiver_agent( so_5::coop_t & coop, so_5::disp::prio_one_thread::strictly_ordered::dispatcher_handle_t & disp, so_5::priority_t priority, const so_5::mbox_t & common_mbox, std::string & sequence ) { class actor_t final : public so_5::agent_t { std::string & m_seq; public: actor_t( context_t ctx, so_5::priority_t priority, const so_5::mbox_t & common_mbox, std::string & seq ) : so_5::agent_t{ ctx + priority } , m_seq{ seq } { so_subscribe( common_mbox ).event( [this, priority](mhood_t) { m_seq += std::to_string( so_5::to_size_t( priority ) ); } ); } }; coop.make_agent_with_binder< actor_t >( disp.binder(), priority, std::cref(common_mbox), std::ref(sequence) ); } std::string & define_main_agent( so_5::coop_t & coop, so_5::disp::prio_one_thread::strictly_ordered::dispatcher_handle_t & disp, const so_5::mbox_t & common_mbox ) { class actor_t final : public so_5::agent_t { std::string m_seq; public: actor_t( context_t ctx, const so_5::mbox_t & common_mbox ) : so_5::agent_t{ ctx + so_5::prio::p0 } { so_subscribe( common_mbox ).event( [this]( mhood_t ) { m_seq += "0"; if( "76543210" != m_seq ) throw std::runtime_error( "Unexpected value of sequence: " + m_seq ); else so_environment().stop(); } ); } std::string & sequence() noexcept { return m_seq; } }; return coop.make_agent_with_binder< actor_t >( disp.binder(), std::cref(common_mbox) )->sequence(); } void define_starter_agent( so_5::coop_t & coop, so_5::disp::prio_one_thread::strictly_ordered::dispatcher_handle_t disp ) { using disp_t = so_5::disp::prio_one_thread::strictly_ordered::dispatcher_handle_t; class actor_t final : public so_5::agent_t { disp_t m_disp; public : actor_t( context_t ctx, disp_t disp ) : so_5::agent_t{ ctx + so_5::prio::p0 } , m_disp{ std::move(disp) } {} void so_evt_start() override { auto common_mbox = so_environment().create_mbox(); so_environment().introduce_coop( [&]( so_5::coop_t & child ) { using namespace so_5::prio; std::string & sequence = define_main_agent( child, m_disp, common_mbox ); define_receiver_agent( child, m_disp, p1, common_mbox, sequence ); define_receiver_agent( child, m_disp, p2, common_mbox, sequence ); define_receiver_agent( child, m_disp, p3, common_mbox, sequence ); define_receiver_agent( child, m_disp, p4, common_mbox, sequence ); define_receiver_agent( child, m_disp, p5, common_mbox, sequence ); define_receiver_agent( child, m_disp, p6, common_mbox, sequence ); define_receiver_agent( child, m_disp, p7, common_mbox, sequence ); } ); so_5::send< msg_hello >( common_mbox ); } }; coop.make_agent_with_binder< actor_t >( disp.binder(), disp ); } void fill_coop( so_5::coop_t & coop ) { using namespace so_5::disp::prio_one_thread::strictly_ordered; define_starter_agent( coop, make_dispatcher(coop.environment()) ); } int main() { try { // Do several iterations to increase probability of errors detection. std::cout << "runing iterations" << std::flush; for( int i = 0; i != 100; ++i ) { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( fill_coop ); } ); }, 20 ); std::cout << "." << std::flush; } std::cout << "done" << std::endl; } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple_seq2/000077500000000000000000000000001523065210100267505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple_seq2/CMakeLists.txt000066400000000000000000000001651523065210100315120ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_ot_strictly_ordered.simple_seq2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple_seq2/main.cpp000066400000000000000000000062031523065210100304010ustar00rootroot00000000000000/* * A test of simple sequence of messages for prio_one_thread::strictly_ordered * dispatcher. A starting msg_hello is sent from on_start() action from the * same coop. */ #include #include struct msg_hello : public so_5::signal_t {}; void define_receiver_agent( so_5::coop_t & coop, so_5::priority_t priority, const so_5::mbox_t & common_mbox, std::string & sequence ) { class actor_t final : public so_5::agent_t { std::string & m_seq; public: actor_t( context_t ctx, so_5::priority_t priority, const so_5::mbox_t & common_mbox, std::string & seq ) : so_5::agent_t{ ctx + priority } , m_seq{ seq } { so_subscribe( common_mbox ).event( [this, priority](mhood_t) { m_seq += std::to_string( so_5::to_size_t( priority ) ); } ); } }; coop.make_agent< actor_t >( priority, std::cref(common_mbox), std::ref(sequence) ); } std::string & define_main_agent( so_5::coop_t & coop, const so_5::mbox_t & common_mbox ) { class actor_t final : public so_5::agent_t { std::string m_seq; const so_5::mbox_t m_common_mbox; public: actor_t( context_t ctx, so_5::mbox_t common_mbox ) : so_5::agent_t{ ctx + so_5::prio::p0 } , m_common_mbox{ std::move(common_mbox) } { so_subscribe( m_common_mbox ).event( [this]( mhood_t ) { m_seq += "0"; if( "76543210" != m_seq ) throw std::runtime_error( "Unexpected value of sequence: " + m_seq ); else so_environment().stop(); } ); } void so_evt_start() override { so_5::send< msg_hello >( m_common_mbox ); } std::string & sequence() noexcept { return m_seq; } }; return coop.make_agent< actor_t >( common_mbox )->sequence(); } void fill_coop( so_5::coop_t & coop ) { using namespace so_5::prio; auto common_mbox = coop.environment().create_mbox(); std::string & sequence = define_main_agent( coop, common_mbox ); define_receiver_agent( coop, p1, common_mbox, sequence ); define_receiver_agent( coop, p2, common_mbox, sequence ); define_receiver_agent( coop, p3, common_mbox, sequence ); define_receiver_agent( coop, p4, common_mbox, sequence ); define_receiver_agent( coop, p5, common_mbox, sequence ); define_receiver_agent( coop, p6, common_mbox, sequence ); define_receiver_agent( coop, p7, common_mbox, sequence ); } int main() { try { // Do several iterations to increase probability of errors detection. std::cout << "runing iterations" << std::flush; for( int i = 0; i != 100; ++i ) { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { using namespace so_5::disp::prio_one_thread::strictly_ordered; env.introduce_coop( make_dispatcher( env ).binder(), fill_coop ); } ); }, 20 ); std::cout << "." << std::flush; } std::cout << "done" << std::endl; } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple_seq3/000077500000000000000000000000001523065210100267515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple_seq3/CMakeLists.txt000066400000000000000000000001651523065210100315130ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.prio_ot_strictly_ordered.simple_seq3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/prio_ot_strictly_ordered/simple_seq3/main.cpp000066400000000000000000000062041523065210100304030ustar00rootroot00000000000000/* * A test of simple sequence of messages for prio_one_thread::strictly_ordered * dispatcher. A starting msg_hello is sent from outside of * prio_one_thread::strictly_ordered workin thread. */ #include #include struct msg_hello : public so_5::signal_t {}; void define_receiver_agent( so_5::coop_t & coop, so_5::priority_t priority, const so_5::mbox_t & common_mbox, std::string & sequence ) { class actor_t final : public so_5::agent_t { std::string & m_seq; public: actor_t( context_t ctx, so_5::priority_t priority, const so_5::mbox_t & common_mbox, std::string & seq ) : so_5::agent_t{ ctx + priority } , m_seq{ seq } { so_subscribe( common_mbox ).event( [this, priority](mhood_t) { m_seq += std::to_string( so_5::to_size_t( priority ) ); } ); } }; coop.make_agent< actor_t >( priority, std::cref(common_mbox), std::ref(sequence) ); } std::string & define_main_agent( so_5::coop_t & coop, const so_5::mbox_t & common_mbox ) { class actor_t final : public so_5::agent_t { std::string m_seq; public: actor_t( context_t ctx, const so_5::mbox_t & common_mbox ) : so_5::agent_t{ ctx + so_5::prio::p0 } { so_subscribe( common_mbox ).event( [this]( mhood_t ) { m_seq += "0"; if( "76543210" != m_seq ) throw std::runtime_error( "Unexpected value of sequence: " + m_seq ); else so_environment().stop(); } ); } std::string & sequence() noexcept { return m_seq; } }; return coop.make_agent< actor_t >( std::cref(common_mbox) )->sequence(); } void fill_coop( const so_5::mbox_t & common_mbox, so_5::coop_t & coop ) { using namespace so_5::prio; std::string & sequence = define_main_agent( coop, common_mbox ); define_receiver_agent( coop, p1, common_mbox, sequence ); define_receiver_agent( coop, p2, common_mbox, sequence ); define_receiver_agent( coop, p3, common_mbox, sequence ); define_receiver_agent( coop, p4, common_mbox, sequence ); define_receiver_agent( coop, p5, common_mbox, sequence ); define_receiver_agent( coop, p6, common_mbox, sequence ); define_receiver_agent( coop, p7, common_mbox, sequence ); } int main() { try { // Do several iterations to increase probability of errors detection. std::cout << "runing iterations" << std::flush; for( int i = 0; i != 100; ++i ) { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { using namespace so_5::disp::prio_one_thread::strictly_ordered; auto common_mbox = env.create_mbox(); env.introduce_coop( make_dispatcher( env ).binder(), [&common_mbox]( so_5::coop_t & coop ) { fill_coop( common_mbox, coop ); } ); so_5::send< msg_hello >( common_mbox ); } ); }, 20 ); std::cout << "." << std::flush; } std::cout << "done" << std::endl; } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/private_dispatchers/000077500000000000000000000000001523065210100234545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/private_dispatchers/CMakeLists.txt000066400000000000000000000000641523065210100262140ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(env_stop) sobjectizer-5.8.6/dev/test/so_5/disp/private_dispatchers/env_stop/000077500000000000000000000000001523065210100253115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/private_dispatchers/env_stop/CMakeLists.txt000066400000000000000000000001551523065210100300520ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.private_dispatchers.env_stop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/private_dispatchers/env_stop/main.cpp000066400000000000000000000054771523065210100267560ustar00rootroot00000000000000/* * A test for calling environment_t::stop() for stopping SObjectizer. */ #include #include #include #include #include #include #include #include #include struct msg_hello : public so_5::signal_t {}; template< typename Dispatcher_Handle > void make_coop( so_5::environment_t & env, Dispatcher_Handle disp ) { using handler_t = std::function< void() >; class actor_t final : public so_5::agent_t { handler_t m_on_start; public: actor_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_on_start{ [](){} } {} void on_start( handler_t lambda ) { m_on_start = std::move(lambda); } void event( handler_t lambda ) { so_subscribe_self().event( [l = std::move(lambda)]( mhood_t ) { l(); } ); } void so_evt_start() override { m_on_start(); } }; env.introduce_coop( disp.binder(), [&]( so_5::coop_t & coop ) { auto a1 = coop.make_agent(); auto a2 = coop.make_agent(); auto a3 = coop.make_agent(); auto a4 = coop.make_agent(); auto a5 = coop.make_agent(); auto a6 = coop.make_agent(); auto a7 = coop.make_agent(); auto a8 = coop.make_agent(); using namespace so_5; a1->on_start( [a2] { send< msg_hello >( *a2 ); } ); a1->event( [a2] { send< msg_hello >( *a2 ); } ); a2->event( [a3] { send< msg_hello >( *a3 ); } ); a3->event( [a4] { send< msg_hello >( *a4 ); } ); a4->event( [a5] { send< msg_hello >( *a5 ); } ); a5->event( [a6] { send< msg_hello >( *a6 ); } ); a6->event( [a7] { send< msg_hello >( *a7 ); } ); a7->event( [a8] { send< msg_hello >( *a8 ); } ); a8->event( [a1] { send< msg_hello >( *a1 ); } ); } ); } void make_stopper( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { struct msg_stop final : public so_5::signal_t {}; public: actor_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [this](mhood_t) { so_environment().stop(); } ); } void so_evt_start() override { so_5::send< msg_stop >( *this ); } }; env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< actor_t >(); } ); } void init( so_5::environment_t & env ) { auto one_thread = so_5::disp::one_thread::make_dispatcher( env ); make_coop( env, one_thread ); make_stopper( env ); } int main() { try { for( int i = 0; i != 1000; ++i ) run_with_time_limit( []() { so_5::launch( &init ); }, 20, "stopping environment via environment_t::stop()" ); std::cout << "done" << std::endl; } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/private_dispatchers/simple/000077500000000000000000000000001523065210100247455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/private_dispatchers/simple/CMakeLists.txt000066400000000000000000000001531523065210100275040ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.private_dispatchers.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/private_dispatchers/simple/main.cpp000066400000000000000000000120371523065210100264000ustar00rootroot00000000000000/* * A simple test for private dispatchers. */ #include #include #include #include #include #include #include #include #include struct msg_start : public so_5::signal_t {}; struct msg_hello : public so_5::message_t { std::string m_who; msg_hello( std::string who ) : m_who( std::move( who ) ) {} }; class a_collector_t : public so_5::agent_t { private : const so_5::mbox_t m_start_mbox; unsigned int m_remaining; public : a_collector_t( so_5::environment_t & env, const so_5::mbox_t & start_mbox, unsigned int messages_to_receive ) : so_5::agent_t( env ) , m_start_mbox( start_mbox ) , m_remaining( messages_to_receive ) {} virtual void so_define_agent() override { so_default_state().event( [this]( const msg_hello & msg ) { std::cout << "received: " << msg.m_who << std::endl; if( 0 == (--m_remaining) ) so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_start >( m_start_mbox ); } }; std::string make_hello_string( const std::string & who ) { std::ostringstream ss; ss << who << " from thread [" << so_5::query_current_thread_id() << "]"; return ss.str(); } class a_hello_sender_t : public so_5::agent_t { protected : const so_5::mbox_t m_start_mbox; a_collector_t * m_collector; const std::string m_text; public : a_hello_sender_t( context_t ctx, so_5::mbox_t start_mbox, a_collector_t * collector, std::string text ) : so_5::agent_t::agent_t{ std::move(ctx) } , m_start_mbox{ std::move(start_mbox) } , m_collector{ collector } , m_text{ std::move(text) } {} void so_define_agent() override { so_subscribe( m_start_mbox ).event( [this](mhood_t) { so_5::send< msg_hello >( *m_collector, make_hello_string( m_text ) ); } ); } }; class a_hello_sender_with_pause_t final : public a_hello_sender_t { public : using a_hello_sender_t::a_hello_sender_t; void so_define_agent() override { so_subscribe( m_start_mbox ).event( [this](mhood_t) { std::this_thread::sleep_for( std::chrono::seconds{2} ); so_5::send< msg_hello >( *m_collector, make_hello_string( m_text ) ); } ); } }; class a_hello_sender_for_atp_dispatcher_t final : public a_hello_sender_t { public : using a_hello_sender_t::a_hello_sender_t; void so_define_agent() override { so_subscribe_self().event( [this](mhood_t) { std::this_thread::sleep_for( std::chrono::seconds{1} ); so_5::send< msg_hello >( *m_collector, make_hello_string( m_text ) ); }, so_5::thread_safe ); } void so_evt_start() override { for( int i = 0; i != 10; ++i ) so_5::send< msg_start >( *this ); } }; void init( so_5::environment_t & env ) { auto one_thread = so_5::disp::one_thread::make_dispatcher( env ); auto active_obj = so_5::disp::active_obj::make_dispatcher( env ); auto active_group = so_5::disp::active_group::make_dispatcher( env ); auto thread_pool = so_5::disp::thread_pool::make_dispatcher( env, 3 ); auto adv_thread_pool = so_5::disp::adv_thread_pool::make_dispatcher( env, 10 ); auto start_mbox = env.create_mbox( "start" ); auto coop = env.make_coop(); auto collector = coop->make_agent< a_collector_t >( start_mbox, 9u + 10u ); auto make_hello_sender = [&]( auto binder, const char * text ) { coop->make_agent_with_binder< a_hello_sender_t >( std::move(binder), start_mbox, collector, text ); }; auto make_hello_sender_with_pause = [&]( auto binder, const char * text ) { coop->make_agent_with_binder< a_hello_sender_with_pause_t >( std::move(binder), start_mbox, collector, text ); }; make_hello_sender( one_thread.binder(), "one_thread" ); make_hello_sender( active_obj.binder(), "active_obj-1" ); make_hello_sender( active_obj.binder(), "active_obj-2" ); make_hello_sender( active_group.binder( "agOne"), "active_group-1" ); make_hello_sender( active_group.binder( "agTwo"), "active_group-2-1" ); make_hello_sender( active_group.binder( "agTwo"), "active_group-2-2" ); const auto tp_params = so_5::disp::thread_pool::bind_params_t{} .fifo( so_5::disp::thread_pool::fifo_t::individual ); make_hello_sender_with_pause( thread_pool.binder( tp_params ), "thread_pool-1" ); make_hello_sender_with_pause( thread_pool.binder( tp_params ), "thread_pool-2" ); make_hello_sender_with_pause( thread_pool.binder( tp_params ), "thread_pool-3" ); const auto atp_params = so_5::disp::adv_thread_pool::bind_params_t{} .fifo( so_5::disp::adv_thread_pool::fifo_t::individual ); coop->make_agent_with_binder< a_hello_sender_for_atp_dispatcher_t >( adv_thread_pool.binder( atp_params ), start_mbox, collector, "adv_thread_pool" ); env.register_coop( std::move( coop ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple private dispatchers test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/shutdown_on_bad_alloc_common_stuff.cpp000066400000000000000000000030111523065210100272160ustar00rootroot00000000000000namespace test { class a_child_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; }; class a_test_t final : public so_5::agent_t { struct msg_make_child final : public so_5::message_t { int m_number; msg_make_child( int number ) : m_number{ number } {} }; struct msg_let_crash final : public so_5::signal_t {}; so_5::disp_binder_shptr_t m_binder; public: a_test_t( context_t ctx, so_5::disp_binder_shptr_t binder ) : so_5::agent_t{ std::move(ctx) } , m_binder{ std::move(binder) } {} void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_make_child ); so_subscribe_self().event( &a_test_t::evt_let_crash ); } void so_evt_start() override { so_5::send< msg_make_child >( *this, 1 ); } private: void evt_make_child( mhood_t< msg_make_child > cmd ) { if( cmd->m_number < 100 ) { so_5::introduce_child_coop( *this, m_binder, []( so_5::coop_t & coop ) { coop.make_agent< a_child_t >(); } ); so_5::send< msg_make_child >( *this, cmd->m_number + 1 ); } else { so_5::test::disp::custom_new::turn_should_throw_on(); std::puts( "should_throw is turned on" ); so_5::send< msg_let_crash >( *this ); } } void evt_let_crash(mhood_t) { // We shouldn't received this message! std::cerr << "evt_let_crash shouldn't be called!" << std::endl; std::abort(); } }; } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/000077500000000000000000000000001523065210100217115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/CMakeLists.txt000066400000000000000000000003041523065210100244460ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(cooperation_fifo) add_subdirectory(cooperation_fifo_2) add_subdirectory(individual_fifo) add_subdirectory(threshold) add_subdirectory(custom_work_thread) sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/cooperation_fifo/000077500000000000000000000000001523065210100252365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/cooperation_fifo/CMakeLists.txt000066400000000000000000000001551523065210100277770ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.thread_pool.cooperation_fifo) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/cooperation_fifo/main.cpp000066400000000000000000000132631523065210100266730ustar00rootroot00000000000000/* * A simple test for thread_pool dispatcher. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace tp_disp = so_5::disp::thread_pool; typedef std::set< so_5::current_thread_id_t > thread_id_set_t; class thread_id_collector_t { public : void lock() { m_lock.lock(); } void unlock() { m_lock.unlock(); } void add_current_thread() { std::lock_guard< so_5::default_spinlock_t > l( m_lock ); m_set.insert( so_5::query_current_thread_id() ); } std::size_t set_size() const { return m_set.size(); } const thread_id_set_t & query_set() const { return m_set; } private : so_5::default_spinlock_t m_lock; thread_id_set_t m_set; }; typedef std::shared_ptr< thread_id_collector_t > thread_id_collector_ptr_t; typedef std::vector< thread_id_collector_ptr_t > collector_container_t; struct msg_shutdown : public so_5::signal_t {}; struct msg_hello : public so_5::signal_t {}; /* * There is a trick in working scheme for this agent. * * The first agent in cooperation will be blocked in so_evt_start() * on m_collector.add_current_thread() call because collector will * be locked before start of cooperation registration. * Collector will be unlocked after return from register_coop(). * At this moment there must be demands for so_evt_start for * all cooperation agents in the same agent_queue. * * During processing of so_evt_start() new demands (for msg_hello) * will be placed to the same agent_queue. And this queue will be * processed on the same working thread because of big value * of max_demands_at_once parameter. */ class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env, thread_id_collector_t & collector, const so_5::mbox_t & shutdowner_mbox ) : so_5::agent_t( env ) , m_collector( collector ) { so_subscribe_self().event( [shutdowner_mbox](mhood_t< msg_hello >) { so_5::send< msg_shutdown >( shutdowner_mbox ); } ); } void so_evt_start() override { m_collector.add_current_thread(); so_5::send< msg_hello >( *this ); } private : thread_id_collector_t & m_collector; }; class a_shutdowner_t : public so_5::agent_t { public : a_shutdowner_t( so_5::environment_t & env, std::size_t working_agents ) : so_5::agent_t( env ) , m_working_agents( working_agents ) {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< msg_shutdown >) { --m_working_agents; if( !m_working_agents ) so_environment().stop(); } ); } private : std::size_t m_working_agents; }; const std::size_t cooperation_count = 1024; // 1000; const std::size_t cooperation_size = 128; // 100; const std::size_t thread_count = 8; collector_container_t create_collectors() { collector_container_t collectors; collectors.reserve( cooperation_count ); for( std::size_t i = 0; i != cooperation_count; ++i ) collectors.emplace_back( std::make_shared< thread_id_collector_t >() ); return collectors; } void run_sobjectizer( tp_disp::queue_traits::lock_factory_t factory, collector_container_t & collectors ) { duration_meter_t duration( "running of test cooperations" ); so_5::launch( [&]( so_5::environment_t & env ) { so_5::mbox_t shutdowner_mbox; { auto c = env.make_coop(); auto a = c->make_agent< a_shutdowner_t >( cooperation_count * cooperation_size ); shutdowner_mbox = a->so_direct_mbox(); env.register_coop( std::move( c ) ); } auto disp = tp_disp::make_dispatcher( env, "thread_pool", tp_disp::disp_params_t{} .thread_count( thread_count ) .set_queue_params( tp_disp::queue_traits::queue_params_t{} .lock_factory( factory ) ) ); auto params = tp_disp::bind_params_t{}.max_demands_at_once( 1024 ); for( std::size_t i = 0; i != cooperation_count; ++i ) { // Lock collector for that cooperation until // register_coop finished. // It guarantees that the first cooperation agent // will be blocked in so_evt_start. And demands for // other agents will be placed into the same demands queue. std::lock_guard< thread_id_collector_t > collector_lock( *(collectors[ i ]) ); auto c = env.make_coop( disp.binder( params ) ); for( std::size_t a = 0; a != cooperation_size; ++a ) { c->make_agent< a_test_t >( *(collectors[ i ]), shutdowner_mbox ); } env.register_coop( std::move( c ) ); } } ); } void analyze_results( const collector_container_t & collectors ) { thread_id_set_t all_threads; for( auto & c : collectors ) if( 1 != c->set_size() ) { std::ostringstream ss; ss << "there is a set with size: " << c->set_size(); throw std::runtime_error( ss.str() ); } else all_threads.insert( c->query_set().begin(), c->query_set().end() ); std::cout << "all_threads size: " << all_threads.size() << std::endl; } void run_and_check( tp_disp::queue_traits::lock_factory_t factory ) { auto collectors = create_collectors(); run_sobjectizer( factory, collectors ); analyze_results( collectors ); } int main() { try { for_each_lock_factory( []( tp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { run_and_check( factory ); }, 240, "cooperation_fifo test" ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/cooperation_fifo_2/000077500000000000000000000000001523065210100254575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/cooperation_fifo_2/CMakeLists.txt000066400000000000000000000001571523065210100302220ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.thread_pool.cooperation_fifo_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/cooperation_fifo_2/main.cpp000066400000000000000000000100571523065210100271120ustar00rootroot00000000000000/* * A test for thread_pool dispatcher when a single binder with * cooperative_fifo is used for several coops. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" namespace tp_disp = so_5::disp::thread_pool; typedef std::set< so_5::current_thread_id_t > thread_id_set_t; class thread_id_collector_t { public : void add_current_thread() { std::lock_guard< so_5::default_spinlock_t > l( m_lock ); m_set.insert( so_5::query_current_thread_id() ); } std::size_t set_size() const { return m_set.size(); } const thread_id_set_t & query_set() const { return m_set; } private : so_5::default_spinlock_t m_lock; thread_id_set_t m_set; }; struct msg_shutdown : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env, thread_id_collector_t & collector, const so_5::mbox_t & shutdowner_mbox ) : so_5::agent_t( env ) , m_collector( collector ) , m_shutdowner_mbox( shutdowner_mbox ) { } void so_evt_start() override { // Block the current thread for some time. // Because of that so_evt_start for an agent from a different // coop has to be started on a separate thread. std::this_thread::sleep_for( std::chrono::milliseconds{ 250 } ); m_collector.add_current_thread(); so_5::send< msg_shutdown >( m_shutdowner_mbox ); } private : thread_id_collector_t & m_collector; const so_5::mbox_t m_shutdowner_mbox; }; class a_shutdowner_t : public so_5::agent_t { public : a_shutdowner_t( so_5::environment_t & env, std::size_t working_agents ) : so_5::agent_t( env ) , m_working_agents( working_agents ) {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< msg_shutdown >) { --m_working_agents; if( !m_working_agents ) so_environment().stop(); } ); } private : std::size_t m_working_agents; }; const std::size_t cooperation_count = 4; void run_sobjectizer( tp_disp::queue_traits::lock_factory_t factory, thread_id_collector_t & collector ) { duration_meter_t duration( "running of test cooperations" ); so_5::launch( [&]( so_5::environment_t & env ) { so_5::mbox_t shutdowner_mbox; { auto c = env.make_coop(); auto a = c->make_agent< a_shutdowner_t >( cooperation_count ); shutdowner_mbox = a->so_direct_mbox(); env.register_coop( std::move( c ) ); } auto disp = tp_disp::make_dispatcher( env, "thread_pool", tp_disp::disp_params_t{} .thread_count( cooperation_count ) .set_queue_params( tp_disp::queue_traits::queue_params_t{} .lock_factory( factory ) ) ); auto params = tp_disp::bind_params_t{} .max_demands_at_once( 1024 ) .fifo( tp_disp::fifo_t::cooperation ); auto the_same_binder = disp.binder( params ); for( std::size_t i = 0; i != cooperation_count; ++i ) { auto c = env.make_coop( the_same_binder ); c->make_agent< a_test_t >( collector, shutdowner_mbox ); env.register_coop( std::move( c ) ); } } ); } void analyze_results( const thread_id_collector_t & collector ) { if( cooperation_count != collector.set_size() ) { throw std::runtime_error{ "there is a set with size: " + std::to_string( collector.set_size() ) }; } } void run_and_check( tp_disp::queue_traits::lock_factory_t factory ) { thread_id_collector_t collector; run_sobjectizer( factory, collector ); analyze_results( collector ); } int main() { try { for_each_lock_factory( []( tp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { run_and_check( factory ); }, 240 ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/custom_work_thread/000077500000000000000000000000001523065210100256145ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/custom_work_thread/CMakeLists.txt000066400000000000000000000001571523065210100303570ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.thread_pool.custom_work_thread) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/custom_work_thread/main.cpp000066400000000000000000000031311523065210100272420ustar00rootroot00000000000000/* * Check for the usage of custom work thread factory. */ #include #include #include #include "../../custom_work_thread.hpp" class a_test_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void run_test() { auto factory = std::make_shared< disp_tests::custom_work_thread_factory_t >(); so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { auto disp = so_5::disp::thread_pool::make_dispatcher( env, "demo_custom_factory", so_5::disp::thread_pool::disp_params_t{} .thread_count( 7 ) .work_thread_factory( factory ) ); coop.make_agent_with_binder< a_test_t >( disp.binder() ); } ); }, []( so_5::environment_params_t & /*params*/ ) { #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); ensure_or_die( 7 == factory->started(), "unexpected number of started threads" ); ensure_or_die( 7 == factory->finished(), "unexpected number of finished threads" ); ensure_or_die( 7 == factory->created(), "unexpected number of created threads" ); ensure_or_die( 7 == factory->destroyed(), "unexpected number of destroyed threads" ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/for_each_lock_factory.hpp000066400000000000000000000015021523065210100267250ustar00rootroot00000000000000#pragma once #include #include template< typename L > void run_with_lock_factory( const char * factory_name, so_5::disp::thread_pool::queue_traits::lock_factory_t factory, L && action ) { std::cout << "=== " << factory_name << " ===" << std::endl; action( factory ); std::cout << "=======" << std::endl; } template< typename L > void for_each_lock_factory( L && action ) { using namespace so_5::disp::thread_pool::queue_traits; run_with_lock_factory( "combined_lock()", combined_lock_factory(), std::forward(action) ); run_with_lock_factory( "combined_lock(250us)", combined_lock_factory( std::chrono::microseconds(250) ), std::forward(action) ); run_with_lock_factory( "simple_lock", simple_lock_factory(), std::forward(action) ); } sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/individual_fifo/000077500000000000000000000000001523065210100250445ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/individual_fifo/CMakeLists.txt000066400000000000000000000001541523065210100276040ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.thread_pool.individual_fifo) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/individual_fifo/main.cpp000066400000000000000000000127151523065210100265020ustar00rootroot00000000000000/* * A for thread_pool dispatcher (individual_fifo mechanism). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" typedef std::set< so_5::current_thread_id_t > thread_id_set_t; namespace tp_disp = so_5::disp::thread_pool; class thread_id_collector_t { public : void add_current_thread() { std::lock_guard< so_5::default_spinlock_t > l( m_lock ); m_set.insert( so_5::query_current_thread_id() ); } std::size_t set_size() const { return m_set.size(); } const thread_id_set_t & query_set() const { return m_set; } private : so_5::default_spinlock_t m_lock; thread_id_set_t m_set; }; typedef std::shared_ptr< thread_id_collector_t > thread_id_collector_ptr_t; typedef std::vector< thread_id_collector_ptr_t > collector_container_t; struct msg_shutdown : public so_5::signal_t {}; struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env, thread_id_collector_t & collector, const so_5::mbox_t & shutdowner_mbox ) : so_5::agent_t( env ) , m_collector( collector ) , m_shutdowner_mbox( shutdowner_mbox ) , m_messages_sent( 0 ) { } void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_hello ); } void so_evt_start() override { m_collector.add_current_thread(); so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { m_collector.add_current_thread(); ++m_messages_sent; if( m_messages_sent >= 20 ) so_5::send< msg_shutdown >( m_shutdowner_mbox ); else so_5::send< msg_hello >( *this ); } private : thread_id_collector_t & m_collector; const so_5::mbox_t m_shutdowner_mbox; std::size_t m_messages_sent; }; class a_shutdowner_t : public so_5::agent_t { public : a_shutdowner_t( so_5::environment_t & env, std::size_t working_agents ) : so_5::agent_t( env ) , m_working_agents( working_agents ) {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< msg_shutdown >) { --m_working_agents; if( !m_working_agents ) so_environment().stop(); } ); } private : std::size_t m_working_agents; }; const std::size_t cooperation_count = 128; // 1000; const std::size_t cooperation_size = 128; // 100; const std::size_t thread_count = 8; const std::size_t total_agent_count = cooperation_count * cooperation_size; collector_container_t create_collectors() { collector_container_t collectors; collectors.reserve( total_agent_count ); for( std::size_t i = 0; i != total_agent_count; ++i ) collectors.emplace_back( std::make_shared< thread_id_collector_t >() ); return collectors; } void run_sobjectizer( tp_disp::queue_traits::lock_factory_t factory, collector_container_t & collectors ) { duration_meter_t duration( "running of test cooperations" ); so_5::launch( [&]( so_5::environment_t & env ) { so_5::mbox_t shutdowner_mbox; { auto c = env.make_coop(); auto a = c->make_agent< a_shutdowner_t >( total_agent_count ); shutdowner_mbox = a->so_direct_mbox(); env.register_coop( std::move( c ) ); } std::size_t collector_index = 0; auto disp = tp_disp::make_dispatcher( env, "thread_pool", tp_disp::disp_params_t{} .thread_count( thread_count ) .set_queue_params( tp_disp::queue_traits::queue_params_t{} .lock_factory( factory ) ) ); tp_disp::bind_params_t bind_params; bind_params.fifo( tp_disp::fifo_t::individual ); bind_params.max_demands_at_once( 2 ); for( std::size_t i = 0; i != cooperation_count; ++i ) { auto c = env.make_coop( disp.binder( bind_params ) ); for( std::size_t a = 0; a != cooperation_size; ++a, ++collector_index ) { c->make_agent< a_test_t >( *(collectors[ collector_index ]), shutdowner_mbox ); } env.register_coop( std::move( c ) ); } } ); } void analyze_results( const collector_container_t & collectors ) { thread_id_set_t all_threads; std::size_t total_threads_id = 0; std::size_t agents_with_only_one_thread = 0; for( auto & c : collectors ) { if( 1 == c->set_size() ) ++agents_with_only_one_thread; total_threads_id += c->set_size(); all_threads.insert( c->query_set().begin(), c->query_set().end() ); } if( agents_with_only_one_thread == total_agent_count ) { std::cerr << "all agents have worked only on one thread!" << std::endl; std::abort(); } double avg_thread_count = double(total_threads_id) / total_agent_count; std::cout << "all_threads size: " << all_threads.size() << std::endl; std::cout << "avg threads size: " << avg_thread_count << std::endl; } void run_and_check( tp_disp::queue_traits::lock_factory_t factory ) { auto collectors = create_collectors(); run_sobjectizer( factory, collectors ); analyze_results( collectors ); } int main() { try { for_each_lock_factory( []( tp_disp::queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { run_and_check( factory ); }, 240, "individual_fifo test" ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/simple/000077500000000000000000000000001523065210100232025ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/simple/CMakeLists.txt000066400000000000000000000001421523065210100257370ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.thread_pool.simpl) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/simple/main.cpp000066400000000000000000000027771523065210100246470ustar00rootroot00000000000000/* * A simple test for thread_pool dispatcher. */ #include #include #include #include #include #include #include #include #include #include "../for_each_lock_factory.hpp" struct msg_hello : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env ) : so_5::agent_t( env ) {} void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_hello ); } void so_evt_start() override { so_5::send< msg_hello >( *this ); } void evt_hello(mhood_t< msg_hello >) { so_environment().stop(); } }; void do_test() { using namespace so_5::disp::thread_pool; for_each_lock_factory( []( queue_traits::lock_factory_t factory ) { run_with_time_limit( [&]() { so_5::launch( [&]( so_5::environment_t & env ) { auto disp = make_dispatcher( env, std::string_view{}, disp_params_t{} .thread_count(4) .set_queue_params( queue_traits::queue_params_t{} .lock_factory( factory ) ) ); env.register_agent_as_coop( env.make_agent< a_test_t >(), disp.binder() ); } ); }, 20, "simple thread_pool dispatcher test" ); } ); } int main() { try { do_test(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/threshold/000077500000000000000000000000001523065210100237055ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/threshold/CMakeLists.txt000066400000000000000000000001461523065210100264460ustar00rootroot00000000000000set(UNITTEST _unit.test.disp.thread_pool.threshold) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/disp/thread_pool/threshold/main.cpp000066400000000000000000000071421523065210100253410ustar00rootroot00000000000000/* * Test for next_thread_wakeup_threshold param. */ #include #include #include #include #include using namespace std; using namespace so_5; using namespace so_5::disp::thread_pool; using predicate_t = function< bool(thread::id, thread::id) >; struct your_turn { thread::id m_id; }; class a_receiver_t final : public agent_t { public : a_receiver_t( context_t ctx, string case_name, predicate_t pred ) : agent_t{ ctx } , m_case_name( std::move(case_name) ) , m_pred( std::move(pred) ) { so_subscribe_self().event( &a_receiver_t::on_your_turn ); } private : const string m_case_name; const predicate_t m_pred; void on_your_turn( const your_turn & msg ) { if( !m_pred( this_thread::get_id(), msg.m_id ) ) { ostringstream ss; ss << m_case_name << ": predicate failed, self id: " << this_thread::get_id() << ", foreign id: " << msg.m_id; throw runtime_error( ss.str() ); } else so_deregister_agent_coop_normally(); } }; class a_sender_t final : public agent_t { struct pause : public signal_t {}; public : a_sender_t( context_t ctx, mbox_t receiver ) : agent_t{ ctx }, m_receiver{ receiver } { so_subscribe_self().event( [this](mhood_t< pause >) { send< your_turn >( m_receiver, this_thread::get_id() ); this_thread::sleep_for( chrono::milliseconds(500) ); } ); } virtual void so_evt_start() override { send_delayed< pause >( *this, chrono::milliseconds(500) ); } private : const mbox_t m_receiver; }; template< typename Queue_Params_Tuner > dispatcher_handle_t make_disp( environment_t & env, const std::string_view disp_name, Queue_Params_Tuner qp_tuner ) { return make_dispatcher( env, disp_name, disp_params_t{} .thread_count( 2 ) .tune_queue_params( [&qp_tuner]( queue_traits::queue_params_t & qp ) { qp_tuner( qp ); } ) ); } void do_check( environment_t & env, dispatcher_handle_t disp, string case_name, predicate_t pred ) { env.introduce_coop( disp.binder( bind_params_t{}.fifo( fifo_t::individual ) ), [case_name, pred]( coop_t & coop ) { auto receiver_mbox = coop.make_agent< a_receiver_t >( case_name, pred )->so_direct_mbox(); coop.make_agent< a_sender_t >( receiver_mbox ); } ); } void check_threshold_default( environment_t & env ) { do_check( env, make_disp( env, "disp_threshold_default", []( queue_traits::queue_params_t & ) {} ), "default", []( thread::id a, thread::id b ) { return a != b; } ); } void check_threshold_0( environment_t & env ) { do_check( env, make_disp( env, "disp_threshold_0", []( queue_traits::queue_params_t & qp ) { qp.next_thread_wakeup_threshold( 0 ); } ), "threshold_0", []( thread::id a, thread::id b ) { return a != b; } ); } void check_threshold_1( environment_t & env ) { do_check( env, make_disp( env, "disp_threshold_1", []( queue_traits::queue_params_t & qp ) { qp.next_thread_wakeup_threshold( 1 ); } ), "threshold_1", []( thread::id a, thread::id b ) { return a == b; } ); } void do_test() { run_with_time_limit( [] { so_5::launch( []( environment_t & env ) { check_threshold_default( env ); check_threshold_0( env ); check_threshold_1( env ); } ); }, 5 ); } int main() { try { do_test(); return 0; } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/000077500000000000000000000000001523065210100224025ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/CMakeLists.txt000066400000000000000000000001321523065210100251360ustar00rootroot00000000000000project(tests) add_subdirectory(simple_mtsafe_st) add_subdirectory(simple_not_mtsafe_st) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/000077500000000000000000000000001523065210100257405ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/CMakeLists.txt000066400000000000000000000012451523065210100305020ustar00rootroot00000000000000project(tests) add_subdirectory(empty_init_fn) add_subdirectory(unknown_exception_init_fn) add_subdirectory(unknown_exception_init_fn_2) add_subdirectory(unknown_exception_init_fn_3) add_subdirectory(stop_in_init_fn) add_subdirectory(simple_agent) add_subdirectory(create_default_disp_binder) add_subdirectory(direct_env_stop) add_subdirectory(thread_id) add_subdirectory(delayed_msg) add_subdirectory(timer_factories) add_subdirectory(periodic_msg) add_subdirectory(periodic_msg_from_outside) add_subdirectory(delayed_msg_from_outside) add_subdirectory(stats_on) add_subdirectory(stats_coop_count) add_subdirectory(stats_wt_activity) add_subdirectory(reg_dereg_notificators) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/create_default_disp_binder/000077500000000000000000000000001523065210100332515ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002121523065210100357250ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/create_default_disp_binderset(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.create_default_disp_binder) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000024771523065210100346340ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/create_default_disp_binder/* * A test for create_default_disp_binder. */ #include #include class dummy_agent : public so_5::agent_t { public : dummy_agent(context_t ctx) : so_5::agent_t(std::move(ctx)) {} }; class coop_shutdowner : public so_5::agent_t { struct stop final : public so_5::signal_t {}; public : coop_shutdowner(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send(*this); } }; void make_coop( so_5::environment_t & env ) { env.introduce_coop( so_5::make_default_disp_binder( env ), []( so_5::coop_t & coop ) { for(int i = 0; i < 100; ++i) coop.make_agent(); coop.make_agent(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { for(int i = 0; i < 10; ++i) make_coop( env ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/delayed_msg/000077500000000000000000000000001523065210100302155ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/delayed_msg/CMakeLists.txt000066400000000000000000000001731523065210100327560ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.delayed_msg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/delayed_msg/main.cpp000066400000000000000000000023131523065210100316440ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with one simple agent * and delayed stop message. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { struct stop {}; public: using so_5::agent_t::agent_t; void so_evt_start() override { so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); so_5::send_delayed< stop >( *this, std::chrono::milliseconds(250) ); } }; env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< actor_t >(); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); }, 5, "simple agent with delayed stop message" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/delayed_msg_from_outside/000077500000000000000000000000001523065210100327745ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002101523065210100354460ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/delayed_msg_from_outsideset(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.delayed_msg_from_outside) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000026401523065210100343470ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/delayed_msg_from_outside/* * A test for simple_mtsafe_st_env_infastructure with one simple agent * and periodic message. */ #include #include #include using namespace std; class a_test_t final : public so_5::agent_t { public : struct tick : public so_5::signal_t {}; a_test_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( [this](mhood_t< tick >) { so_deregister_agent_coop_normally(); } ); } }; int main() { try { run_with_time_limit( []() { thread outside_thread; so_5::launch( [&]( so_5::environment_t & env ) { so_5::mbox_t test_mbox = env.introduce_coop( [&]( so_5::coop_t & coop ) { return coop.make_agent< a_test_t >()->so_direct_mbox(); } ); outside_thread = thread( [test_mbox] { this_thread::sleep_for( chrono::milliseconds( 350 ) ); so_5::send_delayed< a_test_t::tick >( test_mbox, chrono::milliseconds(100) ); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); outside_thread.join(); }, 5, "simple agent with delayed message from outside" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/direct_env_stop/000077500000000000000000000000001523065210100311275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/direct_env_stop/CMakeLists.txt000066400000000000000000000001771523065210100336740ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.direct_env_stop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/direct_env_stop/main.cpp000066400000000000000000000020721523065210100325600ustar00rootroot00000000000000/* * A test for direct call to so_environment().stop(). */ #include #include #include using namespace std; class test_agent : public so_5::agent_t { struct stop final : public so_5::signal_t {}; public : test_agent( context_t ctx ) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &test_agent::on_stop ); } virtual void so_evt_start() override { so_5::send( *this ); } private : void on_stop( mhood_t ) { so_environment().stop(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent >() ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); }, 5 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/empty_init_fn/000077500000000000000000000000001523065210100306045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/empty_init_fn/CMakeLists.txt000066400000000000000000000001751523065210100333470ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.empty_init_fn) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/empty_init_fn/main.cpp000066400000000000000000000012741523065210100322400ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with empty init_fn function. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & ) {}, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); }, 5, "empty init_fn for simple_mtsafe_st_env_infrastructure" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/periodic_msg/000077500000000000000000000000001523065210100304045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/periodic_msg/CMakeLists.txt000066400000000000000000000001741523065210100331460ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.periodic_msg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/periodic_msg/main.cpp000066400000000000000000000024421523065210100320360ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with one simple agent * and periodic message. */ #include #include #include using namespace std; class a_test_t final : public so_5::agent_t { int m_ticks{ 0 }; so_5::timer_id_t m_tick_timer; struct tick : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( [this](mhood_t< tick >) { ++m_ticks; if( 3 == m_ticks ) so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { m_tick_timer = so_5::send_periodic< tick >( *this, chrono::milliseconds(250), chrono::milliseconds(300) ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); }, 5, "simple agent with periodic message" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/periodic_msg_from_outside/000077500000000000000000000000001523065210100331635ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002111523065210100356360ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/periodic_msg_from_outsideset(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.periodic_msg_from_outside) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000042411523065210100345350ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/periodic_msg_from_outside/* * A test for simple_mtsafe_st_env_infastructure with one simple agent * and periodic message. */ #include #include #include using namespace std; namespace test { class timer_holder_t { std::mutex m_lock; so_5::timer_id_t m_timer; public: void store( so_5::timer_id_t timer ) { std::lock_guard< std::mutex > lock{ m_lock }; m_timer = std::move(timer); } void release() { std::lock_guard< std::mutex > lock{ m_lock }; m_timer.release(); } }; class a_test_t final : public so_5::agent_t { timer_holder_t & m_timer_to_drop; int m_ticks{ 0 }; public : struct tick : public so_5::signal_t {}; a_test_t( context_t ctx, timer_holder_t & timer_to_drop ) : so_5::agent_t( std::move(ctx) ) , m_timer_to_drop{ timer_to_drop } { so_subscribe_self().event( [this](mhood_t< tick >) { ++m_ticks; if( 3 == m_ticks ) so_deregister_agent_coop_normally(); } ); } ~a_test_t() override { m_timer_to_drop.release(); } }; } /* namespace test */ using namespace test; int main() { try { run_with_time_limit( []() { thread outside_thread; timer_holder_t timer_id; so_5::launch( [&]( so_5::environment_t & env ) { so_5::mbox_t test_mbox = env.introduce_coop( [&timer_id]( so_5::coop_t & coop ) { return coop.make_agent< a_test_t >( timer_id ) ->so_direct_mbox(); } ); outside_thread = thread( [test_mbox, &timer_id] { this_thread::sleep_for( chrono::milliseconds( 350 ) ); timer_id.store( so_5::send_periodic< a_test_t::tick >( test_mbox, chrono::milliseconds(100), chrono::milliseconds(100) ) ); this_thread::sleep_for( chrono::seconds(1) ); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); outside_thread.join(); }, 5, "simple agent with periodic message from outside" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/reg_dereg_notificators/000077500000000000000000000000001523065210100324475ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002061523065210100351260ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/reg_dereg_notificatorsset(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.reg_dereg_notificators) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/reg_dereg_notificators/main.cpp000066400000000000000000000066151523065210100341070ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with recreation of * 10000 of agents (each of them eats 1MiB memory). * * If there is a problem with deallocation of agent from deregistered * coop then this test will eat a lot of memory. */ #include #include #include using namespace std; class actor_t : public so_5::agent_t { struct next_turn final : public so_5::signal_t {}; std::array< char, 1024u * 1024u > m_dead_data; void on_next_turn( mhood_t ) { so_5::send< next_turn >( *this ); } public : actor_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( &actor_t::on_next_turn ); } void so_evt_start() override { // Touch every 1000th byte in dead-data. for( std::size_t i = 0u; i < m_dead_data.size(); i += 1000u ) m_dead_data[ i ] = static_cast(i % 125u); so_5::send< next_turn >( *this ); } }; class manager_t final { enum class state_t { wait_first_reg_notify, wait_second_reg_notify, wait_first_dereg_notify }; state_t m_state{ state_t::wait_first_reg_notify }; std::size_t m_agents_created{}; void next_turn( so_5::environment_t & env ) { m_state = state_t::wait_first_reg_notify; env.introduce_coop( [this]( so_5::coop_t & coop ) { coop.add_reg_notificator( [this]( so_5::environment_t & environment, const so_5::coop_handle_t & handle ) noexcept { ensure_or_die( m_state == state_t::wait_first_reg_notify, "m_state != state_t::wait_first_reg_notify" ); ++m_agents_created; environment.deregister_coop( handle, so_5::dereg_reason::normal ); m_state = state_t::wait_second_reg_notify; } ); coop.add_reg_notificator( [this]( so_5::environment_t & /*env*/, const so_5::coop_handle_t & /*handle*/ ) noexcept { ensure_or_die( m_state == state_t::wait_second_reg_notify, "m_state != state_t::wait_second_reg_notify" ); m_state = state_t::wait_first_dereg_notify; } ); coop.add_dereg_notificator( [this]( so_5::environment_t & environment, const so_5::coop_handle_t & /*handle*/, const so_5::coop_dereg_reason_t & /*reason*/ ) noexcept { ensure_or_die( m_state == state_t::wait_first_dereg_notify, "m_state != state_t::wait_first_dereg_notify" ); m_state = state_t::wait_first_reg_notify; if( m_agents_created < 10000u ) { std::cout << m_agents_created << "\r" << std::flush; next_turn( environment ); } else environment.stop(); } ); coop.make_agent< actor_t >(); } ); } public : manager_t() = default; void start( so_5::environment_t & env ) { next_turn( env ); } }; int main() { try { run_with_time_limit( []() { manager_t manager; so_5::launch( [&]( so_5::environment_t & env ) { manager.start( env ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); params.disable_autoshutdown(); } ); }, 240 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/simple_agent/000077500000000000000000000000001523065210100304075ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/simple_agent/CMakeLists.txt000066400000000000000000000001741523065210100331510ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.simple_agent) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/simple_agent/main.cpp000066400000000000000000000026511523065210100320430ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with one simple agent. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { bool agent_started = false; so_5::launch( [&]( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { struct stop {}; bool & m_agent_started; public: actor_t( context_t ctx, bool & agent_started ) : so_5::agent_t{ std::move(ctx) } , m_agent_started{ agent_started } {} void so_evt_start() override { m_agent_started = true; so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); so_5::send_delayed< stop >( *this, std::chrono::milliseconds(250) ); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< actor_t >( std::ref(agent_started) ); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); UT_CHECK_CONDITION( agent_started ); }, 5, "simple agent" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stats_coop_count/000077500000000000000000000000001523065210100313265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stats_coop_count/CMakeLists.txt000066400000000000000000000002001523065210100340560ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.stats_coop_count) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stats_coop_count/main.cpp000066400000000000000000000072771523065210100327730ustar00rootroot00000000000000/* * A simple test for getting count of registered cooperations * from run-time monitoring messages. */ #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &a_test_t::evt_monitor_quantity ); } virtual void so_evt_start() override { create_child_coops(); // This big value can lead to test failure if there is an // error in implementation of autoshutdown feature. so_environment().stats_controller(). set_distribution_period( std::chrono::seconds(30) ); so_environment().stats_controller().turn_on(); } private : unsigned int m_actual_values = { 0 }; void evt_monitor_quantity( const so_5::stats::messages::quantity< std::size_t > & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix.c_str() << evt.m_suffix.c_str() << ": " << evt.m_value << std::endl; if( stats::prefixes::coop_repository() == evt.m_prefix ) { if( stats::suffixes::coop_count() == evt.m_suffix ) { // Count of registered cooperations could be // 12 or 11 (it depends of deregistration of // the special autoshutdown-guard cooperation). if( 12 != evt.m_value && 11 != evt.m_value ) throw std::runtime_error( "unexpected count of " "registered cooperations: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } else if( stats::suffixes::agent_count() == evt.m_suffix ) { // Count of registered agents could be // 11 or 12 (it depends of deregistration of // the special autoshutdown-guard cooperation). if( 11 != evt.m_value && 12 != evt.m_value ) throw std::runtime_error( "unexpected count of " "registered agents: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } else if( stats::suffixes::coop_final_dereg_count() == evt.m_suffix ) { // Count of coops waiting for final dereg could be // 0 or 1 (it depends of deregistration of // the special autoshutdown-guard cooperation). if( 0 != evt.m_value && 1 != evt.m_value ) throw std::runtime_error( "unexpected count of " "coops in final dereg state: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } } if( 3 == m_actual_values ) so_deregister_agent_coop_normally(); } void create_child_coops() { class empty_actor_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; }; for( int i = 0; i != 10; ++i ) { auto coop = so_5::create_child_coop( *this ); coop->make_agent< empty_actor_t >(); so_environment().register_coop( std::move( coop ) ); } } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); }, 20, "simple coop count monitoring test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stats_on/000077500000000000000000000000001523065210100275725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stats_on/CMakeLists.txt000066400000000000000000000001701523065210100323300ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.stats_on) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stats_on/main.cpp000066400000000000000000000062261523065210100312300ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with turning run-time * stats distribution on. */ #include #include #include using namespace std; struct run_result_t { int m_first_run_starts{ 0 }; int m_first_run_stops{ 0 }; int m_second_run_starts{ 0 }; int m_second_run_stops{ 0 }; }; class a_test_t final : public so_5::agent_t { so_5::outliving_reference_t< run_result_t > m_result; state_t st_first{ this }, st_second{ this }; struct start_second : public so_5::signal_t {}; struct finish_second : public so_5::signal_t {}; public : a_test_t( context_t ctx, so_5::outliving_reference_t< run_result_t > result ) : so_5::agent_t( std::move(ctx) ) , m_result( std::move(result) ) {} virtual void so_define_agent() override { this >>= st_first; st_first .time_limit( std::chrono::milliseconds( 750 ), st_second ) .on_exit( [this] { so_environment().stats_controller().turn_off(); } ) .event( so_environment().stats_controller().mbox(), [this]( mhood_t< so_5::stats::messages::distribution_started > ) { m_result.get().m_first_run_starts += 1; } ) .event( so_environment().stats_controller().mbox(), [this]( mhood_t< so_5::stats::messages::distribution_finished > ) { m_result.get().m_first_run_stops += 1; } ); st_second .on_enter( [this] { so_5::send_delayed< start_second >( *this, std::chrono::milliseconds( 400 ) ); } ) .event( [this](mhood_t< start_second >) { so_environment().stats_controller().turn_on(); so_5::send_delayed< finish_second >( *this, std::chrono::milliseconds( 1050 ) ); } ) .event( [this](mhood_t< finish_second >) { so_deregister_agent_coop_normally(); } ) .event( so_environment().stats_controller().mbox(), [this]( mhood_t< so_5::stats::messages::distribution_started > ) { m_result.get().m_second_run_starts += 1; } ) .event( so_environment().stats_controller().mbox(), [this]( mhood_t< so_5::stats::messages::distribution_finished > ) { m_result.get().m_second_run_stops += 1; } ); } virtual void so_evt_start() override { auto & controller = so_environment().stats_controller(); controller.set_distribution_period( std::chrono::milliseconds( 300 ) ); controller.turn_on(); } }; int main() { try { run_with_time_limit( []() { run_result_t result; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( so_5::outliving_mutable(result) ); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); UT_CHECK_GT( result.m_first_run_starts, 1 ); UT_CHECK_GT( result.m_first_run_stops, 1 ); UT_CHECK_GT( result.m_second_run_starts, 1 ); UT_CHECK_GT( result.m_second_run_stops, 1 ); }, 5, "start/stop stats_controller" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stats_wt_activity/000077500000000000000000000000001523065210100315245ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stats_wt_activity/CMakeLists.txt000066400000000000000000000002011523065210100342550ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.stats_wt_activity) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stats_wt_activity/main.cpp000066400000000000000000000036121523065210100331560ustar00rootroot00000000000000/* * A simple test for getting run-time stats for work thread activity. */ #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &a_test_t::evt_thread_activity ); } virtual void so_evt_start() override { // This big value can lead to test failure if there is an // error in implementation of autoshutdown feature. so_environment().stats_controller(). set_distribution_period( std::chrono::seconds(30) ); so_environment().stats_controller().turn_on(); } private : void evt_thread_activity( const so_5::stats::messages::work_thread_activity & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix.c_str() << evt.m_suffix.c_str() << ": [" << evt.m_thread_id << "] = (" << evt.m_stats.m_working_stats << ", " << evt.m_stats.m_waiting_stats << ")" << std::endl; so_deregister_agent_coop_normally(); } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, []( so_5::environment_params_t & params ) { params.turn_work_thread_activity_tracking_on(); params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); }, 20, "simple work thread activity monitoring test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stop_in_init_fn/000077500000000000000000000000001523065210100311215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stop_in_init_fn/CMakeLists.txt000066400000000000000000000001771523065210100336660ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.stop_in_init_fn) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/stop_in_init_fn/main.cpp000066400000000000000000000014201523065210100325460ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with call to stop in init_fn function. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.stop(); }, []( so_5::environment_params_t & params ) { params.disable_autoshutdown(); params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); }, 5, "stop() in init_fn for simple_mtsafe_st_env_infrastructure" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/thread_id/000077500000000000000000000000001523065210100276635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/thread_id/CMakeLists.txt000066400000000000000000000001711523065210100324220ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.thread_id) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/thread_id/main.cpp000066400000000000000000000030101523065210100313050ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with one simple agent. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { const auto this_thread_id = so_5::query_current_thread_id(); so_5::current_thread_id_t agent_thread_id; so_5::launch( [&]( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { so_5::current_thread_id_t & m_thread_id; public: actor_t( context_t ctx, so_5::current_thread_id_t & thread_id ) : so_5::agent_t{ std::move(ctx) } , m_thread_id{ thread_id } {} void so_evt_start() override { m_thread_id = so_5::query_current_thread_id(); so_deregister_agent_coop_normally(); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< actor_t >( std::ref(agent_thread_id) ); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); std::cout << "this_thread_id: " << this_thread_id << ", agent_thread_id: " << agent_thread_id << std::endl; UT_CHECK_CONDITION( this_thread_id == agent_thread_id ); }, 5, "thread id check" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/timer_factories/000077500000000000000000000000001523065210100311175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/timer_factories/CMakeLists.txt000066400000000000000000000001771523065210100336640ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.timer_factories) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/timer_factories/main.cpp000066400000000000000000000035131523065210100325510ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with one simple agent * and delayed stop message. */ #include #include #include using namespace std; void launch_with( const std::string & factory_name, so_5::timer_manager_factory_t timer_factory ) { run_with_time_limit( [timer_factory]() { so_5::launch( [&]( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { struct stop {}; public: using so_5::agent_t::agent_t; void so_evt_start() override { so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); so_5::send_delayed< stop >( *this, std::chrono::milliseconds(250) ); } }; env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< actor_t >(); } ); }, [&]( so_5::environment_params_t & params ) { so_5::env_infrastructures::simple_mtsafe::params_t p; p.timer_manager( timer_factory ); params.infrastructure_factory( factory( std::move(p) ) ); } ); }, 5, factory_name + ": simple agent with delayed stop message" ); } int main() { try { struct timer_info_t { std::string m_name; so_5::timer_manager_factory_t m_factory; }; timer_info_t timers[] = { { "timer_wheel", so_5::timer_wheel_manager_factory() }, { "timer_heap", so_5::timer_heap_manager_factory() }, { "timer_list", so_5::timer_list_manager_factory() } }; for( const auto & t : timers ) { std::cout << t.m_name << " -> " << std::flush; launch_with( t.m_name, t.m_factory ); std::cout << "OK" << std::endl; } } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/unknown_exception_init_fn/000077500000000000000000000000001523065210100332235ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002111523065210100356760ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/unknown_exception_init_fnset(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.unknown_exception_init_fn) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000016501523065210100345760ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/unknown_exception_init_fn/* * A test for simple_mtsafe_st_env_infastructure with unknown * exception from init_fn function. */ #include #include using namespace std; int main() { try { run_with_time_limit( []() { try { so_5::launch( []( so_5::environment_t & ) { throw "boom!"; }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch" << std::endl; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } }, 5 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/unknown_exception_init_fn_2/000077500000000000000000000000001523065210100334445ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002131523065210100361210ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/unknown_exception_init_fn_2set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.unknown_exception_init_fn_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000035401523065210100350170ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/unknown_exception_init_fn_2/* * A test for simple_mtsafe_st_env_infastructure with unknown * exception from init_fn function. */ #include #include #include using namespace std; class coop_resource_t { so_5::atomic_counter_t & m_counter; public : coop_resource_t( so_5::atomic_counter_t & counter ) : m_counter{ counter } { ++m_counter; } ~coop_resource_t() noexcept { --m_counter; } }; int main() { try { run_with_time_limit( []() { so_5::atomic_counter_t counter{ 0 }; try { so_5::launch( [&]( so_5::environment_t & env ) { // Create a bunch of empty coops. for( int i = 0; i != 10000; ++i ) { auto coop = env.make_coop(); coop->take_under_control( std::make_unique< coop_resource_t >( std::ref(counter) ) ); auto id = env.register_coop( std::move(coop) ); // Some of them will be deregistered. if( 0 == (i % 3) ) env.deregister_coop( id, so_5::dereg_reason::normal ); } throw "boom!"; }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch"; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } const auto actual_counter = counter.load(); ensure_or_die( 0u == actual_counter, "expected value of counter is 0, actual=" + std::to_string(actual_counter) ); }, 5 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/unknown_exception_init_fn_3/000077500000000000000000000000001523065210100334455ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002131523065210100361220ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/unknown_exception_init_fn_3set(UNITTEST _unit.test.env_infrastructure.simple_mtsafe_st.unknown_exception_init_fn_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000056351523065210100350270ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_mtsafe_st/unknown_exception_init_fn_3/* * A test for simple_mtsafe_st_env_infastructure with unknown * exception from init_fn function. */ #include #include #include using namespace std; class coop_resource_t { so_5::atomic_counter_t & m_counter; public : coop_resource_t( so_5::atomic_counter_t & counter ) : m_counter{ counter } { ++m_counter; } ~coop_resource_t() noexcept { --m_counter; } }; class dummy_actor_t final : public so_5::agent_t { struct next_turn final : public so_5::signal_t {}; so_5::atomic_counter_t & m_finish_counter; public : dummy_actor_t( context_t ctx, so_5::atomic_counter_t & finish_counter ) : so_5::agent_t::agent_t{ std::move(ctx) } , m_finish_counter{ finish_counter } {} void so_evt_start() override { so_subscribe_self().event( &dummy_actor_t::on_next_turn ); so_5::send< next_turn >( *this ); } void so_evt_finish() override { ++m_finish_counter; } private : void on_next_turn( mhood_t ) { so_5::send_delayed< next_turn >( *this, std::chrono::milliseconds(10) ); } }; int main() { try { run_with_time_limit( []() { so_5::atomic_counter_t counter{ 0 }; so_5::atomic_counter_t finish_counter{ 0 }; try { so_5::launch( [&]( so_5::environment_t & env ) { // Create a bunch of coops with dummy_actors inside. auto disp = so_5::disp::one_thread::make_dispatcher( env ); for( int i = 0; i != 10000; ++i ) { auto coop = env.make_coop( disp.binder() ); coop->take_under_control( std::make_unique< coop_resource_t >( std::ref(counter) ) ); coop->make_agent< dummy_actor_t >( std::ref(finish_counter) ); auto id = env.register_coop( std::move(coop) ); // Some of them will be deregistered. if( 0 == (i % 3) ) env.deregister_coop( id, so_5::dereg_reason::normal ); } throw "boom!"; }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory() ); } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch"; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } const auto actual_counter = counter.load(); ensure_or_die( 0u == actual_counter, "expected value of counter is 0, actual=" + std::to_string(actual_counter) ); const auto actual_finish_counter = finish_counter.load(); ensure_or_die( 10000u == actual_finish_counter, "unexpected finish_counter=" + std::to_string(actual_finish_counter) ); }, 300 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/000077500000000000000000000000001523065210100266205ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/CMakeLists.txt000066400000000000000000000011131523065210100313540ustar00rootroot00000000000000project(tests) add_subdirectory(empty_init_fn) add_subdirectory(unknown_exception_init_fn) add_subdirectory(unknown_exception_init_fn_2) add_subdirectory(unknown_exception_init_fn_3) add_subdirectory(autoshutdown_disabled) add_subdirectory(stop_in_init_fn) add_subdirectory(simple_agent) add_subdirectory(stop_when_no_load) add_subdirectory(thread_id) add_subdirectory(delayed_msg) add_subdirectory(timer_factories) add_subdirectory(periodic_msg) add_subdirectory(stats_on) add_subdirectory(stats_coop_count) add_subdirectory(stats_wt_activity) add_subdirectory(reg_dereg_notificators) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/autoshutdown_disabled/000077500000000000000000000000001523065210100332135ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002111523065210100356660ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/autoshutdown_disabledset(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.autoshutdown_disabled) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000017141523065210100345670ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/autoshutdown_disabled/* * A test for simple_not_mtsafe_st_env_infastructure with * autoshutdown_guard. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { try { so_5::launch( []( so_5::environment_t & ) {}, []( so_5::environment_params_t & params ) { params.disable_autoshutdown(); params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); UT_CHECK_CONDITION( nullptr == "Exception must be thrown" ); } catch( const so_5::exception_t & x ) { UT_CHECK_EQ( so_5::rc_autoshutdown_must_be_enabled, x.error_code() ); } }, 5, "empty init_fn for simple_not_mtsafe_st_env_infrastructure" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/delayed_msg/000077500000000000000000000000001523065210100310755ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/delayed_msg/CMakeLists.txt000066400000000000000000000001771523065210100336420ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.delayed_msg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/delayed_msg/main.cpp000066400000000000000000000023511523065210100325260ustar00rootroot00000000000000/* * A test for simple_not_mtsafe_st_env_infastructure with one simple agent * and delayed stop message. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { struct stop {}; public: using so_5::agent_t::agent_t; void so_evt_start() override { so_subscribe_self().event( [](mhood_t) { // Do nothing. // Environment will be stopped anyway. } ); so_5::send_delayed< stop >( *this, std::chrono::milliseconds(250) ); } }; env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< actor_t >(); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); }, 5, "simple agent with delayed stop message" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/empty_init_fn/000077500000000000000000000000001523065210100314645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/empty_init_fn/CMakeLists.txt000066400000000000000000000002011523065210100342150ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.empty_init_fn) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/empty_init_fn/main.cpp000066400000000000000000000013101523065210100331070ustar00rootroot00000000000000/* * A test for simple_not_mtsafe_st_env_infastructure with empty init_fn function. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & ) {}, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); }, 5, "empty init_fn for simple_not_mtsafe_st_env_infrastructure" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/periodic_msg/000077500000000000000000000000001523065210100312645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/periodic_msg/CMakeLists.txt000066400000000000000000000002001523065210100340140ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.periodic_msg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/periodic_msg/main.cpp000066400000000000000000000024461523065210100327220ustar00rootroot00000000000000/* * A test for simple_mtsafe_st_env_infastructure with one simple agent * and periodic message. */ #include #include #include using namespace std; class a_test_t final : public so_5::agent_t { int m_ticks{ 0 }; so_5::timer_id_t m_tick_timer; struct tick : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( [this](mhood_t< tick >) { ++m_ticks; if( 3 == m_ticks ) so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { m_tick_timer = so_5::send_periodic< tick >( *this, chrono::milliseconds(250), chrono::milliseconds(300) ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); }, 5, "simple agent with periodic message" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/reg_dereg_notificators/000077500000000000000000000000001523065210100333275ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002121523065210100360030ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/reg_dereg_notificatorsset(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.reg_dereg_notificators) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000065541523065210100347120ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/reg_dereg_notificators/* * A test for simple_mtsafe_st_env_infastructure with recreation of * 10000 of agents (each of them eats 1MiB memory). * * If there is a problem with deallocation of agent from deregistered * coop then this test will eat a lot of memory. */ #include #include #include using namespace std; class actor_t : public so_5::agent_t { struct next_turn final : public so_5::signal_t {}; std::array< char, 1024u * 1024u > m_dead_data; void on_next_turn( mhood_t ) { so_5::send< next_turn >( *this ); } public : actor_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( &actor_t::on_next_turn ); } void so_evt_start() override { // Touch every 1000th byte in dead-data. for( std::size_t i = 0u; i < m_dead_data.size(); i += 1000u ) m_dead_data[ i ] = static_cast(i % 125u); so_5::send< next_turn >( *this ); } }; class manager_t final { enum class state_t { wait_first_reg_notify, wait_second_reg_notify, wait_first_dereg_notify }; state_t m_state{ state_t::wait_first_reg_notify }; std::size_t m_agents_created{}; void next_turn( so_5::environment_t & env ) { m_state = state_t::wait_first_reg_notify; env.introduce_coop( [this]( so_5::coop_t & coop ) { coop.add_reg_notificator( [this]( so_5::environment_t & environment, const so_5::coop_handle_t & handle ) noexcept { ensure_or_die( m_state == state_t::wait_first_reg_notify, "m_state != state_t::wait_first_reg_notify" ); ++m_agents_created; environment.deregister_coop( handle, so_5::dereg_reason::normal ); m_state = state_t::wait_second_reg_notify; } ); coop.add_reg_notificator( [this]( so_5::environment_t & /*env*/, const so_5::coop_handle_t & /*handle*/ ) noexcept { ensure_or_die( m_state == state_t::wait_second_reg_notify, "m_state != state_t::wait_second_reg_notify" ); m_state = state_t::wait_first_dereg_notify; } ); coop.add_dereg_notificator( [this]( so_5::environment_t & environment, const so_5::coop_handle_t & /*handle*/, const so_5::coop_dereg_reason_t & /*reason*/ ) noexcept { ensure_or_die( m_state == state_t::wait_first_dereg_notify, "m_state != state_t::wait_first_dereg_notify" ); m_state = state_t::wait_first_reg_notify; if( m_agents_created < 10000u ) { std::cout << m_agents_created << "\r" << std::flush; next_turn( environment ); } else environment.stop(); } ); coop.make_agent< actor_t >(); } ); } public : manager_t() = default; void start( so_5::environment_t & env ) { next_turn( env ); } }; int main() { try { run_with_time_limit( []() { manager_t manager; so_5::launch( [&]( so_5::environment_t & env ) { manager.start( env ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); }, 240 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/simple_agent/000077500000000000000000000000001523065210100312675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/simple_agent/CMakeLists.txt000066400000000000000000000002001523065210100340170ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.simple_agent) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/simple_agent/main.cpp000066400000000000000000000025771523065210100327320ustar00rootroot00000000000000/* * A test for simple_not_mtsafe_st_env_infastructure with one simple agent. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { bool agent_started = false; so_5::launch( [&]( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { struct stop {}; bool & m_agent_started; public: actor_t( context_t ctx, bool & agent_started ) : so_5::agent_t{ std::move(ctx) } , m_agent_started{ agent_started } {} void so_evt_start() override { m_agent_started = true; so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); so_5::send< stop >( *this ); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< actor_t >( std::ref(agent_started) ); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); UT_CHECK_CONDITION( agent_started ); }, 5, "simple agent" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_coop_count/000077500000000000000000000000001523065210100322065ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002041523065210100346630ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_coop_countset(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.stats_coop_count) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_coop_count/main.cpp000066400000000000000000000072731523065210100336470ustar00rootroot00000000000000/* * A simple test for getting count of registered cooperations * from run-time monitoring messages. */ #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &a_test_t::evt_monitor_quantity ); } virtual void so_evt_start() override { create_child_coops(); // This big value can lead to test failure if there is an // error in implementation of autoshutdown feature. so_environment().stats_controller(). set_distribution_period( std::chrono::seconds(30) ); so_environment().stats_controller().turn_on(); } private : unsigned int m_actual_values = { 0 }; void evt_monitor_quantity( const so_5::stats::messages::quantity< std::size_t > & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix.c_str() << evt.m_suffix.c_str() << ": " << evt.m_value << std::endl; if( stats::prefixes::coop_repository() == evt.m_prefix ) { if( stats::suffixes::coop_count() == evt.m_suffix ) { // Count of registered cooperations could be // 12 or 11 (it depends of deregistration of // the special autoshutdown-guard cooperation). if( 12 != evt.m_value && 11 != evt.m_value ) throw std::runtime_error( "unexpected count of " "registered cooperations: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } else if( stats::suffixes::agent_count() == evt.m_suffix ) { // Count of registered agents could be // 11 or 12 (it depends of deregistration of // the special autoshutdown-guard cooperation). if( 11 != evt.m_value && 12 != evt.m_value ) throw std::runtime_error( "unexpected count of " "registered agents: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } else if( stats::suffixes::coop_final_dereg_count() == evt.m_suffix ) { // Count of coops waiting for final dereg could be // 0 or 1 (it depends of deregistration of // the special autoshutdown-guard cooperation). if( 0 != evt.m_value && 1 != evt.m_value ) throw std::runtime_error( "unexpected count of " "coops in final dereg state: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } } if( 3 == m_actual_values ) so_deregister_agent_coop_normally(); } void create_child_coops() { class empty_actor_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; }; for( int i = 0; i != 10; ++i ) { auto coop = so_5::create_child_coop( *this ); coop->make_agent< empty_actor_t >(); so_environment().register_coop( std::move( coop ) ); } } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); }, 20, "simple coop count monitoring test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_on/000077500000000000000000000000001523065210100304525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_on/CMakeLists.txt000066400000000000000000000001741523065210100332140ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.stats_on) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_on/main.cpp000066400000000000000000000062361523065210100321110ustar00rootroot00000000000000/* * A test for simple_not_mtsafe_st_env_infastructure with turning run-time * stats distribution on. */ #include #include #include using namespace std; struct run_result_t { int m_first_run_starts{ 0 }; int m_first_run_stops{ 0 }; int m_second_run_starts{ 0 }; int m_second_run_stops{ 0 }; }; class a_test_t final : public so_5::agent_t { so_5::outliving_reference_t< run_result_t > m_result; state_t st_first{ this }, st_second{ this }; struct start_second : public so_5::signal_t {}; struct finish_second : public so_5::signal_t {}; public : a_test_t( context_t ctx, so_5::outliving_reference_t< run_result_t > result ) : so_5::agent_t( std::move(ctx) ) , m_result( std::move(result) ) {} virtual void so_define_agent() override { this >>= st_first; st_first .time_limit( std::chrono::milliseconds( 750 ), st_second ) .on_exit( [this] { so_environment().stats_controller().turn_off(); } ) .event( so_environment().stats_controller().mbox(), [this]( mhood_t< so_5::stats::messages::distribution_started > ) { m_result.get().m_first_run_starts += 1; } ) .event( so_environment().stats_controller().mbox(), [this]( mhood_t< so_5::stats::messages::distribution_finished > ) { m_result.get().m_first_run_stops += 1; } ); st_second .on_enter( [this] { so_5::send_delayed< start_second >( *this, std::chrono::milliseconds( 400 ) ); } ) .event( [this](mhood_t< start_second >) { so_environment().stats_controller().turn_on(); so_5::send_delayed< finish_second >( *this, std::chrono::milliseconds( 1050 ) ); } ) .event( [this](mhood_t< finish_second >) { so_deregister_agent_coop_normally(); } ) .event( so_environment().stats_controller().mbox(), [this]( mhood_t< so_5::stats::messages::distribution_started > ) { m_result.get().m_second_run_starts += 1; } ) .event( so_environment().stats_controller().mbox(), [this]( mhood_t< so_5::stats::messages::distribution_finished > ) { m_result.get().m_second_run_stops += 1; } ); } virtual void so_evt_start() override { auto & controller = so_environment().stats_controller(); controller.set_distribution_period( std::chrono::milliseconds( 300 ) ); controller.turn_on(); } }; int main() { try { run_with_time_limit( []() { run_result_t result; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( so_5::outliving_mutable(result) ); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); UT_CHECK_GT( result.m_first_run_starts, 1 ); UT_CHECK_GT( result.m_first_run_stops, 1 ); UT_CHECK_GT( result.m_second_run_starts, 1 ); UT_CHECK_GT( result.m_second_run_stops, 1 ); }, 5, "start/stop stats_controller" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/000077500000000000000000000000001523065210100324045ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002051523065210100350620ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activityset(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.stats_wt_activity) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stats_wt_activity/main.cpp000066400000000000000000000036161523065210100340420ustar00rootroot00000000000000/* * A simple test for getting run-time stats for work thread activity. */ #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &a_test_t::evt_thread_activity ); } virtual void so_evt_start() override { // This big value can lead to test failure if there is an // error in implementation of autoshutdown feature. so_environment().stats_controller(). set_distribution_period( std::chrono::seconds(30) ); so_environment().stats_controller().turn_on(); } private : void evt_thread_activity( const so_5::stats::messages::work_thread_activity & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix.c_str() << evt.m_suffix.c_str() << ": [" << evt.m_thread_id << "] = (" << evt.m_stats.m_working_stats << ", " << evt.m_stats.m_waiting_stats << ")" << std::endl; so_deregister_agent_coop_normally(); } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, []( so_5::environment_params_t & params ) { params.turn_work_thread_activity_tracking_on(); params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); }, 20, "simple work thread activity monitoring test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stop_in_init_fn/000077500000000000000000000000001523065210100320015ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002031523065210100344550ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stop_in_init_fnset(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.stop_in_init_fn) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stop_in_init_fn/main.cpp000066400000000000000000000013721523065210100334340ustar00rootroot00000000000000/* * A test for simple_not_mtsafe_st_env_infastructure with call to stop in * init_fn function. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.stop(); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); }, 5, "stop() in init_fn for simple_not_mtsafe_st_env_infrastructure" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stop_when_no_load/000077500000000000000000000000001523065210100323215ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002051523065210100347770ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stop_when_no_loadset(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.stop_when_no_load) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/stop_when_no_load/main.cpp000066400000000000000000000032731523065210100337560ustar00rootroot00000000000000/* * A test for simple_not_mtsafe_st_env_infastructure with one simple agent. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { bool agent_started = false; bool stop_received = false; so_5::launch( [&]( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { struct stop {}; bool & m_agent_started; bool & m_stop_received; public: actor_t( context_t ctx, bool & agent_started, bool & stop_received ) : so_5::agent_t{ std::move(ctx) } , m_agent_started{ agent_started } , m_stop_received{ stop_received } {} void so_evt_start() override { m_agent_started = true; so_subscribe_self().event( [this](mhood_t) { // Do nothing. // Shutdown must be initiated anyway. m_stop_received = true; } ); so_5::send< stop >( *this ); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< actor_t >( std::ref(agent_started), std::ref(stop_received) ); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); UT_CHECK_CONDITION( agent_started ); UT_CHECK_CONDITION( stop_received ); }, 5, "simple agent and stop when no load" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/thread_id/000077500000000000000000000000001523065210100305435ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/thread_id/CMakeLists.txt000066400000000000000000000001751523065210100333060ustar00rootroot00000000000000set(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.thread_id) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/thread_id/main.cpp000066400000000000000000000030201523065210100321660ustar00rootroot00000000000000/* * A test for simple_not_mtsafe_st_env_infastructure with one simple agent. */ #include #include #include using namespace std; int main() { try { run_with_time_limit( []() { const auto this_thread_id = so_5::query_current_thread_id(); so_5::current_thread_id_t agent_thread_id; so_5::launch( [&]( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { so_5::current_thread_id_t & m_thread_id; public: actor_t( context_t ctx, so_5::current_thread_id_t & thread_id ) : so_5::agent_t{ std::move(ctx) } , m_thread_id{ thread_id } {} void so_evt_start() override { m_thread_id = so_5::query_current_thread_id(); so_deregister_agent_coop_normally(); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< actor_t >( std::ref(agent_thread_id) ); } ); }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); std::cout << "this_thread_id: " << this_thread_id << ", agent_thread_id: " << agent_thread_id << std::endl; UT_CHECK_CONDITION( this_thread_id == agent_thread_id ); }, 5, "thread id check" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/timer_factories/000077500000000000000000000000001523065210100317775ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002031523065210100344530ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/timer_factoriesset(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.timer_factories) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/timer_factories/main.cpp000066400000000000000000000036241523065210100334340ustar00rootroot00000000000000/* * A test for simple_not_mtsafe_st_env_infastructure with one simple agent * and delayed stop message. */ #include #include #include using namespace std; void launch_with( const std::string & factory_name, so_5::timer_manager_factory_t factory ) { run_with_time_limit( [factory]() { so_5::launch( [&]( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { struct stop {}; public: using so_5::agent_t::agent_t; void so_evt_start() override { so_subscribe_self().event( [](mhood_t) { // Do nothing. // Environment will be stopped anyway. } ); so_5::send_delayed< stop >( *this, std::chrono::milliseconds(250) ); } }; env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< actor_t >(); } ); }, [&]( so_5::environment_params_t & params ) { so_5::env_infrastructures::simple_not_mtsafe::params_t p; p.timer_manager( factory ); params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory( std::move(p) ) ); } ); }, 5, factory_name + ": simple agent with delayed stop message" ); } int main() { try { struct timer_info_t { std::string m_name; so_5::timer_manager_factory_t m_factory; }; timer_info_t timers[] = { { "timer_wheel", so_5::timer_wheel_manager_factory() }, { "timer_heap", so_5::timer_heap_manager_factory() }, { "timer_list", so_5::timer_list_manager_factory() } }; for( const auto & t : timers ) { std::cout << t.m_name << " -> " << std::flush; launch_with( t.m_name, t.m_factory ); std::cout << "OK" << std::endl; } } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/unknown_exception_init_fn/000077500000000000000000000000001523065210100341035ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002151523065210100365620ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/unknown_exception_init_fnset(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.unknown_exception_init_fn) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000016321523065210100354560ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/unknown_exception_init_fn/* * A test for simple_not_mtsafe_st_env_infastructure with unknown exception * from init_fn. */ #include #include using namespace std; int main() { try { run_with_time_limit( []() { try { so_5::launch( []( so_5::environment_t & ) { throw "boom!"; }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch"; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } }, 5 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/unknown_exception_init_fn_2/000077500000000000000000000000001523065210100343245ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002171523065210100370050ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/unknown_exception_init_fn_2set(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.unknown_exception_init_fn_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000035441523065210100357030ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/unknown_exception_init_fn_2/* * A test for simple_mtsafe_st_env_infastructure with unknown * exception from init_fn function. */ #include #include #include using namespace std; class coop_resource_t { so_5::atomic_counter_t & m_counter; public : coop_resource_t( so_5::atomic_counter_t & counter ) : m_counter{ counter } { ++m_counter; } ~coop_resource_t() noexcept { --m_counter; } }; int main() { try { run_with_time_limit( []() { so_5::atomic_counter_t counter{ 0 }; try { so_5::launch( [&]( so_5::environment_t & env ) { // Create a bunch of empty coops. for( int i = 0; i != 10000; ++i ) { auto coop = env.make_coop(); coop->take_under_control( std::make_unique< coop_resource_t >( std::ref(counter) ) ); auto id = env.register_coop( std::move(coop) ); // Some of them will be deregistered. if( 0 == (i % 3) ) env.deregister_coop( id, so_5::dereg_reason::normal ); } throw "boom!"; }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch"; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } const auto actual_counter = counter.load(); ensure_or_die( 0u == actual_counter, "expected value of counter is 0, actual=" + std::to_string(actual_counter) ); }, 5 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/unknown_exception_init_fn_3/000077500000000000000000000000001523065210100343255ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002171523065210100370060ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/unknown_exception_init_fn_3set(UNITTEST _unit.test.env_infrastructure.simple_not_mtsafe_st.unknown_exception_init_fn_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) main.cpp000066400000000000000000000055171523065210100357060ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/env_infrastructure/simple_not_mtsafe_st/unknown_exception_init_fn_3/* * A test for simple_mtsafe_st_env_infastructure with unknown * exception from init_fn function. */ #include #include #include using namespace std; class coop_resource_t { so_5::atomic_counter_t & m_counter; public : coop_resource_t( so_5::atomic_counter_t & counter ) : m_counter{ counter } { ++m_counter; } ~coop_resource_t() noexcept { --m_counter; } }; class dummy_actor_t final : public so_5::agent_t { struct next_turn final : public so_5::signal_t {}; so_5::atomic_counter_t & m_finish_counter; public : dummy_actor_t( context_t ctx, so_5::atomic_counter_t & finish_counter ) : so_5::agent_t::agent_t{ std::move(ctx) } , m_finish_counter{ finish_counter } {} void so_evt_start() override { so_subscribe_self().event( &dummy_actor_t::on_next_turn ); so_5::send< next_turn >( *this ); } void so_evt_finish() override { ++m_finish_counter; } private : void on_next_turn( mhood_t ) { so_5::send_delayed< next_turn >( *this, std::chrono::milliseconds(10) ); } }; int main() { try { run_with_time_limit( []() { so_5::atomic_counter_t counter{ 0 }; so_5::atomic_counter_t finish_counter{ 0 }; try { so_5::launch( [&]( so_5::environment_t & env ) { // Create a bunch of coops with dummy_actors inside. for( int i = 0; i != 10000; ++i ) { auto coop = env.make_coop(); coop->take_under_control( std::make_unique< coop_resource_t >( std::ref(counter) ) ); coop->make_agent< dummy_actor_t >( std::ref(finish_counter) ); auto id = env.register_coop( std::move(coop) ); // Some of them will be deregistered. if( 0 == (i % 3) ) env.deregister_coop( id, so_5::dereg_reason::normal ); } throw "boom!"; }, []( so_5::environment_params_t & params ) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch"; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } const auto actual_counter = counter.load(); ensure_or_die( 0u == actual_counter, "expected value of counter is 0, actual=" + std::to_string(actual_counter) ); const auto actual_finish_counter = finish_counter.load(); ensure_or_die( 10000u == actual_finish_counter, "unexpected finish_counter=" + std::to_string(actual_finish_counter) ); }, 300 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/000077500000000000000000000000001523065210100213015ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/CMakeLists.txt000066400000000000000000000007021523065210100240400ustar00rootroot00000000000000project(tests) add_subdirectory(simplest) add_subdirectory(adv_thread_pool) add_subdirectory(simple_timer) add_subdirectory(simple_delivery_filter) add_subdirectory(simple_mchain_delivery) add_subdirectory(simple_mchain_timer) add_subdirectory(mchain_handled_count) add_subdirectory(message_limits) add_subdirectory(transfer_to_state) add_subdirectory(transfer_to_state_2) add_subdirectory(suppress_event) add_subdirectory(suppress_event_mutable) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/adv_thread_pool/000077500000000000000000000000001523065210100244335ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/adv_thread_pool/CMakeLists.txt000066400000000000000000000001511523065210100271700ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.adv_thread_pool) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/adv_thread_pool/main.cpp000066400000000000000000000061701523065210100260670ustar00rootroot00000000000000/* * Check for enveloped message and adv_thread_pool dispatcher. */ #include #include #include #include #include "../common_stuff.hpp" struct thread_safe_action final : public so_5::signal_t {}; struct thread_unsafe_action final : public so_5::signal_t {}; struct shutdown final : public so_5::signal_t {}; class test_case_t final : public so_5::agent_t { trace_t m_trace; const so_5::mbox_t m_mbox; std::mutex m_lock; std::set< so_5::current_thread_id_t > m_active_threads; int m_active_handlers{}; public : test_case_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_mbox{ special_mbox_t<>::make( so_environment().create_mbox(), so_5::outliving_mutable(m_trace), "mbox" ) } { so_subscribe( m_mbox ) .event( &test_case_t::on_safe_action, so_5::thread_safe ) .event( &test_case_t::on_unsafe_action ); so_subscribe_self() .event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send< thread_safe_action >( m_mbox ); so_5::send< thread_safe_action >( m_mbox ); so_5::send< thread_safe_action >( m_mbox ); so_5::send< thread_unsafe_action >( m_mbox ); so_5::send< thread_unsafe_action >( m_mbox ); so_5::send< thread_unsafe_action >( m_mbox ); so_5::send< shutdown >( *this ); } void so_evt_finish() override { ensure_or_die( 3 == m_active_threads.size(), "m_active_threads.size() is expected to be 3" ); std::cout << "Trace is: " << m_trace.content() << std::endl; ensure_or_die( !m_trace.content().empty(), "trace should not be empty!" ); } private : void on_safe_action( mhood_t ) { { std::lock_guard< std::mutex > lock{ m_lock }; m_active_threads.insert( so_5::query_current_thread_id() ); ++m_active_handlers; } std::this_thread::sleep_for( std::chrono::milliseconds(250) ); { std::lock_guard< std::mutex > lock{ m_lock }; --m_active_handlers; } } void on_unsafe_action( mhood_t ) { { std::lock_guard< std::mutex > lock{ m_lock }; ensure_or_die( 0 == m_active_handlers, "m_active_handlers must be 0" ); ++m_active_handlers; } std::this_thread::sleep_for( std::chrono::milliseconds(100) ); { std::lock_guard< std::mutex > lock{ m_lock }; ensure_or_die( 1 == m_active_handlers, "m_active_handlers must be 1" ); --m_active_handlers; } } }; void run_test() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent_with_binder< test_case_t >( so_5::disp::adv_thread_pool::make_dispatcher( env, 6 ).binder() ); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/common_stuff.hpp000066400000000000000000000077331523065210100245230ustar00rootroot00000000000000/* * Various common stuff for testing enveloped messages. */ #pragma once #include class trace_t final { std::mutex m_lock; std::string m_content; public: void append( const std::string & what ) { std::lock_guard< std::mutex > lock{ m_lock }; m_content += what; } std::string content() { std::string result; { std::lock_guard< std::mutex > lock{ m_lock }; result = m_content; } return result; } }; class test_envelope_t : public so_5::enveloped_msg::envelope_t { so_5::outliving_reference_t< trace_t > m_trace; const std::string m_id; so_5::message_ref_t m_payload; void append_text( const char * what ) { m_trace.get().append( m_id + ":" + what ); } public: test_envelope_t( so_5::outliving_reference_t< trace_t > trace, std::string id, so_5::message_ref_t payload ) : m_trace{ trace } , m_id( std::move(id) ) , m_payload{ std::move(payload) } {} void access_hook( access_context_t context, handler_invoker_t & invoker ) noexcept override { switch( context ) { case access_context_t::handler_found: append_text( "pre_invoke;" ); invoker.invoke( payload_info_t{ m_payload } ); append_text( "post_invoke;" ); break; case access_context_t::transformation: append_text( "transform;" ); invoker.invoke( payload_info_t{ m_payload } ); break; case access_context_t::inspection: append_text( "inspect;" ); invoker.invoke( payload_info_t{ m_payload } ); break; } } }; template< class Envelope = test_envelope_t > class special_mbox_t : public so_5::abstract_message_box_t { const so_5::mbox_t m_actual_mbox; so_5::outliving_reference_t< trace_t > m_trace; const std::string m_id; std::mutex m_counter_lock; int m_counter{}; std::string allocate_counter() { std::lock_guard< std::mutex > lock{ m_counter_lock }; return m_id + "[" + std::to_string(++m_counter) + "]"; } public : special_mbox_t( so_5::mbox_t actual_mbox, so_5::outliving_reference_t< trace_t > trace, std::string id ) : m_actual_mbox{ std::move(actual_mbox) } , m_trace{ trace } , m_id( std::move(id) ) {} static so_5::mbox_t make( so_5::mbox_t actual_mbox, so_5::outliving_reference_t< trace_t > trace, std::string id ) { return so_5::mbox_t{ std::make_unique< special_mbox_t >( std::move(actual_mbox), trace, std::move(id) ) }; } so_5::mbox_id_t id() const override { return m_actual_mbox->id(); } void subscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) override { m_actual_mbox->subscribe_event_handler( type_index, subscriber ); } void unsubscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) noexcept override { m_actual_mbox->unsubscribe_event_handler( type_index, subscriber ); } std::string query_name() const override { return m_actual_mbox->query_name(); } so_5::mbox_type_t type() const override { return m_actual_mbox->type(); } void do_deliver_message( so_5::message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const so_5::message_ref_t & message, unsigned int redirection_deep ) override { auto envelope = std::make_unique< Envelope >( m_trace, allocate_counter(), message ); m_actual_mbox->do_deliver_message( delivery_mode, msg_type, so_5::message_ref_t{ std::move(envelope) }, redirection_deep ); } void set_delivery_filter( const std::type_index & msg_type, const so_5::delivery_filter_t & filter, so_5::abstract_message_sink_t & subscriber ) override { m_actual_mbox->set_delivery_filter( msg_type, filter, subscriber ); } void drop_delivery_filter( const std::type_index & msg_type, so_5::abstract_message_sink_t & subscriber ) noexcept override { m_actual_mbox->drop_delivery_filter( msg_type, subscriber ); } so_5::environment_t & environment() const noexcept override { return m_actual_mbox->environment(); } }; sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/mchain_handled_count/000077500000000000000000000000001523065210100254275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/mchain_handled_count/CMakeLists.txt000066400000000000000000000001561523065210100301710ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.mchain_handled_count) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/mchain_handled_count/main.cpp000066400000000000000000000050731523065210100270640ustar00rootroot00000000000000/* * Check for enveloped service request. */ #include #include #include #include class special_wrapper_t : public so_5::enveloped_msg::envelope_t { so_5::message_ref_t m_payload; std::atomic_bool m_enabled; void invoke_if_enabled( handler_invoker_t & invoker ) noexcept { if( m_enabled.load( std::memory_order_acquire ) ) invoker.invoke( payload_info_t(m_payload) ); } public: special_wrapper_t( so_5::message_ref_t payload ) : m_payload( std::move(payload) ) { m_enabled.store( true, std::memory_order_release ); } void disable() noexcept { m_enabled.store( false, std::memory_order_release ); } void access_hook( access_context_t, handler_invoker_t & invoker ) noexcept override { invoke_if_enabled( invoker ); } }; struct just_test_msg final : public so_5::message_t { int m_v; just_test_msg( int v ) : m_v( v ) {} }; void run_test() { so_5::wrapped_env_t sobj; auto mchain = create_mchain( sobj ); so_5::message_ref_t msg1{ std::make_unique(0) }; so_5::message_ref_t msg2{ std::make_unique(1) }; so_5::intrusive_ptr_t< special_wrapper_t > env1{ std::make_unique(msg1) }; so_5::intrusive_ptr_t< special_wrapper_t > env2{ std::make_unique(msg2) }; mchain->as_mbox()->do_deliver_message( so_5::message_delivery_mode_t::ordinary, so_5::message_payload_type::subscription_type_index(), env1, 1u ); mchain->as_mbox()->do_deliver_message( so_5::message_delivery_mode_t::ordinary, so_5::message_payload_type::subscription_type_index(), env2, 1u ); env1->disable(); std::size_t processed{}; auto result = receive( from(mchain).handle_n(1).no_wait_on_empty(), [&](so_5::mhood_t cmd) { ++processed; std::cout << "Msg: " << cmd->m_v << std::endl; } ); const std::size_t expected = 1; ensure_or_die( expected == processed, "processed missmatch:\n" "expected=" + std::to_string(expected) + "\n" " actual=" + std::to_string(processed) ); ensure_or_die( expected == result.handled(), "handled missmatch:\n" "expected=" + std::to_string(expected) + "\n" " actual=" + std::to_string(result.handled()) ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/000077500000000000000000000000001523065210100243065ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/CMakeLists.txt000066400000000000000000000002021523065210100270400ustar00rootroot00000000000000project(tests) add_subdirectory(transform_normal) add_subdirectory(transform_normal_user_msg) add_subdirectory(transform_signal) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/transform_normal/000077500000000000000000000000001523065210100276715ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/transform_normal/CMakeLists.txt000066400000000000000000000001711523065210100324300ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.message_limits.transform_normal) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/transform_normal/main.cpp000066400000000000000000000050241523065210100313220ustar00rootroot00000000000000/* * Check for limit_then_transform for a normal case. */ #include #include #include #include #include "../../common_stuff.hpp" struct msg_one final : public so_5::message_t { std::string m_value; msg_one( std::string value ) : m_value( std::move(value) ) {} }; struct msg_two final : public so_5::message_t { std::string m_value; msg_two( std::string value ) : m_value( std::move(value) ) {} }; struct shutdown final : public so_5::signal_t {}; class test_agent_t final : public so_5::agent_t { so_5::outliving_reference_t< trace_t > m_trace; const so_5::mbox_t m_mbox; public: test_agent_t( context_t ctx, so_5::outliving_reference_t< trace_t > trace ) : so_5::agent_t{ ctx + limit_then_transform(1, [this]( const msg_one & src ) { return make_transformed< msg_two >( so_direct_mbox(), "[" + src.m_value + "]" ); } ) + limit_then_abort(1) + limit_then_abort(1) } , m_trace{ trace } , m_mbox{ special_mbox_t<>::make( so_environment().create_mbox(), trace, "mbox" ) } { so_default_state().event( m_mbox, [this]( mhood_t cmd ) { m_trace.get().append( "msg_one{" + cmd->m_value + "};" ); } ) .event( [this]( mhood_t cmd ) { m_trace.get().append( "msg_two{" + cmd->m_value + "};" ); so_5::send( *this ); } ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send( m_mbox, "One" ); so_5::send( m_mbox, "Two" ); } }; void run_test() { trace_t trace; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< test_agent_t >( so_5::outliving_mutable(trace) ); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); std::cout << "trace is: " << trace.content() << std::endl; ensure_or_die( "mbox[2]:transform;" "mbox[1]:pre_invoke;msg_one{One};mbox[1]:post_invoke;" "msg_two{[Two]};" == trace.content(), "trace missmatch: " + trace.content() ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/transform_normal_user_msg/000077500000000000000000000000001523065210100315755ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002021523065210100342500ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/transform_normal_user_msgset(UNITTEST _unit.test.enveloped_msg.message_limits.transform_normal_user_msg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/transform_normal_user_msg/main.cpp000066400000000000000000000050301523065210100332230ustar00rootroot00000000000000/* * Check for limit_then_transform for a normal case. */ #include #include #include #include #include "../../common_stuff.hpp" struct msg_one final { std::string m_one; std::string m_two; std::string m_three; }; struct msg_two final { std::string m_value; }; struct shutdown final : public so_5::signal_t {}; class test_agent_t final : public so_5::agent_t { so_5::outliving_reference_t< trace_t > m_trace; const so_5::mbox_t m_mbox; public: test_agent_t( context_t ctx, so_5::outliving_reference_t< trace_t > trace ) : so_5::agent_t{ ctx + limit_then_transform(1, [this]( const msg_one & src ) { return make_transformed< msg_two >( so_direct_mbox(), "[" + src.m_three + "." + src.m_two + "." + src.m_one + "]" ); } ) + limit_then_abort(1) + limit_then_abort(1) } , m_trace{ trace } , m_mbox{ special_mbox_t<>::make( so_environment().create_mbox(), trace, "mbox" ) } { so_default_state().event( m_mbox, [this]( mhood_t cmd ) { m_trace.get().append( "msg_one{" + cmd->m_one + "." + cmd->m_two + "." + cmd->m_three + "};" ); } ) .event( [this]( mhood_t cmd ) { m_trace.get().append( "msg_two{" + cmd->m_value + "};" ); so_5::send( *this ); } ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send( m_mbox, "One", "Two", "Three" ); so_5::send( m_mbox, "Four", "Five", "Six" ); } }; void run_test() { trace_t trace; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< test_agent_t >( so_5::outliving_mutable(trace) ); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); std::cout << "trace is: " << trace.content() << std::endl; ensure_or_die( "mbox[2]:transform;" "mbox[1]:pre_invoke;msg_one{One.Two.Three};mbox[1]:post_invoke;" "msg_two{[Six.Five.Four]};" == trace.content(), "trace missmatch: " + trace.content() ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/transform_signal/000077500000000000000000000000001523065210100276565ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/transform_signal/CMakeLists.txt000066400000000000000000000001711523065210100324150ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.message_limits.transform_signal) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/message_limits/transform_signal/main.cpp000066400000000000000000000047361523065210100313200ustar00rootroot00000000000000/* * Check for limit_then_transform for a normal case. */ #include #include #include #include #include "../../common_stuff.hpp" struct msg_one final : public so_5::signal_t {}; struct msg_two final : public so_5::message_t { std::string m_value; msg_two( std::string value ) : m_value( std::move(value) ) {} }; struct shutdown final : public so_5::signal_t {}; class test_agent_t final : public so_5::agent_t { so_5::outliving_reference_t< trace_t > m_trace; const so_5::mbox_t m_mbox; public: test_agent_t( context_t ctx, so_5::outliving_reference_t< trace_t > trace, so_5::mbox_t mbox ) : so_5::agent_t{ ctx + limit_then_transform< msg_one >( 1, [this]() { return make_transformed< msg_two >( so_direct_mbox(), "[transformed]" ); } ) + limit_then_abort(1) + limit_then_abort(1) } , m_trace{ trace } , m_mbox{ std::move(mbox) } { so_default_state().event( m_mbox, [this]( mhood_t ) { m_trace.get().append( "msg_one{};" ); } ) .event( [this]( mhood_t cmd ) { m_trace.get().append( "msg_two{" + cmd->m_value + "};" ); so_5::send( *this ); } ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send( m_mbox ); so_5::send( m_mbox ); } }; void run_test() { trace_t trace; so_5::launch( [&]( so_5::environment_t & env ) { so_5::mbox_t mbox = special_mbox_t<>::make( env.create_mbox(), so_5::outliving_mutable( trace ), "mbox" ); env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< test_agent_t >( so_5::outliving_mutable( trace ), mbox ); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); std::cout << "trace is: " << trace.content() << std::endl; ensure_or_die( "mbox[2]:transform;" "mbox[1]:pre_invoke;msg_one{};mbox[1]:post_invoke;" "msg_two{[transformed]};" == trace.content(), "trace missmatch: " + trace.content() ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_delivery_filter/000077500000000000000000000000001523065210100260425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_delivery_filter/CMakeLists.txt000066400000000000000000000001601523065210100305770ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.simple_delivery_filter) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_delivery_filter/main.cpp000066400000000000000000000045701523065210100275000ustar00rootroot00000000000000/* * Check for enveloped service request. */ #include #include #include #include #include "../common_stuff.hpp" struct shutdown final : public so_5::signal_t {}; struct test_message final { std::string m_a; int m_b; }; class test_case_t final : public so_5::agent_t { so_5::outliving_reference_t< trace_t > m_trace; const so_5::mbox_t m_dest; public : test_case_t( context_t ctx, so_5::outliving_reference_t< trace_t > trace ) : so_5::agent_t{ std::move(ctx) } , m_trace{ trace } , m_dest{ special_mbox_t<>::make( so_environment().create_mbox(), trace, "mb" ) } {} void so_define_agent() override { so_set_delivery_filter( m_dest, []( const test_message & msg ) { return "test" != msg.m_a; } ); so_subscribe_self() .event( [this]( mhood_t ) { m_trace.get().append( "shutdown;" ); so_deregister_agent_coop_normally(); } ); so_subscribe( m_dest ) .event( [this]( mhood_t cmd ) { m_trace.get().append( "message=" + cmd->m_a + ";" ); } ); } void so_evt_start() override { so_5::send< test_message >( m_dest, "first", 0 ); so_5::send< test_message >( m_dest, "test", 1 ); so_5::send< test_message >( m_dest, "second", 2 ); so_5::send< shutdown >( *this ); } }; void run_test() { trace_t trace; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< test_case_t >( so_5::outliving_mutable(trace) ); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); std::cout << "trace is: " << trace.content() << std::endl; const std::string expected = "mb[1]:inspect;mb[2]:inspect;mb[3]:inspect;" "mb[1]:pre_invoke;message=first;mb[1]:post_invoke;" "mb[3]:pre_invoke;message=second;mb[3]:post_invoke;" "shutdown;"; ensure_or_die( expected == trace.content(), "trace missmatch:\n" " expected=" + expected + "\n" " actual=" + trace.content() ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_mchain_delivery/000077500000000000000000000000001523065210100260145ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_mchain_delivery/CMakeLists.txt000066400000000000000000000001601523065210100305510ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.simple_mchain_delivery) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_mchain_delivery/main.cpp000066400000000000000000000040371523065210100274500ustar00rootroot00000000000000/* * Check for enveloped service request. */ #include #include #include #include #include "../common_stuff.hpp" struct so_based_msg final : public so_5::message_t { const std::string m_value; so_based_msg( std::string value ) : m_value( std::move(value) ) {} }; struct user_msg final { std::string m_value; }; struct simple_signal final : public so_5::message_t {}; struct dummy_msg final {}; void run_test() { trace_t trace; so_5::wrapped_env_t sobj; auto mchain = create_mchain( sobj ); auto special_mbox = special_mbox_t<>::make( mchain->as_mbox(), so_5::outliving_mutable(trace), "mb" ); auto prepared = prepare_receive( from( mchain ).extract_n( 1 ).no_wait_on_empty(), [&]( so_5::mhood_t cmd ) { trace.append( "received{" + cmd->m_value + "};" ); }, [&]( so_5::mhood_t cmd ) { trace.append( "received{" + cmd->m_value + "};" ); }, [&]( so_5::mhood_t ) { trace.append( "simple_signal;" ); } ); so_5::send( special_mbox, "First" ); so_5::receive( prepared ); so_5::send( special_mbox, "Second" ); so_5::receive( prepared ); so_5::send( special_mbox ); so_5::receive( prepared ); so_5::send( special_mbox ); so_5::receive( prepared ); std::cout << "trace is: " << trace.content() << std::endl; const std::string expected{ "mb[1]:pre_invoke;received{First};mb[1]:post_invoke;" "mb[2]:pre_invoke;received{Second};mb[2]:post_invoke;" "mb[3]:pre_invoke;simple_signal;mb[3]:post_invoke;" }; ensure_or_die( expected == trace.content(), "trace missmatch:\n" "expected=" + expected + "\n" " actual=" + trace.content() ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_mchain_timer/000077500000000000000000000000001523065210100253115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_mchain_timer/CMakeLists.txt000066400000000000000000000001551523065210100300520ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.simple_mchain_timer) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_mchain_timer/main.cpp000066400000000000000000000042521523065210100267440ustar00rootroot00000000000000/* * Check for enveloped service request. */ #include #include #include #include #include "../common_stuff.hpp" struct so_based_msg final : public so_5::message_t { const std::string m_value; so_based_msg( std::string value ) : m_value( std::move(value) ) {} }; struct user_msg final { std::string m_value; }; struct simple_signal final : public so_5::message_t {}; struct dummy_msg final {}; void run_test() { trace_t trace; so_5::wrapped_env_t sobj; auto mchain = create_mchain( sobj ); auto special_mbox = special_mbox_t<>::make( mchain->as_mbox(), so_5::outliving_mutable(trace), "mb" ); so_5::send_delayed( special_mbox, std::chrono::milliseconds(10), "First" ); so_5::send_delayed( special_mbox, std::chrono::milliseconds(15), "Second" ); so_5::send_delayed( special_mbox, std::chrono::milliseconds(20) ); so_5::send_delayed( special_mbox, std::chrono::milliseconds(25) ); auto prepared = prepare_receive( from( mchain ) .handle_all() .empty_timeout( std::chrono::milliseconds(100) ), [&]( so_5::mhood_t cmd ) { trace.append( "received{" + cmd->m_value + "};" ); }, [&]( so_5::mhood_t cmd ) { trace.append( "received{" + cmd->m_value + "};" ); }, [&]( so_5::mhood_t ) { trace.append( "simple_signal;" ); } ); so_5::receive( prepared ); std::cout << "trace is: " << trace.content() << std::endl; const std::string expected{ "mb[1]:pre_invoke;received{First};mb[1]:post_invoke;" "mb[2]:pre_invoke;received{Second};mb[2]:post_invoke;" "mb[3]:pre_invoke;simple_signal;mb[3]:post_invoke;" }; ensure_or_die( expected == trace.content(), "trace missmatch:\n" "expected=" + expected + "\n" " actual=" + trace.content() ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_timer/000077500000000000000000000000001523065210100237725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_timer/CMakeLists.txt000066400000000000000000000001461523065210100265330ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.simple_timer) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simple_timer/main.cpp000066400000000000000000000037521523065210100254310ustar00rootroot00000000000000/* * Check for enveloped service request. */ #include #include #include #include #include "../common_stuff.hpp" struct shutdown final : public so_5::signal_t {}; class test_case_t final : public so_5::agent_t { so_5::outliving_reference_t< trace_t > m_trace; public : test_case_t( context_t ctx, so_5::outliving_reference_t< trace_t > trace ) : so_5::agent_t{ std::move(ctx) } , m_trace{ trace } { so_subscribe_self() .event( [this, trace]( mhood_t ) { trace.get().append( "shutdown;" ); so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::message_ref_t envelope( std::make_unique< test_envelope_t >( m_trace, "mb", so_5::message_ref_t{} ) ); // Low-level function should be used because we try to send // a message instead of a signal (signals can't have data, but // data is present here). so_5::low_level_api::single_timer( so_5::message_payload_type< shutdown >::subscription_type_index(), std::move(envelope), so_direct_mbox(), std::chrono::milliseconds(50) ); } }; void run_test() { trace_t trace; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< test_case_t >( so_5::outliving_mutable(trace) ); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); std::cout << "trace is: " << trace.content() << std::endl; ensure_or_die( "mb:pre_invoke;shutdown;mb:post_invoke;" == trace.content(), "trace missmatch: " + trace.content() ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simplest/000077500000000000000000000000001523065210100231415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simplest/CMakeLists.txt000066400000000000000000000001421523065210100256760ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.simplest) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/simplest/main.cpp000066400000000000000000000130641523065210100245750ustar00rootroot00000000000000/* * A simplest test for enveloped message. */ #include #include #include #include class test_envelope_t final : public so_5::enveloped_msg::envelope_t { std::mutex m_lock; so_5::outliving_reference_t< std::string > m_receiver; const std::string m_id; so_5::message_ref_t m_payload; void append_text( const char * what ) { std::lock_guard< std::mutex > lock{ m_lock }; m_receiver.get() += m_id; m_receiver.get() += ":"; m_receiver.get() += what; } public: test_envelope_t( so_5::outliving_reference_t< std::string > receiver, std::string id, so_5::message_ref_t payload ) : m_receiver{ receiver } , m_id( std::move(id) ) , m_payload{ std::move(payload) } {} void access_hook( access_context_t context, handler_invoker_t & invoker ) noexcept override { switch( context ) { case access_context_t::handler_found: append_text( "pre_invoke;" ); invoker.invoke( so_5::enveloped_msg::payload_info_t{ m_payload } ); append_text( "post_invoke;" ); break; case access_context_t::transformation: append_text( "transform;" ); invoker.invoke( payload_info_t{ m_payload } ); break; case access_context_t::inspection: append_text( "inspect;" ); invoker.invoke( payload_info_t{ m_payload } ); break; } } }; template< typename Msg, typename... Args > void post_enveloped( std::string & receiver, std::string id, const so_5::mbox_t & mbox, Args && ...args ) { so_5::message_ref_t msg{ std::make_unique( std::forward(args)... ) }; so_5::message_ref_t enveloped{ std::make_unique( so_5::outliving_mutable(receiver), std::move(id), std::move(msg) ) }; mbox->do_deliver_message( so_5::message_delivery_mode_t::ordinary, so_5::message_payload_type::subscription_type_index(), std::move(enveloped), 1 ); } struct msg_one final : public so_5::message_t { std::string m_value; msg_one( std::string value ) : m_value( std::move(value) ) {} }; struct msg_two final : public so_5::message_t { std::string m_value; msg_two( std::string value ) : m_value( std::move(value) ) {} }; struct shutdown final : public so_5::signal_t {}; class mpmc_mbox_case_t final : public so_5::agent_t { const so_5::mbox_t m_mbox; std::string & m_receiver; state_t st_dummy{ this }; public: mpmc_mbox_case_t( context_t ctx, std::string & receiver ) : so_5::agent_t{ std::move(ctx) } , m_mbox{ so_environment().create_mbox() } , m_receiver( receiver ) { so_default_state().event( m_mbox, [this]( mhood_t cmd ) { m_receiver += "msg_one{" + cmd->m_value + "};"; } ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); st_dummy.event( m_mbox, [this]( mhood_t cmd ) { m_receiver += "msg_two{" + cmd->m_value + "};"; } ); } void so_evt_start() override { post_enveloped( m_receiver, "1", m_mbox, "One" ); post_enveloped( m_receiver, "2", m_mbox, "Two" ); so_5::send( so_direct_mbox() ); } }; class direct_mbox_case_t final : public so_5::agent_t { std::string & m_receiver; public: direct_mbox_case_t( context_t ctx, std::string & receiver ) : so_5::agent_t{ std::move(ctx) } , m_receiver( receiver ) { so_default_state().event( [this]( mhood_t cmd ) { m_receiver += "msg_one{" + cmd->m_value + "};"; } ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { post_enveloped( m_receiver, "1", so_direct_mbox(), "One" ); post_enveloped( m_receiver, "2", so_direct_mbox(), "Two" ); so_5::send( so_direct_mbox() ); } }; class direct_mbox_with_limits_case_t final : public so_5::agent_t { std::string & m_receiver; public: direct_mbox_with_limits_case_t( context_t ctx, std::string & receiver ) : so_5::agent_t{ ctx + limit_then_abort(1) + limit_then_abort(1) + limit_then_abort(1) } , m_receiver( receiver ) { so_default_state().event( [this]( mhood_t cmd ) { m_receiver += "msg_one{" + cmd->m_value + "};"; } ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { post_enveloped( m_receiver, "1", so_direct_mbox(), "One" ); post_enveloped( m_receiver, "2", so_direct_mbox(), "Two" ); so_5::send( so_direct_mbox() ); } }; template< typename Test_Agent > void run_test( const char * test_case_name ) { std::cout << "=== " << test_case_name << " ===" << std::endl; std::string trace; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< Test_Agent >( trace ); } ); } ); std::cout << "trace is: " << trace << std::endl; const std::string expected = "1:pre_invoke;msg_one{One};1:post_invoke;"; ensure_or_die( expected == trace, "trace missmatch:\n" " actual=" + trace + "\n" "expected=" + expected ); std::cout << "DONE" << std::endl; } int main() { try { run_with_time_limit( []() { run_test< mpmc_mbox_case_t >( "mpmc_mbox" ); run_test< direct_mbox_case_t >( "direct_mbox[no-limits]" ); run_test< direct_mbox_with_limits_case_t >( "direct_mbox[with-limits]" ); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/suppress_event/000077500000000000000000000000001523065210100243665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/suppress_event/CMakeLists.txt000066400000000000000000000001501523065210100271220ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.suppress_event) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/suppress_event/main.cpp000066400000000000000000000035571523065210100260300ustar00rootroot00000000000000/* * Check for enveloped message and state.suppress. */ #include #include #include #include #include "../common_stuff.hpp" struct hello final { std::string m_msg; }; struct stop final : public so_5::signal_t {}; class test_case_t final : public so_5::agent_t { trace_t m_trace; const so_5::mbox_t m_mbox; state_t st_parent{ this, "parent" }; state_t st_child{ initial_substate_of{ st_parent }, "child" }; public : test_case_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_mbox{ special_mbox_t<>::make( so_environment().create_mbox(), so_5::outliving_mutable(m_trace), "mbox" ) } { st_parent .event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ) .event( m_mbox, [](mhood_t) { std::cout << "event handler for 'hello' in st_parent " "must not be called!" << std::endl; std::abort(); } ); st_child.suppress( m_mbox ); } void so_evt_start() override { this >>= st_parent; so_5::send< hello >( m_mbox, "hello" ); so_5::send< stop >( *this ); } void so_evt_finish() override { ensure_or_die( m_trace.content().empty(), "trace should not be empty but it isn't: " + m_trace.content() ); } }; void run_test() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< test_case_t >(); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/suppress_event_mutable/000077500000000000000000000000001523065210100260775ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/suppress_event_mutable/CMakeLists.txt000066400000000000000000000001601523065210100306340ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.suppress_event_mutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/suppress_event_mutable/main.cpp000066400000000000000000000037511523065210100275350ustar00rootroot00000000000000/* * Check for enveloped message and state.suppress (for mutable msg). */ #include #include #include #include #include "../common_stuff.hpp" struct hello final { std::string m_msg; }; struct stop final : public so_5::signal_t {}; class test_case_t final : public so_5::agent_t { trace_t m_trace; const so_5::mbox_t m_mbox; state_t st_parent{ this, "parent" }; state_t st_child{ initial_substate_of{ st_parent }, "child" }; public : test_case_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_mbox{ special_mbox_t<>::make( so_make_new_direct_mbox(), so_5::outliving_mutable(m_trace), "mbox" ) } { st_parent .event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ) .event( m_mbox, [](mhood_t cmd) { ensure_or_die( "hello-immutable" == cmd->m_msg, "unexpected value of hello: " + cmd->m_msg ); } ) .event( m_mbox, [](mutable_mhood_t) { std::cout << "event handler for mutable 'hello' in st_parent " "must not be called!" << std::endl; std::abort(); } ); st_child.suppress< so_5::mutable_msg >( m_mbox ); } void so_evt_start() override { this >>= st_parent; so_5::send< so_5::mutable_msg >( m_mbox, "hello" ); so_5::send< hello >( m_mbox, "hello-immutable" ); so_5::send< stop >( *this ); } }; void run_test() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< test_case_t >(); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/transfer_to_state/000077500000000000000000000000001523065210100250275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/transfer_to_state/CMakeLists.txt000066400000000000000000000001531523065210100275660ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.transfer_to_state) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/transfer_to_state/main.cpp000066400000000000000000000033541523065210100264640ustar00rootroot00000000000000/* * Check for enveloped message and transfer_to_state. */ #include #include #include #include #include "../common_stuff.hpp" struct shutdown final { std::string m_msg; }; class test_case_t final : public so_5::agent_t { trace_t m_trace; const so_5::mbox_t m_mbox; state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; public : test_case_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_mbox{ special_mbox_t<>::make( so_environment().create_mbox(), so_5::outliving_mutable(m_trace), "mbox" ) } { st_one.transfer_to_state< shutdown >( m_mbox, st_two ); st_two.event( m_mbox, [this](mhood_t cmd) { ensure_or_die( "bye" == cmd->m_msg, "unexpected message inside shutdown: " + cmd->m_msg ); so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { this >>= st_one; so_5::send< shutdown >( m_mbox, "bye" ); } void so_evt_finish() override { std::cout << "Trace is: " << m_trace.content() << std::endl; ensure_or_die( !m_trace.content().empty(), "trace should not be empty!" ); } }; void run_test() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< test_case_t >(); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/transfer_to_state_2/000077500000000000000000000000001523065210100252505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/transfer_to_state_2/CMakeLists.txt000066400000000000000000000001551523065210100300110ustar00rootroot00000000000000set(UNITTEST _unit.test.enveloped_msg.transfer_to_state_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/enveloped_msg/transfer_to_state_2/main.cpp000066400000000000000000000046061523065210100267060ustar00rootroot00000000000000/* * Check for enveloped message and transfer_to_state. * Another test case: handler for a message is not found in the * target state. */ #include #include #include #include #include "../common_stuff.hpp" struct hello final { std::string m_msg; }; struct shutdown final { std::string m_msg; }; class test_case_t final : public so_5::agent_t { trace_t m_trace_hello; const so_5::mbox_t m_mbox_hello; trace_t m_trace_shutdown; const so_5::mbox_t m_mbox_shutdown; state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; public : test_case_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_mbox_hello{ special_mbox_t<>::make( so_environment().create_mbox(), so_5::outliving_mutable(m_trace_hello), "hello" ) } , m_mbox_shutdown{ special_mbox_t<>::make( so_environment().create_mbox(), so_5::outliving_mutable(m_trace_shutdown), "shutdown" ) } { st_one.transfer_to_state< hello >( m_mbox_hello, st_two ); // NOTE: there is no event handler for 'hello' in st_two. st_two.event( m_mbox_shutdown, [this](mhood_t cmd) { ensure_or_die( "bye" == cmd->m_msg, "unexpected message inside shutdown: " + cmd->m_msg ); ensure_or_die( m_trace_hello.content().empty(), "content of m_trace_hello is expected to be empty: " + m_trace_hello.content() ); so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { this >>= st_one; so_5::send< hello >( m_mbox_hello, "hello" ); so_5::send< shutdown >( m_mbox_shutdown, "bye" ); } void so_evt_finish() override { std::cout << "Trace is: " << m_trace_shutdown.content() << std::endl; ensure_or_die( !m_trace_shutdown.content().empty(), "m_trace_shutdown should not be empty!" ); } }; void run_test() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< test_case_t >(); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); } int main() { try { run_with_time_limit( []() { run_test(); }, 5 ); } catch(const std::exception & ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/000077500000000000000000000000001523065210100210165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/CMakeLists.txt000066400000000000000000000006511523065210100235600ustar00rootroot00000000000000project(tests) add_subdirectory(autoname_coop) add_subdirectory(autoshutdown) add_subdirectory(autoshutdown_disabled) add_subdirectory(autoshutdown_in_init) add_subdirectory(moveable_params) add_subdirectory(reg_coop_after_stop) add_subdirectory(unknown_exception_run) add_subdirectory(unknown_exception_run_2) add_subdirectory(unknown_exception_run_3) add_subdirectory(stop_guards) add_subdirectory(default_subscr_storage) sobjectizer-5.8.6/dev/test/so_5/environment/autoname_coop/000077500000000000000000000000001523065210100236475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/autoname_coop/CMakeLists.txt000066400000000000000000000001451523065210100264070ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.autoname_coop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/autoname_coop/main.cpp000066400000000000000000000022111523065210100252730ustar00rootroot00000000000000#include #include class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env ) : so_5::agent_t( env ) {} void so_evt_start() override { so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { for( int i = 0; i < 1024; ++i ) { auto coop = env.make_coop(); coop->add_agent( std::make_unique< a_test_t >( env ) ); env.register_coop( std::move( coop ) ); env.register_agent_as_coop( std::make_unique< a_test_t >( env ) ); env.register_agent_as_coop( std::make_unique< a_test_t >( env ), so_5::disp::one_thread::make_dispatcher( env, "another" ).binder() ); std::cout << i << "\r" << std::flush; } std::cout << "Done." << std::flush; } ); std::cout << " Completed." << std::endl; }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/autoshutdown/000077500000000000000000000000001523065210100235625ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/autoshutdown/CMakeLists.txt000066400000000000000000000001441523065210100263210ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.autoshutdown) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/autoshutdown/main.cpp000066400000000000000000000023551523065210100252170ustar00rootroot00000000000000/* * A simple test for checking autoshutdown feature of SO Environment. */ #include #include #include #include #include #include #include #include #include #include struct msg_tick : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env ) : so_5::agent_t( env ) {} void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< msg_tick >) { m_ticks += 1; if( m_ticks < 15 ) so_5::send< msg_tick >( *this ); else so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send< msg_tick >( *this ); } private : unsigned int m_ticks = 0; }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { for( int i = 0; i < 1024; ++i ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/autoshutdown_disabled/000077500000000000000000000000001523065210100254115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/autoshutdown_disabled/CMakeLists.txt000066400000000000000000000001551523065210100301520ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.autoshutdown_disabled) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/autoshutdown_disabled/main.cpp000066400000000000000000000046151523065210100270470ustar00rootroot00000000000000/* * A simple test for checking disabling autoshutdown feature of SO Environment. */ #include #include #include #include #include #include #include #include #include #include std::atomic_uint agents_destroyed = ATOMIC_VAR_INIT( 0 ); std::atomic< so_5::environment_t * > environment = ATOMIC_VAR_INIT( nullptr ); const unsigned int agents_count = 1024; struct msg_tick : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public: a_test_t( so_5::environment_t & env ) : so_5::agent_t( env ) {} ~a_test_t() override { ++agents_destroyed; } void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< msg_tick >) { m_ticks += 1; if( m_ticks < 15 ) so_5::send< msg_tick >( *this ); else so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send< msg_tick >( *this ); } private : unsigned int m_ticks = 0; }; void controller() { while( agents_count != agents_destroyed.load( std::memory_order_acquire ) ) std::this_thread::yield(); std::cout << "All agents are destroyed. Take SO Environment some time..." << std::endl; std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); auto env = environment.load( std::memory_order_acquire ); if( !env ) { std::cerr << "environment cannot be nullptr!" << std::endl; std::abort(); } else std::cout << "Stopping SO Environment..." << std::endl; env->stop(); } int main() { try { run_with_time_limit( []() { std::thread controller_thread( &controller ); so_5::launch( []( so_5::environment_t & env ) { std::cout << "Starting agents..." << std::endl; environment.store( &env, std::memory_order_release ); for( unsigned int i = 0; i < agents_count; ++i ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } }, []( so_5::environment_params_t & params ) { params.disable_autoshutdown(); } ); environment.store( nullptr, std::memory_order_release ); controller_thread.join(); }, 20, "SO Environment autoshutdown disabled test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/autoshutdown_in_init/000077500000000000000000000000001523065210100252735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/autoshutdown_in_init/CMakeLists.txt000066400000000000000000000001541523065210100300330ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.autoshutdown_in_init) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/autoshutdown_in_init/main.cpp000066400000000000000000000034451523065210100267310ustar00rootroot00000000000000/* * A test for checking autoshutdown during execution of init function. */ #include #include #include #include #include #include #include #include #include #include class a_first_t : public so_5::agent_t { public : a_first_t( so_5::environment_t & env ) : so_5::agent_t( env ) {} virtual void so_evt_start() override { so_deregister_agent_coop_normally(); } }; class a_second_t : public so_5::agent_t { struct msg_timer : public so_5::signal_t {}; public : a_second_t( so_5::environment_t & env, int * receiver ) : so_5::agent_t( env ) , m_receiver( *receiver ) {} virtual void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< msg_timer >) { m_receiver = 42; so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send_delayed< msg_timer >( *this, std::chrono::milliseconds(250) ); } private : int & m_receiver; }; int main() { try { int actual = 0; run_with_time_limit( [&actual]() { so_5::launch( [&actual]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_first_t >() ); // Time for the first cooperation deregistration. std::this_thread::sleep_for( std::chrono::milliseconds( 250 ) ); env.register_agent_as_coop( env.make_agent< a_second_t >( &actual ) ); } ); }, 20, "SO Environment autoshutdown_in_run test" ); if( 42 != actual ) throw std::runtime_error( "42 != actual!" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/default_subscr_storage/000077500000000000000000000000001523065210100255475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/default_subscr_storage/CMakeLists.txt000066400000000000000000000001561523065210100303110ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.default_subscr_storage) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/default_subscr_storage/main.cpp000066400000000000000000000035021523065210100271770ustar00rootroot00000000000000/* * A simple test for checking autoshutdown feature of SO Environment. */ #include #include #include #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public: a_test_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) {} a_test_t( context_t ctx, so_5::subscription_storage_factory_t factory ) : so_5::agent_t( ctx + factory ) {} void so_evt_start() override { so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { std::atomic< int > factory_call_counter{}; so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( // Will use default storage subscription factory. env.make_agent< a_test_t >() ); env.register_agent_as_coop( // Will use explicitly specified storage subscription_storage_factory, env.make_agent< a_test_t >( so_5::flat_set_based_subscription_storage_factory( 1u ) ) ); }, [&]( so_5::environment_params_t & params ) { params.default_subscription_storage_factory( [&factory_call_counter]() { ++factory_call_counter; return so_5::default_subscription_storage_factory()(); } ); } ); ensure_or_die( 1 == factory_call_counter.load( std::memory_order_acquire ), "custom default subscription storage factory must be called " "exactly once" ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/moveable_params/000077500000000000000000000000001523065210100241535ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/moveable_params/CMakeLists.txt000066400000000000000000000001471523065210100267150ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.moveable_params) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/moveable_params/main.cpp000066400000000000000000000055121523065210100256060ustar00rootroot00000000000000/* Test of moveabillity of environment_params_t. */ #include #include so_5::environment_params_t make_param() { so_5::environment_params_t params; return params; } UT_UNIT_TEST( environment_params ) { so_5::environment_params_t param = make_param(); } UT_UNIT_TEST( default_disp_params_on_move_ctor ) { so_5::environment_params_t params; { { const auto & disp_params = params.default_disp_params(); UT_CHECK_EQ( 0u, disp_params.index() ); const auto & one_thread_params = std::get<0>( disp_params ); UT_CHECK_EQ( true, so_5::work_thread_activity_tracking_t::unspecified == one_thread_params.work_thread_activity_tracking() ); } params.default_disp_params( so_5::disp::one_thread::disp_params_t{} .turn_work_thread_activity_tracking_on() ); { const auto & disp_params = params.default_disp_params(); UT_CHECK_EQ( 0u, disp_params.index() ); const auto & one_thread_params = std::get<0>( disp_params ); UT_CHECK_EQ( true, so_5::work_thread_activity_tracking_t::on == one_thread_params.work_thread_activity_tracking() ); } } so_5::environment_params_t p2{ std::move(params) }; { const auto & disp_params = p2.default_disp_params(); UT_CHECK_EQ( 0u, disp_params.index() ); const auto & one_thread_params = std::get<0>( disp_params ); UT_CHECK_EQ( true, so_5::work_thread_activity_tracking_t::on == one_thread_params.work_thread_activity_tracking() ); } } UT_UNIT_TEST( default_disp_params_on_move_op ) { so_5::environment_params_t params; { { const auto & disp_params = params.default_disp_params(); UT_CHECK_EQ( 0u, disp_params.index() ); const auto & one_thread_params = std::get<0>( disp_params ); UT_CHECK_EQ( true, so_5::work_thread_activity_tracking_t::unspecified == one_thread_params.work_thread_activity_tracking() ); } params.default_disp_params( so_5::disp::one_thread::disp_params_t{} .turn_work_thread_activity_tracking_on() ); { const auto & disp_params = params.default_disp_params(); UT_CHECK_EQ( 0u, disp_params.index() ); const auto & one_thread_params = std::get<0>( disp_params ); UT_CHECK_EQ( true, so_5::work_thread_activity_tracking_t::on == one_thread_params.work_thread_activity_tracking() ); } } so_5::environment_params_t p2; p2 = std::move(params); { const auto & disp_params = p2.default_disp_params(); UT_CHECK_EQ( 0u, disp_params.index() ); const auto & one_thread_params = std::get<0>( disp_params ); UT_CHECK_EQ( true, so_5::work_thread_activity_tracking_t::on == one_thread_params.work_thread_activity_tracking() ); } } int main() { UT_RUN_UNIT_TEST( environment_params ) UT_RUN_UNIT_TEST( default_disp_params_on_move_ctor ) UT_RUN_UNIT_TEST( default_disp_params_on_move_op ) return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/reg_coop_after_stop/000077500000000000000000000000001523065210100250415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/reg_coop_after_stop/CMakeLists.txt000066400000000000000000000001531523065210100276000ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.reg_coop_after_stop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/reg_coop_after_stop/main.cpp000066400000000000000000000050271523065210100264750ustar00rootroot00000000000000/* * A test for checking autoshutdown during execution of init function. */ #include #include #include #include #include #include #include #include #include #include #include class log_t { public : void append( const std::string & what ) { std::lock_guard< std::mutex > l( m_lock ); m_value += what; } std::string get() { std::lock_guard< std::mutex > l( m_lock ); return m_value; } private : std::mutex m_lock; std::string m_value; }; class a_second_t : public so_5::agent_t { struct msg_timer : public so_5::signal_t {}; public : a_second_t( so_5::environment_t & env, log_t & log ) : so_5::agent_t( env ) , m_log( log ) {} virtual void so_evt_start() override { m_log.append( "s.start;" ); std::this_thread::sleep_for( std::chrono::milliseconds( 200 ) ); } virtual void so_evt_finish() override { m_log.append( "s.finish;" ); } private : log_t & m_log; }; class a_first_t : public so_5::agent_t { public : a_first_t( so_5::environment_t & env, log_t & log ) : so_5::agent_t( env ) , m_log( log ) {} virtual void so_evt_start() override { m_log.append( "f.start;" ); std::this_thread::sleep_for( std::chrono::milliseconds( 200 ) ); so_environment().stop(); m_log.append( "env.stop;" ); try { so_environment().register_agent_as_coop( so_environment().make_agent< a_second_t >( m_log ), so_5::disp::active_obj::make_dispatcher( so_environment() ).binder() ); } catch( const so_5::exception_t & x ) { std::ostringstream s; s << "exception(" << x.error_code() << ");"; m_log.append( s.str() ); } } virtual void so_evt_finish() override { m_log.append( "f.finish;" ); } private : log_t & m_log; }; int main() { try { log_t log; run_with_time_limit( [&log]() { so_5::launch( [&log]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_first_t >( log ) ); } ); }, 20, "SO Environment reg_coop_after_stop test" ); const std::string expected = "f.start;env.stop;exception(28);f.finish;"; const std::string actual = log.get(); if( expected != actual ) throw std::runtime_error( expected + " != " + actual ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/000077500000000000000000000000001523065210100233505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/CMakeLists.txt000066400000000000000000000004171523065210100261120ustar00rootroot00000000000000project(tests) add_subdirectory(parallel_remove) add_subdirectory(parallel_remove_50k) add_subdirectory(simple) add_subdirectory(some_actions_after_stop) add_subdirectory(stop_in_progress_nothrow) add_subdirectory(stop_in_progress_throw) add_subdirectory(parallel_stop) sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/parallel_remove/000077500000000000000000000000001523065210100265215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/parallel_remove/CMakeLists.txt000066400000000000000000000001631523065210100312610ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.stop_guards.parallel_remove) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/parallel_remove/main.cpp000066400000000000000000000053001523065210100301470ustar00rootroot00000000000000/* * A test for parallel remove of stop_guards. */ #include #include #include #include using namespace std; struct shutdown_started final : public so_5::signal_t {}; class second_stop_guard_t final : public so_5::stop_guard_t , public std::enable_shared_from_this { public : second_stop_guard_t() {} virtual void stop() noexcept override { std::this_thread::sleep_for( std::chrono::milliseconds(50) ); } }; const std::size_t N = 4; class first_worker_t final : public so_5::agent_t { public : struct worker_started final : public so_5::signal_t {}; first_worker_t( context_t ctx, so_5::mbox_t notify_mbox ) : so_5::agent_t( std::move(ctx) ) , m_notify_mbox( std::move(notify_mbox) ) { so_subscribe_self() .event( &first_worker_t::on_worker_started ); } private : const so_5::mbox_t m_notify_mbox; std::size_t m_active_workers = 0; void on_worker_started( mhood_t< worker_started > ) { ++m_active_workers; if( m_active_workers >= N ) { so_5::send< shutdown_started >( m_notify_mbox ); so_environment().stop(); } } }; class second_worker_t final : public so_5::agent_t { public : second_worker_t( context_t ctx, so_5::mbox_t manager_mbox, so_5::mbox_t notify_mbox ) : so_5::agent_t( std::move(ctx) ) , m_manager_mbox( std::move(manager_mbox) ) { so_subscribe( notify_mbox ).event( &second_worker_t::on_shutdown_started ); } virtual void so_evt_start() override { m_guard = std::make_shared< second_stop_guard_t >(); so_environment().setup_stop_guard( m_guard ); so_5::send< first_worker_t::worker_started >( m_manager_mbox ); } private : const so_5::mbox_t m_manager_mbox; std::shared_ptr< second_stop_guard_t > m_guard; void on_shutdown_started( mhood_t ) { so_environment().remove_stop_guard( m_guard ); } }; void make_stuff( so_5::environment_t & env ) { auto notify_mbox = env.create_mbox(); env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), [&]( so_5::coop_t & coop ) { auto first = coop.make_agent< first_worker_t >( notify_mbox ); for( std::size_t i = 0; i != N; ++i ) coop.make_agent< second_worker_t >( first->so_direct_mbox(), notify_mbox ); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { make_stuff( env ); }, [](so_5::environment_params_t & params) { (void)params; } ); }, 5 ); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/parallel_remove_50k/000077500000000000000000000000001523065210100272005ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/parallel_remove_50k/CMakeLists.txt000066400000000000000000000001671523065210100317440ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.stop_guards.parallel_remove_50k) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/parallel_remove_50k/main.cpp000066400000000000000000000070531523065210100306350ustar00rootroot00000000000000/* * A test for parallel remove of 5K stop_guards. * * NOTE: count of agents is reduced to 5K because it takes * to much time inside virtual machines. */ #include #include #include #include #include using namespace std; int random( int l, int r ) { std::default_random_engine engine; return std::uniform_int_distribution( l, r )( engine ); } struct shutdown_started final : public so_5::signal_t {}; class second_stop_guard_t final : public so_5::stop_guard_t , public std::enable_shared_from_this { public : struct remove_me final : public so_5::signal_t {}; second_stop_guard_t( so_5::mbox_t owner ) : m_owner( std::move(owner) ) {} virtual void stop() noexcept override { so_5::send< remove_me >( m_owner ); } private : const so_5::mbox_t m_owner; }; const std::size_t N = 50000u; class first_worker_t final : public so_5::agent_t { public : struct worker_started final : public so_5::signal_t {}; first_worker_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self() .event( &first_worker_t::on_worker_started ); } virtual void so_evt_finish() override { const auto finish_at = std::chrono::steady_clock::now(); std::cout << "stop completed in: " << std::chrono::duration_cast< std::chrono::milliseconds >( finish_at - m_started_at ).count() << "ms" << std::endl; } private : std::size_t m_active_workers = 0; std::chrono::steady_clock::time_point m_started_at; void on_worker_started( mhood_t< worker_started > ) { ++m_active_workers; if( m_active_workers >= N ) { m_started_at = std::chrono::steady_clock::now(); so_environment().stop(); } } }; class second_worker_t final : public so_5::agent_t { struct do_init final : public so_5::signal_t {}; public : second_worker_t( context_t ctx, so_5::mbox_t manager_mbox ) : so_5::agent_t( std::move(ctx) ) , m_manager_mbox( std::move(manager_mbox) ) { so_subscribe_self() .event( &second_worker_t::on_do_init ) .event( &second_worker_t::on_remove_me ); } virtual void so_evt_start() override { so_5::send_delayed< do_init >( *this, std::chrono::milliseconds( random( 1, 50 ) ) ); } private : const so_5::mbox_t m_manager_mbox; std::shared_ptr< second_stop_guard_t > m_guard; void on_do_init( mhood_t ) { m_guard = std::make_shared< second_stop_guard_t >( so_direct_mbox() ); so_environment().setup_stop_guard( m_guard ); so_5::send< first_worker_t::worker_started >( m_manager_mbox ); } void on_remove_me( mhood_t ) { so_environment().remove_stop_guard( m_guard ); } }; void make_stuff( so_5::environment_t & env ) { namespace tpdisp = so_5::disp::thread_pool; auto notify_mbox = env.create_mbox(); env.introduce_coop( tpdisp::make_dispatcher( env ).binder( tpdisp::bind_params_t().fifo( tpdisp::fifo_t::individual ) ), [&]( so_5::coop_t & coop ) { auto first = coop.make_agent< first_worker_t >(); for( std::size_t i = 0; i != N; ++i ) coop.make_agent< second_worker_t >( first->so_direct_mbox() ); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { make_stuff( env ); }, [](so_5::environment_params_t & params) { (void)params; } ); }, 600 ); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/parallel_stop/000077500000000000000000000000001523065210100262115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/parallel_stop/CMakeLists.txt000066400000000000000000000001611523065210100307470ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.stop_guards.parallel_stop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/parallel_stop/main.cpp000066400000000000000000000075551523065210100276550ustar00rootroot00000000000000/* * A test for parallel shutdown of SOEnv in presence of stop guards. */ #include #include #include #include using namespace std; namespace test { // Test stop_guard. // Its goal is to suspend the current thread in stop() for some time. class my_stop_guard_t final : public so_5::stop_guard_t , public std::enable_shared_from_this { std::atomic m_counter; public : my_stop_guard_t() = default; void stop() noexcept override { ++m_counter; // Suspend the current thread. std::cout << "my_stop_guard_t::stop suspending the current thread..." << std::endl; std::this_thread::sleep_for( std::chrono::milliseconds{ 500 } ); std::cout << "my_stop_guard_t::stop resuming the current thread..." << std::endl; } [[nodiscard]] int counter() const noexcept { return m_counter.load( std::memory_order_acquire ); } }; class worker_t final : public so_5::agent_t { public : struct worker_started final : public so_5::signal_t {}; struct start_shutdown final : public so_5::signal_t {}; struct stop_called final : public so_5::signal_t {}; worker_t( context_t ctx, so_5::mbox_t notify_mbox ) : so_5::agent_t( std::move(ctx) ) , m_notify_mbox( std::move(notify_mbox) ) {} void so_define_agent() override { so_subscribe( m_notify_mbox ).event( &worker_t::evt_start_shutdown ); } void so_evt_start() override { so_5::send< worker_started >( m_notify_mbox ); } private : const so_5::mbox_t m_notify_mbox; void evt_start_shutdown( mhood_t< start_shutdown > ) { so_environment().stop(); so_5::send< stop_called >( m_notify_mbox ); } }; class coordinator_t final : public so_5::agent_t { public: coordinator_t( context_t ctx, std::size_t total_workers, so_5::mbox_t notify_mbox, std::shared_ptr< my_stop_guard_t > stop_guard ) : so_5::agent_t{ std::move(ctx) } , m_total_workers{ total_workers } , m_notify_mbox{ std::move(notify_mbox) } , m_stop_guard{ std::move(stop_guard) } { } void so_define_agent() override { so_subscribe( m_notify_mbox ) .event( &coordinator_t::evt_worker_started ) .event( &coordinator_t::evt_stop_called ) ; } void so_evt_start() override { so_environment().setup_stop_guard( m_stop_guard ); } private: const std::size_t m_total_workers; const so_5::mbox_t m_notify_mbox; const std::shared_ptr< my_stop_guard_t > m_stop_guard; std::size_t m_workers_started{}; std::size_t m_workers_stopped{}; void evt_worker_started( mhood_t< worker_t::worker_started > ) { ++m_workers_started; if( m_total_workers == m_workers_started ) so_5::send< worker_t::start_shutdown >( m_notify_mbox ); } void evt_stop_called( mhood_t< worker_t::stop_called > ) { ++m_workers_stopped; if( m_total_workers == m_workers_stopped ) so_environment().remove_stop_guard( m_stop_guard ); } }; void do_test() { auto sg_1 = std::make_shared< my_stop_guard_t >(); so_5::launch( [&]( so_5::environment_t & env ) { auto notify_mbox = env.create_mbox(); env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), [&]( so_5::coop_t & coop ) { coop.make_agent< coordinator_t >( 3u, notify_mbox, sg_1 ); coop.make_agent< worker_t >( notify_mbox ); coop.make_agent< worker_t >( notify_mbox ); coop.make_agent< worker_t >( notify_mbox ); } ); }, []( so_5::environment_params_t & /*params*/ ) { // params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); ensure_or_die( 1 == sg_1->counter(), "invalid value for sg_1" ); } } /* namespace test */ using namespace test; int main() { try { run_with_time_limit( []() { do_test(); }, 5 ); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/simple/000077500000000000000000000000001523065210100246415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/simple/CMakeLists.txt000066400000000000000000000001521523065210100273770ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.stop_guards.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/simple/main.cpp000066400000000000000000000025711523065210100262760ustar00rootroot00000000000000/* * A test for simple stop_guard. */ #include #include #include #include using namespace std; class dummy_stop_guard_t : public so_5::stop_guard_t , public std::enable_shared_from_this { public : dummy_stop_guard_t( so_5::environment_t & env ) : m_env(env) {} std::shared_ptr< dummy_stop_guard_t > shptr() { return shared_from_this(); } virtual void stop() noexcept override { m_env.remove_stop_guard( shptr() ); } private : so_5::environment_t & m_env; }; void make_stuff( so_5::environment_t & env ) { auto guard = std::make_shared< dummy_stop_guard_t >( std::ref(env) ); env.setup_stop_guard( guard ); class actor_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; void so_evt_start() override { so_environment().stop(); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< actor_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { make_stuff( env ); }, [](so_5::environment_params_t & params) { (void)params; } ); }, 5 ); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/some_actions_after_stop/000077500000000000000000000000001523065210100302615ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/some_actions_after_stop/CMakeLists.txt000066400000000000000000000001731523065210100330220ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.stop_guards.some_actions_after_stop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/some_actions_after_stop/main.cpp000066400000000000000000000044711523065210100317170ustar00rootroot00000000000000/* * A test for simple stop_guard. */ #include #include #include #include using namespace std; using namespace std::chrono_literals; class empty_stop_guard_t : public so_5::stop_guard_t , public std::enable_shared_from_this { public : empty_stop_guard_t() {} virtual void stop() noexcept override {} }; void make_stuff( so_5::environment_t & env, so_5::outliving_reference_t< bool > step_3_completed ) { auto guard = std::make_shared< empty_stop_guard_t >(); env.setup_stop_guard( guard ); class actor_t final : public so_5::agent_t { so_5::outliving_reference_t< bool > m_step_3_completed; so_5::stop_guard_shptr_t m_guard; struct step_1 final : public so_5::signal_t {}; struct step_2 final : public so_5::signal_t {}; struct step_3 final : public so_5::signal_t {}; public : actor_t( context_t ctx, so_5::outliving_reference_t< bool > step_3_completed, so_5::stop_guard_shptr_t guard ) : so_5::agent_t{ std::move(ctx) } , m_step_3_completed{ step_3_completed } , m_guard{ std::move(guard) } { so_subscribe_self() .event( [this]( so_5::mhood_t ) { so_5::send_delayed< step_2 >( *this, 50ms ); } ) .event( [this]( so_5::mhood_t ) { so_5::send_delayed< step_3 >( *this, 50ms ); } ) .event( [this]( so_5::mhood_t ) { m_step_3_completed.get() = true; so_environment().remove_stop_guard( m_guard ); } ); } void so_evt_start() override { so_environment().stop(); so_5::send_delayed< step_1 >( *this, 50ms ); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< actor_t >( step_3_completed, guard ); } ); } int main() { try { run_with_time_limit( []() { bool step_3_completed = false; so_5::launch( [&](so_5::environment_t & env) { make_stuff( env, so_5::outliving_mutable( step_3_completed ) ); }, [](so_5::environment_params_t & params) { (void)params; } ); if( !step_3_completed ) throw std::runtime_error( "step 3 is not completed" ); }, 5 ); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/stop_in_progress_nothrow/000077500000000000000000000000001523065210100305275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/stop_in_progress_nothrow/CMakeLists.txt000066400000000000000000000001741523065210100332710ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.stop_guards.stop_in_progress_nothrow) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/stop_in_progress_nothrow/main.cpp000066400000000000000000000030271523065210100321610ustar00rootroot00000000000000/* * A test for adding stop_guard when stop in progress. * A negative error code is expected. */ #include #include #include #include using namespace std; class empty_stop_guard_t : public so_5::stop_guard_t , public std::enable_shared_from_this { public : empty_stop_guard_t() {} virtual void stop() noexcept override {} }; void make_stuff( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_environment().stop(); const auto r = so_environment().setup_stop_guard( std::make_shared< empty_stop_guard_t >(), so_5::stop_guard_t::what_if_stop_in_progress_t::return_negative_result ); if( so_5::stop_guard_t::setup_result_t::stop_already_in_progress != r ) throw std::runtime_error( "result stop_already_in_progress is expected when " "a stop_guard is added when stop is in progress" ); } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< actor_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { make_stuff( env ); }, [](so_5::environment_params_t & params) { (void)params; } ); }, 5 ); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/stop_in_progress_throw/000077500000000000000000000000001523065210100301725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/stop_in_progress_throw/CMakeLists.txt000066400000000000000000000001721523065210100327320ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.stop_guards.stop_in_progress_throw) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/stop_guards/stop_in_progress_throw/main.cpp000066400000000000000000000030021523065210100316150ustar00rootroot00000000000000/* * A test for adding stop_guard when stop in progress. * An exception is expected by default. */ #include #include #include #include using namespace std; class empty_stop_guard_t : public so_5::stop_guard_t , public std::enable_shared_from_this { public : empty_stop_guard_t() {} virtual void stop() noexcept override {} }; void make_stuff( so_5::environment_t & env ) { class actor_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { so_environment().stop(); try { so_environment().setup_stop_guard( std::make_shared< empty_stop_guard_t >() ); throw std::runtime_error( "an exception is expected when " "a stop_guard is added when stop is in progress" ); } catch( const so_5::exception_t & ex ) { if( so_5::rc_cannot_set_stop_guard_when_stop_is_started != ex.error_code() ) throw; } } }; env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< actor_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { make_stuff( env ); }, [](so_5::environment_params_t & params) { (void)params; } ); }, 5 ); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/unknown_exception_run/000077500000000000000000000000001523065210100254575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/unknown_exception_run/CMakeLists.txt000066400000000000000000000001461523065210100302200ustar00rootroot00000000000000set(UNITTEST _unit.test.coop.unknown_exception_run) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/unknown_exception_run/main.cpp000066400000000000000000000017441523065210100271150ustar00rootroot00000000000000/* * Test for unknown exception from init function. */ #include #include class test_agent_t final : public so_5::agent_t { public : test_agent_t( context_t ctx ) : agent_t{ std::move(ctx) } {} void so_evt_start() override { so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { try { so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); throw( "boom!" ); } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch"; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/unknown_exception_run_2/000077500000000000000000000000001523065210100257005ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/unknown_exception_run_2/CMakeLists.txt000066400000000000000000000001571523065210100304430ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.unknown_exception_run_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/unknown_exception_run_2/main.cpp000066400000000000000000000032241523065210100273310ustar00rootroot00000000000000/* * A test for unknown exception from init_fn function. */ #include #include #include using namespace std; class coop_resource_t { so_5::atomic_counter_t & m_counter; public : coop_resource_t( so_5::atomic_counter_t & counter ) : m_counter{ counter } { ++m_counter; } ~coop_resource_t() noexcept { --m_counter; } }; int main() { try { run_with_time_limit( []() { so_5::atomic_counter_t counter{ 0 }; try { so_5::launch( [&]( so_5::environment_t & env ) { // Create a bunch of empty coops. for( int i = 0; i != 10000; ++i ) { auto coop = env.make_coop(); coop->take_under_control( std::make_unique< coop_resource_t >( std::ref(counter) ) ); auto id = env.register_coop( std::move(coop) ); // Some of them will be deregistered. if( 0 == (i % 3) ) env.deregister_coop( id, so_5::dereg_reason::normal ); } throw "boom!"; } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch"; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } const auto actual_counter = counter.load(); ensure_or_die( 0u == actual_counter, "expected value of counter is 0, actual=" + std::to_string(actual_counter) ); }, 5 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/environment/unknown_exception_run_3/000077500000000000000000000000001523065210100257015ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/environment/unknown_exception_run_3/CMakeLists.txt000066400000000000000000000001571523065210100304440ustar00rootroot00000000000000set(UNITTEST _unit.test.environment.unknown_exception_run_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/environment/unknown_exception_run_3/main.cpp000066400000000000000000000051771523065210100273430ustar00rootroot00000000000000/* * A test for unknown exception from init_fn function. */ #include #include #include using namespace std; class coop_resource_t { so_5::atomic_counter_t & m_counter; public : coop_resource_t( so_5::atomic_counter_t & counter ) : m_counter{ counter } { ++m_counter; } ~coop_resource_t() noexcept { --m_counter; } }; class dummy_actor_t final : public so_5::agent_t { struct next_turn final : public so_5::signal_t {}; so_5::atomic_counter_t & m_finish_counter; public : dummy_actor_t( context_t ctx, so_5::atomic_counter_t & finish_counter ) : so_5::agent_t::agent_t{ std::move(ctx) } , m_finish_counter{ finish_counter } {} void so_evt_start() override { so_subscribe_self().event( &dummy_actor_t::on_next_turn ); so_5::send< next_turn >( *this ); } void so_evt_finish() override { ++m_finish_counter; } private : void on_next_turn( mhood_t ) { so_5::send_delayed< next_turn >( *this, std::chrono::milliseconds(10) ); } }; int main() { try { run_with_time_limit( []() { so_5::atomic_counter_t counter{ 0 }; so_5::atomic_counter_t finish_counter{ 0 }; try { so_5::launch( [&]( so_5::environment_t & env ) { // Create a bunch of coops with dummy_actors inside. for( int i = 0; i != 10000; ++i ) { auto coop = env.make_coop(); coop->take_under_control( std::make_unique< coop_resource_t >( std::ref(counter) ) ); coop->make_agent< dummy_actor_t >( std::ref(finish_counter) ); auto id = env.register_coop( std::move(coop) ); // Some of them will be deregistered. if( 0 == (i % 3) ) env.deregister_coop( id, so_5::dereg_reason::normal ); } throw "boom!"; } ); // An exception should be thrown from so_5::launch. std::cout << "We expect an exception from launch"; std::abort(); } catch( const so_5::exception_t & x ) { std::cout << "Exception is caught: " << x.what() << std::endl; } const auto actual_counter = counter.load(); ensure_or_die( 0u == actual_counter, "expected value of counter is 0, actual=" + std::to_string(actual_counter) ); const auto actual_finish_counter = finish_counter.load(); ensure_or_die( 10000u == actual_finish_counter, "unexpected finish_counter=" + std::to_string(actual_finish_counter) ); }, 300 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/000077500000000000000000000000001523065210100212705ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/CMakeLists.txt000066400000000000000000000024061523065210100240320ustar00rootroot00000000000000add_subdirectory(subscribe_errors) add_subdirectory(ignore_exception) add_subdirectory(exception_reaction_inheritance) add_subdirectory(unknown_exception_evt_handler) add_subdirectory(unknown_exception_evt_start) add_subdirectory(unknown_exception_evt_finish) add_subdirectory(subscribe_before_reg) add_subdirectory(drop_subscr_in_lambda_event_handler) add_subdirectory(has_subscription_1) add_subdirectory(has_subscription_2) add_subdirectory(has_subscription_3) add_subdirectory(has_subscription_4) add_subdirectory(has_subscription_5) add_subdirectory(has_subscription_6) add_subdirectory(has_subscription_7) add_subdirectory(has_subscription_8) add_subdirectory(external_subscription) add_subdirectory(deadletter_handler_simple) add_subdirectory(deadletter_handler_unsubscribe) add_subdirectory(deadletter_handler_unsubscribe_all_states) add_subdirectory(deadletter_handler_has_handler) add_subdirectory(deadletter_handler_formats) add_subdirectory(deactivate_agent_simple) add_subdirectory(deactivate_try_resubscribe) add_subdirectory(deactivate_agent_evt_finish) add_subdirectory(drop_all_subscriptions_simple) add_subdirectory(drop_all_subscriptions_evt_finish) add_subdirectory(thread_safety_check) add_subdirectory(as_event_handler) add_subdirectory(skip_demands_on_dereg) sobjectizer-5.8.6/dev/test/so_5/event_handler/as_event_handler/000077500000000000000000000000001523065210100245715ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/as_event_handler/CMakeLists.txt000066400000000000000000000001521523065210100273270ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.as_event_handler) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/as_event_handler/main.cpp000066400000000000000000000066551523065210100262350ustar00rootroot00000000000000/* * A test for checking of running a block of code like non-thread-safe * event handler. */ #include #include #include namespace test { using custom_demand_handler_t = std::function< void() >; class custom_demand_scheduler_t { public: virtual ~custom_demand_scheduler_t() = default; virtual void schedule( custom_demand_handler_t handler ) = 0; }; class tricky_disp_t final : public so_5::disp_binder_t , public so_5::event_queue_t , public custom_demand_scheduler_t { struct custom_demand_t { custom_demand_handler_t m_handler; }; so_5::mchain_t m_demands; std::thread m_worker_thread; public: tricky_disp_t( so_5::environment_t & env ) : m_demands{ so_5::create_mchain(env) } { m_worker_thread = std::thread{ [this]() { body(); } }; } ~tricky_disp_t() noexcept override { so_5::close_retain_content( so_5::terminate_if_throws, m_demands ); m_worker_thread.join(); } void schedule( custom_demand_handler_t handler ) override { so_5::send< custom_demand_t >( m_demands, std::move(handler) ); } private: void body() { const auto this_thread_id = so_5::query_current_thread_id(); so_5::receive( so_5::from( m_demands ).handle_all(), [this_thread_id]( so_5::mutable_mhood_t< so_5::execution_demand_t > d ) { d->call_handler( this_thread_id ); }, []( const custom_demand_t & d ) { d.m_handler(); } ); } void preallocate_resources( so_5::agent_t & ) override {} void undo_preallocation( so_5::agent_t & ) noexcept override {} void bind( so_5::agent_t & agent ) noexcept override { agent.so_bind_to_dispatcher( *this ); } void unbind( so_5::agent_t & ) noexcept override {} void push( so_5::execution_demand_t demand ) override { so_5::send< so_5::mutable_msg< so_5::execution_demand_t > >( m_demands, std::move(demand) ); } void push_evt_start( so_5::execution_demand_t demand ) override { this->push( std::move(demand) ); } void push_evt_finish( so_5::execution_demand_t demand ) noexcept override { this->push( std::move(demand) ); } }; class a_test_t final : public so_5::agent_t { struct msg_stop final : public so_5::signal_t {}; custom_demand_scheduler_t & m_scheduler; state_t st_waiting{ this, "waiting" }; state_t st_ready{ this, "ready" }; public: a_test_t( context_t ctx, custom_demand_scheduler_t & scheduler ) : so_5::agent_t{ std::move(ctx) } , m_scheduler{ scheduler } {} void so_define_agent() override { st_ready .event( [this]( mhood_t< msg_stop > ) { so_deregister_agent_coop_normally(); } ) ; st_waiting.activate(); } void so_evt_start() override { m_scheduler.schedule( [this]() { so_low_level_exec_as_event_handler( [this]() { st_ready.activate(); so_5::send< msg_stop >( *this ); } ); } ); } }; } /* namespace test */ int main() { using namespace test; try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto tricky_disp = std::make_shared< tricky_disp_t >( coop.environment() ); coop.make_agent_with_binder< a_test_t >( tricky_disp, *tricky_disp ); } ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/deactivate_agent_evt_finish/000077500000000000000000000000001523065210100267755ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/deactivate_agent_evt_finish/CMakeLists.txt000066400000000000000000000001651523065210100315370ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.deactivate_agent_evt_finish) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/deactivate_agent_evt_finish/main.cpp000066400000000000000000000040641523065210100304310ustar00rootroot00000000000000/* * A test for so_evt_finish() after so_deactivate_agent(). */ #include #include #include class a_terminator_t final : public so_5::agent_t { public: struct kill final : public so_5::signal_t {}; using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe( so_environment().create_mbox( "terminator" ) ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } }; class a_test_t final : public so_5::agent_t { struct first final : public so_5::signal_t {}; struct second final : public so_5::signal_t {}; std::atomic_bool & m_evt_finish_called_flag; public: a_test_t( context_t ctx, std::atomic_bool & evt_finish_called_flag ) : so_5::agent_t{ std::move(ctx) } , m_evt_finish_called_flag{ evt_finish_called_flag } {} void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t ) { so_5::send< a_terminator_t::kill >( so_environment().create_mbox( "terminator" ) ); so_deactivate_agent(); } ) .event( []( mhood_t ) { throw std::runtime_error{ "second message received" }; } ); } void so_evt_start() override { m_evt_finish_called_flag = false; so_5::send< first >( *this ); so_5::send< second >( *this ); } void so_evt_finish() override { m_evt_finish_called_flag = true; } }; int main() { try { run_with_time_limit( []() { std::atomic_bool evt_finish_called{ false }; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_terminator_t >(); coop.make_agent< a_test_t >( std::ref(evt_finish_called) ); } ); } ); ensure_or_die( evt_finish_called.load( std::memory_order_acquire ), "evt_finish_called is expected to be 'true'" ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/deactivate_agent_simple/000077500000000000000000000000001523065210100261305ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/deactivate_agent_simple/CMakeLists.txt000066400000000000000000000001611523065210100306660ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.deactivate_agent_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/deactivate_agent_simple/main.cpp000066400000000000000000000032111523065210100275550ustar00rootroot00000000000000/* * A simple test for so_deactivate_agent(). */ #include #include #include class a_terminator_t final : public so_5::agent_t { public: struct kill final : public so_5::signal_t {}; using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe( so_environment().create_mbox( "terminator" ) ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } }; class a_test_t final : public so_5::agent_t { struct first final : public so_5::signal_t {}; struct second final : public so_5::signal_t {}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t ) { so_5::send< a_terminator_t::kill >( so_environment().create_mbox( "terminator" ) ); so_deactivate_agent(); ensure_or_die(!so_default_state().is_active(), "agent should not be in the default_state"); } ) .event( []( mhood_t ) { throw std::runtime_error{ "second message received" }; } ); } void so_evt_start() override { so_5::send< first >( *this ); so_5::send< second >( *this ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_terminator_t >(); coop.make_agent< a_test_t >(); } ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/deactivate_try_resubscribe/000077500000000000000000000000001523065210100266675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/deactivate_try_resubscribe/CMakeLists.txt000066400000000000000000000001641523065210100314300ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.deactivate_try_resubscribe) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/deactivate_try_resubscribe/main.cpp000066400000000000000000000047471523065210100303330ustar00rootroot00000000000000/* * A test for subscription after so_deactivate_agent(). */ #include #include #include class a_terminator_t final : public so_5::agent_t { public: struct kill final : public so_5::signal_t {}; using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe( so_environment().create_mbox( "terminator" ) ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } }; class a_test_t final : public so_5::agent_t { struct first final : public so_5::signal_t {}; struct dummy_msg final : public so_5::message_t { int m_key; dummy_msg( int key ) : m_key{key} {} }; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_test_t::evt_first ) ; } void so_evt_start() override { so_5::send< first >( *this ); } private: void evt_first( mhood_t ) { so_5::send< a_terminator_t::kill >( so_environment().create_mbox( "terminator" ) ); so_deactivate_agent(); if( so_has_subscription(so_direct_mbox()) ) throw std::runtime_error{ "subscription isn't dropped" }; ensure_throws( [this]() { so_subscribe_self().event( &a_test_t::evt_first ); }, "resubscription completed successfully" ); ensure_throws( [this]() { so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::evt_first ); }, "deadletter setup completed successfully" ); ensure_throws( [this]() { so_set_delivery_filter( so_environment().create_mbox( "dummy" ), []( const dummy_msg & msg ) { return msg.m_key > 0; } ); }, "delivery_filter setup completed successfully" ); } template< typename Lambda > void ensure_throws( Lambda && lambda, const char * failure_description ) { bool exception_thrown = false; try { lambda(); } catch( const so_5::exception_t & ) { exception_thrown = true; } if( !exception_thrown ) throw std::runtime_error{ failure_description }; } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_terminator_t >(); coop.make_agent< a_test_t >(); } ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_common.hpp000066400000000000000000000014071523065210100271650ustar00rootroot00000000000000#pragma once #include class direct_mbox_case_t { const so_5::agent_t & m_owner; public : direct_mbox_case_t( const so_5::agent_t & owner ) : m_owner(owner) {} const so_5::mbox_t & mbox() const noexcept { return m_owner.so_direct_mbox(); } }; class mpmc_mbox_case_t { const so_5::mbox_t m_mbox; public: mpmc_mbox_case_t( const so_5::agent_t & owner ) : m_mbox( owner.so_environment().create_mbox() ) {} const so_5::mbox_t & mbox() const noexcept { return m_mbox; } }; template< typename Mbox_Case, typename Msg_Type, template class Test_Agent > void introduce_test_agent( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< Test_Agent >(); } ); } sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_formats/000077500000000000000000000000001523065210100266355ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_formats/CMakeLists.txt000066400000000000000000000001641523065210100313760ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.deadletter_handler_formats) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_formats/main.cpp000066400000000000000000000075121523065210100302720ustar00rootroot00000000000000/* * A test for formats of deadletter handlers. */ #include #include #include struct msg_1 final : public so_5::message_t {}; struct msg_2 final : public so_5::message_t {}; struct msg_3 final : public so_5::message_t {}; struct msg_4 final : public so_5::message_t {}; struct msg_5 final : public so_5::message_t {}; struct msg_6 final : public so_5::message_t {}; struct msg_7 final : public so_5::message_t {}; struct msg_8 final : public so_5::message_t {}; struct msg_9 final : public so_5::message_t {}; struct msg_10 final : public so_5::message_t {}; struct msg_11 final : public so_5::message_t {}; struct msg_12 final : public so_5::message_t {}; class a_test_t final : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) {} virtual void so_define_agent() override { so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_1 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_2 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_3 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_4 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_5 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_6 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_7 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_8 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_9 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_10 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_11 ); so_subscribe_deadletter_handler( so_direct_mbox(), &a_test_t::on_msg_12 ); } virtual void so_evt_start() override { so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send>( *this ); so_deregister_agent_coop_normally(); } virtual void so_evt_finish() override { ensure_or_die( m_received == expected, "received != expected, received=" + std::to_string(m_received) + ", expected=" + std::to_string(expected) ); } private : mutable int m_received{ 0 }; static const int expected = 12; void handle_received() const { ++m_received; } void on_msg_1( msg_1 ) { handle_received(); } void on_msg_2( msg_2 ) const { handle_received(); } void on_msg_3( const msg_3 & ) { handle_received(); } void on_msg_4( const msg_4 & ) const { handle_received(); } void on_msg_5( mhood_t ) { handle_received(); } void on_msg_6( mhood_t ) const { handle_received(); } void on_msg_7( const mhood_t & ) { handle_received(); } void on_msg_8( const mhood_t & ) const { handle_received(); } void on_msg_9( mutable_mhood_t ) { handle_received(); } void on_msg_10( mutable_mhood_t ) const { handle_received(); } void on_msg_11( const mutable_mhood_t & ) { handle_received(); } void on_msg_12( const mutable_mhood_t & ) const { handle_received(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_has_handler/000077500000000000000000000000001523065210100274325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_has_handler/CMakeLists.txt000066400000000000000000000001701523065210100321700ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.deadletter_handler_has_handler) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_has_handler/main.cpp000066400000000000000000000032721523065210100310660ustar00rootroot00000000000000/* * A test for so_has_deadletter_handler. */ #include #include #include struct first_request final : public so_5::signal_t {}; struct second_request final : public so_5::signal_t {}; class provider_t final : public so_5::agent_t { state_t st_test{ this, "test" }; public : provider_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) {} virtual void so_define_agent() override { this >>= st_test; st_test.event( [](mhood_t) -> std::string { return "first"; } ); so_subscribe_deadletter_handler( so_direct_mbox(), [](mhood_t) -> std::string { return "second"; } ); } virtual void so_evt_start() override { ensure_or_die( !so_has_deadletter_handler< first_request >( so_direct_mbox() ), "should have no deadletter handler for first_request" ); ensure_or_die( so_has_deadletter_handler< second_request >( so_direct_mbox() ), "should have deadletter handler for second_request" ); so_drop_deadletter_handler< second_request >( so_direct_mbox() ); ensure_or_die( !so_has_deadletter_handler< second_request >( so_direct_mbox() ), "should have no deadletter handler for second_request" ); so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< provider_t >(); } ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_simple/000077500000000000000000000000001523065210100264535ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_simple/CMakeLists.txt000066400000000000000000000001631523065210100312130ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.deadletter_handler_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_simple/main.cpp000066400000000000000000000057131523065210100301110ustar00rootroot00000000000000/* * A simple test for deadletter handlers. */ #include #include #include #include "../deadletter_handler_common.hpp" class test_message final : public so_5::message_t {}; class test_signal final : public so_5::signal_t {}; template< typename Mbox_Case, typename Msg_Type > class pfn_test_case_t final : public so_5::agent_t { const Mbox_Case m_mbox_holder; state_t st_test{ this, "test" }; public: pfn_test_case_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) , m_mbox_holder( *self_ptr() ) {} virtual void so_define_agent() override { this >>= st_test; so_subscribe_deadletter_handler( m_mbox_holder.mbox(), &pfn_test_case_t::on_deadletter ); } virtual void so_evt_start() override { so_5::send( m_mbox_holder.mbox() ); } private: void on_deadletter( mhood_t ) { so_deregister_agent_coop_normally(); } }; template< typename Mbox_Case, typename Msg_Type > class lambda_test_case_t final : public so_5::agent_t { const Mbox_Case m_mbox_holder; state_t st_test{ this, "test" }; public: lambda_test_case_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) , m_mbox_holder( *self_ptr() ) {} virtual void so_define_agent() override { this >>= st_test; so_subscribe_deadletter_handler( m_mbox_holder.mbox(), [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send( m_mbox_holder.mbox() ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { introduce_test_agent< direct_mbox_case_t, test_message, pfn_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, so_5::mutable_msg, pfn_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, test_signal, pfn_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_message, pfn_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_signal, pfn_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, test_message, lambda_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, so_5::mutable_msg, lambda_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, test_signal, lambda_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_message, lambda_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_signal, lambda_test_case_t >( env ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_unsubscribe/000077500000000000000000000000001523065210100275065ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_unsubscribe/CMakeLists.txt000066400000000000000000000001701523065210100322440ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.deadletter_handler_unsubscribe) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_unsubscribe/main.cpp000066400000000000000000000100121523065210100311300ustar00rootroot00000000000000/* * A test for so_drop_deadletter_handler. */ #include #include #include #include "../deadletter_handler_common.hpp" class test_message final : public so_5::message_t {}; class test_signal final : public so_5::signal_t {}; class finish final : public so_5::signal_t {}; class nontemplate_basic_part_t : public so_5::agent_t { protected: state_t st_test{ this, "test" }; int m_deadletters{ 0 }; void actual_deadletter_handler() { ensure_or_die( 0 == m_deadletters, "m_deadletters must be 0" ); ++m_deadletters; do_next_step(); so_5::send(*this); } virtual void do_next_step() = 0; public: nontemplate_basic_part_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) {} virtual void so_define_agent() override { this >>= st_test; st_test.event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } }; template< typename Mbox_Case, typename Msg_Type > class template_basic_part_t : public nontemplate_basic_part_t { protected: const Mbox_Case m_mbox_holder; virtual void do_next_step() override { so_drop_deadletter_handler< Msg_Type >( m_mbox_holder.mbox() ); so_5::send< Msg_Type >( *this ); } public: template_basic_part_t( context_t ctx ) : nontemplate_basic_part_t( std::move(ctx) ) , m_mbox_holder( *self_ptr() ) {} virtual void so_evt_start() override { so_5::send( m_mbox_holder.mbox() ); } }; template< typename Mbox_Case, typename Msg_Type > class pfn_test_case_t final : public template_basic_part_t< Mbox_Case, Msg_Type > { using base_type_t = template_basic_part_t< Mbox_Case, Msg_Type >; public: pfn_test_case_t( typename base_type_t::context_t ctx ) : base_type_t( std::move(ctx) ) {} virtual void so_define_agent() override { base_type_t::so_define_agent(); this->so_subscribe_deadletter_handler( this->m_mbox_holder.mbox(), &pfn_test_case_t::on_deadletter ); } private: void on_deadletter( so_5::mhood_t ) { this->actual_deadletter_handler(); } }; template< typename Mbox_Case, typename Msg_Type > class lambda_test_case_t final : public template_basic_part_t< Mbox_Case, Msg_Type > { using base_type_t = template_basic_part_t< Mbox_Case, Msg_Type >; public: lambda_test_case_t( typename base_type_t::context_t ctx ) : base_type_t( std::move(ctx) ) {} virtual void so_define_agent() override { base_type_t::so_define_agent(); this->so_subscribe_deadletter_handler( this->m_mbox_holder.mbox(), [this](so_5::mhood_t) { this->actual_deadletter_handler(); } ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { introduce_test_agent< direct_mbox_case_t, test_message, pfn_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, so_5::mutable_msg, pfn_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, test_signal, pfn_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_message, pfn_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_signal, pfn_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, test_message, lambda_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, so_5::mutable_msg, lambda_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, test_signal, lambda_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_message, lambda_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_signal, lambda_test_case_t >( env ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_unsubscribe_all_states/000077500000000000000000000000001523065210100317215ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002031523065210100343750ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_unsubscribe_all_statesset(UNITTEST _unit.test.event_handler.deadletter_handler_unsubscribe_all_states) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/deadletter_handler_unsubscribe_all_states/main.cpp000066400000000000000000000100641523065210100333520ustar00rootroot00000000000000/* * A test for so_drop_subscription_for_all_states for deadletter handlers. */ #include #include #include #include "../deadletter_handler_common.hpp" class test_message final : public so_5::message_t {}; class test_signal final : public so_5::signal_t {}; class finish final : public so_5::signal_t {}; class nontemplate_basic_part_t : public so_5::agent_t { protected: state_t st_test{ this, "test" }; int m_deadletters{ 0 }; void actual_deadletter_handler() { ensure_or_die( 0 == m_deadletters, "m_deadletters must be 0" ); ++m_deadletters; do_next_step(); so_5::send(*this); } virtual void do_next_step() = 0; public: nontemplate_basic_part_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) {} virtual void so_define_agent() override { this >>= st_test; st_test.event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } }; template< typename Mbox_Case, typename Msg_Type > class template_basic_part_t : public nontemplate_basic_part_t { protected: const Mbox_Case m_mbox_holder; virtual void do_next_step() override { so_drop_subscription_for_all_states< Msg_Type >( m_mbox_holder.mbox() ); so_5::send< Msg_Type >( *this ); } public: template_basic_part_t( context_t ctx ) : nontemplate_basic_part_t( std::move(ctx) ) , m_mbox_holder( *self_ptr() ) {} virtual void so_evt_start() override { so_5::send( m_mbox_holder.mbox() ); } }; template< typename Mbox_Case, typename Msg_Type > class pfn_test_case_t final : public template_basic_part_t< Mbox_Case, Msg_Type > { using base_type_t = template_basic_part_t< Mbox_Case, Msg_Type >; public: pfn_test_case_t( typename base_type_t::context_t ctx ) : base_type_t( std::move(ctx) ) {} virtual void so_define_agent() override { base_type_t::so_define_agent(); this->so_subscribe_deadletter_handler( this->m_mbox_holder.mbox(), &pfn_test_case_t::on_deadletter ); } private: void on_deadletter( so_5::mhood_t ) { this->actual_deadletter_handler(); } }; template< typename Mbox_Case, typename Msg_Type > class lambda_test_case_t final : public template_basic_part_t< Mbox_Case, Msg_Type > { using base_type_t = template_basic_part_t< Mbox_Case, Msg_Type >; public: lambda_test_case_t( typename base_type_t::context_t ctx ) : base_type_t( std::move(ctx) ) {} virtual void so_define_agent() override { base_type_t::so_define_agent(); this->so_subscribe_deadletter_handler( this->m_mbox_holder.mbox(), [this](so_5::mhood_t) { this->actual_deadletter_handler(); } ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { introduce_test_agent< direct_mbox_case_t, test_message, pfn_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, so_5::mutable_msg, pfn_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, test_signal, pfn_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_message, pfn_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_signal, pfn_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, test_message, lambda_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, so_5::mutable_msg, lambda_test_case_t >( env ); introduce_test_agent< direct_mbox_case_t, test_signal, lambda_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_message, lambda_test_case_t >( env ); introduce_test_agent< mpmc_mbox_case_t, test_signal, lambda_test_case_t >( env ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/drop_all_subscriptions_evt_finish/000077500000000000000000000000001523065210100302715ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/drop_all_subscriptions_evt_finish/CMakeLists.txt000066400000000000000000000001731523065210100330320ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.drop_all_subscriptions_evt_finish) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/drop_all_subscriptions_evt_finish/main.cpp000066400000000000000000000042731523065210100317270ustar00rootroot00000000000000/* * A test for so_evt_finish() after so_drop_all_subscriptions_and_filters. */ #include #include #include class a_terminator_t final : public so_5::agent_t { public: struct kill final : public so_5::signal_t {}; using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe( so_environment().create_mbox( "terminator" ) ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } }; class a_test_t final : public so_5::agent_t { struct first final : public so_5::signal_t {}; struct second final : public so_5::signal_t {}; std::atomic_bool & m_evt_finish_called_flag; public: a_test_t( context_t ctx, std::atomic_bool & evt_finish_called_flag ) : so_5::agent_t{ std::move(ctx) } , m_evt_finish_called_flag{ evt_finish_called_flag } {} void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t ) { so_5::send< a_terminator_t::kill >( so_environment().create_mbox( "terminator" ) ); so_drop_all_subscriptions_and_filters(); ensure_or_die( so_default_state().is_active(), "agent should be in the default_state" ); } ) .event( []( mhood_t ) { throw std::runtime_error{ "second message received" }; } ); } void so_evt_start() override { m_evt_finish_called_flag = false; so_5::send< first >( *this ); so_5::send< second >( *this ); } void so_evt_finish() override { m_evt_finish_called_flag = true; } }; int main() { try { run_with_time_limit( []() { std::atomic_bool evt_finish_called{ false }; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_terminator_t >(); coop.make_agent< a_test_t >( std::ref(evt_finish_called) ); } ); } ); ensure_or_die( evt_finish_called.load( std::memory_order_acquire ), "evt_finish_called is expected to be 'true'" ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/drop_all_subscriptions_simple/000077500000000000000000000000001523065210100274245ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/drop_all_subscriptions_simple/CMakeLists.txt000066400000000000000000000001671523065210100321700ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.drop_all_subscriptions_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/drop_all_subscriptions_simple/main.cpp000066400000000000000000000032471523065210100310620ustar00rootroot00000000000000/* * A simple test for so_drop_all_subscriptions_and_filters */ #include #include #include class a_terminator_t final : public so_5::agent_t { public: struct kill final : public so_5::signal_t {}; using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe( so_environment().create_mbox( "terminator" ) ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } }; class a_test_t final : public so_5::agent_t { struct first final : public so_5::signal_t {}; struct second final : public so_5::signal_t {}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t ) { so_5::send< a_terminator_t::kill >( so_environment().create_mbox( "terminator" ) ); so_drop_all_subscriptions_and_filters(); ensure_or_die( so_default_state().is_active(), "agent should be in the default_state" ); } ) .event( []( mhood_t ) { throw std::runtime_error{ "second message received" }; } ); } void so_evt_start() override { so_5::send< first >( *this ); so_5::send< second >( *this ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_terminator_t >(); coop.make_agent< a_test_t >(); } ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/drop_subscr_in_lambda_event_handler/000077500000000000000000000000001523065210100305015ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/drop_subscr_in_lambda_event_handler/CMakeLists.txt000066400000000000000000000001751523065210100332440ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.drop_subscr_in_lambda_event_handler) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/drop_subscr_in_lambda_event_handler/main.cpp000066400000000000000000000025361523065210100321370ustar00rootroot00000000000000/* * Test for calling so_drop_subscription in event-handler which is * a lambda-function. */ #include #include #include class a_test_t : public so_5::agent_t { struct test_signal : public so_5::signal_t {}; struct shutdown : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< shutdown >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { for( std::size_t i = 0; i != 1000; ++i ) { auto unique_mbox = so_environment().create_mbox(); so_subscribe( unique_mbox ).event( [this, unique_mbox](mhood_t< test_signal >) { so_drop_subscription< test_signal >( unique_mbox ); } ); so_5::send< test_signal >( unique_mbox ); } so_5::send< shutdown >( *this ); } private : }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "so_drop_subscription in lambda event-handler" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/exception_reaction_inheritance/000077500000000000000000000000001523065210100275235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/exception_reaction_inheritance/CMakeLists.txt000066400000000000000000000001701523065210100322610ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.exception_reaction_inheritance) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/exception_reaction_inheritance/main.cpp000066400000000000000000000032411523065210100311530ustar00rootroot00000000000000/* * A test for checking exception reaction inheritance from * coop, parent_coop and from so_environment. */ #include #include #include #include struct msg_test_signal : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, const so_5::mbox_t & self_mbox ) : base_type_t( env ) , m_self_mbox( self_mbox ) {} void so_define_agent() override { so_subscribe( m_self_mbox ).event( [](mhood_t< msg_test_signal >) { throw std::runtime_error( "Exception from a_test_t!" ); } ); } private : const so_5::mbox_t m_self_mbox; }; class a_parent_t : public so_5::agent_t { public : a_parent_t( so_5::environment_t & env ) : so_5::agent_t( env ) {} void so_evt_start() override { auto child = so_environment().make_coop( so_coop() ); auto mbox = so_environment().create_mbox(); child->make_agent< a_test_t >( mbox ); so_environment().register_coop( std::move(child) ); so_5::send< msg_test_signal >( mbox ); } }; void init( so_5::environment_t & env ) { auto coop = env.make_coop(); coop->make_agent< a_parent_t >(); env.register_coop( std::move( coop ) ); } int main() { try { run_with_time_limit( [] { so_5::launch( &init, []( so_5::environment_params_t & params ) { params.exception_reaction( so_5::shutdown_sobjectizer_on_exception ); } ); }, 10 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/external_subscription/000077500000000000000000000000001523065210100257165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/external_subscription/CMakeLists.txt000066400000000000000000000001571523065210100304610ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.external_subscription) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/external_subscription/main.cpp000066400000000000000000000144061523065210100273530ustar00rootroot00000000000000/* * A test for making a subcription for agent from external entity. */ #include #include #include #include #include #include #include #include #include #include struct subscription_data_t { so_5::mbox_t m_mbox; const so_5::state_t * m_state; std::type_index m_subscription_type; so_5::event_handler_method_t m_handler; subscription_data_t( so_5::mbox_t mbox, const so_5::state_t & state, std::type_index subscription_type, so_5::event_handler_method_t handler ) : m_mbox( std::move(mbox) ) , m_state( &state ) , m_subscription_type( std::move(subscription_type) ) , m_handler( std::move(handler) ) {} }; class one_shot_subscription_t { so_5::outliving_reference_t< so_5::agent_t > m_agent; std::vector< subscription_data_t > m_subscriptions; public : one_shot_subscription_t( so_5::agent_t & agent ) : m_agent( outliving_mutable(agent) ) {} template< typename Handler > void add_handler( const so_5::mbox_t & mbox, const so_5::state_t & state, Handler && handler ) { const auto user_handler_data = make_user_handler( std::forward(handler) ); so_5::details::event_subscription_helpers::ensure_handler_can_be_used_with_mbox( user_handler_data, mbox ); const auto user_handler = user_handler_data.m_handler; so_5::event_handler_method_t actual_handler = [this, user_handler]( so_5::message_ref_t & msg ) { drop_subscriptions(); user_handler( msg ); }; m_subscriptions.emplace_back( mbox, state, user_handler_data.m_msg_type, std::move(actual_handler) ); } void activate() { for( const auto & sd : m_subscriptions ) m_agent.get().so_create_event_subscription( sd.m_mbox, sd.m_subscription_type, *(sd.m_state), sd.m_handler, so_5::thread_safety_t::unsafe, so_5::event_handler_kind_t::final_handler ); } private : template< typename Event_Method > typename std::enable_if< so_5::details::is_agent_method_pointer< so_5::details::method_arity::unary, Event_Method>::value, so_5::details::msg_type_and_handler_pair_t >::type make_user_handler( Event_Method method ) { using namespace so_5::details::event_subscription_helpers; using pfn_traits = so_5::details::is_agent_method_pointer< so_5::details::method_arity::unary, Event_Method>; using agent_type = typename pfn_traits::agent_type; auto cast_result = get_actual_agent_pointer< agent_type >( m_agent.get() ); return make_handler_with_arg_for_agent( cast_result, method ); } template< typename Event_Lambda > typename std::enable_if< so_5::details::lambda_traits::is_lambda::value, so_5::details::msg_type_and_handler_pair_t >::type make_user_handler( Event_Lambda && lambda ) { using namespace so_5::details::event_subscription_helpers; return make_handler_from_lambda_of_free_function( std::forward(lambda) ); } void drop_subscriptions() noexcept { so_5::details::invoke_noexcept_code( [&] { for( const auto & sd : m_subscriptions ) m_agent.get().so_destroy_event_subscription( sd.m_mbox, sd.m_subscription_type, *(sd.m_state) ); } ); } }; class a_test_t final : public so_5::agent_t { private : one_shot_subscription_t m_one_shot; so_5::outliving_reference_t< std::string > m_trace; struct demo_signal final : public so_5::signal_t {}; struct finish_signal final : public so_5::signal_t {}; public : a_test_t( context_t ctx, so_5::outliving_reference_t< std::string > trace ) : so_5::agent_t( std::move(ctx) ) , m_one_shot( *this ) , m_trace( trace ) {} virtual void so_define_agent() override { so_subscribe_self().event( &a_test_t::on_finish ); } virtual void so_evt_start() override { m_one_shot.add_handler( so_direct_mbox(), so_default_state(), &a_test_t::on_demo_signal ); m_one_shot.activate(); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); } private : void on_demo_signal(mhood_t) { m_trace.get() += "demo;"; } void on_finish(mhood_t) { so_deregister_agent_coop_normally(); } }; class a_test2_t final : public so_5::agent_t { private : one_shot_subscription_t m_one_shot; so_5::outliving_reference_t< std::string > m_trace; struct demo_signal final : public so_5::signal_t {}; struct finish_signal final : public so_5::signal_t {}; public : a_test2_t( context_t ctx, so_5::outliving_reference_t< std::string > trace ) : so_5::agent_t( std::move(ctx) ) , m_one_shot( *this ) , m_trace( trace ) {} virtual void so_define_agent() override { so_subscribe_self().event( &a_test2_t::on_finish ); } virtual void so_evt_start() override { m_one_shot.add_handler( so_direct_mbox(), so_default_state(), [this](mhood_t) { m_trace.get() += "demo2;"; } ); m_one_shot.activate(); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); } private : void on_finish(mhood_t) { so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { std::string trace1; std::string trace2; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( so_5::outliving_mutable(trace1) ); } ); env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_test2_t >( so_5::outliving_mutable(trace2) ); } ); } ); ensure_or_die( trace1 == "demo;", "trace1 has unexpected value: " + trace1 ); ensure_or_die( trace2 == "demo2;", "trace2 has unexpected value: " + trace2 ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_1/000077500000000000000000000000001523065210100250675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_1/CMakeLists.txt000066400000000000000000000001541523065210100276270ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.has_subscription_1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_1/main.cpp000066400000000000000000000115531523065210100265240ustar00rootroot00000000000000/* * A simple test for so_has_subscription method. */ #include #include #include #include #include #include #include #include #include "../subscr_storage_factories.hpp" #include #include namespace test { class a_test_t : public so_5::agent_t { struct msg_1 : public so_5::signal_t {}; state_t st_dummy{ this }; state_t st_dummy_2{ this }; public : a_test_t( context_t ctx, so_5::subscription_storage_factory_t factory ) : so_5::agent_t( ctx + factory ) {} void so_define_agent() override { ensure( !so_has_subscription(so_direct_mbox(), so_default_state()), "(1) subscription must be absent (msg_1, direct, default)" ); ensure( !so_has_subscription(so_direct_mbox()), "(1) subscription must be absent (msg_1, direct)" ); ensure( !so_has_subscription(so_direct_mbox(), st_dummy), "(1) subscription must be absent (msg_1, direct, st_dummy)" ); ensure( !so_has_subscription(so_direct_mbox(), st_dummy_2), "(1) subscription must be absent (msg_1, direct, st_dummy_2)" ); so_subscribe_self().event( [](mhood_t< msg_1 >){} ); ensure( so_has_subscription(so_direct_mbox(), so_default_state()), "(2) subscription must be present (msg_1, direct, default)" ); ensure( so_has_subscription(so_direct_mbox()), "(2) subscription must be present (msg_1, direct)" ); ensure( !so_has_subscription(so_direct_mbox(), st_dummy), "(2) subscription must be absent (msg_1, direct, st_dummy)" ); ensure( !so_has_subscription(so_direct_mbox(), st_dummy_2), "(2) subscription must be absent (msg_1, direct, st_dummy_2)" ); so_subscribe_self().in(st_dummy).event( [](mhood_t){} ); ensure( so_has_subscription(so_direct_mbox(), so_default_state()), "(3) subscription must be present (msg_1, direct, default)" ); ensure( so_has_subscription(so_direct_mbox()), "(3) subscription must be present (msg_1, direct)" ); ensure( so_has_subscription(so_direct_mbox(), st_dummy), "(3) subscription must be present (msg_1, direct, st_dummy)" ); ensure( !so_has_subscription(so_direct_mbox(), st_dummy_2), "(3) subscription must be absent (msg_1, direct, st_dummy_2)" ); so_subscribe_self().in(st_dummy_2).event( [](mhood_t){} ); ensure( so_has_subscription(so_direct_mbox(), so_default_state()), "(4) subscription must be present (msg_1, direct, default)" ); ensure( so_has_subscription(so_direct_mbox()), "(4) subscription must be present (msg_1, direct)" ); ensure( so_has_subscription(so_direct_mbox(), st_dummy), "(4) subscription must be present (msg_1, direct, st_dummy)" ); ensure( so_has_subscription(so_direct_mbox(), st_dummy_2), "(4) subscription must be present (msg_1, direct, st_dummy_2)" ); so_drop_subscription(so_direct_mbox(), st_dummy); ensure( so_has_subscription(so_direct_mbox(), so_default_state()), "(5) subscription must be present (msg_1, direct, default)" ); ensure( so_has_subscription(so_direct_mbox()), "(5) subscription must be present (msg_1, direct)" ); ensure( !so_has_subscription(so_direct_mbox(), st_dummy), "(5) subscription must be absent (msg_1, direct, st_dummy)" ); ensure( so_has_subscription(so_direct_mbox(), st_dummy_2), "(5) subscription must be present (msg_1, direct, st_dummy_2)" ); so_drop_subscription_for_all_states(so_direct_mbox()); ensure( !so_has_subscription(so_direct_mbox(), so_default_state()), "(6) subscription must be absent (msg_1, direct, default)" ); ensure( !so_has_subscription(so_direct_mbox()), "(6) subscription must be absent (msg_1, direct)" ); ensure( !so_has_subscription(so_direct_mbox(), st_dummy), "(6) subscription must be absent (msg_1, direct, st_dummy)" ); ensure( !so_has_subscription(so_direct_mbox(), st_dummy_2), "(6) subscription must be absent (msg_1, direct, st_dummy_2)" ); } virtual void so_evt_start() override { so_deregister_agent_coop_normally(); } }; } /* namespace test */ using namespace test; int main() { try { const auto factories = build_subscr_storage_factories(); for( const auto & pair : factories ) { const auto & n = pair.first; const auto & f = pair.second; run_with_time_limit( [&]() { std::cout << n << ": " << std::flush; so_5::launch( [f]( so_5::environment_t & env ) { env.introduce_coop( [f]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(f); } ); } ); std::cout << "OK" << std::endl; }, 20, n ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_2/000077500000000000000000000000001523065210100250705ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_2/CMakeLists.txt000066400000000000000000000001541523065210100276300ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.has_subscription_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_2/main.cpp000066400000000000000000000070551523065210100265270ustar00rootroot00000000000000/* * A simple test for state_t::has_subscription method. */ #include #include #include #include #include #include #include #include #include "../subscr_storage_factories.hpp" #include #include namespace test { class a_test_t : public so_5::agent_t { struct msg_1 : public so_5::signal_t {}; state_t st_dummy{ this }; state_t st_dummy_2{ this }; public : a_test_t( context_t ctx, so_5::subscription_storage_factory_t factory ) : so_5::agent_t( ctx + factory ) {} virtual void so_define_agent() override { ensure( !so_default_state().has_subscription(so_direct_mbox()), "subscription must be absent" ); ensure( !st_dummy.has_subscription(so_direct_mbox()), "subscription must be absent" ); ensure( !st_dummy_2.has_subscription(so_direct_mbox()), "subscription must be absent" ); so_default_state().event( [](mhood_t< msg_1 >){} ); ensure( so_default_state().has_subscription(so_direct_mbox()), "subscription must be present" ); ensure( !st_dummy.has_subscription(so_direct_mbox()), "subscription must be absent" ); ensure( !st_dummy_2.has_subscription(so_direct_mbox()), "subscription must be absent" ); st_dummy.event( [](mhood_t){} ); ensure( so_default_state().has_subscription(so_direct_mbox()), "subscription must be present" ); ensure( st_dummy.has_subscription(so_direct_mbox()), "subscription must be present" ); ensure( !st_dummy_2.has_subscription(so_direct_mbox()), "subscription must be absent" ); st_dummy_2.event( [](mhood_t){} ); ensure( so_default_state().has_subscription(so_direct_mbox()), "subscription must be present" ); ensure( st_dummy.has_subscription(so_direct_mbox()), "subscription must be present" ); ensure( st_dummy_2.has_subscription(so_direct_mbox()), "subscription must be present" ); st_dummy.drop_subscription(so_direct_mbox()); ensure( so_default_state().has_subscription(so_direct_mbox()), "subscription must be present" ); ensure( !st_dummy.has_subscription(so_direct_mbox()), "subscription must be absent" ); ensure( st_dummy_2.has_subscription(so_direct_mbox()), "subscription must be present" ); so_drop_subscription_for_all_states(so_direct_mbox()); ensure( !so_default_state().has_subscription(so_direct_mbox()), "subscription must be absent" ); ensure( !st_dummy.has_subscription(so_direct_mbox()), "subscription must be absent" ); ensure( !st_dummy_2.has_subscription(so_direct_mbox()), "subscription must be absent" ); } virtual void so_evt_start() override { so_deregister_agent_coop_normally(); } }; } /* namespace test */ using namespace test; int main() { try { const auto factories = build_subscr_storage_factories(); for( const auto & pair : factories ) { const auto & n = pair.first; const auto & f = pair.second; run_with_time_limit( [&]() { std::cout << n << ": " << std::flush; so_5::launch( [f]( so_5::environment_t & env ) { env.introduce_coop( [f]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(f); } ); } ); std::cout << "OK" << std::endl; }, 20, n ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_3/000077500000000000000000000000001523065210100250715ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_3/CMakeLists.txt000066400000000000000000000001541523065210100276310ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.has_subscription_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_3/main.cpp000066400000000000000000000227231523065210100265270ustar00rootroot00000000000000/* * A simple test for so_has_subscription method. */ #include #include #include #include #include #include #include #include #include "../subscr_storage_factories.hpp" #include #include namespace test { class a_test_t : public so_5::agent_t { struct finish : public so_5::signal_t {}; struct sig_1 : public so_5::signal_t {}; struct sig_2 : public so_5::signal_t {}; struct msg_1 : public so_5::message_t {}; struct msg_2 : public so_5::message_t {}; struct msg_3 : public so_5::message_t {}; struct msg_4 : public so_5::message_t {}; struct msg_5 : public so_5::message_t {}; struct umsg_1 {}; struct umsg_2 {}; struct umsg_3 {}; struct umsg_4 {}; struct umsg_5 {}; struct umsg_6 {}; struct ret_msg_1 : public so_5::message_t {}; struct ret_msg_2 : public so_5::message_t {}; struct ret_msg_3 : public so_5::message_t {}; struct ret_msg_4 : public so_5::message_t {}; struct ret_msg_5 : public so_5::message_t {}; struct ret_umsg_1 {}; struct ret_umsg_2 {}; struct ret_umsg_3 {}; struct ret_umsg_4 {}; struct ret_umsg_5 {}; struct ret_umsg_6 {}; public : a_test_t( context_t ctx, so_5::subscription_storage_factory_t factory ) : so_5::agent_t( ctx + factory ) {} virtual void so_define_agent() override { so_subscribe_self() .event( &a_test_t::on_finish ) .event( &a_test_t::on_sig_1 ) .event( &a_test_t::on_sig_2 ) .event( &a_test_t::on_msg_1 ) .event( &a_test_t::on_msg_2 ) .event( &a_test_t::on_msg_3 ) .event( &a_test_t::on_msg_4 ) .event( &a_test_t::on_msg_5 ) .event( &a_test_t::on_umsg_1 ) .event( &a_test_t::on_umsg_2 ) .event( &a_test_t::on_umsg_3 ) .event( &a_test_t::on_umsg_4 ) .event( &a_test_t::on_umsg_5 ) .event( &a_test_t::on_umsg_6 ) .event( &a_test_t::on_ret_msg_1 ) .event( &a_test_t::on_ret_msg_2 ) .event( &a_test_t::on_ret_msg_3 ) .event( &a_test_t::on_ret_msg_4 ) .event( &a_test_t::on_ret_msg_5 ) .event( &a_test_t::on_ret_umsg_1 ) .event( &a_test_t::on_ret_umsg_2 ) .event( &a_test_t::on_ret_umsg_3 ) .event( &a_test_t::on_ret_umsg_4 ) .event( &a_test_t::on_ret_umsg_5 ) .event( &a_test_t::on_ret_umsg_6 ) ; #define ENSURE_SUBSCRIBED(handler) \ ensure( so_has_subscription(so_direct_mbox(), \ so_default_state(), \ &a_test_t:: handler), \ #handler " must be subscribed" ) ENSURE_SUBSCRIBED(on_sig_1); ENSURE_SUBSCRIBED(on_sig_2); ENSURE_SUBSCRIBED(on_msg_1); ENSURE_SUBSCRIBED(on_msg_2); ENSURE_SUBSCRIBED(on_msg_3); ENSURE_SUBSCRIBED(on_msg_4); ENSURE_SUBSCRIBED(on_msg_5); ENSURE_SUBSCRIBED(on_umsg_1); ENSURE_SUBSCRIBED(on_umsg_2); ENSURE_SUBSCRIBED(on_umsg_3); ENSURE_SUBSCRIBED(on_umsg_4); ENSURE_SUBSCRIBED(on_umsg_5); ENSURE_SUBSCRIBED(on_umsg_6); ENSURE_SUBSCRIBED(on_ret_msg_1); ENSURE_SUBSCRIBED(on_ret_msg_2); ENSURE_SUBSCRIBED(on_ret_msg_3); ENSURE_SUBSCRIBED(on_ret_msg_4); ENSURE_SUBSCRIBED(on_ret_msg_5); ENSURE_SUBSCRIBED(on_ret_umsg_1); ENSURE_SUBSCRIBED(on_ret_umsg_2); ENSURE_SUBSCRIBED(on_ret_umsg_3); ENSURE_SUBSCRIBED(on_ret_umsg_4); ENSURE_SUBSCRIBED(on_ret_umsg_5); ENSURE_SUBSCRIBED(on_ret_umsg_6); #undef ENSURE_SUBSCRIBED } virtual void so_evt_start() override { so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); } private: void on_sig_1(const mhood_t &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_sig_1 ); } void on_sig_2(mhood_t) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_sig_2 ); } void on_msg_1(const msg_1 &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_1 ); } void on_msg_2(const mhood_t &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_2 ); } void on_msg_3(mhood_t) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_3 ); } void on_msg_4(mutable_mhood_t) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_4 ); } void on_msg_5(const mutable_mhood_t &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_5 ); } void on_umsg_1(const umsg_1 &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_1 ); } void on_umsg_2(const mhood_t &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_2 ); } void on_umsg_3(mhood_t) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_3 ); } void on_umsg_4(mutable_mhood_t) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_4 ); } void on_umsg_5(const mutable_mhood_t &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_5 ); } void on_umsg_6(umsg_6) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_6 ); } int on_ret_msg_1(const ret_msg_1 &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_1 ); return 0; } int on_ret_msg_2(const mhood_t &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_2 ); return 0; } int on_ret_msg_3(mhood_t) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_3 ); return 0; } int on_ret_msg_4(mutable_mhood_t) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_4 ); return 0; } int on_ret_msg_5(const mutable_mhood_t &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_5 ); return 0; } int on_ret_umsg_1(const ret_umsg_1 &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_1 ); return 0; } int on_ret_umsg_2(const mhood_t &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_2 ); return 0; } int on_ret_umsg_3(mhood_t) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_3 ); return 0; } int on_ret_umsg_4(mutable_mhood_t) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_4 ); return 0; } int on_ret_umsg_5(const mutable_mhood_t &) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_5 ); return 0; } int on_ret_umsg_6(ret_umsg_6) { so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_6 ); return 0; } void on_finish(mhood_t) { #define ENSURE_UNSUBSCRIBED(handler) \ ensure( !so_has_subscription(so_direct_mbox(), \ so_default_state(), \ &a_test_t:: handler), \ #handler " must not be subscribed" ) ENSURE_UNSUBSCRIBED(on_sig_1); ENSURE_UNSUBSCRIBED(on_sig_2); ENSURE_UNSUBSCRIBED(on_msg_1); ENSURE_UNSUBSCRIBED(on_msg_2); ENSURE_UNSUBSCRIBED(on_msg_3); ENSURE_UNSUBSCRIBED(on_msg_4); ENSURE_UNSUBSCRIBED(on_msg_5); ENSURE_UNSUBSCRIBED(on_umsg_1); ENSURE_UNSUBSCRIBED(on_umsg_2); ENSURE_UNSUBSCRIBED(on_umsg_3); ENSURE_UNSUBSCRIBED(on_umsg_4); ENSURE_UNSUBSCRIBED(on_umsg_5); ENSURE_UNSUBSCRIBED(on_umsg_6); ENSURE_UNSUBSCRIBED(on_ret_msg_1); ENSURE_UNSUBSCRIBED(on_ret_msg_2); ENSURE_UNSUBSCRIBED(on_ret_msg_3); ENSURE_UNSUBSCRIBED(on_ret_msg_4); ENSURE_UNSUBSCRIBED(on_ret_msg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_1); ENSURE_UNSUBSCRIBED(on_ret_umsg_2); ENSURE_UNSUBSCRIBED(on_ret_umsg_3); ENSURE_UNSUBSCRIBED(on_ret_umsg_4); ENSURE_UNSUBSCRIBED(on_ret_umsg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_6); #undef ENSURE_UNSUBSCRIBED so_deregister_agent_coop_normally(); } }; } /* namespace test */ using namespace test; int main() { try { const auto factories = build_subscr_storage_factories(); for( const auto & pair : factories ) { const auto & n = pair.first; const auto & f = pair.second; run_with_time_limit( [&]() { std::cout << n << ": " << std::flush; so_5::launch( [f]( so_5::environment_t & env ) { env.introduce_coop( [f]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(f); } ); } ); std::cout << "OK" << std::endl; }, 20, n ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_4/000077500000000000000000000000001523065210100250725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_4/CMakeLists.txt000066400000000000000000000001541523065210100276320ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.has_subscription_4) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_4/main.cpp000066400000000000000000000215431523065210100265270ustar00rootroot00000000000000/* * A simple test for so_has_subscription method. */ #include #include #include #include #include #include #include #include #include "../subscr_storage_factories.hpp" #include #include namespace test { class a_test_t : public so_5::agent_t { struct finish : public so_5::signal_t {}; struct sig_1 : public so_5::signal_t {}; struct sig_2 : public so_5::signal_t {}; struct msg_1 : public so_5::message_t {}; struct msg_2 : public so_5::message_t {}; struct msg_3 : public so_5::message_t {}; struct msg_4 : public so_5::message_t {}; struct msg_5 : public so_5::message_t {}; struct umsg_1 {}; struct umsg_2 {}; struct umsg_3 {}; struct umsg_4 {}; struct umsg_5 {}; struct umsg_6 {}; struct ret_msg_1 : public so_5::message_t {}; struct ret_msg_2 : public so_5::message_t {}; struct ret_msg_3 : public so_5::message_t {}; struct ret_msg_4 : public so_5::message_t {}; struct ret_msg_5 : public so_5::message_t {}; struct ret_umsg_1 {}; struct ret_umsg_2 {}; struct ret_umsg_3 {}; struct ret_umsg_4 {}; struct ret_umsg_5 {}; struct ret_umsg_6 {}; public : a_test_t( context_t ctx, so_5::subscription_storage_factory_t factory ) : so_5::agent_t( ctx + factory ) {} virtual void so_define_agent() override { so_subscribe_self() .event( &a_test_t::on_finish ) .event( &a_test_t::on_sig_1 ) .event( &a_test_t::on_sig_2 ) .event( &a_test_t::on_msg_1 ) .event( &a_test_t::on_msg_2 ) .event( &a_test_t::on_msg_3 ) .event( &a_test_t::on_msg_4 ) .event( &a_test_t::on_msg_5 ) .event( &a_test_t::on_umsg_1 ) .event( &a_test_t::on_umsg_2 ) .event( &a_test_t::on_umsg_3 ) .event( &a_test_t::on_umsg_4 ) .event( &a_test_t::on_umsg_5 ) .event( &a_test_t::on_umsg_6 ) .event( &a_test_t::on_ret_msg_1 ) .event( &a_test_t::on_ret_msg_2 ) .event( &a_test_t::on_ret_msg_3 ) .event( &a_test_t::on_ret_msg_4 ) .event( &a_test_t::on_ret_msg_5 ) .event( &a_test_t::on_ret_umsg_1 ) .event( &a_test_t::on_ret_umsg_2 ) .event( &a_test_t::on_ret_umsg_3 ) .event( &a_test_t::on_ret_umsg_4 ) .event( &a_test_t::on_ret_umsg_5 ) .event( &a_test_t::on_ret_umsg_6 ) ; #define ENSURE_SUBSCRIBED(handler) \ ensure( so_has_subscription(so_direct_mbox(), \ &a_test_t:: handler), \ #handler " must be subscribed" ) ENSURE_SUBSCRIBED(on_sig_1); ENSURE_SUBSCRIBED(on_sig_2); ENSURE_SUBSCRIBED(on_msg_1); ENSURE_SUBSCRIBED(on_msg_2); ENSURE_SUBSCRIBED(on_msg_3); ENSURE_SUBSCRIBED(on_msg_4); ENSURE_SUBSCRIBED(on_msg_5); ENSURE_SUBSCRIBED(on_umsg_1); ENSURE_SUBSCRIBED(on_umsg_2); ENSURE_SUBSCRIBED(on_umsg_3); ENSURE_SUBSCRIBED(on_umsg_4); ENSURE_SUBSCRIBED(on_umsg_5); ENSURE_SUBSCRIBED(on_umsg_6); ENSURE_SUBSCRIBED(on_ret_msg_1); ENSURE_SUBSCRIBED(on_ret_msg_2); ENSURE_SUBSCRIBED(on_ret_msg_3); ENSURE_SUBSCRIBED(on_ret_msg_4); ENSURE_SUBSCRIBED(on_ret_msg_5); ENSURE_SUBSCRIBED(on_ret_umsg_1); ENSURE_SUBSCRIBED(on_ret_umsg_2); ENSURE_SUBSCRIBED(on_ret_umsg_3); ENSURE_SUBSCRIBED(on_ret_umsg_4); ENSURE_SUBSCRIBED(on_ret_umsg_5); ENSURE_SUBSCRIBED(on_ret_umsg_6); #undef ENSURE_SUBSCRIBED } virtual void so_evt_start() override { so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); } private: void on_sig_1(const mhood_t &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_sig_1 ); } void on_sig_2(mhood_t) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_sig_2 ); } void on_msg_1(const msg_1 &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_msg_1 ); } void on_msg_2(const mhood_t &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_msg_2 ); } void on_msg_3(mhood_t) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_msg_3 ); } void on_msg_4(mutable_mhood_t) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_msg_4 ); } void on_msg_5(const mutable_mhood_t &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_msg_5 ); } void on_umsg_1(const umsg_1 &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_1 ); } void on_umsg_2(const mhood_t &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_2 ); } void on_umsg_3(mhood_t) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_3 ); } void on_umsg_4(mutable_mhood_t) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_4 ); } void on_umsg_5(const mutable_mhood_t &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_5 ); } void on_umsg_6(umsg_6) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_6 ); } int on_ret_msg_1(const ret_msg_1 &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_1 ); return 0; } int on_ret_msg_2(const mhood_t &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_2 ); return 0; } int on_ret_msg_3(mhood_t) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_3 ); return 0; } int on_ret_msg_4(mutable_mhood_t) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_4 ); return 0; } int on_ret_msg_5(const mutable_mhood_t &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_5 ); return 0; } int on_ret_umsg_1(const ret_umsg_1 &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_1 ); return 0; } int on_ret_umsg_2(const mhood_t &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_2 ); return 0; } int on_ret_umsg_3(mhood_t) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_3 ); return 0; } int on_ret_umsg_4(mutable_mhood_t) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_4 ); return 0; } int on_ret_umsg_5(const mutable_mhood_t &) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_5 ); return 0; } int on_ret_umsg_6(ret_umsg_6) { so_drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_6 ); return 0; } void on_finish(mhood_t) { #define ENSURE_UNSUBSCRIBED(handler) \ ensure( !so_has_subscription(so_direct_mbox(), \ &a_test_t:: handler), \ #handler " must not be subscribed" ) ENSURE_UNSUBSCRIBED(on_sig_1); ENSURE_UNSUBSCRIBED(on_sig_2); ENSURE_UNSUBSCRIBED(on_msg_1); ENSURE_UNSUBSCRIBED(on_msg_2); ENSURE_UNSUBSCRIBED(on_msg_3); ENSURE_UNSUBSCRIBED(on_msg_4); ENSURE_UNSUBSCRIBED(on_msg_5); ENSURE_UNSUBSCRIBED(on_umsg_1); ENSURE_UNSUBSCRIBED(on_umsg_2); ENSURE_UNSUBSCRIBED(on_umsg_3); ENSURE_UNSUBSCRIBED(on_umsg_4); ENSURE_UNSUBSCRIBED(on_umsg_5); ENSURE_UNSUBSCRIBED(on_umsg_6); ENSURE_UNSUBSCRIBED(on_ret_msg_1); ENSURE_UNSUBSCRIBED(on_ret_msg_2); ENSURE_UNSUBSCRIBED(on_ret_msg_3); ENSURE_UNSUBSCRIBED(on_ret_msg_4); ENSURE_UNSUBSCRIBED(on_ret_msg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_1); ENSURE_UNSUBSCRIBED(on_ret_umsg_2); ENSURE_UNSUBSCRIBED(on_ret_umsg_3); ENSURE_UNSUBSCRIBED(on_ret_umsg_4); ENSURE_UNSUBSCRIBED(on_ret_umsg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_6); #undef ENSURE_UNSUBSCRIBED so_deregister_agent_coop_normally(); } }; } /* namespace test */ using namespace test; int main() { try { const auto factories = build_subscr_storage_factories(); for( const auto & pair : factories ) { const auto & n = pair.first; const auto & f = pair.second; run_with_time_limit( [&]() { std::cout << n << ": " << std::flush; so_5::launch( [f]( so_5::environment_t & env ) { env.introduce_coop( [f]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(f); } ); } ); std::cout << "OK" << std::endl; }, 20, n ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_5/000077500000000000000000000000001523065210100250735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_5/CMakeLists.txt000066400000000000000000000001541523065210100276330ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.has_subscription_5) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_5/main.cpp000066400000000000000000000223131523065210100265240ustar00rootroot00000000000000/* * A simple test for so_has_subscription method. */ #include #include #include #include #include #include #include #include #include "../subscr_storage_factories.hpp" #include #include namespace test { class a_test_t : public so_5::agent_t { struct finish : public so_5::signal_t {}; struct sig_1 : public so_5::signal_t {}; struct sig_2 : public so_5::signal_t {}; struct msg_1 : public so_5::message_t {}; struct msg_2 : public so_5::message_t {}; struct msg_3 : public so_5::message_t {}; struct msg_4 : public so_5::message_t {}; struct msg_5 : public so_5::message_t {}; struct umsg_1 {}; struct umsg_2 {}; struct umsg_3 {}; struct umsg_4 {}; struct umsg_5 {}; struct umsg_6 {}; struct ret_msg_1 : public so_5::message_t {}; struct ret_msg_2 : public so_5::message_t {}; struct ret_msg_3 : public so_5::message_t {}; struct ret_msg_4 : public so_5::message_t {}; struct ret_msg_5 : public so_5::message_t {}; struct ret_umsg_1 {}; struct ret_umsg_2 {}; struct ret_umsg_3 {}; struct ret_umsg_4 {}; struct ret_umsg_5 {}; struct ret_umsg_6 {}; public : a_test_t( context_t ctx, so_5::subscription_storage_factory_t factory ) : so_5::agent_t( ctx + factory ) {} virtual void so_define_agent() override { so_subscribe_self() .event( &a_test_t::on_finish ) .event( &a_test_t::on_sig_1 ) .event( &a_test_t::on_sig_2 ) .event( &a_test_t::on_msg_1 ) .event( &a_test_t::on_msg_2 ) .event( &a_test_t::on_msg_3 ) .event( &a_test_t::on_msg_4 ) .event( &a_test_t::on_msg_5 ) .event( &a_test_t::on_umsg_1 ) .event( &a_test_t::on_umsg_2 ) .event( &a_test_t::on_umsg_3 ) .event( &a_test_t::on_umsg_4 ) .event( &a_test_t::on_umsg_5 ) .event( &a_test_t::on_umsg_6 ) .event( &a_test_t::on_ret_msg_1 ) .event( &a_test_t::on_ret_msg_2 ) .event( &a_test_t::on_ret_msg_3 ) .event( &a_test_t::on_ret_msg_4 ) .event( &a_test_t::on_ret_msg_5 ) .event( &a_test_t::on_ret_umsg_1 ) .event( &a_test_t::on_ret_umsg_2 ) .event( &a_test_t::on_ret_umsg_3 ) .event( &a_test_t::on_ret_umsg_4 ) .event( &a_test_t::on_ret_umsg_5 ) .event( &a_test_t::on_ret_umsg_6 ) ; #define ENSURE_SUBSCRIBED(handler) \ ensure( so_has_subscription(so_direct_mbox(), \ &a_test_t:: handler), \ #handler " must be subscribed" ) ENSURE_SUBSCRIBED(on_sig_1); ENSURE_SUBSCRIBED(on_sig_2); ENSURE_SUBSCRIBED(on_msg_1); ENSURE_SUBSCRIBED(on_msg_2); ENSURE_SUBSCRIBED(on_msg_3); ENSURE_SUBSCRIBED(on_msg_4); ENSURE_SUBSCRIBED(on_msg_5); ENSURE_SUBSCRIBED(on_umsg_1); ENSURE_SUBSCRIBED(on_umsg_2); ENSURE_SUBSCRIBED(on_umsg_3); ENSURE_SUBSCRIBED(on_umsg_4); ENSURE_SUBSCRIBED(on_umsg_5); ENSURE_SUBSCRIBED(on_umsg_6); ENSURE_SUBSCRIBED(on_ret_msg_1); ENSURE_SUBSCRIBED(on_ret_msg_2); ENSURE_SUBSCRIBED(on_ret_msg_3); ENSURE_SUBSCRIBED(on_ret_msg_4); ENSURE_SUBSCRIBED(on_ret_msg_5); ENSURE_SUBSCRIBED(on_ret_umsg_1); ENSURE_SUBSCRIBED(on_ret_umsg_2); ENSURE_SUBSCRIBED(on_ret_umsg_3); ENSURE_SUBSCRIBED(on_ret_umsg_4); ENSURE_SUBSCRIBED(on_ret_umsg_5); ENSURE_SUBSCRIBED(on_ret_umsg_6); #undef ENSURE_SUBSCRIBED } virtual void so_evt_start() override { so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); } private: void on_sig_1(const mhood_t &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_sig_1 ); } void on_sig_2(mhood_t) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_sig_2 ); } void on_msg_1(const msg_1 &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_msg_1 ); } void on_msg_2(const mhood_t &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_msg_2 ); } void on_msg_3(mhood_t) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_msg_3 ); } void on_msg_4(mutable_mhood_t) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_msg_4 ); } void on_msg_5(const mutable_mhood_t &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_msg_5 ); } void on_umsg_1(const umsg_1 &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_umsg_1 ); } void on_umsg_2(const mhood_t &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_umsg_2 ); } void on_umsg_3(mhood_t) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_umsg_3 ); } void on_umsg_4(mutable_mhood_t) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_umsg_4 ); } void on_umsg_5(const mutable_mhood_t &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_umsg_5 ); } void on_umsg_6(umsg_6) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_umsg_6 ); } int on_ret_msg_1(const ret_msg_1 &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_msg_1 ); return 0; } int on_ret_msg_2(const mhood_t &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_msg_2 ); return 0; } int on_ret_msg_3(mhood_t) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_msg_3 ); return 0; } int on_ret_msg_4(mutable_mhood_t) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_msg_4 ); return 0; } int on_ret_msg_5(const mutable_mhood_t &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_msg_5 ); return 0; } int on_ret_umsg_1(const ret_umsg_1 &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_umsg_1 ); return 0; } int on_ret_umsg_2(const mhood_t &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_umsg_2 ); return 0; } int on_ret_umsg_3(mhood_t) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_umsg_3 ); return 0; } int on_ret_umsg_4(mutable_mhood_t) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_umsg_4 ); return 0; } int on_ret_umsg_5(const mutable_mhood_t &) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_umsg_5 ); return 0; } int on_ret_umsg_6(ret_umsg_6) { so_drop_subscription_for_all_states( so_direct_mbox(), &a_test_t::on_ret_umsg_6 ); return 0; } void on_finish(mhood_t) { #define ENSURE_UNSUBSCRIBED(handler) \ ensure( !so_has_subscription(so_direct_mbox(), \ &a_test_t:: handler), \ #handler " must not be subscribed" ) ENSURE_UNSUBSCRIBED(on_sig_1); ENSURE_UNSUBSCRIBED(on_sig_2); ENSURE_UNSUBSCRIBED(on_msg_1); ENSURE_UNSUBSCRIBED(on_msg_2); ENSURE_UNSUBSCRIBED(on_msg_3); ENSURE_UNSUBSCRIBED(on_msg_4); ENSURE_UNSUBSCRIBED(on_msg_5); ENSURE_UNSUBSCRIBED(on_umsg_1); ENSURE_UNSUBSCRIBED(on_umsg_2); ENSURE_UNSUBSCRIBED(on_umsg_3); ENSURE_UNSUBSCRIBED(on_umsg_4); ENSURE_UNSUBSCRIBED(on_umsg_5); ENSURE_UNSUBSCRIBED(on_umsg_6); ENSURE_UNSUBSCRIBED(on_ret_msg_1); ENSURE_UNSUBSCRIBED(on_ret_msg_2); ENSURE_UNSUBSCRIBED(on_ret_msg_3); ENSURE_UNSUBSCRIBED(on_ret_msg_4); ENSURE_UNSUBSCRIBED(on_ret_msg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_1); ENSURE_UNSUBSCRIBED(on_ret_umsg_2); ENSURE_UNSUBSCRIBED(on_ret_umsg_3); ENSURE_UNSUBSCRIBED(on_ret_umsg_4); ENSURE_UNSUBSCRIBED(on_ret_umsg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_6); #undef ENSURE_UNSUBSCRIBED so_deregister_agent_coop_normally(); } }; } /* namespace test */ using namespace test; int main() { try { const auto factories = build_subscr_storage_factories(); for( const auto & pair : factories ) { const auto & n = pair.first; const auto & f = pair.second; run_with_time_limit( [&]() { std::cout << n << ": " << std::flush; so_5::launch( [f]( so_5::environment_t & env ) { env.introduce_coop( [f]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(f); } ); } ); std::cout << "OK" << std::endl; }, 20, n ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_6/000077500000000000000000000000001523065210100250745ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_6/CMakeLists.txt000066400000000000000000000001541523065210100276340ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.has_subscription_6) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_6/main.cpp000066400000000000000000000224021523065210100265240ustar00rootroot00000000000000/* * A simple test for so_has_subscription method. */ #include #include #include #include #include #include #include #include #include "../subscr_storage_factories.hpp" #include #include namespace test { class a_test_t : public so_5::agent_t { struct finish : public so_5::signal_t {}; struct sig_1 : public so_5::signal_t {}; struct sig_2 : public so_5::signal_t {}; struct msg_1 : public so_5::message_t {}; struct msg_2 : public so_5::message_t {}; struct msg_3 : public so_5::message_t {}; struct msg_4 : public so_5::message_t {}; struct msg_5 : public so_5::message_t {}; struct umsg_1 {}; struct umsg_2 {}; struct umsg_3 {}; struct umsg_4 {}; struct umsg_5 {}; struct umsg_6 {}; struct ret_msg_1 : public so_5::message_t {}; struct ret_msg_2 : public so_5::message_t {}; struct ret_msg_3 : public so_5::message_t {}; struct ret_msg_4 : public so_5::message_t {}; struct ret_msg_5 : public so_5::message_t {}; struct ret_umsg_1 {}; struct ret_umsg_2 {}; struct ret_umsg_3 {}; struct ret_umsg_4 {}; struct ret_umsg_5 {}; struct ret_umsg_6 {}; public : a_test_t( context_t ctx, so_5::subscription_storage_factory_t factory ) : so_5::agent_t( ctx + factory ) {} virtual void so_define_agent() override { so_default_state() .event( &a_test_t::on_finish ) .event( &a_test_t::on_sig_1 ) .event( &a_test_t::on_sig_2 ) .event( &a_test_t::on_msg_1 ) .event( &a_test_t::on_msg_2 ) .event( &a_test_t::on_msg_3 ) .event( &a_test_t::on_msg_4 ) .event( &a_test_t::on_msg_5 ) .event( &a_test_t::on_umsg_1 ) .event( &a_test_t::on_umsg_2 ) .event( &a_test_t::on_umsg_3 ) .event( &a_test_t::on_umsg_4 ) .event( &a_test_t::on_umsg_5 ) .event( &a_test_t::on_umsg_6 ) .event( &a_test_t::on_ret_msg_1 ) .event( &a_test_t::on_ret_msg_2 ) .event( &a_test_t::on_ret_msg_3 ) .event( &a_test_t::on_ret_msg_4 ) .event( &a_test_t::on_ret_msg_5 ) .event( &a_test_t::on_ret_umsg_1 ) .event( &a_test_t::on_ret_umsg_2 ) .event( &a_test_t::on_ret_umsg_3 ) .event( &a_test_t::on_ret_umsg_4 ) .event( &a_test_t::on_ret_umsg_5 ) .event( &a_test_t::on_ret_umsg_6 ) ; #define ENSURE_SUBSCRIBED(handler) \ ensure( so_default_state().has_subscription(so_direct_mbox(), \ &a_test_t:: handler), \ #handler " must be subscribed" ) ENSURE_SUBSCRIBED(on_sig_1); ENSURE_SUBSCRIBED(on_sig_2); ENSURE_SUBSCRIBED(on_msg_1); ENSURE_SUBSCRIBED(on_msg_2); ENSURE_SUBSCRIBED(on_msg_3); ENSURE_SUBSCRIBED(on_msg_4); ENSURE_SUBSCRIBED(on_msg_5); ENSURE_SUBSCRIBED(on_umsg_1); ENSURE_SUBSCRIBED(on_umsg_2); ENSURE_SUBSCRIBED(on_umsg_3); ENSURE_SUBSCRIBED(on_umsg_4); ENSURE_SUBSCRIBED(on_umsg_5); ENSURE_SUBSCRIBED(on_umsg_6); ENSURE_SUBSCRIBED(on_ret_msg_1); ENSURE_SUBSCRIBED(on_ret_msg_2); ENSURE_SUBSCRIBED(on_ret_msg_3); ENSURE_SUBSCRIBED(on_ret_msg_4); ENSURE_SUBSCRIBED(on_ret_msg_5); ENSURE_SUBSCRIBED(on_ret_umsg_1); ENSURE_SUBSCRIBED(on_ret_umsg_2); ENSURE_SUBSCRIBED(on_ret_umsg_3); ENSURE_SUBSCRIBED(on_ret_umsg_4); ENSURE_SUBSCRIBED(on_ret_umsg_5); ENSURE_SUBSCRIBED(on_ret_umsg_6); #undef ENSURE_SUBSCRIBED } virtual void so_evt_start() override { so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send( *this ); so_5::send>( *this ); so_5::send>( *this ); so_5::send( *this ); so_5::send( *this ); } private: void on_sig_1(const mhood_t &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_sig_1 ); } void on_sig_2(mhood_t) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_sig_2 ); } void on_msg_1(const msg_1 &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_1 ); } void on_msg_2(const mhood_t &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_2 ); } void on_msg_3(mhood_t) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_3 ); } void on_msg_4(mutable_mhood_t) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_4 ); } void on_msg_5(const mutable_mhood_t &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_5 ); } void on_umsg_1(const umsg_1 &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_1 ); } void on_umsg_2(const mhood_t &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_2 ); } void on_umsg_3(mhood_t) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_3 ); } void on_umsg_4(mutable_mhood_t) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_4 ); } void on_umsg_5(const mutable_mhood_t &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_5 ); } void on_umsg_6(umsg_6) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_6 ); } int on_ret_msg_1(const ret_msg_1 &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_1 ); return 0; } int on_ret_msg_2(const mhood_t &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_2 ); return 0; } int on_ret_msg_3(mhood_t) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_3 ); return 0; } int on_ret_msg_4(mutable_mhood_t) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_4 ); return 0; } int on_ret_msg_5(const mutable_mhood_t &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_5 ); return 0; } int on_ret_umsg_1(const ret_umsg_1 &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_1 ); return 0; } int on_ret_umsg_2(const mhood_t &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_2 ); return 0; } int on_ret_umsg_3(mhood_t) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_3 ); return 0; } int on_ret_umsg_4(mutable_mhood_t) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_4 ); return 0; } int on_ret_umsg_5(const mutable_mhood_t &) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_5 ); return 0; } int on_ret_umsg_6(ret_umsg_6) { so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_6 ); return 0; } void on_finish(mhood_t) { #define ENSURE_UNSUBSCRIBED(handler) \ ensure( !so_default_state().has_subscription(so_direct_mbox(), \ &a_test_t:: handler), \ #handler " must not be subscribed" ) ENSURE_UNSUBSCRIBED(on_sig_1); ENSURE_UNSUBSCRIBED(on_sig_2); ENSURE_UNSUBSCRIBED(on_msg_1); ENSURE_UNSUBSCRIBED(on_msg_2); ENSURE_UNSUBSCRIBED(on_msg_3); ENSURE_UNSUBSCRIBED(on_msg_4); ENSURE_UNSUBSCRIBED(on_msg_5); ENSURE_UNSUBSCRIBED(on_umsg_1); ENSURE_UNSUBSCRIBED(on_umsg_2); ENSURE_UNSUBSCRIBED(on_umsg_3); ENSURE_UNSUBSCRIBED(on_umsg_4); ENSURE_UNSUBSCRIBED(on_umsg_5); ENSURE_UNSUBSCRIBED(on_umsg_6); ENSURE_UNSUBSCRIBED(on_ret_msg_1); ENSURE_UNSUBSCRIBED(on_ret_msg_2); ENSURE_UNSUBSCRIBED(on_ret_msg_3); ENSURE_UNSUBSCRIBED(on_ret_msg_4); ENSURE_UNSUBSCRIBED(on_ret_msg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_1); ENSURE_UNSUBSCRIBED(on_ret_umsg_2); ENSURE_UNSUBSCRIBED(on_ret_umsg_3); ENSURE_UNSUBSCRIBED(on_ret_umsg_4); ENSURE_UNSUBSCRIBED(on_ret_umsg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_6); #undef ENSURE_UNSUBSCRIBED so_deregister_agent_coop_normally(); } }; } /* namespace test */ using namespace test; int main() { try { const auto factories = build_subscr_storage_factories(); for( const auto & pair : factories ) { const auto & n = pair.first; const auto & f = pair.second; run_with_time_limit( [&]() { std::cout << n << ": " << std::flush; so_5::launch( [f]( so_5::environment_t & env ) { env.introduce_coop( [f]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(f); } ); } ); std::cout << "OK" << std::endl; }, 20, n ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_7/000077500000000000000000000000001523065210100250755ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_7/CMakeLists.txt000066400000000000000000000001541523065210100276350ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.has_subscription_7) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_7/main.cpp000066400000000000000000000210271523065210100265270ustar00rootroot00000000000000/* * A simple test for so_has_subscription method. */ #include #include #include #include #include #include #include #include #include "../subscr_storage_factories.hpp" #include #include namespace test { class a_test_t : public so_5::agent_t { struct sig_1 : public so_5::signal_t {}; struct sig_2 : public so_5::signal_t {}; struct msg_1 : public so_5::message_t {}; struct msg_2 : public so_5::message_t {}; struct msg_3 : public so_5::message_t {}; struct msg_4 : public so_5::message_t {}; struct msg_5 : public so_5::message_t {}; struct umsg_1 {}; struct umsg_2 {}; struct umsg_3 {}; struct umsg_4 {}; struct umsg_5 {}; struct umsg_6 {}; struct ret_msg_1 : public so_5::message_t {}; struct ret_msg_2 : public so_5::message_t {}; struct ret_msg_3 : public so_5::message_t {}; struct ret_msg_4 : public so_5::message_t {}; struct ret_msg_5 : public so_5::message_t {}; struct ret_umsg_1 {}; struct ret_umsg_2 {}; struct ret_umsg_3 {}; struct ret_umsg_4 {}; struct ret_umsg_5 {}; struct ret_umsg_6 {}; public : a_test_t( context_t ctx, so_5::subscription_storage_factory_t factory ) : so_5::agent_t( ctx + factory ) {} virtual void so_define_agent() override { so_subscribe_self() .event( &a_test_t::on_sig_1 ) .event( &a_test_t::on_sig_2 ) .event( &a_test_t::on_msg_1 ) .event( &a_test_t::on_msg_2 ) .event( &a_test_t::on_msg_3 ) .event( &a_test_t::on_msg_4 ) .event( &a_test_t::on_msg_5 ) .event( &a_test_t::on_umsg_1 ) .event( &a_test_t::on_umsg_2 ) .event( &a_test_t::on_umsg_3 ) .event( &a_test_t::on_umsg_4 ) .event( &a_test_t::on_umsg_5 ) .event( &a_test_t::on_umsg_6 ) .event( &a_test_t::on_ret_msg_1 ) .event( &a_test_t::on_ret_msg_2 ) .event( &a_test_t::on_ret_msg_3 ) .event( &a_test_t::on_ret_msg_4 ) .event( &a_test_t::on_ret_msg_5 ) .event( &a_test_t::on_ret_umsg_1 ) .event( &a_test_t::on_ret_umsg_2 ) .event( &a_test_t::on_ret_umsg_3 ) .event( &a_test_t::on_ret_umsg_4 ) .event( &a_test_t::on_ret_umsg_5 ) .event( &a_test_t::on_ret_umsg_6 ) ; #define ENSURE_SUBSCRIBED(handler) \ ensure( so_has_subscription(so_direct_mbox(), \ so_default_state(), \ &a_test_t:: handler), \ #handler " must be subscribed" ) ENSURE_SUBSCRIBED(on_sig_1); ENSURE_SUBSCRIBED(on_sig_2); ENSURE_SUBSCRIBED(on_msg_1); ENSURE_SUBSCRIBED(on_msg_2); ENSURE_SUBSCRIBED(on_msg_3); ENSURE_SUBSCRIBED(on_msg_4); ENSURE_SUBSCRIBED(on_msg_5); ENSURE_SUBSCRIBED(on_umsg_1); ENSURE_SUBSCRIBED(on_umsg_2); ENSURE_SUBSCRIBED(on_umsg_3); ENSURE_SUBSCRIBED(on_umsg_4); ENSURE_SUBSCRIBED(on_umsg_5); ENSURE_SUBSCRIBED(on_umsg_6); ENSURE_SUBSCRIBED(on_ret_msg_1); ENSURE_SUBSCRIBED(on_ret_msg_2); ENSURE_SUBSCRIBED(on_ret_msg_3); ENSURE_SUBSCRIBED(on_ret_msg_4); ENSURE_SUBSCRIBED(on_ret_msg_5); ENSURE_SUBSCRIBED(on_ret_umsg_1); ENSURE_SUBSCRIBED(on_ret_umsg_2); ENSURE_SUBSCRIBED(on_ret_umsg_3); ENSURE_SUBSCRIBED(on_ret_umsg_4); ENSURE_SUBSCRIBED(on_ret_umsg_5); ENSURE_SUBSCRIBED(on_ret_umsg_6); #undef ENSURE_SUBSCRIBED so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_sig_1 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_sig_2 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_1 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_2 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_3 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_4 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_msg_5 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_1 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_2 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_3 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_4 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_5 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_umsg_6 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_1 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_2 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_3 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_4 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_msg_5 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_1 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_2 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_3 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_4 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_5 ); so_drop_subscription( so_direct_mbox(), so_default_state(), &a_test_t::on_ret_umsg_6 ); #define ENSURE_UNSUBSCRIBED(handler) \ ensure( !so_has_subscription(so_direct_mbox(), \ so_default_state(), \ &a_test_t:: handler), \ #handler " must not be subscribed" ) ENSURE_UNSUBSCRIBED(on_sig_1); ENSURE_UNSUBSCRIBED(on_sig_2); ENSURE_UNSUBSCRIBED(on_msg_1); ENSURE_UNSUBSCRIBED(on_msg_2); ENSURE_UNSUBSCRIBED(on_msg_3); ENSURE_UNSUBSCRIBED(on_msg_4); ENSURE_UNSUBSCRIBED(on_msg_5); ENSURE_UNSUBSCRIBED(on_umsg_1); ENSURE_UNSUBSCRIBED(on_umsg_2); ENSURE_UNSUBSCRIBED(on_umsg_3); ENSURE_UNSUBSCRIBED(on_umsg_4); ENSURE_UNSUBSCRIBED(on_umsg_5); ENSURE_UNSUBSCRIBED(on_umsg_6); ENSURE_UNSUBSCRIBED(on_ret_msg_1); ENSURE_UNSUBSCRIBED(on_ret_msg_2); ENSURE_UNSUBSCRIBED(on_ret_msg_3); ENSURE_UNSUBSCRIBED(on_ret_msg_4); ENSURE_UNSUBSCRIBED(on_ret_msg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_1); ENSURE_UNSUBSCRIBED(on_ret_umsg_2); ENSURE_UNSUBSCRIBED(on_ret_umsg_3); ENSURE_UNSUBSCRIBED(on_ret_umsg_4); ENSURE_UNSUBSCRIBED(on_ret_umsg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_6); #undef ENSURE_UNSUBSCRIBED } virtual void so_evt_start() override { so_deregister_agent_coop_normally(); } private: void on_sig_1(const mhood_t &) const {} void on_sig_2(mhood_t) const {} void on_msg_1(const msg_1 &) const {} void on_msg_2(const mhood_t &) const {} void on_msg_3(mhood_t) const {} void on_msg_4(mutable_mhood_t) const {} void on_msg_5(const mutable_mhood_t &) const {} void on_umsg_1(const umsg_1 &) const {} void on_umsg_2(const mhood_t &) const {} void on_umsg_3(mhood_t) const {} void on_umsg_4(mutable_mhood_t) const {} void on_umsg_5(const mutable_mhood_t &) const {} void on_umsg_6(umsg_6) const {} int on_ret_msg_1(const ret_msg_1 &) const { return 0; } int on_ret_msg_2(const mhood_t &) const { return 0; } int on_ret_msg_3(mhood_t) const { return 0; } int on_ret_msg_4(mutable_mhood_t) const { return 0; } int on_ret_msg_5(const mutable_mhood_t &) const { return 0; } int on_ret_umsg_1(const ret_umsg_1 &) const { return 0; } int on_ret_umsg_2(const mhood_t &) const { return 0; } int on_ret_umsg_3(mhood_t) const { return 0; } int on_ret_umsg_4(mutable_mhood_t) const { return 0; } int on_ret_umsg_5(const mutable_mhood_t &) const { return 0; } int on_ret_umsg_6(ret_umsg_6) const { return 0; } }; } /* namespace test */ using namespace test; int main() { try { const auto factories = build_subscr_storage_factories(); for( const auto & pair : factories ) { const auto & n = pair.first; const auto & f = pair.second; run_with_time_limit( [&]() { std::cout << n << ": " << std::flush; so_5::launch( [f]( so_5::environment_t & env ) { env.introduce_coop( [f]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(f); } ); } ); std::cout << "OK" << std::endl; }, 20, n ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_8/000077500000000000000000000000001523065210100250765ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_8/CMakeLists.txt000066400000000000000000000001541523065210100276360ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.has_subscription_8) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/has_subscription_8/main.cpp000066400000000000000000000204751523065210100265360ustar00rootroot00000000000000/* * A simple test for so_has_subscription method. */ #include #include #include #include #include #include #include #include #include "../subscr_storage_factories.hpp" #include #include namespace test { class a_test_t : public so_5::agent_t { struct sig_1 : public so_5::signal_t {}; struct sig_2 : public so_5::signal_t {}; struct msg_1 : public so_5::message_t {}; struct msg_2 : public so_5::message_t {}; struct msg_3 : public so_5::message_t {}; struct msg_4 : public so_5::message_t {}; struct msg_5 : public so_5::message_t {}; struct umsg_1 {}; struct umsg_2 {}; struct umsg_3 {}; struct umsg_4 {}; struct umsg_5 {}; struct umsg_6 {}; struct ret_msg_1 : public so_5::message_t {}; struct ret_msg_2 : public so_5::message_t {}; struct ret_msg_3 : public so_5::message_t {}; struct ret_msg_4 : public so_5::message_t {}; struct ret_msg_5 : public so_5::message_t {}; struct ret_umsg_1 {}; struct ret_umsg_2 {}; struct ret_umsg_3 {}; struct ret_umsg_4 {}; struct ret_umsg_5 {}; struct ret_umsg_6 {}; public : a_test_t( context_t ctx, so_5::subscription_storage_factory_t factory ) : so_5::agent_t( ctx + factory ) {} virtual void so_define_agent() override { so_default_state() .event( &a_test_t::on_sig_1 ) .event( &a_test_t::on_sig_2 ) .event( &a_test_t::on_msg_1 ) .event( &a_test_t::on_msg_2 ) .event( &a_test_t::on_msg_3 ) .event( &a_test_t::on_msg_4 ) .event( &a_test_t::on_msg_5 ) .event( &a_test_t::on_umsg_1 ) .event( &a_test_t::on_umsg_2 ) .event( &a_test_t::on_umsg_3 ) .event( &a_test_t::on_umsg_4 ) .event( &a_test_t::on_umsg_5 ) .event( &a_test_t::on_umsg_6 ) .event( &a_test_t::on_ret_msg_1 ) .event( &a_test_t::on_ret_msg_2 ) .event( &a_test_t::on_ret_msg_3 ) .event( &a_test_t::on_ret_msg_4 ) .event( &a_test_t::on_ret_msg_5 ) .event( &a_test_t::on_ret_umsg_1 ) .event( &a_test_t::on_ret_umsg_2 ) .event( &a_test_t::on_ret_umsg_3 ) .event( &a_test_t::on_ret_umsg_4 ) .event( &a_test_t::on_ret_umsg_5 ) .event( &a_test_t::on_ret_umsg_6 ) ; #define ENSURE_SUBSCRIBED(handler) \ ensure( so_default_state().has_subscription(so_direct_mbox(), \ &a_test_t:: handler), \ #handler " must be subscribed" ) ENSURE_SUBSCRIBED(on_sig_1); ENSURE_SUBSCRIBED(on_sig_2); ENSURE_SUBSCRIBED(on_msg_1); ENSURE_SUBSCRIBED(on_msg_2); ENSURE_SUBSCRIBED(on_msg_3); ENSURE_SUBSCRIBED(on_msg_4); ENSURE_SUBSCRIBED(on_msg_5); ENSURE_SUBSCRIBED(on_umsg_1); ENSURE_SUBSCRIBED(on_umsg_2); ENSURE_SUBSCRIBED(on_umsg_3); ENSURE_SUBSCRIBED(on_umsg_4); ENSURE_SUBSCRIBED(on_umsg_5); ENSURE_SUBSCRIBED(on_umsg_6); ENSURE_SUBSCRIBED(on_ret_msg_1); ENSURE_SUBSCRIBED(on_ret_msg_2); ENSURE_SUBSCRIBED(on_ret_msg_3); ENSURE_SUBSCRIBED(on_ret_msg_4); ENSURE_SUBSCRIBED(on_ret_msg_5); ENSURE_SUBSCRIBED(on_ret_umsg_1); ENSURE_SUBSCRIBED(on_ret_umsg_2); ENSURE_SUBSCRIBED(on_ret_umsg_3); ENSURE_SUBSCRIBED(on_ret_umsg_4); ENSURE_SUBSCRIBED(on_ret_umsg_5); ENSURE_SUBSCRIBED(on_ret_umsg_6); #undef ENSURE_SUBSCRIBED so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_sig_1 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_sig_2 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_1 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_2 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_3 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_4 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_msg_5 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_1 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_2 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_3 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_4 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_5 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_umsg_6 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_1 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_2 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_3 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_4 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_msg_5 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_1 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_2 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_3 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_4 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_5 ); so_default_state().drop_subscription( so_direct_mbox(), &a_test_t::on_ret_umsg_6 ); #define ENSURE_UNSUBSCRIBED(handler) \ ensure( !so_default_state().has_subscription(so_direct_mbox(), \ &a_test_t:: handler), \ #handler " must not be subscribed" ) ENSURE_UNSUBSCRIBED(on_sig_1); ENSURE_UNSUBSCRIBED(on_sig_2); ENSURE_UNSUBSCRIBED(on_msg_1); ENSURE_UNSUBSCRIBED(on_msg_2); ENSURE_UNSUBSCRIBED(on_msg_3); ENSURE_UNSUBSCRIBED(on_msg_4); ENSURE_UNSUBSCRIBED(on_msg_5); ENSURE_UNSUBSCRIBED(on_umsg_1); ENSURE_UNSUBSCRIBED(on_umsg_2); ENSURE_UNSUBSCRIBED(on_umsg_3); ENSURE_UNSUBSCRIBED(on_umsg_4); ENSURE_UNSUBSCRIBED(on_umsg_5); ENSURE_UNSUBSCRIBED(on_umsg_6); ENSURE_UNSUBSCRIBED(on_ret_msg_1); ENSURE_UNSUBSCRIBED(on_ret_msg_2); ENSURE_UNSUBSCRIBED(on_ret_msg_3); ENSURE_UNSUBSCRIBED(on_ret_msg_4); ENSURE_UNSUBSCRIBED(on_ret_msg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_1); ENSURE_UNSUBSCRIBED(on_ret_umsg_2); ENSURE_UNSUBSCRIBED(on_ret_umsg_3); ENSURE_UNSUBSCRIBED(on_ret_umsg_4); ENSURE_UNSUBSCRIBED(on_ret_umsg_5); ENSURE_UNSUBSCRIBED(on_ret_umsg_6); #undef ENSURE_UNSUBSCRIBED } virtual void so_evt_start() override { so_deregister_agent_coop_normally(); } private: void on_sig_1(const mhood_t &) const {} void on_sig_2(mhood_t) const {} void on_msg_1(const msg_1 &) const {} void on_msg_2(const mhood_t &) const {} void on_msg_3(mhood_t) const {} void on_msg_4(mutable_mhood_t) const {} void on_msg_5(const mutable_mhood_t &) const {} void on_umsg_1(const umsg_1 &) const {} void on_umsg_2(const mhood_t &) const {} void on_umsg_3(mhood_t) const {} void on_umsg_4(mutable_mhood_t) const {} void on_umsg_5(const mutable_mhood_t &) const {} void on_umsg_6(umsg_6) const {} int on_ret_msg_1(const ret_msg_1 &) const { return 0; } int on_ret_msg_2(const mhood_t &) const { return 0; } int on_ret_msg_3(mhood_t) const { return 0; } int on_ret_msg_4(mutable_mhood_t) const { return 0; } int on_ret_msg_5(const mutable_mhood_t &) const { return 0; } int on_ret_umsg_1(const ret_umsg_1 &) const { return 0; } int on_ret_umsg_2(const mhood_t &) const { return 0; } int on_ret_umsg_3(mhood_t) const { return 0; } int on_ret_umsg_4(mutable_mhood_t) const { return 0; } int on_ret_umsg_5(const mutable_mhood_t &) const { return 0; } int on_ret_umsg_6(ret_umsg_6) const { return 0; } }; } /* namespace test */ using namespace test; int main() { try { const auto factories = build_subscr_storage_factories(); for( const auto & pair : factories ) { const auto & n = pair.first; const auto & f = pair.second; run_with_time_limit( [&]() { std::cout << n << ": " << std::flush; so_5::launch( [f]( so_5::environment_t & env ) { env.introduce_coop( [f]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(f); } ); } ); std::cout << "OK" << std::endl; }, 20, n ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/ignore_exception/000077500000000000000000000000001523065210100246315ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/ignore_exception/CMakeLists.txt000066400000000000000000000001521523065210100273670ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.ignore_exception) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/ignore_exception/main.cpp000066400000000000000000000025721523065210100262670ustar00rootroot00000000000000/* * A test for checking so_5::ignore_exception behaviour. */ #include #include #include struct msg_test_signal : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env ) : base_type_t( env ) , m_self_mbox( env.create_mbox() ) , m_counter( 0 ) , m_max_attempts( 3 ) {} void so_define_agent() override { so_subscribe( m_self_mbox ).event( &a_test_t::evt_signal ); } void so_evt_start() override { so_5::send< msg_test_signal >( m_self_mbox ); } void evt_signal( mhood_t< msg_test_signal > ) { if( m_counter < m_max_attempts ) { m_counter += 1; so_5::send< msg_test_signal >( m_self_mbox ); throw std::runtime_error( "Another exception from evt_signal" ); } else so_environment().stop(); } so_5::exception_reaction_t so_exception_reaction() const noexcept override { return so_5::ignore_exception; } private : const so_5::mbox_t m_self_mbox; int m_counter; const int m_max_attempts; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/skip_demands_on_dereg/000077500000000000000000000000001523065210100255735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/skip_demands_on_dereg/CMakeLists.txt000066400000000000000000000001571523065210100303360ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.skip_demands_on_dereg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/skip_demands_on_dereg/main.cpp000066400000000000000000000026121523065210100272240ustar00rootroot00000000000000/* * A simple test for so_5::skip_demands_on_dereg. */ #include #include #include namespace test { class a_test_t final : public so_5::agent_t { struct first final : public so_5::signal_t {}; struct second final : public so_5::signal_t {}; struct third final : public so_5::signal_t {}; public: a_test_t( context_t ctx ) : so_5::agent_t{ ctx + so_5::skip_demands_on_dereg } {} void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ) .event( []( mhood_t ) { throw std::runtime_error{ "second message received" }; } ) .event( []( mhood_t ) { throw std::runtime_error{ "third message received" }; } ) ; } void so_evt_start() override { so_5::send< first >( *this ); so_5::send< second >( *this ); so_5::send< third >( *this ); } }; } /* namespace test */ using namespace test; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/subscr_storage_factories.hpp000066400000000000000000000017271523065210100270740ustar00rootroot00000000000000#pragma once #include #include #include #include [[nodiscard]] std::vector< std::pair< std::string, so_5::subscription_storage_factory_t > > build_subscr_storage_factories() { using namespace std::string_literals; return { { "vector[1]"s, so_5::vector_based_subscription_storage_factory( 1 ) } , { "vector[8]"s, so_5::vector_based_subscription_storage_factory( 8 ) } , { "vector[16]"s, so_5::vector_based_subscription_storage_factory( 16 ) } , { "map"s, so_5::map_based_subscription_storage_factory() } , { "hash_table"s, so_5::hash_table_based_subscription_storage_factory() } , { "flat_set[1]"s, so_5::flat_set_based_subscription_storage_factory( 1 ) } , { "flat_set[8]"s, so_5::flat_set_based_subscription_storage_factory( 8 ) } , { "flat_set[16]"s, so_5::flat_set_based_subscription_storage_factory( 16 ) } , { "default"s, so_5::default_subscription_storage_factory() } }; } sobjectizer-5.8.6/dev/test/so_5/event_handler/subscribe_before_reg/000077500000000000000000000000001523065210100254305ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/subscribe_before_reg/CMakeLists.txt000066400000000000000000000001561523065210100301720ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.subscribe_before_reg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/subscribe_before_reg/main.cpp000066400000000000000000000026721523065210100270670ustar00rootroot00000000000000/* * A simple test for subscription before agent registration. */ #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { struct msg_1 : public so_5::signal_t {}; struct msg_2 : public so_5::signal_t {}; struct msg_3 : public so_5::signal_t {}; const so_5::state_t st_1{ this, "st_1" }; const so_5::state_t st_2{ this, "st_2" }; const so_5::state_t st_3{ this, "st_3" }; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) { this >>= st_1; st_1.event( [this](mhood_t< msg_1 >) { st_2.activate(); so_5::send< msg_2 >( *this ); } ); st_2.event( [this](mhood_t< msg_2 >) { st_3.activate(); so_5::send< msg_3 >( *this ); } ); st_3.event( [this](mhood_t< msg_3 >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_1 >( *this ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } ); }, 20, "subscription before registration test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/subscribe_errors/000077500000000000000000000000001523065210100246455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/subscribe_errors/CMakeLists.txt000066400000000000000000000001521523065210100274030ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.subscribe_errors) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/subscribe_errors/main.cpp000066400000000000000000000054431523065210100263030ustar00rootroot00000000000000/* * A test for event subscription and unsubsription. */ #include #include #include #include #define PERROR_AND_ABORT_IMPL(file, line, condition_text) \ std::cerr << file << ":" << line << ": exception expected but not thrown: " << condition_text << std::endl; \ std::abort(); #define ENSURE_EXCEPTION(condition) \ { \ bool exception_thrown = false; \ try { condition; } \ catch( const so_5::exception_t & ) { exception_thrown = true; } \ if( !exception_thrown ) \ { \ PERROR_AND_ABORT_IMPL(__FILE__, __LINE__, #condition ) \ } \ } struct msg_test : public so_5::signal_t {}; struct msg_stop : public so_5::signal_t {}; class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; const so_5::state_t m_state_a{ this }; const so_5::state_t m_state_b{ this }; public: test_agent_t( so_5::environment_t & env ) : base_type_t( env ), m_mbox( so_environment().create_mbox() ) {} ~test_agent_t() override {} void so_define_agent() override; void so_evt_start() override; void evt_handler1( mhood_t< msg_test > ) { // Should be called in default state. } void evt_handler3( mhood_t< msg_test > ) { std::cerr << "Error: evt_handler3 called..." << std::endl; std::abort(); } void evt_stop( mhood_t< msg_stop > ) { so_environment().stop(); } private: // Mbox to subscribe. so_5::mbox_t m_mbox; }; void test_agent_t::so_define_agent() { so_subscribe( m_mbox ) .event( &test_agent_t::evt_stop ); } void test_agent_t::so_evt_start() { // Subscribe by one handler. // Then trying to subscribe by another handler. Error expected. so_subscribe( m_mbox ).event( &test_agent_t::evt_handler1 ); so_subscribe( m_mbox ).in( m_state_a ).event( &test_agent_t::evt_handler1 ); so_subscribe( m_mbox ).in( m_state_b ).event( &test_agent_t::evt_handler3 ); ENSURE_EXCEPTION( so_subscribe( m_mbox ).event( &test_agent_t::evt_handler1 ) ) ENSURE_EXCEPTION( so_subscribe( m_mbox ).event( &test_agent_t::evt_handler3 ) ) ENSURE_EXCEPTION( so_subscribe( m_mbox ).in( m_state_a ).event( &test_agent_t::evt_handler1 ) ) ENSURE_EXCEPTION( so_subscribe( m_mbox ).in( m_state_a ).event( &test_agent_t::evt_handler3 ) ) ENSURE_EXCEPTION( so_subscribe( m_mbox ).in( m_state_b ).event( &test_agent_t::evt_handler1 ) ) ENSURE_EXCEPTION( so_subscribe( m_mbox ).in( m_state_b ).event( &test_agent_t::evt_handler3 ) ) so_5::send< msg_test >( m_mbox ); so_5::send< msg_stop >( m_mbox ); } void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/thread_safety_check/000077500000000000000000000000001523065210100252475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/thread_safety_check/CMakeLists.txt000066400000000000000000000001551523065210100300100ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.thread_safety_check) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/thread_safety_check/main.cpp000066400000000000000000000047471523065210100267130ustar00rootroot00000000000000/* * A test for checking of actions inside thread_safe event-handlers. */ #include #include #include class a_test_t final : public so_5::agent_t { struct first final : public so_5::signal_t {}; struct second final : public so_5::signal_t {}; struct quit final : public so_5::signal_t {}; state_t st_dummy{ this }; struct dummy_msg final : public so_5::message_t { int m_key; dummy_msg( int key ) : m_key{key} {} }; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_test_t::evt_first ) .event( &a_test_t::evt_second, so_5::thread_safe ) .event( &a_test_t::evt_quit ) ; } void so_evt_start() override { so_5::send< first >( *this ); so_5::send< second >( *this ); so_5::send< quit >( *this ); } private: void evt_first( mhood_t ) { so_subscribe_self() .in( st_dummy ) .event( []( mhood_t ) {} ); this >>= st_dummy; so_drop_subscription< dummy_msg >( so_direct_mbox(), st_dummy ); this >>= so_default_state(); } void evt_second( mhood_t ) { ensure_throws( [this]() { so_subscribe_self() .in( st_dummy ) .event( []( mhood_t ) {} ); }, "create subscription should fail" ); ensure_throws( [this]() { st_dummy.activate(); }, "change agent state should fail" ); ensure_throws( [this]() { so_drop_subscription< dummy_msg >( so_direct_mbox(), st_dummy ); }, "dropping subscription should fail" ); ensure_throws( [this]() { so_default_state().activate(); }, "change agent state should fail" ); } void evt_quit( mhood_t ) { so_deregister_agent_coop_normally(); } template< typename Lambda > void ensure_throws( Lambda && lambda, const char * failure_description ) { bool exception_thrown = false; try { lambda(); } catch( const so_5::exception_t & ) { exception_thrown = true; } if( !exception_thrown ) throw std::runtime_error{ failure_description }; } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/unknown_exception_evt_finish/000077500000000000000000000000001523065210100272635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/unknown_exception_evt_finish/CMakeLists.txt000066400000000000000000000001661523065210100320260ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.unknown_exception_evt_finish) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/unknown_exception_evt_finish/main.cpp000066400000000000000000000015641523065210100307210ustar00rootroot00000000000000/* * Test for unknown exception from so_evt_start. */ #include #include class test_agent_t final : public so_5::agent_t { public : test_agent_t( context_t ctx ) : agent_t{ std::move(ctx) } {} void so_evt_start() override { so_deregister_agent_coop_normally(); } void so_evt_finish() override { throw( "boom!" ); } so_5::exception_reaction_t so_exception_reaction() const noexcept override { return so_5::shutdown_sobjectizer_on_exception; } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/unknown_exception_evt_handler/000077500000000000000000000000001523065210100274205ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/unknown_exception_evt_handler/CMakeLists.txt000066400000000000000000000001671523065210100321640ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.unknown_exception_evt_handler) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/unknown_exception_evt_handler/main.cpp000066400000000000000000000017651523065210100310610ustar00rootroot00000000000000/* * Test for unknown exception from event handler. */ #include #include class test_agent_t final : public so_5::agent_t { struct do_check final : public so_5::signal_t {}; public : test_agent_t( context_t ctx ) : agent_t{ std::move(ctx) } { so_subscribe_self().event( &test_agent_t::on_do_check ); } void so_evt_start() override { so_5::send< do_check >( *this ); } so_5::exception_reaction_t so_exception_reaction() const noexcept override { return so_5::shutdown_sobjectizer_on_exception; } private : void on_do_check( mhood_t ) { throw( "boom!" ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_handler/unknown_exception_evt_start/000077500000000000000000000000001523065210100271405ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_handler/unknown_exception_evt_start/CMakeLists.txt000066400000000000000000000001651523065210100317020ustar00rootroot00000000000000set(UNITTEST _unit.test.event_handler.unknown_exception_evt_start) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_handler/unknown_exception_evt_start/main.cpp000066400000000000000000000014461523065210100305750ustar00rootroot00000000000000/* * Test for unknown exception from so_evt_start. */ #include #include class test_agent_t final : public so_5::agent_t { public : test_agent_t( context_t ctx ) : agent_t{ std::move(ctx) } {} void so_evt_start() override { throw( "boom!" ); } so_5::exception_reaction_t so_exception_reaction() const noexcept override { return so_5::shutdown_sobjectizer_on_exception; } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 2; } return 0; } sobjectizer-5.8.6/dev/test/so_5/event_queue_hook/000077500000000000000000000000001523065210100220175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_queue_hook/CMakeLists.txt000066400000000000000000000000511523065210100245530ustar00rootroot00000000000000project(tests) add_subdirectory(simple) sobjectizer-5.8.6/dev/test/so_5/event_queue_hook/simple/000077500000000000000000000000001523065210100233105ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/event_queue_hook/simple/CMakeLists.txt000066400000000000000000000001431523065210100260460ustar00rootroot00000000000000set(UNITTEST _unit.test.event_queue_hook.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/event_queue_hook/simple/main.cpp000066400000000000000000000066741523065210100247550ustar00rootroot00000000000000/* * Simple test for event_queue_hook. */ #include #include #include class test_agent_t final : public so_5::agent_t { struct hello final : public so_5::signal_t {}; public : test_agent_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send< hello >( *this ); } }; void generate_agents( so_5::environment_t & env, std::size_t agent_count ) { using namespace so_5::disp::thread_pool; auto disp = make_dispatcher( env ); for( std::size_t i = 0u; i != agent_count; ++i ) { env.register_agent_as_coop( env.make_agent< test_agent_t >(), disp.binder() ); } } class test_event_queue_t final : public so_5::event_queue_t { so_5::event_queue_t * m_actual; public : static std::atomic< std::size_t > m_instances_created; static std::atomic< std::size_t > m_instances_destroyed; test_event_queue_t( so_5::event_queue_t * actual ) : m_actual( actual ) { ++m_instances_created; } ~test_event_queue_t() override { ++m_instances_destroyed; } void push( so_5::execution_demand_t demand ) override { m_actual->push( std::move(demand) ); } void push_evt_start( so_5::execution_demand_t demand ) override { this->push( std::move(demand) ); } void push_evt_finish( so_5::execution_demand_t demand ) noexcept override { this->push( std::move(demand) ); } }; std::atomic< std::size_t > test_event_queue_t::m_instances_created( 0u ); std::atomic< std::size_t > test_event_queue_t::m_instances_destroyed( 0u ); class test_event_queue_hook_t final : public so_5::event_queue_hook_t { std::atomic< std::size_t > m_created; std::atomic< std::size_t > m_destroyed; public : test_event_queue_hook_t() : m_created( 0u ) , m_destroyed( 0u ) {} [[nodiscard]] so_5::event_queue_t * on_bind( so_5::agent_t * /*agent*/, so_5::event_queue_t * original_queue ) noexcept override { ++m_created; return new test_event_queue_t( original_queue ); } void on_unbind( so_5::agent_t * /*agent*/, so_5::event_queue_t * queue ) noexcept override { ++m_destroyed; delete queue; } [[nodiscard]] std::size_t created() const noexcept { return m_created.load(); } [[nodiscard]] std::size_t destroyed() const noexcept { return m_destroyed.load(); } }; void do_test() { std::size_t test_agents = 137; test_event_queue_hook_t hook; so_5::launch( [test_agents]( so_5::environment_t & env ) { generate_agents( env, test_agents ); }, [&]( so_5::environment_params_t & params ) { params.event_queue_hook( so_5::event_queue_hook_unique_ptr_t( &hook, &so_5::event_queue_hook_t::noop_deleter ) ); } ); const auto ensure_equal = [](const char * name, std::size_t expected, std::size_t actual) { std::ostringstream ss; ss << "check: " << name << "; expected=" << expected << ", actual=" << actual; ensure_or_die( expected == actual, ss.str() ); }; ensure_equal( "created", test_agents, hook.created() ); ensure_equal( "destroyed", test_agents, hook.destroyed() ); ensure_equal( "instances_created", test_agents, test_event_queue_t::m_instances_created.load() ); ensure_equal( "instances_destroyed", test_agents, test_event_queue_t::m_instances_destroyed.load() ); } int main() { run_with_time_limit( do_test, 10 ); } sobjectizer-5.8.6/dev/test/so_5/execution_hint/000077500000000000000000000000001523065210100214775ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/execution_hint/basic_checks/000077500000000000000000000000001523065210100241005ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/execution_hint/basic_checks/CMakeLists.txt000066400000000000000000000001471523065210100266420ustar00rootroot00000000000000set(UNITTEST _unit.test.execution_hint.basic_checks) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/execution_hint/basic_checks/main.cpp000066400000000000000000000067551523065210100255450ustar00rootroot00000000000000/* * Test for basic operations with execution hints. */ #define SO_5_EXECUTION_HINT_UNIT_TEST #include #include class test_environment_t : public so_5::environment_t { public : test_environment_t() : so_5::environment_t( so_5::environment_params_t() ) {} void init() override {} }; struct msg_signal : public so_5::signal_t {}; struct msg_thread_safe_signal : public so_5::signal_t {}; struct msg_get_status : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env ) : so_5::agent_t( env ) , m_signal_handled( false ) , m_thread_safe_signal_handled( false ) , m_get_status_handled( false ) {} void evt_signal(mhood_t< msg_signal >) { m_signal_handled = true; } void evt_thread_safe_signal(mhood_t< msg_thread_safe_signal >) { m_thread_safe_signal_handled = true; } std::string evt_get_status(mhood_t< msg_get_status >) { m_get_status_handled = true; return "OK"; } bool m_signal_handled; bool m_thread_safe_signal_handled; bool m_get_status_handled; }; UT_UNIT_TEST( no_handlers ) { using namespace so_5; test_environment_t env; a_test_t agent( env ); { execution_demand_t demand( &agent, message_limit::control_block_t::none(), 0, typeid(msg_signal), message_ref_t(), agent_t::get_demand_handler_on_message_ptr() ); auto hint = agent_t::so_create_execution_hint( demand ); UT_CHECK_EQ( false, hint ); } { execution_demand_t demand( &agent, message_limit::control_block_t::none(), 0, typeid(msg_signal), message_ref_t(), agent_t::get_demand_handler_on_start_ptr() ); auto hint = agent_t::so_create_execution_hint( demand ); UT_CHECK_EQ( true, hint ); } { execution_demand_t demand( &agent, message_limit::control_block_t::none(), 0, typeid(msg_signal), message_ref_t(), agent_t::get_demand_handler_on_finish_ptr() ); auto hint = agent_t::so_create_execution_hint( demand ); UT_CHECK_EQ( true, hint ); } } UT_UNIT_TEST( event_handler ) { using namespace so_5; test_environment_t env; a_test_t agent( env ); agent.so_subscribe( agent.so_direct_mbox() ) .event( &a_test_t::evt_signal ); agent.so_subscribe( agent.so_direct_mbox() ) .event( &a_test_t::evt_thread_safe_signal, thread_safe ); { execution_demand_t demand( &agent, message_limit::control_block_t::none(), agent.so_direct_mbox()->id(), typeid(msg_signal), message_ref_t(), agent_t::get_demand_handler_on_message_ptr() ); auto hint = agent_t::so_create_execution_hint( demand ); UT_CHECK_EQ( true, hint ); UT_CHECK_EQ( false, hint.is_thread_safe() ); UT_CHECK_EQ( false, agent.m_signal_handled ); hint.exec( query_current_thread_id() ); UT_CHECK_EQ( true, agent.m_signal_handled ); } { execution_demand_t demand( &agent, message_limit::control_block_t::none(), agent.so_direct_mbox()->id(), typeid(msg_thread_safe_signal), message_ref_t(), agent_t::get_demand_handler_on_message_ptr() ); auto hint = agent_t::so_create_execution_hint( demand ); UT_CHECK_EQ( true, hint ); UT_CHECK_EQ( true, hint.is_thread_safe() ); UT_CHECK_EQ( false, agent.m_thread_safe_signal_handled ); hint.exec( query_current_thread_id() ); UT_CHECK_EQ( true, agent.m_thread_safe_signal_handled ); } } int main() { UT_RUN_UNIT_TEST( no_handlers ) UT_RUN_UNIT_TEST( event_handler ) return 0; } sobjectizer-5.8.6/dev/test/so_5/internal_stats/000077500000000000000000000000001523065210100215045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/internal_stats/CMakeLists.txt000066400000000000000000000004771523065210100242540ustar00rootroot00000000000000add_subdirectory(simple_turn_on) add_subdirectory(simple_coop_count) add_subdirectory(simple_named_mbox_count) add_subdirectory(simple_timer_thread) add_subdirectory(simple_work_thread_activity) add_subdirectory(simple_work_thread_activity_wrapped_env) add_subdirectory(quantity_int) add_subdirectory(all_dispatchers) sobjectizer-5.8.6/dev/test/so_5/internal_stats/all_dispatchers/000077500000000000000000000000001523065210100246455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/internal_stats/all_dispatchers/CMakeLists.txt000066400000000000000000000002221523065210100274010ustar00rootroot00000000000000set(UNITTEST _test.internal_stats.all_dispatchers) add_executable(${UNITTEST} main.cpp) target_link_libraries(${UNITTEST} sobjectizer::SharedLib) sobjectizer-5.8.6/dev/test/so_5/internal_stats/all_dispatchers/main.cpp000066400000000000000000000215471523065210100263060ustar00rootroot00000000000000/* * Demo application to show run-time monitoring information * from all types of standard dispatchers. */ #include #include #include #include #include #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif class a_worker_t : public so_5::agent_t { public : static const std::size_t targets_count = 4; struct hello : public so_5::signal_t {}; a_worker_t( context_t ctx ) : so_5::agent_t( ctx + limit_then_drop< hello >( targets_count ) ) {} void set_targets( const so_5::mbox_t targets[ targets_count ] ) { std::copy( targets, targets + targets_count, m_targets ); } void so_define_agent() override { so_default_state().event( &a_worker_t::evt_hello ); } private : so_5::mbox_t m_targets[ targets_count ]; void evt_hello(mhood_t< hello >) { using namespace std; for_each( begin( m_targets ), end( m_targets ), []( const so_5::mbox_t & t ) { so_5::send< hello >( t ); } ); } }; class a_controller_t : public so_5::agent_t { public : a_controller_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_subscribe( so_environment().stats_controller().mbox() ) .event( &a_controller_t::evt_monitor_quantity ) .event( &a_controller_t::evt_activity_tracking ); so_default_state().event( [this](mhood_t< finish >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { create_child_coops(); so_environment().stats_controller().set_distribution_period( std::chrono::milliseconds(500) ); so_environment().stats_controller().turn_on(); so_5::send_delayed< finish >( *this, std::chrono::seconds( 6 ) ); } private : struct finish : public so_5::signal_t {}; using workers_vector_t = std::vector< a_worker_t * >; void evt_monitor_quantity( const so_5::stats::messages::quantity< std::size_t > & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix.c_str() << evt.m_suffix.c_str() << ": " << evt.m_value << std::endl; } void evt_activity_tracking( const so_5::stats::messages::work_thread_activity & evt ) { std::cout << evt.m_prefix << evt.m_suffix << " [" << evt.m_thread_id << "] ->\n " << evt.m_prefix << evt.m_suffix << evt.m_stats.m_working_stats << evt.m_stats.m_waiting_stats << std::endl; } void create_child_coops() { auto coop = so_5::create_child_coop( *this ); workers_vector_t workers; workers.reserve( 100 ); create_children_on_default_disp( *coop, workers ); create_children_on_one_thread_disp( *coop, workers ); create_children_on_nef_one_thread_disp( *coop, workers ); create_children_on_active_obj_disp( *coop, workers ); create_children_on_active_group_disp( *coop, workers ); create_children_on_thread_pool_disp_1( *coop, workers ); create_children_on_thread_pool_disp_2( *coop, workers ); create_children_on_adv_thread_pool_disp_1( *coop, workers ); create_children_on_adv_thread_pool_disp_2( *coop, workers ); create_children_on_prio_ot_strictly_ordered_disp( *coop, workers ); create_children_on_prio_ot_quoted_round_robin_disp( *coop, workers ); create_children_on_prio_dt_one_per_prio_disp( *coop, workers ); connect_workers( workers ); so_environment().register_coop( std::move( coop ) ); send_initial_hello( workers ); } void create_children_on_default_disp( so_5::coop_t & coop, workers_vector_t & workers ) { for( int i = 0; i != 5; ++i ) workers.push_back( coop.make_agent< a_worker_t >() ); } void create_children_on_one_thread_disp( so_5::coop_t & coop, workers_vector_t & workers ) { auto disp = so_5::disp::one_thread::make_dispatcher( so_environment() ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder(); } ); } void create_children_on_nef_one_thread_disp( so_5::coop_t & coop, workers_vector_t & workers ) { auto disp = so_5::disp::nef_one_thread::make_dispatcher( so_environment() ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder(); } ); } void create_children_on_active_obj_disp( so_5::coop_t & coop, workers_vector_t & workers ) { auto disp = so_5::disp::active_obj::make_dispatcher( so_environment() ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder(); } ); } void create_children_on_active_group_disp( so_5::coop_t & coop, workers_vector_t & workers ) { auto disp = so_5::disp::active_group::make_dispatcher( so_environment() ); unsigned int group_no = 0; create_children_on( coop, workers, [d=std::move(disp), &group_no] { return d.binder( "group#" + std::to_string( ++group_no ) ); } ); } void create_children_on_thread_pool_disp_1( so_5::coop_t & coop, workers_vector_t & workers ) { auto disp = so_5::disp::thread_pool::make_dispatcher( so_environment() ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder(); } ); } void create_children_on_thread_pool_disp_2( so_5::coop_t & coop, workers_vector_t & workers ) { using namespace so_5::disp::thread_pool; auto disp = make_dispatcher( so_environment() ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder( bind_params_t{}.fifo( fifo_t::individual ) ); } ); } void create_children_on_adv_thread_pool_disp_1( so_5::coop_t & coop, workers_vector_t & workers ) { using namespace so_5::disp::adv_thread_pool; auto disp = make_dispatcher( so_environment() ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder(); } ); } void create_children_on_adv_thread_pool_disp_2( so_5::coop_t & coop, workers_vector_t & workers ) { using namespace so_5::disp::adv_thread_pool; auto disp = make_dispatcher( so_environment() ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder( bind_params_t{}.fifo( fifo_t::individual ) ); } ); } void create_children_on_prio_ot_strictly_ordered_disp( so_5::coop_t & coop, workers_vector_t & workers ) { auto disp = so_5::disp::prio_one_thread::strictly_ordered:: make_dispatcher( so_environment() ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder(); } ); } void create_children_on_prio_ot_quoted_round_robin_disp( so_5::coop_t & coop, workers_vector_t & workers ) { using namespace so_5::disp::prio_one_thread::quoted_round_robin; auto disp = make_dispatcher( so_environment(), quotes_t{ 20 } ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder(); } ); } void create_children_on_prio_dt_one_per_prio_disp( so_5::coop_t & coop, workers_vector_t & workers ) { using namespace so_5::disp::prio_dedicated_threads::one_per_prio; auto disp = make_dispatcher( so_environment() ); create_children_on( coop, workers, [d=std::move(disp)] { return d.binder(); } ); } template< typename LAMBDA > void create_children_on( so_5::coop_t & coop, workers_vector_t & workers, LAMBDA binder ) { for( int i = 0; i != 5; ++i ) workers.push_back( coop.make_agent_with_binder< a_worker_t >( binder() ) ); } void connect_workers( const workers_vector_t & workers ) { using namespace std; for( auto w : workers ) { so_5::mbox_t targets[ a_worker_t::targets_count ]; generate( begin( targets ), end( targets ), [&workers] { return workers[ random_index( workers.size() ) ] ->so_direct_mbox(); } ); w->set_targets( targets ); } } void send_initial_hello( const workers_vector_t & workers ) { so_5::send< a_worker_t::hello >( *( workers[ random_index( workers.size() ) ] ) ); } static std::size_t random_index( std::size_t max_size ) { if( max_size > 0 ) { std::random_device rd; std::mt19937 gen{ rd() }; return std::uniform_int_distribution< std::size_t >{0, max_size-1}(gen); } else return 0; } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_controller_t >() ); }, []( so_5::environment_params_t & params ) { params.turn_work_thread_activity_tracking_on(); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/internal_stats/quantity_int/000077500000000000000000000000001523065210100242345ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/internal_stats/quantity_int/CMakeLists.txt000066400000000000000000000001471523065210100267760ustar00rootroot00000000000000set(UNITTEST _unit.test.internal_stats.quantity_int) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/internal_stats/quantity_int/main.cpp000066400000000000000000000037561523065210100256770ustar00rootroot00000000000000#include #include namespace test { class a_stats_listener_t final : public so_5::agent_t { public : a_stats_listener_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) {} void so_define_agent() override { using namespace so_5::stats; auto & controller = so_environment().stats_controller(); so_default_state() .event( controller.mbox(), &a_stats_listener_t::evt_quantity_int ) ; } void so_evt_start() override { so_environment().stats_controller().set_distribution_period( std::chrono::milliseconds( 100 ) ); so_environment().stats_controller().turn_on(); } private : void evt_quantity_int( const so_5::stats::messages::quantity< int > & evt ) { std::cout << "I: '" << evt.m_prefix << evt.m_suffix << "': " << evt.m_value << std::endl; so_deregister_agent_coop_normally(); } }; class my_data_source_t final : public so_5::stats::source_t { public: void distribute(const so_5::mbox_t & to) override { so_5::send< so_5::stats::messages::quantity >( to, "my_data_source", "/dummy", 0 ); }; }; class a_custom_ds_holder_t final : public so_5::agent_t { so_5::stats::manually_registered_source_holder_t< my_data_source_t > m_ds_holder; public: a_custom_ds_holder_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_evt_start() override { m_ds_holder.start( so_5::outliving_mutable( so_environment().stats_repository() ) ); } void so_evt_finish() override { m_ds_holder.stop(); } }; } /* namespace test */ using namespace test; int main() { try { run_with_time_limit( []() { so_5::launch( [](so_5::environment_t & env) { env.introduce_coop( [](so_5::coop_t & coop) { coop.make_agent< a_stats_listener_t >(); coop.make_agent< a_custom_ds_holder_t >(); } ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_coop_count/000077500000000000000000000000001523065210100252255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_coop_count/CMakeLists.txt000066400000000000000000000001541523065210100277650ustar00rootroot00000000000000set(UNITTEST _unit.test.internal_stats.simple_coop_count) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_coop_count/main.cpp000066400000000000000000000065051523065210100266630ustar00rootroot00000000000000/* * A simple test for getting count of registered cooperations * from run-time monitoring messages. */ #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &a_test_t::evt_monitor_quantity ); } virtual void so_evt_start() override { create_child_coops(); so_environment().stats_controller().turn_on(); } private : unsigned int m_actual_values = { 0 }; void evt_monitor_quantity( const so_5::stats::messages::quantity< std::size_t > & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix.c_str() << evt.m_suffix.c_str() << ": " << evt.m_value << std::endl; if( stats::prefixes::coop_repository() == evt.m_prefix ) { if( stats::suffixes::coop_count() == evt.m_suffix ) { // Count of registered cooperations could be // 12 or 11 (it depends of deregistration of // the special autoshutdown-guard cooperation). if( 12 != evt.m_value && 11 != evt.m_value ) throw std::runtime_error( "unexpected count of " "registered cooperations: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } else if( stats::suffixes::agent_count() == evt.m_suffix ) { // Count of registered agents could be // 11 or 12 (it depends of deregistration of // the special autoshutdown-guard cooperation). if( 11 != evt.m_value && 12 != evt.m_value ) throw std::runtime_error( "unexpected count of " "registered agents: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } else if( stats::suffixes::coop_final_dereg_count() == evt.m_suffix ) { // Count of coops waiting for final dereg could be // 0 or 1 (it depends of deregistration of // the special autoshutdown-guard cooperation). if( 0 != evt.m_value && 1 != evt.m_value ) throw std::runtime_error( "unexpected count of " "coops in final dereg state: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } } if( 4 == m_actual_values ) so_deregister_agent_coop_normally(); } void create_child_coops() { class empty_actor_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; }; for( int i = 0; i != 10; ++i ) { auto coop = so_5::create_child_coop( *this ); coop->make_agent< empty_actor_t >(); so_environment().register_coop( std::move( coop ) ); } } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple coop count monitoring test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_named_mbox_count/000077500000000000000000000000001523065210100263765ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_named_mbox_count/CMakeLists.txt000066400000000000000000000001621523065210100311350ustar00rootroot00000000000000set(UNITTEST _unit.test.internal_stats.simple_named_mbox_count) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_named_mbox_count/main.cpp000066400000000000000000000050371523065210100300330ustar00rootroot00000000000000/* * A simple test for getting count of named mboxes * from run-time monitoring messages. */ #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_m1( so_environment().create_mbox( "m1" ) ) , m_m2( so_environment().create_mbox( "m2" ) ) , m_m3( so_environment().create_mbox( "m3" ) ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &a_test_t::evt_monitor_quantity ); } virtual void so_evt_start() override { so_environment().stats_controller().turn_on(); } private : const so_5::mbox_t m_m1; const so_5::mbox_t m_m2; const so_5::mbox_t m_m3; unsigned int m_actual_values = { 0 }; void evt_monitor_quantity( const so_5::stats::messages::quantity< std::size_t > & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix << evt.m_suffix << ": " << evt.m_value << std::endl; if( stats::prefixes::mbox_repository() == evt.m_prefix ) { // Check as_string_view method for prefix_t. if( stats::prefixes::mbox_repository().as_string_view() != evt.m_prefix.c_str() ) { throw std::runtime_error( "as_string_view() for prefix_t doesn't work" ); } if( stats::suffixes::named_mbox_count() == evt.m_suffix ) { // Check as_string_view method for suffix_t. if( stats::suffixes::named_mbox_count().as_string_view() != evt.m_suffix.c_str() ) { throw std::runtime_error( "as_string_view() for suffix_t doesn't work" ); } if( 3 != evt.m_value ) throw std::runtime_error( "unexpected count of " "named mboxes: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } } if( 1 == m_actual_values ) so_deregister_agent_coop_normally(); } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple named mboxes count monitoring test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_timer_thread/000077500000000000000000000000001523065210100255245ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_timer_thread/CMakeLists.txt000066400000000000000000000001561523065210100302660ustar00rootroot00000000000000set(UNITTEST _unit.test.internal_stats.simple_timer_thread) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_timer_thread/main.cpp000066400000000000000000000052061523065210100271570ustar00rootroot00000000000000/* * A simple test for getting count of timers from run-time monitoring messages. */ #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public : struct msg_delayed : so_5::signal_t {}; a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &a_test_t::evt_monitor_quantity ); } virtual void so_evt_start() override { using namespace std::chrono; so_5::send_delayed< msg_delayed >( *this, seconds( 10 ) ); so_5::send_delayed< msg_delayed >( *this, seconds( 10 ) ); m_p1 = so_5::send_periodic< msg_delayed >( *this, seconds( 10 ), seconds( 10 ) ); m_p2 = so_5::send_periodic< msg_delayed >( *this, seconds( 10 ), seconds( 10 ) ); m_p3 = so_5::send_periodic< msg_delayed >( *this, seconds( 10 ), seconds( 10 ) ); so_environment().stats_controller().turn_on(); } private : unsigned int m_actual_values = { 0 }; so_5::timer_id_t m_p1; so_5::timer_id_t m_p2; so_5::timer_id_t m_p3; void evt_monitor_quantity( const so_5::stats::messages::quantity< std::size_t > & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix.c_str() << evt.m_suffix.c_str() << ": " << evt.m_value << std::endl; if( stats::prefixes::timer_thread() == evt.m_prefix ) { if( stats::suffixes::timer_single_shot_count() == evt.m_suffix ) { if( 2 != evt.m_value ) throw std::runtime_error( "unexpected count of " "single-shot timers: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } else if( stats::suffixes::timer_periodic_count() == evt.m_suffix ) { if( 3 != evt.m_value ) throw std::runtime_error( "unexpected count of " "periodic timers: " + std::to_string( evt.m_value ) ); else ++m_actual_values; } } if( 2 == m_actual_values ) so_deregister_agent_coop_normally(); } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple timer thread monitoring test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_turn_on/000077500000000000000000000000001523065210100245415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_turn_on/CMakeLists.txt000066400000000000000000000001511523065210100272760ustar00rootroot00000000000000set(UNITTEST _unit.test.internal_stats.simple_turn_on) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_turn_on/main.cpp000066400000000000000000000015721523065210100261760ustar00rootroot00000000000000/* * A simple test for turning run-time monitoring on. */ #include #include #include #include #include #include #include #include #include void init( so_5::environment_t & env ) { env.stats_controller().turn_on(); class actor_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; void so_evt_start() override { so_environment().stop(); } }; env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< actor_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple stats_controller().turn_on() test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_work_thread_activity/000077500000000000000000000000001523065210100273025ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_work_thread_activity/CMakeLists.txt000066400000000000000000000001661523065210100320450ustar00rootroot00000000000000set(UNITTEST _unit.test.internal_stats.simple_work_thread_activity) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_work_thread_activity/main.cpp000066400000000000000000000055461523065210100307440ustar00rootroot00000000000000/* * A simple test for getting stats about work thread activity. */ #include #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &a_test_t::evt_monitor_activity ); } virtual void so_evt_start() override { create_child_coops(); so_environment().stats_controller().turn_on(); } private : unsigned int m_actual_values = { 0 }; void evt_monitor_activity( const so_5::stats::messages::work_thread_activity & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix << evt.m_suffix << " [" << evt.m_thread_id << "] ->\n" << " working: " << evt.m_stats.m_working_stats << "\n" << " waiting: " << evt.m_stats.m_waiting_stats << std::endl; ++m_actual_values; if( 12 == m_actual_values ) so_deregister_agent_coop_normally(); } void create_child_coops() { class empty_actor_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; }; for( int i = 0; i != 10; ++i ) { auto coop = so_5::create_child_coop( *this ); coop->make_agent< empty_actor_t >(); so_environment().register_coop( std::move( coop ) ); } } }; void init( so_5::environment_t & env ) { ensure_or_die( so_5::work_thread_activity_tracking_t::on == env.work_thread_activity_tracking(), "work_thread_activity_tracking should be 'on' at this point" ); env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); class actor_t final : public so_5::agent_t { struct next final : public so_5::signal_t {}; public : using so_5::agent_t::agent_t; void so_evt_start() override { so_subscribe_self().event( [this](mhood_t) { so_5::send< next >( *this ); std::this_thread::sleep_for( std::chrono::seconds(3) ); } ); so_5::send< next >( *this ); } }; coop.make_agent_with_binder< actor_t >( so_5::disp::one_thread::make_dispatcher( coop.environment(), "busy" ).binder() ); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, []( so_5::environment_params_t & params ) { namespace otd = so_5::disp::one_thread; params.turn_work_thread_activity_tracking_on(); } ); }, 20, "simple coop count monitoring test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_work_thread_activity_wrapped_env/000077500000000000000000000000001523065210100316745ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002021523065210100343470ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_work_thread_activity_wrapped_envset(UNITTEST _unit.test.internal_stats.simple_work_thread_activity_wrapped_env) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/internal_stats/simple_work_thread_activity_wrapped_env/main.cpp000066400000000000000000000055461523065210100333360ustar00rootroot00000000000000/* * A simple test for getting stats about work thread activity. */ #include #include #include #include #include #include #include #include #include #include class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &a_test_t::evt_monitor_activity ); } virtual void so_evt_start() override { create_child_coops(); so_environment().stats_controller().turn_on(); } private : unsigned int m_actual_values = { 0 }; void evt_monitor_activity( const so_5::stats::messages::work_thread_activity & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix << evt.m_suffix << " [" << evt.m_thread_id << "] ->\n" << " working: " << evt.m_stats.m_working_stats << "\n" << " waiting: " << evt.m_stats.m_waiting_stats << std::endl; ++m_actual_values; if( 4 == m_actual_values ) so_environment().stop(); } void create_child_coops() { class empty_actor_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; }; for( int i = 0; i != 10; ++i ) { auto coop = so_5::create_child_coop( *this ); coop->make_agent< empty_actor_t >(); so_environment().register_coop( std::move( coop ) ); } } }; void init( so_5::environment_t & env ) { ensure_or_die( so_5::work_thread_activity_tracking_t::on == env.work_thread_activity_tracking(), "work_thread_activity_tracking should be 'on' at this point" ); env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); class actor_t final : public so_5::agent_t { struct next final : public so_5::signal_t {}; public : using so_5::agent_t::agent_t; void so_evt_start() override { so_subscribe_self().event( [this](mhood_t) { so_5::send< next >( *this ); std::this_thread::sleep_for( std::chrono::seconds(1) ); } ); so_5::send< next >( *this ); } }; coop.make_agent_with_binder< actor_t >( so_5::disp::one_thread::make_dispatcher( coop.environment(), "busy" ).binder() ); } ); } int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env{ &init, []( so_5::environment_params_t & params ) { params.turn_work_thread_activity_tracking_on(); } }; std::cout << "Waiting for the completion..." << std::endl; env.join(); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/layer/000077500000000000000000000000001523065210100175665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/layer/extra_layer_errors/000077500000000000000000000000001523065210100235015ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/layer/extra_layer_errors/CMakeLists.txt000066400000000000000000000001441523065210100262400ustar00rootroot00000000000000set(UNITTEST _unit.test.layer.extra_layer_errors) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/layer/extra_layer_errors/main.cpp000066400000000000000000000075231523065210100251400ustar00rootroot00000000000000/* * A tests for layers. */ #include #include #include #include #include #include #include #include "../separate_so_thread_inl.cpp" template < int N > class test_layer_t : public so_5::layer_t { public: test_layer_t() {} void start() override {} void shutdown() override {} void wait() override {} }; class test_layer_bad_start_t : public so_5::layer_t { public: test_layer_bad_start_t() {} void start() override { throw std::runtime_error( "failure" ); } void shutdown() override {} void wait() override {} }; class so_environment_error_checker_t : public so_5::environment_t , public separate_so_thread::init_finish_signal_mixin_t { typedef so_5::environment_t base_type_t; public: so_environment_error_checker_t() : base_type_t( std::move( so_5::environment_params_t() .add_layer( std::make_unique< test_layer_t< 0 > >() ) .disable_autoshutdown() ) ) {} void init() override { init_finished(); } }; UT_UNIT_TEST( check_errors ) { so_environment_error_checker_t so_env; separate_so_thread::run_on( so_env, [&]() { const char * const null_str = nullptr; // Try to set up layer which is already set. try { so_env.add_extra_layer( std::make_unique< test_layer_t< 0 > >() ); UT_CHECK_EQ( null_str, "exception must be thrown" ); } catch( const so_5::exception_t & x ) { UT_CHECK_EQ( so_5::rc_trying_to_add_extra_layer_that_already_exists_in_default_list, x.error_code() ); } // Try to set up layer by zero pointer. try { so_env.add_extra_layer( std::unique_ptr< test_layer_t< 1 > >( nullptr ) ); UT_CHECK_EQ( null_str, "exception must be thrown" ); } catch( const so_5::exception_t & x ) { UT_CHECK_EQ( so_5::rc_trying_to_add_nullptr_extra_layer, x.error_code() ); } // Try to add new layer. No errors expected. so_env.add_extra_layer( std::make_unique< test_layer_t< 1 > >() ); // Try to add layer which is already set. try { so_env.add_extra_layer( std::make_unique< test_layer_t< 1 > >() ); UT_CHECK_EQ( null_str, "exception must be thrown" ); } catch( const so_5::exception_t & x ) { UT_CHECK_EQ( so_5::rc_trying_to_add_extra_layer_that_already_exists_in_extra_list, x.error_code() ); } // Try to add layer which is failed to start. try { so_env.add_extra_layer( std::make_unique< test_layer_bad_start_t >() ); UT_CHECK_EQ( null_str, "exception must be thrown" ); } catch( const so_5::exception_t & x ) { UT_CHECK_EQ( so_5::rc_unable_to_start_extra_layer, x.error_code() ); } } ); } UT_UNIT_TEST( check_exceptions ) { so_environment_error_checker_t so_env; separate_so_thread::run_on( so_env, [&]() { // Try to set up layer which is already set. UT_CHECK_THROW( so_5::exception_t, so_env.add_extra_layer( std::unique_ptr< test_layer_t< 0 > >( new test_layer_t< 0 > ) ) ); // Try to set up layer by zero pointer. UT_CHECK_THROW( so_5::exception_t, so_env.add_extra_layer( std::unique_ptr< test_layer_t< 1 > >( nullptr ) ) ); // Try to add new layer. No errors expected. so_env.add_extra_layer( std::unique_ptr< test_layer_t< 1 > >( new test_layer_t< 1 > ) ); // Try to add layer which is already set. UT_CHECK_THROW( so_5::exception_t, so_env.add_extra_layer( std::unique_ptr< test_layer_t< 1 > >( new test_layer_t< 1 > ) ) ); // Try to add layer which is failed to start. UT_CHECK_THROW( so_5::exception_t, so_env.add_extra_layer( std::unique_ptr< test_layer_bad_start_t >( new test_layer_bad_start_t ) ) ); } ); } int main() { UT_RUN_UNIT_TEST( check_errors ) UT_RUN_UNIT_TEST( check_exceptions ) return 0; } sobjectizer-5.8.6/dev/test/so_5/layer/extra_layer_query/000077500000000000000000000000001523065210100233325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/layer/extra_layer_query/CMakeLists.txt000066400000000000000000000001431523065210100260700ustar00rootroot00000000000000set(UNITTEST _unit.test.layer.extra_layer_query) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/layer/extra_layer_query/main.cpp000066400000000000000000000166411523065210100247720ustar00rootroot00000000000000/* * A test for layers. */ #include #include #include #include #include #include #include #include "../separate_so_thread_inl.cpp" std::array< so_5::layer_t *, 64 > last_created_objects; template < int N > class test_layer_t : public so_5::layer_t { public: test_layer_t() { last_created_objects[ N ] = &*this; } void start() override {} void shutdown() override {} void wait() override {} }; class test_environment_t : public so_5::environment_t , public separate_so_thread::init_finish_signal_mixin_t { typedef so_5::environment_t base_type_t; public: test_environment_t( test_layer_t< 1 > * tl1, test_layer_t< 2 > * tl2, test_layer_t< 3 > * tl3 ) : base_type_t( get_params() ), m_tl1( tl1 ), m_tl2( tl2 ), m_tl3( tl3 ) {} void init() override { if( nullptr != m_tl1.get() ) add_extra_layer( std::move( m_tl1 ) ); if( nullptr != m_tl2.get() ) add_extra_layer( std::move( m_tl2 ) ); if( nullptr != m_tl3.get() ) add_extra_layer( std::move( m_tl3 ) ); init_finished(); } private: std::unique_ptr< test_layer_t< 1 > > m_tl1; std::unique_ptr< test_layer_t< 2 > > m_tl2; std::unique_ptr< test_layer_t< 3 > > m_tl3; static so_5::environment_params_t get_params() { so_5::environment_params_t params; params.disable_autoshutdown(); return params; } }; void check_layers_match( test_layer_t< 1 > * tl1, test_layer_t< 2 > * tl2, test_layer_t< 3 > * tl3, const test_environment_t & so_env ) { UT_CHECK_EQ( so_env.query_layer_noexcept< test_layer_t< 1 > >(), tl1 ); UT_CHECK_EQ( so_env.query_layer_noexcept< test_layer_t< 2 > >(), tl2 ); UT_CHECK_EQ( so_env.query_layer_noexcept< test_layer_t< 3 > >(), tl3 ); } UT_UNIT_TEST( check_all_exist ) { test_layer_t< 1 > * tl1 = new test_layer_t< 1 >; test_layer_t< 2 > * tl2 = new test_layer_t< 2 >; test_layer_t< 3 > * tl3 = new test_layer_t< 3 >; test_environment_t so_env( tl1, tl2, tl3 ); separate_so_thread::run_on( so_env, [&]() { check_layers_match( tl1, tl2, tl3, so_env ); } ); } UT_UNIT_TEST( check_1_2_exist ) { test_layer_t< 1 > * tl1 = new test_layer_t< 1 >; test_layer_t< 2 > * tl2 = new test_layer_t< 2 >; test_layer_t< 3 > * tl3 = nullptr; test_environment_t so_env( tl1, tl2, tl3 ); separate_so_thread::run_on( so_env, [&]() { check_layers_match( tl1, tl2, tl3, so_env ); } ); } UT_UNIT_TEST( check_1_3_exist ) { test_layer_t< 1 > * tl1 = new test_layer_t< 1 >; test_layer_t< 2 > * tl2 = nullptr; test_layer_t< 3 > * tl3 = new test_layer_t< 3 >; test_environment_t so_env( tl1, tl2, tl3 ); separate_so_thread::run_on( so_env, [&]() { check_layers_match( tl1, tl2, tl3, so_env ); } ); } UT_UNIT_TEST( check_2_3_exist ) { test_layer_t< 1 > * tl1 = nullptr; test_layer_t< 2 > * tl2 = new test_layer_t< 2 >; test_layer_t< 3 > * tl3 = new test_layer_t< 3 >; test_environment_t so_env( tl1, tl2, tl3 ); separate_so_thread::run_on( so_env, [&]() { check_layers_match( tl1, tl2, tl3, so_env ); } ); } #define CHECK_LAYER_DONT_EXISTS( so_env, N ) \ UT_CHECK_EQ( \ so_env.query_layer_noexcept< test_layer_t< N > >(), \ static_cast< so_5::layer_t* >( nullptr) ) #define ADD_LAYER( so_env, N ) \ so_env.add_extra_layer( \ std::unique_ptr< test_layer_t< N > >( \ new test_layer_t< N > ) ) #define CHECK_LAYER_EXISTS( so_env, N ) \ UT_CHECK_EQ( \ so_env.query_layer_noexcept< test_layer_t< N > >(), \ last_created_objects[ N ] ) #define CHECK_LAYER( so_env, N ) \ CHECK_LAYER_DONT_EXISTS( so_env, N ); \ ADD_LAYER( so_env, N ); \ CHECK_LAYER_EXISTS( so_env, N ); void init( so_5::environment_t & env ) { CHECK_LAYER( env, 1 ) CHECK_LAYER( env, 2 ) CHECK_LAYER( env, 3 ) CHECK_LAYER( env, 4 ) CHECK_LAYER( env, 5 ) CHECK_LAYER( env, 6 ) CHECK_LAYER( env, 7 ) CHECK_LAYER( env, 8 ) CHECK_LAYER( env, 9 ) CHECK_LAYER( env, 10 ) CHECK_LAYER( env, 11 ) CHECK_LAYER( env, 12 ) CHECK_LAYER( env, 13 ) CHECK_LAYER( env, 14 ) CHECK_LAYER( env, 15 ) CHECK_LAYER( env, 16 ) CHECK_LAYER( env, 17 ) CHECK_LAYER( env, 18 ) CHECK_LAYER( env, 19 ) CHECK_LAYER( env, 20 ) CHECK_LAYER( env, 21 ) CHECK_LAYER( env, 22 ) CHECK_LAYER( env, 23 ) CHECK_LAYER( env, 24 ) CHECK_LAYER( env, 25 ) CHECK_LAYER( env, 26 ) CHECK_LAYER( env, 27 ) CHECK_LAYER( env, 28 ) CHECK_LAYER( env, 29 ) CHECK_LAYER( env, 30 ) CHECK_LAYER( env, 31 ) CHECK_LAYER( env, 32 ) CHECK_LAYER( env, 33 ) CHECK_LAYER( env, 34 ) CHECK_LAYER( env, 35 ) CHECK_LAYER( env, 36 ) CHECK_LAYER( env, 37 ) CHECK_LAYER( env, 38 ) CHECK_LAYER( env, 39 ) CHECK_LAYER( env, 40 ) CHECK_LAYER( env, 41 ) CHECK_LAYER( env, 42 ) CHECK_LAYER( env, 43 ) CHECK_LAYER( env, 44 ) CHECK_LAYER( env, 45 ) CHECK_LAYER( env, 46 ) CHECK_LAYER( env, 47 ) CHECK_LAYER( env, 48 ) CHECK_LAYER( env, 49 ) CHECK_LAYER( env, 50 ) CHECK_LAYER( env, 51 ) CHECK_LAYER( env, 52 ) CHECK_LAYER( env, 53 ) CHECK_LAYER( env, 54 ) CHECK_LAYER( env, 55 ) CHECK_LAYER( env, 56 ) CHECK_LAYER( env, 57 ) CHECK_LAYER( env, 58 ) CHECK_LAYER( env, 59 ) CHECK_LAYER( env, 60 ) CHECK_LAYER( env, 61 ) CHECK_LAYER( env, 62 ) CHECK_LAYER( env, 63 ) CHECK_LAYER_EXISTS( env, 1 ); CHECK_LAYER_EXISTS( env, 2 ); CHECK_LAYER_EXISTS( env, 3 ); CHECK_LAYER_EXISTS( env, 4 ); CHECK_LAYER_EXISTS( env, 5 ); CHECK_LAYER_EXISTS( env, 6 ); CHECK_LAYER_EXISTS( env, 7 ); CHECK_LAYER_EXISTS( env, 8 ); CHECK_LAYER_EXISTS( env, 9 ); CHECK_LAYER_EXISTS( env, 10 ); CHECK_LAYER_EXISTS( env, 11 ); CHECK_LAYER_EXISTS( env, 12 ); CHECK_LAYER_EXISTS( env, 13 ); CHECK_LAYER_EXISTS( env, 14 ); CHECK_LAYER_EXISTS( env, 15 ); CHECK_LAYER_EXISTS( env, 16 ); CHECK_LAYER_EXISTS( env, 17 ); CHECK_LAYER_EXISTS( env, 18 ); CHECK_LAYER_EXISTS( env, 19 ); CHECK_LAYER_EXISTS( env, 20 ); CHECK_LAYER_EXISTS( env, 21 ); CHECK_LAYER_EXISTS( env, 22 ); CHECK_LAYER_EXISTS( env, 23 ); CHECK_LAYER_EXISTS( env, 24 ); CHECK_LAYER_EXISTS( env, 25 ); CHECK_LAYER_EXISTS( env, 26 ); CHECK_LAYER_EXISTS( env, 27 ); CHECK_LAYER_EXISTS( env, 28 ); CHECK_LAYER_EXISTS( env, 29 ); CHECK_LAYER_EXISTS( env, 30 ); CHECK_LAYER_EXISTS( env, 31 ); CHECK_LAYER_EXISTS( env, 32 ); CHECK_LAYER_EXISTS( env, 33 ); CHECK_LAYER_EXISTS( env, 34 ); CHECK_LAYER_EXISTS( env, 35 ); CHECK_LAYER_EXISTS( env, 36 ); CHECK_LAYER_EXISTS( env, 37 ); CHECK_LAYER_EXISTS( env, 38 ); CHECK_LAYER_EXISTS( env, 39 ); CHECK_LAYER_EXISTS( env, 40 ); CHECK_LAYER_EXISTS( env, 41 ); CHECK_LAYER_EXISTS( env, 42 ); CHECK_LAYER_EXISTS( env, 43 ); CHECK_LAYER_EXISTS( env, 44 ); CHECK_LAYER_EXISTS( env, 45 ); CHECK_LAYER_EXISTS( env, 46 ); CHECK_LAYER_EXISTS( env, 47 ); CHECK_LAYER_EXISTS( env, 48 ); CHECK_LAYER_EXISTS( env, 49 ); CHECK_LAYER_EXISTS( env, 50 ); CHECK_LAYER_EXISTS( env, 51 ); CHECK_LAYER_EXISTS( env, 52 ); CHECK_LAYER_EXISTS( env, 53 ); CHECK_LAYER_EXISTS( env, 54 ); CHECK_LAYER_EXISTS( env, 55 ); CHECK_LAYER_EXISTS( env, 56 ); CHECK_LAYER_EXISTS( env, 57 ); CHECK_LAYER_EXISTS( env, 58 ); CHECK_LAYER_EXISTS( env, 59 ); CHECK_LAYER_EXISTS( env, 60 ); CHECK_LAYER_EXISTS( env, 61 ); CHECK_LAYER_EXISTS( env, 62 ); CHECK_LAYER_EXISTS( env, 63 ); env.stop(); } UT_UNIT_TEST( check_many_layers ) { so_5::launch( &init ); } int main() { UT_RUN_UNIT_TEST( check_all_exist ) UT_RUN_UNIT_TEST( check_1_2_exist ) UT_RUN_UNIT_TEST( check_1_3_exist ) UT_RUN_UNIT_TEST( check_2_3_exist ) UT_RUN_UNIT_TEST( check_many_layers ) return 0; } sobjectizer-5.8.6/dev/test/so_5/layer/layer_init/000077500000000000000000000000001523065210100217255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/layer/layer_init/CMakeLists.txt000066400000000000000000000001341523065210100244630ustar00rootroot00000000000000set(UNITTEST _unit.test.layer.layer_init) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/layer/layer_init/main.cpp000066400000000000000000000027201523065210100233560ustar00rootroot00000000000000/* * A test for checking layer initialization. */ #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif // // test_layer_t // class test_layer_t : public so_5::layer_t { private: int m_op_seq_counter; enum operations_t { OP_START = 0, OP_SHUTDOWN = 1, OP_WAIT = 2 }; static int op_calls[3]; public: test_layer_t() : m_op_seq_counter( 0 ) {} void start() override { op_calls[ OP_START ] = m_op_seq_counter++; } void shutdown() override { op_calls[ OP_SHUTDOWN ] = m_op_seq_counter++; } void wait() override { op_calls[ OP_WAIT ] = m_op_seq_counter++; } static void check_calls(); }; int test_layer_t::op_calls[3] = { -1, -1, -1 }; void test_layer_t::check_calls() { UT_CHECK_EQ( op_calls[ OP_START ], OP_START ); UT_CHECK_EQ( op_calls[ OP_SHUTDOWN ], OP_SHUTDOWN ); UT_CHECK_EQ( op_calls[ OP_WAIT ], OP_WAIT ); } void init( so_5::environment_t & env ) { env.stop(); } UT_UNIT_TEST( check_layer_lifecircle_op_calls ) { so_5::launch( &init, []( so_5::environment_params_t & params ) { params.add_layer( std::make_unique< test_layer_t >() ); } ); test_layer_t::check_calls(); } int main() { UT_RUN_UNIT_TEST( check_layer_lifecircle_op_calls ) return 0; } sobjectizer-5.8.6/dev/test/so_5/layer/layer_query/000077500000000000000000000000001523065210100221275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/layer/layer_query/CMakeLists.txt000066400000000000000000000001351523065210100246660ustar00rootroot00000000000000set(UNITTEST _unit.test.layer.layer_query) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/layer/layer_query/main.cpp000066400000000000000000000122111523065210100235540ustar00rootroot00000000000000/* * A test for layers set and get. */ #include #include #include #include #include #include "../separate_so_thread_inl.cpp" #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif void * last_created_objects[ 64 ]; template < int N > class test_layer_t : public so_5::layer_t { public: test_layer_t() { last_created_objects[ N ] = this; } void start() override {} void shutdown() override {} void wait() override {} }; class test_environment_t : public so_5::environment_t , public separate_so_thread::init_finish_signal_mixin_t { typedef so_5::environment_t base_type_t; public: test_environment_t( test_layer_t< 1 > * tl1, test_layer_t< 2 > * tl2, test_layer_t< 3 > * tl3 ) : base_type_t( std::move( so_5::environment_params_t() .add_layer( std::unique_ptr< test_layer_t< 1 > >( tl1 ) ) .add_layer( std::unique_ptr< test_layer_t< 2 > >( tl2 ) ) .add_layer( std::unique_ptr< test_layer_t< 3 > >( tl3 ) ) .disable_autoshutdown() ) ) {} void init() override { stop(); init_finished(); } }; void check_layers_match( test_layer_t< 1 > * tl1, test_layer_t< 2 > * tl2, test_layer_t< 3 > * tl3, const test_environment_t & so_env ) { UT_CHECK_EQ( so_env.query_layer_noexcept< test_layer_t< 1 > >(), tl1 ); UT_CHECK_EQ( so_env.query_layer_noexcept< test_layer_t< 2 > >(), tl2 ); UT_CHECK_EQ( so_env.query_layer_noexcept< test_layer_t< 3 > >(), tl3 ); } UT_UNIT_TEST( check_all_exist ) { test_layer_t< 1 > * tl1 = new test_layer_t< 1 >; test_layer_t< 2 > * tl2 = new test_layer_t< 2 >; test_layer_t< 3 > * tl3 = new test_layer_t< 3 >; test_environment_t so_env( tl1, tl2, tl3 ); separate_so_thread::run_on( so_env, [&]() { check_layers_match( tl1, tl2, tl3, so_env ); } ); } UT_UNIT_TEST( check_1_2_exist ) { test_layer_t< 1 > * tl1 = new test_layer_t< 1 >; test_layer_t< 2 > * tl2 = new test_layer_t< 2 >; test_environment_t so_env( tl1, tl2, nullptr ); separate_so_thread::run_on( so_env, [&]() { check_layers_match( tl1, tl2, nullptr, so_env ); } ); } UT_UNIT_TEST( check_1_3_exist ) { test_layer_t< 1 > * tl1 = new test_layer_t< 1 >; test_layer_t< 3 > * tl3 = new test_layer_t< 3 >; test_environment_t so_env( tl1, nullptr, tl3 ); separate_so_thread::run_on( so_env, [&]() { check_layers_match( tl1, nullptr, tl3, so_env ); } ); } UT_UNIT_TEST( check_2_3_exist ) { test_layer_t< 2 > * tl2 = new test_layer_t< 2 >; test_layer_t< 3 > * tl3 = new test_layer_t< 3 >; test_environment_t so_env( nullptr, tl2, tl3 ); separate_so_thread::run_on( so_env, [&]() { check_layers_match( nullptr, tl2, tl3, so_env ); } ); } #define CHECK_LAYER_EXISTANCE( so_env, N ) \ UT_CHECK_EQ( \ so_env.query_layer< test_layer_t< N > >(), \ static_cast< test_layer_t< N >* >(last_created_objects[ N ]) ) void init( so_5::environment_t & env ) { CHECK_LAYER_EXISTANCE( env, 1 ); CHECK_LAYER_EXISTANCE( env, 2 ); CHECK_LAYER_EXISTANCE( env, 3 ); CHECK_LAYER_EXISTANCE( env, 4 ); CHECK_LAYER_EXISTANCE( env, 5 ); CHECK_LAYER_EXISTANCE( env, 6 ); CHECK_LAYER_EXISTANCE( env, 7 ); CHECK_LAYER_EXISTANCE( env, 8 ); CHECK_LAYER_EXISTANCE( env, 9 ); CHECK_LAYER_EXISTANCE( env, 10 ); CHECK_LAYER_EXISTANCE( env, 11 ); CHECK_LAYER_EXISTANCE( env, 12 ); CHECK_LAYER_EXISTANCE( env, 13 ); CHECK_LAYER_EXISTANCE( env, 14 ); CHECK_LAYER_EXISTANCE( env, 15 ); CHECK_LAYER_EXISTANCE( env, 16 ); CHECK_LAYER_EXISTANCE( env, 17 ); CHECK_LAYER_EXISTANCE( env, 18 ); CHECK_LAYER_EXISTANCE( env, 19 ); CHECK_LAYER_EXISTANCE( env, 20 ); CHECK_LAYER_EXISTANCE( env, 21 ); CHECK_LAYER_EXISTANCE( env, 22 ); CHECK_LAYER_EXISTANCE( env, 23 ); CHECK_LAYER_EXISTANCE( env, 24 ); CHECK_LAYER_EXISTANCE( env, 25 ); CHECK_LAYER_EXISTANCE( env, 26 ); CHECK_LAYER_EXISTANCE( env, 27 ); CHECK_LAYER_EXISTANCE( env, 28 ); CHECK_LAYER_EXISTANCE( env, 29 ); CHECK_LAYER_EXISTANCE( env, 30 ); CHECK_LAYER_EXISTANCE( env, 31 ); CHECK_LAYER_EXISTANCE( env, 32 ); env.stop(); } #define ADD_LAYER( N ) \ .add_layer( std::make_unique< test_layer_t< N > >() ) UT_UNIT_TEST( check_many_layers ) { so_5::launch( &init, []( so_5::environment_params_t & params ) { params ADD_LAYER( 1 ) ADD_LAYER( 2 ) ADD_LAYER( 3 ) ADD_LAYER( 4 ) ADD_LAYER( 5 ) ADD_LAYER( 6 ) ADD_LAYER( 7 ) ADD_LAYER( 8 ) ADD_LAYER( 9 ) ADD_LAYER( 10 ) ADD_LAYER( 11 ) ADD_LAYER( 12 ) ADD_LAYER( 13 ) ADD_LAYER( 14 ) ADD_LAYER( 15 ) ADD_LAYER( 16 ) ADD_LAYER( 17 ) ADD_LAYER( 18 ) ADD_LAYER( 19 ) ADD_LAYER( 20 ) ADD_LAYER( 21 ) ADD_LAYER( 22 ) ADD_LAYER( 23 ) ADD_LAYER( 24 ) ADD_LAYER( 25 ) ADD_LAYER( 26 ) ADD_LAYER( 27 ) ADD_LAYER( 28 ) ADD_LAYER( 29 ) ADD_LAYER( 30 ) ADD_LAYER( 31 ) ADD_LAYER( 32 ); } ); } int main() { UT_RUN_UNIT_TEST( check_all_exist ) UT_RUN_UNIT_TEST( check_1_2_exist ) UT_RUN_UNIT_TEST( check_1_3_exist ) UT_RUN_UNIT_TEST( check_2_3_exist ) UT_RUN_UNIT_TEST( check_many_layers ) return 0; } sobjectizer-5.8.6/dev/test/so_5/layer/separate_so_thread_inl.cpp000066400000000000000000000032711523065210100247730ustar00rootroot00000000000000/* * This file should be #included into test source file. */ #include #include #include #include namespace separate_so_thread { void entry_point( so_5::environment_t * env ) { try { env->run(); } catch( const std::exception & ex ) { std::cerr << "error: " << ex.what() << std::endl; std::abort(); } } typedef std::map< so_5::environment_t *, std::thread > env_thread_map_t; env_thread_map_t g_env_thread_map; std::mutex g_lock; void start( so_5::environment_t & env ) { std::lock_guard< std::mutex > lock( g_lock ); if( g_env_thread_map.end() == g_env_thread_map.find( &env ) ) { std::thread thread( entry_point, &env ); g_env_thread_map[ &env ] = std::move( thread ); } } void wait( so_5::environment_t & env ) { std::lock_guard< std::mutex > lock( g_lock ); env_thread_map_t::iterator it = g_env_thread_map.find( &env ); if( g_env_thread_map.end() != it ) { it->second.join(); g_env_thread_map.erase( it ); } } class init_finish_signal_mixin_t { public : init_finish_signal_mixin_t() : m_init_finish_lock( m_init_finish_mutex ) { } void wait_for_init_finish() { m_init_finish_signal.wait( m_init_finish_lock ); } protected : void init_finished() { std::lock_guard< std::mutex > lock( m_init_finish_mutex ); m_init_finish_signal.notify_one(); } private : std::mutex m_init_finish_mutex; std::unique_lock< std::mutex > m_init_finish_lock; std::condition_variable m_init_finish_signal; }; template< class ENV, class FUNCTOR > void run_on( ENV & env, FUNCTOR f ) { start( env ); env.wait_for_init_finish(); f(); env.stop(); wait( env ); } } /* namespace separate_so_thread */ sobjectizer-5.8.6/dev/test/so_5/mbox/000077500000000000000000000000001523065210100174175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/CMakeLists.txt000066400000000000000000000012331523065210100221560ustar00rootroot00000000000000add_subdirectory(subscribe_when_deregistered) add_subdirectory(drop_subscription) add_subdirectory(drop_subscr_when_demand_in_queue) add_subdirectory(drop_subscr_on_dereg) add_subdirectory(adaptive_subscr_storage) add_subdirectory(mpsc_mbox) add_subdirectory(mpsc_mbox_illegal_subscriber) add_subdirectory(mpsc_mbox_stress) add_subdirectory(hanging_subscriptions) add_subdirectory(delivery_filters) add_subdirectory(local_mbox_growth) add_subdirectory(custom_mbox_simple) add_subdirectory(make_new_direct_mbox) add_subdirectory(custom_direct_mbox_factory) add_subdirectory(unique_subscribers) add_subdirectory(sink_binding) add_subdirectory(introduce_named_mbox) sobjectizer-5.8.6/dev/test/so_5/mbox/adaptive_subscr_storage/000077500000000000000000000000001523065210100243215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/adaptive_subscr_storage/CMakeLists.txt000066400000000000000000000001501523065210100270550ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.adaptive_subscr_storage) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/adaptive_subscr_storage/main.cpp000066400000000000000000000220171523065210100257530ustar00rootroot00000000000000/* * A test for adaptive subscription storage. */ #include #include #include #include class test_mbox_t : public so_5::abstract_message_box_t { private : const so_5::mbox_t m_actual_mbox; unsigned int m_subscriptions = 0; unsigned int m_unsubscriptions = 0; public : test_mbox_t( so_5::environment_t & env ) : m_actual_mbox( env.create_mbox() ) { } virtual ~test_mbox_t() override { if( m_subscriptions != m_unsubscriptions ) { std::cerr << "subscriptions(" << m_subscriptions << ") != unsubscriptions(" << m_unsubscriptions << "). Test aborted!" << std::endl; std::abort(); } } virtual so_5::mbox_id_t id() const override { return m_actual_mbox->id(); } virtual void do_deliver_message( so_5::message_delivery_mode_t delivery_mode, const std::type_index & type_index, const so_5::message_ref_t & message_ref, unsigned int redirection_deep ) override { m_actual_mbox->do_deliver_message( delivery_mode, type_index, message_ref, redirection_deep ); } virtual void subscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) override { ++m_subscriptions; m_actual_mbox->subscribe_event_handler( type_index, subscriber ); } virtual void unsubscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) noexcept override { ++m_unsubscriptions; m_actual_mbox->unsubscribe_event_handler( type_index, subscriber ); } virtual std::string query_name() const override { return m_actual_mbox->query_name(); } virtual so_5::mbox_type_t type() const override { return m_actual_mbox->type(); } virtual void set_delivery_filter( const std::type_index & msg_type, const so_5::delivery_filter_t & filter, so_5::abstract_message_sink_t & subscriber ) override { m_actual_mbox->set_delivery_filter( msg_type, filter, subscriber ); } virtual void drop_delivery_filter( const std::type_index & msg_type, so_5::abstract_message_sink_t & subscriber ) noexcept override { m_actual_mbox->drop_delivery_filter( msg_type, subscriber ); } so_5::environment_t & environment() const noexcept override { return m_actual_mbox->environment(); } static so_5::mbox_t create( so_5::environment_t & env ) { return so_5::mbox_t( new test_mbox_t( env ) ); } }; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, so_5::subscription_storage_factory_t factory ) : base_type_t( env + factory ) , m_mbox( test_mbox_t::create( env ) ) { } void so_define_agent() override { this >>= st_1_1; st_1_1.event( m_mbox, &a_test_t::evt_st_1_1 ); } void so_evt_start() override { so_5::send< next >( m_mbox ); } private : struct next : public so_5::signal_t {}; so_5::mbox_t m_mbox; so_5::state_t st_1_1{ this, "st_1_1" }; so_5::state_t st_1_2{ this, "st_1_2" }; so_5::state_t st_1_3{ this, "st_1_3" }; so_5::state_t st_1_4{ this, "st_1_4" }; so_5::state_t st_1_5{ this, "st_1_5" }; so_5::state_t st_1_6{ this, "st_1_6" }; so_5::state_t st_1_7{ this, "st_1_7" }; so_5::state_t st_1_8{ this, "st_1_8" }; so_5::state_t st_2_1{ this, "st_2_1" }; so_5::state_t st_2_2{ this, "st_2_2" }; so_5::state_t st_2_3{ this, "st_2_3" }; so_5::state_t st_2_4{ this, "st_2_4" }; so_5::state_t st_2_5{ this, "st_2_5" }; so_5::state_t st_2_6{ this, "st_2_6" }; so_5::state_t st_2_7{ this, "st_2_7" }; so_5::state_t st_2_8{ this, "st_2_8" }; so_5::state_t st_finish{ this, "st_finish" }; void perform_action( const so_5::state_t & next_state, void (a_test_t::*event_handler)(mhood_t) ) { this >>= next_state; next_state.event( m_mbox, event_handler ); so_5::send< next >( m_mbox ); } void evt_st_1_1(mhood_t< next >) { perform_action( st_1_2, &a_test_t::evt_st_1_2 ); } void evt_st_1_2(mhood_t< next >) { perform_action( st_1_3, &a_test_t::evt_st_1_3 ); } void evt_st_1_3(mhood_t< next >) { perform_action( st_1_4, &a_test_t::evt_st_1_4 ); } void evt_st_1_4(mhood_t< next >) { perform_action( st_1_5, &a_test_t::evt_st_1_5 ); } void evt_st_1_5(mhood_t< next >) { perform_action( st_1_6, &a_test_t::evt_st_1_6 ); } void evt_st_1_6(mhood_t< next >) { perform_action( st_1_7, &a_test_t::evt_st_1_7 ); } void evt_st_1_7(mhood_t< next >) { perform_action( st_1_8, &a_test_t::evt_st_1_8 ); } void evt_st_1_8(mhood_t< next >) { // Subscription storage must switch back from large to small. so_drop_subscription< next >( m_mbox, st_1_1 ); so_drop_subscription< next >( m_mbox, st_1_2 ); so_drop_subscription< next >( m_mbox, st_1_3 ); so_drop_subscription< next >( m_mbox, st_1_4 ); so_drop_subscription< next >( m_mbox, st_1_5 ); so_drop_subscription< next >( m_mbox, st_1_6 ); so_drop_subscription< next >( m_mbox, st_1_7 ); so_drop_subscription< next >( m_mbox, st_1_8 ); this >>= st_2_1; st_2_1.event( m_mbox, &a_test_t::evt_st_2_1 ); so_5::send< next >( m_mbox ); } void evt_st_2_1(mhood_t< next >) { perform_action( st_2_2, &a_test_t::evt_st_2_2 ); } void evt_st_2_2(mhood_t< next >) { perform_action( st_2_3, &a_test_t::evt_st_2_3 ); } void evt_st_2_3(mhood_t< next >) { perform_action( st_2_4, &a_test_t::evt_st_2_4 ); } void evt_st_2_4(mhood_t< next >) { perform_action( st_2_5, &a_test_t::evt_st_2_5 ); } void evt_st_2_5(mhood_t< next >) { perform_action( st_2_6, &a_test_t::evt_st_2_6 ); } void evt_st_2_6(mhood_t< next >) { perform_action( st_2_7, &a_test_t::evt_st_2_7 ); } void evt_st_2_7(mhood_t< next >) { perform_action( st_2_8, &a_test_t::evt_st_2_8 ); } void evt_st_2_8(mhood_t< next >) { // Subscription storage must switch back from large to small. so_drop_subscription_for_all_states< next >( m_mbox ); this >>= st_finish; st_finish.event( m_mbox, [this](mhood_t) { so_deregister_agent_coop_normally(); } ); so_5::send< next >( m_mbox ); } }; void do_test() { using namespace std; using namespace so_5; const std::size_t threshold = 4; using factory_info_t = pair< std::string, subscription_storage_factory_t >; factory_info_t factories[] = { { "default", adaptive_subscription_storage_factory( threshold ) } , { "vector+hash_table", adaptive_subscription_storage_factory( threshold, vector_based_subscription_storage_factory( threshold ), hash_table_based_subscription_storage_factory() ) } , { "hash_table+vector", adaptive_subscription_storage_factory( threshold, hash_table_based_subscription_storage_factory(), vector_based_subscription_storage_factory( threshold ) ) } , { "map+hash_table", adaptive_subscription_storage_factory( threshold, map_based_subscription_storage_factory(), hash_table_based_subscription_storage_factory() ) } , { "hash_table+map", adaptive_subscription_storage_factory( threshold, hash_table_based_subscription_storage_factory(), map_based_subscription_storage_factory() ) } , { "vector+map", adaptive_subscription_storage_factory( threshold, vector_based_subscription_storage_factory( threshold ), map_based_subscription_storage_factory() ) } , { "map+vector", adaptive_subscription_storage_factory( threshold, map_based_subscription_storage_factory(), vector_based_subscription_storage_factory( threshold ) ) } , { "flat_set+hash_table", adaptive_subscription_storage_factory( threshold, flat_set_based_subscription_storage_factory( threshold ), hash_table_based_subscription_storage_factory() ) } , { "hash_table+flat_set", adaptive_subscription_storage_factory( threshold, hash_table_based_subscription_storage_factory(), flat_set_based_subscription_storage_factory( threshold ) ) } , { "flat_set+map", adaptive_subscription_storage_factory( threshold, flat_set_based_subscription_storage_factory( threshold ), map_based_subscription_storage_factory() ) } , { "map+flat_set", adaptive_subscription_storage_factory( threshold, map_based_subscription_storage_factory(), flat_set_based_subscription_storage_factory( threshold ) ) } }; for( auto & f : factories ) { std::cout << "checking factory: " << f.first << " -> " << std::flush; run_with_time_limit( [&f] { for( int i = 0; i != 10; ++i ) so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( f.second ) ); } ); }, 20, "checking factory " + f.first ); std::cout << "OK" << std::endl; } } int main() { try { do_test(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/custom_direct_mbox_factory/000077500000000000000000000000001523065210100250375ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/custom_direct_mbox_factory/CMakeLists.txt000066400000000000000000000001531523065210100275760ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.custom_direct_mbox_factory) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/custom_direct_mbox_factory/main.cpp000066400000000000000000000101161523065210100264660ustar00rootroot00000000000000/* * A test for custom_direct_mbox_factory. */ #include #include #include #include #include #include struct msg_check : public so_5::signal_t {}; class protocol_t { std::mutex m_lock; std::string m_protocol; public: protocol_t() = default; protocol_t( const protocol_t & ) = delete; protocol_t( protocol_t && ) = delete; void append( const std::string & what ) { std::lock_guard< std::mutex > lock{ m_lock }; m_protocol += what; } [[nodiscard]] std::string get() { std::lock_guard< std::mutex > lock{ m_lock }; return m_protocol; } }; class test_mbox_t final : public so_5::abstract_message_box_t { const so_5::mbox_t m_target; protocol_t & m_protocol; public: test_mbox_t( so_5::mbox_t target, protocol_t & protocol ) : m_target{ std::move(target) } , m_protocol{ protocol } {} so_5::mbox_id_t id() const override { return m_target->id(); } void subscribe_event_handler( const std::type_index & type_index, ::so_5::abstract_message_sink_t & subscriber ) override { m_protocol.append( "subscribe;" ); m_target->subscribe_event_handler( type_index, subscriber ); } void unsubscribe_event_handler( const std::type_index & type_index, ::so_5::abstract_message_sink_t & subscriber ) noexcept override { m_target->unsubscribe_event_handler( type_index, subscriber ); m_protocol.append( "unsubscribe;" ); } std::string query_name() const override { return m_target->query_name(); } so_5::mbox_type_t type() const override { return m_target->type(); } void do_deliver_message( so_5::message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const ::so_5::message_ref_t & message, unsigned int redirection_deep ) override { m_protocol.append( "deliver;" ); m_target->do_deliver_message( delivery_mode, msg_type, message, redirection_deep ); } void set_delivery_filter( const std::type_index & msg_type, const ::so_5::delivery_filter_t & filter, ::so_5::abstract_message_sink_t & subscriber ) override { m_protocol.append( "set_delivery_filter;" ); m_target->set_delivery_filter( msg_type, filter, subscriber ); } void drop_delivery_filter( const std::type_index & msg_type, ::so_5::abstract_message_sink_t & subscriber ) noexcept override { m_target->drop_delivery_filter( msg_type, subscriber ); m_protocol.append( "drop_delivery_filter;" ); } so_5::environment_t & environment() const noexcept override { return m_target->environment(); } }; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( context_t ctx, protocol_t & protocol ) : base_type_t( ctx + custom_direct_mbox_factory( [&protocol]( so_5::partially_constructed_agent_ptr_t, so_5::mbox_t actual_mbox ) -> so_5::mbox_t { return { std::make_unique< test_mbox_t >( std::move(actual_mbox), protocol ) }; } ) ) , m_protocol( protocol ) { } void so_define_agent() override { so_subscribe( so_direct_mbox() ) .event( &a_test_t::evt_check ); } void so_evt_start() override { so_5::send< msg_check >( *this ); } void evt_check( mhood_t< msg_check > ) { m_protocol.append( "received;" ); so_environment().stop(); } private : protocol_t & m_protocol; }; UT_UNIT_TEST( simple ) { protocol_t protocol; run_with_time_limit( [&protocol]() { so_5::launch( [&protocol]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( std::ref(protocol) ) ); } ); }, 5 ); UT_CHECK_EQ( protocol.get(), "subscribe;deliver;received;unsubscribe;" ); } int main() { UT_RUN_UNIT_TEST( simple ) return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/custom_mbox_simple/000077500000000000000000000000001523065210100233275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/custom_mbox_simple/CMakeLists.txt000066400000000000000000000001431523065210100260650ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.custom_mbox_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/custom_mbox_simple/main.cpp000066400000000000000000000101561523065210100247620ustar00rootroot00000000000000/* * A test for custom mbox creation. */ #include #include #include #include struct msg_one : public so_5::signal_t {}; struct msg_two : public so_5::signal_t {}; struct msg_three : public so_5::signal_t {}; struct msg_four : public so_5::signal_t {}; class my_mbox_t : public so_5::abstract_message_box_t { public : my_mbox_t( so_5::mbox_t actual_mbox ) : m_actual_mbox(std::move(actual_mbox)) {} virtual so_5::mbox_id_t id() const override { return m_actual_mbox->id(); } virtual void subscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) override { if( is_enabled_message( type_index ) ) m_actual_mbox->subscribe_event_handler( type_index, subscriber ); } virtual void unsubscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) noexcept override { if( is_enabled_message( type_index ) ) m_actual_mbox->unsubscribe_event_handler( type_index, subscriber ); } virtual std::string query_name() const override { return ""; } virtual so_5::mbox_type_t type() const override { return m_actual_mbox->type(); } virtual void do_deliver_message( so_5::message_delivery_mode_t delivery_mode, const std::type_index & msg_type, const so_5::message_ref_t & message, unsigned int redirection_deep ) override { m_actual_mbox->do_deliver_message( delivery_mode, msg_type, message, redirection_deep ); } virtual void set_delivery_filter( const std::type_index & msg_type, const so_5::delivery_filter_t & filter, so_5::abstract_message_sink_t & subscriber ) override { m_actual_mbox->set_delivery_filter( msg_type, filter, subscriber ); } virtual void drop_delivery_filter( const std::type_index & msg_type, so_5::abstract_message_sink_t & subscriber ) noexcept override { m_actual_mbox->drop_delivery_filter( msg_type, subscriber ); } so_5::environment_t & environment() const noexcept override { return m_actual_mbox->environment(); } private : const so_5::mbox_t m_actual_mbox; static bool is_enabled_message( const std::type_index & msg_type ) { static const std::type_index t1 = typeid(msg_one); static const std::type_index t4 = typeid(msg_four); return (t1 == msg_type || t4 == msg_type); } }; class a_test_t : public so_5::agent_t { using base_type_t = so_5::agent_t; public : a_test_t( context_t ctx, so_5::mbox_t mbox, std::string & sequence ) : base_type_t( std::move(ctx) ) , m_mbox( std::move(mbox) ) , m_sequence( sequence ) { } void so_define_agent() override { so_subscribe( m_mbox ) .event( &a_test_t::evt_one ) .event( &a_test_t::evt_two ) .event( &a_test_t::evt_three ) .event( &a_test_t::evt_four ); } void evt_one( mhood_t ) { m_sequence += "e1:"; } void evt_two( mhood_t ) { m_sequence += "e2:"; } void evt_three( mhood_t ) { m_sequence += "e3:"; } void evt_four( mhood_t ) { m_sequence += "e4:"; so_environment().stop(); } private : const so_5::mbox_t m_mbox; std::string & m_sequence; }; int main() { run_with_time_limit( [] { std::string sequence; so_5::launch( [&sequence]( so_5::environment_t & env ) { auto actual_mbox = env.create_mbox(); auto my_mbox = env.make_custom_mbox( [actual_mbox]( const so_5::mbox_creation_data_t & ) { return so_5::mbox_t( new my_mbox_t(actual_mbox) ); } ); env.introduce_coop( [my_mbox, &sequence]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( my_mbox, std::ref(sequence) ); } ); so_5::send< msg_one >(my_mbox); so_5::send< msg_two >(my_mbox); so_5::send< msg_three >(my_mbox); so_5::send< msg_four >(my_mbox); } ); const std::string expected = "e1:e4:"; if( sequence != expected ) throw std::runtime_error( "sequence mismatch! " "expected: '" + expected + "', actual: '" + sequence + "'" ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/000077500000000000000000000000001523065210100227725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/CMakeLists.txt000066400000000000000000000004341523065210100255330ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(simple_mpsc_immutable) add_subdirectory(simple_mpsc_mutable) add_subdirectory(filter_no_subscriptions) add_subdirectory(dereg_subscriber) add_subdirectory(set_unset) add_subdirectory(foreign_mpsc_mbox) add_subdirectory(exception_in_filter) sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/dereg_subscriber/000077500000000000000000000000001523065210100263035ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/dereg_subscriber/CMakeLists.txt000066400000000000000000000001621523065210100310420ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.delivery_filters.dereg_subscriber) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/dereg_subscriber/main.cpp000066400000000000000000000044661523065210100277450ustar00rootroot00000000000000/* * A test for removing delivery filter during subscriber deregistration. */ #include #include #include #include #include #include #include #include #include struct data { int m_key; }; struct finish : public so_5::signal_t {}; class a_child_t : public so_5::agent_t { public : a_child_t( context_t ctx, so_5::mbox_t data_mbox ) : so_5::agent_t( ctx ) , m_data_mbox( data_mbox ) {} virtual void so_define_agent() override { so_set_delivery_filter( m_data_mbox, []( const data & msg ) { return 1 == msg.m_key; } ); so_subscribe( m_data_mbox ).event( [this]( const data & msg ) { const auto value = msg.m_key; if( 1 == value ) so_deregister_agent_coop_normally(); else throw std::runtime_error( "unexpected value: " + std::to_string( value ) ); } ); } private : const so_5::mbox_t m_data_mbox; }; class a_parent_t : public so_5::agent_t { public : a_parent_t( context_t ctx ) : so_5::agent_t( ctx ) , m_data_mbox( so_environment().create_mbox() ) {} virtual void so_define_agent() override { so_default_state() .event( [this]( const so_5::msg_coop_deregistered & ) { so_5::send< data >( m_data_mbox, 0 ); so_5::send< data >( m_data_mbox, 1 ); so_5::send< data >( m_data_mbox, 2 ); so_5::send< finish >( *this ); } ) .event( [this](mhood_t< finish >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::introduce_child_coop( *this, [this]( so_5::coop_t & coop ) { coop.add_dereg_notificator( so_5::make_coop_dereg_notificator( so_direct_mbox() ) ); coop.make_agent< a_child_t >( m_data_mbox ); } ); so_5::send< data >( m_data_mbox, 0 ); so_5::send< data >( m_data_mbox, 1 ); so_5::send< data >( m_data_mbox, 2 ); } private : const so_5::mbox_t m_data_mbox; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_parent_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "delivery filter for MPSC-mboxes" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/exception_in_filter/000077500000000000000000000000001523065210100270235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/exception_in_filter/CMakeLists.txt000066400000000000000000000002241523065210100315610ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.delivery_filters.exception_in_filter) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/exception_in_filter/main.cpp000066400000000000000000000043371523065210100304620ustar00rootroot00000000000000/* * A test for exception from delivery filter. */ #include #include #include #include #include #include #include #include #include struct data { int m_key; }; struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_data_mbox( so_environment().create_mbox() ) {} virtual void so_define_agent() override { so_set_delivery_filter( m_data_mbox, []( const data & msg ) { if( 1 == msg.m_key ) throw std::runtime_error( "value is 1" ); return true; } ); so_default_state() .event( m_data_mbox, [this]( const data & msg ) { const auto value = msg.m_key; if( 1 != value ) throw std::runtime_error( "unexpected data value: " + std::to_string( value ) ); ++m_values_accepted; } ) .event( [this](mhood_t< finish >) { if( 2 != m_values_accepted ) throw std::runtime_error( "unexpected count of " "accepted data instances: " + std::to_string( m_values_accepted ) ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< data >( m_data_mbox, 0 ); so_5::send< data >( m_data_mbox, 1 ); so_5::send< data >( m_data_mbox, 2 ); so_5::send< data >( m_data_mbox, 3 ); so_5::send< data >( m_data_mbox, 4 ); so_5::send< data >( m_data_mbox, 0 ); so_5::send< data >( m_data_mbox, 1 ); so_5::send< data >( m_data_mbox, 2 ); so_5::send< data >( m_data_mbox, 3 ); so_5::send< data >( m_data_mbox, 4 ); so_5::send< finish >( *this ); } private : const so_5::mbox_t m_data_mbox; unsigned int m_values_accepted = 0; }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.set_exception_reaction( so_5::shutdown_sobjectizer_on_exception ); coop.make_agent< a_test_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "exception in delivery filter test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/filter_no_subscriptions/000077500000000000000000000000001523065210100277425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/filter_no_subscriptions/CMakeLists.txt000066400000000000000000000001711523065210100325010ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.delivery_filters.filter_no_subscriptions) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/filter_no_subscriptions/main.cpp000066400000000000000000000032561523065210100314000ustar00rootroot00000000000000/* * A test for defining only delivery filter without subscriptions. */ #include #include #include #include #include #include #include #include #include struct data { int m_key; }; struct one : public so_5::signal_t {}; struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_data_mbox( so_environment().create_mbox() ) {} virtual void so_define_agent() override { so_set_delivery_filter( m_data_mbox, []( const data & msg ) { return 1 == msg.m_key; } ); so_default_state() .event( [this](mhood_t< one >) { so_drop_delivery_filter< data >( m_data_mbox ); so_5::send< data >( m_data_mbox, 0 ); so_5::send< data >( m_data_mbox, 1 ); so_5::send< data >( m_data_mbox, 2 ); so_5::send< finish >( *this ); } ) .event( [this](mhood_t< finish >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< data >( m_data_mbox, 0 ); so_5::send< data >( m_data_mbox, 1 ); so_5::send< data >( m_data_mbox, 2 ); so_5::send< one >( *this ); } private : const so_5::mbox_t m_data_mbox; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "delivery filter without subscriptions test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/foreign_mpsc_mbox/000077500000000000000000000000001523065210100264725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/foreign_mpsc_mbox/CMakeLists.txt000066400000000000000000000001631523065210100312320ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.delivery_filters.foreign_mpsc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/foreign_mpsc_mbox/main.cpp000066400000000000000000000032331523065210100301230ustar00rootroot00000000000000/* * A test for mpsc_mbox: setting a delivery filter form another agent must * lead to exception. */ #include #include #include struct msg_demo : public so_5::message_t {}; class a_first_t : public so_5::agent_t { public : a_first_t( so_5::environment_t & env ) : so_5::agent_t( env ) { } void so_define_agent() override { so_set_delivery_filter( so_direct_mbox(), []( const msg_demo & ) { return true; } ); so_subscribe( so_direct_mbox() ).event( &a_first_t::evt_demo ); } void so_evt_start() override { so_environment().stop(); } void evt_demo( mhood_t< msg_demo > ) {} }; class a_second_t : public so_5::agent_t { public : a_second_t( so_5::environment_t & env, const so_5::mbox_t & mbox ) : so_5::agent_t( env ) , m_mbox( mbox ) { } void so_define_agent() override { try { so_set_delivery_filter( m_mbox, []( const msg_demo & ) { return true; } ); // Shouldn't be here! throw std::runtime_error{ "successful so_set_delivery_filter completion!" }; } catch( const so_5::exception_t & x ) { if( so_5::rc_illegal_subscriber_for_mpsc_mbox != x.error_code() ) throw; } } private : const so_5::mbox_t m_mbox; }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { auto coop = env.make_coop(); auto a_first = coop->make_agent< a_first_t >(); coop->make_agent< a_second_t >( a_first->so_direct_mbox() ); env.register_coop( std::move( coop ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/set_unset/000077500000000000000000000000001523065210100250035ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/set_unset/CMakeLists.txt000066400000000000000000000001531523065210100275420ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.delivery_filters.set_unset) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/set_unset/main.cpp000066400000000000000000000072361523065210100264430ustar00rootroot00000000000000/* * A test for setting and unsetting filters. */ #include #include #include #include #include #include #include #include #include struct data { int m_key; }; struct next : public so_5::signal_t {}; struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_data_mbox( so_environment().create_mbox() ) {} virtual void so_define_agent() override { this >>= st_1; so_set_delivery_filter( m_data_mbox, []( const data & msg ) { return 1 == msg.m_key; } ); so_subscribe( m_data_mbox ) .in( st_1 ) .in( st_2 ) .in( st_3 ) .in( st_4 ) .in( st_5 ) .in( st_6 ) .in( st_7 ) .event( &a_test_t::evt_data ); st_1.event( [this](mhood_t< next >) { st_2.activate(); m_accumulator += '|'; so_drop_delivery_filter< data >( m_data_mbox ); send_bunch(); } ); st_2.event( [this](mhood_t< next >) { st_3.activate(); m_accumulator += '|'; so_set_delivery_filter( m_data_mbox, []( const data & msg ) { return 2 == msg.m_key; } ); send_bunch(); } ); st_3.event( [this](mhood_t< next >) { st_4.activate(); m_accumulator += '|'; so_drop_delivery_filter< data >( m_data_mbox ); send_bunch(); } ); st_4.event( [this](mhood_t< next >) { st_5.activate(); m_accumulator += '|'; so_set_delivery_filter( m_data_mbox, []( const data & msg ) { return 3 == msg.m_key; } ); send_bunch(); } ); st_5.event( [this](mhood_t< next >) { st_6.activate(); m_accumulator += '|'; so_drop_delivery_filter< data >( m_data_mbox ); send_bunch(); } ); st_6.event( [this](mhood_t< next >) { st_7.activate(); m_accumulator += '|'; so_set_delivery_filter( m_data_mbox, []( const data & msg ) { return 4 == msg.m_key; } ); send_bunch(); } ); st_7.event( [this](mhood_t< next >) { const std::string expected = "1,|0,1,2,3,4,|2,|0,1,2,3,4,|3,|0,1,2,3,4,|4,"; if( expected == m_accumulator ) so_deregister_agent_coop_normally(); else throw std::runtime_error( "values mismatch, actual: '" + m_accumulator + "', expected: '" + expected + "'" ); } ); } virtual void so_evt_start() override { send_bunch(); } private : const so_5::state_t st_1{ this }; const so_5::state_t st_2{ this }; const so_5::state_t st_3{ this }; const so_5::state_t st_4{ this }; const so_5::state_t st_5{ this }; const so_5::state_t st_6{ this }; const so_5::state_t st_7{ this }; const so_5::mbox_t m_data_mbox; std::string m_accumulator; void send_bunch() { so_5::send< data >( m_data_mbox, 0 ); so_5::send< data >( m_data_mbox, 1 ); so_5::send< data >( m_data_mbox, 2 ); so_5::send< data >( m_data_mbox, 3 ); so_5::send< data >( m_data_mbox, 4 ); so_5::send< next >( *this ); } void evt_data( const data & msg ) { m_accumulator += std::to_string( msg.m_key ); m_accumulator += ','; } }; void init( so_5::environment_t & env ) { auto disp = so_5::disp::thread_pool::make_dispatcher( env ); const auto params = so_5::disp::thread_pool::bind_params_t{}. max_demands_at_once( 1 ); for( unsigned int i = 0; i != 1000u; ++i ) env.register_agent_as_coop( env.make_agent< a_test_t >(), disp.binder( params ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 40, "delivery filter set/unset test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/simple/000077500000000000000000000000001523065210100242635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/simple/CMakeLists.txt000066400000000000000000000001501523065210100270170ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.delivery_filters.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/simple/main.cpp000066400000000000000000000063411523065210100257170ustar00rootroot00000000000000/* * A simple test for delivery filters. */ #include #include #include #include #include #include #include #include #include struct data { int m_key; }; struct finish : public so_5::signal_t {}; struct default_filter_setter_t { static void set( so_5::agent_t & to, const so_5::mbox_t & mbox ) { to.so_set_delivery_filter( mbox, []( const data & msg ) { return 1 == msg.m_key; } ); } }; struct filter_as_variable_setter_t { static void set( so_5::agent_t & to, const so_5::mbox_t & mbox ) { auto filter = []( const data & msg ) { return 1 == msg.m_key; }; to.so_set_delivery_filter( mbox, filter ); } }; struct filter_as_const_setter_t { static void set( so_5::agent_t & to, const so_5::mbox_t & mbox ) { const auto filter = []( const data & msg ) { return 1 == msg.m_key; }; to.so_set_delivery_filter( mbox, filter ); } }; template< typename Delivery_Filter_Setter > class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_data_mbox( so_environment().create_mbox() ) {} virtual void so_define_agent() override { Delivery_Filter_Setter::set( *this, m_data_mbox ); so_default_state() .event( m_data_mbox, [this]( const data & msg ) { const auto value = msg.m_key; if( 1 != value ) throw std::runtime_error( "unexpected data value: " + std::to_string( value ) ); ++m_values_accepted; } ) .event( [this](mhood_t< finish >) { if( 2 != m_values_accepted ) throw std::runtime_error( "unexpected count of " "accepted data instances: " + std::to_string( m_values_accepted ) ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< data >( m_data_mbox, 0 ); so_5::send< data >( m_data_mbox, 1 ); so_5::send< data >( m_data_mbox, 2 ); so_5::send< data >( m_data_mbox, 3 ); so_5::send< data >( m_data_mbox, 4 ); so_5::send< data >( m_data_mbox, 0 ); so_5::send< data >( m_data_mbox, 1 ); so_5::send< data >( m_data_mbox, 2 ); so_5::send< data >( m_data_mbox, 3 ); so_5::send< data >( m_data_mbox, 4 ); so_5::send< finish >( *this ); } private : const so_5::mbox_t m_data_mbox; unsigned int m_values_accepted = 0; }; void test_case_default_filter_setter( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t< default_filter_setter_t > >() ); } void test_case_filter_as_variable_setter( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t< filter_as_variable_setter_t > >() ); } void test_case_filter_as_const_setter( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t< filter_as_const_setter_t > >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &test_case_default_filter_setter ); so_5::launch( &test_case_filter_as_variable_setter ); so_5::launch( &test_case_filter_as_const_setter ); }, 20, "simple delivery filter test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/simple_mpsc_immutable/000077500000000000000000000000001523065210100273445ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/simple_mpsc_immutable/CMakeLists.txt000066400000000000000000000001671523065210100321100ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.delivery_filters.simple_mpsc_immutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/simple_mpsc_immutable/main.cpp000066400000000000000000000040751523065210100310020ustar00rootroot00000000000000/* * A simple test for delivery filters for immutable messages and MPSC mboxes. */ #include #include #include #include #include #include #include #include #include struct data { int m_key; }; struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_set_delivery_filter( so_direct_mbox(), []( const data & msg ) { return 1 == msg.m_key; } ); so_default_state() .event( so_direct_mbox(), [this]( const data & msg ) { const auto value = msg.m_key; if( 1 != value ) throw std::runtime_error( "unexpected data value: " + std::to_string( value ) ); ++m_values_accepted; } ) .event( [this](mhood_t< finish >) { if( 2 != m_values_accepted ) throw std::runtime_error( "unexpected count of " "accepted data instances: " + std::to_string( m_values_accepted ) ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< data >( so_direct_mbox(), 0 ); so_5::send< data >( so_direct_mbox(), 1 ); so_5::send< data >( so_direct_mbox(), 2 ); so_5::send< data >( so_direct_mbox(), 3 ); so_5::send< data >( so_direct_mbox(), 4 ); so_5::send< data >( so_direct_mbox(), 0 ); so_5::send< data >( so_direct_mbox(), 1 ); so_5::send< data >( so_direct_mbox(), 2 ); so_5::send< data >( so_direct_mbox(), 3 ); so_5::send< data >( so_direct_mbox(), 4 ); so_5::send< finish >( *this ); } private : const so_5::mbox_t m_data_mbox; unsigned int m_values_accepted = 0; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/simple_mpsc_mutable/000077500000000000000000000000001523065210100270165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/simple_mpsc_mutable/CMakeLists.txt000066400000000000000000000001651523065210100315600ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.delivery_filters.simple_mpsc_mutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/delivery_filters/simple_mpsc_mutable/main.cpp000066400000000000000000000067071523065210100304600ustar00rootroot00000000000000/* * A simple test for delivery filters for mutable messages and MPSC mboxes. */ #include #include #include #include #include #include #include #include #include struct data { int m_key; }; struct finish : public so_5::signal_t {}; struct default_filter_setter_t { static void set( so_5::agent_t & to, const so_5::mbox_t & mbox ) { to.so_set_delivery_filter_for_mutable_msg( mbox, []( const data & msg ) { return 1 == msg.m_key; } ); } }; struct filter_as_variable_setter_t { static void set( so_5::agent_t & to, const so_5::mbox_t & mbox ) { auto filter = []( const data & msg ) { return 1 == msg.m_key; }; to.so_set_delivery_filter_for_mutable_msg( mbox, filter ); } }; struct filter_as_const_setter_t { static void set( so_5::agent_t & to, const so_5::mbox_t & mbox ) { const auto filter = []( const data & msg ) { return 1 == msg.m_key; }; to.so_set_delivery_filter_for_mutable_msg( mbox, filter ); } }; template< typename Delivery_Filter_Setter > class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { Delivery_Filter_Setter::set( *this, so_direct_mbox() ); so_default_state() .event( so_direct_mbox(), [this]( mutable_mhood_t< data > cmd ) { const auto value = cmd->m_key; if( 1 != value ) throw std::runtime_error( "unexpected data value: " + std::to_string( value ) ); ++m_values_accepted; } ) .event( [this](mhood_t< finish >) { if( 2 != m_values_accepted ) throw std::runtime_error( "unexpected count of " "accepted data instances: " + std::to_string( m_values_accepted ) ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< so_5::mutable_msg >( so_direct_mbox(), 0 ); so_5::send< so_5::mutable_msg >( so_direct_mbox(), 1 ); so_5::send< so_5::mutable_msg >( so_direct_mbox(), 2 ); so_5::send< so_5::mutable_msg >( so_direct_mbox(), 3 ); so_5::send< so_5::mutable_msg >( so_direct_mbox(), 4 ); so_5::send< so_5::mutable_msg >( so_direct_mbox(), 0 ); so_5::send< so_5::mutable_msg >( so_direct_mbox(), 1 ); so_5::send< so_5::mutable_msg >( so_direct_mbox(), 2 ); so_5::send< so_5::mutable_msg >( so_direct_mbox(), 3 ); so_5::send< so_5::mutable_msg >( so_direct_mbox(), 4 ); so_5::send< finish >( *this ); } private : unsigned int m_values_accepted = 0; }; void test_case_default_filter_setter( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t< default_filter_setter_t > >() ); } void test_case_filter_as_variable_setter( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t< filter_as_variable_setter_t > >() ); } void test_case_filter_as_const_setter( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t< filter_as_const_setter_t > >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &test_case_default_filter_setter ); so_5::launch( &test_case_filter_as_variable_setter ); so_5::launch( &test_case_filter_as_const_setter ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/drop_subscr_on_dereg/000077500000000000000000000000001523065210100236065ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/drop_subscr_on_dereg/CMakeLists.txt000066400000000000000000000001451523065210100263460ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.drop_subscr_on_dereg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/drop_subscr_on_dereg/main.cpp000066400000000000000000000131211523065210100252340ustar00rootroot00000000000000/* * A test for automatic unsubscription on dereg. */ #include #include #include #include class test_mbox_t : public so_5::abstract_message_box_t { private : const so_5::mbox_t m_actual_mbox; unsigned int m_subscriptions = 0; unsigned int m_unsubscriptions = 0; public : test_mbox_t( so_5::environment_t & env ) : m_actual_mbox( env.create_mbox() ) { } ~test_mbox_t() override { if( m_subscriptions != m_unsubscriptions ) { std::cerr << "subscriptions(" << m_subscriptions << ") != unsubscriptions(" << m_unsubscriptions << "). Test aborted!" << std::endl; std::abort(); } } virtual so_5::mbox_id_t id() const override { return m_actual_mbox->id(); } virtual void do_deliver_message( so_5::message_delivery_mode_t delivery_mode, const std::type_index & type_index, const so_5::message_ref_t & message_ref, unsigned int redirection_deep ) override { m_actual_mbox->do_deliver_message( delivery_mode, type_index, message_ref, redirection_deep ); } virtual void subscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) override { ++m_subscriptions; m_actual_mbox->subscribe_event_handler( type_index, subscriber ); } virtual void unsubscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) noexcept override { ++m_unsubscriptions; m_actual_mbox->unsubscribe_event_handler( type_index, subscriber ); } virtual std::string query_name() const override { return m_actual_mbox->query_name(); } virtual so_5::mbox_type_t type() const override { return m_actual_mbox->type(); } virtual void set_delivery_filter( const std::type_index & msg_type, const so_5::delivery_filter_t & filter, so_5::abstract_message_sink_t & subscriber ) override { m_actual_mbox->set_delivery_filter( msg_type, filter, subscriber ); } virtual void drop_delivery_filter( const std::type_index & msg_type, so_5::abstract_message_sink_t & subscriber ) noexcept override { m_actual_mbox->drop_delivery_filter( msg_type, subscriber ); } so_5::environment_t & environment() const noexcept override { return m_actual_mbox->environment(); } static so_5::mbox_t create( so_5::environment_t & env ) { return so_5::mbox_t( new test_mbox_t( env ) ); } }; struct msg_one : public so_5::signal_t {}; struct msg_two : public so_5::signal_t {}; struct msg_three : public so_5::signal_t {}; struct msg_four : public so_5::signal_t {}; struct msg_five : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; state_t st_1{ this, "st_1" }; state_t st_2{ this, "st_2" }; state_t st_3{ this, "st_3" }; state_t st_4{ this, "st_4" }; state_t st_5{ this, "st_5" }; const so_5::mbox_t m_mbox; public : a_test_t( so_5::environment_t & env, so_5::subscription_storage_factory_t factory ) : base_type_t( env + factory ) , m_mbox( test_mbox_t::create( env ) ) { } void so_define_agent() override { const auto subscribe_to = [this]( const state_t & st ) { st.event( m_mbox, &a_test_t::evt_one ); st.event( m_mbox, &a_test_t::evt_two ); st.event( m_mbox, &a_test_t::evt_three ); st.event( m_mbox, &a_test_t::evt_four ); st.event( m_mbox, &a_test_t::evt_five ); }; subscribe_to( so_default_state() ); subscribe_to( st_1 ); subscribe_to( st_2 ); subscribe_to( st_3 ); subscribe_to( st_4 ); subscribe_to( st_5 ); } void so_evt_start() override { so_deregister_agent_coop_normally(); } private : void evt_one( mhood_t ) {} void evt_two( mhood_t ) {} void evt_three( mhood_t ) {} void evt_four( mhood_t ) {} void evt_five( mhood_t ) {} }; void do_test() { using factory_info_t = std::pair< std::string, so_5::subscription_storage_factory_t >; factory_info_t factories[] = { { "vector[1]", so_5::vector_based_subscription_storage_factory( 1 ) } , { "vector[8]", so_5::vector_based_subscription_storage_factory( 8 ) } , { "vector[16]", so_5::vector_based_subscription_storage_factory( 16 ) } , { "map", so_5::map_based_subscription_storage_factory() } , { "hash_table", so_5::hash_table_based_subscription_storage_factory() } , { "adaptive[1]", so_5::adaptive_subscription_storage_factory( 1 ) } , { "adaptive[2]", so_5::adaptive_subscription_storage_factory( 2 ) } , { "adaptive[3]", so_5::adaptive_subscription_storage_factory( 3 ) } , { "adaptive[8]", so_5::adaptive_subscription_storage_factory( 8 ) } , { "flat_set[1]", so_5::flat_set_based_subscription_storage_factory( 1 ) } , { "flat_set[8]", so_5::flat_set_based_subscription_storage_factory( 8 ) } , { "flat_set[16]", so_5::flat_set_based_subscription_storage_factory( 16 ) } , { "default", so_5::default_subscription_storage_factory() } }; for( auto & f : factories ) { std::cout << "checking factory: " << f.first << " -> " << std::flush; run_with_time_limit( [f] { so_5::launch( [f]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( f.second ) ); } ); }, 5, "checking factory " + f.first ); std::cout << "OK" << std::endl; } } int main() { try { do_test(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/drop_subscr_when_demand_in_queue/000077500000000000000000000000001523065210100261675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/drop_subscr_when_demand_in_queue/CMakeLists.txt000066400000000000000000000001611523065210100307250ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.drop_subscr_when_demand_in_queue) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/drop_subscr_when_demand_in_queue/main.cpp000066400000000000000000000034311523065210100276200ustar00rootroot00000000000000/* * A test for processing drop_subscription when demand is in queue. */ #include #include #include #include #include struct msg_one : public so_5::signal_t {}; struct msg_two : public so_5::signal_t {}; struct msg_three : public so_5::signal_t {}; struct msg_four : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env ) : base_type_t( env ) , m_mbox( env.create_mbox() ) { } void so_define_agent() override { so_subscribe( m_mbox ) .event( &a_test_t::evt_one ); so_subscribe( m_mbox ) .event( &a_test_t::evt_two ); so_subscribe( m_mbox ) .event( &a_test_t::evt_three ); so_subscribe( m_mbox ) .event( &a_test_t::evt_four ); } void so_evt_start() override { so_5::send< msg_one >( m_mbox ); so_5::send< msg_two >( m_mbox ); so_5::send< msg_three >( m_mbox ); } void evt_one( mhood_t< msg_one > ) { for( size_t i = 0; i != 10000u; ++i ) { so_drop_subscription( m_mbox, &a_test_t::evt_two ); so_subscribe( m_mbox ).event( &a_test_t::evt_two ); so_5::send< msg_two >( m_mbox ); } so_drop_subscription( m_mbox, &a_test_t::evt_two ); } void evt_two( mhood_t< msg_two > ) { std::abort(); } void evt_three( mhood_t< msg_three > ) { so_5::send< msg_four >( m_mbox ); } void evt_four( mhood_t< msg_four > ) { so_environment().stop(); } private : so_5::mbox_t m_mbox; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { run_with_time_limit( [] { so_5::launch( &init ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/drop_subscription/000077500000000000000000000000001523065210100231675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/drop_subscription/CMakeLists.txt000066400000000000000000000001421523065210100257240ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.drop_subscription) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/drop_subscription/main.cpp000066400000000000000000000201641523065210100246220ustar00rootroot00000000000000/* * A test for so_drop_subscription methods. */ #include #include #include #include class test_mbox_t : public so_5::abstract_message_box_t { private : const so_5::mbox_t m_actual_mbox; unsigned int m_subscriptions = 0; unsigned int m_unsubscriptions = 0; public : test_mbox_t( so_5::environment_t & env ) : m_actual_mbox( env.create_mbox() ) { } ~test_mbox_t() override { if( m_subscriptions != m_unsubscriptions ) { std::cerr << "subscriptions(" << m_subscriptions << ") != unsubscriptions(" << m_unsubscriptions << "). Test aborted!" << std::endl; std::abort(); } } virtual so_5::mbox_id_t id() const override { return m_actual_mbox->id(); } virtual void do_deliver_message( so_5::message_delivery_mode_t delivery_mode, const std::type_index & type_index, const so_5::message_ref_t & message_ref, unsigned int redirection_deep ) override { m_actual_mbox->do_deliver_message( delivery_mode, type_index, message_ref, redirection_deep ); } virtual void subscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) override { ++m_subscriptions; m_actual_mbox->subscribe_event_handler( type_index, subscriber ); } virtual void unsubscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) noexcept override { ++m_unsubscriptions; m_actual_mbox->unsubscribe_event_handler( type_index, subscriber ); } virtual std::string query_name() const override { return m_actual_mbox->query_name(); } virtual so_5::mbox_type_t type() const override { return m_actual_mbox->type(); } virtual void set_delivery_filter( const std::type_index & msg_type, const so_5::delivery_filter_t & filter, so_5::abstract_message_sink_t & subscriber ) override { m_actual_mbox->set_delivery_filter( msg_type, filter, subscriber ); } virtual void drop_delivery_filter( const std::type_index & msg_type, so_5::abstract_message_sink_t & subscriber ) noexcept override { m_actual_mbox->drop_delivery_filter( msg_type, subscriber ); } so_5::environment_t & environment() const noexcept override { return m_actual_mbox->environment(); } static so_5::mbox_t create( so_5::environment_t & env ) { return so_5::mbox_t( new test_mbox_t( env ) ); } }; struct msg_one : public so_5::signal_t {}; struct msg_two : public so_5::signal_t {}; struct msg_three : public so_5::signal_t {}; struct msg_four : public so_5::message_t {}; struct msg_five : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, so_5::subscription_storage_factory_t factory, std::string & sequence ) : base_type_t( env + factory ) , m_sequence( sequence ) , m_mbox( test_mbox_t::create( env ) ) { } void so_define_agent() override { so_subscribe( m_mbox ) .event( &a_test_t::evt_default_one ) .event( &a_test_t::evt_default_two ) .event( &a_test_t::evt_default_three ) .event( &a_test_t::evt_default_four ) .event( &a_test_t::evt_five ); so_subscribe( m_mbox ).in( st_1 ) .event( &a_test_t::evt_st_1_one ) .event( &a_test_t::evt_st_1_two ) .event( &a_test_t::evt_st_1_three ) .event( &a_test_t::evt_default_four ) .event( &a_test_t::evt_five ); so_subscribe( m_mbox ).in( st_2 ) .event( &a_test_t::evt_st_2_one ) .event( &a_test_t::evt_st_2_two ) .event( &a_test_t::evt_st_2_three ) .event( &a_test_t::evt_five ); } void so_evt_start() override { so_5::send< msg_one >( m_mbox ); so_5::send< msg_two >( m_mbox ); so_5::send< msg_four >( m_mbox ); so_5::send< msg_five >( m_mbox ); } void evt_default_one( mhood_t< msg_one > ) { m_sequence += "d1:"; } void evt_default_two( mhood_t< msg_two > ) { m_sequence += "d2:"; so_drop_subscription( m_mbox, &a_test_t::evt_default_one ); so_5::send< msg_one >( m_mbox ); so_5::send< msg_three >( m_mbox ); } void evt_default_three( mhood_t< msg_three > ) { m_sequence += "d3:"; so_change_state( st_1 ); so_5::send< msg_one >( m_mbox ); so_5::send< msg_two >( m_mbox ); } void evt_default_four( const msg_four & ) { m_sequence += "d4:"; so_5::send< msg_four >( m_mbox ); so_drop_subscription( m_mbox, &a_test_t::evt_default_four ); so_drop_subscription( m_mbox, st_1, &a_test_t::evt_default_four ); } void evt_five(mhood_t< msg_five >) { m_sequence += "d5:"; so_5::send< msg_five >( m_mbox ); so_drop_subscription_for_all_states< msg_five >( m_mbox ); } void evt_st_1_one( mhood_t< msg_one > ) { m_sequence += "1_d1:"; } void evt_st_1_two( mhood_t< msg_two > ) { m_sequence += "1_d2:"; so_drop_subscription( m_mbox, st_1, &a_test_t::evt_st_1_one ); so_5::send< msg_one >( m_mbox ); so_5::send< msg_three >( m_mbox ); } void evt_st_1_three( mhood_t< msg_three > ) { m_sequence += "1_d3:"; so_drop_subscription_for_all_states( m_mbox, &a_test_t::evt_default_one ); so_change_state( st_2 ); so_5::send< msg_one >( m_mbox ); so_5::send< msg_two >( m_mbox ); so_5::send< msg_three >( m_mbox ); } void evt_st_1_four( const msg_four & ) { m_sequence += "1_d4:"; } void evt_st_2_one( mhood_t< msg_one > ) { m_sequence += "2_d1:"; } void evt_st_2_two( mhood_t< msg_two > ) { m_sequence += "2_d2:"; } void evt_st_2_three( mhood_t< msg_three > ) { m_sequence += "2_d3:"; so_environment().stop(); } private : std::string & m_sequence; so_5::mbox_t m_mbox; so_5::state_t st_1{ this, "st_1" }; so_5::state_t st_2{ this, "st_2" }; }; class test_env_t { public : test_env_t( so_5::subscription_storage_factory_t factory ) : m_factory( std::move( factory ) ) {} void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( m_factory, m_sequence ) ); } void check_result() const { const std::string expected = "d1:d2:d4:d5:d3:1_d1:1_d2:1_d3:2_d2:2_d3:"; if( m_sequence != expected ) throw std::runtime_error( "Wrong message sequence: actial: " + m_sequence + ", expected: " + expected ); } private : const so_5::subscription_storage_factory_t m_factory; std::string m_sequence; }; void do_test() { using factory_info_t = std::pair< std::string, so_5::subscription_storage_factory_t >; factory_info_t factories[] = { { "vector[1]", so_5::vector_based_subscription_storage_factory( 1 ) } , { "vector[8]", so_5::vector_based_subscription_storage_factory( 8 ) } , { "vector[16]", so_5::vector_based_subscription_storage_factory( 16 ) } , { "map", so_5::map_based_subscription_storage_factory() } , { "hash_table", so_5::hash_table_based_subscription_storage_factory() } , { "adaptive[1]", so_5::adaptive_subscription_storage_factory( 1 ) } , { "adaptive[2]", so_5::adaptive_subscription_storage_factory( 2 ) } , { "adaptive[3]", so_5::adaptive_subscription_storage_factory( 3 ) } , { "adaptive[8]", so_5::adaptive_subscription_storage_factory( 8 ) } , { "flat_set[1]", so_5::flat_set_based_subscription_storage_factory( 1 ) } , { "flat_set[8]", so_5::flat_set_based_subscription_storage_factory( 8 ) } , { "flat_set[16]", so_5::flat_set_based_subscription_storage_factory( 16 ) } , { "default", so_5::default_subscription_storage_factory() } }; for( auto & f : factories ) { std::cout << "checking factory: " << f.first << " -> " << std::flush; run_with_time_limit( [f] { test_env_t test_env{ f.second }; so_5::launch( [&]( so_5::environment_t & env ) { test_env.init( env ); } ); test_env.check_result(); }, 20, "checking factory " + f.first ); std::cout << "OK" << std::endl; } } int main() { try { do_test(); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/hanging_subscriptions/000077500000000000000000000000001523065210100240215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/hanging_subscriptions/CMakeLists.txt000066400000000000000000000001461523065210100265620ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.hanging_subscriptions) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/hanging_subscriptions/main.cpp000066400000000000000000000106651523065210100254610ustar00rootroot00000000000000/* * A test for checking deletion of subscriptions to mbox in destructor. */ #include #include #include class test_mbox_t : public so_5::abstract_message_box_t { private : const so_5::mbox_t m_actual_mbox; public : static unsigned int subscriptions; static unsigned int unsubscriptions; test_mbox_t( so_5::environment_t & env ) : m_actual_mbox( env.create_mbox() ) { } virtual ~test_mbox_t() override { } virtual so_5::mbox_id_t id() const override { return m_actual_mbox->id(); } virtual void do_deliver_message( so_5::message_delivery_mode_t delivery_mode, const std::type_index & type_index, const so_5::message_ref_t & message_ref, unsigned int redirection_deep ) override { m_actual_mbox->do_deliver_message( delivery_mode, type_index, message_ref, redirection_deep ); } virtual void subscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) override { ++subscriptions; m_actual_mbox->subscribe_event_handler( type_index, subscriber ); } virtual void unsubscribe_event_handler( const std::type_index & type_index, so_5::abstract_message_sink_t & subscriber ) noexcept override { ++unsubscriptions; m_actual_mbox->unsubscribe_event_handler( type_index, subscriber ); } virtual std::string query_name() const override { return m_actual_mbox->query_name(); } virtual so_5::mbox_type_t type() const override { return m_actual_mbox->type(); } virtual void set_delivery_filter( const std::type_index & msg_type, const so_5::delivery_filter_t & filter, so_5::abstract_message_sink_t & subscriber ) override { m_actual_mbox->set_delivery_filter( msg_type, filter, subscriber ); } virtual void drop_delivery_filter( const std::type_index & msg_type, so_5::abstract_message_sink_t & subscriber ) noexcept override { m_actual_mbox->drop_delivery_filter( msg_type, subscriber ); } so_5::environment_t & environment() const noexcept override { return m_actual_mbox->environment(); } static so_5::mbox_t create( so_5::environment_t & env ) { return so_5::mbox_t( new test_mbox_t( env ) ); } }; unsigned int test_mbox_t::subscriptions = 0; unsigned int test_mbox_t::unsubscriptions = 0; struct msg_one : public so_5::signal_t {}; struct msg_two : public so_5::signal_t {}; class a_first_t : public so_5::agent_t { public : a_first_t( so_5::environment_t & env, const so_5::mbox_t & mbox ) : so_5::agent_t( env ) { so_subscribe( mbox ).event( &a_first_t::evt_one ); so_subscribe( mbox ).event( &a_first_t::evt_two ); } void evt_one( mhood_t< msg_one > ) {} void evt_two( mhood_t< msg_two > ) {} }; class a_second_t : public so_5::agent_t { public : a_second_t( so_5::environment_t & env, const so_5::mbox_t & mbox ) : so_5::agent_t( env ) { so_subscribe( mbox ).event( &a_second_t::evt_one ); } void evt_one( mhood_t< msg_one > ) {} }; class a_third_t : public so_5::agent_t { public : a_third_t( so_5::environment_t & env, const so_5::mbox_t & mbox ) : so_5::agent_t( env ) { so_subscribe( mbox ).event( &a_third_t::evt_two ); so_subscribe( mbox ).event( &a_third_t::evt_one ); } void evt_one( mhood_t< msg_one > ) {} void evt_two( mhood_t< msg_two > ) {} }; class a_stopper_t final : public so_5::agent_t { public : using so_5::agent_t::agent_t; void so_evt_start() override { so_environment().stop(); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { { auto test_mbox = test_mbox_t::create( env ); auto coop1 = env.make_coop(); { (void)std::make_unique< a_first_t >( std::ref(env), std::cref(test_mbox) ); } coop1->make_agent< a_second_t >( std::cref(test_mbox) ); coop1->make_agent< a_third_t >( std::cref(test_mbox) ); } env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_stopper_t >(); } ); } ); if( test_mbox_t::subscriptions != test_mbox_t::unsubscriptions ) { std::cerr << "subscriptions(" << test_mbox_t::subscriptions << ") != unsubscriptions(" << test_mbox_t::unsubscriptions << ") !!!" << std::endl; return 3; } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/introduce_named_mbox/000077500000000000000000000000001523065210100236045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/introduce_named_mbox/CMakeLists.txt000066400000000000000000000000311523065210100263360ustar00rootroot00000000000000add_subdirectory(basic) sobjectizer-5.8.6/dev/test/so_5/mbox/introduce_named_mbox/basic/000077500000000000000000000000001523065210100246655ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/introduce_named_mbox/basic/CMakeLists.txt000066400000000000000000000001531523065210100274240ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.introduce_named_mbox.basic) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/introduce_named_mbox/basic/main.cpp000066400000000000000000000107211523065210100263160ustar00rootroot00000000000000#include #include #include #include UT_UNIT_TEST( mbox_namespace_name ) { UT_CHECK_THROW( so_5::exception_t, so_5::mbox_namespace_name_t{ "" } ); std::string_view name{ "a" }; so_5::mbox_namespace_name_t mbox_namespace{ name }; UT_CHECK_EQ( name, mbox_namespace.query_name() ); } UT_UNIT_TEST( all_different_names ) { so_5::mbox_id_t first_id{}, second_id{}, third_id{}; run_with_time_limit( [&] { so_5::launch( [&](so_5::environment_t & env) { auto first = env.create_mbox( "demo" ); first_id = first->id(); auto second = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "global" }, "demo", [&env]() { return env.create_mbox(); } ); second_id = second->id(); auto third = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "local" }, "demo", [&env]() { return env.create_mbox(); } ); third_id = third->id(); } ); }, 5 ); UT_CHECK_NE( first_id, second_id ); UT_CHECK_NE( first_id, third_id ); UT_CHECK_NE( second_id, third_id ); } UT_UNIT_TEST( duplicate_names ) { so_5::mbox_id_t first_0{}, first_1{}, second_0{}, second_1{}, third_0{}, third_1{}; run_with_time_limit( [&] { so_5::launch( [&](so_5::environment_t & env) { { auto m1 = env.create_mbox( "demo" ); first_0 = m1->id(); auto m2 = env.create_mbox( "demo" ); first_1 = m2->id(); } { auto m1 = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "global" }, "demo", [&env]() { return env.create_mbox(); } ); second_0 = m1->id(); auto m2 = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "global" }, "demo", [&env]() { return env.create_mbox(); } ); second_1 = m2->id(); } { auto m1 = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "local" }, "demo", [&env]() { return env.create_mbox(); } ); third_0 = m1->id(); auto m2 = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "local" }, "demo", [&env]() { return env.create_mbox(); } ); third_1 = m2->id(); } } ); }, 5 ); UT_CHECK_EQ( first_0, first_1 ); UT_CHECK_EQ( second_0, second_1 ); UT_CHECK_EQ( third_0, third_1 ); UT_CHECK_NE( first_0, second_0 ); UT_CHECK_NE( first_0, third_0 ); UT_CHECK_NE( second_0, third_0 ); } UT_UNIT_TEST( nested_factory_call ) { so_5::mbox_id_t first_id{}, second_id{}, third_id{}; run_with_time_limit( [&] { so_5::launch( [&](so_5::environment_t & env) { auto m1 = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "global" }, "demo", [&]() { auto m2 = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "local" }, "demo", [&]() { auto m3 = env.create_mbox( "demo" ); third_id = m3->id(); return m3; } ); second_id = m2->id(); return m2; } ); first_id = m1->id(); } ); }, 5 ); UT_CHECK_EQ( first_id, second_id ); UT_CHECK_EQ( first_id, third_id ); } UT_UNIT_TEST( exception_from_factory ) { run_with_time_limit( [&] { so_5::launch( [&](so_5::environment_t & env) { try { auto m1 = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "global" }, "demo", []() -> so_5::mbox_t { throw std::runtime_error{ "Oops!" }; } ); // We can't be here! std::cerr << "An exception has to be thrown by " "introduce_named_mbox call, aborting..." << std::endl; std::abort(); } catch( const std::runtime_error & x ) { if( std::string_view{ "Oops!" } != x.what() ) { std::cerr << "Unexpected exception caught: " << x.what() << std::endl << "Aborting..." << std::endl; std::abort(); } } std::ignore = env.introduce_named_mbox( so_5::mbox_namespace_name_t{ "global" }, "demo", [&env]() { return env.create_mbox(); } ); } ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( mbox_namespace_name ) UT_RUN_UNIT_TEST( all_different_names ) UT_RUN_UNIT_TEST( duplicate_names ) UT_RUN_UNIT_TEST( nested_factory_call ) UT_RUN_UNIT_TEST( exception_from_factory ) } sobjectizer-5.8.6/dev/test/so_5/mbox/local_mbox_growth/000077500000000000000000000000001523065210100231305ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/local_mbox_growth/CMakeLists.txt000066400000000000000000000001421523065210100256650ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.local_mbox_growth) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/local_mbox_growth/main.cpp000066400000000000000000000076361523065210100245740ustar00rootroot00000000000000/* * A test for local_mbox adaptive subscription container. */ #include #include class a_test_t : public so_5::agent_t { struct ping : public so_5::signal_t {}; struct pong : public so_5::signal_t {}; const so_5::state_t st_creating_coops{ this }; const so_5::state_t st_destroying_coops{ this }; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_ping_mbox( so_environment().create_mbox() ) {} virtual void so_define_agent() override { this >>= st_creating_coops; st_creating_coops .event( &a_test_t::evt_coop_registered ) .event( &a_test_t::evt_pong_when_creating ); st_destroying_coops .event( &a_test_t::evt_coop_deregistered ) .event( &a_test_t::evt_pong_when_destroying ); } virtual void so_evt_start() override { create_next_coop(); } private : const so_5::mbox_t m_ping_mbox; unsigned int m_iterations_passed = { 0 }; unsigned int m_last_coop_size = { 1 }; std::size_t m_live_agents = { 0 }; std::size_t m_pongs_received = { 0 }; std::vector< so_5::coop_handle_t > m_live_coops; void evt_coop_registered( const so_5::msg_coop_registered & ) { so_5::send< ping >( m_ping_mbox ); } void evt_pong_when_creating(mhood_t< pong >) { ++m_pongs_received; if( m_pongs_received == m_live_agents ) { m_pongs_received = 0; if( m_live_coops.size() < 16 ) { m_last_coop_size *= 2; create_next_coop(); } else { this >>= st_destroying_coops; destroy_next_coop(); } } } void evt_coop_deregistered( const so_5::msg_coop_deregistered & ) { if( m_live_coops.empty() ) { ++m_iterations_passed; if( m_iterations_passed >= 5 ) so_deregister_agent_coop_normally(); else { std::cout << "--- NEXT ITERATION ---" << std::endl; this >>= st_creating_coops; m_last_coop_size = 1; create_next_coop(); } } else so_5::send< ping >( m_ping_mbox ); } void evt_pong_when_destroying(mhood_t< pong >) { ++m_pongs_received; if( m_pongs_received == m_live_agents ) { m_pongs_received = 0; destroy_next_coop(); } } void create_next_coop() { auto coop = so_5::create_child_coop( *this ); coop->add_reg_notificator( so_5::make_coop_reg_notificator( so_direct_mbox() ) ); coop->add_dereg_notificator( so_5::make_coop_dereg_notificator( so_direct_mbox() ) ); class actor_t final : public so_5::agent_t { const so_5::mbox_t m_pong_mbox; public : actor_t( context_t ctx, const so_5::mbox_t & ping_mbox, so_5::mbox_t pong_mbox ) : so_5::agent_t{ std::move(ctx) } , m_pong_mbox{ std::move(pong_mbox) } { so_subscribe( ping_mbox ).event( [this](mhood_t) { so_5::send< pong >( m_pong_mbox ); } ); } }; for( unsigned int i = 0; i != m_last_coop_size; ++i ) coop->make_agent< actor_t >( std::cref(m_ping_mbox), so_direct_mbox() ); auto handle = so_environment().register_coop( std::move( coop ) ); m_live_agents += m_last_coop_size; m_live_coops.push_back( handle ); } void destroy_next_coop() { auto coop_to_destroy = m_live_coops.back(); m_live_coops.pop_back(); m_live_agents -= m_last_coop_size; m_last_coop_size /= 2; so_environment().deregister_coop( coop_to_destroy, so_5::dereg_reason::normal ); } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 240, "local_mbox_growth" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/make_new_direct_mbox/000077500000000000000000000000001523065210100235645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/make_new_direct_mbox/CMakeLists.txt000066400000000000000000000001451523065210100263240ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.make_new_direct_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/make_new_direct_mbox/main.cpp000066400000000000000000000023311523065210100252130ustar00rootroot00000000000000/* * A test for agent_t::so_make_new_direct_mbox(). */ #include #include #include #include class a_test_t final : public so_5::agent_t { struct first final : public so_5::signal_t {}; struct second final : public so_5::signal_t {}; public : using so_5::agent_t::agent_t; void so_evt_start() override { so_subscribe_self() .event( [](mhood_t) {} ) .event( [](mhood_t) { throw std::runtime_error( "second shouldn't be " "received from so_direct_mbox()" ); } ); const auto another = so_make_new_direct_mbox(); so_subscribe( another ) .event( [](mhood_t) { throw std::runtime_error( "second shouldn't be " "received from another direct mbox" ); } ) .event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); so_5::send< first >( *this ); so_5::send< second >( another ); } }; int main() { run_with_time_limit( [] { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/mpsc_mbox/000077500000000000000000000000001523065210100214065ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/mpsc_mbox/CMakeLists.txt000066400000000000000000000001321523065210100241420ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.mpsc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/mpsc_mbox/main.cpp000066400000000000000000000041431523065210100230400ustar00rootroot00000000000000/* * A test for mpsc_mbox. */ #include #include #include struct msg_one : public so_5::signal_t {}; struct msg_two : public so_5::signal_t {}; struct msg_three : public so_5::signal_t {}; struct msg_four : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, std::string & sequence ) : base_type_t( env ) , m_sequence( sequence ) { } void so_define_agent() override { so_subscribe( so_direct_mbox() ) .event( &a_test_t::evt_one ); so_subscribe( so_direct_mbox() ) .event( &a_test_t::evt_three ); so_subscribe( so_direct_mbox() ) .event( &a_test_t::evt_four ); } void so_evt_start() override { so_5::send< msg_one >( *this ); so_5::send< msg_two >( *this ); so_5::send< msg_three >( *this ); } void evt_one( mhood_t< msg_one > ) { m_sequence += "e1:"; } void evt_two( mhood_t< msg_two > ) { m_sequence += "e2:"; } void evt_three( mhood_t< msg_three > ) { m_sequence += "e3:"; so_drop_subscription( so_direct_mbox(), &a_test_t::evt_one ); so_subscribe( so_direct_mbox() ).event( &a_test_t::evt_two ); so_5::send< msg_one >( *this ); so_5::send< msg_two >( *this ); so_5::send< msg_four >( *this ); } void evt_four( mhood_t< msg_four > ) { m_sequence += "e4:"; so_environment().stop(); } private : std::string & m_sequence; }; int main() { try { std::string sequence; so_5::launch( [&sequence]( so_5::environment_t & env ) { auto coop = env.make_coop( so_5::disp::active_obj::make_dispatcher( env ).binder() ); coop->make_agent< a_test_t >( std::ref(sequence) ); env.register_coop( std::move( coop ) ); } ); const std::string expected = "e1:e3:e2:e4:"; if( sequence != expected ) throw std::runtime_error( "sequence mismatch! " "expected: '" + expected + "', actual: '" + sequence + "'" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/mpsc_mbox_illegal_subscriber/000077500000000000000000000000001523065210100253225ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/mpsc_mbox_illegal_subscriber/CMakeLists.txt000066400000000000000000000001551523065210100300630ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.mpsc_mbox_illegal_subscriber) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/mpsc_mbox_illegal_subscriber/main.cpp000066400000000000000000000030471523065210100267560ustar00rootroot00000000000000/* * A test for mpsc_mbox: subscription form another agent must * lead to exception. */ #include #include #include struct msg_one : public so_5::signal_t {}; class a_first_t : public so_5::agent_t { public : a_first_t( so_5::environment_t & env ) : so_5::agent_t( env ) { } void so_define_agent() override { so_subscribe( so_direct_mbox() ).event( &a_first_t::evt_one ); } void so_evt_start() override { so_environment().stop(); } void evt_one( mhood_t< msg_one > ) {} }; class a_second_t : public so_5::agent_t { public : a_second_t( so_5::environment_t & env, const so_5::mbox_t & mbox ) : so_5::agent_t( env ) , m_mbox( mbox ) { } void so_define_agent() override { try { so_subscribe( m_mbox ).event( &a_second_t::evt_one ); // Shouldn't be here! throw std::runtime_error{ "successful subscription!" }; } catch( const so_5::exception_t & x ) { if( so_5::rc_illegal_subscriber_for_mpsc_mbox != x.error_code() ) throw; } } void evt_one( mhood_t< msg_one > ) {} private : const so_5::mbox_t m_mbox; }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { auto coop = env.make_coop(); auto a_first = coop->make_agent< a_first_t >(); coop->make_agent< a_second_t >( a_first->so_direct_mbox() ); env.register_coop( std::move( coop ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/mpsc_mbox_stress/000077500000000000000000000000001523065210100230115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/mpsc_mbox_stress/CMakeLists.txt000066400000000000000000000001341523065210100255470ustar00rootroot00000000000000set(UNITTEST _test.so_5.mpsc_mbox_stress) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/mpsc_mbox_stress/main.cpp000066400000000000000000000131651523065210100244470ustar00rootroot00000000000000/* * A test for massive usage of direct mboxes even when owners are * destroyed. */ #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif struct msg_ping : public so_5::signal_t {}; struct msg_ack : public so_5::signal_t {}; struct msg_child_agent_destroyed : public so_5::signal_t {}; struct msg_next_iteration : public so_5::signal_t {}; // A class of child agent. class a_child_t : public so_5::agent_t { public : a_child_t( so_5::environment_t & env, const so_5::mbox_t & parent_mbox ) : so_5::agent_t( env ) , m_parent_mbox( parent_mbox ) {} ~a_child_t() override { so_5::send< msg_child_agent_destroyed >( m_parent_mbox ); } void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< msg_ping >) { so_5::send< msg_ack >( m_parent_mbox ); } ); } private : const so_5::mbox_t m_parent_mbox; }; // A class of parent agent. class a_parent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_parent_t( so_5::environment_t & env, int iterations ) : base_type_t( env ) , m_iterations_left( iterations ) , m_state( state_t::awaiting_creation ) , m_max_agents( 1000 ) , m_acks_received( 0 ) , m_destroy_received( 0 ) {} void so_define_agent() override { so_subscribe_self().event( &a_parent_t::evt_child_created ); so_subscribe_self().event( &a_parent_t::evt_child_destroyed ); so_subscribe_self().event( &a_parent_t::evt_ack ); so_subscribe_self().event( &a_parent_t::evt_child_agent_destroyed ); so_subscribe_self().event( &a_parent_t::evt_next_iteration ); } void so_evt_start() override { try_start_new_iteration(); } void evt_child_created( const so_5::msg_coop_registered & ) { if( m_state != state_t::awaiting_creation ) throw std::runtime_error( "expected awaiting_creation state!" ); m_state = state_t::awaiting_acks; for( auto & m : m_child_mboxes ) so_5::send< msg_ping >( m ); } void evt_child_destroyed( const so_5::msg_coop_deregistered & ) { if( m_state != state_t::awaiting_destroying ) throw std::runtime_error( "msg_coop_deregistered when " "m_state != state_t::awaiting_destroying" ); if( m_destroy_received != m_max_agents ) throw std::runtime_error( "not all agents destroyed before " "msg_coop_deregistered received" ); // This action must not lead to any damages (like memory leaks). consume_some_memory(); run_with_time_limit( [this] { for( auto & m : m_child_mboxes ) so_5::send< msg_ping >( m ); }, 40, "attempts to send signal to MPSC mbox of destroyed agent" ); --m_iterations_left; try_start_new_iteration(); } void evt_ack(mhood_t< msg_ack >) { if( m_state != state_t::awaiting_acks ) throw std::runtime_error( "msg_ack when " "m_state != state_t::awaiting_acks" ); ++m_acks_received; if( m_acks_received == m_max_agents ) { m_state = state_t::awaiting_destroying; so_environment().deregister_coop( m_child_coop_handle, so_5::dereg_reason::normal ); } } void evt_child_agent_destroyed(mhood_t< msg_child_agent_destroyed >) { if( m_state != state_t::awaiting_destroying ) throw std::runtime_error( "msg_child_agent_destroyed when " "m_state != state_t::awaiting_destroying" ); ++m_destroy_received; } void evt_next_iteration(mhood_t< msg_next_iteration >) { try_start_new_iteration(); } private : int m_iterations_left; enum class state_t { awaiting_creation, awaiting_acks, awaiting_destroying }; state_t m_state; const std::size_t m_max_agents; std::size_t m_acks_received; std::size_t m_destroy_received; std::vector< so_5::mbox_t > m_child_mboxes; so_5::coop_handle_t m_child_coop_handle; void try_start_new_iteration() { if( m_iterations_left <= 0 ) { std::cout << "COMPLETED!" << std::endl; so_environment().stop(); return; } std::cout << m_iterations_left << " iterations left...\r" << std::flush; m_state = state_t::awaiting_creation; m_acks_received = 0; m_destroy_received = 0; m_child_mboxes = std::vector< so_5::mbox_t >(); m_child_mboxes.reserve( m_max_agents ); auto coop = so_environment().make_coop( so_coop() ); coop->add_reg_notificator( so_5::make_coop_reg_notificator( so_direct_mbox() ) ); coop->add_dereg_notificator( so_5::make_coop_dereg_notificator( so_direct_mbox() ) ); for( std::size_t i = 0; i != m_max_agents; ++i ) { std::unique_ptr< so_5::agent_t > agent( new a_child_t( so_environment(), so_direct_mbox() ) ); m_child_mboxes.push_back( agent->so_direct_mbox() ); coop->add_agent( std::move( agent ) ); } m_child_coop_handle = so_environment().register_coop( std::move( coop ) ); } void consume_some_memory() { std::vector< std::vector< int > > v; v.reserve( 10 ); for( std::size_t i = 1; i != 10; ++i ) { v.push_back( std::vector< int >( i*1024u, 12345 ) ); } } }; int main( int argc, char ** argv ) { try { const int iterations = argc == 2 ? std::atoi( argv[ 1 ] ) : 100; so_5::launch( [iterations]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_parent_t >( iterations ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/000077500000000000000000000000001523065210100220555ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/CMakeLists.txt000066400000000000000000000014571523065210100246240ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(simple_2) add_subdirectory(simple_with_delivery_filter) add_subdirectory(simple_with_delivery_filter_2) add_subdirectory(single_sink_clear) add_subdirectory(single_sink_too_deep) add_subdirectory(single_sink_mutable) add_subdirectory(single_sink_mutable_with_dr) add_subdirectory(multi_sink_simple) add_subdirectory(multi_sink_simple_2) add_subdirectory(multi_sink_with_delivery_filter) add_subdirectory(multi_sink_with_delivery_filter_2) add_subdirectory(multi_sink_unbind) add_subdirectory(multi_sink_mutable) add_subdirectory(multi_sink_mutable_2) add_subdirectory(multi_sink_mutable_with_dr) add_subdirectory(bind_then_transform_msg) add_subdirectory(bind_then_transform_signal) add_subdirectory(transformer_redirect_deep) add_subdirectory(transformer_and_envelope) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/000077500000000000000000000000001523065210100267505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/CMakeLists.txt000066400000000000000000000010061523065210100315050ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.bind_then_transform_msg) set(UNITTEST_SRCFILES impl_no_opt_no_dr.cpp impl_no_opt_with_dr.cpp impl_no_opt_with_dr2.cpp impl_with_opt_no_dr.cpp impl_with_opt_with_dr.cpp expl_no_opt_no_dr.cpp expl_no_opt_with_dr.cpp expl_no_opt_with_dr2.cpp expl_with_opt_no_dr.cpp expl_with_opt_with_dr.cpp message_holder_no_opt_no_dr.cpp impl_free_func_no_opt_no_dr.cpp impl_free_func_with_opt_no_dr.cpp main.cpp ) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/common.hpp000066400000000000000000000236561523065210100307650ustar00rootroot00000000000000#pragma once #include #include #include #include namespace test { struct msg_src_1 final : public so_5::message_t { const so_5::mbox_t m_dest; int m_a; int m_b; int m_c; msg_src_1( so_5::mbox_t dest, int a, int b, int c ) : m_dest{ std::move(dest) }, m_a{ a }, m_b{ b }, m_c{ c } {} }; struct msg_res_1 final : public so_5::message_t { std::string m_v; explicit msg_res_1( std::string v ) : m_v{ std::move(v) } {} }; struct msg_src_2 { const so_5::mbox_t m_dest; int m_a; int m_b; int m_c; msg_src_2( so_5::mbox_t dest, int a, int b, int c ) : m_dest{ std::move(dest) }, m_a{ a }, m_b{ b }, m_c{ c } {} }; struct msg_res_2 { std::string m_v; explicit msg_res_2( std::string v ) : m_v{ std::move(v) } {} }; struct msg_complete final : public so_5::signal_t {}; template< typename Msg > struct mhood_selector_t { using type = so_5::mhood_t; }; template< typename Msg > struct mhood_selector_t< so_5::mutable_msg > { using type = so_5::mutable_mhood_t; }; template< typename Msg > using mhood_from_param_t = typename mhood_selector_t::type; template< typename Msg > struct dr_param_selector_t { using type = Msg; }; template< typename Msg > struct dr_param_selector_t< so_5::immutable_msg > { using type = Msg; }; template< typename Msg > struct dr_param_selector_t< so_5::mutable_msg > { using type = Msg; }; template< typename Msg > using dr_param_from_source_msg_t = typename dr_param_selector_t::type; template< typename Result_Msg, typename Test_Case_Handler > class a_receiver_t final : public so_5::agent_t { std::string m_log; public: a_receiver_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe_self() .event( &a_receiver_t::evt_res ) .event( &a_receiver_t::evt_complete ) ; } private: void evt_res( mhood_from_param_t cmd ) { m_log += cmd->m_v; m_log += ';'; } void evt_complete( so_5::mhood_t ) { Test_Case_Handler::check_result( m_log ); so_deregister_agent_coop_normally(); } }; template< typename Binding, typename Source_Msg, typename Result_Msg, typename Test_Case_Handler > class a_sender_t final : public so_5::agent_t { const so_5::mbox_t m_receiver; public: a_sender_t( context_t ctx, so_5::mbox_t receiver ) : so_5::agent_t{ std::move(ctx) } , m_receiver{ std::move(receiver) } {} void so_evt_start() override { Binding binding; const auto src_mbox = so_5::make_unique_subscribers_mbox( so_environment() ); Test_Case_Handler::template tune_binding< Source_Msg, Result_Msg >( binding, src_mbox, m_receiver ); so_5::send< Source_Msg >( src_mbox, m_receiver, 1, 2, 3 ); so_5::send< Source_Msg >( src_mbox, m_receiver, 2, 3, 4 ); so_5::send< Source_Msg >( src_mbox, m_receiver, 3, 4, 5 ); so_5::send< Source_Msg >( src_mbox, m_receiver, 4, 5, 6 ); so_5::send< msg_complete >( m_receiver ); } }; template< typename Binding, typename Source_Msg, typename Result_Msg, typename Test_Case_Handler > void run_test_case() { using receiver_t = a_receiver_t< Result_Msg, Test_Case_Handler >; using sender_t = a_sender_t< Binding, Source_Msg, Result_Msg, Test_Case_Handler >; std::cout << "running test case:" << "\n Binding: " << typeid(Binding).name() << "\n Source : " << typeid(Source_Msg).name() << "\n Result : " << typeid(Result_Msg).name() << "\n Name : " << Test_Case_Handler::name() << "\n ... : " << std::flush; run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * receiver = coop.make_agent< receiver_t >(); coop.make_agent< sender_t >( receiver->so_direct_mbox() ); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 5 ); std::cout << "OK" << std::endl; } inline void ensure_valid_or_die( const std::string_view case_name, const std::string_view expected, const std::string_view actual ) { ensure_or_die( expected == actual, std::string{ case_name } + ": expected='" + std::string{ expected } + "', actual='" + std::string{ actual } + "'" ); } template< typename Source_Msg, typename Result_Msg, typename Test_Case_Handler > void run_test_case_for_msg_pair() { constexpr bool is_applicable_to_implicit_case = std::is_same_v< typename so_5::message_payload_type< Source_Msg >::payload_type, Source_Msg >; if constexpr( ( Test_Case_Handler::is_implicit && is_applicable_to_implicit_case ) || !Test_Case_Handler::is_implicit ) { run_test_case< so_5::single_sink_binding_t, Source_Msg, Result_Msg, Test_Case_Handler >(); run_test_case< so_5::multi_sink_binding_t<>, Source_Msg, Result_Msg, Test_Case_Handler >(); } } template< typename Test_Case_Handler > void run_tests_for_case_handler() { // msg_src_1, msg_res_1 // run_test_case_for_msg_pair< msg_src_1, msg_res_1, Test_Case_Handler >(); // msg_src_2, msg_res_1 // run_test_case_for_msg_pair< msg_src_2, msg_res_1, Test_Case_Handler >(); // msg_src_2, msg_res_2 // run_test_case_for_msg_pair< msg_src_2, msg_res_2, Test_Case_Handler >(); // msg_src_1, msg_res_2 // run_test_case_for_msg_pair< msg_src_1, msg_res_2, Test_Case_Handler >(); // so_5::immutable_msg. // msg_src_1, msg_res_1 // run_test_case_for_msg_pair< so_5::immutable_msg, msg_res_1, Test_Case_Handler >(); run_test_case_for_msg_pair< msg_src_1, so_5::immutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::immutable_msg, so_5::immutable_msg, Test_Case_Handler >(); // msg_src_2, msg_res_1 // run_test_case_for_msg_pair< so_5::immutable_msg, msg_res_1, Test_Case_Handler >(); run_test_case_for_msg_pair< msg_src_2, so_5::immutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::immutable_msg, so_5::immutable_msg, Test_Case_Handler >(); // msg_src_2, msg_res_2 // run_test_case_for_msg_pair< so_5::immutable_msg, msg_res_2, Test_Case_Handler >(); run_test_case_for_msg_pair< msg_src_2, so_5::immutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::immutable_msg, so_5::immutable_msg, Test_Case_Handler >(); // msg_src_1, msg_res_2 // run_test_case_for_msg_pair< so_5::immutable_msg, msg_res_2, Test_Case_Handler >(); run_test_case_for_msg_pair< msg_src_1, so_5::immutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::immutable_msg, so_5::immutable_msg, Test_Case_Handler >(); // so_5::mutable_msg. // msg_src_1, msg_res_1 // run_test_case_for_msg_pair< so_5::mutable_msg, msg_res_1, Test_Case_Handler >(); run_test_case_for_msg_pair< msg_src_1, so_5::mutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::mutable_msg, so_5::mutable_msg, Test_Case_Handler >(); // msg_src_2, msg_res_1 // run_test_case_for_msg_pair< so_5::mutable_msg, msg_res_1, Test_Case_Handler >(); run_test_case_for_msg_pair< msg_src_2, so_5::mutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::mutable_msg, so_5::mutable_msg, Test_Case_Handler >(); // msg_src_2, msg_res_2 // run_test_case_for_msg_pair< so_5::mutable_msg, msg_res_2, Test_Case_Handler >(); run_test_case_for_msg_pair< msg_src_2, so_5::mutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::mutable_msg, so_5::mutable_msg, Test_Case_Handler >(); // msg_src_1, msg_res_2 // run_test_case_for_msg_pair< so_5::mutable_msg, msg_res_2, Test_Case_Handler >(); run_test_case_for_msg_pair< msg_src_1, so_5::mutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::mutable_msg, so_5::mutable_msg, Test_Case_Handler >(); // so_5::mutable_msg + so_5::immutable_msg. // msg_src_1, msg_res_1 // run_test_case_for_msg_pair< so_5::mutable_msg, so_5::immutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::immutable_msg, so_5::mutable_msg, Test_Case_Handler >(); // msg_src_2, msg_res_1 // run_test_case_for_msg_pair< so_5::mutable_msg, so_5::immutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::immutable_msg, so_5::mutable_msg, Test_Case_Handler >(); // msg_src_2, msg_res_2 // run_test_case_for_msg_pair< so_5::mutable_msg, so_5::immutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::immutable_msg, so_5::mutable_msg, Test_Case_Handler >(); // msg_src_1, msg_res_2 // run_test_case_for_msg_pair< so_5::mutable_msg, so_5::immutable_msg, Test_Case_Handler >(); run_test_case_for_msg_pair< so_5::immutable_msg, so_5::mutable_msg, Test_Case_Handler >(); } struct implicit_type_case_t { static constexpr bool is_implicit = true; }; struct explicit_type_case_t { static constexpr bool is_implicit = false; }; } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/expl_no_opt_no_dr.cpp000066400000000000000000000016141523065210100331650ustar00rootroot00000000000000#include "common.hpp" namespace test { struct explicit_type_no_optional_no_dr_t : public explicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "explicit_type_no_optional_no_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer< Source_Msg >( binding, from, [to]( auto && src ) { return so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ); } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "1-3;2-4;3-5;4-6;", log ); } }; void run_explicit_type_no_optional_no_dr() { run_tests_for_case_handler< explicit_type_no_optional_no_dr_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/expl_no_opt_with_dr.cpp000066400000000000000000000017661523065210100335340ustar00rootroot00000000000000#include "common.hpp" namespace test { struct explicit_type_no_optional_with_dr_t : public explicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "explicit_type_no_optional_with_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer< Source_Msg >( binding, from, [to]( auto && src ) { return so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ); }, []( dr_param_from_source_msg_t const & src ) { return 1 != src.m_a; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "2-4;3-5;4-6;", log ); } }; void run_explicit_type_no_optional_with_dr() { run_tests_for_case_handler< explicit_type_no_optional_with_dr_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/expl_no_opt_with_dr2.cpp000066400000000000000000000017301523065210100336050ustar00rootroot00000000000000#include "common.hpp" namespace test { struct explicit_type_no_optional_with_dr2_t : public explicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "explicit_type_no_optional_with_dr2" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer< Source_Msg >( binding, from, [to]( auto && src ) { return so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ); }, []( const auto & src ) { return 1 != src.m_a; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "2-4;3-5;4-6;", log ); } }; void run_explicit_type_no_optional_with_dr2() { run_tests_for_case_handler< explicit_type_no_optional_with_dr2_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/expl_with_opt_no_dr.cpp000066400000000000000000000021131523065210100335170ustar00rootroot00000000000000#include "common.hpp" namespace test { struct explicit_type_with_optional_no_dr_t : public explicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "explicit_type_with_optional_no_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { using ret_val_t = std::optional< so_5::transformed_message_t< Result_Msg > >; so_5::bind_transformer< Source_Msg >( binding, from, [to]( auto && src ) -> ret_val_t { if( 3 == src.m_a && 4 == src.m_b && 5 == src.m_c ) return std::nullopt; return { so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ) }; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "1-3;2-4;4-6;", log ); } }; void run_explicit_type_with_optional_no_dr() { run_tests_for_case_handler< explicit_type_with_optional_no_dr_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/expl_with_opt_with_dr.cpp000066400000000000000000000022651523065210100340660ustar00rootroot00000000000000#include "common.hpp" namespace test { struct explicit_type_with_optional_with_dr_t : public explicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "explicit_type_with_optional_with_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { using ret_val_t = std::optional< so_5::transformed_message_t< Result_Msg > >; so_5::bind_transformer< Source_Msg >( binding, from, [to]( auto && src ) -> ret_val_t { if( 3 == src.m_a && 4 == src.m_b && 5 == src.m_c ) return std::nullopt; return { so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ) }; }, []( dr_param_from_source_msg_t const & src ) { return 1 != src.m_a; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "2-4;4-6;", log ); } }; void run_explicit_type_with_optional_with_dr() { run_tests_for_case_handler< explicit_type_with_optional_with_dr_t >(); } } /* namespace test */ impl_free_func_no_opt_no_dr.cpp000066400000000000000000000022621523065210100351130ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg#include "common.hpp" namespace test { struct implicit_type_free_func_no_optional_no_dr_t : public implicit_type_case_t { template< typename Source_Msg, typename Result_Msg > struct actual_transformer_t { [[nodiscard]] static so_5::transformed_message_t< Result_Msg > transform( const Source_Msg & src ) { return so_5::make_transformed< Result_Msg >( src.m_dest, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ); } }; [[nodiscard]] static std::string_view name() { return { "implicit_type_free_func_no_optional_no_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & /*to*/ ) { so_5::bind_transformer( binding, from, actual_transformer_t< Source_Msg, Result_Msg >::transform ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "1-3;2-4;3-5;4-6;", log ); } }; void run_implicit_type_free_func_no_optional_no_dr() { run_tests_for_case_handler< implicit_type_free_func_no_optional_no_dr_t >(); } } /* namespace test */ impl_free_func_with_opt_no_dr.cpp000066400000000000000000000024411523065210100354510ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg#include "common.hpp" namespace test { struct implicit_type_free_func_with_optional_no_dr_t : public implicit_type_case_t { template< typename Source_Msg, typename Result_Msg > struct actual_transformer_t { [[nodiscard]] static std::optional< so_5::transformed_message_t< Result_Msg > > transform( const Source_Msg & src ) { if( 3 == src.m_a && 4 == src.m_b && 5 == src.m_c ) return std::nullopt; return { so_5::make_transformed< Result_Msg >( src.m_dest, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ) }; } }; [[nodiscard]] static std::string_view name() { return { "implicit_type_free_func_with_optional_no_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & /*to*/ ) { so_5::bind_transformer( binding, from, actual_transformer_t< Source_Msg, Result_Msg >::transform ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "1-3;2-4;4-6;", log ); } }; void run_implicit_type_free_func_with_optional_no_dr() { run_tests_for_case_handler< implicit_type_free_func_with_optional_no_dr_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/impl_no_opt_no_dr.cpp000066400000000000000000000016111523065210100331530ustar00rootroot00000000000000#include "common.hpp" namespace test { struct implicit_type_no_optional_no_dr_t : public implicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "implicit_type_no_optional_no_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer( binding, from, [to]( const Source_Msg & src ) { return so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ); } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "1-3;2-4;3-5;4-6;", log ); } }; void run_implicit_type_no_optional_no_dr() { run_tests_for_case_handler< implicit_type_no_optional_no_dr_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/impl_no_opt_with_dr.cpp000066400000000000000000000017631523065210100335220ustar00rootroot00000000000000#include "common.hpp" namespace test { struct implicit_type_no_optional_with_dr_t : public implicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "implicit_type_no_optional_with_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer( binding, from, [to]( const Source_Msg & src ) { return so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ); }, []( dr_param_from_source_msg_t const & src ) { return 1 != src.m_a; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "2-4;3-5;4-6;", log ); } }; void run_implicit_type_no_optional_with_dr() { run_tests_for_case_handler< implicit_type_no_optional_with_dr_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/impl_no_opt_with_dr2.cpp000066400000000000000000000017261523065210100336030ustar00rootroot00000000000000#include "common.hpp" namespace test { struct implicit_type_no_optional_with_dr2_t : public implicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "implicit_type_no_optional_with_dr2" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer( binding, from, [to]( const Source_Msg & src ) { return so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ); }, []( const auto & src ) { return 1 != src.m_a; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "2-4;3-5;4-6;", log ); } }; void run_implicit_type_no_optional_with_dr2() { run_tests_for_case_handler< implicit_type_no_optional_with_dr2_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/impl_with_opt_no_dr.cpp000066400000000000000000000021111523065210100335060ustar00rootroot00000000000000#include "common.hpp" namespace test { struct implicit_type_with_optional_no_dr_t : public implicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "implicit_type_with_optional_no_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { using ret_val_t = std::optional< so_5::transformed_message_t< Result_Msg > >; so_5::bind_transformer( binding, from, [to]( const Source_Msg & src ) -> ret_val_t { if( 3 == src.m_a && 4 == src.m_b && 5 == src.m_c ) return std::nullopt; return { so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ) }; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "1-3;2-4;4-6;", log ); } }; void run_implicit_type_with_optional_no_dr() { run_tests_for_case_handler< implicit_type_with_optional_no_dr_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/impl_with_opt_with_dr.cpp000066400000000000000000000022621523065210100340540ustar00rootroot00000000000000#include "common.hpp" namespace test { struct implicit_type_with_optional_with_dr_t : public implicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "implicit_type_with_optional_with_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { using ret_val_t = std::optional< so_5::transformed_message_t< Result_Msg > >; so_5::bind_transformer( binding, from, [to]( const Source_Msg & src ) -> ret_val_t { if( 3 == src.m_a && 4 == src.m_b && 5 == src.m_c ) return std::nullopt; return { so_5::make_transformed< Result_Msg >( to, std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ) }; }, []( dr_param_from_source_msg_t const & src ) { return 1 != src.m_a; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "2-4;4-6;", log ); } }; void run_implicit_type_with_optional_with_dr() { run_tests_for_case_handler< implicit_type_with_optional_with_dr_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg/main.cpp000066400000000000000000000025741523065210100304100ustar00rootroot00000000000000/* * Test cases for bind_transformer and messages. */ #include "common.hpp" namespace test { void run_implicit_type_no_optional_no_dr(); void run_implicit_type_no_optional_with_dr(); void run_implicit_type_no_optional_with_dr2(); void run_implicit_type_with_optional_no_dr(); void run_implicit_type_with_optional_with_dr(); void run_explicit_type_no_optional_no_dr(); void run_explicit_type_no_optional_with_dr(); void run_explicit_type_no_optional_with_dr2(); void run_explicit_type_with_optional_no_dr(); void run_explicit_type_with_optional_with_dr(); void run_message_holder_no_optional_no_dr(); void run_implicit_type_free_func_no_optional_no_dr(); void run_implicit_type_free_func_with_optional_no_dr(); void run_tests() { run_implicit_type_no_optional_no_dr(); run_implicit_type_no_optional_with_dr(); run_implicit_type_no_optional_with_dr2(); run_implicit_type_with_optional_no_dr(); run_implicit_type_with_optional_with_dr(); run_explicit_type_no_optional_no_dr(); run_explicit_type_no_optional_with_dr(); run_explicit_type_no_optional_with_dr2(); run_explicit_type_with_optional_no_dr(); run_explicit_type_with_optional_with_dr(); run_message_holder_no_optional_no_dr(); run_implicit_type_free_func_no_optional_no_dr(); run_implicit_type_free_func_with_optional_no_dr(); } } /* namespace test */ int main() { test::run_tests(); return 0; } message_holder_no_opt_no_dr.cpp000066400000000000000000000017421523065210100351210ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_msg#include "common.hpp" namespace test { struct message_holder_no_optional_no_dr_t : public explicit_type_case_t { [[nodiscard]] static std::string_view name() { return { "message_holder_no_optional_no_dr" }; } template< typename Source_Msg, typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer< Source_Msg >( binding, from, [to]( auto && src ) { auto msg = so_5::message_holder_t< Result_Msg >::make( std::to_string( src.m_a ) + "-" + std::to_string( src.m_c ) ); return so_5::make_transformed< Result_Msg >( to, std::move(msg) ); } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "1-3;2-4;3-5;4-6;", log ); } }; void run_message_holder_no_optional_no_dr() { run_tests_for_case_handler< message_holder_no_optional_no_dr_t >(); } } /* namespace test */ sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_signal/000077500000000000000000000000001523065210100274375ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_signal/CMakeLists.txt000066400000000000000000000001701523065210100321750ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.bind_then_transform_signal) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/bind_then_transform_signal/main.cpp000066400000000000000000000217431523065210100310760ustar00rootroot00000000000000/* * Test cases for bind_transformer and signals. */ #include #include #include #include #include namespace test { struct msg_signal final : public so_5::signal_t {}; struct msg_res_1 final : public so_5::message_t { std::string m_v; explicit msg_res_1( std::string v ) : m_v{ std::move(v) } {} }; struct msg_res_2 { std::string m_v; explicit msg_res_2( std::string v ) : m_v{ std::move(v) } {} }; struct msg_res_3 final : public so_5::signal_t {}; struct msg_complete final : public so_5::signal_t {}; template< typename Msg > struct mhood_selector_t { using type = so_5::mhood_t; }; template< typename Msg > struct mhood_selector_t< so_5::mutable_msg > { using type = so_5::mutable_mhood_t; }; template< typename Msg > using mhood_from_param_t = typename mhood_selector_t::type; template< typename Result_Msg, typename Test_Case_Handler > class a_receiver_t final : public so_5::agent_t { std::string m_log; public: a_receiver_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe_self() .event( &a_receiver_t::evt_res ) .event( &a_receiver_t::evt_complete ) ; } private: void evt_res( mhood_from_param_t cmd ) { Test_Case_Handler::on_res( m_log, cmd ); m_log += ';'; } void evt_complete( so_5::mhood_t ) { Test_Case_Handler::check_result( m_log ); so_deregister_agent_coop_normally(); } }; template< typename Binding, typename Result_Msg, typename Test_Case_Handler > class a_sender_t final : public so_5::agent_t { const so_5::mbox_t m_receiver; public: a_sender_t( context_t ctx, so_5::mbox_t receiver ) : so_5::agent_t{ std::move(ctx) } , m_receiver{ std::move(receiver) } {} void so_evt_start() override { Binding binding; const auto src_mbox = so_5::make_unique_subscribers_mbox( so_environment() ); Test_Case_Handler::template tune_binding< Result_Msg >( binding, src_mbox, m_receiver ); so_5::send< msg_signal >( src_mbox ); so_5::send< msg_signal >( src_mbox ); so_5::send< msg_signal >( src_mbox ); so_5::send< msg_complete >( m_receiver ); } }; template< typename Binding, typename Result_Msg, typename Test_Case_Handler > void run_test_case() { using receiver_t = a_receiver_t< Result_Msg, Test_Case_Handler >; using sender_t = a_sender_t< Binding, Result_Msg, Test_Case_Handler >; std::cout << "running test case:" << "\n Binding: " << typeid(Binding).name() << "\n Result : " << typeid(Result_Msg).name() << "\n Name : " << Test_Case_Handler::name() << "\n ... : " << std::flush; run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * receiver = coop.make_agent< receiver_t >(); coop.make_agent< sender_t >( receiver->so_direct_mbox() ); } ); } ); }, 5 ); std::cout << "OK" << std::endl; } void ensure_valid_or_die( const std::string_view case_name, const std::string_view expected, const std::string_view actual ) { ensure_or_die( expected == actual, std::string{ case_name } + ": expected='" + std::string{ expected } + "', actual='" + std::string{ actual } + "'" ); } struct explicit_type_no_optional_t { [[nodiscard]] static std::string_view name() { return { "explicit_type_no_optional" }; } template< typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer< msg_signal >( binding, from, [to]() { return so_5::make_transformed< Result_Msg >( to, "T" ); } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "T;T;T;", log ); } template< typename Mhood > static void on_res( std::string & log, Mhood && cmd ) { log += cmd->m_v; } }; struct explicit_type_no_optional2_t { [[nodiscard]] static std::string_view name() { return { "explicit_type_no_optional2" }; } template< typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer< so_5::immutable_msg >( binding, from, [to]() { return so_5::make_transformed< Result_Msg >( to, "T" ); } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "T;T;T;", log ); } template< typename Mhood > static void on_res( std::string & log, Mhood && cmd ) { log += cmd->m_v; } }; struct explicit_type_with_optional_t { [[nodiscard]] static std::string_view name() { return { "explicit_type_with_optional" }; } template< typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { using ret_val_t = std::optional< so_5::transformed_message_t< Result_Msg > >; so_5::bind_transformer< msg_signal >( binding, from, [to, counter=int{0}]() mutable -> ret_val_t { const auto c = counter++; if( 1 == c ) return std::nullopt; return { so_5::make_transformed< Result_Msg >( to, "T" ) }; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "T;T;", log ); } template< typename Mhood > static void on_res( std::string & log, Mhood && cmd ) { log += cmd->m_v; } }; struct transform_to_singal_no_optional_t { [[nodiscard]] static std::string_view name() { return { "transform_to_singal_no_optional" }; } template< typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { so_5::bind_transformer< msg_signal >( binding, from, [to]() { return so_5::make_transformed< Result_Msg >( to ); } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "t;t;t;", log ); } template< typename Mhood > static void on_res( std::string & log, Mhood && /*cmd*/ ) { log += "t"; } }; struct transform_to_singal_with_optional_t { [[nodiscard]] static std::string_view name() { return { "transform_to_singal_with_optional" }; } template< typename Result_Msg, typename Binding > static void tune_binding( Binding & binding, const so_5::mbox_t & from, const so_5::mbox_t & to ) { using ret_val_t = std::optional< so_5::transformed_message_t< Result_Msg > >; so_5::bind_transformer< msg_signal >( binding, from, [to, counter=int{0}]() mutable -> ret_val_t { const auto c = counter++; if( 1 == c ) return std::nullopt; return { so_5::make_transformed< Result_Msg >( to ) }; } ); } static void check_result( std::string_view log ) { ensure_valid_or_die( name(), "t;t;", log ); } template< typename Mhood > static void on_res( std::string & log, Mhood && /*cmd*/ ) { log += "t"; } }; template< typename Result_Msg > void run_test_case_for_msg_pair() { if constexpr( !std::is_same_v< msg_res_3, Result_Msg > ) { run_test_case< so_5::single_sink_binding_t, Result_Msg, explicit_type_no_optional_t >(); run_test_case< so_5::single_sink_binding_t, Result_Msg, explicit_type_no_optional2_t >(); run_test_case< so_5::multi_sink_binding_t<>, Result_Msg, explicit_type_no_optional_t >(); run_test_case< so_5::single_sink_binding_t, Result_Msg, explicit_type_with_optional_t >(); run_test_case< so_5::multi_sink_binding_t<>, Result_Msg, explicit_type_with_optional_t >(); } else { run_test_case< so_5::single_sink_binding_t, Result_Msg, transform_to_singal_no_optional_t >(); run_test_case< so_5::multi_sink_binding_t<>, Result_Msg, transform_to_singal_no_optional_t >(); run_test_case< so_5::single_sink_binding_t, Result_Msg, transform_to_singal_with_optional_t >(); run_test_case< so_5::multi_sink_binding_t<>, Result_Msg, transform_to_singal_with_optional_t >(); } } void run_tests() { // msg_res_1 // run_test_case_for_msg_pair< msg_res_1 >(); run_test_case_for_msg_pair< so_5::immutable_msg >(); run_test_case_for_msg_pair< so_5::mutable_msg >(); // msg_res_2 // run_test_case_for_msg_pair< msg_res_2 >(); run_test_case_for_msg_pair< so_5::immutable_msg >(); run_test_case_for_msg_pair< so_5::mutable_msg >(); // msg_res_3 // run_test_case_for_msg_pair< msg_res_3 >(); } } /* namespace test */ int main() { test::run_tests(); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_mutable/000077500000000000000000000000001523065210100257445ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_mutable/CMakeLists.txt000066400000000000000000000001601523065210100305010ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.multi_sink_mutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_mutable/main.cpp000066400000000000000000000043541523065210100274020ustar00rootroot00000000000000/* * A simple case for multi_sink_binding_t and mutable msg. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx, so_5::mbox_t dest ) : so_5::agent_t{ std::move(ctx) } , m_dest{ dest } {} void so_evt_start() override { so_5::send< so_5::mutable_msg >( m_dest, 1 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { state_t st_wait_data{ this, "wait_data" }; state_t st_wait_signal{ this, "wait_signal" }; public: using so_5::agent_t::agent_t; void so_define_agent() override { st_wait_data.activate(); st_wait_data .event( &a_consumer_t::evt_data ) ; st_wait_signal .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mutable_mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; st_wait_signal.activate(); } void evt_signal( mhood_t ) { so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto data_mbox = so_5::make_unique_subscribers_mbox( coop.environment() ); coop.make_agent< a_producer_t >( data_mbox ); auto * consumer = coop.make_agent< a_consumer_t >(); auto consumer_msink = so_5::wrap_to_msink( consumer->so_direct_mbox() ); auto * binding = coop.take_under_control( std::make_unique< so_5::multi_sink_binding_t<> >() ); binding->bind< so_5::mutable_msg >( data_mbox, consumer_msink ); binding->bind< msg_signal >( data_mbox, consumer_msink ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_mutable_2/000077500000000000000000000000001523065210100261655ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_mutable_2/CMakeLists.txt000066400000000000000000000001621523065210100307240ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.multi_sink_mutable_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_mutable_2/main.cpp000066400000000000000000000057371523065210100276310ustar00rootroot00000000000000/* * A check that only one bind can be made for a mutable message. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx, so_5::mbox_t dest ) : so_5::agent_t{ std::move(ctx) } , m_dest{ dest } {} void so_evt_start() override { so_5::send< so_5::mutable_msg >( m_dest, 1 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { state_t st_wait_data{ this, "wait_data" }; state_t st_wait_signal{ this, "wait_signal" }; public: using so_5::agent_t::agent_t; void so_define_agent() override { st_wait_data.activate(); st_wait_data .event( &a_consumer_t::evt_data ) ; st_wait_signal .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mutable_mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; st_wait_signal.activate(); } void evt_signal( mhood_t ) { so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto data_mbox = so_5::make_unique_subscribers_mbox( coop.environment() ); coop.make_agent< a_producer_t >( data_mbox ); auto * consumer_1 = coop.make_agent< a_consumer_t >(); auto consumer_1_msink = so_5::wrap_to_msink( consumer_1->so_direct_mbox() ); auto * consumer_2 = coop.make_agent< a_consumer_t >(); auto consumer_2_msink = so_5::wrap_to_msink( consumer_2->so_direct_mbox() ); auto * binding = coop.take_under_control( std::make_unique< so_5::multi_sink_binding_t<> >() ); binding->bind< so_5::mutable_msg >( data_mbox, consumer_1_msink ); bool error_happens = false; try { binding->bind< so_5::mutable_msg >( data_mbox, consumer_2_msink ); } catch( const so_5::exception_t & x ) { error_happens = ( so_5::rc_evt_handler_already_provided == x.error_code() ); if( error_happens ) std::cout << "*** expected exception caught: " << x.what() << std::endl; else std::cout << "*** UNEXPECTED EXCEPTION CAUGHT: " << x.what() << std::endl; } ensure_or_die( error_happens, "the second bind should throw an exception" ); binding->bind< msg_signal >( data_mbox, consumer_1_msink ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_mutable_with_dr/000077500000000000000000000000001523065210100274645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_mutable_with_dr/CMakeLists.txt000066400000000000000000000001701523065210100322220ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.multi_sink_mutable_with_dr) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_mutable_with_dr/main.cpp000066400000000000000000000053721523065210100311230ustar00rootroot00000000000000/* * A simple case for multi_sink_binding_t and mutable msg and delivery filter. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx, so_5::mbox_t dest ) : so_5::agent_t{ std::move(ctx) } , m_dest{ dest } {} void so_evt_start() override { so_5::send< so_5::mutable_msg >( m_dest, -1 ); so_5::send< so_5::mutable_msg >( m_dest, 0 ); so_5::send< so_5::mutable_msg >( m_dest, 1 ); so_5::send< so_5::mutable_msg >( m_dest, 10 ); so_5::send< so_5::mutable_msg >( m_dest, 11 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { unsigned int m_messages_received{}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_default_state() .event( &a_consumer_t::evt_data ) .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mutable_mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; ensure_or_die( 0 <= cmd->m_v && cmd->m_v <= 10, "value in msg_data is out of range: " + std::to_string( cmd->m_v ) ); m_messages_received += 1u; } void evt_signal( mhood_t ) { ensure_or_die( 3 == m_messages_received, "unexpected value of m_messages_received: " + std::to_string( m_messages_received ) ); so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto data_mbox = so_5::make_unique_subscribers_mbox( coop.environment() ); coop.make_agent< a_producer_t >( data_mbox ); auto * consumer = coop.make_agent< a_consumer_t >(); auto consumer_msink = so_5::wrap_to_msink( consumer->so_direct_mbox() ); auto * binding = coop.take_under_control( std::make_unique< so_5::multi_sink_binding_t<> >() ); binding->bind< so_5::mutable_msg >( data_mbox, consumer_msink, []( const msg_data & msg ) { return 0 <= msg.m_v && msg.m_v <= 10; } ); binding->bind< msg_signal >( data_mbox, consumer_msink ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_simple/000077500000000000000000000000001523065210100256045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_simple/CMakeLists.txt000066400000000000000000000001571523065210100303470ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.multi_sink_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_simple/main.cpp000066400000000000000000000046561523065210100272470ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_data_dest; const so_5::mbox_t m_signal_dest; public: a_producer_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_data_dest{ so_environment().create_mbox() } , m_signal_dest{ so_environment().create_mbox() } {} [[nodiscard]] so_5::mbox_t data_dest() const { return m_data_dest; } [[nodiscard]] so_5::mbox_t signal_dest() const { return m_signal_dest; } void so_evt_start() override { so_5::send< msg_data >( m_data_dest, 1 ); so_5::send< msg_signal >( m_signal_dest ); } }; class a_consumer_t final : public so_5::agent_t { state_t st_wait_data{ this, "wait_data" }; state_t st_wait_signal{ this, "wait_signal" }; public: using so_5::agent_t::agent_t; void so_define_agent() override { st_wait_data.activate(); st_wait_data .event( &a_consumer_t::evt_data ) ; st_wait_signal .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; st_wait_signal.activate(); } void evt_signal( mhood_t ) { so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * producer = coop.make_agent< a_producer_t >(); auto * consumer = coop.make_agent< a_consumer_t >(); auto consumer_msink = so_5::wrap_to_msink( consumer->so_direct_mbox() ); auto * binding = coop.take_under_control( std::make_unique< so_5::multi_sink_binding_t<> >() ); binding->bind< msg_data >( producer->data_dest(), consumer_msink ); binding->bind< msg_signal >( producer->signal_dest(), consumer_msink ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_simple_2/000077500000000000000000000000001523065210100260255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_simple_2/CMakeLists.txt000066400000000000000000000001611523065210100305630ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.multi_sink_simple_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_simple_2/main.cpp000066400000000000000000000071351523065210100274630ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_data_dest; const so_5::mbox_t m_signal_dest; public: a_producer_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_data_dest{ so_environment().create_mbox() } , m_signal_dest{ so_environment().create_mbox() } {} [[nodiscard]] so_5::mbox_t data_dest() const { return m_data_dest; } [[nodiscard]] so_5::mbox_t signal_dest() const { return m_signal_dest; } void so_evt_start() override { so_5::send< msg_data >( m_data_dest, 1 ); so_5::send< msg_signal >( m_signal_dest ); } }; class a_collector_t final : public so_5::agent_t { const unsigned int m_expected_acks; unsigned int m_received_acks{}; public: struct msg_ack final : public so_5::signal_t {}; a_collector_t( context_t ctx, unsigned int expected_acks ) : so_5::agent_t{ std::move(ctx) } , m_expected_acks{ expected_acks } {} void so_define_agent() override { so_subscribe_self().event( [this]( mhood_t ) { ++m_received_acks; if( m_received_acks == m_expected_acks ) so_deregister_agent_coop_normally(); } ); } }; class a_consumer_t final : public so_5::agent_t { state_t st_wait_data{ this, "wait_data" }; state_t st_wait_signal{ this, "wait_signal" }; const so_5::mbox_t m_collector_mbox; public: a_consumer_t( context_t ctx, so_5::mbox_t collector_mbox ) : so_5::agent_t{ std::move(ctx) } , m_collector_mbox{ std::move(collector_mbox) } {} void so_define_agent() override { st_wait_data.activate(); st_wait_data .event( &a_consumer_t::evt_data ) ; st_wait_signal .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; st_wait_signal.activate(); } void evt_signal( mhood_t ) { so_5::send< a_collector_t::msg_ack >( m_collector_mbox ); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto collector_mbox = coop.make_agent< a_collector_t >( 2u )->so_direct_mbox(); auto * producer = coop.make_agent< a_producer_t >(); auto * consumer_1 = coop.make_agent< a_consumer_t >( collector_mbox ); auto * consumer_2 = coop.make_agent< a_consumer_t >( collector_mbox ); auto consumer_1_msink = so_5::wrap_to_msink( consumer_1->so_direct_mbox() ); auto consumer_2_msink = so_5::wrap_to_msink( consumer_2->so_direct_mbox() ); auto * binding = coop.take_under_control( std::make_unique< so_5::multi_sink_binding_t<> >() ); binding->bind< msg_data >( producer->data_dest(), consumer_1_msink ); binding->bind< msg_data >( producer->data_dest(), consumer_2_msink ); binding->bind< msg_signal >( producer->signal_dest(), consumer_1_msink ); binding->bind< msg_signal >( producer->signal_dest(), consumer_2_msink ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_unbind/000077500000000000000000000000001523065210100255725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_unbind/CMakeLists.txt000066400000000000000000000001571523065210100303350ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.multi_sink_unbind) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_unbind/main.cpp000066400000000000000000000063431523065210100272300ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_i; int m_v; msg_data( int i, int v ) : m_i{ i }, m_v{ v } {} }; [[nodiscard]] std::string to_string( const msg_data & msg ) { return std::to_string( msg.m_i ) + ":" + std::to_string( msg.m_v ); } struct msg_signal final : public so_5::signal_t {}; class a_test_t final : public so_5::agent_t { so_5::multi_sink_binding_t<> m_binding; const so_5::mbox_t m_d1; const so_5::mbox_t m_d2; const so_5::mbox_t m_d3; const so_5::mbox_t m_signal_dest; std::string & m_protocol; public: a_test_t( context_t ctx, std::string & protocol ) : so_5::agent_t{ std::move(ctx) } , m_d1{ so_environment().create_mbox() } , m_d2{ so_environment().create_mbox() } , m_d3{ so_environment().create_mbox() } , m_signal_dest{ so_environment().create_mbox() } , m_protocol{ protocol } {} void so_define_agent() override { so_subscribe_self() .event( &a_test_t::evt_data ) .event( &a_test_t::evt_signal ) ; } void so_evt_start() override { auto self_msink = so_5::wrap_to_msink( so_direct_mbox() ); m_binding.bind< msg_signal >( m_signal_dest, self_msink ); m_binding.bind< msg_data >( m_d1, self_msink ); m_binding.bind< msg_data >( m_d2, self_msink ); m_binding.bind< msg_data >( m_d3, self_msink ); so_5::send< msg_data >( m_d1, 1, 0 ); so_5::send< msg_data >( m_d2, 2, 0 ); so_5::send< msg_data >( m_d3, 3, 0 ); m_binding.unbind< msg_data >( m_d2, self_msink ); so_5::send< msg_data >( m_d1, 1, 1 ); so_5::send< msg_data >( m_d2, 2, 1 ); so_5::send< msg_data >( m_d3, 3, 1 ); m_binding.unbind_all_for( m_d3, self_msink ); so_5::send< msg_data >( m_d1, 1, 2 ); so_5::send< msg_data >( m_d2, 2, 2 ); so_5::send< msg_data >( m_d3, 3, 2 ); m_binding.bind< msg_data >( m_d2, self_msink ); m_binding.unbind< msg_data >( m_d1, self_msink ); so_5::send< msg_data >( m_d1, 1, 3 ); so_5::send< msg_data >( m_d2, 2, 3 ); so_5::send< msg_data >( m_d3, 3, 3 ); so_5::send< msg_signal >( m_signal_dest ); } private: void evt_data( mhood_t cmd ) { m_protocol += to_string( *cmd ) + ";"; } void evt_signal( mhood_t ) { so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env, std::string & protocol ) { env.introduce_coop( [&protocol]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( protocol ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { std::string protocol; so_5::launch( [&protocol]( so_5::environment_t & env ) { test::introduce_test_coop( env, protocol ); } ); const std::string expected{ "1:0;2:0;3:0;" "1:1;3:1;" "1:2;" "2:3;" }; ensure_or_die( expected == protocol, std::string( "invalid result protocol: '" ) + protocol + "', expected: '" + expected + "'" ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_with_delivery_filter/000077500000000000000000000000001523065210100305365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_with_delivery_filter/CMakeLists.txt000066400000000000000000000001751523065210100333010ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.multi_sink_with_delivery_filter) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_with_delivery_filter/main.cpp000066400000000000000000000047061523065210100321750ustar00rootroot00000000000000/* * A simple case for multi_sink_binding_t and delivery_filter. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_dest{ so_environment().create_mbox() } {} [[nodiscard]] so_5::mbox_t dest() const { return m_dest; } void so_evt_start() override { so_5::send< msg_data >( m_dest, 1 ); so_5::send< msg_data >( m_dest, 10 ); so_5::send< msg_data >( m_dest, 9 ); so_5::send< msg_data >( m_dest, 11 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { int m_messages_received{}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_consumer_t::evt_data ) .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; ++m_messages_received; } void evt_signal( mhood_t ) { ensure_or_die( 2 == m_messages_received, "unexpected number of messages_received: " + std::to_string( m_messages_received ) ); so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * producer = coop.make_agent< a_producer_t >(); auto * consumer = coop.make_agent< a_consumer_t >(); auto consumer_sink = so_5::wrap_to_msink( consumer->so_direct_mbox() ); auto * binding = coop.take_under_control( std::make_unique< so_5::multi_sink_binding_t<> >() ); binding->bind< msg_data >( producer->dest(), consumer_sink, []( const msg_data & msg ) { return msg.m_v < 10; } ); binding->bind< msg_signal >( producer->dest(), consumer_sink ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_with_delivery_filter_2/000077500000000000000000000000001523065210100307575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_with_delivery_filter_2/CMakeLists.txt000066400000000000000000000001771523065210100335240ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.multi_sink_with_delivery_filter_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/multi_sink_with_delivery_filter_2/main.cpp000066400000000000000000000050441523065210100324120ustar00rootroot00000000000000/* * A simple case for multi_sink_binding_t and delivery_filter. * The delivery_filter will be specified by a lambda that receives * a `const auto &` reference. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_dest{ so_environment().create_mbox() } {} [[nodiscard]] so_5::mbox_t dest() const { return m_dest; } void so_evt_start() override { so_5::send< msg_data >( m_dest, 1 ); so_5::send< msg_data >( m_dest, 10 ); so_5::send< msg_data >( m_dest, 9 ); so_5::send< msg_data >( m_dest, 11 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { int m_messages_received{}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_consumer_t::evt_data ) .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; ++m_messages_received; } void evt_signal( mhood_t ) { ensure_or_die( 2 == m_messages_received, "unexpected number of messages_received: " + std::to_string( m_messages_received ) ); so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * producer = coop.make_agent< a_producer_t >(); auto * consumer = coop.make_agent< a_consumer_t >(); auto consumer_sink = so_5::wrap_to_msink( consumer->so_direct_mbox() ); auto * binding = coop.take_under_control( std::make_unique< so_5::multi_sink_binding_t<> >() ); binding->bind< msg_data >( producer->dest(), consumer_sink, []( const auto & msg ) { return msg.m_v < 10; } ); binding->bind< msg_signal >( producer->dest(), consumer_sink ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple/000077500000000000000000000000001523065210100233465ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple/CMakeLists.txt000066400000000000000000000001441523065210100261050ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple/main.cpp000066400000000000000000000042521523065210100250010ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_dest{ so_environment().create_mbox() } {} [[nodiscard]] so_5::mbox_t dest() const { return m_dest; } void so_evt_start() override { so_5::send< msg_data >( m_dest, 1 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_consumer_t::evt_data ) .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; } void evt_signal( mhood_t ) { so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * producer = coop.make_agent< a_producer_t >(); auto * consumer = coop.make_agent< a_consumer_t >(); auto * msg_data_binding = coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() ); msg_data_binding->bind< msg_data >( producer->dest(), so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); auto * msg_signal_binding = coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() ); msg_signal_binding->bind< msg_signal >( producer->dest(), so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple_2/000077500000000000000000000000001523065210100235675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple_2/CMakeLists.txt000066400000000000000000000001461523065210100263300ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.simple_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple_2/main.cpp000066400000000000000000000044071523065210100252240ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_dest{ so_environment().create_mbox() } {} [[nodiscard]] so_5::mbox_t dest() const { return m_dest; } void so_evt_start() override { so_5::send< msg_data >( m_dest, 1 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_consumer_t::evt_data ) .event( &a_consumer_t::evt_signal ) ; } private: bool m_msg_data_received{ false }; void evt_data( mhood_t cmd ) { std::cout << "\n\n\n\n" << std::flush << "data: " << cmd->m_v << std::endl; m_msg_data_received = true; } void evt_signal( mhood_t ) { ensure_or_die( !m_msg_data_received, "msg_data should not be received before msg_signal" ); so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * producer = coop.make_agent< a_producer_t >(); auto * consumer = coop.make_agent< a_consumer_t >(); auto * binding = coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() ); binding->bind< msg_data >( producer->dest(), so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); // Should drop the first binding. binding->bind< msg_signal >( producer->dest(), so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple_with_delivery_filter/000077500000000000000000000000001523065210100276515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple_with_delivery_filter/CMakeLists.txt000066400000000000000000000001711523065210100324100ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.simple_with_delivery_filter) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple_with_delivery_filter/main.cpp000066400000000000000000000047721523065210100313130ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t and delivery_filter. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_dest{ so_environment().create_mbox() } {} [[nodiscard]] so_5::mbox_t dest() const { return m_dest; } void so_evt_start() override { so_5::send< msg_data >( m_dest, 1 ); so_5::send< msg_data >( m_dest, 10 ); so_5::send< msg_data >( m_dest, 9 ); so_5::send< msg_data >( m_dest, 11 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { int m_messages_received{}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_consumer_t::evt_data ) .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; ++m_messages_received; } void evt_signal( mhood_t ) { ensure_or_die( 2 == m_messages_received, "unexpected number of messages_received: " + std::to_string( m_messages_received ) ); so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * producer = coop.make_agent< a_producer_t >(); auto * consumer = coop.make_agent< a_consumer_t >(); coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() )-> bind< msg_data >( producer->dest(), so_5::wrap_to_msink( consumer->so_direct_mbox() ), []( const msg_data & msg ) { return msg.m_v < 10; } ); coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() )-> bind< msg_signal >( producer->dest(), so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple_with_delivery_filter_2/000077500000000000000000000000001523065210100300725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple_with_delivery_filter_2/CMakeLists.txt000066400000000000000000000001731523065210100326330ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.simple_with_delivery_filter_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/simple_with_delivery_filter_2/main.cpp000066400000000000000000000051301523065210100315210ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t and delivery_filter. * The delivery_filter will be specified by a lambda that receives * a `const auto &` reference. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_dest{ so_environment().create_mbox() } {} [[nodiscard]] so_5::mbox_t dest() const { return m_dest; } void so_evt_start() override { so_5::send< msg_data >( m_dest, 1 ); so_5::send< msg_data >( m_dest, 10 ); so_5::send< msg_data >( m_dest, 9 ); so_5::send< msg_data >( m_dest, 11 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { int m_messages_received{}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self() .event( &a_consumer_t::evt_data ) .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; ++m_messages_received; } void evt_signal( mhood_t ) { ensure_or_die( 2 == m_messages_received, "unexpected number of messages_received: " + std::to_string( m_messages_received ) ); so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto * producer = coop.make_agent< a_producer_t >(); auto * consumer = coop.make_agent< a_consumer_t >(); coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() )-> bind< msg_data >( producer->dest(), so_5::wrap_to_msink( consumer->so_direct_mbox() ), []( const auto & msg ) { return msg.m_v < 10; } ); coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() )-> bind< msg_signal >( producer->dest(), so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_clear/000077500000000000000000000000001523065210100255305ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_clear/CMakeLists.txt000066400000000000000000000001571523065210100302730ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.single_sink_clear) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_clear/main.cpp000066400000000000000000000050771523065210100271710ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t::clear. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_stop final : public so_5::signal_t {}; class a_test_t final : public so_5::agent_t { int m_messages_received{}; public: a_test_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe_self() .event( &a_test_t::evt_data ) .event( &a_test_t::evt_stop ) ; } void so_evt_start() override { auto dest = so_environment().create_mbox(); so_5::single_sink_binding_t msg_data_binding; so_5::single_sink_binding_t msg_stop_binding; ensure_or_die( msg_data_binding.empty(), "(1) msg_data_binding has to be empty" ); ensure_or_die( msg_stop_binding.empty(), "(2) msg_data_binding has to be empty" ); msg_data_binding.bind< msg_data >( dest, so_5::wrap_to_msink( so_direct_mbox() ) ); ensure_or_die( !msg_data_binding.empty(), "(3) msg_data_binding should have a value" ); ensure_or_die( msg_data_binding.has_value(), "(4) msg_data_binding should have a value" ); msg_stop_binding.bind< msg_stop >( dest, so_5::wrap_to_msink( so_direct_mbox() ) ); ensure_or_die( !msg_stop_binding.empty(), "(5) msg_stop_binding should have a value" ); ensure_or_die( msg_stop_binding.has_value(), "(6) msg_stop_binding should have a value" ); so_5::send< msg_data >( dest, 1 ); msg_data_binding.clear(); ensure_or_die( msg_data_binding.empty(), "(7) msg_data_binding has to be empty" ); so_5::send< msg_data >( dest, 2 ); so_5::send< msg_stop >( dest ); } void evt_data( mhood_t< msg_data > ) { ++m_messages_received; } void evt_stop( mhood_t< msg_stop > ) { ensure_or_die( 1 == m_messages_received, "unexpected value of m_messages_received: " + std::to_string( m_messages_received ) ); so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_mutable/000077500000000000000000000000001523065210100260735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_mutable/CMakeLists.txt000066400000000000000000000001611523065210100306310ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.single_sink_mutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_mutable/main.cpp000066400000000000000000000046121523065210100275260ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t and mutable msg. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx, so_5::mbox_t dest ) : so_5::agent_t{ std::move(ctx) } , m_dest{ dest } {} void so_evt_start() override { so_5::send< so_5::mutable_msg >( m_dest, 1 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { state_t st_wait_data{ this, "wait_data" }; state_t st_wait_signal{ this, "wait_signal" }; public: using so_5::agent_t::agent_t; void so_define_agent() override { st_wait_data.activate(); st_wait_data .event( &a_consumer_t::evt_data ) ; st_wait_signal .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mutable_mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; st_wait_signal.activate(); } void evt_signal( mhood_t ) { so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto data_mbox = so_5::make_unique_subscribers_mbox( coop.environment() ); coop.make_agent< a_producer_t >( data_mbox ); auto * consumer = coop.make_agent< a_consumer_t >(); auto * msg_data_binding = coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() ); msg_data_binding->bind< so_5::mutable_msg >( data_mbox, so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); auto * msg_signal_binding = coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() ); msg_signal_binding->bind< msg_signal >( data_mbox, so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_mutable_with_dr/000077500000000000000000000000001523065210100276135ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_mutable_with_dr/CMakeLists.txt000066400000000000000000000001711523065210100323520ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.single_sink_mutable_with_dr) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_mutable_with_dr/main.cpp000066400000000000000000000056171523065210100312540ustar00rootroot00000000000000/* * A simple case for single_sink_binding_t and mutable msg and delivery filter. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_signal final : public so_5::signal_t {}; class a_producer_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_producer_t( context_t ctx, so_5::mbox_t dest ) : so_5::agent_t{ std::move(ctx) } , m_dest{ dest } {} void so_evt_start() override { so_5::send< so_5::mutable_msg >( m_dest, -1 ); so_5::send< so_5::mutable_msg >( m_dest, 0 ); so_5::send< so_5::mutable_msg >( m_dest, 1 ); so_5::send< so_5::mutable_msg >( m_dest, 10 ); so_5::send< so_5::mutable_msg >( m_dest, 11 ); so_5::send< msg_signal >( m_dest ); } }; class a_consumer_t final : public so_5::agent_t { unsigned int m_messages_received{}; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_default_state() .event( &a_consumer_t::evt_data ) .event( &a_consumer_t::evt_signal ) ; } private: void evt_data( mutable_mhood_t cmd ) { std::cout << "data: " << cmd->m_v << std::endl; ensure_or_die( 0 <= cmd->m_v && cmd->m_v <= 10, "value in msg_data is out of range: " + std::to_string( cmd->m_v ) ); m_messages_received += 1u; } void evt_signal( mhood_t ) { ensure_or_die( 3 == m_messages_received, "unexpected value of m_messages_received: " + std::to_string( m_messages_received ) ); so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto data_mbox = so_5::make_unique_subscribers_mbox( coop.environment() ); coop.make_agent< a_producer_t >( data_mbox ); auto * consumer = coop.make_agent< a_consumer_t >(); auto * msg_data_binding = coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() ); msg_data_binding->bind< so_5::mutable_msg >( data_mbox, so_5::wrap_to_msink( consumer->so_direct_mbox() ), []( const msg_data & msg ) { return 0 <= msg.m_v && msg.m_v <= 10; } ); auto * msg_signal_binding = coop.take_under_control( std::make_unique< so_5::single_sink_binding_t >() ); msg_signal_binding->bind< msg_signal >( data_mbox, so_5::wrap_to_msink( consumer->so_direct_mbox() ) ); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_too_deep/000077500000000000000000000000001523065210100262405ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_too_deep/CMakeLists.txt000066400000000000000000000001621523065210100307770ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.single_sink_too_deep) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/single_sink_too_deep/main.cpp000066400000000000000000000033241523065210100276720ustar00rootroot00000000000000/* * A simple test for circular message subscription. */ #include #include #include #include #include namespace test { struct msg_data final : public so_5::message_t { int m_v; msg_data( int v ) : m_v{ v } {} }; struct msg_stop final : public so_5::signal_t {}; class a_test_t final : public so_5::agent_t { public: a_test_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe_self() .event( &a_test_t::evt_stop ) ; } void so_evt_start() override { auto m1 = so_environment().create_mbox(); auto m2 = so_environment().create_mbox(); so_5::single_sink_binding_t m1_binding; so_5::single_sink_binding_t m2_binding; m1_binding.bind< msg_data >( m1, so_5::wrap_to_msink( m2 ) ); m2_binding.bind< msg_data >( m2, so_5::wrap_to_msink( m1 ) ); // This message has to be ignored. so_5::send< msg_data >( m1, 2 ); so_5::send< msg_stop >( *this ); } void evt_stop( mhood_t< msg_stop > ) { so_deregister_agent_coop_normally(); } }; void introduce_test_coop( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::introduce_test_coop( env ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/transformer_and_envelope/000077500000000000000000000000001523065210100271365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/transformer_and_envelope/CMakeLists.txt000066400000000000000000000001661523065210100317010ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.transformer_and_envelope) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/transformer_and_envelope/main.cpp000066400000000000000000000066701523065210100305770ustar00rootroot00000000000000/* * Test for checking redirection deep for bind_transformer. */ #include #include #include #include #include namespace test { class test_envelope_t final : public so_5::enveloped_msg::envelope_t { std::mutex m_lock; so_5::outliving_reference_t< std::string > m_receiver; const std::string m_id; so_5::message_ref_t m_payload; void append_text( const char * what ) { std::lock_guard< std::mutex > lock{ m_lock }; m_receiver.get() += m_id; m_receiver.get() += ":"; m_receiver.get() += what; } public: test_envelope_t( so_5::outliving_reference_t< std::string > receiver, std::string id, so_5::message_ref_t payload ) : m_receiver{ receiver } , m_id( std::move(id) ) , m_payload{ std::move(payload) } {} void access_hook( access_context_t context, handler_invoker_t & invoker ) noexcept override { switch( context ) { case access_context_t::handler_found: append_text( "pre_invoke;" ); invoker.invoke( so_5::enveloped_msg::payload_info_t{ m_payload } ); append_text( "post_invoke;" ); break; case access_context_t::transformation: append_text( "transform;" ); invoker.invoke( payload_info_t{ m_payload } ); break; case access_context_t::inspection: append_text( "inspect;" ); invoker.invoke( payload_info_t{ m_payload } ); break; } } }; template< typename Msg, typename... Args > void post_enveloped( std::string & receiver, std::string id, const so_5::mbox_t & mbox, Args && ...args ) { so_5::message_ref_t msg{ std::make_unique( std::forward(args)... ) }; so_5::message_ref_t enveloped{ std::make_unique( so_5::outliving_mutable(receiver), std::move(id), std::move(msg) ) }; mbox->do_deliver_message( so_5::message_delivery_mode_t::ordinary, so_5::message_payload_type::subscription_type_index(), std::move(enveloped), 1 ); } struct msg_source final : public so_5::message_t { std::string m_v; explicit msg_source( std::string v ) : m_v{ std::move(v) } {} }; struct msg_result final : public so_5::message_t { std::string m_v; explicit msg_result( std::string v ) : m_v{ std::move(v) } {} }; void do_test( so_5::environment_t & env ) { auto src = env.create_mbox(); auto dest = so_5::create_mchain( env ); so_5::single_sink_binding_t binding; so_5::bind_transformer< msg_source >( binding, src, [dest]( const auto & msg ) { return so_5::make_transformed< msg_result >( dest->as_mbox(), "<" + msg.m_v + ">" ); } ); std::string log; post_enveloped< msg_source >( log, "1", src, "a" ); so_5::receive( so_5::from( dest ).handle_n( 1 ), [&log]( const msg_result & msg ) { log += msg.m_v; log += ";"; } ); const std::string expected{ "1:transform;;" }; ensure_or_die( log == expected, "log: '" + log + "', expected: '" + expected + "'" ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::do_test( env ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/transformer_redirect_deep/000077500000000000000000000000001523065210100272755ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/transformer_redirect_deep/CMakeLists.txt000066400000000000000000000001671523065210100320410ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.sink_binding.transformer_redirect_deep) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/sink_binding/transformer_redirect_deep/main.cpp000066400000000000000000000016371523065210100307340ustar00rootroot00000000000000/* * Test for checking redirection deep for bind_transformer. */ #include #include #include #include #include namespace test { struct msg_signal final : public so_5::signal_t {}; void do_test( so_5::environment_t & env ) { auto dest = env.create_mbox(); so_5::single_sink_binding_t binding; so_5::bind_transformer< msg_signal >( binding, dest, [dest]() { return so_5::make_transformed< msg_signal >( dest ); } ); // This call should lead to infinite recursion if // redirection deep isn't controlled. so_5::send< msg_signal >( dest ); } } /* namespace test */ int main() { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { test::do_test( env ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/subscribe_when_deregistered/000077500000000000000000000000001523065210100251475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/subscribe_when_deregistered/CMakeLists.txt000066400000000000000000000001541523065210100277070ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.subscribe_when_deregistered) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/subscribe_when_deregistered/main.cpp000066400000000000000000000076571523065210100266160ustar00rootroot00000000000000/* * A test for subscription inside so_evt_finish() where they * should have no effects. */ #include #include #include #include #include #include struct msg1 : public so_5::message_t {}; struct msg2 : public so_5::message_t {}; struct msg3 : public so_5::message_t {}; struct msg4 : public so_5::message_t {}; struct msg5 : public so_5::message_t {}; class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_t( so_5::environment_t & env ) : base_type_t( env ), m_mbox( so_environment().create_mbox() ) {} void so_define_agent() override; void so_evt_finish() override; #define ABORT_HANDLER( handler, msg ) \ void\ handler ( mhood_t< msg > ) \ {\ std::cerr << "Error: " #msg " handler called..." << std::endl; \ std::abort(); \ } ABORT_HANDLER( handler1, msg1 ) ABORT_HANDLER( handler2, msg2 ) ABORT_HANDLER( handler3, msg3 ) ABORT_HANDLER( handler4, msg4 ) ABORT_HANDLER( handler5, msg5 ) private: // Mbox for subscription. so_5::mbox_t m_mbox; }; void test_agent_t::so_define_agent() { } void test_agent_t::so_evt_finish() { so_subscribe( m_mbox ) .event( &test_agent_t::handler1 ); so_subscribe( m_mbox ) .event( &test_agent_t::handler2 ); so_subscribe( m_mbox ) .event( &test_agent_t::handler3 ); so_subscribe( m_mbox ) .event( &test_agent_t::handler4 ); so_subscribe( m_mbox ) .event( &test_agent_t::handler5 ); } class stage_monitors_t { private : std::mutex m_reg_mutex; std::condition_variable m_reg_signal; std::mutex m_dereg_mutex; std::condition_variable m_dereg_signal; enum stage_t { NOT_STARTED, COOP_REGISTERED, COOP_DEREGISTERED }; stage_t m_stage; public : stage_monitors_t() : m_stage( NOT_STARTED ) {} void wait_for_registration() { std::unique_lock< std::mutex > lock( m_reg_mutex ); if( COOP_REGISTERED != m_stage ) m_reg_signal.wait( lock ); } void notify_about_registration() { std::unique_lock< std::mutex > lock( m_reg_mutex ); m_stage = COOP_REGISTERED; m_reg_signal.notify_one(); } void wait_for_deregistration() { std::unique_lock< std::mutex > lock( m_dereg_mutex ); if( COOP_DEREGISTERED != m_stage ) m_dereg_signal.wait( lock ); } void notify_about_deregistration() { std::unique_lock< std::mutex > lock( m_dereg_mutex ); m_stage = COOP_DEREGISTERED; m_dereg_signal.notify_one(); } }; stage_monitors_t g_stage_monitors; void init( so_5::environment_t & env ) { for( int i = 0; i < 8; ++i ) { auto coop = env.make_coop( so_5::disp::active_obj::make_dispatcher( env ).binder() ); coop->make_agent< test_agent_t >(); coop->make_agent< test_agent_t >(); coop->make_agent< test_agent_t >(); coop->make_agent< test_agent_t >(); coop->make_agent< test_agent_t >(); coop->make_agent< test_agent_t >(); coop->make_agent< test_agent_t >(); coop->make_agent< test_agent_t >(); auto coop_handle = env.register_coop( std::move( coop ) ); g_stage_monitors.wait_for_registration(); env.deregister_coop( coop_handle, so_5::dereg_reason::normal ); g_stage_monitors.wait_for_deregistration(); } env.stop(); } class listener_t : public so_5::coop_listener_t { public : void on_registered( so_5::environment_t &, const so_5::coop_handle_t & ) noexcept override { g_stage_monitors.notify_about_registration(); } void on_deregistered( so_5::environment_t &, const so_5::coop_handle_t &, const so_5::coop_dereg_reason_t &) noexcept override { g_stage_monitors.notify_about_deregistration(); } }; int main() { try { so_5::launch( &init, []( so_5::environment_params_t & params ) { params.coop_listener( so_5::coop_listener_unique_ptr_t( new listener_t() ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/000077500000000000000000000000001523065210100233335ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/CMakeLists.txt000066400000000000000000000002051523065210100260700ustar00rootroot00000000000000add_subdirectory(delivery_filter) add_subdirectory(repeated_subscribe) add_subdirectory(simple) add_subdirectory(simple_null_mutex) sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/delivery_filter/000077500000000000000000000000001523065210100265235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/delivery_filter/CMakeLists.txt000066400000000000000000000001701523065210100312610ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.unique_subscribers_mbox.delivery_filter) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/delivery_filter/main.cpp000066400000000000000000000123411523065210100301540ustar00rootroot00000000000000#include #include #include #include #include struct msg_value_a final : public so_5::message_t { int m_a; msg_value_a( int a ) : m_a{ a } {} }; struct msg_value_b final : public so_5::message_t { int m_b; msg_value_b( int b ) : m_b{ b } {} }; class first final : public so_5::agent_t { const so_5::mbox_t m_test_mbox; std::string m_protocol; public: first( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void so_define_agent() override { so_set_delivery_filter( m_test_mbox, []( const msg_value_a & msg ) { return (msg.m_a > 0) && (msg.m_a < 10); } ); so_subscribe( m_test_mbox ) .event( [this]( mhood_t cmd ) { m_protocol += std::to_string( cmd->m_a ) + ";"; } ) ; } void so_evt_start() override { so_5::send< msg_value_a >( m_test_mbox, -1 ); so_5::send< msg_value_a >( m_test_mbox, 0 ); so_5::send< msg_value_a >( m_test_mbox, 1 ); so_5::send< msg_value_a >( m_test_mbox, 2 ); so_5::send< msg_value_a >( m_test_mbox, 9 ); so_5::send< msg_value_a >( m_test_mbox, 10 ); so_5::send< msg_value_a >( m_test_mbox, 11 ); so_deregister_agent_coop_normally(); } void so_evt_finish() override { const std::string expected_protocol = "1;2;9;"; ensure_or_die( expected_protocol == m_protocol, "unexpected protocol for 'first', expected='" + expected_protocol + "', actual='" + m_protocol + "'" ); } }; class second final : public so_5::agent_t { const so_5::mbox_t m_test_mbox; std::string m_protocol; public: second( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void so_define_agent() override { so_set_delivery_filter_for_mutable_msg( m_test_mbox, []( const msg_value_a & msg ) { return (msg.m_a > 0) && (msg.m_a < 10); } ); so_subscribe( m_test_mbox ) .event( [this]( mutable_mhood_t cmd ) { m_protocol += std::to_string( cmd->m_a ) + ";"; } ) ; } void so_evt_start() override { so_5::send< so_5::mutable_msg >( m_test_mbox, -1 ); so_5::send< so_5::mutable_msg >( m_test_mbox, 0 ); so_5::send< so_5::mutable_msg >( m_test_mbox, 1 ); so_5::send< so_5::mutable_msg >( m_test_mbox, 2 ); so_5::send< so_5::mutable_msg >( m_test_mbox, 9 ); so_5::send< so_5::mutable_msg >( m_test_mbox, 10 ); so_5::send< so_5::mutable_msg >( m_test_mbox, 11 ); so_drop_delivery_filter< so_5::mutable_msg >( m_test_mbox ); so_5::send< so_5::mutable_msg >( m_test_mbox, -5 ); so_deregister_agent_coop_normally(); } void so_evt_finish() override { const std::string expected_protocol = "1;2;9;-5;"; ensure_or_die( expected_protocol == m_protocol, "unexpected protocol for 'second', expected='" + expected_protocol + "', actual='" + m_protocol + "'" ); } }; class third final : public so_5::agent_t { const so_5::mbox_t m_test_mbox; std::string m_protocol; public: third( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void so_define_agent() override { so_set_delivery_filter( m_test_mbox, []( const msg_value_b & msg ) { return (msg.m_b > 0) && (msg.m_b < 10); } ); so_subscribe( m_test_mbox ) .event( [this]( mhood_t cmd ) { m_protocol += std::to_string( cmd->m_b ) + ";"; } ) ; } void so_evt_start() override { so_5::send< msg_value_b >( m_test_mbox, -1 ); so_5::send< msg_value_b >( m_test_mbox, 0 ); so_5::send< msg_value_b >( m_test_mbox, 1 ); so_5::send< msg_value_b >( m_test_mbox, 2 ); so_5::send< msg_value_b >( m_test_mbox, 9 ); so_5::send< msg_value_b >( m_test_mbox, 10 ); so_5::send< msg_value_b >( m_test_mbox, 11 ); so_drop_delivery_filter< msg_value_b >( m_test_mbox ); so_5::send< msg_value_b >( m_test_mbox, 12 ); so_deregister_agent_coop_normally(); } void so_evt_finish() override { const std::string expected_protocol = "1;2;9;12;"; ensure_or_die( expected_protocol == m_protocol, "unexpected protocol for 'third', expected='" + expected_protocol + "', actual='" + m_protocol + "'" ); } }; UT_UNIT_TEST( simple_case ) { run_with_time_limit( [] { so_5::launch( [&](so_5::environment_t & env) { auto test_mbox = so_5::make_unique_subscribers_mbox( env ); env.register_agent_as_coop( env.make_agent< first >( test_mbox ), so_5::disp::one_thread::make_dispatcher( env ).binder() ); env.register_agent_as_coop( env.make_agent< second >( test_mbox ), so_5::disp::one_thread::make_dispatcher( env ).binder() ); env.register_agent_as_coop( env.make_agent< third >( test_mbox ), so_5::disp::one_thread::make_dispatcher( env ).binder() ); }, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( simple_case ) } sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/repeated_subscribe/000077500000000000000000000000001523065210100271655ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/repeated_subscribe/CMakeLists.txt000066400000000000000000000001731523065210100317260ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.unique_subscribers_mbox.repeated_subscribe) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/repeated_subscribe/main.cpp000066400000000000000000000065131523065210100306220ustar00rootroot00000000000000#include #include #include #include struct test_message final : public so_5::message_t { std::string m_data; explicit test_message( std::string data ) : m_data{ std::move(data) } {} }; struct try_1 final : public so_5::signal_t {}; struct try_2 final : public so_5::signal_t {}; class first final : public so_5::agent_t { const so_5::mbox_t m_test_mbox; so_5::mbox_t m_second_mbox; public: first( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void set_second_mbox( so_5::mbox_t mbox ) { m_second_mbox = std::move(mbox); } void so_define_agent() override { so_subscribe_self() .event( &first::evt_try_1 ) ; } void so_evt_start() override { so_subscribe( m_test_mbox ) .event( &first::evt_test_message ) ; so_5::send< test_message >( m_test_mbox, "to first" ); } private: void evt_test_message( mhood_t< test_message > cmd ) { std::cout << "first.evt_test_message: " << cmd->m_data << std::endl; so_5::send< try_1 >( m_second_mbox ); } void evt_try_1( mhood_t< try_1 > ) { so_drop_subscription_for_all_states( m_test_mbox, &first::evt_test_message ); so_5::send< try_2 >( m_second_mbox ); } }; class second final : public so_5::agent_t { const so_5::mbox_t m_test_mbox; so_5::mbox_t m_first_mbox; public: second( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void set_first_mbox( so_5::mbox_t mbox ) { m_first_mbox = std::move(mbox); } void so_define_agent() override { so_subscribe_self() .event( &second::evt_try_1 ) .event( &second::evt_try_2 ) ; } private: void evt_try_1( mhood_t< try_1 > ) { bool exception_caught = false; try { so_subscribe( m_test_mbox ) .event( &second::evt_test_message ) ; } catch( const so_5::exception_t & x ) { std::cout << "exception with error_code=" << x.error_code() << std::endl; exception_caught = (x.error_code() == so_5::rc_evt_handler_already_provided); } if( !exception_caught ) throw std::runtime_error{ "expected exception isn't thrown!" }; so_5::send< try_1 >( m_first_mbox ); } void evt_try_2( mhood_t< try_2 > ) { so_subscribe( m_test_mbox ) .event( &second::evt_test_message ) ; so_5::send< test_message >( m_test_mbox, "to second" ); } void evt_test_message( mhood_t< test_message > cmd ) { std::cout << "second.evt_test_message: " << cmd->m_data << std::endl; so_deregister_agent_coop_normally(); } }; UT_UNIT_TEST( simple_case ) { run_with_time_limit( [] { so_5::launch( [&](so_5::environment_t & env) { auto test_mbox = so_5::make_unique_subscribers_mbox( env ); env.introduce_coop( [&]( so_5::coop_t & coop ) { auto * f = coop.make_agent< first >( test_mbox ); auto * s = coop.make_agent< second >( test_mbox ); f->set_second_mbox( s->so_direct_mbox() ); s->set_first_mbox( f->so_direct_mbox() ); } ); }, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( simple_case ) } sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/simple/000077500000000000000000000000001523065210100246245ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/simple/CMakeLists.txt000066400000000000000000000001571523065210100273670ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.unique_subscribers_mbox.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/simple/main.cpp000066400000000000000000000056061523065210100262630ustar00rootroot00000000000000#include #include #include #include class first final : public so_5::agent_t { struct ready final : public so_5::signal_t {}; const so_5::mbox_t m_test_mbox; public: first( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void so_define_agent() override { so_subscribe( m_test_mbox ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ) ; } void so_evt_start() override { so_5::send< ready >( m_test_mbox ); } }; class second final : public so_5::agent_t { struct ready final : public so_5::message_t { std::string m_data; explicit ready( std::string data ) : m_data{ std::move(data) } {} }; const so_5::mbox_t m_test_mbox; public: second( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void so_define_agent() override { so_subscribe( m_test_mbox ) .event( [this]( mhood_t cmd ) { std::cout << "second.ready: " << cmd->m_data << std::endl; so_deregister_agent_coop_normally(); } ) ; } void so_evt_start() override { so_5::send< ready >( m_test_mbox, "Hello, Immutable World!" ); } }; class third final : public so_5::agent_t { struct ready final : public so_5::message_t { std::string m_data; explicit ready( std::string data ) : m_data{ std::move(data) } {} }; const so_5::mbox_t m_test_mbox; public: third( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void so_define_agent() override { so_subscribe( m_test_mbox ) .event( [this]( mutable_mhood_t cmd ) { std::cout << "third.ready: " << cmd->m_data << std::endl; so_deregister_agent_coop_normally(); } ) ; } void so_evt_start() override { so_5::send< so_5::mutable_msg >( m_test_mbox, "Hello, Mutable World!" ); } }; UT_UNIT_TEST( simple_case ) { run_with_time_limit( [] { so_5::launch( [&](so_5::environment_t & env) { auto test_mbox = so_5::make_unique_subscribers_mbox( env ); env.register_agent_as_coop( env.make_agent< first >( test_mbox ), so_5::disp::one_thread::make_dispatcher( env ).binder() ); env.register_agent_as_coop( env.make_agent< second >( test_mbox ), so_5::disp::one_thread::make_dispatcher( env ).binder() ); env.register_agent_as_coop( env.make_agent< third >( test_mbox ), so_5::disp::one_thread::make_dispatcher( env ).binder() ); }, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( simple_case ) } sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/simple_null_mutex/000077500000000000000000000000001523065210100271005ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/simple_null_mutex/CMakeLists.txt000066400000000000000000000001721523065210100316400ustar00rootroot00000000000000set(UNITTEST _unit.test.mbox.unique_subscribers_mbox.simple_null_mutex) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mbox/unique_subscribers/simple_null_mutex/main.cpp000066400000000000000000000055021523065210100305320ustar00rootroot00000000000000#include #include #include #include class first final : public so_5::agent_t { struct ready final : public so_5::signal_t {}; const so_5::mbox_t m_test_mbox; public: first( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void so_define_agent() override { so_subscribe( m_test_mbox ) .event( [this]( mhood_t ) { so_deregister_agent_coop_normally(); } ) ; } void so_evt_start() override { so_5::send< ready >( m_test_mbox ); } }; class second final : public so_5::agent_t { struct ready final : public so_5::message_t { std::string m_data; explicit ready( std::string data ) : m_data{ std::move(data) } {} }; const so_5::mbox_t m_test_mbox; public: second( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void so_define_agent() override { so_subscribe( m_test_mbox ) .event( [this]( mhood_t cmd ) { std::cout << "second.ready: " << cmd->m_data << std::endl; so_deregister_agent_coop_normally(); } ) ; } void so_evt_start() override { so_5::send< ready >( m_test_mbox, "Hello, Immutable World!" ); } }; class third final : public so_5::agent_t { struct ready final : public so_5::message_t { std::string m_data; explicit ready( std::string data ) : m_data{ std::move(data) } {} }; const so_5::mbox_t m_test_mbox; public: third( context_t ctx, so_5::mbox_t test_mbox ) : so_5::agent_t{ std::move(ctx) } , m_test_mbox{ std::move(test_mbox) } {} void so_define_agent() override { so_subscribe( m_test_mbox ) .event( [this]( mutable_mhood_t cmd ) { std::cout << "third.ready: " << cmd->m_data << std::endl; so_deregister_agent_coop_normally(); } ) ; } void so_evt_start() override { so_5::send< so_5::mutable_msg >( m_test_mbox, "Hello, Mutable World!" ); } }; UT_UNIT_TEST( simple_case ) { run_with_time_limit( [] { so_5::launch( [&](so_5::environment_t & env) { auto test_mbox = so_5::make_unique_subscribers_mbox< so_5::null_mutex_t >( env ); env.register_agent_as_coop( env.make_agent< first >( test_mbox ) ); env.register_agent_as_coop( env.make_agent< second >( test_mbox ) ); env.register_agent_as_coop( env.make_agent< third >( test_mbox ) ); }, [](so_5::environment_params_t & params) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory() ); params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( simple_case ) } sobjectizer-5.8.6/dev/test/so_5/mchain/000077500000000000000000000000001523065210100177115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/CMakeLists.txt000066400000000000000000000025031523065210100224510ustar00rootroot00000000000000add_subdirectory(params_clone) add_subdirectory(infinite_wait) add_subdirectory(simple) add_subdirectory(insert_after_close) add_subdirectory(func_as_handler) add_subdirectory(close_chain) add_subdirectory(timers) add_subdirectory(limited_no_app_abort) add_subdirectory(adv_receive) add_subdirectory(adv_prepared_receive) add_subdirectory(not_empty_notify) add_subdirectory(empty_notify) add_subdirectory(multithread_receive) add_subdirectory(multithread_receive_close) add_subdirectory(select_simple) add_subdirectory(prepared_select_simple) add_subdirectory(adv_prepared_select) add_subdirectory(select_simple_close) add_subdirectory(select_count_messages) add_subdirectory(select_mthread_close) add_subdirectory(select_mthread_close2) add_subdirectory(select_mthread_close3) add_subdirectory(select_mthread_read) add_subdirectory(select_send_simple) add_subdirectory(adv_select_mthread_close) add_subdirectory(adv_select_mthread_read) add_subdirectory(adv_select_mthread_empty_timeout) add_subdirectory(adv_select_mthread_stop_on) add_subdirectory(auto_close_chains) add_subdirectory(auto_close_chains_ex) add_subdirectory(master_handle) add_subdirectory(receive_closed_handler) add_subdirectory(select_closed_handler) add_subdirectory(extensible_select_simple) add_subdirectory(adv_extensible_select) add_subdirectory(limited_app_abort) sobjectizer-5.8.6/dev/test/so_5/mchain/adv_extensible_select/000077500000000000000000000000001523065210100242445ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/adv_extensible_select/CMakeLists.txt000066400000000000000000000001501523065210100270000ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.adv_extensible_select) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/adv_extensible_select/main.cpp000066400000000000000000000223071523065210100257000ustar00rootroot00000000000000/* * An advanced test for extensible select. */ #include #include #include #include "../mchain_params.hpp" using namespace std; using namespace chrono; using namespace chrono_literals; void do_check_on_empty_cases_list( const so_5::mchain_t & chain ) { so_5::send(chain, "Hello!"); auto sel = make_extensible_select( so_5::from_all().handle_all().empty_timeout( 20ms ), receive_case( chain ) ); auto r = select( sel ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 0 == r.handled() ); } UT_UNIT_TEST( test_empty_cases_list ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_on_empty_cases_list( env.environment().create_mchain( p.second ) ); }, 20, "test_timeout_on_empty_queue: " + p.first ); } } void do_check_timeout_on_empty_queue( const so_5::mchain_t & chain ) { std::thread child{ [&] { auto sel = make_extensible_select( so_5::from_all().handle_all().empty_timeout( 500ms ), receive_case( chain ) ); auto r = select( sel ); UT_CHECK_CONDITION( 0 == r.extracted() ); } }; child.join(); } UT_UNIT_TEST( test_timeout_on_empty_queue ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_timeout_on_empty_queue( env.environment().create_mchain( p.second ) ); }, 20, "test_timeout_on_empty_queue: " + p.first ); } } void do_check_total_time( const so_5::mchain_t & chain ) { so_5::send< int >( chain, 0 ); so_5::send< int >( chain, 1 ); so_5::send< string >( chain, "hello!" ); std::thread child{ [&] { auto r = select( make_extensible_select( so_5::from_all().handle_all().total_time( 500ms ), receive_case( chain, []( const std::string & ) {} ) ) ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); } }; child.join(); } UT_UNIT_TEST( test_total_time ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_total_time( env.environment().create_mchain( p.second ) ); }, 20, "test_total_time: " + p.first ); } } void do_check_handle_n( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { auto r = select( make_extensible_select( so_5::from_all().handle_n( 3 ), receive_case( ch1, [&ch2]( int i ) { so_5::send< int >( ch2, i ); } ) ) ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 3 == r.handled() ); } }; so_5::send< int >( ch1, 0 ); auto r = select( make_extensible_select( so_5::from_all().handle_n( 2 ), receive_case( ch2, [&ch1]( int i ) { so_5::send< int >( ch1, i + 1 ); } ) ) ); UT_CHECK_CONDITION( 2 == r.extracted() ); UT_CHECK_CONDITION( 2 == r.handled() ); child.join(); } UT_UNIT_TEST( test_handle_n ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_handle_n( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_handle_n: " + p.first ); } } void do_check_extract_n( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { auto r = select( make_extensible_select( so_5::from_all().handle_n( 3 ).extract_n( 3 ), receive_case( ch1, [&ch2]( int i ) { so_5::send< int >( ch2, i ); } ) ) ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); } }; so_5::send< string >( ch1, "0" ); so_5::send< int >( ch1, 0 ); auto r = select( make_extensible_select( so_5::from_all().handle_n( 1 ), receive_case( ch2, [&ch1]( int i ) { so_5::send< string >( ch1, to_string( i + 1 ) ); so_5::send< int >( ch1, i + 1 ); } ) ) ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); child.join(); } UT_UNIT_TEST( test_extract_n ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_extract_n( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_extract_n: " + p.first ); } } void do_check_stop_pred( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { int last_received = 0; auto r = select( make_extensible_select( so_5::from_all().handle_all().stop_on( [&last_received] { return last_received > 10; } ), receive_case( ch1, [&ch2, &last_received]( int i ) { last_received = i; so_5::send< int >( ch2, i ); } ) ) ); UT_CHECK_CONDITION( r.extracted() > 10 ); UT_CHECK_CONDITION( r.handled() > 10 ); } }; int i = 0; so_5::send< int >( ch1, i ); auto r = select( make_extensible_select( so_5::from_all().handle_all().stop_on( [&i] { return i > 10; } ), receive_case( ch2, [&ch1, &i]( int ) { ++i; so_5::send< int >( ch1, i ); } ) ) ); UT_CHECK_CONDITION( r.extracted() > 10 ); UT_CHECK_CONDITION( r.handled() > 10 ); child.join(); } UT_UNIT_TEST( test_stop_pred ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_stop_pred( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_stop_pred(no_msg_tracing): " + p.first ); run_with_time_limit( [&p]() { so_5::wrapped_env_t env( []( so_5::environment_t & ) {}, []( so_5::environment_params_t & env_params ) { env_params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } ); do_check_stop_pred( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_stop_pred(msg_tracing): " + p.first ); } } void do_check_nested_select( const so_5::mchain_t & ch ) { struct try_nested_select {}; std::optional error; std::thread child{ [&] { auto sel = make_extensible_select( so_5::from_all().handle_all() ); add_select_cases( sel, receive_case( ch, [&]( const try_nested_select & ) { try { select( sel ); // Nested is prohibited. } catch( const so_5::exception_t & x ) { error = x.error_code(); } } ) ); auto r = select( sel ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( error && so_5::rc_extensible_select_is_active_now == *error ); } }; so_5::send< try_nested_select >( ch ); so_5::close_retain_content( so_5::exceptions_enabled, ch ); child.join(); } UT_UNIT_TEST( test_nested_select ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_nested_select( env.environment().create_mchain( p.second ) ); }, 20, "test_nested_select: " + p.first ); } } void do_check_modify_from_select( const so_5::mchain_t & ch ) { struct try_modify {}; struct empty {}; std::optional error; std::thread child{ [&] { auto sel = make_extensible_select( so_5::from_all().handle_all() ); add_select_cases( sel, receive_case( ch, [&]( const try_modify & ) { try { add_select_cases( sel, receive_case( ch, []( const empty & ) {} ) ); } catch( const so_5::exception_t & x ) { error = x.error_code(); } } ) ); auto r = select( sel ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( error && so_5::rc_extensible_select_is_active_now == *error ); } }; so_5::send< try_modify >( ch ); so_5::close_retain_content( so_5::exceptions_enabled, ch ); child.join(); } UT_UNIT_TEST( test_modify_from_select ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_modify_from_select( env.environment().create_mchain( p.second ) ); }, 20, "test_nested_select: " + p.first ); } } int main() { UT_RUN_UNIT_TEST( test_empty_cases_list ) UT_RUN_UNIT_TEST( test_timeout_on_empty_queue ) UT_RUN_UNIT_TEST( test_total_time ) UT_RUN_UNIT_TEST( test_handle_n ) UT_RUN_UNIT_TEST( test_extract_n ) UT_RUN_UNIT_TEST( test_stop_pred ) UT_RUN_UNIT_TEST( test_nested_select ) UT_RUN_UNIT_TEST( test_modify_from_select ) return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/adv_prepared_receive/000077500000000000000000000000001523065210100240475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/adv_prepared_receive/CMakeLists.txt000066400000000000000000000001471523065210100266110ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.adv_prepared_receive) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/adv_prepared_receive/main.cpp000066400000000000000000000134641523065210100255070ustar00rootroot00000000000000/* * A simple test for prepared advanced receive. */ #include #include #include #include "../mchain_params.hpp" using namespace std; using namespace chrono; void do_check_timeout_on_empty_queue( const so_5::mchain_t & chain ) { std::thread child{ [&] { auto r = receive( prepare_receive( from( chain ).handle_all().empty_timeout( milliseconds( 500 ) ) ) ); UT_CHECK_CONDITION( 0 == r.extracted() ); UT_CHECK_CONDITION( so_5::mchain_props::extraction_status_t::no_messages == r.status() ); } }; child.join(); } UT_UNIT_TEST( test_timeout_on_empty_queue ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_timeout_on_empty_queue( env.environment().create_mchain( p.second ) ); }, 20, "test_timeout_on_empty_queue: " + p.first ); } } void do_check_total_time( const so_5::mchain_t & chain ) { so_5::send< int >( chain, 0 ); so_5::send< int >( chain, 1 ); so_5::send< string >( chain, "hello!" ); std::thread child{ [&] { auto r = receive( prepare_receive( from( chain ).handle_all().total_time( milliseconds( 500 ) ), []( const std::string & ) {} ) ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( so_5::mchain_props::extraction_status_t::msg_extracted == r.status() ); } }; child.join(); } UT_UNIT_TEST( test_total_time ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_total_time( env.environment().create_mchain( p.second ) ); }, 20, "test_total_time: " + p.first ); } } void do_check_handle_n( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { auto r = receive( prepare_receive( from( ch1 ).handle_n( 3 ), [&ch2]( int i ) { so_5::send< int >( ch2, i ); } ) ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 3 == r.handled() ); } }; so_5::send< int >( ch1, 0 ); auto r = receive( from( ch2 ).handle_n( 2 ), [&ch1]( int i ) { so_5::send< int >( ch1, i + 1 ); } ); UT_CHECK_CONDITION( 2 == r.extracted() ); UT_CHECK_CONDITION( 2 == r.handled() ); child.join(); } UT_UNIT_TEST( test_handle_n ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_handle_n( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_handle_n: " + p.first ); } } void do_check_extract_n( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { auto r = receive( prepare_receive( from( ch1 ).handle_n( 3 ).extract_n( 3 ), [&ch2]( int i ) { so_5::send< int >( ch2, i ); } ) ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); } }; so_5::send< string >( ch1, "0" ); so_5::send< int >( ch1, 0 ); auto r = receive( prepare_receive( from( ch2 ).handle_n( 1 ), [&ch1]( int i ) { so_5::send< string >( ch1, to_string( i + 1 ) ); so_5::send< int >( ch1, i + 1 ); } ) ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); child.join(); } UT_UNIT_TEST( test_extract_n ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_extract_n( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_extract_n: " + p.first ); } } void do_check_stop_pred( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { int last_received = 0; auto r = receive( prepare_receive( from( ch1 ).handle_all().stop_on( [&last_received] { return last_received > 10; } ), [&ch2, &last_received]( int i ) { last_received = i; so_5::send< int >( ch2, i ); } ) ); UT_CHECK_CONDITION( r.extracted() > 10 ); UT_CHECK_CONDITION( r.handled() > 10 ); } }; int i = 0; so_5::send< int >( ch1, i ); auto r = receive( prepare_receive( from( ch2 ).handle_all().stop_on( [&i] { return i > 10; } ), [&ch1, &i]( int ) { ++i; so_5::send< int >( ch1, i ); } ) ); UT_CHECK_CONDITION( r.extracted() > 10 ); UT_CHECK_CONDITION( r.handled() > 10 ); child.join(); } UT_UNIT_TEST( test_stop_pred ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_stop_pred( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_stop_pred(no_msg_tracing): " + p.first ); run_with_time_limit( [&p]() { so_5::wrapped_env_t env( []( so_5::environment_t & ) {}, []( so_5::environment_params_t & env_params ) { env_params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } ); do_check_stop_pred( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_stop_pred(msg_tracing): " + p.first ); } } int main() { UT_RUN_UNIT_TEST( test_timeout_on_empty_queue ) UT_RUN_UNIT_TEST( test_total_time ) UT_RUN_UNIT_TEST( test_handle_n ) UT_RUN_UNIT_TEST( test_extract_n ) UT_RUN_UNIT_TEST( test_stop_pred ) return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/adv_prepared_select/000077500000000000000000000000001523065210100237045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/adv_prepared_select/CMakeLists.txt000066400000000000000000000001461523065210100264450ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.adv_prepared_select) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/adv_prepared_select/main.cpp000066400000000000000000000201141523065210100253320ustar00rootroot00000000000000/* * An advanced test for extensible select. */ #include #include #include #include "../mchain_params.hpp" using namespace std; using namespace chrono; using namespace chrono_literals; void do_check_timeout_on_empty_queue( const so_5::mchain_t & chain ) { auto sel = prepare_select( so_5::from_all().handle_all().empty_timeout( 500ms ), receive_case( chain ) ); auto r = select( sel ); UT_CHECK_CONDITION( 0 == r.extracted() ); UT_CHECK_CONDITION( !r.was_sent_or_received() ); } UT_UNIT_TEST( test_timeout_on_empty_queue ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_timeout_on_empty_queue( env.environment().create_mchain( p.second ) ); }, 20, "test_timeout_on_empty_queue: " + p.first ); } } void do_check_total_time( const so_5::mchain_t & chain ) { so_5::send< int >( chain, 0 ); so_5::send< int >( chain, 1 ); so_5::send< string >( chain, "hello!" ); auto r = select( prepare_select( so_5::from_all().handle_all().total_time( 500ms ), receive_case( chain, []( const std::string & ) {} ) ) ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( r.was_handled() ); } UT_UNIT_TEST( test_total_time ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_total_time( env.environment().create_mchain( p.second ) ); }, 20, "test_total_time: " + p.first ); } } void do_check_no_wait_on_empty_with_total_time( const so_5::mchain_t & chain ) { auto sel = prepare_select( so_5::from_all() .handle_all() .no_wait_on_empty() .total_time( 500s ), receive_case( chain ) ); auto r = select( sel ); UT_CHECK_CONDITION( 0 == r.extracted() ); UT_CHECK_CONDITION( !r.was_sent_or_received() ); } UT_UNIT_TEST( test_no_wait_on_empty_with_total_time ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_no_wait_on_empty_with_total_time( env.environment().create_mchain( p.second ) ); }, 20, "test_no_wait_on_empty_with_total_time: " + p.first ); } } void do_check_handle_n( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { auto r = select( prepare_select( so_5::from_all().handle_n( 3 ), receive_case( ch1, [&ch2]( int i ) { so_5::send< int >( ch2, i ); } ) ) ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 3 == r.handled() ); } }; so_5::send< int >( ch1, 0 ); auto r = select( prepare_select( so_5::from_all().handle_n( 2 ), receive_case( ch2, [&ch1]( int i ) { so_5::send< int >( ch1, i + 1 ); } ) ) ); UT_CHECK_CONDITION( 2 == r.extracted() ); UT_CHECK_CONDITION( 2 == r.handled() ); child.join(); } UT_UNIT_TEST( test_handle_n ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_handle_n( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_handle_n: " + p.first ); } } void do_check_extract_n( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { auto r = select( prepare_select( so_5::from_all().handle_n( 3 ).extract_n( 3 ), receive_case( ch1, [&ch2]( int i ) { so_5::send< int >( ch2, i ); } ) ) ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); } }; so_5::send< string >( ch1, "0" ); so_5::send< int >( ch1, 0 ); auto r = select( prepare_select( so_5::from_all().handle_n( 1 ), receive_case( ch2, [&ch1]( int i ) { so_5::send< string >( ch1, to_string( i + 1 ) ); so_5::send< int >( ch1, i + 1 ); } ) ) ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); child.join(); } UT_UNIT_TEST( test_extract_n ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_extract_n( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_extract_n: " + p.first ); } } void do_check_stop_pred( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { int last_received = 0; auto r = select( prepare_select( so_5::from_all().handle_all().stop_on( [&last_received] { return last_received > 10; } ), receive_case( ch1, [&ch2, &last_received]( int i ) { last_received = i; so_5::send< int >( ch2, i ); } ) ) ); UT_CHECK_CONDITION( r.extracted() > 10 ); UT_CHECK_CONDITION( r.handled() > 10 ); } }; int i = 0; so_5::send< int >( ch1, i ); auto r = select( prepare_select( so_5::from_all().handle_all().stop_on( [&i] { return i > 10; } ), receive_case( ch2, [&ch1, &i]( int ) { ++i; so_5::send< int >( ch1, i ); } ) ) ); UT_CHECK_CONDITION( r.extracted() > 10 ); UT_CHECK_CONDITION( r.handled() > 10 ); child.join(); } UT_UNIT_TEST( test_stop_pred ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_stop_pred( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_stop_pred(no_msg_tracing): " + p.first ); run_with_time_limit( [&p]() { so_5::wrapped_env_t env( []( so_5::environment_t & ) {}, []( so_5::environment_params_t & env_params ) { env_params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } ); do_check_stop_pred( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_stop_pred(msg_tracing): " + p.first ); } } void do_check_parallel_select( const so_5::mchain_t & ch, const so_5::mchain_t & failure_ch ) { struct nothing {}; auto sel = prepare_select( so_5::from_all().handle_all(), receive_case( ch, [](const nothing&) {} ) ); std::thread child1{ [&] { try { select( sel ); } catch( const so_5::exception_t & x ) { so_5::send( failure_ch, x.error_code() ); } } }; std::thread child2{ [&] { try { select( sel ); } catch( const so_5::exception_t & x ) { so_5::send( failure_ch, x.error_code() ); } } }; std::optional error; so_5::receive( so_5::from( failure_ch ).handle_n(1).empty_timeout(500ms), [&error](int error_code) { error = error_code; } ); so_5::close_retain_content( so_5::exceptions_enabled, ch ); child1.join(); child2.join(); UT_CHECK_CONDITION( error && so_5::rc_prepared_select_is_active_now == *error ); } UT_UNIT_TEST( test_parallel_select ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_parallel_select( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_parallel_select: " + p.first ); } } int main() { UT_RUN_UNIT_TEST( test_timeout_on_empty_queue ) UT_RUN_UNIT_TEST( test_total_time ) UT_RUN_UNIT_TEST( test_no_wait_on_empty_with_total_time ) UT_RUN_UNIT_TEST( test_handle_n ) UT_RUN_UNIT_TEST( test_extract_n ) UT_RUN_UNIT_TEST( test_stop_pred ) UT_RUN_UNIT_TEST( test_parallel_select ) return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/adv_receive/000077500000000000000000000000001523065210100221655ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/adv_receive/CMakeLists.txt000066400000000000000000000001361523065210100247250ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.adv_receive) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/adv_receive/main.cpp000066400000000000000000000145351523065210100236250ustar00rootroot00000000000000/* * A simple test for mchain. */ #include #include #include #include "../mchain_params.hpp" using namespace std; using namespace chrono; void do_check_timeout_on_empty_queue( const so_5::mchain_t & chain ) { auto r = receive( from( chain ).handle_all().empty_timeout( milliseconds( 500 ) ) ); UT_CHECK_CONDITION( 0 == r.extracted() ); UT_CHECK_CONDITION( so_5::mchain_props::extraction_status_t::no_messages == r.status() ); } UT_UNIT_TEST( test_timeout_on_empty_queue ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_timeout_on_empty_queue( env.environment().create_mchain( p.second ) ); }, 20, "test_timeout_on_empty_queue: " + p.first ); } } void do_check_total_time( const so_5::mchain_t & chain ) { so_5::send< int >( chain, 0 ); so_5::send< int >( chain, 1 ); so_5::send< string >( chain, "hello!" ); auto r = receive( from( chain ).handle_all().total_time( milliseconds( 500 ) ), []( const std::string & ) {} ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( so_5::mchain_props::extraction_status_t::msg_extracted == r.status() ); } UT_UNIT_TEST( test_total_time ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_total_time( env.environment().create_mchain( p.second ) ); }, 20, "test_total_time: " + p.first ); } } void do_check_no_wait_on_empty_with_total_time( const so_5::mchain_t & chain ) { auto r = receive( from( chain ) .handle_all() .no_wait_on_empty() .total_time( seconds( 500 ) ) ); UT_CHECK_CONDITION( 0 == r.extracted() ); UT_CHECK_CONDITION( so_5::mchain_props::extraction_status_t::no_messages == r.status() ); } UT_UNIT_TEST( test_no_wait_on_empty_with_total_time ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_no_wait_on_empty_with_total_time( env.environment().create_mchain( p.second ) ); }, 5, "test_no_wait_on_empty_with_total_time: " + p.first ); } } void do_check_handle_n( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { auto r = receive( from( ch1 ).handle_n( 3 ), [&ch2]( int i ) { so_5::send< int >( ch2, i ); } ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 3 == r.handled() ); } }; so_5::send< int >( ch1, 0 ); auto r = receive( from( ch2 ).handle_n( 2 ), [&ch1]( int i ) { so_5::send< int >( ch1, i + 1 ); } ); UT_CHECK_CONDITION( 2 == r.extracted() ); UT_CHECK_CONDITION( 2 == r.handled() ); child.join(); } UT_UNIT_TEST( test_handle_n ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_handle_n( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_handle_n: " + p.first ); } } void do_check_extract_n( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { auto r = receive( from( ch1 ).handle_n( 3 ).extract_n( 3 ), [&ch2]( int i ) { so_5::send< int >( ch2, i ); } ); UT_CHECK_CONDITION( 3 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); } }; so_5::send< string >( ch1, "0" ); so_5::send< int >( ch1, 0 ); auto r = receive( from( ch2 ).handle_n( 1 ), [&ch1]( int i ) { so_5::send< string >( ch1, to_string( i + 1 ) ); so_5::send< int >( ch1, i + 1 ); } ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); child.join(); } UT_UNIT_TEST( test_extract_n ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_extract_n( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_extract_n: " + p.first ); } } void do_check_stop_pred( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { std::thread child{ [&] { int last_received = 0; auto r = receive( from( ch1 ).handle_all().stop_on( [&last_received] { return last_received > 10; } ), [&ch2, &last_received]( int i ) { last_received = i; so_5::send< int >( ch2, i ); } ); UT_CHECK_CONDITION( r.extracted() > 10 ); UT_CHECK_CONDITION( r.handled() > 10 ); } }; int i = 0; so_5::send< int >( ch1, i ); auto r = receive( from( ch2 ).handle_all().stop_on( [&i] { return i > 10; } ), [&ch1, &i]( int ) { ++i; so_5::send< int >( ch1, i ); } ); UT_CHECK_CONDITION( r.extracted() > 10 ); UT_CHECK_CONDITION( r.handled() > 10 ); child.join(); } UT_UNIT_TEST( test_stop_pred ) { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check_stop_pred( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_stop_pred(no_msg_tracing): " + p.first ); run_with_time_limit( [&p]() { so_5::wrapped_env_t env( []( so_5::environment_t & ) {}, []( so_5::environment_params_t & env_params ) { env_params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } ); do_check_stop_pred( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); }, 20, "test_stop_pred(msg_tracing): " + p.first ); } } int main() { UT_RUN_UNIT_TEST( test_timeout_on_empty_queue ) UT_RUN_UNIT_TEST( test_total_time ) UT_RUN_UNIT_TEST( test_no_wait_on_empty_with_total_time ) UT_RUN_UNIT_TEST( test_handle_n ) UT_RUN_UNIT_TEST( test_extract_n ) UT_RUN_UNIT_TEST( test_stop_pred ) return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_close/000077500000000000000000000000001523065210100247135ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_close/CMakeLists.txt000066400000000000000000000001531523065210100274520ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.adv_select_mthread_close) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_close/main.cpp000066400000000000000000000026541523065210100263520ustar00rootroot00000000000000/* * Test for closing mchain which is used in different thread in * different selects. */ #include #include #include #include using namespace std; struct ready {}; void worker_thread( so_5::mchain_t ready_ch, so_5::mchain_t test_ch ) { so_5::send< ready >( ready_ch ); so_5::select( so_5::from_all().handle_all(), receive_case( test_ch, [](int) {} ) ); } so_5::mchain_params_t mchain_params() { return so_5::make_unlimited_mchain_params(); } int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env; auto ready_ch = env.environment().create_mchain( mchain_params() ); auto test_ch = env.environment().create_mchain( mchain_params() ); thread w1{ [ready_ch, test_ch]{ worker_thread( ready_ch, test_ch ); } }; thread w2{ [ready_ch, test_ch]{ worker_thread( ready_ch, test_ch ); } }; thread w3{ [ready_ch, test_ch]{ worker_thread( ready_ch, test_ch ); } }; receive( from( ready_ch ).handle_n( 3 ), []( ready ) {} ); this_thread::sleep_for( chrono::milliseconds(500) ); close_drop_content( so_5::exceptions_enabled, test_ch ); w1.join(); w2.join(); w3.join(); }, 20, "close mchain (adv_select in different threads)" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_empty_timeout/000077500000000000000000000000001523065210100265125ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_empty_timeout/CMakeLists.txt000066400000000000000000000001631523065210100312520ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.adv_select_mthread_empty_timeout) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_empty_timeout/main.cpp000066400000000000000000000045261523065210100301510ustar00rootroot00000000000000/* * Test for empty_timeout on adv_select. */ #include #include #include #include #include "../mchain_params.hpp" using namespace std; struct worker_result { int m_id; unsigned int m_values; }; template< typename DURATION > void worker_thread( int id, so_5::mchain_t control_ch, so_5::mchain_t test_ch, DURATION empty_timeout ) { worker_result r{ id, 0u }; select( so_5::from_all() .handle_n( 5 ) .empty_timeout( empty_timeout ), receive_case( test_ch, [&r]( unsigned int ) { ++r.m_values; } ) ); so_5::send< worker_result >( control_ch, r ); } so_5::mchain_params_t mchain_params() { return so_5::make_unlimited_mchain_params(); } template< typename DURATION > void do_test( const char * test_case, DURATION empty_timeout, DURATION min_time ) { std::cout << "starting " << test_case << "..." << std::endl; run_with_time_limit( [empty_timeout, min_time]() { so_5::wrapped_env_t env; auto control_ch = env.environment().create_mchain( mchain_params() ); auto test_ch = env.environment().create_mchain( mchain_params() ); const std::size_t N = 3; array< thread, N > workers; for( std::size_t i = 0; i != N; ++i ) workers[ i ] = thread{ [i, control_ch, test_ch, empty_timeout] { worker_thread( static_cast< int >(i), control_ch, test_ch, empty_timeout ); } }; const auto start_point = chrono::steady_clock::now(); receive( from( control_ch ).handle_n( N ), []( const worker_result & r ) { cout << r.m_id << " -> " << r.m_values << std::endl; ensure_or_die( r.m_values == 0, "no values must be received" ); } ); const auto elapsed = chrono::steady_clock::now() - start_point; ensure_or_die( min_time <= elapsed, "min_time must be less than elapsed time" ); for( auto & w : workers ) w.join(); }, 20, "read mchain (select in different threads)" ); std::cout << test_case << "-> OK" << std::endl; } int main() { try { do_test( "zero_empty_time", chrono::milliseconds::zero(), chrono::milliseconds::zero() ); do_test( "1s_empty_time", chrono::milliseconds(1000), chrono::milliseconds(750) ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_read/000077500000000000000000000000001523065210100245215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_read/CMakeLists.txt000066400000000000000000000001521523065210100272570ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.adv_select_mthread_read) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_read/main.cpp000066400000000000000000000052101523065210100261470ustar00rootroot00000000000000/* * Test for reading from chain from different threads. */ #include #include #include #include #include "../mchain_params.hpp" using namespace std; struct ready {}; struct done {}; struct worker_result { int m_id; unsigned int m_values; unsigned int m_sum; }; template< typename DURATION > void worker_thread( int id, so_5::mchain_t control_ch, so_5::mchain_t test_ch, DURATION total_time ) { worker_result r{ id, 0u, 0u }; so_5::send< ready >( control_ch ); bool finish = false; select( so_5::from_all() .handle_n( 5 ) .total_time( total_time ) .stop_on( [&finish]{ return finish; } ), receive_case( test_ch, [&r]( unsigned int v ) { ++r.m_values; r.m_sum += v; }, [&finish]( done ) { finish = true; } ) ); so_5::send< worker_result >( control_ch, r ); } so_5::mchain_params_t mchain_params() { return so_5::make_unlimited_mchain_params(); } template< typename DURATION > void do_test( const char * test_case, DURATION total_time ) { std::cout << "starting " << test_case << "..." << std::endl; run_with_time_limit( [total_time]() { so_5::wrapped_env_t env; auto control_ch = env.environment().create_mchain( mchain_params() ); auto test_ch = env.environment().create_mchain( mchain_params() ); thread w1{ [control_ch, test_ch, total_time]{ worker_thread( 0, control_ch, test_ch, total_time ); } }; thread w2{ [control_ch, test_ch, total_time]{ worker_thread( 1, control_ch, test_ch, total_time ); } }; thread w3{ [control_ch, test_ch, total_time]{ worker_thread( 3, control_ch, test_ch, total_time ); } }; receive( from( control_ch ).handle_n( 3 ), []( ready ) {} ); for( unsigned int i = 0; i != 1000; ++i ) so_5::send< unsigned int >( test_ch, i ); so_5::send< done >( test_ch ); so_5::send< done >( test_ch ); so_5::send< done >( test_ch ); receive( from( control_ch ).handle_n( 3 ), []( const worker_result & r ) { cout << r.m_id << " -> " << r.m_values << " {" << r.m_sum << "}" << endl; ensure_or_die( r.m_values == 5, "only five values must be received" ); } ); w1.join(); w2.join(); w3.join(); }, 20, "read mchain (select in different threads)" ); std::cout << test_case << "-> OK" << std::endl; } int main() { try { do_test( "infinite_total_time", so_5::infinite_wait ); do_test( "finite_total_time(5s)", chrono::seconds(5) ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_stop_on/000077500000000000000000000000001523065210100252675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_stop_on/CMakeLists.txt000066400000000000000000000001551523065210100300300ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.adv_select_mthread_stop_on) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/adv_select_mthread_stop_on/main.cpp000066400000000000000000000040051523065210100267160ustar00rootroot00000000000000/* * Test for stop_on on adv_select. */ #include #include #include #include #include "../mchain_params.hpp" using namespace std; struct worker_result { int m_id; unsigned int m_values; }; void worker_thread( int id, so_5::mchain_t control_ch, so_5::mchain_t test_ch ) { worker_result r{ id, 0u }; bool odd_found = false; select( so_5::from_all() .handle_all() .stop_on( [&odd_found]{ return odd_found; } ), receive_case( test_ch, [&r, &odd_found]( unsigned int v ) { ++r.m_values; odd_found = 0 != (v & 1); } ) ); so_5::send< worker_result >( control_ch, r ); } so_5::mchain_params_t mchain_params() { return so_5::make_unlimited_mchain_params(); } void do_test( const char * test_case ) { std::cout << "starting " << test_case << "..." << std::endl; run_with_time_limit( []() { so_5::wrapped_env_t env; auto control_ch = env.environment().create_mchain( mchain_params() ); auto test_ch = env.environment().create_mchain( mchain_params() ); const std::size_t N = 3; array< thread, N > workers; for( std::size_t i = 0; i != N; ++i ) workers[ i ] = thread{ [i, control_ch, test_ch] { worker_thread( static_cast< int >(i), control_ch, test_ch ); } }; for( unsigned int i = 0; i != 10; ++i ) so_5::send< unsigned int >( test_ch, i ); receive( from( control_ch ).handle_n( N ), []( const worker_result & r ) { cout << r.m_id << " -> " << r.m_values << std::endl; ensure_or_die( r.m_values > 0, "at least one value must be received" ); } ); for( auto & w : workers ) w.join(); }, 20, "stop_on on adv_select (select in different threads)" ); std::cout << test_case << "-> OK" << std::endl; } int main() { try { do_test( "multi-thread adv_select with stop_on" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/auto_close_chains/000077500000000000000000000000001523065210100233735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/auto_close_chains/CMakeLists.txt000066400000000000000000000001441523065210100261320ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.auto_close_chains) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/auto_close_chains/main.cpp000066400000000000000000000103121523065210100250200ustar00rootroot00000000000000/* * Test for auto_close_mchains() function. */ #include #include #include "../mchain_params.hpp" using namespace std; void handle_unexpected( int i ) { throw runtime_error( "unexpected message: " + to_string(i) ); } void handle_expected( int i ) { if( i != 42 ) throw runtime_error( "unexpected value: " + to_string(i) ); } void check_drop_content1( so_5::mchain_t ch1 ) { { auto closer = so_5::auto_close_drop_content( ch1 ); so_5::send< int >( ch1, 0 ); } auto r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), receive_case( ch1, []( int i ) { handle_unexpected(i); } ) ); if( !r.was_closed() ) throw runtime_error( "r.was_closed() is not true" ); } void check_drop_content3( so_5::mchain_t ch1, so_5::mchain_t ch2, so_5::mchain_t ch3 ) { { auto closer = so_5::auto_close_drop_content( ch1, ch2, ch3 ); so_5::send< int >( ch1, 0 ); so_5::send< int >( ch2, 1 ); so_5::send< int >( ch3, 1 ); } auto r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), receive_case( ch1, []( int i ) { handle_unexpected(i); } ), receive_case( ch2, []( int i ) { handle_unexpected(i); } ), receive_case( ch3, []( int i ) { handle_unexpected(i); } ) ); if( !r.was_closed() ) throw runtime_error( "r.was_closed() is not true" ); } void check_retain_content1( so_5::mchain_t ch1 ) { { auto closer = so_5::auto_close_retain_content( ch1 ); so_5::send< int >( ch1, 42 ); so_5::send< int >( ch1, 42 ); } auto r = so_5::select( so_5::from_all().handle_n(2), receive_case( ch1, []( int i ) { handle_expected(i); } ) ); if( 2 != r.handled() ) throw runtime_error( "unexpected count of handled messages: " + to_string( r.handled() ) ); r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), receive_case( ch1, []( int i ) { handle_unexpected(i); } ) ); if( !r.was_closed() ) throw runtime_error( "r.was_closed() is not true" ); } void check_retain_content3( so_5::mchain_t ch1, so_5::mchain_t ch2, so_5::mchain_t ch3 ) { { auto closer = so_5::auto_close_retain_content( ch1, ch2, ch3 ); so_5::send< int >( ch1, 42 ); so_5::send< int >( ch1, 42 ); so_5::send< int >( ch2, 42 ); so_5::send< int >( ch2, 42 ); so_5::send< int >( ch3, 42 ); so_5::send< int >( ch3, 42 ); } auto r = so_5::select( so_5::from_all().handle_n(6), receive_case( ch1, []( int i ) { handle_expected(i); } ), receive_case( ch2, []( int i ) { handle_expected(i); } ), receive_case( ch3, []( int i ) { handle_expected(i); } ) ); if( 6 != r.handled() ) throw runtime_error( "unexpected count of handled messages: " + to_string( r.handled() ) ); r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), receive_case( ch1, []( int i ) { handle_unexpected(i); } ), receive_case( ch2, []( int i ) { handle_unexpected(i); } ), receive_case( ch3, []( int i ) { handle_unexpected(i); } ) ); if( !r.was_closed() ) throw runtime_error( "r.was_closed() is not true" ); } void do_check( bool msg_tracing_enabled ) { run_with_time_limit( [msg_tracing_enabled]() { so_5::wrapped_env_t env{ []( so_5::environment_t & ) {}, [msg_tracing_enabled]( so_5::environment_params_t & params ) { if( msg_tracing_enabled ) params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } }; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; check_drop_content1( env.environment().create_mchain( p.second ) ); check_drop_content3( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); check_retain_content1( env.environment().create_mchain( p.second ) ); check_retain_content3( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); } }, 20 ); } int main() { try { do_check( false ); do_check( true ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/auto_close_chains_ex/000077500000000000000000000000001523065210100240675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/auto_close_chains_ex/CMakeLists.txt000066400000000000000000000001471523065210100266310ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.auto_close_chains_ex) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/auto_close_chains_ex/main.cpp000066400000000000000000000047351523065210100255300ustar00rootroot00000000000000/* * Test for auto_close_mchains() function with exceptions. */ #include #include using namespace std; class test_ex_t : public runtime_error { public : test_ex_t() : runtime_error{ "test exception" } {} }; void worker_thread( so_5::mchain_t command_ch, so_5::mchain_t reply_ch ) { receive( from( command_ch ).handle_all(), [reply_ch]( int i ) { so_5::send< string >( reply_ch, to_string(i) ); } ); close_retain_content( so_5::exceptions_enabled, reply_ch ); } void do_test_case( so_5::environment_t & env, bool throw_exception ) { thread first_worker; auto first_joiner = so_5::auto_join( first_worker ); auto first_cmd_ch = create_mchain( env ); auto first_reply_ch = create_mchain( env ); auto first_ch_closer = so_5::auto_close_drop_content( first_cmd_ch, first_reply_ch ); first_worker = thread{ worker_thread, first_cmd_ch, first_reply_ch }; thread second_worker; auto second_joiner = so_5::auto_join( second_worker ); auto second_cmd_ch = create_mchain( env ); auto second_reply_ch = create_mchain( env ); auto second_ch_closer = so_5::auto_close_drop_content( second_cmd_ch, second_reply_ch ); second_worker = thread{ worker_thread, second_cmd_ch, second_reply_ch }; if( throw_exception ) throw test_ex_t{}; for( int i = 0; i != 3; ++i ) { so_5::send< int >( first_cmd_ch, i ); so_5::send< int >( second_cmd_ch, -i ); } so_5::select( so_5::from_all().handle_all().empty_timeout( chrono::milliseconds(500) ), receive_case( first_reply_ch, []( string s ) { cout << "first: " << s << endl; } ), receive_case( second_reply_ch, []( string s ) { cout << "second: " << s << endl; } ) ); close_drop_content( so_5::exceptions_enabled, first_cmd_ch ); close_drop_content( so_5::exceptions_enabled, second_cmd_ch ); } void do_check( bool throw_exception ) { run_with_time_limit( [throw_exception]() { so_5::wrapped_env_t sobj; cout << "=== " << (throw_exception ? "throw" : "no throw") << endl; if( throw_exception ) { try { do_test_case( sobj.environment(), throw_exception ); throw runtime_error( "test_ex_t is not thrown" ); } catch( const test_ex_t & ) {} } else do_test_case( sobj.environment(), throw_exception ); }, 20 ); } int main() { try { do_check( false ); do_check( true ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/close_chain/000077500000000000000000000000001523065210100221605ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/close_chain/CMakeLists.txt000066400000000000000000000001361523065210100247200ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.close_chain) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/close_chain/main.cpp000066400000000000000000000045621523065210100236170ustar00rootroot00000000000000/* * A simple test for mchain. */ #include #include #include "../mchain_params.hpp" using namespace std; void check_drop_content( const so_5::mchain_t & chain ) { so_5::send< int >( chain, 0 ); so_5::send< int >( chain, 1 ); close_drop_content( so_5::exceptions_enabled, chain ); auto r = receive( from(chain).handle_all().no_wait_on_empty(), []( int i ) { throw runtime_error( "unexpected message: " + to_string(i) ); } ); if( r.status() != so_5::mchain_props::extraction_status_t::chain_closed ) throw runtime_error( "unexpected value of so_5::receive " "return code: " + to_string( static_cast(r.status()) ) ); } void check_retain_content( const so_5::mchain_t & chain ) { so_5::send< int >( chain, 0 ); so_5::send< int >( chain, 1 ); close_retain_content( so_5::exceptions_enabled, chain ); so_5::mchain_receive_result_t r; r = receive( from(chain).handle_n(1).no_wait_on_empty(), []( int i ) { if( i != 0 ) throw runtime_error( "unexpected message: " + to_string(i) ); } ); if( r.handled() != 1 ) throw runtime_error( "1: unexpected value of so_5::receive " "return code: " + to_string( r.handled() ) ); r = receive( from(chain).handle_n(1).no_wait_on_empty(), []( int i ) { if( i != 1 ) throw runtime_error( "unexpected message: " + to_string(i) ); } ); if( r.handled() != 1 ) throw runtime_error( "2: unexpected value of so_5::receive " "return code: " + to_string( r.handled() ) ); } void do_check( bool msg_tracing_enabled ) { run_with_time_limit( [msg_tracing_enabled]() { so_5::wrapped_env_t env{ []( so_5::environment_t & ) {}, [msg_tracing_enabled]( so_5::environment_params_t & params ) { if( msg_tracing_enabled ) params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } }; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; check_drop_content( env.environment().create_mchain( p.second ) ); check_retain_content( env.environment().create_mchain( p.second ) ); } }, 20, "close_chain" ); } int main() { try { do_check( false ); do_check( true ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/empty_notify/000077500000000000000000000000001523065210100224375ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/empty_notify/CMakeLists.txt000066400000000000000000000001371523065210100252000ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.empty_notify) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/empty_notify/main.cpp000066400000000000000000000035351523065210100240750ustar00rootroot00000000000000/* * Test for custom 'empty' notificator for mchain. */ #include #include #include #include "../mchain_params.hpp" using namespace std; class event { std::mutex m; std::condition_variable cv; bool signaled = { false }; public : void notify_one() { std::lock_guard< std::mutex > l{ m }; signaled = true; cv.notify_one(); } void wait() { std::unique_lock< std::mutex > l{ m }; if( !signaled ) cv.wait( l, [this]{ return signaled; } ); signaled = false; } }; void do_check( so_5::environment_t & env, so_5::mchain_params_t params ) { event child_started; event chain_empty; std::atomic< int > how_many_times_called{ 0 }; params.empty_notificator( [&chain_empty, &how_many_times_called] { how_many_times_called += 1; chain_empty.notify_one(); } ); auto ch = env.create_mchain( params ); std::thread child{ [&] { child_started.notify_one(); so_5::send< int >( ch, 1 ); receive( from(ch).handle_n(1), []( int i ) { UT_CHECK_CONDITION( i != 0 ); } ); } }; std::cout << "child starting." << std::flush; child_started.wait(); std::cout << "waiting for notification." << std::flush; chain_empty.wait(); std::cout << "joining." << std::flush; child.join(); UT_CHECK_CONDITION( 1 == how_many_times_called.load() ); std::cout << "OK" << std::endl; } int main() { try { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check( env.environment(), p.second ); }, 20, p.first ); } } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/extensible_select_simple/000077500000000000000000000000001523065210100247635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/extensible_select_simple/CMakeLists.txt000066400000000000000000000001531523065210100275220ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.extensible_select_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/extensible_select_simple/main.cpp000066400000000000000000000027641523065210100264240ustar00rootroot00000000000000/* * A simple test for simple prepared select on several mchains. */ #include #include #include #include "../mchain_params.hpp" using namespace std; void do_test() { struct hello {}; so_5::wrapped_env_t env; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; auto ch1 = env.environment().create_mchain( p.second ); auto ch2 = env.environment().create_mchain( p.second ); auto ch3 = env.environment().create_mchain( p.second ); bool hello_received = false; so_5::send< hello >( ch2 ); auto sel = make_extensible_select( so_5::from_all().extract_n( 1 ).no_wait_on_empty(), receive_case( ch1, []( hello ) { throw std::runtime_error( "hello from ch1!" ); } ) ); add_select_cases( sel, receive_case( ch2, [&hello_received]( hello ) { hello_received = true; } ) ); add_select_cases( sel, receive_case( ch3, []( hello ) { throw std::runtime_error( "hello from ch3!" ); } ) ); auto r = select( sel ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( hello_received ); } } int main() { try { run_with_time_limit( do_test, 20, "test for simple multi chain select" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/func_as_handler/000077500000000000000000000000001523065210100230245ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/func_as_handler/CMakeLists.txt000066400000000000000000000001421523065210100255610ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.func_as_handler) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/func_as_handler/main.cpp000066400000000000000000000057731523065210100244700ustar00rootroot00000000000000/* * A test for using functions as handlers. */ #include #include #include #include "../mchain_params.hpp" using namespace std; struct bye {}; bool g_bye_received = false; void on_bye_1(bye) { g_bye_received = true; } void on_bye_2(so_5::mhood_t) { g_bye_received = true; } void on_bye_3(so_5::mutable_mhood_t) { g_bye_received = true; } void check_by_value(so_5::mchain_t chain ) { so_5::send< bye >( chain ); g_bye_received = false; auto r = receive( from(chain).handle_n(1).no_wait_on_empty(), &on_bye_1 ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( g_bye_received ); } void check_ref_by_value(so_5::mchain_t chain ) { so_5::send< bye >( chain ); g_bye_received = false; auto r = receive( from(chain).handle_n(1).no_wait_on_empty(), on_bye_1 ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( g_bye_received ); } void check_by_mhood(so_5::mchain_t chain ) { so_5::send< bye >( chain ); g_bye_received = false; auto r = receive( from(chain).handle_n(1).no_wait_on_empty(), &on_bye_2 ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( g_bye_received ); } void check_ref_by_mhood(so_5::mchain_t chain ) { so_5::send< bye >( chain ); g_bye_received = false; auto r = receive( from(chain).handle_n(1).no_wait_on_empty(), on_bye_2 ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( g_bye_received ); } void check_by_mutable_mhood(so_5::mchain_t chain ) { so_5::send< so_5::mutable_msg >( chain ); g_bye_received = false; auto r = receive( from(chain).handle_n(1).no_wait_on_empty(), &on_bye_3 ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( g_bye_received ); } void check_ref_by_mutable_mhood(so_5::mchain_t chain ) { so_5::send< so_5::mutable_msg >( chain ); g_bye_received = false; auto r = receive( from(chain).handle_n(1).no_wait_on_empty(), on_bye_3 ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( g_bye_received ); } int main() { try { run_with_time_limit( []() { struct hello : public so_5::signal_t {}; so_5::wrapped_env_t env; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; auto chain = env.environment().create_mchain( p.second ); check_by_value( chain ); check_ref_by_value( chain ); check_by_mhood( chain ); check_ref_by_mhood( chain ); check_by_mutable_mhood( chain ); check_ref_by_mutable_mhood( chain ); } }, 20 ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/infinite_wait/000077500000000000000000000000001523065210100225425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/infinite_wait/CMakeLists.txt000066400000000000000000000001401523065210100252750ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.infinite_wait) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/infinite_wait/main.cpp000066400000000000000000000030351523065210100241730ustar00rootroot00000000000000/* * A simple test for mchain. */ #include #include #include "../mchain_params.hpp" using namespace std; void do_check( const so_5::mchain_t & chain ) { std::mutex m; std::condition_variable cv; std::unique_lock< std::mutex > lock{ m }; bool child_started = false; std::thread child{ [&] { { std::lock_guard< std::mutex > l{ m }; child_started = true; cv.notify_one(); } auto r = receive( from(chain).handle_all() ); if( so_5::mchain_props::extraction_status_t::chain_closed != r.status() ) { std::cerr << "unexpected receive status: " << static_cast(r.status()) << std::endl; std::abort(); } } }; std::cout << "child starting." << std::flush; cv.wait( lock, [&child_started] { return child_started; } ); lock.unlock(); std::cout << "sleeping." << std::flush; std::this_thread::sleep_for( std::chrono::seconds(1) ); std::cout << "closing." << std::flush; close_drop_content( so_5::exceptions_enabled, chain ); std::cout << "joining." << std::flush; child.join(); std::cout << "OK" << std::endl; } int main() { try { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check( env.environment().create_mchain( p.second ) ); }, 20, p.first ); } } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/insert_after_close/000077500000000000000000000000001523065210100235635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/insert_after_close/CMakeLists.txt000066400000000000000000000001451523065210100263230ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.insert_after_close) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/insert_after_close/main.cpp000066400000000000000000000020231523065210100252100ustar00rootroot00000000000000/* * A test for insertion of a message into closed chain. */ #include #include #include int main() { try { run_with_time_limit( []() { struct hello : public so_5::signal_t {}; so_5::wrapped_env_t env; auto ch = create_mchain( env, std::chrono::seconds{45}, 2, so_5::mchain_props::memory_usage_t::dynamic, so_5::mchain_props::overflow_reaction_t::remove_oldest ); std::thread worker{ [ch] { so_5::send< hello >( ch ); so_5::send< hello >( ch ); // This send should be blocked. so_5::send< hello >( ch ); } }; while( 2u != ch->size() ) std::this_thread::sleep_for( std::chrono::milliseconds{25} ); close_drop_content( so_5::exceptions_enabled, ch ); worker.join(); UT_CHECK_CONDITION( 0u == ch->size() ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/limited_app_abort/000077500000000000000000000000001523065210100233675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/limited_app_abort/CMakeLists.txt000066400000000000000000000002031523065210100261220ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.limited_app_abort) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/limited_app_abort/main.cpp000066400000000000000000000012341523065210100250170ustar00rootroot00000000000000/* * Test for size-limited mchains with abort_app overflow reaction. */ #include #include #include using namespace std; using namespace chrono; namespace props = so_5::mchain_props; int main() { so_5::wrapped_env_t env; auto ch = env.environment().create_mchain( so_5::make_limited_without_waiting_mchain_params( 3, props::memory_usage_t::dynamic, props::overflow_reaction_t::abort_app ) ); so_5::send< int >( ch, 1 ); so_5::send< int >( ch, 2 ); so_5::send< int >( ch, 3 ); so_5::send< int >( ch, 4 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/limited_no_app_abort/000077500000000000000000000000001523065210100240635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/limited_no_app_abort/CMakeLists.txt000066400000000000000000000001471523065210100266250ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.limited_no_app_abort) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/limited_no_app_abort/main.cpp000066400000000000000000000170201523065210100255130ustar00rootroot00000000000000/* * Test for size-limited mchains without abort_app overflow reaction. */ #include #include #include #include using namespace std; using namespace chrono; namespace props = so_5::mchain_props; void do_check_no_wait_drop_newest_impl( so_5::environment_t & env, const char * case_name, props::memory_usage_t memory ) { cout << "no_wait, drop_newest, " << case_name << ": " << std::flush; auto ch = env.create_mchain( so_5::make_limited_without_waiting_mchain_params( 3, memory, props::overflow_reaction_t::drop_newest ) ); so_5::send< int >( ch, 1 ); so_5::send< int >( ch, 2 ); so_5::send< int >( ch, 3 ); so_5::send< int >( ch, 4 ); receive( from(ch).handle_n(4).empty_timeout(so_5::no_wait), []( int i ) { UT_CHECK_CONDITION( i < 4 ); } ); cout << "OK" << std::endl; } void do_check_no_wait_drop_newest( so_5::environment_t & env ) { do_check_no_wait_drop_newest_impl( env, "dynamic", props::memory_usage_t::dynamic ); do_check_no_wait_drop_newest_impl( env, "prealloc", props::memory_usage_t::preallocated ); } void do_check_no_wait_remove_oldest_impl( so_5::environment_t & env, const char * case_name, props::memory_usage_t memory ) { cout << "no_wait, remove_oldest, " << case_name << ": " << std::flush; auto ch = env.create_mchain( so_5::make_limited_without_waiting_mchain_params( 3, memory, props::overflow_reaction_t::remove_oldest ) ); so_5::send< int >( ch, 1 ); so_5::send< int >( ch, 2 ); so_5::send< int >( ch, 3 ); so_5::send< int >( ch, 4 ); receive( from(ch).handle_n(4).empty_timeout(so_5::no_wait), []( int i ) { UT_CHECK_CONDITION( i > 1 ); } ); cout << "OK" << std::endl; } void do_check_no_wait_remove_oldest( so_5::environment_t & env ) { do_check_no_wait_remove_oldest_impl( env, "dynamic", props::memory_usage_t::dynamic ); do_check_no_wait_remove_oldest_impl( env, "prealloc", props::memory_usage_t::preallocated ); } void do_check_no_wait_throw_exception_impl( so_5::environment_t & env, const char * case_name, props::memory_usage_t memory ) { cout << "no_wait, throw_exception, " << case_name << ": " << std::flush; auto ch = env.create_mchain( so_5::make_limited_without_waiting_mchain_params( 3, memory, props::overflow_reaction_t::throw_exception ) ); so_5::send< int >( ch, 1 ); so_5::send< int >( ch, 2 ); so_5::send< int >( ch, 3 ); try { so_5::send< int >( ch, 4 ); ensure_or_die( false, "An exception must be throw before this line!" ); } catch( const so_5::exception_t & ex ) { UT_CHECK_CONDITION( so_5::rc_msg_chain_overflow == ex.error_code() ); } cout << "OK" << std::endl; } void do_check_no_wait_throw_exception( so_5::environment_t & env ) { do_check_no_wait_throw_exception_impl( env, "dynamic", props::memory_usage_t::dynamic ); do_check_no_wait_throw_exception_impl( env, "prealloc", props::memory_usage_t::preallocated ); } void do_test_no_wait( bool use_tracer ) { const std::string env_desc = use_tracer ? "msg_tracing" : "no_msg_tracing"; cout << "test_no_wait + " << env_desc << ": " << std::endl; run_with_time_limit( [use_tracer] { so_5::environment_params_t params; if( use_tracer ) params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); so_5::wrapped_env_t env{ std::move(params) }; do_check_no_wait_drop_newest( env.environment() ); do_check_no_wait_remove_oldest( env.environment() ); do_check_no_wait_throw_exception( env.environment() ); }, 20, "test_no_wait + " + env_desc ); } UT_UNIT_TEST( test_no_wait ) { do_test_no_wait( false ); do_test_no_wait( true ); } const chrono::milliseconds wait_timeout{ 100 }; const chrono::milliseconds timeout_delta{ 20 }; template< typename L > void check_pause( L lambda ) { auto s = chrono::steady_clock::now(); lambda(); auto f = chrono::steady_clock::now(); UT_CHECK_CONDITION( s + (wait_timeout - timeout_delta) < f ); } void do_check_wait_drop_newest_impl( so_5::environment_t & env, const char * case_name, props::memory_usage_t memory ) { cout << "wait, drop_newest, " << case_name << ": " << std::flush; auto ch = env.create_mchain( so_5::make_limited_with_waiting_mchain_params( 3, memory, props::overflow_reaction_t::drop_newest, wait_timeout ) ); so_5::send< int >( ch, 1 ); so_5::send< int >( ch, 2 ); so_5::send< int >( ch, 3 ); check_pause( [&] { so_5::send< int >( ch, 4 ); } ); receive( from(ch).handle_n(4).empty_timeout(so_5::no_wait), []( int i ) { UT_CHECK_CONDITION( i < 4 ); } ); cout << "OK" << std::endl; } void do_check_wait_drop_newest( so_5::environment_t & env ) { do_check_wait_drop_newest_impl( env, "dynamic", props::memory_usage_t::dynamic ); do_check_wait_drop_newest_impl( env, "prealloc", props::memory_usage_t::preallocated ); } void do_check_wait_remove_oldest_impl( so_5::environment_t & env, const char * case_name, props::memory_usage_t memory ) { cout << "wait, remove_oldest, " << case_name << ": " << std::flush; auto ch = env.create_mchain( so_5::make_limited_with_waiting_mchain_params( 3, memory, props::overflow_reaction_t::remove_oldest, wait_timeout ) ); so_5::send< int >( ch, 1 ); so_5::send< int >( ch, 2 ); so_5::send< int >( ch, 3 ); check_pause( [&] { so_5::send< int >( ch, 4 ); } ); receive( from(ch).handle_n(4).empty_timeout(so_5::no_wait), []( int i ) { UT_CHECK_CONDITION( i > 1 ); } ); cout << "OK" << std::endl; } void do_check_wait_remove_oldest( so_5::environment_t & env ) { do_check_wait_remove_oldest_impl( env, "dynamic", props::memory_usage_t::dynamic ); do_check_wait_remove_oldest_impl( env, "prealloc", props::memory_usage_t::preallocated ); } void do_check_wait_throw_exception_impl( so_5::environment_t & env, const char * case_name, props::memory_usage_t memory ) { cout << "wait, throw_exception, " << case_name << ": " << std::flush; auto ch = env.create_mchain( so_5::make_limited_with_waiting_mchain_params( 3, memory, props::overflow_reaction_t::throw_exception, wait_timeout ) ); so_5::send< int >( ch, 1 ); so_5::send< int >( ch, 2 ); so_5::send< int >( ch, 3 ); check_pause( [&] { try { so_5::send< int >( ch, 4 ); ensure_or_die( false, "An exception must be throw before this line!" ); } catch( const so_5::exception_t & ex ) { UT_CHECK_CONDITION( so_5::rc_msg_chain_overflow == ex.error_code() ); } } ); cout << "OK" << std::endl; } void do_check_wait_throw_exception( so_5::environment_t & env ) { do_check_wait_throw_exception_impl( env, "dynamic", props::memory_usage_t::dynamic ); do_check_wait_throw_exception_impl( env, "prealloc", props::memory_usage_t::preallocated ); } void do_test_wait( bool use_tracer ) { const std::string env_desc = use_tracer ? "msg_tracing" : "no_msg_tracing"; cout << "test_wait + " << env_desc << ": " << std::endl; run_with_time_limit( [use_tracer] { so_5::environment_params_t params; if( use_tracer ) params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); so_5::wrapped_env_t env{ std::move(params) }; do_check_wait_drop_newest( env.environment() ); do_check_wait_remove_oldest( env.environment() ); do_check_wait_throw_exception( env.environment() ); }, 20, "test_wait + " + env_desc ); } UT_UNIT_TEST( test_wait ) { do_test_wait( false ); do_test_wait( true ); } int main() { UT_RUN_UNIT_TEST( test_no_wait ) UT_RUN_UNIT_TEST( test_wait ) return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/master_handle/000077500000000000000000000000001523065210100225175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/master_handle/CMakeLists.txt000066400000000000000000000001401523065210100252520ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.master_handle) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/master_handle/main.cpp000066400000000000000000000112401523065210100241450ustar00rootroot00000000000000/* * Test for mchain_master_handle_t class. */ #include #include #include "../mchain_params.hpp" using namespace std; void handle_unexpected( int i ) { throw runtime_error( "unexpected message: " + to_string(i) ); } void handle_expected( int i ) { if( i != 42 ) throw runtime_error( "unexpected value: " + to_string(i) ); } void check_drop_content1( const so_5::mchain_t & ch1 ) { { auto master = so_5::mchain_master_handle_t::with_drop_content( ch1 ); so_5::send< int >( *master, 0 ); } auto r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), receive_case( ch1, []( int i ) { handle_unexpected(i); } ) ); if( !r.was_closed() ) throw runtime_error( "r.was_closed() is not true" ); } void check_drop_content3( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2, const so_5::mchain_t & ch3 ) { { auto master1 = so_5::mchain_master_handle_t::with_drop_content( ch1 ); auto master2 = so_5::mchain_master_handle_t::with_drop_content( ch2 ); auto master3 = so_5::mchain_master_handle_t::with_drop_content( ch3 ); so_5::send< int >( *master1, 0 ); so_5::send< int >( *master2, 1 ); so_5::send< int >( *master3, 2 ); } auto r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), receive_case( ch1, []( int i ) { handle_unexpected(i); } ), receive_case( ch2, []( int i ) { handle_unexpected(i); } ), receive_case( ch3, []( int i ) { handle_unexpected(i); } ) ); if( !r.was_closed() ) throw runtime_error( "r.was_closed() is not true" ); } void check_retain_content1( const so_5::mchain_t & ch1 ) { { auto master = so_5::mchain_master_handle_t::with_retain_content( ch1 ); so_5::send< int >( *master, 42 ); so_5::send< int >( *master, 42 ); } auto r = so_5::select( so_5::from_all().handle_n(2), receive_case( ch1, []( int i ) { handle_expected(i); } ) ); if( 2 != r.handled() ) throw runtime_error( "unexpected count of handled messages: " + to_string( r.handled() ) ); r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), receive_case( ch1, []( int i ) { handle_unexpected(i); } ) ); if( !r.was_closed() ) throw runtime_error( "r.was_closed() is not true" ); } void check_retain_content3( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2, const so_5::mchain_t & ch3 ) { { auto master1 = so_5::mchain_master_handle_t::with_retain_content( ch1 ); auto master2 = so_5::mchain_master_handle_t::with_retain_content( ch2 ); auto master3 = so_5::mchain_master_handle_t::with_retain_content( ch3 ); so_5::send< int >( *master1, 42 ); so_5::send< int >( *master1, 42 ); so_5::send< int >( *master2, 42 ); so_5::send< int >( *master2, 42 ); so_5::send< int >( *master3, 42 ); so_5::send< int >( *master3, 42 ); } auto r = so_5::select( so_5::from_all().handle_n(6), receive_case( ch1, []( int i ) { handle_expected(i); } ), receive_case( ch2, []( int i ) { handle_expected(i); } ), receive_case( ch3, []( int i ) { handle_expected(i); } ) ); if( 6 != r.handled() ) throw runtime_error( "unexpected count of handled messages: " + to_string( r.handled() ) ); r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), receive_case( ch1, []( int i ) { handle_unexpected(i); } ), receive_case( ch2, []( int i ) { handle_unexpected(i); } ), receive_case( ch3, []( int i ) { handle_unexpected(i); } ) ); if( !r.was_closed() ) throw runtime_error( "r.was_closed() is not true" ); } void do_check( bool msg_tracing_enabled ) { run_with_time_limit( [msg_tracing_enabled]() { so_5::wrapped_env_t env{ []( so_5::environment_t & ) {}, [msg_tracing_enabled]( so_5::environment_params_t & params ) { if( msg_tracing_enabled ) params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } }; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; check_drop_content1( env.environment().create_mchain( p.second ) ); check_drop_content3( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); check_retain_content1( env.environment().create_mchain( p.second ) ); check_retain_content3( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); } }, 20 ); } int main() { try { do_check( false ); do_check( true ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/mchain_params.hpp000066400000000000000000000024141523065210100232250ustar00rootroot00000000000000#pragma once #include #include #include #include std::vector< std::pair< std::string, so_5::mchain_params_t > > build_mchain_params() { using namespace std; namespace props = so_5::mchain_props; vector< pair< string, so_5::mchain_params_t > > params; params.emplace_back( "unlimited", so_5::make_unlimited_mchain_params() ); params.emplace_back( "limited(dynamic,nowait)", so_5::make_limited_without_waiting_mchain_params( 5, props::memory_usage_t::dynamic, props::overflow_reaction_t::drop_newest ) ); params.emplace_back( "limited(preallocated,nowait)", so_5::make_limited_without_waiting_mchain_params( 5, props::memory_usage_t::preallocated, props::overflow_reaction_t::drop_newest ) ); params.emplace_back( "limited(dynamic,wait)", so_5::make_limited_with_waiting_mchain_params( 5, props::memory_usage_t::dynamic, props::overflow_reaction_t::drop_newest, chrono::milliseconds(200) ) ); params.emplace_back( "limited(preallocated,wait)", so_5::make_limited_with_waiting_mchain_params( 5, props::memory_usage_t::preallocated, props::overflow_reaction_t::drop_newest, chrono::milliseconds(200) ) ); return params; } sobjectizer-5.8.6/dev/test/so_5/mchain/multithread_receive/000077500000000000000000000000001523065210100237355ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/multithread_receive/CMakeLists.txt000066400000000000000000000001461523065210100264760ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.multithread_receive) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/multithread_receive/main.cpp000066400000000000000000000025711523065210100253720ustar00rootroot00000000000000/* * A simple test for calling receive for one mchain from several threads. */ #include #include #include #include "../mchain_params.hpp" using namespace std; void do_test( so_5::mchain_t & ch ) { struct hello {}; const size_t THREADS_COUNT = 4; atomic< int > started_threads{ 0 }; vector< thread > threads; threads.reserve( THREADS_COUNT ); for( size_t i = 0; i != THREADS_COUNT; ++i ) threads.emplace_back( thread{ [&ch, &started_threads] { ++started_threads; receive( from(ch).handle_n(1), []( hello ){} ); } } ); while( THREADS_COUNT != started_threads ) this_thread::yield(); for( size_t i = 0; i != THREADS_COUNT; ++i ) so_5::send< hello >( ch ); for( size_t i = 0; i != THREADS_COUNT; ++i ) threads[ i ].join(); } int main() { try { run_with_time_limit( []() { struct hello : public so_5::signal_t {}; so_5::wrapped_env_t env; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; auto chain = env.environment().create_mchain( p.second ); do_test( chain ); } }, 20, "multithread receive for mchain" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/multithread_receive_close/000077500000000000000000000000001523065210100251225ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/multithread_receive_close/CMakeLists.txt000066400000000000000000000001541523065210100276620ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.multithread_receive_close) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/multithread_receive_close/main.cpp000066400000000000000000000043041523065210100265530ustar00rootroot00000000000000/* * A simple test for calling close on mchain when several threads are * sleeping on it. */ #include #include #include #include "../mchain_params.hpp" using namespace std; void do_test_case_1( so_5::mchain_t ch ) { struct hello {}; const size_t THREADS_COUNT = 4; atomic< int > started_threads{ 0 }; vector< thread > threads; threads.reserve( THREADS_COUNT ); for( size_t i = 0; i != THREADS_COUNT; ++i ) threads.emplace_back( thread{ [&ch, &started_threads] { ++started_threads; receive( from(ch).handle_n(1), []( hello ){} ); } } ); while( THREADS_COUNT != started_threads ) this_thread::yield(); for( size_t i = 0; i != THREADS_COUNT / 2; ++i ) so_5::send< hello >( ch ); close_retain_content( so_5::exceptions_enabled, ch ); for( size_t i = 0; i != THREADS_COUNT; ++i ) threads[ i ].join(); } void do_test_case_2( so_5::mchain_t ch ) { struct hello {}; const size_t THREADS_COUNT = 4; atomic< int > started_threads{ 0 }; vector< thread > threads; threads.reserve( THREADS_COUNT ); for( size_t i = 0; i != THREADS_COUNT; ++i ) threads.emplace_back( thread{ [&ch, &started_threads] { ++started_threads; receive( from(ch).handle_n(1), []( hello ){} ); } } ); while( THREADS_COUNT != started_threads ) this_thread::yield(); close_retain_content( so_5::exceptions_enabled, ch ); for( size_t i = 0; i != THREADS_COUNT; ++i ) threads[ i ].join(); } int main() { try { run_with_time_limit( []() { struct hello : public so_5::signal_t {}; so_5::wrapped_env_t env; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; cout << "case_1: " << flush; do_test_case_1( env.environment().create_mchain( p.second ) ); cout << "OK" << endl; cout << "case_2: " << flush; do_test_case_2( env.environment().create_mchain( p.second ) ); cout << "OK" << endl; } }, 20, "multithread receive for closed mchain" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/not_empty_notify/000077500000000000000000000000001523065210100233175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/not_empty_notify/CMakeLists.txt000066400000000000000000000001431523065210100260550ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.not_empty_notify) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/not_empty_notify/main.cpp000066400000000000000000000032741523065210100247550ustar00rootroot00000000000000/* * Test for custom 'not_empty' notificator for mchain. */ #include #include #include #include "../mchain_params.hpp" using namespace std; class event { std::mutex m; std::condition_variable cv; bool signaled = { false }; public : void notify_one() { std::lock_guard< std::mutex > l{ m }; signaled = true; cv.notify_one(); } void wait() { std::unique_lock< std::mutex > l{ m }; if( !signaled ) cv.wait( l, [this]{ return signaled; } ); signaled = false; } }; void do_check( so_5::environment_t & env, so_5::mchain_params_t params ) { event child_started; event chain_not_empty; params.not_empty_notificator( [&chain_not_empty] { chain_not_empty.notify_one(); } ); auto ch = env.create_mchain( params ); std::thread child{ [&] { child_started.notify_one(); chain_not_empty.wait(); receive( from(ch).handle_n(1), []( int i ) { UT_CHECK_CONDITION( i != 0 ); } ); } }; std::cout << "child starting." << std::flush; child_started.wait(); std::cout << "sending." << std::flush; so_5::send< int >( ch, 1 ); std::cout << "joining." << std::flush; child.join(); std::cout << "OK" << std::endl; } int main() { try { auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; run_with_time_limit( [&p]() { so_5::wrapped_env_t env; do_check( env.environment(), p.second ); }, 20, p.first ); } } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/params_clone/000077500000000000000000000000001523065210100223545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/params_clone/CMakeLists.txt000066400000000000000000000001371523065210100251150ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.params_clone) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/params_clone/main.cpp000066400000000000000000000045131523065210100240070ustar00rootroot00000000000000/* * A simple test for mchain. */ #include #include #include using namespace std; using namespace std::string_literals; template< typename A, typename B > decltype(auto) ensure_same_pointers( A && a, const B & b, std::string description ) { ensure_or_die( &a == &b, description ); return std::forward(a); } template< typename A, typename B > decltype(auto) ensure_different_pointers( A && a, const B & b, std::string description ) { const auto void_ptr = [](const auto & v) { return static_cast(&v); }; ensure_or_die( void_ptr(a) != void_ptr(b), description ); return std::forward(a); } void check_receive_params( so_5::environment_t & env ) { auto ch = so_5::create_mchain( env ); auto p1 = so_5::from( ch ); ensure_same_pointers( p1.no_wait_on_empty(), p1, "receive: no_wait_on_empty() should return the same object"s ); auto p2 = ensure_different_pointers( p1.handle_n(1), p1, "receive: first call to handle_n() should create a new object"s ); ensure_same_pointers( p2.extract_n(1), p2, "receive: repeated call to extract_n() should return the same object" ); ensure_same_pointers( p2.handle_all(), p2, "receive: repeated call to handle_all() should return the same object" ); ensure_same_pointers( p2.empty_timeout( std::chrono::milliseconds(200) ), p2, "receive: empty_timeout() should return the same object" ); } void check_select_params() { auto p1 = so_5::from_all(); ensure_same_pointers( p1.no_wait_on_empty(), p1, "select: no_wait_on_empty() should return the same object"s ); auto p2 = ensure_different_pointers( p1.handle_n(1), p1, "select: first call to handle_n() should create a new object"s ); ensure_same_pointers( p2.extract_n(1), p2, "select: repeated call to extract_n() should return the same object" ); ensure_same_pointers( p2.handle_all(), p2, "select: repeated call to handle_all() should return the same object" ); ensure_same_pointers( p2.empty_timeout( std::chrono::milliseconds(200) ), p2, "select: empty_timeout() should return the same object" ); } int main() { run_with_time_limit( []() { so_5::wrapped_env_t env; check_receive_params( env.environment() ); check_select_params(); }, 20 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/prepared_select_simple/000077500000000000000000000000001523065210100244235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/prepared_select_simple/CMakeLists.txt000066400000000000000000000001511523065210100271600ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.prepared_select_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/prepared_select_simple/main.cpp000066400000000000000000000026401523065210100260550ustar00rootroot00000000000000/* * A simple test for simple prepared select on several mchains. */ #include #include #include #include "../mchain_params.hpp" using namespace std; void do_test() { struct hello {}; so_5::wrapped_env_t env; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; auto ch1 = env.environment().create_mchain( p.second ); auto ch2 = env.environment().create_mchain( p.second ); auto ch3 = env.environment().create_mchain( p.second ); bool hello_received = false; so_5::send< hello >( ch2 ); auto r = select( so_5::prepare_select( so_5::from_all().extract_n( 1 ).no_wait_on_empty(), receive_case( ch1, []( hello ) { throw std::runtime_error( "hello from ch1!" ); } ), receive_case( ch2, [&hello_received]( hello ) { hello_received = true; } ), receive_case( ch3, []( hello ) { throw std::runtime_error( "hello from ch3!" ); } ) ) ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( hello_received ); } } int main() { try { run_with_time_limit( do_test, 20, "test for simple multi chain select" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/receive_closed_handler/000077500000000000000000000000001523065210100243615ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/receive_closed_handler/CMakeLists.txt000066400000000000000000000001511523065210100271160ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.receive_closed_handler) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/receive_closed_handler/main.cpp000066400000000000000000000032371523065210100260160ustar00rootroot00000000000000/* * A simple test for closed_handler on receive operation. */ #include #include #include "../mchain_params.hpp" using namespace std; void check_op( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2 ) { thread second_thread( [&] { bool must_continue = true; do { receive( from( ch1 ).handle_all().on_close( [&must_continue]( const so_5::mchain_t & ) { must_continue = false; } ), [&ch2]( int v ) { so_5::send< int >( ch2, v+1 ); } ); } while( must_continue ); } ); auto second_thread_joiner = so_5::auto_join( second_thread ); so_5::send< int >( ch1, 0 ); receive( from( ch2 ).handle_n(3), [&ch1]( int v ) { cout << "received: " << v << endl; so_5::send< int >( ch1, v+1 ); } ); close_retain_content( so_5::exceptions_enabled, ch1 ); } void do_check( bool msg_tracing_enabled ) { run_with_time_limit( [msg_tracing_enabled]() { so_5::wrapped_env_t env{ []( so_5::environment_t & ) {}, [msg_tracing_enabled]( so_5::environment_params_t & params ) { if( msg_tracing_enabled ) params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } }; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; check_op( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); } }, 20, "close_chain" ); } int main() { try { do_check( false ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/select_closed_handler/000077500000000000000000000000001523065210100242165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/select_closed_handler/CMakeLists.txt000066400000000000000000000001501523065210100267520ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.select_closed_handler) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/select_closed_handler/main.cpp000066400000000000000000000045541523065210100256560ustar00rootroot00000000000000/* * A simple test for closed_handler on select operation. */ #include #include #include #include "../mchain_params.hpp" using namespace std; void check_op( const so_5::mchain_t & ch1, const so_5::mchain_t & ch2, const so_5::mchain_t & ch3 ) { thread second_thread( [&] { bool must_continue = true; size_t chains_closed = 0; do { so_5::select( so_5::from_all().handle_all().on_close( [&must_continue, &chains_closed]( const so_5::mchain_t & ch ) { ++chains_closed; cout << "[" << ch->id() << "] closed (" << chains_closed << ")" << endl; if( 2 == chains_closed ) must_continue = false; } ), receive_case( ch1, [&ch1]( int v ) { cout << "[" << ch1->id() << "]: received " << v << endl; } ), receive_case( ch2, [&ch2]( int v ) { cout << "[" << ch2->id() << "]: received " << v << endl; } ) ); } while( must_continue ); so_5::send< size_t >( ch3, chains_closed ); } ); auto second_thread_joiner = so_5::auto_join( second_thread ); so_5::send< int >( ch1, 1 ); so_5::send< int >( ch2, 2 ); close_retain_content( so_5::exceptions_enabled, ch1 ); close_retain_content( so_5::exceptions_enabled, ch2 ); receive( from(ch3).handle_n(1), []( size_t chains_closed ) { cout << "total chains closed: " << chains_closed << endl; ensure_or_die( 2u == chains_closed, "expected value for chains_closed is 2" ); } ); } void do_check( bool msg_tracing_enabled ) { run_with_time_limit( [msg_tracing_enabled]() { so_5::wrapped_env_t env{ []( so_5::environment_t & ) {}, [msg_tracing_enabled]( so_5::environment_params_t & params ) { if( msg_tracing_enabled ) params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } }; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; check_op( env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ), env.environment().create_mchain( p.second ) ); } }, 20, "do_check" ); } int main() { try { do_check( false ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/select_count_messages/000077500000000000000000000000001523065210100242675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/select_count_messages/CMakeLists.txt000066400000000000000000000001501523065210100270230ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.select_count_messages) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/select_count_messages/main.cpp000066400000000000000000000055151523065210100257250ustar00rootroot00000000000000/* * A simple test for simple select on several mchains. */ #include #include #include #include #include "../mchain_params.hpp" using namespace std; void worker_thread( so_5::mchain_t ch, unsigned int values ) { for( unsigned int i = 0; i != values; ++i ) { so_5::send< unsigned int >( ch, i ); } close_retain_content( so_5::exceptions_enabled, ch ); } void try_accept_value( const char * marker, unsigned int value, unsigned int & counter, unsigned int & expected_value ) { if( value != expected_value ) throw runtime_error( marker + string( ": unexpected value, actual: " ) + to_string( value ) + ", expected: " + to_string( expected_value ) ); expected_value += 1; counter += 1; } so_5::mchain_params_t mchain_params() { return so_5::make_limited_with_waiting_mchain_params( 10, so_5::mchain_props::memory_usage_t::dynamic, so_5::mchain_props::overflow_reaction_t::drop_newest, chrono::seconds(2) ); } int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env; vector< unsigned int > counters = { 0, 0, 0 }; vector< unsigned int > expected_values = { 0, 0, 0 }; auto ch1 = env.environment().create_mchain( mchain_params() ); auto ch2 = env.environment().create_mchain( mchain_params() ); auto ch3 = env.environment().create_mchain( mchain_params() ); thread w1{ [ch1]{ worker_thread( ch1, 20 ); } }; thread w2{ [ch2]{ worker_thread( ch2, 30 ); } }; thread w3{ [ch3]{ worker_thread( ch3, 40 ); } }; unsigned int total_values = 0; while( true ) { auto r = so_5::select( so_5::from_all().handle_n(1), receive_case( ch1, [&]( unsigned int v ) { try_accept_value( "ch1", v, counters[ 0 ], expected_values[ 0 ] ); ++total_values; } ), receive_case( ch2, [&]( unsigned int v ) { try_accept_value( "ch2", v, counters[ 1 ], expected_values[ 1 ] ); ++total_values; } ), receive_case( ch3, [&]( unsigned int v ) { try_accept_value( "ch3", v, counters[ 2 ], expected_values[ 2 ] ); ++total_values; } ) ); if( !r.was_handled() ) break; } ensure_or_die( 90 == total_values, "total values must be 90" ); ensure_or_die( 20 == counters[ 0 ], "20 messages from ch1" ); ensure_or_die( 30 == counters[ 1 ], "30 messages from ch2" ); ensure_or_die( 40 == counters[ 2 ], "40 messages from ch3" ); w1.join(); w2.join(); w3.join(); }, 20, "count messages" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_close/000077500000000000000000000000001523065210100240615ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_close/CMakeLists.txt000066400000000000000000000001471523065210100266230ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.select_mthread_close) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_close/main.cpp000066400000000000000000000027101523065210100255110ustar00rootroot00000000000000/* * Test for closing mchain which is used in different thread in * different selects. */ #include #include #include #include #include "../mchain_params.hpp" using namespace std; struct ready {}; void worker_thread( so_5::mchain_t ready_ch, so_5::mchain_t test_ch ) { so_5::send< ready >( ready_ch ); so_5::select( so_5::from_all().handle_n(1), receive_case( test_ch, [](int) {} ) ); } so_5::mchain_params_t mchain_params() { return so_5::make_unlimited_mchain_params(); } int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env; auto ready_ch = env.environment().create_mchain( mchain_params() ); auto test_ch = env.environment().create_mchain( mchain_params() ); thread w1{ [ready_ch, test_ch]{ worker_thread( ready_ch, test_ch ); } }; thread w2{ [ready_ch, test_ch]{ worker_thread( ready_ch, test_ch ); } }; thread w3{ [ready_ch, test_ch]{ worker_thread( ready_ch, test_ch ); } }; receive( from( ready_ch ).handle_n( 3 ), []( ready ) {} ); this_thread::sleep_for( chrono::milliseconds(500) ); close_drop_content( so_5::exceptions_enabled, test_ch ); w1.join(); w2.join(); w3.join(); }, 20, "close mchain (select in different threads)" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_close2/000077500000000000000000000000001523065210100241435ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_close2/CMakeLists.txt000066400000000000000000000001501523065210100266770ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.select_mthread_close2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_close2/main.cpp000066400000000000000000000047111523065210100255760ustar00rootroot00000000000000/* * Test for closing mchain which is used in different thread in * different selects. * * If `select` waits on send_case than closing the chain from * receive_case doesn't break the `select`. */ #include #include #include #include #include "../mchain_params.hpp" using namespace std; struct ready {}; struct finished {}; void worker_thread( so_5::mchain_t ready_ch, so_5::mchain_t in_ch, so_5::mchain_t out_ch) { so_5::send< ready >( ready_ch ); so_5::select( so_5::from_all().handle_n(1), send_case( out_ch, so_5::message_holder_t::make(0), []() {}), receive_case( in_ch, [](int) {} ) ); so_5::send< finished >( ready_ch ); } so_5::mchain_params_t mchain_params() { return so_5::make_unlimited_mchain_params(); } int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env; auto ready_ch = env.environment().create_mchain( mchain_params() ); auto out_ch = so_5::create_mchain(env, std::chrono::seconds{ 1 }, 1, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app); auto in_ch = env.environment().create_mchain( mchain_params() ); // out_ch has to be full to avoid send_case(). so_5::send(out_ch, 0); thread w1{ worker_thread, ready_ch, in_ch, out_ch }; thread w2{ worker_thread, ready_ch, in_ch, out_ch }; thread w3{ worker_thread, ready_ch, in_ch, out_ch }; receive( from( ready_ch ).handle_n( 3 ), []( ready ) {} ); this_thread::sleep_for( chrono::milliseconds(500) ); close_drop_content( so_5::exceptions_enabled, in_ch ); // We shouldn't receive any `finished` signals here. const auto r1 = receive( from( ready_ch ) .handle_n( 3 ) .total_time( std::chrono::milliseconds{ 300 } ), []( finished ) {} ); ensure_or_die(0 == r1.handled(), "no `finished` singals has to be received"); close_drop_content( so_5::exceptions_enabled, out_ch ); const auto r2 = receive( from( ready_ch ).handle_n( 3 ), []( finished ) {} ); ensure_or_die(3 == r2.handled(), "all `finished` singals have to be received"); w1.join(); w2.join(); w3.join(); }, 2, "close mchain (select in different threads)" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_close3/000077500000000000000000000000001523065210100241445ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_close3/CMakeLists.txt000066400000000000000000000001501523065210100267000ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.select_mthread_close3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_close3/main.cpp000066400000000000000000000036671523065210100256100ustar00rootroot00000000000000/* * Test for closing mchain which is used in different thread in * different selects. * * If `select` waits on send_case than closing the chain from * receive_case doesn't break the `select`. */ #include #include #include #include #include "../mchain_params.hpp" using namespace std; struct ready {}; void worker_thread( so_5::mchain_t ready_ch, so_5::mchain_t in_ch, so_5::mchain_t out_ch) { so_5::send< ready >( ready_ch ); bool close_detected = false; so_5::select( so_5::from_all() .handle_n(1) .on_close([&close_detected](const so_5::mchain_t &) { close_detected = true; }) .stop_on([&close_detected]() { return close_detected; }), send_case( out_ch, so_5::message_holder_t::make(0), []() {}), receive_case( in_ch, [](int) {} ) ); } so_5::mchain_params_t mchain_params() { return so_5::make_unlimited_mchain_params(); } int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env; auto ready_ch = env.environment().create_mchain( mchain_params() ); auto out_ch = so_5::create_mchain(env, std::chrono::seconds{ 1 }, 1, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app); auto in_ch = env.environment().create_mchain( mchain_params() ); // out_ch has to be full to avoid send_case(). so_5::send(out_ch, 0); thread w1{ worker_thread, ready_ch, in_ch, out_ch }; thread w2{ worker_thread, ready_ch, in_ch, out_ch }; thread w3{ worker_thread, ready_ch, in_ch, out_ch }; close_drop_content( so_5::exceptions_enabled, in_ch ); w1.join(); w2.join(); w3.join(); }, 2, "close mchain (select in different threads)" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_read/000077500000000000000000000000001523065210100236675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_read/CMakeLists.txt000066400000000000000000000001461523065210100264300ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.select_mthread_read) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/select_mthread_read/main.cpp000066400000000000000000000041341523065210100253210ustar00rootroot00000000000000/* * Test for reading from chain from different threads. */ #include #include #include #include #include "../mchain_params.hpp" using namespace std; struct ready {}; struct done {}; struct worker_result { int m_id; unsigned int m_values; unsigned int m_sum; }; void worker_thread( int id, so_5::mchain_t control_ch, so_5::mchain_t test_ch ) { worker_result r{ id, 0u, 0u }; so_5::send< ready >( control_ch ); bool finish = false; while( !finish ) so_5::select( so_5::from_all().handle_n(1), receive_case( test_ch, [&r]( unsigned int v ) { ++r.m_values; r.m_sum += v; }, [&finish]( done ) { finish = true; } ) ); so_5::send< worker_result >( control_ch, r ); } so_5::mchain_params_t mchain_params() { return so_5::make_unlimited_mchain_params(); } int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env; auto control_ch = env.environment().create_mchain( mchain_params() ); auto test_ch = env.environment().create_mchain( mchain_params() ); thread w1{ [control_ch, test_ch]{ worker_thread( 0, control_ch, test_ch ); } }; thread w2{ [control_ch, test_ch]{ worker_thread( 1, control_ch, test_ch ); } }; thread w3{ [control_ch, test_ch]{ worker_thread( 3, control_ch, test_ch ); } }; receive( from( control_ch ).handle_n( 3 ), []( ready ) {} ); for( unsigned int i = 0; i != 1000; ++i ) so_5::send< unsigned int >( test_ch, i ); so_5::send< done >( test_ch ); so_5::send< done >( test_ch ); so_5::send< done >( test_ch ); receive( from( control_ch ).handle_n( 3 ), []( const worker_result & r ) { cout << r.m_id << " -> " << r.m_values << " {" << r.m_sum << "}" << endl; } ); w1.join(); w2.join(); w3.join(); }, 20, "read mchain (select in different threads)" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/select_send_simple/000077500000000000000000000000001523065210100235525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/select_send_simple/CMakeLists.txt000066400000000000000000000001451523065210100263120ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.select_send_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/select_send_simple/main.cpp000066400000000000000000000262161523065210100252110ustar00rootroot00000000000000/* * A simple test for simple select with send_case. */ #include #include #include using namespace std::chrono_literals; UT_UNIT_TEST( simple_failed_send_attempt ) { run_with_time_limit( []() { struct hello {}; so_5::wrapped_env_t env; auto ch = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch ); so_5::send< hello >( ch ); bool send_successed = false; auto r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), send_case( ch, so_5::message_holder_t< hello >::make(), [&send_successed] { send_successed = true; } ) ); UT_CHECK_CONDITION( !r.was_sent() ); UT_CHECK_CONDITION( !send_successed ); }, 5 ); } UT_UNIT_TEST( simple_failed_send_attempt_2 ) { run_with_time_limit( []() { struct hello {}; so_5::wrapped_env_t env; auto ch = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch ); so_5::send< hello >( ch ); bool send_successed = false; const auto timeout = 250ms; const auto started_at = std::chrono::steady_clock::now(); auto r = so_5::select( so_5::from_all().handle_n(1).total_time(timeout), send_case( ch, so_5::message_holder_t< hello >::make(), [&send_successed] { send_successed = true; } ) ); const auto finished_at = std::chrono::steady_clock::now(); UT_CHECK_CONDITION( !r.was_sent() ); UT_CHECK_CONDITION( !send_successed ); std::cout << "time spent: " << std::chrono::duration_cast< std::chrono::milliseconds >( finished_at - started_at ) .count() << std::endl; UT_CHECK_CONDITION( timeout <= finished_at - started_at ); }, 5 ); } UT_UNIT_TEST( simple_success_send_attempt ) { run_with_time_limit( []() { struct hello {}; so_5::wrapped_env_t env; auto ch = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch ); bool send_successed = false; auto r = so_5::select( so_5::from_all().handle_n(1).no_wait_on_empty(), send_case( ch, so_5::message_holder_t< hello >::make(), [&send_successed] { send_successed = true; } ) ); UT_CHECK_CONDITION( r.was_sent() ); UT_CHECK_CONDITION( send_successed ); UT_CHECK_CONDITION( 2u == ch->size() ); }, 5 ); } void send_with_receive_test_case() { struct hello {}; so_5::wrapped_env_t env; auto ch1 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch1 ); so_5::send< hello >( ch1 ); auto ch2 = so_5::create_mchain( env.environment() ); so_5::send_delayed< hello >( ch2, 250ms ); bool send_successed = false; auto r = so_5::select( so_5::from_all().handle_n(2), send_case( ch1, so_5::message_holder_t< hello >::make(), [&send_successed] { send_successed = true; } ), receive_case( ch2, [&ch1](hello) { // Make free space in ch1. so_5::receive( so_5::from(ch1).handle_n(1).no_wait_on_empty(), [](hello) {} ); } ) ); UT_CHECK_CONDITION( r.was_handled() ); UT_CHECK_CONDITION( r.was_sent() ); UT_CHECK_CONDITION( send_successed ); UT_CHECK_CONDITION( 2u == ch1->size() ); } UT_UNIT_TEST( send_with_receive ) { run_with_time_limit( send_with_receive_test_case, 5 ); } void two_sends_with_receive_test_case() { struct hello {}; so_5::wrapped_env_t env; auto ch1 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch1 ); so_5::send< hello >( ch1 ); auto ch2 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch2 ); so_5::send< hello >( ch2 ); auto ch3 = so_5::create_mchain( env.environment() ); so_5::send_delayed< hello >( ch3, 250ms ); bool send_successed = false; auto r = so_5::select( so_5::from_all().handle_n(2), send_case( ch1, so_5::message_holder_t< hello >::make(), [&send_successed] { send_successed = true; } ), send_case( ch2, so_5::message_holder_t< hello >::make(), [](){} ), receive_case( ch3, [&ch1](hello) { // Make free space in ch1. so_5::receive( so_5::from(ch1).handle_n(1).no_wait_on_empty(), [](hello) {} ); } ) ); UT_CHECK_CONDITION( r.was_handled() ); UT_CHECK_CONDITION( r.was_sent() ); UT_CHECK_CONDITION( send_successed ); UT_CHECK_CONDITION( 2u == ch1->size() ); UT_CHECK_CONDITION( 2u == ch2->size() ); } UT_UNIT_TEST( two_sends_with_receive ) { run_with_time_limit( two_sends_with_receive_test_case, 5 ); } UT_UNIT_TEST( three_sends ) { run_with_time_limit( []() { struct hello {}; so_5::wrapped_env_t env; auto ch1 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch1 ); so_5::send< hello >( ch1 ); auto ch2 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch2 ); so_5::send< hello >( ch2 ); auto ch3 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch3 ); int used_chain = 0; auto r = so_5::select( so_5::from_all().handle_n(1), send_case( ch1, so_5::message_holder_t< hello >::make(), [&used_chain] { used_chain = 1; } ), send_case( ch2, so_5::message_holder_t< hello >::make(), [&used_chain] { used_chain = 2; } ), send_case( ch3, so_5::message_holder_t< hello >::make(), [&used_chain] { used_chain = 3; } ) ); UT_CHECK_CONDITION( !r.was_handled() ); UT_CHECK_CONDITION( r.was_sent() ); UT_CHECK_CONDITION( 3 == used_chain ); UT_CHECK_CONDITION( 2u == ch1->size() ); UT_CHECK_CONDITION( 2u == ch2->size() ); UT_CHECK_CONDITION( 2u == ch3->size() ); }, 5 ); } void three_sends_2_test_case() { struct hello {}; so_5::wrapped_env_t env; auto ch1 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch1 ); so_5::send< hello >( ch1 ); auto ch2 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch2 ); so_5::send< hello >( ch2 ); auto ch3 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch3 ); so_5::send< hello >( ch3 ); std::promise< void > reader_started; std::thread reader_thread{ [ch1, ch2, ch3, &reader_started] { reader_started.set_value(); std::this_thread::sleep_for(250ms); receive( from(ch1).handle_n(1), [](hello){} ); std::this_thread::sleep_for(50ms); receive( from(ch2).handle_n(1), [](hello){} ); std::this_thread::sleep_for(50ms); receive( from(ch3).handle_n(1), [](hello){} ); } }; auto reader_joiner = so_5::auto_join( reader_thread ); reader_started.get_future().get(); auto r = so_5::select( so_5::from_all().handle_n(3), send_case( ch1, so_5::message_holder_t< hello >::make(), [](){ std::cout << "send to ch1" << std::endl; } ), send_case( ch2, so_5::message_holder_t< hello >::make(), [](){ std::cout << "send to ch2" << std::endl; } ), send_case( ch3, so_5::message_holder_t< hello >::make(), [](){ std::cout << "send to ch3" << std::endl; } ) ); UT_CHECK_CONDITION( !r.was_handled() ); UT_CHECK_CONDITION( r.was_sent() ); UT_CHECK_CONDITION( 3u == r.sent() ); UT_CHECK_CONDITION( 2u == ch1->size() ); UT_CHECK_CONDITION( 2u == ch2->size() ); UT_CHECK_CONDITION( 2u == ch3->size() ); } UT_UNIT_TEST( three_sends_2 ) { run_with_time_limit( three_sends_2_test_case, 5 ); } void send_when_closed_drop_content_test_case() { struct hello {}; so_5::wrapped_env_t env; auto ch1 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch1 ); so_5::send< hello >( ch1 ); auto ch2 = so_5::create_mchain( env.environment() ); so_5::send_delayed< hello >( ch2, 250ms ); bool send_successed = false; auto r = so_5::select( so_5::from_all().handle_all(), send_case( ch1, so_5::message_holder_t< hello >::make(), [&send_successed] { send_successed = true; } ), receive_case( ch2, [&ch1, &ch2](hello) { so_5::close_drop_content( so_5::exceptions_enabled, ch1 ); so_5::close_drop_content( so_5::exceptions_enabled, ch2 ); } ) ); UT_CHECK_CONDITION( r.was_handled() ); UT_CHECK_CONDITION( !r.was_sent() ); UT_CHECK_CONDITION( !send_successed ); } UT_UNIT_TEST( send_when_closed_drop_content ) { run_with_time_limit( send_when_closed_drop_content_test_case, 5 ); } void send_when_closed_retain_content_test_case() { struct hello {}; so_5::wrapped_env_t env; auto ch1 = so_5::create_mchain( env.environment(), 2, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::abort_app ); so_5::send< hello >( ch1 ); so_5::send< hello >( ch1 ); auto ch2 = so_5::create_mchain( env.environment() ); so_5::send_delayed< hello >( ch2, 250ms ); bool send_successed = false; auto r = so_5::select( so_5::from_all().handle_all(), send_case( ch1, so_5::message_holder_t< hello >::make(), [&send_successed] { send_successed = true; } ), receive_case( ch2, [&ch1, &ch2](hello) { so_5::close_retain_content( so_5::exceptions_enabled, ch1 ); so_5::close_retain_content( so_5::exceptions_enabled, ch2 ); } ) ); UT_CHECK_CONDITION( r.was_handled() ); UT_CHECK_CONDITION( !r.was_sent() ); UT_CHECK_CONDITION( !send_successed ); } UT_UNIT_TEST( send_when_closed_retain_content ) { run_with_time_limit( send_when_closed_retain_content_test_case, 5 ); } int main() { UT_RUN_UNIT_TEST( simple_failed_send_attempt ) UT_RUN_UNIT_TEST( simple_failed_send_attempt_2 ) UT_RUN_UNIT_TEST( simple_success_send_attempt ) UT_RUN_UNIT_TEST( send_with_receive ) UT_RUN_UNIT_TEST( two_sends_with_receive ) UT_RUN_UNIT_TEST( three_sends ) UT_RUN_UNIT_TEST( three_sends_2 ) UT_RUN_UNIT_TEST( send_when_closed_drop_content ) UT_RUN_UNIT_TEST( send_when_closed_retain_content ) return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/select_simple/000077500000000000000000000000001523065210100225415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/select_simple/CMakeLists.txt000066400000000000000000000001401523065210100252740ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.select_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/select_simple/main.cpp000066400000000000000000000025531523065210100241760ustar00rootroot00000000000000/* * A simple test for simple select on several mchains. */ #include #include #include #include "../mchain_params.hpp" using namespace std; void do_test() { struct hello {}; so_5::wrapped_env_t env; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; auto ch1 = env.environment().create_mchain( p.second ); auto ch2 = env.environment().create_mchain( p.second ); auto ch3 = env.environment().create_mchain( p.second ); bool hello_received = false; so_5::send< hello >( ch2 ); auto r = so_5::select( so_5::from_all().handle_n(1), receive_case( ch1, []( hello ) { throw std::runtime_error( "hello from ch1!" ); } ), receive_case( ch2, [&hello_received]( hello ) { hello_received = true; } ), receive_case( ch3, []( hello ) { throw std::runtime_error( "hello from ch3!" ); } ) ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( hello_received ); } } int main() { try { run_with_time_limit( do_test, 20, "test for simple multi chain select" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/select_simple_close/000077500000000000000000000000001523065210100237265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/select_simple_close/CMakeLists.txt000066400000000000000000000001461523065210100264670ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.select_simple_close) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/select_simple_close/main.cpp000066400000000000000000000031671523065210100253650ustar00rootroot00000000000000/* * A simple test for simple select on several closed mchains. */ #include #include #include #include "../mchain_params.hpp" using namespace std; void do_test() { struct second_started {}; so_5::wrapped_env_t env; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; auto ch1 = env.environment().create_mchain( p.second ); auto ch2 = env.environment().create_mchain( p.second ); auto ch3 = env.environment().create_mchain( p.second ); auto ready = env.environment().create_mchain( so_5::make_unlimited_mchain_params() ); std::thread second{ [ch1, ch2, ch3, ready]{ so_5::send< second_started >( ready ); so_5::select( so_5::from_all().handle_n(1), receive_case( ch1, [](int) {} ), receive_case( ch2, [](int) {} ), receive_case( ch3, [](int) {} ) ); } }; receive( from(ready).handle_n(1), [&ch1, &ch2, &ch3]( second_started ) { std::cout << "second thread started, closing chains..." << std::endl; std::this_thread::sleep_for( std::chrono::milliseconds(200) ); close_retain_content( so_5::exceptions_enabled, ch1 ); close_retain_content( so_5::exceptions_enabled, ch2 ); close_retain_content( so_5::exceptions_enabled, ch3 ); } ); second.join(); } } int main() { try { run_with_time_limit( do_test, 60, "test for simple multi chain select on closed chains" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/simple/000077500000000000000000000000001523065210100212025ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/simple/CMakeLists.txt000066400000000000000000000001311523065210100237350ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/simple/main.cpp000066400000000000000000000030671523065210100226400ustar00rootroot00000000000000/* * A simple test for mchain. */ #include #include #include #include "../mchain_params.hpp" using namespace std; int main() { try { run_with_time_limit( []() { struct hello : public so_5::signal_t {}; so_5::wrapped_env_t env; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; auto chain = env.environment().create_mchain( p.second ); bool hello_received = false; so_5::send< int >( chain, 42 ); so_5::send< hello >( chain ); auto r = receive( from(chain).handle_n(1).no_wait_on_empty(), []( int i ) { if( 42 != i ) throw runtime_error( "unexpected int-message: " + to_string( i ) ); }, []( const std::string & s ) { throw runtime_error( "unexpected string msg: " + s ); }, [&hello_received](so_5::mhood_t< hello >) { hello_received = true; } ); UT_CHECK_CONDITION( 1 == r.extracted() ); UT_CHECK_CONDITION( 1 == r.handled() ); UT_CHECK_CONDITION( !hello_received ); const auto [extracted, handled, status] = r; UT_CHECK_CONDITION(extracted == r.extracted()); UT_CHECK_CONDITION(handled == r.handled()); UT_CHECK_CONDITION(status == r.status()); } }, 20, "simple test for mchain" ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mchain/timers/000077500000000000000000000000001523065210100212145ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mchain/timers/CMakeLists.txt000066400000000000000000000001311523065210100237470ustar00rootroot00000000000000set(UNITTEST _unit.test.mchain.timers) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mchain/timers/main.cpp000066400000000000000000000041011523065210100226400ustar00rootroot00000000000000/* * Test for checking delayed and periodic messages delivery to mchains. */ #include #include #include #include "../mchain_params.hpp" using namespace std; const chrono::milliseconds timeout_delta{ 20 }; template< typename L > void check_pause( const chrono::milliseconds wait_timeout, L lambda ) { auto s = chrono::steady_clock::now(); lambda(); auto f = chrono::steady_clock::now(); UT_CHECK_CONDITION( s + (wait_timeout - timeout_delta) < f ); } void check_delayed( const so_5::mchain_t & chain ) { cout << "check_delayed..." << endl; check_pause( chrono::milliseconds(100), [&] { so_5::send_delayed< int >( chain, chrono::milliseconds(100), 1 ); receive( from(chain).handle_n(1), []( int i ) { UT_CHECK_CONDITION( i == 1 ); } ); } ); } void check_periodic( const so_5::mchain_t & chain ) { cout << "check_periodic..." << endl; check_pause( chrono::milliseconds(400), [&] { auto t = so_5::send_periodic< int >( chain, chrono::milliseconds(100), chrono::milliseconds(100), 1 ); receive( from(chain).handle_n(4), []( int i ) { UT_CHECK_CONDITION( i == 1 ); } ); } ); } void do_check( bool msg_tracing_enabled ) { run_with_time_limit( [msg_tracing_enabled]() { so_5::wrapped_env_t env{ []( so_5::environment_t & ) {}, [msg_tracing_enabled]( so_5::environment_params_t & params ) { if( msg_tracing_enabled ) params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); } }; auto params = build_mchain_params(); for( const auto & p : params ) { cout << "=== " << p.first << " ===" << endl; check_delayed( env.environment().create_mchain( p.second ) ); check_periodic( env.environment().create_mchain( p.second ) ); } }, 20, "timers" ); } int main() { try { do_check( false ); do_check( true ); } catch( const exception & ex ) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/000077500000000000000000000000001523065210100214575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/CMakeLists.txt000066400000000000000000000017331523065210100242230ustar00rootroot00000000000000add_subdirectory(duplicate_limit) add_subdirectory(drop) add_subdirectory(drop_at_peaks) add_subdirectory(default_redirect_msg) add_subdirectory(direct_mbox_delayed_subscription) add_subdirectory(redirect_msg) add_subdirectory(redirect_msg_too_deep) add_subdirectory(subscr_without_limit) add_subdirectory(transform_msg) add_subdirectory(transform_msg_too_deep) add_subdirectory(transform_to_mutable_msg) add_subdirectory(transform_to_mutable_msg_2) add_subdirectory(transform_to_mutable_msg_3) add_subdirectory(transform_to_mutable_msg_4) add_subdirectory(transform_from_mutable_msg) add_subdirectory(another_direct_mbox) add_subdirectory(redirect_from_timer_to_full_mchain) add_subdirectory(transform_from_timer_to_full_mchain) add_subdirectory(any_unspecified_msg_and_state_time_limit) add_subdirectory(subscription_unsubscription_1) add_subdirectory(state_time_limit) add_subdirectory(log_then_abort_for_msg) add_subdirectory(log_then_abort_for_signal) add_subdirectory(abort_app) sobjectizer-5.8.6/dev/test/so_5/message_limits/abort_app/000077500000000000000000000000001523065210100234265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/abort_app/CMakeLists.txt000066400000000000000000000000651523065210100261670ustar00rootroot00000000000000add_subdirectory(sc_mbox) add_subdirectory(mc_mbox) sobjectizer-5.8.6/dev/test/so_5/message_limits/abort_app/mc_mbox/000077500000000000000000000000001523065210100250525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/abort_app/mc_mbox/CMakeLists.txt000066400000000000000000000002121523065210100276050ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.abort_app.mc_mbox) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/abort_app/mc_mbox/main.cpp000066400000000000000000000003701523065210100265020ustar00rootroot00000000000000/* * A simple test for message limits (aborting application). */ #include "../test_logic.inl" int main() { do_test( "mpmc_mbox test case", []( a_test_t & a ) { a.set_working_mbox( a.so_environment().create_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/abort_app/sc_mbox/000077500000000000000000000000001523065210100250605ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/abort_app/sc_mbox/CMakeLists.txt000066400000000000000000000002121523065210100276130ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.abort_app.sc_mbox) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/abort_app/sc_mbox/main.cpp000066400000000000000000000003451523065210100265120ustar00rootroot00000000000000/* * A simple test for message limits (aborting application). */ #include "../test_logic.inl" int main() { do_test( "mpsc_mbox test case", []( a_test_t & a ) { a.set_working_mbox( a.so_direct_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/abort_app/test_logic.inl000066400000000000000000000027501523065210100262720ustar00rootroot00000000000000/* * A simple test for message limits (dropping the message). */ #include #include #include #include #include #include #include #include #include struct msg_one : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env ) : so_5::agent_t( env + limit_then_abort< msg_one >( 1 ) ) {} void set_working_mbox( const so_5::mbox_t & mbox ) { m_working_mbox = mbox; } virtual void so_define_agent() override { so_default_state().event( m_working_mbox, [this](mhood_t< msg_one >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_one >( m_working_mbox ); so_5::send< msg_one >( m_working_mbox ); so_5::send< msg_one >( m_working_mbox ); } private : so_5::mbox_t m_working_mbox; }; void do_test( const std::string & test_name, std::function< void(a_test_t &) > test_tuner ) { try { run_with_time_limit( [test_tuner]() { so_5::launch( [test_tuner]( so_5::environment_t & env ) { auto coop = env.make_coop(); auto agent = coop->make_agent< a_test_t >(); test_tuner( *agent ); env.register_coop( std::move( coop ) ); } ); }, 20, test_name ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; std::abort(); } } sobjectizer-5.8.6/dev/test/so_5/message_limits/another_direct_mbox/000077500000000000000000000000001523065210100254765ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/another_direct_mbox/CMakeLists.txt000066400000000000000000000001561523065210100302400ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.another_direct_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/another_direct_mbox/main.cpp000066400000000000000000000035731523065210100271360ustar00rootroot00000000000000/* * A test for using direct mbox with limits and another mbox MPSC * mbox without limits. */ #include #include #include #include #include #include #include #include #include #include #include struct msg_timeout : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { const so_5::mbox_t m_another_mbox; bool m_second_msg_timeout_received{ false }; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx + limit_then_abort< msg_timeout >( 1 ) ) , m_another_mbox{ so_5::impl::internal_env_iface_t{ so_environment() } .create_limitless_mpsc_mbox( *this ) } {} void so_define_agent() override { so_default_state() // Message from the standard direct mbox. .event( [this](mhood_t< msg_timeout >) { so_deregister_agent_coop_normally(); } ) // Message from manually created MPSC mbox. .event( m_another_mbox, [this](mhood_t< msg_timeout >) { m_second_msg_timeout_received = true; } ) ; } void so_evt_start() override { so_5::send_delayed< msg_timeout >( *this, std::chrono::milliseconds{50} ); so_5::send_delayed< msg_timeout >( m_another_mbox, std::chrono::milliseconds{70} ); std::this_thread::sleep_for( std::chrono::milliseconds{150} ); } void so_evt_finish() override { ensure_or_die( m_second_msg_timeout_received, "second msg_timeout wasn't received!" ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/any_unspecified_msg_and_state_time_limit/000077500000000000000000000000001523065210100317305ustar00rootroot00000000000000CMakeLists.txt000066400000000000000000000002031523065210100344040ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/any_unspecified_msg_and_state_time_limitset(UNITTEST _unit.test.message_limits.any_unspecified_msg_and_state_time_limit) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/any_unspecified_msg_and_state_time_limit/main.cpp000066400000000000000000000042271523065210100333650ustar00rootroot00000000000000/* * A test for any_unspecified_message and state.time_limit. * (A limitless mpsc mbox has to be created for controlling time * limit for a state). */ #include #include #include #include #include #include #include #include #include #include #include struct msg_start : public so_5::signal_t {}; struct msg_finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { state_t st_first{ this, "first" }; state_t st_second{ this, "second" }; state_t st_third{ this, "third" }; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx + limit_then_abort< msg_start >( 1 ) + limit_then_abort< msg_finish >( 1 ) + limit_then_abort< any_unspecified_message >( 2 ) ) {} void so_define_agent() override { so_default_state() .event( &a_test_t::evt_start ) .event( &a_test_t::evt_finish ) ; st_first .time_limit( std::chrono::milliseconds{25}, so_default_state() ) ; st_second .time_limit( std::chrono::milliseconds{25}, so_default_state() ) ; st_third .time_limit( std::chrono::milliseconds{25}, so_default_state() ) ; } void so_evt_start() override { so_5::send< msg_start >( *this ); } private: void evt_start( mhood_t ) { st_first.activate(); std::this_thread::sleep_for( std::chrono::milliseconds{50} ); st_second.activate(); std::this_thread::sleep_for( std::chrono::milliseconds{50} ); st_third.activate(); std::this_thread::sleep_for( std::chrono::milliseconds{50} ); so_default_state().activate(); so_5::send< msg_finish >( *this ); } void evt_finish( mhood_t ) { so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/default_redirect_msg/000077500000000000000000000000001523065210100256325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/default_redirect_msg/CMakeLists.txt000066400000000000000000000000641523065210100303720ustar00rootroot00000000000000add_subdirectory(mc_mbox) add_subdirectory(sc_mbox) sobjectizer-5.8.6/dev/test/so_5/message_limits/default_redirect_msg/mc_mbox/000077500000000000000000000000001523065210100272565ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/default_redirect_msg/mc_mbox/CMakeLists.txt000066400000000000000000000001671523065210100320220ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.default_redirect_msg.mc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/default_redirect_msg/mc_mbox/main.cpp000066400000000000000000000062241523065210100307120ustar00rootroot00000000000000/* * A simple test for default message limits (redirecting message). */ #include #include #include #include #include #include #include #include #include struct msg_request : public so_5::message_t { const so_5::mbox_t m_reply_to; msg_request( so_5::mbox_t reply_to ) : m_reply_to( std::move( reply_to ) ) {} }; struct msg_response : public so_5::message_t { const std::string m_reply; msg_response( std::string reply ) : m_reply( std::move( reply ) ) {} }; class a_worker_t : public so_5::agent_t { public : a_worker_t( so_5::environment_t & env, so_5::mbox_t self_mbox, std::string reply ) : so_5::agent_t( env + limit_then_drop< msg_request >( 1 ) ) , m_self_mbox( std::move( self_mbox ) ) , m_reply( std::move( reply ) ) {} a_worker_t( so_5::environment_t & env, so_5::mbox_t self_mbox, std::string reply, const so_5::mbox_t & redirect_to ) : so_5::agent_t( env + limit_then_redirect< any_unspecified_message >( 1, [redirect_to] { return redirect_to; } ) ) , m_self_mbox( std::move( self_mbox ) ) , m_reply( std::move( reply ) ) {} virtual void so_define_agent() override { so_default_state().event( m_self_mbox, [&]( const msg_request & evt ) { so_5::send< msg_response >( evt.m_reply_to, m_reply ); } ); } private : const so_5::mbox_t m_self_mbox; const std::string m_reply; }; class a_manager_t : public so_5::agent_t { public : a_manager_t( so_5::environment_t & env, so_5::mbox_t self_mbox, so_5::mbox_t target_mbox, std::string expected_response ) : so_5::agent_t( env ) , m_self_mbox( std::move( self_mbox ) ) , m_target_mbox( std::move( target_mbox ) ) , m_expected_response( expected_response ) {} virtual void so_define_agent() override { so_default_state().event( m_self_mbox, [&]( const msg_response & evt ) { m_responses += evt.m_reply; if( m_expected_response == m_responses ) so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_request >( m_target_mbox, m_self_mbox ); } private : const so_5::mbox_t m_self_mbox; const so_5::mbox_t m_target_mbox; const std::string m_expected_response; std::string m_responses; }; void init( so_5::environment_t & env ) { auto w1_mbox = env.create_mbox(); auto w2_mbox = env.create_mbox(); auto w3_mbox = env.create_mbox(); auto m_mbox = env.create_mbox(); auto coop = env.make_coop(); coop->make_agent< a_manager_t >( m_mbox, w1_mbox, "[one][two][three]" ); coop->make_agent< a_worker_t >( w1_mbox, "[one]", w2_mbox ); coop->make_agent< a_worker_t >( w2_mbox, "[two]", w3_mbox ); coop->make_agent< a_worker_t >( w3_mbox, "[three]" ); env.register_coop( std::move( coop ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/default_redirect_msg/sc_mbox/000077500000000000000000000000001523065210100272645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/default_redirect_msg/sc_mbox/CMakeLists.txt000066400000000000000000000001671523065210100320300ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.default_redirect_msg.sc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/default_redirect_msg/sc_mbox/main.cpp000066400000000000000000000054641523065210100307250ustar00rootroot00000000000000/* * A simple test for message limits (redirecting message). */ #include #include #include #include #include #include #include #include #include struct msg_request : public so_5::message_t { const so_5::mbox_t m_reply_to; msg_request( so_5::mbox_t reply_to ) : m_reply_to( std::move( reply_to ) ) {} }; struct msg_response : public so_5::message_t { const std::string m_reply; msg_response( std::string reply ) : m_reply( std::move( reply ) ) {} }; class a_worker_t : public so_5::agent_t { public : a_worker_t( so_5::environment_t & env, std::string reply ) : so_5::agent_t( env + limit_then_drop< msg_request >( 1 ) ) , m_reply( std::move( reply ) ) {} a_worker_t( so_5::environment_t & env, std::string reply, const so_5::mbox_t & redirect_to ) : so_5::agent_t( env + limit_then_redirect< any_unspecified_message >( 1, [redirect_to] { return redirect_to; } ) ) , m_reply( std::move( reply ) ) {} virtual void so_define_agent() override { so_default_state().event( [&]( const msg_request & evt ) { so_5::send< msg_response >( evt.m_reply_to, m_reply ); } ); } private : const std::string m_reply; }; class a_manager_t : public so_5::agent_t { public : a_manager_t( so_5::environment_t & env, so_5::mbox_t target_mbox, std::string expected_response ) : so_5::agent_t( env ) , m_target_mbox( std::move( target_mbox ) ) , m_expected_response( expected_response ) {} virtual void so_define_agent() override { so_default_state().event( [&]( const msg_response & evt ) { m_responses += evt.m_reply; if( m_expected_response == m_responses ) so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_request >( m_target_mbox, so_direct_mbox() ); so_5::send< msg_request >( m_target_mbox, so_direct_mbox() ); so_5::send< msg_request >( m_target_mbox, so_direct_mbox() ); } private : const so_5::mbox_t m_target_mbox; const std::string m_expected_response; std::string m_responses; }; void init( so_5::environment_t & env ) { auto coop = env.make_coop(); auto w3 = coop->make_agent< a_worker_t >( "[three]" ); auto w2 = coop->make_agent< a_worker_t >( "[two]", w3->so_direct_mbox() ); auto w1 = coop->make_agent< a_worker_t >( "[one]", w2->so_direct_mbox() ); coop->make_agent< a_manager_t >( w1->so_direct_mbox(), "[one][two][three]" ); env.register_coop( std::move( coop ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple message redirect on MPSC-mboxes test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/direct_mbox_delayed_subscription/000077500000000000000000000000001523065210100302515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/direct_mbox_delayed_subscription/CMakeLists.txt000066400000000000000000000001731523065210100330120ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.direct_mbox_delayed_subscription) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/direct_mbox_delayed_subscription/main.cpp000066400000000000000000000035451523065210100317100ustar00rootroot00000000000000/* * A simple test for message limits (dropping the message). */ #include #include #include #include #include #include #include #include #include struct check final : public so_5::signal_t {}; struct subscribe final : public so_5::signal_t {}; struct shutdown final : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env ) : so_5::agent_t( env + limit_then_abort< subscribe >( 1u ) + limit_then_drop< any_unspecified_message >( 2u ) + limit_then_abort< shutdown >( 1u ) ) {} void so_define_agent() override { so_default_state() .event( [&](mhood_t< subscribe >) { so_subscribe_self().event( [this](mhood_t) { ++m_received; } ); } ) .event( [&](mhood_t< shutdown >) { if( 0u != m_received ) throw std::runtime_error( "unexpected count of " "received 'check' instances: " + std::to_string( m_received ) ); so_deregister_agent_coop_normally(); } ); } void so_evt_start() override { so_5::send< subscribe >( *this ); // At this moment there is no subscription for check message. // All those messages should be ignored. so_5::send< check >( *this ); so_5::send< check >( *this ); so_5::send< check >( *this ); so_5::send< check >( *this ); so_5::send< check >( *this ); so_5::send< shutdown >( *this ); } private : unsigned int m_received = 0; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/drop/000077500000000000000000000000001523065210100224235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/drop/CMakeLists.txt000066400000000000000000000001371523065210100251640ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.drop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/drop/main.cpp000066400000000000000000000031511523065210100240530ustar00rootroot00000000000000/* * A simple test for message limits (dropping the message). */ #include #include #include #include #include #include #include #include #include struct msg_one : public so_5::signal_t {}; struct msg_two : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env ) : so_5::agent_t( env + limit_then_drop< msg_one >( 2 ) + limit_then_drop< msg_two >( 1000 ) ) {} virtual void so_define_agent() override { so_default_state() .event( [&](mhood_t< msg_one >) { ++m_received; } ) .event( [&](mhood_t< msg_two >) { if( 2 != m_received ) throw std::runtime_error( "unexpected count of " "received msg_one instances: " + std::to_string( m_received ) ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_two >( *this ); } private : unsigned int m_received = 0; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple message drop test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/drop_at_peaks/000077500000000000000000000000001523065210100242725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/drop_at_peaks/CMakeLists.txt000066400000000000000000000001501523065210100270260ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.drop_at_peaks) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/drop_at_peaks/main.cpp000066400000000000000000000051121523065210100257210ustar00rootroot00000000000000/* * A simple test for message limits (dropping the message at message peaks). */ #include #include #include #include #include #include #include #include #include struct msg_ping : public so_5::signal_t {}; struct msg_pong : public so_5::signal_t {}; struct msg_finish : public so_5::signal_t {}; class a_sender_t : public so_5::agent_t { public : a_sender_t( context_t ctx ) : so_5::agent_t( ctx ) {} void set_receiver( const so_5::mbox_t & mbox ) { m_receiver = mbox; } virtual void so_define_agent() override { so_default_state().event( [&](mhood_t< msg_pong >) { const unsigned int max_series = 5; ++m_pongs; if( !( m_pongs % 2) ) { if( m_series_sent < max_series ) send_pings(); else if( m_series_sent == max_series ) so_5::send< msg_finish >( *this ); } } ); so_default_state().event( [&](mhood_t< msg_finish >) { const auto expected = m_series_sent * 2; if( m_pongs == expected ) so_deregister_agent_coop_normally(); else throw std::runtime_error( "pongs count mismatch; " "expected: " + std::to_string( expected ) + ", received: " + std::to_string( m_pongs ) ); } ); } virtual void so_evt_start() override { send_pings(); } private : so_5::mbox_t m_receiver; unsigned int m_series_sent = 0; unsigned int m_pongs = 0; void send_pings() { so_5::send< msg_ping >( m_receiver ); so_5::send< msg_ping >( m_receiver ); so_5::send< msg_ping >( m_receiver ); ++m_series_sent; } }; class a_receiver_t : public so_5::agent_t { public : a_receiver_t( context_t ctx, so_5::mbox_t sender ) : so_5::agent_t( ctx + limit_then_drop< msg_ping >( 2 ) ) , m_sender( std::move( sender ) ) {} virtual void so_define_agent() override { so_default_state() .event( [&](mhood_t< msg_ping >){ so_5::send< msg_pong >( m_sender ); } ); } private : const so_5::mbox_t m_sender; }; void init( so_5::environment_t & env ) { auto coop = env.make_coop(); auto sender = coop->make_agent< a_sender_t >(); auto receiver = coop->make_agent< a_receiver_t >( sender->so_direct_mbox() ); sender->set_receiver( receiver->so_direct_mbox() ); env.register_coop( std::move( coop ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple message drop at peaks test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/duplicate_limit/000077500000000000000000000000001523065210100246275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/duplicate_limit/CMakeLists.txt000066400000000000000000000001521523065210100273650ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.duplicate_limit) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/duplicate_limit/main.cpp000066400000000000000000000047761523065210100262750ustar00rootroot00000000000000/* * A simple test for message limits (dropping the message). */ #include #include #include #include #include #include #include #include #include struct msg_one : public so_5::signal_t {}; struct msg_two : public so_5::signal_t {}; class a_wrong_agent_t : public so_5::agent_t { public : a_wrong_agent_t( so_5::environment_t & env ) : so_5::agent_t( env + limit_then_drop< msg_one >( 2 ) + limit_then_drop< msg_two >( 1000 ) + limit_then_redirect< msg_one >( 3, [this] { return so_direct_mbox(); } ) ) {} virtual void so_define_agent() override { so_default_state() .event( [&](mhood_t< msg_one >) { ++m_received; } ) .event( [&](mhood_t< msg_two >) { if( 2 != m_received ) throw std::runtime_error( "unexpected count of " "received msg_one instances: " + std::to_string( m_received ) ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_one >( *this ); so_5::send< msg_two >( *this ); } private : unsigned int m_received = 0; }; class a_normal_agent_t : public so_5::agent_t { public : a_normal_agent_t( so_5::environment_t & env ) : so_5::agent_t( env + limit_then_drop< msg_one >( 1 ) + limit_then_drop< msg_two >( 1 ) ) {} virtual void so_define_agent() override { so_default_state() .event( [&](mhood_t< msg_two >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_one >( *this ); so_5::send< msg_two >( *this ); } }; void init( so_5::environment_t & env ) { auto coop = env.make_coop(); try { coop->make_agent< a_wrong_agent_t >(); throw std::runtime_error( "Exception from SObjectizer " "about duplicate limit expected" ); } catch( const so_5::exception_t & x ) { if( so_5::rc_several_limits_for_one_message_type != x.error_code() ) throw; } coop->make_agent< a_normal_agent_t >(); env.register_coop( std::move( coop ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "duplicate message limit test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_msg/000077500000000000000000000000001523065210100261615ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_msg/CMakeLists.txt000066400000000000000000000000651523065210100307220ustar00rootroot00000000000000add_subdirectory(sc_mbox) add_subdirectory(mc_mbox) sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_msg/mc_mbox/000077500000000000000000000000001523065210100276055ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_msg/mc_mbox/CMakeLists.txt000066400000000000000000000002261523065210100323450ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.log_then_abort_for_msg.mc_mbox) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_msg/mc_mbox/main.cpp000066400000000000000000000003701523065210100312350ustar00rootroot00000000000000/* * A simple test for message limits (aborting application). */ #include "../test_logic.inl" int main() { do_test( "mpmc_mbox test case", []( a_test_t & a ) { a.set_working_mbox( a.so_environment().create_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_msg/sc_mbox/000077500000000000000000000000001523065210100276135ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_msg/sc_mbox/CMakeLists.txt000066400000000000000000000002261523065210100323530ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.log_then_abort_for_msg.sc_mbox) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_msg/sc_mbox/main.cpp000066400000000000000000000003451523065210100312450ustar00rootroot00000000000000/* * A simple test for message limits (aborting application). */ #include "../test_logic.inl" int main() { do_test( "mpsc_mbox test case", []( a_test_t & a ) { a.set_working_mbox( a.so_direct_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_msg/test_logic.inl000066400000000000000000000033101523065210100310160ustar00rootroot00000000000000/* * A simple test for message limits (log then abort). */ #include #include #include #include #include #include #include #include #include struct msg_one : public so_5::message_t { int m_id; msg_one( int id ) : m_id( id ) {} }; class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env ) : so_5::agent_t( env + limit_then_abort< msg_one >( 1, []( const agent_t & a, const msg_one & m ) { std::cout << "Message limit exceeded for agent: " << &a << ", msg_id: " << m.m_id << std::endl; } ) ) {} void set_working_mbox( const so_5::mbox_t & mbox ) { m_working_mbox = mbox; } virtual void so_define_agent() override { so_default_state().event( m_working_mbox, []( const msg_one & ){} ); } virtual void so_evt_start() override { so_5::send< msg_one >( m_working_mbox, 0 ); so_5::send< msg_one >( m_working_mbox, 1 ); so_5::send< msg_one >( m_working_mbox, 2 ); so_deregister_agent_coop_normally(); } private : so_5::mbox_t m_working_mbox; }; void do_test( const std::string & test_name, std::function< void(a_test_t &) > test_tuner ) { try { run_with_time_limit( [test_tuner]() { so_5::launch( [test_tuner]( so_5::environment_t & env ) { auto coop = env.make_coop(); auto agent = coop->make_agent< a_test_t >(); test_tuner( *agent ); env.register_coop( std::move( coop ) ); } ); }, 20, test_name ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; std::abort(); } } sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_signal/000077500000000000000000000000001523065210100266505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_signal/CMakeLists.txt000066400000000000000000000000651523065210100314110ustar00rootroot00000000000000add_subdirectory(sc_mbox) add_subdirectory(mc_mbox) sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_signal/mc_mbox/000077500000000000000000000000001523065210100302745ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_signal/mc_mbox/CMakeLists.txt000066400000000000000000000002321523065210100330310ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.log_then_abort_for_signal.mc_mbox) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_signal/mc_mbox/main.cpp000066400000000000000000000003701523065210100317240ustar00rootroot00000000000000/* * A simple test for message limits (aborting application). */ #include "../test_logic.inl" int main() { do_test( "mpmc_mbox test case", []( a_test_t & a ) { a.set_working_mbox( a.so_environment().create_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_signal/sc_mbox/000077500000000000000000000000001523065210100303025ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_signal/sc_mbox/CMakeLists.txt000066400000000000000000000002321523065210100330370ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.log_then_abort_for_signal.sc_mbox) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_signal/sc_mbox/main.cpp000066400000000000000000000003451523065210100317340ustar00rootroot00000000000000/* * A simple test for message limits (aborting application). */ #include "../test_logic.inl" int main() { do_test( "mpsc_mbox test case", []( a_test_t & a ) { a.set_working_mbox( a.so_direct_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/log_then_abort_for_signal/test_logic.inl000066400000000000000000000031441523065210100315120ustar00rootroot00000000000000/* * A simple test for message limits (dropping the message). */ #include #include #include #include #include #include #include #include #include struct msg_one : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env ) : so_5::agent_t( env + limit_then_abort< msg_one >( 1, []( const agent_t & a ) { std::cout << "Message limit exceeded for agent: " << &a << std::endl; } ) ) {} void set_working_mbox( const so_5::mbox_t & mbox ) { m_working_mbox = mbox; } virtual void so_define_agent() override { so_default_state().event( m_working_mbox, [](mhood_t< msg_one >){} ); } virtual void so_evt_start() override { so_5::send< msg_one >( m_working_mbox ); so_5::send< msg_one >( m_working_mbox ); so_5::send< msg_one >( m_working_mbox ); so_deregister_agent_coop_normally(); } private : so_5::mbox_t m_working_mbox; }; void do_test( const std::string & test_name, std::function< void(a_test_t &) > test_tuner ) { try { run_with_time_limit( [test_tuner]() { so_5::launch( [test_tuner]( so_5::environment_t & env ) { auto coop = env.make_coop(); auto agent = coop->make_agent< a_test_t >(); test_tuner( *agent ); env.register_coop( std::move( coop ) ); } ); }, 20, test_name ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; std::abort(); } } sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_from_timer_to_full_mchain/000077500000000000000000000000001523065210100305465ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_from_timer_to_full_mchain/CMakeLists.txt000066400000000000000000000001751523065210100333110ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.redirect_from_timer_to_full_mchain) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_from_timer_to_full_mchain/main.cpp000066400000000000000000000062211523065210100321770ustar00rootroot00000000000000/* * A test for redirection of a message from a timer to a full mchain. */ #include #include #include #include #include #include #include #include #include struct msg_with_limit final : public so_5::signal_t {}; struct msg_control_delayed_msg final : public so_5::signal_t {}; struct msg_dummy final : public so_5::signal_t {}; class agent_with_limit_t final : public so_5::agent_t { public : agent_with_limit_t( context_t ctx, so_5::mchain_t redirect_ch ) : so_5::agent_t{ ctx + limit_then_redirect< msg_with_limit >( 1u, redirect_ch->as_mbox() ) } , m_redirect_ch{ std::move(redirect_ch) } {} virtual void so_define_agent() override { so_subscribe_self() .event( []( mhood_t ) { /* nothing to do */ } ) ; } virtual void so_evt_start() override { so_5::send< msg_with_limit >( *this ); so_5::send< msg_dummy >( m_redirect_ch ); so_5::send_delayed< msg_with_limit >( *this, std::chrono::milliseconds{ 50 } ); // Block the current thread and the current agent for some time. std::this_thread::sleep_for( std::chrono::milliseconds{ 500 } ); } private : const so_5::mchain_t m_redirect_ch; }; class time_checker_t final : public so_5::agent_t { public: time_checker_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe_self() .event( &time_checker_t::evt_delayed_msg ) ; } void so_evt_start() override { m_sent_at = std::chrono::steady_clock::now(); so_5::send_delayed< msg_control_delayed_msg >( *this, std::chrono::milliseconds{ 100 } ); } private: std::chrono::steady_clock::time_point m_sent_at; void evt_delayed_msg( mhood_t< msg_control_delayed_msg > ) { const auto received_at = std::chrono::steady_clock::now(); const auto diff = (received_at - m_sent_at); std::cout << "msg_control_delayed_msg actual delay: " << std::chrono::duration_cast< std::chrono::milliseconds >(diff).count() << "ms" << std::endl; if( diff > std::chrono::milliseconds{ 300 } ) { std::abort(); } else { so_deregister_agent_coop_normally(); } } }; void init( so_5::environment_t & env ) { env.introduce_coop( // Every agent should work on a separate worker thread. so_5::disp::active_obj::make_dispatcher( env ).binder(), []( so_5::coop_t & coop ) { coop.make_agent< time_checker_t >(); auto redirect_ch = so_5::create_mchain( coop.environment(), std::chrono::milliseconds{ 500 }, 1, // Very limited capacity. so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::drop_newest ); coop.make_agent< agent_with_limit_t >( redirect_ch ); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg/000077500000000000000000000000001523065210100241265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg/CMakeLists.txt000066400000000000000000000000641523065210100266660ustar00rootroot00000000000000add_subdirectory(mc_mbox) add_subdirectory(sc_mbox) sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg/mc_mbox/000077500000000000000000000000001523065210100255525ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg/mc_mbox/CMakeLists.txt000066400000000000000000000001571523065210100303150ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.redirect_msg.mc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg/mc_mbox/main.cpp000066400000000000000000000062621523065210100272100ustar00rootroot00000000000000/* * A simple test for message limits (redirecting message). */ #include #include #include #include #include #include #include #include #include struct msg_request : public so_5::message_t { const so_5::mbox_t m_reply_to; msg_request( so_5::mbox_t reply_to ) : m_reply_to( std::move( reply_to ) ) {} }; struct msg_response : public so_5::message_t { const std::string m_reply; msg_response( std::string reply ) : m_reply( std::move( reply ) ) {} }; class a_worker_t : public so_5::agent_t { public : a_worker_t( so_5::environment_t & env, so_5::mbox_t self_mbox, std::string reply ) : so_5::agent_t( env + limit_then_drop< msg_request >( 1 ) ) , m_self_mbox( std::move( self_mbox ) ) , m_reply( std::move( reply ) ) {} a_worker_t( so_5::environment_t & env, so_5::mbox_t self_mbox, std::string reply, const so_5::mbox_t & redirect_to ) : so_5::agent_t( env + limit_then_redirect< msg_request >( 1, [redirect_to] { return redirect_to; } ) ) , m_self_mbox( std::move( self_mbox ) ) , m_reply( std::move( reply ) ) {} virtual void so_define_agent() override { so_default_state().event( m_self_mbox, [&]( const msg_request & evt ) { so_5::send< msg_response >( evt.m_reply_to, m_reply ); } ); } private : const so_5::mbox_t m_self_mbox; const std::string m_reply; }; class a_manager_t : public so_5::agent_t { public : a_manager_t( so_5::environment_t & env, so_5::mbox_t self_mbox, so_5::mbox_t target_mbox, std::string expected_response ) : so_5::agent_t( env ) , m_self_mbox( std::move( self_mbox ) ) , m_target_mbox( std::move( target_mbox ) ) , m_expected_response( expected_response ) {} virtual void so_define_agent() override { so_default_state().event( m_self_mbox, [&]( const msg_response & evt ) { m_responses += evt.m_reply; if( m_expected_response == m_responses ) so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_request >( m_target_mbox, m_self_mbox ); } private : const so_5::mbox_t m_self_mbox; const so_5::mbox_t m_target_mbox; const std::string m_expected_response; std::string m_responses; }; void init( so_5::environment_t & env ) { auto w1_mbox = env.create_mbox(); auto w2_mbox = env.create_mbox(); auto w3_mbox = env.create_mbox(); auto m_mbox = env.create_mbox(); auto coop = env.make_coop(); coop->make_agent< a_manager_t >( m_mbox, w1_mbox, "[one][two][three]" ); coop->make_agent< a_worker_t >( w1_mbox, "[one]", w2_mbox ); coop->make_agent< a_worker_t >( w2_mbox, "[two]", w3_mbox ); coop->make_agent< a_worker_t >( w3_mbox, "[three]" ); env.register_coop( std::move( coop ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple message redirect on MPMC-mboxes test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg/sc_mbox/000077500000000000000000000000001523065210100255605ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg/sc_mbox/CMakeLists.txt000066400000000000000000000001571523065210100303230ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.redirect_msg.sc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg/sc_mbox/main.cpp000066400000000000000000000054501523065210100272140ustar00rootroot00000000000000/* * A simple test for message limits (redirecting message). */ #include #include #include #include #include #include #include #include #include struct msg_request : public so_5::message_t { const so_5::mbox_t m_reply_to; msg_request( so_5::mbox_t reply_to ) : m_reply_to( std::move( reply_to ) ) {} }; struct msg_response : public so_5::message_t { const std::string m_reply; msg_response( std::string reply ) : m_reply( std::move( reply ) ) {} }; class a_worker_t : public so_5::agent_t { public : a_worker_t( so_5::environment_t & env, std::string reply ) : so_5::agent_t( env + limit_then_drop< msg_request >( 1 ) ) , m_reply( std::move( reply ) ) {} a_worker_t( so_5::environment_t & env, std::string reply, const so_5::mbox_t & redirect_to ) : so_5::agent_t( env + limit_then_redirect< msg_request >( 1, [redirect_to] { return redirect_to; } ) ) , m_reply( std::move( reply ) ) {} virtual void so_define_agent() override { so_default_state().event( [&]( const msg_request & evt ) { so_5::send< msg_response >( evt.m_reply_to, m_reply ); } ); } private : const std::string m_reply; }; class a_manager_t : public so_5::agent_t { public : a_manager_t( so_5::environment_t & env, so_5::mbox_t target_mbox, std::string expected_response ) : so_5::agent_t( env ) , m_target_mbox( std::move( target_mbox ) ) , m_expected_response( expected_response ) {} virtual void so_define_agent() override { so_default_state().event( [&]( const msg_response & evt ) { m_responses += evt.m_reply; if( m_expected_response == m_responses ) so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_request >( m_target_mbox, so_direct_mbox() ); so_5::send< msg_request >( m_target_mbox, so_direct_mbox() ); so_5::send< msg_request >( m_target_mbox, so_direct_mbox() ); } private : const so_5::mbox_t m_target_mbox; const std::string m_expected_response; std::string m_responses; }; void init( so_5::environment_t & env ) { auto coop = env.make_coop(); auto w3 = coop->make_agent< a_worker_t >( "[three]" ); auto w2 = coop->make_agent< a_worker_t >( "[two]", w3->so_direct_mbox() ); auto w1 = coop->make_agent< a_worker_t >( "[one]", w2->so_direct_mbox() ); coop->make_agent< a_manager_t >( w1->so_direct_mbox(), "[one][two][three]" ); env.register_coop( std::move( coop ) ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple message redirect on MPSC-mboxes test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg_too_deep/000077500000000000000000000000001523065210100260045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg_too_deep/CMakeLists.txt000066400000000000000000000000641523065210100305440ustar00rootroot00000000000000add_subdirectory(mc_mbox) add_subdirectory(sc_mbox) sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg_too_deep/mc_mbox/000077500000000000000000000000001523065210100274305ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg_too_deep/mc_mbox/CMakeLists.txt000066400000000000000000000001701523065210100321660ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.redirect_msg_too_deep.mc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg_too_deep/mc_mbox/main.cpp000066400000000000000000000007311523065210100310610ustar00rootroot00000000000000/* * A simple test for message limits (redirecting message with * too deep overlimit reaction level). */ #include "../test_logic.inl" int main() { do_test( "simple too deep message redirect on MPMC-mboxes test", []( a_manager_t & m, a_worker_t & w ) { auto m_mbox = m.so_environment().create_mbox(); auto w_mbox = m.so_environment().create_mbox(); w.set_self_mbox( w_mbox ); m.set_self_mbox( m_mbox ); m.set_target_mbox( w_mbox ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg_too_deep/sc_mbox/000077500000000000000000000000001523065210100274365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg_too_deep/sc_mbox/CMakeLists.txt000066400000000000000000000001701523065210100321740ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.redirect_msg_too_deep.sc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg_too_deep/sc_mbox/main.cpp000066400000000000000000000006311523065210100310660ustar00rootroot00000000000000/* * A simple test for message limits (redirecting message with * too deep overlimit reaction level). */ #include "../test_logic.inl" int main() { do_test( "simple too deep message redirect on MPSC-mboxes test", []( a_manager_t & m, a_worker_t & w ) { w.set_self_mbox( w.so_direct_mbox() ); m.set_self_mbox( m.so_direct_mbox() ); m.set_target_mbox( w.so_direct_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/redirect_msg_too_deep/test_logic.inl000066400000000000000000000070071523065210100306500ustar00rootroot00000000000000/* * A simple test for message limits (redirecting message with * too deep overlimit reaction level). */ #include #include #include #include #include #include #include #include #include struct msg_request : public so_5::message_t { const so_5::mbox_t m_reply_to; msg_request( so_5::mbox_t reply_to ) : m_reply_to( std::move( reply_to ) ) {} }; struct msg_response : public so_5::message_t { const std::string m_reply; msg_response( std::string reply ) : m_reply( std::move( reply ) ) {} }; struct msg_finish : public so_5::message_t { const so_5::mbox_t m_reply_to; msg_finish( so_5::mbox_t reply_to ) : m_reply_to( std::move( reply_to ) ) {} }; struct msg_finish_ack : public so_5::signal_t {}; class a_worker_t : public so_5::agent_t { public : a_worker_t( so_5::environment_t & env, std::string reply ) : so_5::agent_t( env + limit_then_redirect< msg_request >( 2, [this] { return m_self_mbox; } ) + limit_then_drop< msg_finish >( 1 ) ) , m_reply( std::move( reply ) ) {} void set_self_mbox( const so_5::mbox_t & mbox ) { m_self_mbox = mbox; } virtual void so_define_agent() override { so_default_state() .event( m_self_mbox, [&]( const msg_request & evt ) { so_5::send< msg_response >( evt.m_reply_to, m_reply ); } ) .event( m_self_mbox, [&]( const msg_finish & evt ) { so_5::send< msg_finish_ack >( evt.m_reply_to ); } ); } private : so_5::mbox_t m_self_mbox; const std::string m_reply; }; class a_manager_t : public so_5::agent_t { public : a_manager_t( so_5::environment_t & env, std::string expected_response ) : so_5::agent_t( env ) , m_expected_response( expected_response ) {} void set_self_mbox( const so_5::mbox_t & mbox ) { m_self_mbox = mbox; } void set_target_mbox( const so_5::mbox_t & mbox ) { m_target_mbox = mbox; } virtual void so_define_agent() override { so_default_state() .event( m_self_mbox, [&]( const msg_response & evt ) { m_responses += evt.m_reply; } ) .event( m_self_mbox, [&](mhood_t< msg_finish_ack >) { if( m_expected_response == m_responses ) so_deregister_agent_coop_normally(); else throw std::runtime_error( "m_expected_response(" + m_expected_response + ") != " "m_responses(" + m_responses + ")" ); } ); } virtual void so_evt_start() override { so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_finish >( m_target_mbox, m_self_mbox ); } private : so_5::mbox_t m_self_mbox; so_5::mbox_t m_target_mbox; const std::string m_expected_response; std::string m_responses; }; void do_test( const std::string & test_name, std::function< void(a_manager_t &, a_worker_t &) > test_tuner ) { try { run_with_time_limit( [test_tuner]() { so_5::launch( [test_tuner]( so_5::environment_t & env ) { auto coop = env.make_coop(); auto manager = coop->make_agent< a_manager_t >( "[one][one]" ); auto worker = coop->make_agent< a_worker_t >( "[one]" ); test_tuner( *manager, *worker ); env.register_coop( std::move( coop ) ); } ); }, 20, test_name ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; std::abort(); } } sobjectizer-5.8.6/dev/test/so_5/message_limits/state_time_limit/000077500000000000000000000000001523065210100250135ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/state_time_limit/CMakeLists.txt000066400000000000000000000001531523065210100275520ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.state_time_limit) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/state_time_limit/main.cpp000066400000000000000000000025421523065210100264460ustar00rootroot00000000000000/* * A test for using message limits and time limit for a state. */ #include #include #include #include #include #include #include #include #include #include #include struct msg_timeout : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { state_t st_wait_timeout{ this, "wait_timeout" }; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx + limit_then_abort< msg_timeout >( 1 ) ) {} void so_define_agent() override { st_wait_timeout // Message from the standard direct mbox. .event( [this](mhood_t< msg_timeout >) { so_deregister_agent_coop_normally(); } ) .time_limit( std::chrono::seconds{ 2 }, so_default_state() ) ; this >>= st_wait_timeout; } void so_evt_start() override { so_5::send_delayed< msg_timeout >( *this, std::chrono::milliseconds{50} ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/subscr_without_limit/000077500000000000000000000000001523065210100257415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/subscr_without_limit/CMakeLists.txt000066400000000000000000000001571523065210100305040ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.subscr_without_limit) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/subscr_without_limit/main.cpp000066400000000000000000000047011523065210100273730ustar00rootroot00000000000000/* * A simple test for message limits (exception during attempt to * subscribe to the message without limit predefined). */ #include #include #include #include #include #include #include #include #include struct msg_one : public so_5::signal_t {}; struct msg_two : public so_5::signal_t {}; struct msg_three : public so_5::signal_t {}; class a_with_limits_t : public so_5::agent_t { public : a_with_limits_t( so_5::environment_t & env ) : so_5::agent_t( env + limit_then_drop< msg_one >( 2 ) + limit_then_drop< msg_two >( 1000 ) ) {} virtual void so_define_agent() override { so_default_state().event( [](mhood_t< msg_one >){} ); so_default_state().event( [](mhood_t< msg_two >){} ); bool exception_thrown = false; try { so_default_state().event( [](mhood_t< msg_three >){} ); } catch( const so_5::exception_t & ex ) { if( so_5::rc_message_has_no_limit_defined == ex.error_code() ) exception_thrown = true; else throw std::runtime_error( "unexpected exception with code: " + std::to_string( ex.error_code() ) ); } if( !exception_thrown ) throw std::runtime_error( "exception expected during subscription " "to message without a limit" ); } virtual void so_evt_start() override { so_deregister_agent_coop_normally(); } }; class a_without_limits_t : public so_5::agent_t { public : a_without_limits_t( so_5::environment_t & env ) : so_5::agent_t( env ) {} virtual void so_define_agent() override { so_default_state().event( [](mhood_t< msg_one >){} ); so_default_state().event( [](mhood_t< msg_two >){} ); so_default_state().event( [](mhood_t< msg_three >){} ); } virtual void so_evt_start() override { so_deregister_agent_coop_normally(); } }; void case_with_message_limits( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_with_limits_t >() ); } void case_without_message_limits( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_without_limits_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( case_with_message_limits ); so_5::launch( case_without_message_limits ); }, 20, "subscription without limit" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/subscription_unsubscription_1/000077500000000000000000000000001523065210100275725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/subscription_unsubscription_1/CMakeLists.txt000066400000000000000000000001701523065210100323300ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.subscription_unsubscription_1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/subscription_unsubscription_1/main.cpp000066400000000000000000000077411523065210100312330ustar00rootroot00000000000000/* * Test for correctness of handling subscription/unsubscriptions * with delivery filters and message limits. */ #include #include #include #include #include #include #include #include #include #include #include struct msg_start final : public so_5::signal_t {}; struct msg_unsubscribe final : public so_5::signal_t {}; struct msg_unsubscribed final : public so_5::signal_t {}; struct msg_subscribe final : public so_5::signal_t {}; struct msg_subscribed final : public so_5::signal_t {}; struct msg_complete final : public so_5::signal_t {}; struct msg_first final : public so_5::message_t { int m_v; msg_first( int v ) : m_v{v} {} }; class a_test_t : public so_5::agent_t { const so_5::mbox_t m_mbox; int m_index; public : a_test_t( context_t ctx, so_5::mbox_t mbox, int index ) : so_5::agent_t( ctx + limit_then_abort< any_unspecified_message >( 100 ) ) , m_mbox{ std::move(mbox) } , m_index{ index } {} void so_define_agent() override { so_subscribe_self() .event( &a_test_t::evt_start ) .event( &a_test_t::evt_subscribe ) .event( &a_test_t::evt_unsubscribe ) ; } private: void evt_start( mhood_t ) { so_set_delivery_filter( m_mbox, []( const msg_first & ) { return true; } ); so_subscribe( m_mbox ) .event( &a_test_t::evt_first ) ; } void evt_subscribe( mhood_t ) { so_subscribe( m_mbox ) .event( &a_test_t::evt_first ) ; so_5::send< msg_subscribed >( m_mbox ); } void evt_unsubscribe( mhood_t ) { so_drop_subscription( m_mbox, &a_test_t::evt_first ); so_5::send< msg_unsubscribed >( m_mbox ); } void evt_first( mhood_t cmd ) { if( m_index == cmd->m_v ) throw std::runtime_error{ "message with the same index received" }; } }; class a_coordinator_t final : public so_5::agent_t { const so_5::mbox_t m_mbox; const so_5::mbox_t m_a1; const so_5::mbox_t m_a2; const so_5::mbox_t m_a3; int m_iterations{}; public: a_coordinator_t( context_t ctx, so_5::mbox_t mbox, so_5::mbox_t a1, so_5::mbox_t a2, so_5::mbox_t a3 ) : so_5::agent_t{ std::move(ctx) } , m_mbox{ std::move(mbox) } , m_a1{ std::move(a1) } , m_a2{ std::move(a2) } , m_a3{ std::move(a3) } {} void so_define_agent() override { so_subscribe_self() .event( &a_coordinator_t::evt_complete ) ; so_subscribe( m_mbox ) .event( &a_coordinator_t::evt_unsubscribed ) .event( &a_coordinator_t::evt_subscribed ) ; } void so_evt_start() override { so_5::send< msg_start >( m_a1 ); so_5::send< msg_start >( m_a2 ); so_5::send< msg_start >( m_a3 ); do_unsubscription(); } private: void evt_unsubscribed( mhood_t ) { so_5::send< msg_first >( m_mbox, 2 ); do_subscription(); } void evt_subscribed( mhood_t ) { ++m_iterations; if( m_iterations < 10000 ) do_unsubscription(); else so_5::send< msg_complete >( *this ); } void evt_complete( mhood_t ) { so_deregister_agent_coop_normally(); } void do_unsubscription() { so_5::send< msg_unsubscribe >( m_a2 ); } void do_subscription() { so_5::send< msg_subscribe >( m_a2 ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto mbox = coop.environment().create_mbox(); auto a1 = coop.make_agent< a_test_t >( mbox, 1 )->so_direct_mbox(); auto a2 = coop.make_agent< a_test_t >( mbox, 2 )->so_direct_mbox(); auto a3 = coop.make_agent< a_test_t >( mbox, 3 )->so_direct_mbox(); coop.make_agent< a_coordinator_t >( mbox, a1, a2, a3 ); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( init ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_from_mutable_msg/000077500000000000000000000000001523065210100270745ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_from_mutable_msg/CMakeLists.txt000066400000000000000000000001651523065210100316360ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_from_mutable_msg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_from_mutable_msg/main.cpp000066400000000000000000000046001523065210100305240ustar00rootroot00000000000000/* * A test for trasformation of a mutable message into another mutable message. */ #include #include #include #include #include #include #include #include #include struct msg_initial_message final : public so_5::message_t { const std::string m_msg; msg_initial_message( std::string msg ) : m_msg{ std::move(msg) } {} }; struct msg_transformed_message final : public so_5::message_t { std::string m_msg; msg_transformed_message( std::string msg ) : m_msg{ std::move(msg) } {} }; class agent_with_limit_t final : public so_5::agent_t { public : agent_with_limit_t( context_t ctx ) : so_5::agent_t{ ctx + limit_then_transform< so_5::mutable_msg< msg_initial_message > >( 1u, [this]( msg_initial_message & msg ) { return make_transformed< so_5::mutable_msg< msg_transformed_message > >( so_direct_mbox(), "<" + msg.m_msg + ">" ); } ) + limit_then_abort< so_5::mutable_msg< msg_transformed_message > >( 1u ) } {} void so_define_agent() override { so_subscribe_self() .event( [this]( mutable_mhood_t cmd ) { m_received += "[initial:" + cmd->m_msg + "]"; } ) .event( [this]( mutable_mhood_t cmd ) { m_received += "[transformed:" + cmd->m_msg + "]"; const std::string expected = "[initial:hello][transformed:]"; if( expected != m_received ) throw std::runtime_error( expected + " != " + m_received ); else so_deregister_agent_coop_normally(); } ) ; } virtual void so_evt_start() override { so_5::send< so_5::mutable_msg >( *this, "hello" ); so_5::send< so_5::mutable_msg >( *this, "bye" ); } private : std::string m_received; }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< agent_with_limit_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_from_timer_to_full_mchain/000077500000000000000000000000001523065210100307605ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_from_timer_to_full_mchain/CMakeLists.txt000066400000000000000000000001761523065210100335240ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_from_timer_to_full_mchain) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_from_timer_to_full_mchain/main.cpp000066400000000000000000000065211523065210100324140ustar00rootroot00000000000000/* * A test for trasformation of a message from a timer * with redirection to a full mchain. */ #include #include #include #include #include #include #include #include #include struct msg_with_limit final : public so_5::signal_t {}; struct msg_transformed_signal final : public so_5::signal_t {}; struct msg_control_delayed_msg final : public so_5::signal_t {}; struct msg_dummy final : public so_5::signal_t {}; class agent_with_limit_t final : public so_5::agent_t { public : agent_with_limit_t( context_t ctx, so_5::mchain_t redirect_ch ) : so_5::agent_t{ ctx + limit_then_transform< msg_with_limit >( 1u, [redirect_ch]() { return make_transformed< msg_transformed_signal >( redirect_ch->as_mbox() ); } ) } , m_redirect_ch{ std::move(redirect_ch) } {} virtual void so_define_agent() override { so_subscribe_self() .event( []( mhood_t ) { /* nothing to do */ } ) ; } virtual void so_evt_start() override { so_5::send< msg_with_limit >( *this ); so_5::send< msg_dummy >( m_redirect_ch ); so_5::send_delayed< msg_with_limit >( *this, std::chrono::milliseconds{ 50 } ); // Block the current thread and the current agent for some time. std::this_thread::sleep_for( std::chrono::milliseconds{ 500 } ); } private : const so_5::mchain_t m_redirect_ch; }; class time_checker_t final : public so_5::agent_t { public: time_checker_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override { so_subscribe_self() .event( &time_checker_t::evt_delayed_msg ) ; } void so_evt_start() override { m_sent_at = std::chrono::steady_clock::now(); so_5::send_delayed< msg_control_delayed_msg >( *this, std::chrono::milliseconds{ 100 } ); } private: std::chrono::steady_clock::time_point m_sent_at; void evt_delayed_msg( mhood_t< msg_control_delayed_msg > ) { const auto received_at = std::chrono::steady_clock::now(); const auto diff = (received_at - m_sent_at); std::cout << "msg_control_delayed_msg actual delay: " << std::chrono::duration_cast< std::chrono::milliseconds >(diff).count() << "ms" << std::endl; if( diff > std::chrono::milliseconds{ 300 } ) { std::abort(); } else { so_deregister_agent_coop_normally(); } } }; void init( so_5::environment_t & env ) { env.introduce_coop( // Every agent should work on a separate worker thread. so_5::disp::active_obj::make_dispatcher( env ).binder(), []( so_5::coop_t & coop ) { coop.make_agent< time_checker_t >(); auto redirect_ch = so_5::create_mchain( coop.environment(), std::chrono::milliseconds{ 500 }, 1, // Very limited capacity. so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::drop_newest ); coop.make_agent< agent_with_limit_t >( redirect_ch ); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg/000077500000000000000000000000001523065210100243405ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg/CMakeLists.txt000066400000000000000000000000641523065210100271000ustar00rootroot00000000000000add_subdirectory(mc_mbox) add_subdirectory(sc_mbox) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg/mc_mbox/000077500000000000000000000000001523065210100257645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg/mc_mbox/CMakeLists.txt000066400000000000000000000001601523065210100305210ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_msg.mc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg/mc_mbox/main.cpp000066400000000000000000000003701523065210100274140ustar00rootroot00000000000000/* * A simple test for message limits (transforming message). */ #include "../test_logic.inl" int main() { do_test( "mpmc_mbox test case", []( a_test_t & a ) { a.set_working_mbox( a.so_environment().create_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg/sc_mbox/000077500000000000000000000000001523065210100257725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg/sc_mbox/CMakeLists.txt000066400000000000000000000001601523065210100305270ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_msg.sc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg/sc_mbox/main.cpp000066400000000000000000000003451523065210100274240ustar00rootroot00000000000000/* * A simple test for message limits (transforming message). */ #include "../test_logic.inl" int main() { do_test( "mpsc_mbox test case", []( a_test_t & a ) { a.set_working_mbox( a.so_direct_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg/test_logic.inl000066400000000000000000000074221523065210100272050ustar00rootroot00000000000000/* * A simple test for message limits (transforming the message). */ #include #include #include #include #include #include #include #include #include struct msg_hello : public so_5::message_t { std::string m_text; msg_hello( std::string text ) : m_text( std::move( text ) ) {} }; struct msg_hello_overlimit : public so_5::message_t { std::string m_desc; msg_hello_overlimit( std::string desc ) : m_desc( std::move( desc ) ) {} }; struct msg_double_overlimit : public so_5::signal_t {}; struct msg_triple_overlimit : public so_5::signal_t {}; struct msg_final_overlimit : public so_5::message_t { std::string m_text; msg_final_overlimit( std::string text ) : m_text( std::move( text ) ) {} }; class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env ) : so_5::agent_t( env + limit_then_transform( 1, [&]( const msg_hello & msg ) { return make_transformed< msg_hello_overlimit >( m_working_mbox, "<=" + msg.m_text + "=>" ); } ) + limit_then_transform( 1, [&]( const msg_hello_overlimit & ) { return make_transformed< msg_double_overlimit >( m_working_mbox ); } ) + limit_then_transform< msg_double_overlimit >( 1, [&] { return make_transformed< msg_triple_overlimit >( m_working_mbox ); } ) + limit_then_transform< msg_triple_overlimit >( 1, [&] { return make_transformed< msg_final_overlimit >( m_working_mbox, "done" ); } ) + limit_then_drop< msg_final_overlimit >( 1 ) + limit_then_drop< msg_finish >( 1 ) ) {} void set_working_mbox( const so_5::mbox_t & mbox ) { m_working_mbox = mbox; } virtual void so_define_agent() override { so_default_state() .event( m_working_mbox, [&]( const msg_hello & msg ) { m_received += "[" + msg.m_text + "]"; } ) .event( m_working_mbox, [&]( const msg_hello_overlimit & msg ) { m_received += "[" + msg.m_desc + "]"; } ) .event( m_working_mbox, [&]( mhood_t< msg_double_overlimit > ) { m_received += "[]"; } ) .event( m_working_mbox, [&]( mhood_t< msg_triple_overlimit > ) { m_received += "[]"; } ) .event( m_working_mbox, [&]( const msg_final_overlimit & msg ) { m_received += "[" + msg.m_text + "]"; } ) .event( m_working_mbox, [&]( mhood_t< msg_finish > ) { const std::string expected = "[hello][<=hello2=>][][][done]"; if( expected != m_received ) throw std::runtime_error( expected + " != " + m_received ); else so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< msg_hello >( m_working_mbox, "hello" ); so_5::send< msg_hello >( m_working_mbox, "hello2" ); so_5::send< msg_hello >( m_working_mbox, "hello3" ); so_5::send< msg_hello >( m_working_mbox, "hello4" ); so_5::send< msg_hello >( m_working_mbox, "hello5" ); so_5::send< msg_hello >( m_working_mbox, "hello6" ); so_5::send< msg_finish >( m_working_mbox ); } private : struct msg_finish : public so_5::signal_t {}; so_5::mbox_t m_working_mbox; std::string m_received; }; void do_test( const std::string & test_name, std::function< void(a_test_t &) > test_tuner ) { try { run_with_time_limit( [test_tuner]() { so_5::launch( [test_tuner]( so_5::environment_t & env ) { auto coop = env.make_coop(); auto agent = coop->make_agent< a_test_t >(); test_tuner( *agent ); env.register_coop( std::move( coop ) ); } ); }, 20, test_name ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; std::abort(); } } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg_too_deep/000077500000000000000000000000001523065210100262165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg_too_deep/CMakeLists.txt000066400000000000000000000000641523065210100307560ustar00rootroot00000000000000add_subdirectory(mc_mbox) add_subdirectory(sc_mbox) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg_too_deep/mc_mbox/000077500000000000000000000000001523065210100276425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg_too_deep/mc_mbox/CMakeLists.txt000066400000000000000000000001711523065210100324010ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_msg_too_deep.mc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg_too_deep/mc_mbox/main.cpp000066400000000000000000000007461523065210100313010ustar00rootroot00000000000000/* * A simple test for message limits (transforming message with * too deep overlimit reaction level). */ #include "../test_logic.inl" int main() { do_test( "simple too deep message transformations on MPMC-mboxes test", []( a_manager_t & m, a_worker_t & w ) { auto m_mbox = m.so_environment().create_mbox(); auto w_mbox = m.so_environment().create_mbox(); w.set_self_mbox( w_mbox ); m.set_self_mbox( m_mbox ); m.set_target_mbox( w_mbox ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg_too_deep/sc_mbox/000077500000000000000000000000001523065210100276505ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg_too_deep/sc_mbox/CMakeLists.txt000066400000000000000000000001711523065210100324070ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_msg_too_deep.sc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg_too_deep/sc_mbox/main.cpp000066400000000000000000000006431523065210100313030ustar00rootroot00000000000000/* * A simple test for message limits (transforming message with * too deep overlimit reaction level). */ #include "../test_logic.inl" int main() { do_test( "simple too deep message transformations on MPSC-mboxes test", []( a_manager_t & m, a_worker_t & w ) { w.set_self_mbox( w.so_direct_mbox() ); m.set_self_mbox( m.so_direct_mbox() ); m.set_target_mbox( w.so_direct_mbox() ); } ); return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_msg_too_deep/test_logic.inl000066400000000000000000000071351523065210100310640ustar00rootroot00000000000000/* * A simple test for message limits (transforming message with * too deep overlimit reaction level). */ #include #include #include #include #include #include #include #include #include struct msg_request : public so_5::message_t { const so_5::mbox_t m_reply_to; msg_request( so_5::mbox_t reply_to ) : m_reply_to( std::move( reply_to ) ) {} }; struct msg_response : public so_5::message_t { const std::string m_reply; msg_response( std::string reply ) : m_reply( std::move( reply ) ) {} }; struct msg_finish : public so_5::message_t { const so_5::mbox_t m_reply_to; msg_finish( so_5::mbox_t reply_to ) : m_reply_to( std::move( reply_to ) ) {} }; struct msg_finish_ack : public so_5::signal_t {}; class a_worker_t : public so_5::agent_t { public : a_worker_t( so_5::environment_t & env, std::string reply ) : so_5::agent_t( env + limit_then_transform( 2, [this]( const msg_request & evt ) { return make_transformed< msg_request >( m_self_mbox, evt.m_reply_to ); } ) + limit_then_drop< msg_finish >( 1 ) ) , m_reply( std::move( reply ) ) {} void set_self_mbox( const so_5::mbox_t & mbox ) { m_self_mbox = mbox; } virtual void so_define_agent() override { so_default_state() .event( m_self_mbox, [&]( const msg_request & evt ) { so_5::send< msg_response >( evt.m_reply_to, m_reply ); } ) .event( m_self_mbox, [&]( const msg_finish & evt ) { so_5::send< msg_finish_ack >( evt.m_reply_to ); } ); } private : so_5::mbox_t m_self_mbox; const std::string m_reply; }; class a_manager_t : public so_5::agent_t { public : a_manager_t( so_5::environment_t & env, std::string expected_response ) : so_5::agent_t( env ) , m_expected_response( expected_response ) {} void set_self_mbox( const so_5::mbox_t & mbox ) { m_self_mbox = mbox; } void set_target_mbox( const so_5::mbox_t & mbox ) { m_target_mbox = mbox; } virtual void so_define_agent() override { so_default_state() .event( m_self_mbox, [&]( const msg_response & evt ) { m_responses += evt.m_reply; } ) .event( m_self_mbox, [&]( mhood_t< msg_finish_ack > ) { if( m_expected_response == m_responses ) so_deregister_agent_coop_normally(); else throw std::runtime_error( "m_expected_response(" + m_expected_response + ") != " "m_responses(" + m_responses + ")" ); } ); } virtual void so_evt_start() override { so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_request >( m_target_mbox, m_self_mbox ); so_5::send< msg_finish >( m_target_mbox, m_self_mbox ); } private : so_5::mbox_t m_self_mbox; so_5::mbox_t m_target_mbox; const std::string m_expected_response; std::string m_responses; }; void do_test( const std::string & test_name, std::function< void(a_manager_t &, a_worker_t &) > test_tuner ) { try { run_with_time_limit( [test_tuner]() { so_5::launch( [test_tuner]( so_5::environment_t & env ) { auto coop = env.make_coop(); auto manager = coop->make_agent< a_manager_t >( "[one][one]" ); auto worker = coop->make_agent< a_worker_t >( "[one]" ); test_tuner( *manager, *worker ); env.register_coop( std::move( coop ) ); } ); }, 20, test_name ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; std::abort(); } } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg/000077500000000000000000000000001523065210100265535ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg/CMakeLists.txt000066400000000000000000000001631523065210100313130ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_to_mutable_msg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg/main.cpp000066400000000000000000000044511523065210100302070ustar00rootroot00000000000000/* * A test for trasformation of an immutable message into a mutable message. */ #include #include #include #include #include #include #include #include #include struct msg_initial_message final : public so_5::message_t { const std::string m_msg; msg_initial_message( std::string msg ) : m_msg{ std::move(msg) } {} }; struct msg_transformed_message final : public so_5::message_t { std::string m_msg; msg_transformed_message( std::string msg ) : m_msg{ std::move(msg) } {} }; class agent_with_limit_t final : public so_5::agent_t { public : agent_with_limit_t( context_t ctx ) : so_5::agent_t{ ctx + limit_then_transform( 1u, [this]( const msg_initial_message & msg ) { return make_transformed< so_5::mutable_msg< msg_transformed_message > >( so_direct_mbox(), "<" + msg.m_msg + ">" ); } ) + limit_then_abort< so_5::mutable_msg< msg_transformed_message > >( 1u ) } {} void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t cmd ) { m_received += "[initial:" + cmd->m_msg + "]"; } ) .event( [this]( mutable_mhood_t cmd ) { m_received += "[transformed:" + cmd->m_msg + "]"; const std::string expected = "[initial:hello][transformed:]"; if( expected != m_received ) throw std::runtime_error( expected + " != " + m_received ); else so_deregister_agent_coop_normally(); } ) ; } virtual void so_evt_start() override { so_5::send< msg_initial_message >( *this, "hello" ); so_5::send< msg_initial_message >( *this, "bye" ); } private : std::string m_received; }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< agent_with_limit_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg_2/000077500000000000000000000000001523065210100267745ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg_2/CMakeLists.txt000066400000000000000000000001651523065210100315360ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_to_mutable_msg_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg_2/main.cpp000066400000000000000000000044471523065210100304350ustar00rootroot00000000000000/* * A test for trasformation of an immutable message into a mutable message. * Messages that not derived from so_5::message_t are used. */ #include #include #include #include #include #include #include #include #include struct msg_initial_message { const std::string m_msg; msg_initial_message( std::string msg ) : m_msg{ std::move(msg) } {} }; struct msg_transformed_message { std::string m_msg; msg_transformed_message( std::string msg ) : m_msg{ std::move(msg) } {} }; class agent_with_limit_t final : public so_5::agent_t { public : agent_with_limit_t( context_t ctx ) : so_5::agent_t{ ctx + limit_then_transform( 1u, [this]( const msg_initial_message & msg ) { return make_transformed< so_5::mutable_msg< msg_transformed_message > >( so_direct_mbox(), "<" + msg.m_msg + ">" ); } ) + limit_then_abort< so_5::mutable_msg< msg_transformed_message > >( 1u ) } {} void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t cmd ) { m_received += "[initial:" + cmd->m_msg + "]"; } ) .event( [this]( mutable_mhood_t cmd ) { m_received += "[transformed:" + cmd->m_msg + "]"; const std::string expected = "[initial:hello][transformed:]"; if( expected != m_received ) throw std::runtime_error( expected + " != " + m_received ); else so_deregister_agent_coop_normally(); } ) ; } virtual void so_evt_start() override { so_5::send< msg_initial_message >( *this, "hello" ); so_5::send< msg_initial_message >( *this, "bye" ); } private : std::string m_received; }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< agent_with_limit_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg_3/000077500000000000000000000000001523065210100267755ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg_3/CMakeLists.txt000066400000000000000000000001651523065210100315370ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_to_mutable_msg_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg_3/main.cpp000066400000000000000000000045261523065210100304340ustar00rootroot00000000000000/* * A test for trasformation of an immutable message into a mutable message. * An incident in the form of so_5::immutable_msg<> is used. */ #include #include #include #include #include #include #include #include #include struct msg_initial_message { const std::string m_msg; msg_initial_message( std::string msg ) : m_msg{ std::move(msg) } {} }; struct msg_transformed_message { std::string m_msg; msg_transformed_message( std::string msg ) : m_msg{ std::move(msg) } {} }; class agent_with_limit_t final : public so_5::agent_t { public : agent_with_limit_t( context_t ctx ) : so_5::agent_t{ ctx + limit_then_transform< so_5::immutable_msg< msg_initial_message > >( 1u, [this]( const msg_initial_message & msg ) { return make_transformed< so_5::mutable_msg< msg_transformed_message > >( so_direct_mbox(), "<" + msg.m_msg + ">" ); } ) + limit_then_abort< so_5::mutable_msg< msg_transformed_message > >( 1u ) } {} void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t cmd ) { m_received += "[initial:" + cmd->m_msg + "]"; } ) .event( [this]( mutable_mhood_t cmd ) { m_received += "[transformed:" + cmd->m_msg + "]"; const std::string expected = "[initial:hello][transformed:]"; if( expected != m_received ) throw std::runtime_error( expected + " != " + m_received ); else so_deregister_agent_coop_normally(); } ) ; } virtual void so_evt_start() override { so_5::send< msg_initial_message >( *this, "hello" ); so_5::send< msg_initial_message >( *this, "bye" ); } private : std::string m_received; }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< agent_with_limit_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg_4/000077500000000000000000000000001523065210100267765ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg_4/CMakeLists.txt000066400000000000000000000001651523065210100315400ustar00rootroot00000000000000set(UNITTEST _unit.test.message_limits.transform_to_mutable_msg_4) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/message_limits/transform_to_mutable_msg_4/main.cpp000066400000000000000000000051561523065210100304350ustar00rootroot00000000000000/* * A test for trasformation of an immutable message into a mutable message. * An instance of message_holder_t is used. */ #include #include #include #include #include #include #include #include #include struct msg_initial_message final : public so_5::message_t { const std::string m_msg; msg_initial_message( std::string msg ) : m_msg{ std::move(msg) } {} }; struct msg_transformed_message final : public so_5::message_t { std::string m_msg; msg_transformed_message( std::string msg ) : m_msg{ std::move(msg) } {} }; class agent_with_limit_t final : public so_5::agent_t { public : agent_with_limit_t( context_t ctx ) : so_5::agent_t{ ctx + limit_then_transform( 1u, [this]( const msg_initial_message & msg ) { auto fresh_msg = so_5::message_holder_t< so_5::mutable_msg< msg_transformed_message > >::make( "<" + msg.m_msg + ">" ); return make_transformed< so_5::mutable_msg< msg_transformed_message > >( m_transformed_mbox, std::move(fresh_msg) ); } ) + limit_then_abort< so_5::mutable_msg< msg_transformed_message > >( 1u ) } , m_transformed_mbox{ so_make_new_direct_mbox() } {} void so_define_agent() override { so_subscribe_self() .event( [this]( mhood_t cmd ) { m_received += "[initial:" + cmd->m_msg + "]"; } ) ; so_subscribe( m_transformed_mbox ) .event( [this]( mutable_mhood_t cmd ) { m_received += "[transformed:" + cmd->m_msg + "]"; const std::string expected = "[initial:hello][transformed:]"; if( expected != m_received ) throw std::runtime_error( expected + " != " + m_received ); else so_deregister_agent_coop_normally(); } ) ; } virtual void so_evt_start() override { so_5::send< msg_initial_message >( *this, "hello" ); so_5::send< msg_initial_message >( *this, "bye" ); } private : const so_5::mbox_t m_transformed_mbox; std::string m_received; }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< agent_with_limit_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/000077500000000000000000000000001523065210100202615ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/CMakeLists.txt000066400000000000000000000006431523065210100230240ustar00rootroot00000000000000add_subdirectory(message_holder) add_subdirectory(resend_message) add_subdirectory(resend_message_2) add_subdirectory(resend_message_as_mutable) add_subdirectory(resend_message_as_mutable_2) add_subdirectory(store_and_resend_later) add_subdirectory(three_messages) add_subdirectory(lambda_handlers) add_subdirectory(signal_redirection) add_subdirectory(make_transformed_message_holder) add_subdirectory(user_type_msgs) sobjectizer-5.8.6/dev/test/so_5/messages/lambda_handlers/000077500000000000000000000000001523065210100233615ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/lambda_handlers/CMakeLists.txt000066400000000000000000000001441523065210100261200ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.lambda_handlers) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/lambda_handlers/main.cpp000066400000000000000000000042641523065210100250170ustar00rootroot00000000000000/* * A simple test for various format of lambda handlers. */ #include #include class a_test_t : public so_5::agent_t { struct s1 : public so_5::signal_t {}; struct s2 : public so_5::signal_t {}; struct s3 : public so_5::signal_t {}; struct s4 : public so_5::signal_t {}; struct s5 : public so_5::signal_t {}; struct s6 : public so_5::signal_t {}; struct stop : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_subscribe_self().event( [this](mhood_t< s1 >) { m_result += "s1;"; } ); so_subscribe_self().event( [this](mhood_t< s2 >) mutable { m_result += "s2;"; } ); so_subscribe_self().event( [this]( mhood_t< s3 > ) { m_result += "s3;"; } ); so_subscribe_self().event( [this]( mhood_t< s4 > ) mutable { m_result += "s4;"; } ); so_subscribe_self().event( [this]( const mhood_t< s5 > & ) { m_result += "s5;"; } ); so_subscribe_self().event( [this]( const mhood_t< s6 > & ) mutable { m_result += "s6;"; } ); so_subscribe_self().event( &a_test_t::on_stop ); } virtual void so_evt_start() override { so_5::send< s1 >( *this ); so_5::send< s2 >( *this ); so_5::send< s3 >( *this ); so_5::send< s4 >( *this ); so_5::send< s5 >( *this ); so_5::send< s6 >( *this ); so_5::send< stop >( *this ); } private : std::string m_result; void on_stop(mhood_t< stop >) { const std::string expected = "s1;s2;s3;s4;s5;s6;"; if( expected != m_result ) throw std::runtime_error( "expected(" + expected + ") != result(" + m_result + ")" ); so_deregister_agent_coop_normally(); } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init /*, []( so_5::environment_params_t & p ) { p.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for various types of event handlers" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/make_transformed_message_holder/000077500000000000000000000000001523065210100266435ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/make_transformed_message_holder/CMakeLists.txt000066400000000000000000000001641523065210100314040ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.make_transformed_message_holder) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/make_transformed_message_holder/main.cpp000066400000000000000000000156321523065210100303020ustar00rootroot00000000000000/* * Tests for use message_holder_t with make_transformed. */ #include #include #include namespace test { struct classical_message_t final : public so_5::message_t { int m_a; explicit classical_message_t( int a ) : m_a{ a } {} }; struct user_defined_message_t { int m_a; explicit user_defined_message_t( int a ) : m_a{ a } {} }; void ensure_expected_value( int expected, int actual ) { if( expected != actual ) throw std::runtime_error{ std::to_string( expected ) + " != " + std::to_string( actual ) }; } template< typename T > [[nodiscard]] auto & extract_payload_ptr( so_5::message_ref_t msg_ref ) { auto * p = so_5::message_payload_type< T >::extract_payload_ptr( msg_ref ); if( !p ) throw std::runtime_error{ "payload extraction failed" }; return *p; } void run_inside_sobjectizer( so_5::environment_t & env ) { constexpr int expected_v = 5436; const auto dummy_mbox = env.create_mbox(); // // classical message // { using T = classical_message_t; auto msg = so_5::message_holder_t< T >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = classical_message_t; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::shared >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = classical_message_t; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::unique >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::immutable_msg< classical_message_t >; auto msg = so_5::message_holder_t< T >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::immutable_msg< classical_message_t >; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::shared >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::immutable_msg< classical_message_t >; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::unique >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::mutable_msg< classical_message_t >; auto msg = so_5::message_holder_t< T >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::mutable_msg< classical_message_t >; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::shared >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::mutable_msg< classical_message_t >; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::unique >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } // // user defined message // { using T = user_defined_message_t; auto msg = so_5::message_holder_t< T >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = user_defined_message_t; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::shared >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = user_defined_message_t; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::unique >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::immutable_msg< user_defined_message_t >; auto msg = so_5::message_holder_t< T >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::immutable_msg< user_defined_message_t >; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::shared >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::immutable_msg< user_defined_message_t >; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::unique >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::mutable_msg< user_defined_message_t >; auto msg = so_5::message_holder_t< T >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::mutable_msg< user_defined_message_t >; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::shared >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } { using T = so_5::mutable_msg< user_defined_message_t >; auto msg = so_5::message_holder_t< T, so_5::message_ownership_t::unique >::make( expected_v ); auto r = so_5::make_transformed( dummy_mbox, std::move(msg) ); ensure_expected_value( expected_v, extract_payload_ptr< T >( r.message() ).m_a ); } } } /* namespace test */ int main() { using namespace test; try { run_with_time_limit( []() { so_5::launch( run_inside_sobjectizer /*, []( so_5::environment_params_t & p ) { p.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 5 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/message_holder/000077500000000000000000000000001523065210100232425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/message_holder/CMakeLists.txt000066400000000000000000000001431523065210100260000ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.message_holder) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/message_holder/main.cpp000066400000000000000000000213541523065210100246770ustar00rootroot00000000000000/* * Test for message_holder_t. */ #include #include #include struct so5_message final : public so_5::message_t { int m_a; std::string m_b; std::chrono::milliseconds m_c; so5_message( int a, std::string b, std::chrono::milliseconds c ) : m_a{ a } , m_b{ std::move(b) } , m_c{ c } {} }; struct user_message final { int m_a; std::string m_b; std::chrono::milliseconds m_c; user_message( int a, std::string b, std::chrono::milliseconds c ) : m_a{ a } , m_b{ std::move(b) } , m_c{ c } {} }; const int expected_a{ 234 }; const std::string expected_b{ "Hello!" }; const std::chrono::milliseconds expected_c{ 12345 }; template< typename Msg, so_5::message_ownership_t Ownership > decltype(auto) forward_to_send( so_5::message_holder_t & holder ) { using H = so_5::message_holder_t; if constexpr( (so_5::message_ownership_t::unique == Ownership) || (so_5::message_ownership_t::autodetected == Ownership && so_5::message_mutability_t::mutable_message == so_5::details::message_mutability_traits::mutability) ) { return H{ std::move(holder) }; } else return holder; } template< typename Msg, so_5::message_ownership_t Ownership > struct make_then_send_case_t { static constexpr const int expected = 1; static void run( const so_5::mbox_t & target ) { so_5::send( target, so_5::message_holder_t< Msg, Ownership >::make( expected_a, expected_b, expected_c ) ); } }; template< typename Holder > void ensure_not_empty_holder( const Holder & holder ) { ensure_or_die( holder, "msg shouldn't be empty!" ); ensure_or_die( !holder.empty(), "msg shouldn't be empty!" ); ensure_or_die( !!holder, "msg shouldn't be empty!" ); } template< typename Msg, so_5::message_ownership_t Ownership > struct construct_then_send_case_t { static constexpr const int expected = 1; static void run( const so_5::mbox_t & target ) { so_5::message_holder_t< Msg, Ownership > msg{ std::piecewise_construct, expected_a, expected_b, expected_c }; ensure_not_empty_holder( msg ); so_5::send( target, forward_to_send(msg) ); } }; template< typename Msg, so_5::message_ownership_t Ownership > struct make_then_send_delayed_case_t { static constexpr const int expected = 1; static void run( const so_5::mbox_t & target ) { so_5::send_delayed( target, std::chrono::milliseconds(10), so_5::message_holder_t< Msg, Ownership >::make( expected_a, expected_b, expected_c ) ); } }; template< typename Msg, so_5::message_ownership_t Ownership > struct construct_then_send_delayed_case_t { static constexpr const int expected = 1; static void run( const so_5::mbox_t & target ) { so_5::message_holder_t< Msg, Ownership > msg{ std::piecewise_construct, expected_a, expected_b, expected_c }; ensure_not_empty_holder( msg ); so_5::send_delayed( target, std::chrono::milliseconds(10), forward_to_send(msg) ); } }; template< typename Msg, so_5::message_ownership_t Ownership > struct make_then_send_periodic_case_t { static constexpr const int expected = 1; static auto run( const so_5::mbox_t & target ) { return so_5::send_periodic( target, std::chrono::milliseconds(5), std::chrono::hours(10), so_5::message_holder_t< Msg, Ownership >::make( expected_a, expected_b, expected_c ) ); } }; template< typename Msg, so_5::message_ownership_t Ownership > struct make_then_send_periodic_case_t< so_5::mutable_msg, Ownership > { static constexpr const int expected = 0; static auto run( const so_5::mbox_t & /*target*/ ) { return so_5::timer_id_t{}; } }; template< typename Msg, so_5::message_ownership_t Ownership > struct construct_then_send_periodic_case_t { static constexpr const int expected = 1; static auto run( const so_5::mbox_t & target ) { so_5::message_holder_t< Msg, Ownership > msg{ std::piecewise_construct, expected_a, expected_b, expected_c }; ensure_not_empty_holder( msg ); return so_5::send_periodic( target, std::chrono::milliseconds(5), std::chrono::hours(10), forward_to_send(msg) ); } }; template< typename Msg, so_5::message_ownership_t Ownership > struct construct_then_send_periodic_case_t< so_5::mutable_msg, Ownership > { static constexpr const int expected = 0; static auto run( const so_5::mbox_t & /*target*/ ) { return so_5::timer_id_t{}; } }; template< typename Msg, so_5::message_ownership_t Ownership > class test_t final : public so_5::agent_t { public : test_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } , m_values_to_receive{ make_then_send_case_t::expected + construct_then_send_case_t::expected + make_then_send_delayed_case_t::expected + construct_then_send_delayed_case_t::expected + make_then_send_periodic_case_t::expected + construct_then_send_periodic_case_t::expected + 0 // just for simplification of adding new cases. } { so_subscribe_self().event( &test_t::on_message ); } void so_evt_start() override { make_then_send_case_t::run( so_direct_mbox() ); construct_then_send_case_t::run( so_direct_mbox() ); make_then_send_delayed_case_t::run( so_direct_mbox() ); construct_then_send_delayed_case_t::run( so_direct_mbox() ); m_timer_1 = make_then_send_periodic_case_t::run( so_direct_mbox() ); m_timer_2 = construct_then_send_periodic_case_t::run( so_direct_mbox() ); } private : const int m_values_to_receive; int m_values_received{}; so_5::timer_id_t m_timer_1; so_5::timer_id_t m_timer_2; void on_message( mhood_t cmd ) { ensure_or_die( expected_a == cmd->m_a, "m_a mismatch!" ); ensure_or_die( expected_b == cmd->m_b, "m_b mismatch!" ); ensure_or_die( expected_c == cmd->m_c, "m_c mismatch!" ); m_values_received += 1; if( m_values_to_receive == m_values_received ) so_deregister_agent_coop_normally(); } }; template< typename Msg, so_5::message_ownership_t Ownership > void do_test( std::string_view case_name ) { std::cout << case_name << "..." << std::flush; run_with_time_limit( [] { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_t >() ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 10 ); std::cout << " OK!" << std::endl; } int main() { do_test< so5_message, so_5::message_ownership_t::autodetected >( "so5_message: immutable, autodetected" ); do_test< so5_message, so_5::message_ownership_t::shared >( "so5_message: immutable, shared" ); do_test< so5_message, so_5::message_ownership_t::unique >( "so5_message: immutable, unique" ); do_test< so_5::immutable_msg, so_5::message_ownership_t::autodetected >( "so5_message: immutable, autodetected" ); do_test< so_5::immutable_msg, so_5::message_ownership_t::shared >( "so5_message: immutable, shared" ); do_test< so_5::immutable_msg, so_5::message_ownership_t::unique >( "so5_message: immutable, unique" ); do_test< so_5::mutable_msg, so_5::message_ownership_t::autodetected >( "so5_message: mutable, autodetected" ); do_test< so_5::mutable_msg, so_5::message_ownership_t::shared >( "so5_message: mutable, shared" ); do_test< so_5::mutable_msg, so_5::message_ownership_t::unique >( "so5_message: mutable, unique" ); do_test< user_message, so_5::message_ownership_t::autodetected >( "user_message: immutable, autodetected" ); do_test< user_message, so_5::message_ownership_t::shared >( "user_message: immutable, shared" ); do_test< user_message, so_5::message_ownership_t::unique >( "user_message: immutable, unique" ); do_test< so_5::immutable_msg, so_5::message_ownership_t::autodetected >( "user_message: immutable, autodetected" ); do_test< so_5::immutable_msg, so_5::message_ownership_t::shared >( "user_message: immutable, shared" ); do_test< so_5::immutable_msg, so_5::message_ownership_t::unique >( "user_message: immutable, unique" ); do_test< so_5::mutable_msg, so_5::message_ownership_t::autodetected >( "user_message: mutable, autodetected" ); do_test< so_5::mutable_msg, so_5::message_ownership_t::shared >( "user_message: mutable, shared" ); do_test< so_5::mutable_msg, so_5::message_ownership_t::unique >( "user_message: mutable, unique" ); return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/resend_message/000077500000000000000000000000001523065210100232455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/resend_message/CMakeLists.txt000066400000000000000000000001431523065210100260030ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.resend_message) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/resend_message/main.cpp000066400000000000000000000054141523065210100247010ustar00rootroot00000000000000/* * Test of resending the same message as a mutable. */ #include #include #include struct message final : public so_5::message_t { std::string m_value; message( std::string value ) : m_value{ std::move(value) } {} }; class first_sender_t final : public so_5::agent_t { public : first_sender_t( context_t ctx, so_5::mbox_t second ) : so_5::agent_t{ std::move(ctx) } , m_second{ std::move(second) } , m_message{ so_5::message_holder_t::make( "hello!" ) } {} void so_evt_start() override { so_5::send( m_second, m_message ); } private : const so_5::mbox_t m_second; so_5::message_holder_t m_message; }; class second_sender_t final : public so_5::agent_t { public : second_sender_t( context_t ctx, so_5::mbox_t third ) : so_5::agent_t{ std::move(ctx) } , m_third{ std::move(third) } { so_subscribe_self().event( &second_sender_t::on_message ); } private : const so_5::mbox_t m_third; void on_message( so_5::mhood_t cmd ) { so_5::send( m_third, std::move(cmd) ); } }; class third_sender_t final : public so_5::agent_t { struct resend final : public so_5::signal_t {}; public : third_sender_t( context_t ctx, so_5::mbox_t fourth ) : so_5::agent_t{ std::move(ctx) } , m_fourth{ std::move(fourth) } { so_subscribe_self() .event( &third_sender_t::on_message ) .event( &third_sender_t::on_resend ); } private : const so_5::mbox_t m_fourth; so_5::message_holder_t< message > m_message; void on_message( so_5::mhood_t cmd ) { m_message = cmd.make_holder(); so_5::send( *this ); } void on_resend( so_5::mhood_t ) { so_5::send( m_fourth, std::move(m_message) ); } }; class last_t final : public so_5::agent_t { public : last_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( &last_t::on_message ); } private : void on_message( so_5::mhood_t cmd ) { ensure_or_die( cmd->m_value == "hello!", "unexpected value: " + cmd->m_value ); so_deregister_agent_coop_normally(); } }; int main() { run_with_time_limit( [] { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto last = coop.make_agent< last_t >(); auto third = coop.make_agent< third_sender_t >( last->so_direct_mbox() ); auto second = coop.make_agent< second_sender_t >( third->so_direct_mbox() ); coop.make_agent< first_sender_t >( second->so_direct_mbox() ); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/resend_message_2/000077500000000000000000000000001523065210100234665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/resend_message_2/CMakeLists.txt000066400000000000000000000001451523065210100262260ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.resend_message_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/resend_message_2/main.cpp000066400000000000000000000053631523065210100251250ustar00rootroot00000000000000/* * Test of resending the same message as a mutable. */ #include #include #include struct message final { std::string m_value; message( std::string value ) : m_value{ std::move(value) } {} }; class first_sender_t final : public so_5::agent_t { public : first_sender_t( context_t ctx, so_5::mbox_t second ) : so_5::agent_t{ std::move(ctx) } , m_second{ std::move(second) } , m_message{ so_5::message_holder_t::make( "hello!" ) } {} void so_evt_start() override { so_5::send( m_second, m_message ); } private : const so_5::mbox_t m_second; so_5::message_holder_t m_message; }; class second_sender_t final : public so_5::agent_t { public : second_sender_t( context_t ctx, so_5::mbox_t third ) : so_5::agent_t{ std::move(ctx) } , m_third{ std::move(third) } { so_subscribe_self().event( &second_sender_t::on_message ); } private : const so_5::mbox_t m_third; void on_message( so_5::mhood_t cmd ) { so_5::send( m_third, std::move(cmd) ); } }; class third_sender_t final : public so_5::agent_t { struct resend final : public so_5::signal_t {}; public : third_sender_t( context_t ctx, so_5::mbox_t fourth ) : so_5::agent_t{ std::move(ctx) } , m_fourth{ std::move(fourth) } { so_subscribe_self() .event( &third_sender_t::on_message ) .event( &third_sender_t::on_resend ); } private : const so_5::mbox_t m_fourth; so_5::message_holder_t< message > m_message; void on_message( so_5::mhood_t cmd ) { m_message = cmd.make_holder(); so_5::send( *this ); } void on_resend( so_5::mhood_t ) { so_5::send( m_fourth, std::move(m_message) ); } }; class last_t final : public so_5::agent_t { public : last_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( &last_t::on_message ); } private : void on_message( so_5::mhood_t cmd ) { ensure_or_die( cmd->m_value == "hello!", "unexpected value: " + cmd->m_value ); so_deregister_agent_coop_normally(); } }; int main() { run_with_time_limit( [] { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto last = coop.make_agent< last_t >(); auto third = coop.make_agent< third_sender_t >( last->so_direct_mbox() ); auto second = coop.make_agent< second_sender_t >( third->so_direct_mbox() ); coop.make_agent< first_sender_t >( second->so_direct_mbox() ); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/resend_message_as_mutable/000077500000000000000000000000001523065210100254415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/resend_message_as_mutable/CMakeLists.txt000066400000000000000000000001561523065210100302030ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.resend_message_as_mutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/resend_message_as_mutable/main.cpp000066400000000000000000000053531523065210100270770ustar00rootroot00000000000000/* * Test of resending the same message as a mutable. */ #include #include #include struct message final : public so_5::message_t { std::string m_value; message( std::string value ) : m_value{ std::move(value) } {} }; class first_sender_t final : public so_5::agent_t { public : first_sender_t( context_t ctx, so_5::mbox_t second ) : so_5::agent_t{ std::move(ctx) } , m_second{ std::move(second) } , m_message{ std::piecewise_construct, "hello!" } {} void so_evt_start() override { so_5::send( m_second, std::move(m_message) ); } private : const so_5::mbox_t m_second; so_5::message_holder_t< so_5::mutable_msg< message > > m_message; }; class second_sender_t final : public so_5::agent_t { public : second_sender_t( context_t ctx, so_5::mbox_t third ) : so_5::agent_t{ std::move(ctx) } , m_third{ std::move(third) } { so_subscribe_self().event( &second_sender_t::on_message ); } private : const so_5::mbox_t m_third; void on_message( so_5::mutable_mhood_t cmd ) { cmd->m_value += "!"; so_5::send( m_third, std::move(cmd) ); } }; class third_sender_t final : public so_5::agent_t { struct resend final : public so_5::signal_t {}; public : third_sender_t( context_t ctx, so_5::mbox_t fourth ) : so_5::agent_t{ std::move(ctx) } , m_fourth{ std::move(fourth) } { so_subscribe_self() .event( &third_sender_t::on_message ) .event( &third_sender_t::on_resend ); } private : const so_5::mbox_t m_fourth; so_5::message_holder_t< so_5::mutable_msg > m_message; void on_message( so_5::mutable_mhood_t cmd ) { cmd->m_value += "!"; m_message = cmd.make_holder(); so_5::send( *this ); } void on_resend( so_5::mhood_t ) { so_5::send( m_fourth, std::move(m_message) ); } }; class last_t final : public so_5::agent_t { public : last_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( &last_t::on_message ); } private : void on_message( so_5::mutable_mhood_t cmd ) { ensure_or_die( cmd->m_value == "hello!!!", "unexpected value: " + cmd->m_value ); so_deregister_agent_coop_normally(); } }; int main() { run_with_time_limit( [] { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto last = coop.make_agent< last_t >(); auto third = coop.make_agent< third_sender_t >( last->so_direct_mbox() ); auto second = coop.make_agent< second_sender_t >( third->so_direct_mbox() ); coop.make_agent< first_sender_t >( second->so_direct_mbox() ); } ); } ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/resend_message_as_mutable_2/000077500000000000000000000000001523065210100256625ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/resend_message_as_mutable_2/CMakeLists.txt000066400000000000000000000001601523065210100304170ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.resend_message_as_mutable_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/resend_message_as_mutable_2/main.cpp000066400000000000000000000055371523065210100273240ustar00rootroot00000000000000/* * Test of resending the same message as a mutable. */ #include #include #include struct message final { std::string m_value; message( std::string value ) : m_value{ std::move(value) } {} }; class first_sender_t final : public so_5::agent_t { public : first_sender_t( context_t ctx, so_5::mbox_t second ) : so_5::agent_t{ std::move(ctx) } , m_second{ std::move(second) } , m_message{ std::piecewise_construct, "hello!" } {} void so_evt_start() override { so_5::send( m_second, std::move(m_message) ); } private : const so_5::mbox_t m_second; so_5::message_holder_t< so_5::mutable_msg > m_message; }; class second_sender_t final : public so_5::agent_t { public : second_sender_t( context_t ctx, so_5::mbox_t third ) : so_5::agent_t{ std::move(ctx) } , m_third{ std::move(third) } { so_subscribe_self().event( &second_sender_t::on_message ); } private : const so_5::mbox_t m_third; void on_message( so_5::mutable_mhood_t cmd ) { cmd->m_value += "!"; so_5::send( m_third, std::move(cmd) ); } }; class third_sender_t final : public so_5::agent_t { struct resend final : public so_5::signal_t {}; public : third_sender_t( context_t ctx, so_5::mbox_t fourth ) : so_5::agent_t{ std::move(ctx) } , m_fourth{ std::move(fourth) } { so_subscribe_self() .event( &third_sender_t::on_message ) .event( &third_sender_t::on_resend ); } private : const so_5::mbox_t m_fourth; so_5::message_holder_t< so_5::mutable_msg > m_message; void on_message( so_5::mutable_mhood_t cmd ) { cmd->m_value += "!"; m_message = cmd.make_holder(); so_5::send( *this ); } void on_resend( so_5::mhood_t ) { so_5::send( m_fourth, std::move(m_message) ); } }; class last_t final : public so_5::agent_t { public : last_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( &last_t::on_message ); } private : void on_message( so_5::mutable_mhood_t cmd ) { ensure_or_die( cmd->m_value == "hello!!!", "unexpected value: " + cmd->m_value ); so_deregister_agent_coop_normally(); } }; int main() { run_with_time_limit( [] { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto last = coop.make_agent< last_t >(); auto third = coop.make_agent< third_sender_t >( last->so_direct_mbox() ); auto second = coop.make_agent< second_sender_t >( third->so_direct_mbox() ); coop.make_agent< first_sender_t >( second->so_direct_mbox() ); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 10 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/signal_redirection/000077500000000000000000000000001523065210100241255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/signal_redirection/CMakeLists.txt000066400000000000000000000001471523065210100266670ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.signal_redirection) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/signal_redirection/main.cpp000066400000000000000000000023521523065210100255570ustar00rootroot00000000000000/* * Test for redirection of a signal. */ #include #include struct test_signal final : public so_5::signal_t {}; class receiver_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( [this](mhood_t) { so_deregister_agent_coop_normally(); } ); } }; class sender_t final : public so_5::agent_t { const so_5::mbox_t m_receiver; void evt_test_signal( mhood_t cmd ) { so_5::send( m_receiver, cmd ); } public: sender_t( context_t ctx, so_5::mbox_t receiver ) : so_5::agent_t{ std::move(ctx) } , m_receiver{ std::move(receiver) } {} void so_define_agent() override { so_subscribe_self().event( &sender_t::evt_test_signal ); } void so_evt_start() override { so_5::send( *this ); } }; int main() { run_with_time_limit( [] { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto receiver = coop.make_agent< receiver_t >() ->so_direct_mbox(); coop.make_agent< sender_t >( receiver ); } ); } ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/store_and_resend_later/000077500000000000000000000000001523065210100247665ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/store_and_resend_later/CMakeLists.txt000066400000000000000000000001531523065210100275250ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.store_and_resend_later) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/store_and_resend_later/main.cpp000066400000000000000000000112251523065210100264170ustar00rootroot00000000000000/* * Test of resending the same message. */ #include #include #include #include struct msg_test; class controller_t { private : enum state_t { INITIAL, MSG_CREATED, MSG_SEND_1, MSG_RECEIVE_1, MSG_SEND_2, MSG_RECEIVE_2, MSG_DESTROYED }; state_t m_state; const msg_test * m_msg_ptr; static const char * state_name( state_t state ) { const char * r = nullptr; switch( state ) { case INITIAL: r = "INITIAL"; break; case MSG_CREATED: r = "MSG_CREATED"; break; case MSG_SEND_1: r = "MSG_SEND_1"; break; case MSG_RECEIVE_1: r = "MSG_RECEIVE_1"; break; case MSG_SEND_2: r = "MSG_SEND_2"; break; case MSG_RECEIVE_2: r = "MSG_RECEIVE_2"; break; case MSG_DESTROYED: r = "MSG_DESTROYED"; break; } return r; } void ensure_valid_state( state_t expected_state ) const { if( m_state != expected_state ) { std::cerr << "expected and actual states mismatch! " "current state: " << state_name( m_state ) << " expected state: " << state_name( expected_state ); std::abort(); } } void ensure_valid_msg_ptr( const msg_test * ptr ) { if( m_msg_ptr != ptr ) { std::cerr << "message pointers mismatch!"; std::abort(); } } public : controller_t() : m_state( INITIAL ) , m_msg_ptr( nullptr ) {} void msg_created( const msg_test * ptr ) { ensure_valid_state( INITIAL ); m_state = MSG_CREATED; m_msg_ptr = ptr; } void msg_send_1( const msg_test * ptr ) { ensure_valid_state( MSG_CREATED ); ensure_valid_msg_ptr( ptr ); m_state = MSG_SEND_1; } void msg_receive_1( const msg_test * ptr ) { ensure_valid_state( MSG_SEND_1 ); ensure_valid_msg_ptr( ptr ); m_state = MSG_RECEIVE_1; } void msg_send_2( const msg_test * ptr ) { ensure_valid_state( MSG_RECEIVE_1 ); ensure_valid_msg_ptr( ptr ); m_state = MSG_SEND_2; } void msg_receive_2( const msg_test * ptr ) { ensure_valid_state( MSG_SEND_2 ); ensure_valid_msg_ptr( ptr ); m_state = MSG_RECEIVE_2; } void msg_destroyed( const msg_test * ptr ) { ensure_valid_state( MSG_RECEIVE_2 ); ensure_valid_msg_ptr( ptr ); m_state = MSG_DESTROYED; m_msg_ptr = nullptr; } void ensure_test_passed() { ensure_valid_state( MSG_DESTROYED ); ensure_valid_msg_ptr( nullptr ); } }; struct msg_test : public so_5::message_t { msg_test( controller_t & controller ) : m_controller( controller ) { m_controller.msg_created( this ); } ~msg_test() override { m_controller.msg_destroyed( this ); } controller_t & m_controller; }; struct msg_do_resend : public so_5::signal_t {}; struct msg_stop : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, controller_t & controller ) : base_type_t( env ) , m_controller( controller ) , m_mbox( env.create_mbox() ) { } void so_define_agent() override { so_subscribe( m_mbox ) .event( &a_test_t::evt_msg_1 ); so_subscribe( m_mbox ).in( st_stored ) .event( &a_test_t::evt_do_resend ); so_subscribe( m_mbox ).in( st_stored ) .event( &a_test_t::evt_msg_2 ); so_subscribe( m_mbox ).in( st_stored ) .event( &a_test_t::evt_stop ); } void so_evt_start() override { auto msg = so_5::message_holder_t::make( m_controller ); m_controller.msg_send_1( msg.get() ); so_5::send( m_mbox, msg ); } void evt_msg_1( mhood_t< msg_test > evt ) { m_controller.msg_receive_1( evt.get() ); m_stored_message = evt.make_holder(); so_change_state( st_stored ); so_5::send< msg_do_resend >( m_mbox ); } void evt_do_resend( mhood_t< msg_do_resend > ) { m_controller.msg_send_2( m_stored_message.get() ); so_5::send( m_mbox, m_stored_message ); m_stored_message.reset(); } void evt_msg_2( mhood_t< msg_test > evt ) { m_controller.msg_receive_2( evt.get() ); so_5::send< msg_stop >( m_mbox ); } void evt_stop( mhood_t< msg_stop > ) { so_environment().stop(); } private : controller_t & m_controller; const so_5::mbox_t m_mbox; so_5::message_holder_t< msg_test > m_stored_message; so_5::state_t st_stored{ this, "stored" }; }; int main() { try { controller_t controller; so_5::launch( [&]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent( std::ref(controller) ) ); } ); controller.ensure_test_passed(); return 0; } catch( const std::exception & x ) { std::cerr << "Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/messages/three_messages/000077500000000000000000000000001523065210100232575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/three_messages/CMakeLists.txt000066400000000000000000000001431523065210100260150ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.three_messages) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/three_messages/main.cpp000066400000000000000000000053161523065210100247140ustar00rootroot00000000000000/* * A test for receiving different messages. */ #include #include #include #include struct test_message_1 : public so_5::message_t { test_message_1(): m_year_1( 2010 ), m_year_2( 2011 ) {} int m_year_1; int m_year_2; }; struct test_message_2 : public so_5::message_t { test_message_2(): m_so( "SObjectizer" ), m_ver( "5" ) {} std::string m_so; std::string m_ver; }; struct test_message_3 : public so_5::message_t { test_message_3(): m_where( "Gomel" ) {} std::string m_where; }; class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_t( so_5::environment_t & env ) : base_type_t( env ), m_test_mbox( so_environment().create_mbox() ) {} ~test_agent_t() override {} void so_define_agent() override; void so_evt_start() override; void evt_test_1( mhood_t< test_message_1 > msg ); void evt_test_2( mhood_t< test_message_2 > msg ); void evt_test_3( mhood_t< test_message_3 > msg ); private: so_5::mbox_t m_test_mbox; }; void test_agent_t::so_define_agent() { so_default_state().event( m_test_mbox, &test_agent_t::evt_test_1 ); so_default_state().event( m_test_mbox, &test_agent_t::evt_test_2 ); so_default_state().event( m_test_mbox, &test_agent_t::evt_test_3 ); } void test_agent_t::so_evt_start() { so_5::send< test_message_1 >( m_test_mbox ); so_5::send< test_message_2 >( m_test_mbox ); so_5::send< test_message_3 >( m_test_mbox ); } void test_agent_t::evt_test_1( mhood_t< test_message_1 > msg ) { if( nullptr == msg.get() ) throw std::runtime_error( "evt_test_1: 0 == msg.get()" ); if( msg->m_year_1 != 2010 || msg->m_year_2 != 2011 ) { throw std::runtime_error( "evt_test_1: " "msg->m_year_1 != 2010 || msg->m_year_2 != 2011" ); } } void test_agent_t::evt_test_2( mhood_t< test_message_2 > msg ) { if( nullptr == msg.get() ) throw std::runtime_error( "evt_test_2: 0 == msg.get()" ); if( msg->m_so !="SObjectizer" || msg->m_ver != "5" ) { throw std::runtime_error( "evt_test_2: " "msg->m_so !=\"SObjectizer\" || msg->m_ver != \"5\"" ); } } void test_agent_t::evt_test_3( mhood_t< test_message_3 > msg ) { if( nullptr == msg.get() ) throw std::runtime_error( "evt_test_3: 0 == msg.get()" ); if( msg->m_where !="Gomel" ) { throw std::runtime_error( "evt_test_3: " "msg->m_where !=\"Gomel\"" ); } so_environment().stop(); } void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/000077500000000000000000000000001523065210100233315ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/CMakeLists.txt000066400000000000000000000002451523065210100260720ustar00rootroot00000000000000add_subdirectory(delivery_filters) add_subdirectory(event_data) add_subdirectory(limit_transform) add_subdirectory(simple_msgs) add_subdirectory(different_handlers) sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/delivery_filters/000077500000000000000000000000001523065210100267045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/delivery_filters/CMakeLists.txt000066400000000000000000000001641523065210100314450ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.user_type_msgs.delivery_filters) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/delivery_filters/main.cpp000066400000000000000000000043371523065210100303430ustar00rootroot00000000000000/* * A simple test for delivery filters for messages of user types. */ #include #include class a_test_t : public so_5::agent_t { struct msg { std::string m_a; std::string m_b; }; struct stop : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_mbox( ctx.env().create_mbox() ) {} virtual void so_define_agent() override { so_set_delivery_filter( m_mbox, []( int v ) { return v > 3; } ); so_set_delivery_filter( m_mbox, []( const std::string & v ) { return v.length() == 5; } ); so_set_delivery_filter( m_mbox, []( const msg & v ) { return "Bye" == v.m_a; } ); so_subscribe( m_mbox ) .event( [&]( int evt ) { m_accumulator += "i{" + std::to_string( evt ) + "}"; } ) .event( [&]( const std::string & evt ) { m_accumulator += "s{" + evt + "}"; } ) .event( [&]( const msg & evt ) { m_accumulator += "m{" + evt.m_a + "," + evt.m_b + "}"; } ) .event( [&]( mhood_t< stop > ) { const std::string expected = "i{4}s{Hello}m{Bye,World}"; if( expected != m_accumulator ) throw std::runtime_error( "unexpected accumulator value: " + m_accumulator + ", expected: " + expected ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< int >( m_mbox, 1 ); so_5::send< int >( m_mbox, 3 ); so_5::send< int >( m_mbox, 4 ); so_5::send< std::string >( m_mbox, "Bye" ); so_5::send< std::string >( m_mbox, "Hello" ); so_5::send< std::string >( m_mbox, "Hello, World!" ); so_5::send< msg >( m_mbox, "Hello", "World" ); so_5::send< msg >( m_mbox, "Bye", "World" ); so_5::send< msg >( m_mbox, "Bye-Bye", "World" ); so_5::send< stop >( m_mbox ); } private : const so_5::mbox_t m_mbox; std::string m_accumulator; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple delivery filter for user message type test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/different_handlers/000077500000000000000000000000001523065210100271575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/different_handlers/CMakeLists.txt000066400000000000000000000001661523065210100317220ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.user_type_msgs.different_handlers) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/different_handlers/main.cpp000066400000000000000000000055001523065210100306070ustar00rootroot00000000000000/* * A simple test for different formats of event handlers for * messages of arbitrary types. */ #include #include class a_test_t : public so_5::agent_t { struct msg1 { std::string m_a; std::string m_b; }; struct msg2 { std::string m_a; std::string m_b; }; struct stop : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state() .event( [&]( const int & evt ) { m_accumulator += "i{" + std::to_string( evt ) + "}"; } ) .event( [&]( const long evt ) { m_accumulator += "l{" + std::to_string( evt ) + "}"; } ) .event( &a_test_t::evt_uint ) .event( &a_test_t::evt_ulong ) .event( [&]( so_5::mhood_t< short > evt ) { m_accumulator += "si{" + std::to_string( *evt ) + "}"; } ) .event( &a_test_t::evt_ushort ) .event( [&]( const std::string & evt ) { m_accumulator += "s{" + evt + "}"; } ) .event( [&]( const msg1 & evt ) { m_accumulator += "m1{" + evt.m_a + "," + evt.m_b + "}"; } ) .event( &a_test_t::evt_msg2 ) .event( [&]( mhood_t< stop > ) { const std::string expected = "i{1}l{2}ui{3}ul{4}si{5}usi{6}s{Hello}" "m1{Bye,World}m2{Bye,Bye}"; if( expected != m_accumulator ) throw std::runtime_error( "unexpected accumulator value: " + m_accumulator + ", expected: " + expected ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< int >( *this, 1 ); so_5::send< long >( *this, 2 ); so_5::send< unsigned int >( *this, 3u ); so_5::send< unsigned long >( *this, 4ul ); so_5::send< short >( *this, static_cast< short >(5) ); so_5::send< unsigned short >( *this, static_cast< unsigned short >(6) ); so_5::send< std::string >( *this, "Hello" ); so_5::send< msg1 >( *this, "Bye", "World" ); so_5::send< msg2 >( *this, "Bye", "Bye" ); so_5::send< stop >( *this ); } private : std::string m_accumulator; void evt_uint( const unsigned int & evt ) { m_accumulator += "ui{" + std::to_string( evt ) + "}"; } void evt_ulong( unsigned long evt ) { m_accumulator += "ul{" + std::to_string( evt ) + "}"; } void evt_ushort( const so_5::mhood_t< unsigned short > & evt ) { m_accumulator += "usi{" + std::to_string( *evt ) + "}"; } void evt_msg2( msg2 evt ) { m_accumulator += "m2{" + evt.m_a + "," + evt.m_b + "}"; } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple user message type test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/event_data/000077500000000000000000000000001523065210100254435ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/event_data/CMakeLists.txt000066400000000000000000000001561523065210100302050ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.user_type_msgs.event_data) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/event_data/main.cpp000066400000000000000000000025761523065210100271050ustar00rootroot00000000000000/* * A simple test for redirecting the same instance of message of user type. */ #include #include class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_m1( ctx.environment().create_mbox() ) , m_m2( ctx.environment().create_mbox() ) {} virtual void so_define_agent() override { so_subscribe( m_m1 ).event( &a_test_t::evt_one ); so_subscribe( m_m2 ).event( &a_test_t::evt_two ); } virtual void so_evt_start() override { so_5::send< std::string >( m_m1, "Hello, World!" ); } private : const so_5::mbox_t m_m1; const so_5::mbox_t m_m2; void evt_one( const so_5::mhood_t< std::string > & evt ) { std::cout << "One: '" << *evt << "' at " << evt.get() << std::endl; so_5::send( m_m2, evt ); } void evt_two( const std::string & evt ) { std::cout << "Two: '" << evt << "' at " << &evt << std::endl; so_deregister_agent_coop_normally(); } }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple test for resending same instance of user type message" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/limit_transform/000077500000000000000000000000001523065210100265425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/limit_transform/CMakeLists.txt000066400000000000000000000001631523065210100313020ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.user_type_msgs.limit_transform) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/limit_transform/main.cpp000066400000000000000000000033771523065210100302040ustar00rootroot00000000000000/* * A simple test for limit_then_transform for user type message. */ #include #include class a_test_t : public so_5::agent_t { struct transformed { std::string m_src; std::string m_new; }; struct stop : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx + limit_then_transform( 1, [this]( const std::string & v ) { return make_transformed< transformed >( so_direct_mbox(), v, "<" + v + ">" ); } ) + limit_then_drop< stop >( 1 ) + limit_then_drop< transformed >( 1 ) ) {} virtual void so_define_agent() override { so_default_state() .event( [&]( const std::string & evt ) { m_accumulator += "s{" + evt + "}"; } ) .event( [&]( const transformed & evt ) { m_accumulator += "t{" + evt.m_src + "," + evt.m_new + "}"; } ) .event( [&]( mhood_t< stop > ) { const std::string expected = "s{One}t{Two,}"; if( expected != m_accumulator ) throw std::runtime_error( "unexpected accumulator value: " + m_accumulator + ", expected: " + expected ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< std::string >( *this, "One" ); so_5::send< std::string >( *this, "Two" ); so_5::send< stop >( *this ); } private : std::string m_accumulator; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple user message type test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/simple_msgs/000077500000000000000000000000001523065210100256535ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/simple_msgs/CMakeLists.txt000066400000000000000000000001571523065210100304160ustar00rootroot00000000000000set(UNITTEST _unit.test.messages.user_type_msgs.simple_msgs) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/messages/user_type_msgs/simple_msgs/main.cpp000066400000000000000000000030351523065210100273040ustar00rootroot00000000000000/* * A simple test for messages of user types. */ #include #include class a_test_t : public so_5::agent_t { struct msg { std::string m_a; std::string m_b; }; struct stop : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state() .event( [&]( const int & evt ) { m_accumulator += "i{" + std::to_string( evt ) + "}"; } ) .event( [&]( const std::string & evt ) { m_accumulator += "s{" + evt + "}"; } ) .event( [&]( const msg & evt ) { m_accumulator += "m{" + evt.m_a + "," + evt.m_b + "}"; } ) .event( [&]( mhood_t< stop > ) { if( "i{1}s{Hello}m{Bye,World}" != m_accumulator ) throw std::runtime_error( "unexpected accumulator value: " + m_accumulator ); so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send< int >( *this, 1 ); so_5::send< std::string >( *this, "Hello" ); so_5::send< msg >( *this, "Bye", "World" ); so_5::send< stop >( *this ); } private : std::string m_accumulator; }; void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init ); }, 20, "simple user message type test" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mpsc_queue_traits/000077500000000000000000000000001523065210100222065ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mpsc_queue_traits/CMakeLists.txt000066400000000000000000000000651523065210100247470ustar00rootroot00000000000000add_subdirectory(locks) add_subdirectory(agent_ring) sobjectizer-5.8.6/dev/test/so_5/mpsc_queue_traits/agent_ring/000077500000000000000000000000001523065210100243235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mpsc_queue_traits/agent_ring/CMakeLists.txt000066400000000000000000000001501523065210100270570ustar00rootroot00000000000000set(UNITTEST _unit.test.mpsc_queue_traits.agent_ring) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mpsc_queue_traits/agent_ring/main.cpp000066400000000000000000000245151523065210100257620ustar00rootroot00000000000000#include #include #include #include #include #include #include class a_ring_member_t : public so_5::agent_t { public : struct msg_start : public so_5::signal_t {}; struct msg_your_turn { unsigned long long m_request_number; }; a_ring_member_t( context_t ctx ) : so_5::agent_t( ctx ) {} void set_next_mbox( const so_5::mbox_t & mbox ) { m_next_mbox = mbox; } void so_define_agent() override { so_default_state() .event( &a_ring_member_t::evt_start ) .event( &a_ring_member_t::evt_your_turn ); } void evt_start(mhood_t< msg_start >) { so_5::send< msg_your_turn >( m_next_mbox, 0ull ); } void evt_your_turn( const msg_your_turn & evt ) { ++m_rounds_passed; if( m_rounds_passed < m_rounds ) so_5::send< msg_your_turn >( m_next_mbox, evt.m_request_number + 1 ); else { so_environment().stop(); } } private : so_5::mbox_t m_self_mbox; so_5::mbox_t m_next_mbox; unsigned int m_rounds_passed = 0; const unsigned int m_rounds = 20; }; using lock_factory_t = so_5::disp::mpsc_queue_traits::lock_factory_t; class case_setter_t; struct cleanup_caller_t { void operator()(case_setter_t *) noexcept; }; using case_setter_cleaner_t = std::unique_ptr; class case_setter_t { public : case_setter_t( lock_factory_t lock_factory ) : m_lock_factory{ std::move(lock_factory) } {} virtual ~case_setter_t() {} virtual void tune_env_params( so_5::environment_params_t & ) { // Nothing to do by default. } virtual case_setter_cleaner_t make_dispatcher( so_5::environment_t & env ) = 0; virtual so_5::disp_binder_shptr_t make_binder() = 0; virtual void cleanup() noexcept = 0; protected : const lock_factory_t & lock_factory() const { return m_lock_factory; } template< typename P > P setup_lock_factory( P params ) const { params.tune_queue_params( [&]( so_5::disp::mpsc_queue_traits::queue_params_t & p ) { p.lock_factory( m_lock_factory ); } ); return params; } auto make_cleaner() noexcept { return case_setter_cleaner_t{ this }; } private : const lock_factory_t m_lock_factory; }; using case_setter_unique_ptr_t = std::unique_ptr< case_setter_t >; void cleanup_caller_t::operator()( case_setter_t * setter ) noexcept { setter->cleanup(); } class default_disp_setter_t final : public case_setter_t { so_5::disp_binder_shptr_t m_binder; public : using case_setter_t::case_setter_t; void tune_env_params( so_5::environment_params_t & params ) override { params.default_disp_params( setup_lock_factory( so_5::disp::one_thread::disp_params_t{} ) ); } case_setter_cleaner_t make_dispatcher( so_5::environment_t & env ) override { m_binder = so_5::make_default_disp_binder( env ); return make_cleaner(); } so_5::disp_binder_shptr_t make_binder() override { return m_binder; } void cleanup() noexcept override { m_binder.reset(); } }; class one_thread_case_setter_t final : public case_setter_t { so_5::disp::one_thread::dispatcher_handle_t m_disp; public : using case_setter_t::case_setter_t; case_setter_cleaner_t make_dispatcher( so_5::environment_t & env ) override { m_disp = so_5::disp::one_thread::make_dispatcher( env, "one_thread", setup_lock_factory( so_5::disp::one_thread::disp_params_t{} ) ); return make_cleaner(); } so_5::disp_binder_shptr_t make_binder() override { return m_disp.binder(); } void cleanup() noexcept override { m_disp.reset(); } }; class active_obj_case_setter_t : public case_setter_t { so_5::disp::active_obj::dispatcher_handle_t m_disp; public : using case_setter_t::case_setter_t; case_setter_cleaner_t make_dispatcher( so_5::environment_t & env ) override { m_disp = so_5::disp::active_obj::make_dispatcher( env, "active_obj", setup_lock_factory( so_5::disp::active_obj::disp_params_t{} ) ); return make_cleaner(); } so_5::disp_binder_shptr_t make_binder() override { return m_disp.binder(); } void cleanup() noexcept override { m_disp.reset(); } }; class active_group_case_setter_t : public case_setter_t { so_5::disp::active_group::dispatcher_handle_t m_disp; public : using case_setter_t::case_setter_t; case_setter_cleaner_t make_dispatcher( so_5::environment_t & env ) override { m_disp = so_5::disp::active_group::make_dispatcher( env, "active_group", setup_lock_factory( so_5::disp::active_group::disp_params_t{} ) ); return make_cleaner(); } so_5::disp_binder_shptr_t make_binder() override { auto id = ++m_id; return m_disp.binder( std::to_string(id) ); } void cleanup() noexcept override { m_disp.reset(); } private : unsigned int m_id = {0}; }; class prio_strictly_ordered_case_setter_t : public case_setter_t { so_5::disp::prio_one_thread::strictly_ordered::dispatcher_handle_t m_disp; public : using case_setter_t::case_setter_t; case_setter_cleaner_t make_dispatcher( so_5::environment_t & env ) override { namespace disp_ns = so_5::disp::prio_one_thread::strictly_ordered; m_disp = disp_ns::make_dispatcher( env, "prio::strictly_ordered", setup_lock_factory( disp_ns::disp_params_t{} ) ); return make_cleaner(); } so_5::disp_binder_shptr_t make_binder() override { return m_disp.binder(); } void cleanup() noexcept override { m_disp.reset(); } }; class prio_quoted_round_robin_case_setter_t : public case_setter_t { so_5::disp::prio_one_thread::quoted_round_robin::dispatcher_handle_t m_disp; public : using case_setter_t::case_setter_t; case_setter_cleaner_t make_dispatcher( so_5::environment_t & env ) override { namespace disp_ns = so_5::disp::prio_one_thread::quoted_round_robin; m_disp = disp_ns::make_dispatcher( env, "prio::quoted_round_robin", disp_ns::quotes_t{ 10 }, setup_lock_factory( disp_ns::disp_params_t{} ) ); return make_cleaner(); } so_5::disp_binder_shptr_t make_binder() override { return m_disp.binder(); } void cleanup() noexcept override { m_disp.reset(); } }; class one_per_prio_case_setter_t : public case_setter_t { so_5::disp::prio_dedicated_threads::one_per_prio::dispatcher_handle_t m_disp; public : using case_setter_t::case_setter_t; case_setter_cleaner_t make_dispatcher( so_5::environment_t & env ) override { namespace disp_ns = so_5::disp::prio_dedicated_threads::one_per_prio; m_disp = disp_ns::make_dispatcher( env, "prio::one_per_prio", setup_lock_factory( disp_ns::disp_params_t{} ) ); return make_cleaner(); } so_5::disp_binder_shptr_t make_binder() override { return m_disp.binder(); } void cleanup() noexcept override { m_disp.reset(); } }; void create_coop( so_5::environment_t & env, case_setter_t & setter ) { auto setter_cleaner = setter.make_dispatcher( env ); so_5::mbox_t first_agent_mbox = env.introduce_coop( [&]( so_5::coop_t & coop ) { const std::size_t ring_size = 16; std::vector< a_ring_member_t * > agents; agents.reserve( ring_size ); std::vector< so_5::mbox_t > mboxes; mboxes.reserve( ring_size ); for( unsigned int i = 0; i != ring_size; ++i ) { auto member = coop.make_agent_with_binder< a_ring_member_t >( setter.make_binder() ); agents.push_back( member ); mboxes.push_back( member->so_direct_mbox() ); } for( unsigned int i = 0; i != ring_size; ++i ) { agents[ i ]->set_next_mbox( mboxes[ (i + 1) % ring_size ] ); } return mboxes[ 0 ]; } ); so_5::send< a_ring_member_t::msg_start >( first_agent_mbox ); } using case_maker_t = std::function< case_setter_unique_ptr_t(lock_factory_t) >; template< typename Setter > case_maker_t maker() { return []( lock_factory_t lock_factory ) { return std::make_unique< Setter >( std::move(lock_factory) ); }; } void do_test() { struct case_info_t { std::string m_disp_name; case_maker_t m_maker; }; std::vector< case_info_t > cases; cases.push_back( case_info_t{ "default_disp", maker< default_disp_setter_t >() } ); cases.push_back( case_info_t{ "one_thread", maker< one_thread_case_setter_t >() } ); cases.push_back( case_info_t{ "active_obj", maker< active_obj_case_setter_t >() } ); cases.push_back( case_info_t{ "active_group", maker< active_group_case_setter_t >() } ); cases.push_back( case_info_t{ "prio::strictly_ordered", maker< prio_strictly_ordered_case_setter_t >() } ); cases.push_back( case_info_t{ "prio::quoted_round_robin", maker< prio_quoted_round_robin_case_setter_t >() } ); cases.push_back( case_info_t{ "prio::one_per_prio", maker< one_per_prio_case_setter_t >() } ); struct lock_factory_info_t { std::string m_name; lock_factory_t m_factory; }; std::vector< lock_factory_info_t > factories; factories.push_back( lock_factory_info_t{ "combined_lock", so_5::disp::mpsc_queue_traits::combined_lock_factory() } ); factories.push_back( lock_factory_info_t{ "combined_lock(1s)", so_5::disp::mpsc_queue_traits::combined_lock_factory( std::chrono::seconds(1) ) } ); factories.push_back( lock_factory_info_t{ "simple_lock", so_5::disp::mpsc_queue_traits::simple_lock_factory() } ); for( const auto & c : cases ) for( const auto & f : factories ) { std::cout << "--- " << c.m_disp_name << "+" << f.m_name << "---" << std::endl; run_with_time_limit( [&] { auto setter = c.m_maker( f.m_factory ); so_5::launch( [&]( so_5::environment_t & env ) { create_coop( env, *setter ); }, [&]( so_5::environment_params_t & params ) { setter->tune_env_params( params ); } ); }, 100, "dispatcher: " + c.m_disp_name + ", lock: " + f.m_name ); std::cout << "--- DONE ---" << std::endl; } } int main() { try { do_test(); return 0; } catch( const std::exception & x ) { std::cerr << "*** Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/mpsc_queue_traits/locks/000077500000000000000000000000001523065210100233215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mpsc_queue_traits/locks/CMakeLists.txt000066400000000000000000000001431523065210100260570ustar00rootroot00000000000000set(UNITTEST _unit.test.mpsc_queue_traits.locks) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mpsc_queue_traits/locks/main.cpp000066400000000000000000000071531523065210100247570ustar00rootroot00000000000000/* * A simple unit-test for combined_queue_lock_t. */ #include #include #include #include #include #include using namespace so_5::disp::mpsc_queue_traits; void test_thread( lock_factory_t factory, unsigned int milliseconds_to_wait ) { lock_unique_ptr_t lock_a{ factory() }; lock_unique_ptr_t lock_b{ factory() }; unique_lock_t guard_b{ *lock_b }; std::thread child{ [&lock_a, &lock_b] { unique_lock_t a{ *lock_a }; { // Informs parent that child has started. lock_guard_t b{ *lock_b }; b.notify_one(); } // Try to wait on lock_a. a.wait_for_notify(); { // Informs parent that child has started. lock_guard_t b{ *lock_b }; b.notify_one(); } } }; guard_b.wait_for_notify(); // Acquire lock_a and make child thread to wait for some time. { lock_guard_t guard_a{ *lock_a }; std::this_thread::sleep_for( std::chrono::milliseconds{ milliseconds_to_wait } ); guard_a.notify_one(); } // Wait for notify again. guard_b.wait_for_notify(); child.join(); } void do_pair_thread_test_case( lock_factory_t factory, unsigned int milliseconds_to_wait ) { std::thread parent{ test_thread, factory, milliseconds_to_wait }; parent.join(); } void single_pair_test_case( lock_factory_t factory ) { run_with_time_limit( [factory] { std::cout << "single pair: " << std::flush; do_pair_thread_test_case( factory, 250u ); std::cout << "OK" << std::endl; }, 20, "single_pair_test_case" ); } void serie_of_pair_tests_with_equal_intervals( lock_factory_t factory ) { std::cout << "serie of pair tests with equal interval: " << std::flush; for( unsigned int i = 0; i != 50; ++i ) { run_with_time_limit( [factory] { do_pair_thread_test_case( factory, 25u ); }, 20, "serie_of_pair_tests_with_equal_intervals" ); } std::cout << "OK" << std::endl; } void serie_of_pair_tests_with_different_intervals( lock_factory_t factory ) { std::cout << "serie of pair tests with different intervals: " << std::flush; for( unsigned int i = 1; i != 50; ++i ) { run_with_time_limit( [factory, i] { do_pair_thread_test_case( factory, i*5u ); }, 20, "serie_of_pair_tests_with_different_intervals" ); } std::cout << "OK" << std::endl; } void bunch_of_threads( lock_factory_t factory ) { std::cout << "bunch of threads: " << std::flush; std::vector< std::thread > threads; threads.reserve( 50 ); run_with_time_limit( [&] { for( std::size_t i = 0; i != 50; ++i ) threads.push_back( std::thread{ test_thread, factory, 250u } ); for( auto & t : threads ) t.join(); }, 100, "bunch_of_threads" ); std::cout << "OK" << std::endl; } int main() { try { struct case_info_t { std::string m_name; lock_factory_t m_factory; }; std::vector< case_info_t > cases; cases.push_back( case_info_t{ "combined_lock(default)", combined_lock_factory() } ); cases.push_back( case_info_t{ "combined_lock(1us)", combined_lock_factory( std::chrono::microseconds(1) ) } ); cases.push_back( case_info_t{ "simple_lock", simple_lock_factory() } ); for( const auto & c : cases ) { std::cout << "CHECKING CASE: " << c.m_name << "..." << std::endl; single_pair_test_case( c.m_factory ); serie_of_pair_tests_with_equal_intervals( c.m_factory ); serie_of_pair_tests_with_different_intervals( c.m_factory ); bunch_of_threads( c.m_factory ); std::cout << "--- DONE ---" << std::endl; } return 0; } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/000077500000000000000000000000001523065210100207475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/CMakeLists.txt000066400000000000000000000010271523065210100235070ustar00rootroot00000000000000add_subdirectory(simple_msg_count) add_subdirectory(simple_msg_count_mpsc_no_limits) add_subdirectory(simple_msg_count_mpsc_limits) add_subdirectory(overlimit_drop) add_subdirectory(overlimit_redirect) add_subdirectory(overlimit_transform) add_subdirectory(simple_deny_all_filter) add_subdirectory(simple_deny_msg_filter) add_subdirectory(overlimit_redirect_with_filter) add_subdirectory(change_filter_1) add_subdirectory(simple_individual_msg_count) add_subdirectory(simple_individual_delayed) add_subdirectory(overlimit_abort_app) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/change_filter_1/000077500000000000000000000000001523065210100237615ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/change_filter_1/CMakeLists.txt000066400000000000000000000001471523065210100265230ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.change_filter_1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/change_filter_1/main.cpp000066400000000000000000000057121523065210100254160ustar00rootroot00000000000000/* * A simple check for change_message_delivery_tracer_filter() method. */ #include #include #include #include "../simple_tracer.hpp" class a_test_t : public so_5::agent_t { struct next_step : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) , m_mbox1( so_environment().create_mbox() ) , m_mbox2( so_environment().create_mbox() ) , m_mbox3( so_environment().create_mbox() ) {} virtual void so_evt_start() override { so_subscribe( m_mbox1 ).event( &a_test_t::first_event ); send_messages(); } private : const so_5::mbox_t m_mbox1; const so_5::mbox_t m_mbox2; const so_5::mbox_t m_mbox3; void send_messages() { so_5::send< next_step >( m_mbox1 ); so_5::send< next_step >( m_mbox2 ); so_5::send< next_step >( m_mbox3 ); } void first_event( mhood_t ) { so_drop_subscription( m_mbox1, &a_test_t::first_event ); so_subscribe( m_mbox2 ).event( &a_test_t::second_event ); so_environment().change_message_delivery_tracer_filter( make_filter( m_mbox2 ) ); send_messages(); } void second_event( mhood_t ) { so_drop_subscription( m_mbox2, &a_test_t::second_event ); so_subscribe( m_mbox3 ).event( &a_test_t::third_event ); so_environment().change_message_delivery_tracer_filter( so_5::msg_tracing::make_enable_all_filter() ); send_messages(); } void third_event( mhood_t ) { so_environment().change_message_delivery_tracer_filter( so_5::msg_tracing::make_disable_all_filter() ); so_deregister_agent_coop_normally(); } so_5::msg_tracing::filter_shptr_t make_filter( const so_5::mbox_t & mbox ) { const auto id = mbox->id(); return so_5::msg_tracing::make_filter( [id]( const so_5::msg_tracing::trace_data_t & td ) { const auto ms = td.msg_source(); return ms && id == ms->m_id; } ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); params.message_delivery_tracer_filter( so_5::msg_tracing::make_disable_all_filter() ); } ); const unsigned int expected_value = 6; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_abort_app/000077500000000000000000000000001523065210100250105ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_abort_app/CMakeLists.txt000066400000000000000000000002121523065210100275430ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.overlimit_abort_app) set(UNITTEST_SHOULD_FAIL ON) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_abort_app/main.cpp000066400000000000000000000024101523065210100264350ustar00rootroot00000000000000/* * A simple test for message delivery tracing in overlimit action abort_app. */ #include #include #include class a_test_t : public so_5::agent_t { struct dummy_msg { int m_i; }; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx + limit_then_abort< dummy_msg >( 1 ) } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_dummy_msg ); } virtual void so_evt_start() override { so_5::send< dummy_msg >( *this, 1 ); so_5::send< dummy_msg >( *this, 2 ); } private : void evt_dummy_msg( const dummy_msg & msg ) { std::cout << "message received: " << msg.m_i << std::endl; } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } int main() { try { run_with_time_limit( []() { so_5::launch( &init, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 20, "simple tracing for overlimit reaction abort_app" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_drop/000077500000000000000000000000001523065210100240055ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_drop/CMakeLists.txt000066400000000000000000000001461523065210100265460ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.overlimit_drop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_drop/main.cpp000066400000000000000000000040051523065210100254340ustar00rootroot00000000000000/* * A simple test for message delivery tracing inside overlimit reaction * (drop_message case). */ #include #include #include #include "../simple_tracer.hpp" struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { struct dummy_msg { int m_i; }; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx + limit_then_drop< dummy_msg >(1) + limit_then_abort< finish >(1) } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_finish ); so_subscribe_self().event( &a_test_t::evt_dummy_msg ); } virtual void so_evt_start() override { so_5::send< dummy_msg >( *this, 1 ); so_5::send< dummy_msg >( *this, 2 ); } private : void evt_finish(mhood_t< finish >) { so_deregister_agent_coop_normally(); } void evt_dummy_msg( const dummy_msg & msg ) { std::cout << "message received: " << msg.m_i << std::endl; so_5::send< finish >( *this ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); } ); const unsigned int expected_value = 5; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20, "simple tracing overlimit reaction (drop_message case)" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_redirect/000077500000000000000000000000001523065210100246425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_redirect/CMakeLists.txt000066400000000000000000000001521523065210100274000ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.overlimit_redirect) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_redirect/main.cpp000066400000000000000000000042631523065210100262770ustar00rootroot00000000000000/* * A simple test for message delivery tracing inside overlimit reaction * (drop_message case). */ #include #include #include #include "../simple_tracer.hpp" struct hello : public so_5::signal_t {}; class a_first_t : public so_5::agent_t { public : a_first_t( context_t ctx ) : so_5::agent_t{ ctx } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_first_t::evt_hello ); } private : void evt_hello(mhood_t< hello >) { so_deregister_agent_coop_normally(); } }; class a_second_t : public so_5::agent_t { public : a_second_t( context_t ctx, so_5::mbox_t target ) : so_5::agent_t{ ctx + limit_then_redirect< hello >( 1, [this]{ return m_target; } ) } , m_target{ std::move(target) } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_second_t::evt_hello ); } virtual void so_evt_start() override { so_5::send< hello >( *this ); so_5::send< hello >( *this ); } private : const so_5::mbox_t m_target; void evt_hello(mhood_t< hello >) { } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto first = coop.make_agent< a_first_t >(); coop.make_agent< a_second_t >( first->so_direct_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); } ); const unsigned int expected_value = 5; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20, "simple tracing overlimit reaction (redirect case)" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_redirect_with_filter/000077500000000000000000000000001523065210100272425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_redirect_with_filter/CMakeLists.txt000066400000000000000000000001661523065210100320050ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.overlimit_redirect_with_filter) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_redirect_with_filter/main.cpp000066400000000000000000000051351523065210100306760ustar00rootroot00000000000000/* * A simple test for message delivery tracing inside overlimit reaction * (redirect case). * * With message delivery tracing with filter for overlimit messages. */ #include #include #include #include "../simple_tracer.hpp" struct hello : public so_5::signal_t {}; class a_first_t : public so_5::agent_t { public : a_first_t( context_t ctx ) : so_5::agent_t{ ctx } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_first_t::evt_hello ); } private : void evt_hello(mhood_t< hello >) { so_deregister_agent_coop_normally(); } }; class a_second_t : public so_5::agent_t { public : a_second_t( context_t ctx, so_5::mbox_t target ) : so_5::agent_t{ ctx + limit_then_redirect< hello >( 1, [this]{ return m_target; } ) } , m_target{ std::move(target) } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_second_t::evt_hello ); } virtual void so_evt_start() override { so_5::send< hello >( *this ); so_5::send< hello >( *this ); } private : const so_5::mbox_t m_target; void evt_hello(mhood_t< hello >) { } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto first = coop.make_agent< a_first_t >(); coop.make_agent< a_second_t >( first->so_direct_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); params.message_delivery_tracer_filter( so_5::msg_tracing::make_filter( []( const so_5::msg_tracing::trace_data_t & d ) { const auto ca = d.compound_action(); if( ca ) { return nullptr != std::strstr( ca->m_second, "overlimit" ); } return false; } ) ); } ); const unsigned int expected_value = 1; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20, "simple tracing overlimit reaction (redirect case)" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_transform/000077500000000000000000000000001523065210100250545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_transform/CMakeLists.txt000066400000000000000000000001531523065210100276130ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.overlimit_transform) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/overlimit_transform/main.cpp000066400000000000000000000044771523065210100265200ustar00rootroot00000000000000/* * A simple test for message delivery tracing inside overlimit reaction * (drop_message case). */ #include #include #include #include "../simple_tracer.hpp" struct hello { int m_v; }; struct bye { std::string m_v; }; class a_first_t : public so_5::agent_t { public : a_first_t( context_t ctx ) : so_5::agent_t{ ctx } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_first_t::evt_bye ); } private : void evt_bye( const bye & msg ) { if( "1" == msg.m_v ) so_deregister_agent_coop_normally(); } }; class a_second_t : public so_5::agent_t { public : a_second_t( context_t ctx, so_5::mbox_t target ) : so_5::agent_t{ ctx + limit_then_transform( 1, [this]( const hello & msg ) { return make_transformed< bye >( m_target, std::to_string( msg.m_v ) ); } ) } , m_target{ std::move(target) } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_second_t::evt_hello ); } virtual void so_evt_start() override { so_5::send< hello >( *this, 0 ); so_5::send< hello >( *this, 1 ); } private : const so_5::mbox_t m_target; void evt_hello( const hello & ) {} }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto first = coop.make_agent< a_first_t >(); coop.make_agent< a_second_t >( first->so_direct_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); } ); const unsigned int expected_value = 5; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20, "simple tracing overlimit reaction (transform case)" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_deny_all_filter/000077500000000000000000000000001523065210100254545ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_deny_all_filter/CMakeLists.txt000066400000000000000000000001561523065210100302160ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.simple_deny_all_filter) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_deny_all_filter/main.cpp000066400000000000000000000060001523065210100271000ustar00rootroot00000000000000/* * A simple test for message delivery tracing. */ #include #include #include #include "../simple_tracer.hpp" class deny_all_filter_t : public so_5::msg_tracing::filter_t { public : deny_all_filter_t( counter_t & counter ) : m_counter(counter) {} virtual bool filter( const so_5::msg_tracing::trace_data_t & ) noexcept override { ++m_counter; return false; } private : counter_t & m_counter; }; struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { struct dummy_msg { int m_i; }; public : a_test_t( context_t ctx, so_5::mbox_t data_mbox ) : so_5::agent_t{ ctx } , m_data_mbox{ std::move( data_mbox ) } {} virtual void so_define_agent() override { so_set_delivery_filter( m_data_mbox, []( const dummy_msg & msg ) { return 0 == msg.m_i; } ); so_subscribe( m_data_mbox ).event( &a_test_t::evt_finish ); so_subscribe( m_data_mbox ).event( &a_test_t::evt_dummy_msg ); } virtual void so_evt_start() override { so_5::send< dummy_msg >( m_data_mbox, 1 ); so_5::send< finish >( m_data_mbox ); } private : const so_5::mbox_t m_data_mbox; void evt_finish(mhood_t< finish >) { so_deregister_agent_coop_normally(); } void evt_dummy_msg( const dummy_msg & msg ) { if( 0 != msg.m_i ) throw std::runtime_error( "msg.m_i != 0" ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( coop.environment().create_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t msg_counter = { 0 }; counter_t filter_counter = { 0 }; so_5::launch( &init, [&]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ msg_counter, so_5::msg_tracing::std_cout_tracer() } } ); params.message_delivery_tracer_filter( new deny_all_filter_t{ filter_counter } ); } ); const unsigned int expected_msg_count_value = 0; const unsigned int expected_filter_count_value = 3; const auto actual_msg_count_value = msg_counter.load( std::memory_order_acquire ); const auto actual_filter_count_value = filter_counter.load( std::memory_order_acquire ); if( expected_msg_count_value != actual_msg_count_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_msg_count_value) + ", actual=" + std::to_string(actual_msg_count_value) ); if( expected_filter_count_value != actual_filter_count_value ) throw std::runtime_error( "Unexpected count of filter invocations: " "expected=" + std::to_string(expected_filter_count_value) + ", actual=" + std::to_string(actual_filter_count_value) ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_deny_msg_filter/000077500000000000000000000000001523065210100254725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_deny_msg_filter/CMakeLists.txt000066400000000000000000000001561523065210100302340ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.simple_deny_msg_filter) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_deny_msg_filter/main.cpp000066400000000000000000000047331523065210100271310ustar00rootroot00000000000000/* * A simple test for message delivery tracing. */ #include #include #include #include "../simple_tracer.hpp" class deny_msg_filter_t : public so_5::msg_tracing::filter_t { public : deny_msg_filter_t() {} virtual bool filter( const so_5::msg_tracing::trace_data_t & d ) noexcept override { const auto ms = d.message_or_signal(); if( ms ) return so_5::msg_tracing::message_or_signal_flag_t::message != *ms; return true; } }; struct finish : public so_5::signal_t {}; struct lost_signal : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { struct dummy_msg { int m_i; }; public : a_test_t( context_t ctx, so_5::mbox_t data_mbox ) : so_5::agent_t{ ctx } , m_data_mbox{ std::move( data_mbox ) } {} virtual void so_define_agent() override { so_subscribe( m_data_mbox ).event( &a_test_t::evt_finish ); so_subscribe( m_data_mbox ).event( &a_test_t::evt_dummy_msg ); } virtual void so_evt_start() override { so_5::send< dummy_msg >( m_data_mbox, 1 ); so_5::send< finish >( m_data_mbox ); so_5::send< lost_signal >( m_data_mbox ); } private : const so_5::mbox_t m_data_mbox; void evt_finish(mhood_t< finish >) { so_deregister_agent_coop_normally(); } void evt_dummy_msg( const dummy_msg & /*msg*/ ) { } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( coop.environment().create_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t msg_counter = { 0 }; so_5::launch( &init, [&]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ msg_counter, so_5::msg_tracing::std_cout_tracer() } } ); params.message_delivery_tracer_filter( new deny_msg_filter_t{} ); } ); const unsigned int expected_msg_count_value = 3; const auto actual_msg_count_value = msg_counter.load( std::memory_order_acquire ); if( expected_msg_count_value != actual_msg_count_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_msg_count_value) + ", actual=" + std::to_string(actual_msg_count_value) ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_individual_delayed/000077500000000000000000000000001523065210100261375ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_individual_delayed/CMakeLists.txt000066400000000000000000000001611523065210100306750ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.simple_individual_delayed) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_individual_delayed/main.cpp000066400000000000000000000045231523065210100275730ustar00rootroot00000000000000/* * A simple test for message delivery tracing. */ #include #include #include #include "../simple_tracer.hpp" struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { struct dummy_msg { int m_i; }; public : a_test_t( context_t ctx, so_5::mbox_t data_mbox ) : so_5::agent_t{ ctx } , m_data_mbox{ std::move( data_mbox ) } {} virtual void so_define_agent() override { so_set_delivery_filter( m_data_mbox, []( const dummy_msg & msg ) { return 0 == msg.m_i; } ); so_subscribe( m_data_mbox ).event( &a_test_t::evt_finish ); so_subscribe( m_data_mbox ).event( &a_test_t::evt_dummy_msg ); } virtual void so_evt_start() override { so_5::send_delayed< dummy_msg >( so_5::msg_tracing::individual_trace( m_data_mbox ), std::chrono::milliseconds{ 25 }, 1 ); so_5::send_delayed< finish >( m_data_mbox, std::chrono::milliseconds{ 50 } ); } private : const so_5::mbox_t m_data_mbox; void evt_finish(mhood_t< finish >) { so_deregister_agent_coop_normally(); } void evt_dummy_msg( const dummy_msg & msg ) { if( 0 != msg.m_i ) throw std::runtime_error( "msg.m_i != 0" ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( coop.environment().create_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); params.message_delivery_tracer_filter( so_5::msg_tracing::make_individual_trace_filter() ); } ); const unsigned int expected_value = 1; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20, "simple tracing for MPMC-mboxes" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_individual_msg_count/000077500000000000000000000000001523065210100265265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_individual_msg_count/CMakeLists.txt000066400000000000000000000001631523065210100312660ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.simple_individual_msg_count) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_individual_msg_count/main.cpp000066400000000000000000000043551523065210100301650ustar00rootroot00000000000000/* * A simple test for message delivery tracing. */ #include #include #include #include "../simple_tracer.hpp" struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { struct dummy_msg { int m_i; }; public : a_test_t( context_t ctx, so_5::mbox_t data_mbox ) : so_5::agent_t{ ctx } , m_data_mbox{ std::move( data_mbox ) } {} virtual void so_define_agent() override { so_set_delivery_filter( m_data_mbox, []( const dummy_msg & msg ) { return 0 == msg.m_i; } ); so_subscribe( m_data_mbox ).event( &a_test_t::evt_finish ); so_subscribe( m_data_mbox ).event( &a_test_t::evt_dummy_msg ); } virtual void so_evt_start() override { so_5::send< dummy_msg >( so_5::msg_tracing::individual_trace( m_data_mbox ), 1 ); so_5::send< finish >( m_data_mbox ); } private : const so_5::mbox_t m_data_mbox; void evt_finish(mhood_t< finish >) { so_deregister_agent_coop_normally(); } void evt_dummy_msg( const dummy_msg & msg ) { if( 0 != msg.m_i ) throw std::runtime_error( "msg.m_i != 0" ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( coop.environment().create_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); params.message_delivery_tracer_filter( so_5::msg_tracing::make_individual_trace_filter() ); } ); const unsigned int expected_value = 1; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20, "simple tracing for MPMC-mboxes" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_msg_count/000077500000000000000000000000001523065210100243165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_msg_count/CMakeLists.txt000066400000000000000000000001501523065210100270520ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.simple_msg_count) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_msg_count/main.cpp000066400000000000000000000041341523065210100257500ustar00rootroot00000000000000/* * A simple test for message delivery tracing. */ #include #include #include #include "../simple_tracer.hpp" struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { struct dummy_msg { int m_i; }; public : a_test_t( context_t ctx, so_5::mbox_t data_mbox ) : so_5::agent_t{ ctx } , m_data_mbox{ std::move( data_mbox ) } {} virtual void so_define_agent() override { so_set_delivery_filter( m_data_mbox, []( const dummy_msg & msg ) { return 0 == msg.m_i; } ); so_subscribe( m_data_mbox ).event( &a_test_t::evt_finish ); so_subscribe( m_data_mbox ).event( &a_test_t::evt_dummy_msg ); } virtual void so_evt_start() override { so_5::send< dummy_msg >( m_data_mbox, 1 ); so_5::send< finish >( m_data_mbox ); } private : const so_5::mbox_t m_data_mbox; void evt_finish(mhood_t< finish >) { so_deregister_agent_coop_normally(); } void evt_dummy_msg( const dummy_msg & msg ) { if( 0 != msg.m_i ) throw std::runtime_error( "msg.m_i != 0" ); } }; void init( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( coop.environment().create_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); } ); const unsigned int expected_value = 3; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20, "simple tracing for MPMC-mboxes" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_msg_count_mpsc_limits/000077500000000000000000000000001523065210100267215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_msg_count_mpsc_limits/CMakeLists.txt000066400000000000000000000001641523065210100314620ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.simple_msg_count_mpsc_limits) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_msg_count_mpsc_limits/main.cpp000066400000000000000000000043761523065210100303630ustar00rootroot00000000000000/* * A simple test for message delivery tracing on limitful MPSC mbox. */ #include #include #include #include "../simple_tracer.hpp" struct start : public so_5::signal_t {}; struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx + limit_then_abort< start >( 1 ) + limit_then_abort< finish >( 1 ) } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_start ); so_subscribe_self().event( &a_test_t::evt_finish ); } private : void evt_start(mhood_t< start >) { so_5::send< finish >( *this ); } void evt_finish(mhood_t< finish >) { so_deregister_agent_coop_normally(); } }; class a_request_initator_t : public so_5::agent_t { public : a_request_initator_t( context_t ctx, so_5::mbox_t other_mbox ) : so_5::agent_t{ ctx } , m_other_mbox{ std::move( other_mbox ) } {} virtual void so_evt_start() override { so_5::send< start >( m_other_mbox ); } private : const so_5::mbox_t m_other_mbox; }; void init( so_5::environment_t & env ) { env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), []( so_5::coop_t & coop ) { auto a_test = coop.make_agent< a_test_t >(); coop.make_agent< a_request_initator_t >( a_test->so_direct_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); } ); const unsigned int expected_value = 4; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20, "simple tracing for limitless MPSC-mboxes" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_msg_count_mpsc_no_limits/000077500000000000000000000000001523065210100274155ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_msg_count_mpsc_no_limits/CMakeLists.txt000066400000000000000000000001671523065210100321610ustar00rootroot00000000000000set(UNITTEST _unit.test.msg_tracing.simple_msg_count_mpsc_no_limits) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_msg_count_mpsc_no_limits/main.cpp000066400000000000000000000042641523065210100310530ustar00rootroot00000000000000/* * A simple test for message delivery tracing on limitless MPSC mbox. */ #include #include #include #include "../simple_tracer.hpp" struct start : public so_5::signal_t {}; struct finish : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } {} virtual void so_define_agent() override { so_subscribe_self().event( &a_test_t::evt_start ); so_subscribe_self().event( &a_test_t::evt_finish ); } private : void evt_start(mhood_t< start >) { so_5::send< finish >( *this ); } void evt_finish(mhood_t< finish >) { so_deregister_agent_coop_normally(); } }; class a_request_initator_t : public so_5::agent_t { public : a_request_initator_t( context_t ctx, so_5::mbox_t other_mbox ) : so_5::agent_t{ ctx } , m_other_mbox{ std::move( other_mbox ) } {} virtual void so_evt_start() override { so_5::send< start >( m_other_mbox ); } private : const so_5::mbox_t m_other_mbox; }; void init( so_5::environment_t & env ) { env.introduce_coop( so_5::disp::active_obj::make_dispatcher( env ).binder(), []( so_5::coop_t & coop ) { auto a_test = coop.make_agent< a_test_t >(); coop.make_agent< a_request_initator_t >( a_test->so_direct_mbox() ); } ); } int main() { try { run_with_time_limit( []() { counter_t counter = { 0 }; so_5::launch( &init, [&counter]( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::tracer_unique_ptr_t{ new tracer_t{ counter, so_5::msg_tracing::std_cout_tracer() } } ); } ); const unsigned int expected_value = 4; auto actual_value = counter.load( std::memory_order_acquire ); if( expected_value != actual_value ) throw std::runtime_error( "Unexpected count of trace messages: " "expected=" + std::to_string(expected_value) + ", actual=" + std::to_string(actual_value) ); }, 20, "simple tracing for limitless MPSC-mboxes" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/msg_tracing/simple_tracer.hpp000066400000000000000000000010641523065210100243120ustar00rootroot00000000000000#pragma once #include #include using counter_t = std::atomic< unsigned int >; class tracer_t : public so_5::msg_tracing::tracer_t { public : tracer_t( counter_t & counter, so_5::msg_tracing::tracer_unique_ptr_t actual_tracer ) : m_counter( counter ) , m_actual_tracer( std::move( actual_tracer ) ) {} virtual void trace( const std::string & message ) noexcept override { ++m_counter; m_actual_tracer->trace( message ); } private : counter_t & m_counter; so_5::msg_tracing::tracer_unique_ptr_t m_actual_tracer; }; sobjectizer-5.8.6/dev/test/so_5/mutable_msg/000077500000000000000000000000001523065210100207515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/CMakeLists.txt000066400000000000000000000010561523065210100235130ustar00rootroot00000000000000project(tests) add_subdirectory(receive_immutable) add_subdirectory(receive_mutable) add_subdirectory(receive_mutable_lambda) add_subdirectory(mutable_to_mpmc_mbox) add_subdirectory(mutable_redirect) add_subdirectory(mutable_to_immutable) add_subdirectory(delayed_mutable) add_subdirectory(delayed_mutable_to_immutable) add_subdirectory(delayed_to_mpmc_mbox) add_subdirectory(periodic) add_subdirectory(periodic_to_mpmc_mbox) add_subdirectory(delayed_mutable_with_timer_id) add_subdirectory(simple_mchain) add_subdirectory(subscr_to_mutable_from_mpmc_mbox) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_mutable/000077500000000000000000000000001523065210100240715ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_mutable/CMakeLists.txt000066400000000000000000000001471523065210100266330ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.delayed_mutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_mutable/main.cpp000066400000000000000000000056361523065210100255330ustar00rootroot00000000000000/* * A test for sending delayed mutable messages. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct hello final : public so_5::message_t { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &sobj_message_tester::on_hello ); } virtual void so_evt_start() override { so_5::send_delayed< so_5::mutable_msg< hello > >( *this, std::chrono::milliseconds(50), "hello" ); } private : const hello * m_received_ptr{ nullptr }; void on_hello( mhood_t< so_5::mutable_msg< hello > > cmd ) { std::cout << "sobj: " << cmd->m_content << std::endl; if( !m_received_ptr ) { m_received_ptr = cmd.get(); cmd->m_content = "bye"; so_5::send_delayed( so_direct_mbox(), std::chrono::milliseconds(25), std::move(cmd) ); } else { ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } } }; class user_message_tester final : public so_5::agent_t { struct hello final { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &user_message_tester::on_hello ); } virtual void so_evt_start() override { so_5::send_delayed< so_5::mutable_msg< hello > >( *this, std::chrono::milliseconds(50), "hello" ); } private : const hello * m_received_ptr{ nullptr }; void on_hello( mhood_t< so_5::mutable_msg< hello > > cmd ) { std::cout << "user: " << cmd->m_content << std::endl; if( !m_received_ptr ) { m_received_ptr = cmd.get(); cmd->m_content = "bye"; so_5::send_delayed( so_direct_mbox(), std::chrono::milliseconds(25), std::move(cmd) ); } else { ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_mutable_to_immutable/000077500000000000000000000000001523065210100266325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_mutable_to_immutable/CMakeLists.txt000066400000000000000000000001641523065210100313730ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.delayed_mutable_to_immutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_mutable_to_immutable/main.cpp000066400000000000000000000063301523065210100302640ustar00rootroot00000000000000/* * A test for sending delayed mutable and resending it as immutable messages. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct hello final : public so_5::message_t { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &sobj_message_tester::on_hello_mut ); so_subscribe_self().event( &sobj_message_tester::on_hello_imm ); } virtual void so_evt_start() override { so_5::send_delayed< so_5::mutable_msg< hello > >( *this, std::chrono::milliseconds(50), "hello" ); } private : const hello * m_received_ptr{ nullptr }; void on_hello_mut( mhood_t< so_5::mutable_msg< hello > > cmd ) { std::cout << "sobj: " << cmd->m_content << std::endl; m_received_ptr = cmd.get(); cmd->m_content = "bye"; so_5::send_delayed( so_direct_mbox(), std::chrono::milliseconds(25), to_immutable( std::move(cmd) ) ); } void on_hello_imm( mhood_t< hello > cmd ) { std::cout << "sobj: " << cmd->m_content << std::endl; ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } }; class user_message_tester final : public so_5::agent_t { struct hello final { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &user_message_tester::on_hello_mut ); so_subscribe_self().event( &user_message_tester::on_hello_imm ); } virtual void so_evt_start() override { so_5::send_delayed< so_5::mutable_msg< hello > >( *this, std::chrono::milliseconds(50), "hello" ); } private : const hello * m_received_ptr{ nullptr }; void on_hello_mut( mhood_t< so_5::mutable_msg< hello > > cmd ) { std::cout << "user: " << cmd->m_content << std::endl; m_received_ptr = cmd.get(); cmd->m_content = "bye"; so_5::send_delayed( so_direct_mbox(), std::chrono::milliseconds(25), to_immutable( std::move(cmd) ) ); } void on_hello_imm( mhood_t< hello > cmd ) { std::cout << "user: " << cmd->m_content << std::endl; ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_mutable_with_timer_id/000077500000000000000000000000001523065210100270005ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_mutable_with_timer_id/CMakeLists.txt000066400000000000000000000001651523065210100315420ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.delayed_mutable_with_timer_id) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_mutable_with_timer_id/main.cpp000066400000000000000000000062571523065210100304420ustar00rootroot00000000000000/* * A test for sending delayed mutable messages. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct hello final : public so_5::message_t { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &sobj_message_tester::on_hello ); } virtual void so_evt_start() override { m_timer = so_5::send_periodic< so_5::mutable_msg< hello > >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds::zero(), "hello" ); } private : so_5::timer_id_t m_timer; const hello * m_received_ptr{ nullptr }; void on_hello( mhood_t< so_5::mutable_msg< hello > > cmd ) { std::cout << "sobj: " << cmd->m_content << std::endl; if( !m_received_ptr ) { m_received_ptr = cmd.get(); cmd->m_content = "bye"; m_timer = so_5::send_periodic( so_direct_mbox(), std::chrono::milliseconds(25), std::chrono::milliseconds::zero(), std::move(cmd) ); } else { ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } } }; class user_message_tester final : public so_5::agent_t { struct hello final { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &user_message_tester::on_hello ); } virtual void so_evt_start() override { m_timer = so_5::send_periodic< so_5::mutable_msg< hello > >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds::zero(), "hello" ); } private : so_5::timer_id_t m_timer; const hello * m_received_ptr{ nullptr }; void on_hello( mhood_t< so_5::mutable_msg< hello > > cmd ) { std::cout << "user: " << cmd->m_content << std::endl; if( !m_received_ptr ) { m_received_ptr = cmd.get(); cmd->m_content = "bye"; m_timer = so_5::send_periodic( so_direct_mbox(), std::chrono::milliseconds(25), std::chrono::milliseconds::zero(), std::move(cmd) ); } else { ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_to_mpmc_mbox/000077500000000000000000000000001523065210100251235ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_to_mpmc_mbox/CMakeLists.txt000066400000000000000000000001541523065210100276630ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.delayed_to_mpmc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/delayed_to_mpmc_mbox/main.cpp000066400000000000000000000045361523065210100265630ustar00rootroot00000000000000/* * A test for sending delayed mutable message to mpmc mbox. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct first final : public so_5::message_t {}; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) , m_mbox( so_environment().create_mbox() ) { } virtual void so_evt_start() override { try { so_5::send_delayed< so_5::mutable_msg >( m_mbox, std::chrono::milliseconds(50) ); ensure( false, "an exception must be thrown before this point (sobj)" ); } catch( const so_5::exception_t & x ) { ensure( so_5::rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox == x.error_code(), "an rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox expected" ); } so_deregister_agent_coop_normally(); } private : const so_5::mbox_t m_mbox; }; class user_message_tester final : public so_5::agent_t { struct first final {}; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) , m_mbox( so_environment().create_mbox() ) { } virtual void so_evt_start() override { try { so_5::send_delayed< so_5::mutable_msg >( m_mbox, std::chrono::milliseconds(50) ); ensure( false, "an exception must be thrown before this point (user)" ); } catch( const so_5::exception_t & x ) { ensure( so_5::rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox == x.error_code(), "an rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox expected" ); } so_deregister_agent_coop_normally(); } private : const so_5::mbox_t m_mbox; }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/mutable_redirect/000077500000000000000000000000001523065210100242635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/mutable_redirect/CMakeLists.txt000066400000000000000000000001501523065210100270170ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.mutable_redirect) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/mutable_redirect/main.cpp000066400000000000000000000052671523065210100257250ustar00rootroot00000000000000/* * A test for redirection of mutable messages. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct hello final : public so_5::message_t { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &sobj_message_tester::on_hello ); } virtual void so_evt_start() override { so_5::send< so_5::mutable_msg< hello > >( *this, "hello" ); } private : const hello * m_received_ptr{ nullptr }; void on_hello( mhood_t< so_5::mutable_msg< hello > > cmd ) { std::cout << "sobj: " << cmd->m_content << std::endl; if( !m_received_ptr ) { m_received_ptr = cmd.get(); cmd->m_content = "bye"; send( *this, std::move(cmd) ); } else { ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } } }; class user_message_tester final : public so_5::agent_t { struct hello final { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &user_message_tester::on_hello ); } virtual void so_evt_start() override { so_5::send< so_5::mutable_msg< hello > >( *this, "hello" ); } private : const hello * m_received_ptr{ nullptr }; void on_hello( mhood_t< so_5::mutable_msg< hello > > cmd ) { std::cout << "user: " << cmd->m_content << std::endl; if( !m_received_ptr ) { m_received_ptr = cmd.get(); cmd->m_content = "bye"; send( *this, std::move(cmd) ); } else { ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/mutable_to_immutable/000077500000000000000000000000001523065210100251435ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/mutable_to_immutable/CMakeLists.txt000066400000000000000000000001541523065210100277030ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.mutable_to_immutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/mutable_to_immutable/main.cpp000066400000000000000000000063641523065210100266040ustar00rootroot00000000000000/* * A test for redirection of mutable as immutable messages. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct hello final : public so_5::message_t { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &sobj_message_tester::on_hello_mut ); so_subscribe_self().event( &sobj_message_tester::on_hello_imm ); } virtual void so_evt_start() override { so_5::send< so_5::mutable_msg< hello > >( *this, "hello" ); } private : const hello * m_received_ptr{ nullptr }; void on_hello_mut( mhood_t< so_5::mutable_msg< hello > > cmd ) { ensure( so_5::message_mutability_t::mutable_message == message_mutability( *cmd ), "mutable message is expected" ); std::cout << "sobj: " << cmd->m_content << std::endl; m_received_ptr = cmd.get(); cmd->m_content = "bye"; send( *this, to_immutable( std::move(cmd) ) ); } void on_hello_imm( mhood_t< hello > cmd ) { ensure( so_5::message_mutability_t::immutable_message == message_mutability( *cmd ), "immutable message is expected" ); std::cout << "sobj: " << cmd->m_content << std::endl; ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } }; class user_message_tester final : public so_5::agent_t { struct hello final { std::string m_content; hello( std::string content ) : m_content( std::move(content) ) {} }; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &user_message_tester::on_hello_mut ); so_subscribe_self().event( &user_message_tester::on_hello_imm ); } virtual void so_evt_start() override { so_5::send< so_5::mutable_msg< hello > >( *this, "hello" ); } private : const hello * m_received_ptr{ nullptr }; void on_hello_mut( mhood_t< so_5::mutable_msg< hello > > cmd ) { std::cout << "user: " << cmd->m_content << std::endl; m_received_ptr = cmd.get(); cmd->m_content = "bye"; send( *this, to_immutable( std::move(cmd) ) ); } void on_hello_imm( mhood_t< hello > cmd ) { std::cout << "user: " << cmd->m_content << std::endl; ensure( m_received_ptr == cmd.get(), "expect the same message" ); ensure( "bye" == cmd->m_content, "expect 'bye' message" ); so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/mutable_to_mpmc_mbox/000077500000000000000000000000001523065210100251455ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/mutable_to_mpmc_mbox/CMakeLists.txt000066400000000000000000000001541523065210100277050ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.mutable_to_mpmc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/mutable_to_mpmc_mbox/main.cpp000066400000000000000000000043421523065210100266000ustar00rootroot00000000000000/* * A test for sending mutable message to mpmc mbox. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct first final : public so_5::message_t {}; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) , m_mbox( so_environment().create_mbox() ) { } virtual void so_evt_start() override { try { so_5::send< so_5::mutable_msg >(m_mbox); ensure( false, "an exception must be thrown before this point" ); } catch( const so_5::exception_t & x ) { ensure( so_5::rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox == x.error_code(), "an rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox expected" ); } so_deregister_agent_coop_normally(); } private : const so_5::mbox_t m_mbox; }; class user_message_tester final : public so_5::agent_t { struct first final {}; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) , m_mbox( so_environment().create_mbox() ) { } virtual void so_evt_start() override { try { so_5::send< so_5::mutable_msg >(m_mbox); ensure( false, "an exception must be thrown before this point" ); } catch( const so_5::exception_t & x ) { ensure( so_5::rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox == x.error_code(), "an rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox expected" ); } so_deregister_agent_coop_normally(); } private : const so_5::mbox_t m_mbox; }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/periodic/000077500000000000000000000000001523065210100225475ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/periodic/CMakeLists.txt000066400000000000000000000001401523065210100253020ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.periodic) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/periodic/main.cpp000066400000000000000000000044421523065210100242030ustar00rootroot00000000000000/* * A test for sending delayed mutable message to mpmc mbox. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct first final : public so_5::message_t {}; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { } virtual void so_evt_start() override { try { m_timer = so_5::send_periodic< so_5::mutable_msg >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds(25) ); ensure( false, "an exception must be thrown before this point (sobj)" ); } catch( const so_5::exception_t & x ) { ensure( so_5::rc_mutable_msg_cannot_be_periodic == x.error_code(), "an rc_mutable_msg_cannot_be_periodic expected" ); } so_deregister_agent_coop_normally(); } private : so_5::timer_id_t m_timer; }; class user_message_tester final : public so_5::agent_t { struct first final {}; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { } virtual void so_evt_start() override { try { m_timer = so_5::send_periodic< so_5::mutable_msg >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds(25) ); ensure( false, "an exception must be thrown before this point (user)" ); } catch( const so_5::exception_t & x ) { ensure( so_5::rc_mutable_msg_cannot_be_periodic == x.error_code(), "an rc_mutable_msg_cannot_be_periodic expected" ); } so_deregister_agent_coop_normally(); } private : so_5::timer_id_t m_timer; }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/periodic_to_mpmc_mbox/000077500000000000000000000000001523065210100253125ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/periodic_to_mpmc_mbox/CMakeLists.txt000066400000000000000000000001551523065210100300530ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.periodic_to_mpmc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/periodic_to_mpmc_mbox/main.cpp000066400000000000000000000047731523065210100267550ustar00rootroot00000000000000/* * A test for sending periodic mutable message to mpmc mbox. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct first final : public so_5::message_t {}; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) , m_mbox( so_environment().create_mbox() ) { } virtual void so_evt_start() override { try { m_timer = so_5::send_periodic< so_5::mutable_msg >( m_mbox, std::chrono::milliseconds(50), std::chrono::milliseconds::zero() ); ensure( false, "an exception must be thrown before this point (sobj)" ); } catch( const so_5::exception_t & x ) { ensure( so_5::rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox == x.error_code(), "an rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox expected" ); } so_deregister_agent_coop_normally(); } private : const so_5::mbox_t m_mbox; so_5::timer_id_t m_timer; }; class user_message_tester final : public so_5::agent_t { struct first final {}; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) , m_mbox( so_environment().create_mbox() ) { } virtual void so_evt_start() override { try { m_timer = so_5::send_periodic< so_5::mutable_msg >( m_mbox, std::chrono::milliseconds(50), std::chrono::milliseconds::zero() ); ensure( false, "an exception must be thrown before this point (user)" ); } catch( const so_5::exception_t & x ) { ensure( so_5::rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox == x.error_code(), "an rc_mutable_msg_cannot_be_delivered_via_mpmc_mbox expected" ); } so_deregister_agent_coop_normally(); } private : const so_5::mbox_t m_mbox; so_5::timer_id_t m_timer; }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/receive_immutable/000077500000000000000000000000001523065210100244325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/receive_immutable/CMakeLists.txt000066400000000000000000000001511523065210100271670ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.receive_immutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/receive_immutable/main.cpp000066400000000000000000000063361523065210100260720ustar00rootroot00000000000000/* * A test for receive immutable message. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct first final : public so_5::message_t {}; struct second final : public so_5::message_t {}; struct third final : public so_5::message_t {}; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event(&sobj_message_tester::on_first); so_subscribe_self().event(&sobj_message_tester::on_second); so_subscribe_self().event(&sobj_message_tester::on_third); } virtual void so_evt_start() override { so_5::send>(*this); } private : void on_first( const first & ) { std::cout << "so-first" << std::endl; so_5::send( *this ); } void on_second( mhood_t ) { std::cout << "so-second" << std::endl; so_5::send( *this ); } void on_third( mhood_t> ) { std::cout << "so-third" << std::endl; so_deregister_agent_coop_normally(); } }; class user_message_tester final : public so_5::agent_t { struct first final {}; struct second final {}; struct third final {}; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event(&user_message_tester::on_first); so_subscribe_self().event(&user_message_tester::on_second); so_subscribe_self().event(&user_message_tester::on_third); } virtual void so_evt_start() override { so_5::send>(*this); } private : void on_first( const first & ) { std::cout << "u-first" << std::endl; so_5::send( *this ); } void on_second( mhood_t ) { std::cout << "u-second" << std::endl; so_5::send( *this ); } void on_third( mhood_t> ) { std::cout << "u-third" << std::endl; so_deregister_agent_coop_normally(); } }; class signal_tester final : public so_5::agent_t { struct first final : public so_5::signal_t {}; struct second final : public so_5::signal_t {}; public : signal_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event(&signal_tester::on_first); so_subscribe_self().event(&signal_tester::on_second); } virtual void so_evt_start() override { so_5::send>(*this); } private : void on_first( mhood_t ) { std::cout << "s-first" << std::endl; so_5::send( *this ); } void on_second( mhood_t> ) { std::cout << "s-second" << std::endl; so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/receive_mutable/000077500000000000000000000000001523065210100241045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/receive_mutable/CMakeLists.txt000066400000000000000000000001471523065210100266460ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.receive_mutable) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/receive_mutable/main.cpp000066400000000000000000000065531523065210100255450ustar00rootroot00000000000000/* * A test for receive mutable message. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct first final : public so_5::message_t {}; struct second final : public so_5::message_t {}; struct third final : public so_5::message_t {}; struct fourth final : public so_5::message_t {}; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event(&sobj_message_tester::on_first); so_subscribe_self().event(&sobj_message_tester::on_second); so_subscribe_self().event(&sobj_message_tester::on_third); so_subscribe_self().event(&sobj_message_tester::on_fourth); } virtual void so_evt_start() override { so_5::send>(*this); so_5::send>(*this); so_5::send>(*this); so_5::send>(*this); } private : void on_first( const first & ) { throw std::runtime_error( "sobj_message_tester::on_first must not be called!" ); } void on_second( mhood_t ) { throw std::runtime_error( "sobj_message_tester::on_second must not be called!" ); } void on_third( mhood_t> ) { throw std::runtime_error( "sobj_message_tester::on_third must not be called!" ); } void on_fourth( mhood_t> ) { so_deregister_agent_coop_normally(); } }; class user_message_tester final : public so_5::agent_t { struct first final {}; struct second final {}; struct third final {}; struct fourth final {}; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event(&user_message_tester::on_first); so_subscribe_self().event(&user_message_tester::on_second); so_subscribe_self().event(&user_message_tester::on_third); so_subscribe_self().event(&user_message_tester::on_fourth); } virtual void so_evt_start() override { so_5::send>(*this); so_5::send>(*this); so_5::send>(*this); so_5::send>(*this); } private : void on_first( const first & ) { throw std::runtime_error( "user_message_tester::on_first must not be called!" ); } void on_second( mhood_t ) { throw std::runtime_error( "user_message_tester::on_second must not be called!" ); } void on_third( mhood_t> ) { throw std::runtime_error( "user_message_tester::on_third must not be called!" ); } void on_fourth( mhood_t> ) { so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/receive_mutable_lambda/000077500000000000000000000000001523065210100254045ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/receive_mutable_lambda/CMakeLists.txt000066400000000000000000000001561523065210100301460ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.receive_mutable_lambda) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/receive_mutable_lambda/main.cpp000066400000000000000000000062151523065210100270400ustar00rootroot00000000000000/* * A test for receive mutable message. */ #include #include #include #include using namespace std; class sobj_message_tester final : public so_5::agent_t { struct first final : public so_5::message_t {}; struct second final : public so_5::message_t {}; struct third final : public so_5::message_t {}; struct fourth final : public so_5::message_t {}; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( [](const first &) { throw std::runtime_error( "sobj_message_tester::on_first must not be called!" ); } ); so_subscribe_self().event( [](mhood_t) { throw std::runtime_error( "sobj_message_tester::on_second must not be called!" ); } ); so_subscribe_self().event( [](mhood_t>) { throw std::runtime_error( "sobj_message_tester::on_third must not be called!" ); } ); so_subscribe_self().event( [this](mhood_t>) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send>(*this); so_5::send>(*this); so_5::send>(*this); so_5::send>(*this); } }; class user_message_tester final : public so_5::agent_t { struct first final {}; struct second final {}; struct third final {}; struct fourth final {}; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( [](const first &) { throw std::runtime_error( "user_message_tester::on_first must not be called!" ); } ); // This code caused internal compiler error on GCC < 5.1.0 #if !(defined(__GNUC__) && (__GNUC__ < 5)) so_subscribe_self().event( [](mhood_t) { throw std::runtime_error( "user_message_tester::on_second must not be called!" ); } ); so_subscribe_self().event( [](mhood_t>) { throw std::runtime_error( "user_message_tester::on_third must not be called!" ); } ); #endif so_subscribe_self().event( [this](mhood_t>) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { so_5::send>(*this); so_5::send>(*this); so_5::send>(*this); so_5::send>(*this); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/simple_mchain/000077500000000000000000000000001523065210100235615ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/simple_mchain/CMakeLists.txt000066400000000000000000000001451523065210100263210ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.simple_mchain) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/simple_mchain/main.cpp000066400000000000000000000064131523065210100252150ustar00rootroot00000000000000/* * A test for mutable/immutable messages and mchain. */ #include #include #include #include using namespace std; void do_test( so_5::environment_t & env ) { struct sobj_hello final : public so_5::message_t { string m_msg; sobj_hello( string msg ) : m_msg( std::move(msg) ) {} }; struct user_hello final { string m_msg; user_hello( string msg ) : m_msg( std::move(msg) ) {} }; struct sig_hello final : public so_5::signal_t {}; auto ch = create_mchain( env ); so_5::send< sobj_hello >( ch, "sh1" ); so_5::send< so_5::mutable_msg >( ch, "sh2" ); so_5::send< so_5::immutable_msg >( ch, "sh3" ); so_5::send< user_hello >( ch, "uh1" ); so_5::send< so_5::immutable_msg >( ch, "uh2" ); so_5::send< so_5::mutable_msg >( ch, "uh3" ); so_5::send< sig_hello >( ch ); so_5::send< so_5::immutable_msg >( ch ); so_5::send_delayed< so_5::mutable_msg >( ch, std::chrono::milliseconds(200), "shd" ); so_5::send_delayed< so_5::mutable_msg >( ch, std::chrono::milliseconds(205), "uhd" ); auto t1 = so_5::send_periodic< so_5::mutable_msg >( ch, std::chrono::milliseconds(210), std::chrono::milliseconds::zero(), "shp" ); auto t2 = so_5::send_periodic< so_5::mutable_msg >( ch, std::chrono::milliseconds(215), std::chrono::milliseconds::zero(), "uhp" ); const auto send_mutable_periodic_helper = [](so_5::mchain_t chain_to_send, std::chrono::milliseconds pause, std::chrono::milliseconds period, const char * str) { auto t = so_5::send_periodic< so_5::mutable_msg >( chain_to_send, pause, period, str ); }; UT_CHECK_THROW( so_5::exception_t, send_mutable_periodic_helper( ch, std::chrono::milliseconds(220), std::chrono::milliseconds(200), "shp2" ) ); UT_CHECK_THROW( so_5::exception_t, send_mutable_periodic_helper( ch, std::chrono::milliseconds(225), std::chrono::milliseconds(200), "uhp2" ) ); string collector; auto append_immutable = [&collector](const string & what) { collector += "imm(" + what + ");"; }; auto append_mutable = [&collector](const string & what) { collector += "mut(" + what + ");"; }; receive( from(ch).handle_n(12), [&]( so_5::mhood_t cmd ) { append_immutable( cmd->m_msg ); }, [&]( so_5::mhood_t< so_5::mutable_msg > cmd ) { append_mutable( cmd->m_msg ); }, [&]( so_5::mhood_t cmd ) { append_immutable( cmd->m_msg ); }, [&]( so_5::mhood_t< so_5::mutable_msg > cmd ) { append_mutable( cmd->m_msg ); }, [&]( so_5::mhood_t /*cmd*/ ) { append_immutable( "sig" ); } ); UT_CHECK_EQ( collector, "imm(sh1);mut(sh2);imm(sh3);" "imm(uh1);imm(uh2);mut(uh3);" "imm(sig);imm(sig);" "mut(shd);mut(uhd);" "mut(shp);mut(uhp);" ); } int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t sobj; do_test( sobj.environment() ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/mutable_msg/subscr_to_mutable_from_mpmc_mbox/000077500000000000000000000000001523065210100275515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/mutable_msg/subscr_to_mutable_from_mpmc_mbox/CMakeLists.txt000066400000000000000000000001701523065210100323070ustar00rootroot00000000000000set(UNITTEST _unit.test.mutable_msg.subscr_to_mutable_from_mpmc_mbox) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/mutable_msg/subscr_to_mutable_from_mpmc_mbox/main.cpp000066400000000000000000000053161523065210100312060ustar00rootroot00000000000000/* * A test for subscription to mutable message from mpmc mbox. */ #include #include #include #include using namespace std; template< typename L > void ensure_subscription_error( const std::string & test_case, L && lambda ) { try { lambda(); ensure( false, test_case + ": an exception must be thrown before this point" ); } catch( const so_5::exception_t & x ) { ensure( so_5::rc_subscription_to_mutable_msg_from_mpmc_mbox == x.error_code(), test_case + "an rc_subscription_to_mutable_msg_from_mpmc_mbox expected" ); } } class sobj_message_tester final : public so_5::agent_t { struct first final : public so_5::message_t {}; public : sobj_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) , m_mbox( so_environment().create_mbox() ) { } virtual void so_evt_start() override { ensure_subscription_error( "sobj_message_tester(lambda-event-handler)", [this]{ so_subscribe(m_mbox).event( []( mutable_mhood_t ) {} ); } ); ensure_subscription_error( "sobj_message_tester(method-event-handler)", [this]{ so_subscribe(m_mbox).event( &sobj_message_tester::on_first ); } ); so_deregister_agent_coop_normally(); } private : const so_5::mbox_t m_mbox; void on_first( mutable_mhood_t ) {} }; class user_message_tester final : public so_5::agent_t { struct first final {}; public : user_message_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) , m_mbox( so_environment().create_mbox() ) { } virtual void so_evt_start() override { ensure_subscription_error( "user_message_tester(lambda-event-handler)", [this]{ so_subscribe(m_mbox).event( []( mutable_mhood_t ) {} ); } ); ensure_subscription_error( "user_message_tester(method-event-handler)", [this]{ so_subscribe(m_mbox).event( &user_message_tester::on_first ); } ); so_deregister_agent_coop_normally(); } private : const so_5::mbox_t m_mbox; void on_first( mutable_mhood_t ) {} }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent()); env.register_agent_as_coop( env.make_agent()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/spinlocks/000077500000000000000000000000001523065210100204575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/spinlocks/llvm_inspired_test/000077500000000000000000000000001523065210100243655ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/spinlocks/llvm_inspired_test/CMakeLists.txt000066400000000000000000000001501523065210100271210ustar00rootroot00000000000000set(UNITTEST _unit.test.spinlocks.llvm_inspired_test) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/spinlocks/llvm_inspired_test/main.cpp000066400000000000000000000052601523065210100260200ustar00rootroot00000000000000/* * This test is inspired by advice of Dmitry Vyukov and is based on * test implementation from LLVM code base: * http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/tsan/unit_tests/tsan_mutex_test.cc?view=markup&pathrev=164021 */ #include #include #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif template< typename M, typename WRITE_LOCK, typename READ_LOCK > class TestData { public: explicit TestData( M & m ) : mtx_(m) { for (int i = 0; i < kSize; i++) data_[i] = 0; } void Write() { WRITE_LOCK l(mtx_); T v0 = data_[0]; for (int i = 0; i < kSize; i++) { UT_CHECK_EQ(data_[i], v0); data_[i]++; } } void Read() { READ_LOCK l(mtx_); T v0 = data_[0]; for (int i = 0; i < kSize; i++) { UT_CHECK_EQ(data_[i], v0); } } void Backoff() { volatile T data[kSize] = {}; for (int i = 0; i < kSize; i++) { data[i] = data[i]+1; UT_CHECK_EQ(data[i], 1); } } private: static const int kCacheLineSize = 64; static const int kSize = 64; typedef std::int64_t T; M & mtx_; char pad_[kCacheLineSize]; T data_[kSize]; }; const int kThreads = 8; const int kWriteRate = 1024; const int kIters = 64*1024; template< typename T > void write_mutex_thread( T * data ) { for (int i = 0; i < kIters; i++) { data->Write(); data->Backoff(); } } template< typename T > void read_mutex_thread( T * data ) { for (int i = 0; i < kIters; i++) { if ((i % kWriteRate) == 0) data->Write(); else data->Read(); data->Backoff(); } } template< typename THREAD_FUNC, typename THREAD_ARG > void run_test_threads( THREAD_FUNC func, THREAD_ARG * arg ) { std::thread threads[kThreads]; for( int i = 0; i != kThreads; ++i ) threads[i] = std::thread( func, arg ); for( int i = 0; i != kThreads; ++i ) threads[i].join(); } UT_UNIT_TEST(Spinlock_Write) { so_5::default_spinlock_t lock; TestData< so_5::default_spinlock_t, std::lock_guard< so_5::default_spinlock_t >, std::lock_guard< so_5::default_spinlock_t > > data( lock ); run_test_threads( write_mutex_thread< decltype(data) >, &data ); } UT_UNIT_TEST(RWSpinlock_ReadWrite) { so_5::default_rw_spinlock_t lock; TestData< so_5::default_rw_spinlock_t, std::lock_guard< so_5::default_rw_spinlock_t >, so_5::read_lock_guard_t< so_5::default_rw_spinlock_t > > data( lock ); run_test_threads( read_mutex_thread< decltype(data) >, &data ); } int main() { UT_RUN_UNIT_TEST( Spinlock_Write ) UT_RUN_UNIT_TEST( RWSpinlock_ReadWrite ) } sobjectizer-5.8.6/dev/test/so_5/state/000077500000000000000000000000001523065210100175725ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/CMakeLists.txt000066400000000000000000000013021523065210100223260ustar00rootroot00000000000000add_subdirectory(state_comparison) add_subdirectory(change_state) add_subdirectory(composite_state_change) add_subdirectory(enter_exit_handlers) add_subdirectory(const_enter_exit_handlers) add_subdirectory(noexcept_enter_exit_handlers) add_subdirectory(enter_exit_current_state_ptr) add_subdirectory(on_exit_on_dereg_1) add_subdirectory(on_exit_on_dereg_2) add_subdirectory(nesting_deep) add_subdirectory(parent_state_handler) add_subdirectory(suppress_event) add_subdirectory(state_history) add_subdirectory(state_history_clear) add_subdirectory(transfer_to_state) add_subdirectory(transfer_to_state_loop) add_subdirectory(just_switch_to) add_subdirectory(state_switch_guard) add_subdirectory(time_limit) sobjectizer-5.8.6/dev/test/so_5/state/change_state/000077500000000000000000000000001523065210100222175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/change_state/CMakeLists.txt000066400000000000000000000001361523065210100247570ustar00rootroot00000000000000set(UNITTEST _unit.test.state.change_state) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/change_state/main.cpp000066400000000000000000000074361523065210100236610ustar00rootroot00000000000000/* * A test for messages handling in difference states. */ #include #include #include #include struct test_message : public so_5::signal_t {}; class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; const so_5::state_t m_first_state{ this }; const so_5::state_t m_second_state{ this }; const so_5::state_t m_third_state{ this }; public: test_agent_t( so_5::environment_t & env ) : base_type_t( env ), m_test_mbox( so_environment().create_mbox() ) {} ~test_agent_t() override {} void so_define_agent() override; void so_evt_start() override; void evt_in_state_default( mhood_t< test_message > ); void evt_in_state_1( mhood_t< test_message > ); void evt_in_state_2( mhood_t< test_message > ); void evt_in_state_3( mhood_t< test_message > ); // Count for event handlers calls. static int m_handler_in_state_default_calls; static int m_handler_in_state_1_calls; static int m_handler_in_state_2_calls; static int m_handler_in_state_3_calls; private: so_5::mbox_t m_test_mbox; }; int test_agent_t::m_handler_in_state_default_calls = 0; int test_agent_t::m_handler_in_state_1_calls = 0; int test_agent_t::m_handler_in_state_2_calls = 0; int test_agent_t::m_handler_in_state_3_calls = 0; void test_agent_t::so_define_agent() { // Subscribe to message in default state... so_subscribe( m_test_mbox ) .event( &test_agent_t::evt_in_state_default ); // ...in first state... so_subscribe( m_test_mbox ) .in( m_first_state ) .event( &test_agent_t::evt_in_state_1 ); // ...in second state... so_subscribe( m_test_mbox ) .in( m_second_state ) .event( &test_agent_t::evt_in_state_2 ); // ...in third state. so_subscribe( m_test_mbox ) .in( m_third_state ) .event( &test_agent_t::evt_in_state_3 ); } void test_agent_t::so_evt_start() { so_5::send< test_message >( m_test_mbox ); } void test_agent_t::evt_in_state_default( mhood_t< test_message > ) { ++m_handler_in_state_default_calls; so_5::send< test_message >( m_test_mbox ); // Change state after message has been sent. so_change_state( m_first_state ); } void test_agent_t::evt_in_state_1( mhood_t< test_message > ) { ++m_handler_in_state_1_calls; // Change state before message will be sent. so_change_state( m_second_state ); so_5::send< test_message >( m_test_mbox ); } void test_agent_t::evt_in_state_2( mhood_t< test_message > ) { ++m_handler_in_state_2_calls; // Change state before message will be sent. so_change_state( m_third_state ); so_5::send< test_message >( m_test_mbox ); } void test_agent_t::evt_in_state_3( mhood_t< test_message > ) { ++m_handler_in_state_3_calls; std::cout << "Stop\n"; // Shutting down. so_environment().stop(); } void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); } int main() { try { so_5::launch( &init ); if( test_agent_t::m_handler_in_state_default_calls != 1 || test_agent_t::m_handler_in_state_1_calls != 1 || test_agent_t::m_handler_in_state_2_calls != 1 || test_agent_t::m_handler_in_state_3_calls != 1 ) { std::cerr << "test_agent_t::m_handler_in_state_default_calls = " << test_agent_t::m_handler_in_state_default_calls << "\n" << "test_agent_t::m_handler_in_state_1_calls = " << test_agent_t::m_handler_in_state_1_calls << "\n" << "test_agent_t::m_handler_in_state_2_calls = " << test_agent_t::m_handler_in_state_2_calls << "\n" << "test_agent_t::m_handler_in_state_3_calls = " << test_agent_t::m_handler_in_state_3_calls << "\n"; throw std::runtime_error( "handler calls count error" ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/composite_state_change/000077500000000000000000000000001523065210100243015ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/composite_state_change/CMakeLists.txt000066400000000000000000000001501523065210100270350ustar00rootroot00000000000000set(UNITTEST _unit.test.state.composite_state_change) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/composite_state_change/main.cpp000066400000000000000000000055551523065210100257430ustar00rootroot00000000000000/* * A very simple test case for changing state to a composite state. */ #include #include #include #include class a_test_t final : public so_5::agent_t { state_t st_top_1{ this, "top_1" }; state_t st_child_1_1{ initial_substate_of{ st_top_1 }, "child_1" }; state_t st_child_1_2{ substate_of{ st_top_1 }, "child_2" }; state_t st_child_1_1_1{ initial_substate_of{ st_child_1_1 }, "1" }; state_t st_child_1_1_2{ substate_of{ st_child_1_1 }, "2" }; state_t st_child_1_1_3{ substate_of{ st_child_1_1 }, "3" }; state_t st_top_2{ this, "top_2" }; state_t st_child_2_1{ substate_of{ st_top_2 }, "child_1" }; state_t st_child_2_2{ substate_of{ st_top_2 }, "child_2" }; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { this >>= st_top_1; } virtual void so_evt_start() override { ensure( st_child_1_1_1 == so_current_state(), "unexpected current state, expected: " + st_child_1_1_1.query_name() + ", actual: " + so_current_state().query_name() ); ensure( so_is_active_state( st_child_1_1_1 ) && st_child_1_1_1.is_active(), "state st_child_1_1_1 is expected to be active" ); ensure( so_is_active_state( st_child_1_1 ) && st_child_1_1.is_active(), "state st_child_1_1 is expected to be active" ); ensure( so_is_active_state( st_top_1 ) && st_top_1.is_active(), "state st_top_1 is expected to be active" ); ensure( !so_is_active_state( st_top_2 ) && !st_top_2.is_active(), "state st_top_2 is expected not to be active" ); ensure( !so_is_active_state( st_child_1_1_3 ) && !st_child_1_1_3.is_active(), "state st_child_1_1_3 is expected not to be active" ); ensure( !so_is_active_state( st_child_2_2 ) && !st_child_2_2.is_active(), "state st_child_2_2 is expected not to be active" ); try { this >>= st_top_2; throw std::runtime_error( "exception must be throw on attempt " "to change state to st_top_2!" ); } catch( const so_5::exception_t & ex ) { std::cout << "Exception: " << ex.what() << std::endl; if( so_5::rc_no_initial_substate != ex.error_code() ) throw std::runtime_error( "expected error_code: " + std::to_string( ex.error_code() ) ); } so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for switching to a composite state" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/const_enter_exit_handlers/000077500000000000000000000000001523065210100250265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/const_enter_exit_handlers/CMakeLists.txt000066400000000000000000000001531523065210100275650ustar00rootroot00000000000000set(UNITTEST _unit.test.state.const_enter_exit_handlers) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/const_enter_exit_handlers/main.cpp000066400000000000000000000040431523065210100264570ustar00rootroot00000000000000/* * A test for constant on_exit/on_enter handlers. */ #include #include #include #include class a_supervisor_t final : public so_5::agent_t { state_t first_state_entered{ this, "first_state_entered" }; public : struct first_state_enter : public so_5::signal_t {}; struct first_state_exit : public so_5::signal_t {}; a_supervisor_t( context_t ctx ) : so_5::agent_t(std::move(ctx)) { so_default_state().event( &a_supervisor_t::on_enter ); first_state_entered.event( &a_supervisor_t::on_exit ); } private : void on_enter(mhood_t) { this >>= first_state_entered; } void on_exit(mhood_t) { so_deregister_agent_coop_normally(); } }; class a_test_t final : public so_5::agent_t { state_t first_state{ this, "first" }; const so_5::mbox_t m_supervisor; public : a_test_t( context_t ctx, so_5::mbox_t supervisor ) : so_5::agent_t{ ctx } , m_supervisor{ std::move(supervisor) } { first_state.on_enter( &a_test_t::on_enter ); first_state.on_exit( &a_test_t::on_exit ); } virtual void so_evt_start() override { this >>= first_state; this >>= so_default_state(); } private : void on_enter() const { so_5::send( m_supervisor ); } void on_exit() const { so_5::send( m_supervisor ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto supervisor = coop.make_agent< a_supervisor_t >(); coop.make_agent< a_test_t >( supervisor->so_direct_mbox() ); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/enter_exit_current_state_ptr/000077500000000000000000000000001523065210100255675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/enter_exit_current_state_ptr/CMakeLists.txt000066400000000000000000000001561523065210100303310ustar00rootroot00000000000000set(UNITTEST _unit.test.state.enter_exit_current_state_ptr) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/enter_exit_current_state_ptr/main.cpp000066400000000000000000000043551523065210100272260ustar00rootroot00000000000000/* * A test for checking the value of so_current_state() in * on_enter/on_exit state handlers. */ #include #include #include #include class a_test_t final : public so_5::agent_t { state_t st_top_1{ this, "top_1" }; state_t st_child_1_1{ initial_substate_of{ st_top_1 }, "child_1" }; state_t st_child_1_2{ substate_of{ st_top_1 }, "child_2" }; state_t st_child_1_1_1{ initial_substate_of{ st_child_1_1 }, "1" }; state_t st_child_1_1_2{ substate_of{ st_child_1_1 }, "2" }; state_t st_child_1_1_3{ substate_of{ st_child_1_1 }, "3" }; state_t st_top_2{ this, "top_2" }; state_t st_child_2_1{ initial_substate_of{ st_top_2 }, "child_1" }; state_t st_child_2_2{ substate_of{ st_top_2 }, "child_2" }; void setup_handlers( state_t & to ) { to.on_enter( [this, &to]{ ensure( to == so_current_state(), to.query_name() + ": on_enter check failed!" ); } ); to.on_exit( [this, &to]{ ensure( to == so_current_state(), to.query_name() + ": on_exit check failed!" ); } ); } public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } {} virtual void so_evt_start() override { std::array states{ { &st_top_1, &st_child_1_1, &st_child_1_2, &st_child_1_1_1, &st_child_1_1_2, &st_child_1_1_3, &st_top_2, &st_child_2_1, &st_child_2_2 } }; for( auto s : states ) setup_handlers( *s ); this >>= st_child_1_1_3; ensure( st_child_1_1_3 == so_current_state(), "st_child_1_1_3 expected as current state" ); this >>= st_child_2_2; ensure( st_child_2_2 == so_current_state(), "st_child_2_2 expected as current state" ); so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/enter_exit_handlers/000077500000000000000000000000001523065210100236205ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/enter_exit_handlers/CMakeLists.txt000066400000000000000000000001451523065210100263600ustar00rootroot00000000000000set(UNITTEST _unit.test.state.enter_exit_handlers) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/enter_exit_handlers/main.cpp000066400000000000000000000142721523065210100252560ustar00rootroot00000000000000/* * A very simple test case for checking on_enter and on_exit handlers. */ #include #include #include class a_only_top_level_states_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; state_t st_three{ this, "three" }; public : a_only_top_level_states_t( context_t ctx ) : so_5::agent_t{ ctx } { st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ); st_three .on_enter( [this] { m_log += "+3"; } ) .on_exit( [this] { m_log += "-3"; } ); } virtual void so_evt_start() override { this >>= st_one; this >>= st_two; this >>= st_three; this >>= so_default_state(); const std::string expected = "+1-1+2-2+3-3"; if( expected != m_log ) throw std::runtime_error( expected + " != " + m_log ); so_deregister_agent_coop_normally(); } private : std::string m_log; }; class a_substates_of_one_state_t final : public so_5::agent_t { state_t st_one{ this }; state_t st_two{ this }; state_t st_one_1_1{ initial_substate_of{ st_one } }; state_t st_one_1_1_1{ initial_substate_of{ st_one_1_1 } }; state_t st_one_1_1_2{ substate_of{ st_one_1_1 } }; state_t st_one_1_2{ substate_of{ st_one } }; state_t st_one_1_2_1{ initial_substate_of{ st_one_1_2 } }; state_t st_one_1_2_2{ substate_of{ st_one_1_2 } }; public : a_substates_of_one_state_t( context_t ctx ) : so_5::agent_t{ ctx } { st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ); st_one_1_1 .on_enter( [this] { m_log += "+1_1"; } ) .on_exit( [this] { m_log += "-1_1"; } ); st_one_1_1_1 .on_enter( [this] { m_log += "+1_1_1"; } ) .on_exit( [this] { m_log += "-1_1_1"; } ); st_one_1_1_2 .on_enter( [this] { m_log += "+1_1_2"; } ) .on_exit( [this] { m_log += "-1_1_2"; } ); st_one_1_2 .on_enter( [this] { m_log += "+1_2"; } ) .on_exit( [this] { m_log += "-1_2"; } ); st_one_1_2_1 .on_enter( [this] { m_log += "+1_2_1"; } ) .on_exit( [this] { m_log += "-1_2_1"; } ); st_one_1_2_2 .on_enter( [this] { m_log += "+1_2_2"; } ) .on_exit( [this] { m_log += "-1_2_2"; } ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ); } virtual void so_evt_start() override { this >>= st_one; this >>= st_one_1_2_2; this >>= so_default_state(); const std::string expected = "+1+1_1+1_1_1-1_1_1-1_1+1_2+1_2_2" "-1_2_2-1_2-1"; if( expected != m_log ) throw std::runtime_error( expected + " != " + m_log ); so_deregister_agent_coop_normally(); } private : std::string m_log; }; class a_substates_of_two_state_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; state_t st_one_1_1{ initial_substate_of{ st_one } }; state_t st_one_1_1_1{ initial_substate_of{ st_one_1_1 } }; state_t st_one_1_1_2{ substate_of{ st_one_1_1 } }; state_t st_one_1_2{ substate_of{ st_one } }; state_t st_one_1_2_1{ initial_substate_of{ st_one_1_2 } }; state_t st_one_1_2_2{ substate_of{ st_one_1_2 } }; state_t st_two_1_1{ initial_substate_of{ st_two } }; state_t st_two_1_1_1{ initial_substate_of{ st_two_1_1 } }; state_t st_two_1_1_2{ substate_of{ st_two_1_1 } }; state_t st_two_1_2{ substate_of{ st_two } }; state_t st_two_1_2_1{ initial_substate_of{ st_two_1_2 } }; state_t st_two_1_2_2{ substate_of{ st_two_1_2 } }; public : a_substates_of_two_state_t( context_t ctx ) : so_5::agent_t{ ctx } { st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ); st_one_1_1 .on_enter( [this] { m_log += "+1_1"; } ) .on_exit( [this] { m_log += "-1_1"; } ); st_one_1_1_1 .on_enter( [this] { m_log += "+1_1_1"; } ) .on_exit( [this] { m_log += "-1_1_1"; } ); st_one_1_1_2 .on_enter( [this] { m_log += "+1_1_2"; } ) .on_exit( [this] { m_log += "-1_1_2"; } ); st_one_1_2 .on_enter( [this] { m_log += "+1_2"; } ) .on_exit( [this] { m_log += "-1_2"; } ); st_one_1_2_1 .on_enter( [this] { m_log += "+1_2_1"; } ) .on_exit( [this] { m_log += "-1_2_1"; } ); st_one_1_2_2 .on_enter( [this] { m_log += "+1_2_2"; } ) .on_exit( [this] { m_log += "-1_2_2"; } ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ); st_two_1_1 .on_enter( [this] { m_log += "+2_1"; } ) .on_exit( [this] { m_log += "-2_1"; } ); st_two_1_1_1 .on_enter( [this] { m_log += "+2_1_1"; } ) .on_exit( [this] { m_log += "-2_1_1"; } ); st_two_1_1_2 .on_enter( [this] { m_log += "+2_1_2"; } ) .on_exit( [this] { m_log += "-2_1_2"; } ); st_two_1_2 .on_enter( [this] { m_log += "+2_2"; } ) .on_exit( [this] { m_log += "-2_2"; } ); st_two_1_2_1 .on_enter( [this] { m_log += "+2_2_1"; } ) .on_exit( [this] { m_log += "-2_2_1"; } ); st_two_1_2_2 .on_enter( [this] { m_log += "+2_2_2"; } ) .on_exit( [this] { m_log += "-2_2_2"; } ); } virtual void so_evt_start() override { this >>= st_one; this >>= st_two_1_2_2; this >>= so_default_state(); const std::string expected = "+1+1_1+1_1_1-1_1_1-1_1-1+2+2_2+2_2_2" "-2_2_2-2_2-2"; if( expected != m_log ) throw std::runtime_error( expected + " != " + m_log ); so_deregister_agent_coop_normally(); } private : std::string m_log; }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_only_top_level_states_t >(); } ); env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_substates_of_one_state_t >(); } ); env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_substates_of_two_state_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for on_enter and on_exit handlers" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/just_switch_to/000077500000000000000000000000001523065210100226425ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/just_switch_to/CMakeLists.txt000066400000000000000000000001401523065210100253750ustar00rootroot00000000000000set(UNITTEST _unit.test.state.just_switch_to) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/just_switch_to/main.cpp000066400000000000000000000067471523065210100243100ustar00rootroot00000000000000/* * A very simple test case for checking transfer_to_state. */ #include #include #include class a_transfer_message_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; state_t st_three{ this, "three" }; struct message { int m_value; }; struct bye {}; struct finish : public so_5::signal_t {}; public : a_transfer_message_t( context_t ctx ) : so_5::agent_t{ ctx } { so_default_state() .event( &a_transfer_message_t::evt_finish ); st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ) .transfer_to_state< message >( st_two ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ) .transfer_to_state< message >( st_three ); st_three .on_enter( [this] { m_log += "+3"; } ) .on_exit( [this] { m_log += "-3"; } ) .event( [this]( const message & msg ) { m_log += "{m:" + std::to_string( msg.m_value ) + "}"; so_5::send< bye >( *this ); so_5::send< finish >( *this ); } ) .just_switch_to< bye >( so_default_state() ); } virtual void so_evt_start() override { this >>= st_one; so_5::send< message >( *this, 42 ); } private : std::string m_log; void evt_finish(mhood_t< finish >) { const std::string expected = "+1-1+2-2+3{m:42}-3"; if( expected != m_log ) throw std::runtime_error( expected + " != " + m_log ); so_deregister_agent_coop_normally(); } }; class a_transfer_signal_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; state_t st_three{ this, "three" }; struct signal : public so_5::signal_t {}; struct bye : public so_5::signal_t {}; struct finish : public so_5::signal_t {}; public : a_transfer_signal_t( context_t ctx ) : so_5::agent_t{ ctx } { so_default_state() .event( &a_transfer_signal_t::evt_finish ); st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ) .transfer_to_state< signal >( st_two ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ) .transfer_to_state< signal >( st_three ); st_three .on_enter( [this] { m_log += "+3"; } ) .on_exit( [this] { m_log += "-3"; } ) .event( [this](mhood_t< signal >) { m_log += "{s}"; so_5::send< bye >( *this ); so_5::send< finish >( *this ); } ) .just_switch_to< bye >( so_default_state() ); } virtual void so_evt_start() override { this >>= st_one; so_5::send< signal >( *this ); } private : std::string m_log; void evt_finish(mhood_t< finish >) { const std::string expected = "+1-1+2-2+3{s}-3"; if( expected != m_log ) throw std::runtime_error( expected + " != " + m_log ); so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_transfer_message_t >(); } ); env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_transfer_signal_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for transfer_to_state" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/nesting_deep/000077500000000000000000000000001523065210100222365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/nesting_deep/CMakeLists.txt000066400000000000000000000001361523065210100247760ustar00rootroot00000000000000set(UNITTEST _unit.test.state.nesting_deep) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/nesting_deep/main.cpp000066400000000000000000000040421523065210100236660ustar00rootroot00000000000000/* * A very simple test case for checking deep of agent state nesting. */ #include #include #include class a_test_t final : public so_5::agent_t { struct sig_1 : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { m_states.push_back( state_unique_ptr{ new state_t{ this, "s" } } ); for( std::size_t i = 1; i < state_t::max_deep; ++i ) { state_unique_ptr s{ new state_t{ initial_substate_of{ *m_states.back() }, "s" } }; m_states.push_back( std::move(s) ); } try { state_unique_ptr s{ new state_t{ initial_substate_of{ *m_states.back() } } }; m_states.push_back( std::move(s) ); throw std::runtime_error( "exception must be throw on attempt " "to create another nested state!" ); } catch( const so_5::exception_t & ex ) { std::cout << "Exception: " << ex.what() << std::endl; if( so_5::rc_state_nesting_is_too_deep != ex.error_code() ) throw std::runtime_error( "expected error_code: " + std::to_string( ex.error_code() ) ); } m_states.front()->event( [this](mhood_t< sig_1 >) { so_deregister_agent_coop_normally(); } ); this >>= *m_states.front(); } virtual void so_evt_start() override { std::cout << so_current_state().query_name() << std::endl; so_5::send< sig_1 >( *this ); } private : using state_unique_ptr = std::unique_ptr< state_t >; std::vector< state_unique_ptr > m_states; }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for too deep nesting of agent states" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/noexcept_enter_exit_handlers/000077500000000000000000000000001523065210100255255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/noexcept_enter_exit_handlers/CMakeLists.txt000066400000000000000000000001561523065210100302670ustar00rootroot00000000000000set(UNITTEST _unit.test.state.noexcept_enter_exit_handlers) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/noexcept_enter_exit_handlers/main.cpp000066400000000000000000000040571523065210100271630ustar00rootroot00000000000000/* * A test for noexcept on_exit/on_enter handlers. */ #include #include #include #include class a_supervisor_t final : public so_5::agent_t { state_t first_state_entered{ this, "first_state_entered" }; public : struct first_state_enter : public so_5::signal_t {}; struct first_state_exit : public so_5::signal_t {}; a_supervisor_t( context_t ctx ) : so_5::agent_t(std::move(ctx)) { so_default_state().event( &a_supervisor_t::on_enter ); first_state_entered.event( &a_supervisor_t::on_exit ); } private : void on_enter(mhood_t) { this >>= first_state_entered; } void on_exit(mhood_t) { so_deregister_agent_coop_normally(); } }; class a_test_t final : public so_5::agent_t { state_t first_state{ this, "first" }; const so_5::mbox_t m_supervisor; public : a_test_t( context_t ctx, so_5::mbox_t supervisor ) : so_5::agent_t{ ctx } , m_supervisor{ std::move(supervisor) } { first_state.on_enter( &a_test_t::on_enter ); first_state.on_exit( &a_test_t::on_exit ); } virtual void so_evt_start() override { this >>= first_state; this >>= so_default_state(); } private : void on_enter() noexcept { so_5::send( m_supervisor ); } void on_exit() const noexcept { so_5::send( m_supervisor ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { auto supervisor = coop.make_agent< a_supervisor_t >(); coop.make_agent< a_test_t >( supervisor->so_direct_mbox() ); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/on_exit_on_dereg_1/000077500000000000000000000000001523065210100233215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/on_exit_on_dereg_1/CMakeLists.txt000066400000000000000000000001441523065210100260600ustar00rootroot00000000000000set(UNITTEST _unit.test.state.on_exit_on_dereg_1) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/on_exit_on_dereg_1/main.cpp000066400000000000000000000070031523065210100247510ustar00rootroot00000000000000/* * A test for on_exit handlers on coop deregistration. */ #include #include #include #include class a_only_top_level_states_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; state_t st_three{ this, "three" }; public : a_only_top_level_states_t( context_t ctx, std::string & log ) : so_5::agent_t( ctx ) , m_log( log ) { st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ); st_three .on_enter( [this] { m_log += "+3"; } ) .on_exit( [this] { m_log += "-3"; } ); } virtual void so_evt_start() override { this >>= st_two; so_deregister_agent_coop_normally(); } private : std::string & m_log; }; class a_substates_of_one_state_t final : public so_5::agent_t { state_t st_one{ this }; state_t st_two{ this }; state_t st_one_1_1{ initial_substate_of{ st_one } }; state_t st_one_1_1_1{ initial_substate_of{ st_one_1_1 } }; state_t st_one_1_1_2{ substate_of{ st_one_1_1 } }; state_t st_one_1_2{ substate_of{ st_one } }; state_t st_one_1_2_1{ initial_substate_of{ st_one_1_2 } }; state_t st_one_1_2_2{ substate_of{ st_one_1_2 } }; public : a_substates_of_one_state_t( context_t ctx, std::string & log ) : so_5::agent_t( ctx ) , m_log( log ) { st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ); st_one_1_1 .on_enter( [this] { m_log += "+1_1"; } ) .on_exit( [this] { m_log += "-1_1"; } ); st_one_1_1_1 .on_enter( [this] { m_log += "+1_1_1"; } ) .on_exit( [this] { m_log += "-1_1_1"; } ); st_one_1_1_2 .on_enter( [this] { m_log += "+1_1_2"; } ) .on_exit( [this] { m_log += "-1_1_2"; } ); st_one_1_2 .on_enter( [this] { m_log += "+1_2"; } ) .on_exit( [this] { m_log += "-1_2"; } ); st_one_1_2_1 .on_enter( [this] { m_log += "+1_2_1"; } ) .on_exit( [this] { m_log += "-1_2_1"; } ); st_one_1_2_2 .on_enter( [this] { m_log += "+1_2_2"; } ) .on_exit( [this] { m_log += "-1_2_2"; } ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ); } virtual void so_evt_start() override { this >>= st_one_1_2_2; so_deregister_agent_coop_normally(); } private : std::string & m_log; }; int main() { try { run_with_time_limit( []() { std::string log1; std::string log2; so_5::launch( [&]( so_5::environment_t & env ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_only_top_level_states_t >( std::ref(log1)); } ); env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_substates_of_one_state_t >( std::ref(log2)); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); const std::string expected1 = "+2-2"; const std::string expected2 = "+1+1_2+1_2_2-1_2_2-1_2-1"; ensure( expected1 == log1, "unexpected value of log1: " + log1 + ", expected: " + expected1 ); ensure( expected2 == log2, "unexpected value of log2: " + log2 + ", expected: " + expected2 ); }, 20, "test for calling on_exit on coop dereg" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/on_exit_on_dereg_2/000077500000000000000000000000001523065210100233225ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/on_exit_on_dereg_2/CMakeLists.txt000066400000000000000000000001441523065210100260610ustar00rootroot00000000000000set(UNITTEST _unit.test.state.on_exit_on_dereg_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/on_exit_on_dereg_2/main.cpp000066400000000000000000000077111523065210100247600ustar00rootroot00000000000000/* * A test for on_exit handlers on environment shutdown. */ #include #include #include #include struct complete {}; class a_only_top_level_states_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; state_t st_three{ this, "three" }; public : a_only_top_level_states_t( context_t ctx, std::string & log, so_5::mchain_t notify_ch ) : so_5::agent_t( ctx ) , m_log( log ) , m_notify_ch( std::move(notify_ch) ) { st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ); st_three .on_enter( [this] { m_log += "+3"; } ) .on_exit( [this] { m_log += "-3"; } ); } virtual void so_evt_start() override { this >>= st_two; so_5::send< complete >( m_notify_ch ); } private : std::string & m_log; const so_5::mchain_t m_notify_ch; }; class a_substates_of_one_state_t final : public so_5::agent_t { state_t st_one{ this }; state_t st_two{ this }; state_t st_one_1_1{ initial_substate_of{ st_one } }; state_t st_one_1_1_1{ initial_substate_of{ st_one_1_1 } }; state_t st_one_1_1_2{ substate_of{ st_one_1_1 } }; state_t st_one_1_2{ substate_of{ st_one } }; state_t st_one_1_2_1{ initial_substate_of{ st_one_1_2 } }; state_t st_one_1_2_2{ substate_of{ st_one_1_2 } }; public : a_substates_of_one_state_t( context_t ctx, std::string & log, so_5::mchain_t notify_ch ) : so_5::agent_t( ctx ) , m_log( log ) , m_notify_ch( std::move(notify_ch) ) { st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ); st_one_1_1 .on_enter( [this] { m_log += "+1_1"; } ) .on_exit( [this] { m_log += "-1_1"; } ); st_one_1_1_1 .on_enter( [this] { m_log += "+1_1_1"; } ) .on_exit( [this] { m_log += "-1_1_1"; } ); st_one_1_1_2 .on_enter( [this] { m_log += "+1_1_2"; } ) .on_exit( [this] { m_log += "-1_1_2"; } ); st_one_1_2 .on_enter( [this] { m_log += "+1_2"; } ) .on_exit( [this] { m_log += "-1_2"; } ); st_one_1_2_1 .on_enter( [this] { m_log += "+1_2_1"; } ) .on_exit( [this] { m_log += "-1_2_1"; } ); st_one_1_2_2 .on_enter( [this] { m_log += "+1_2_2"; } ) .on_exit( [this] { m_log += "-1_2_2"; } ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ); } virtual void so_evt_start() override { this >>= st_one_1_2_2; so_5::send< complete >( m_notify_ch ); } private : std::string & m_log; const so_5::mchain_t m_notify_ch; }; int main() { try { run_with_time_limit( []() { std::string log1; std::string log2; so_5::launch( [&]( so_5::environment_t & env ) { auto notify_ch = env.create_mchain( so_5::make_unlimited_mchain_params() ); env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_only_top_level_states_t >( std::ref(log1), notify_ch ); } ); env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent< a_substates_of_one_state_t >( std::ref(log2), notify_ch ); } ); receive( from( notify_ch ).handle_n( 2 ), []( complete ){} ); env.stop(); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); const std::string expected1 = "+2-2"; const std::string expected2 = "+1+1_2+1_2_2-1_2_2-1_2-1"; ensure( expected1 == log1, "unexpected value of log1: " + log1 + ", expected: " + expected1 ); ensure( expected2 == log2, "unexpected value of log2: " + log2 + ", expected: " + expected2 ); }, 20, "test for calling on_exit on environment shutdown" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/parent_state_handler/000077500000000000000000000000001523065210100237605ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/parent_state_handler/CMakeLists.txt000066400000000000000000000001461523065210100265210ustar00rootroot00000000000000set(UNITTEST _unit.test.state.parent_state_handler) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/parent_state_handler/main.cpp000066400000000000000000000034501523065210100254120ustar00rootroot00000000000000/* * A very simple test case for searching event handlers in parent states. */ #include #include #include class a_test_t final : public so_5::agent_t { struct sig_1 : public so_5::signal_t {}; struct sig_2 : public so_5::signal_t {}; struct sig_3 : public so_5::signal_t {}; state_t st_parent{ this, "parent" }; state_t st_child_1{ initial_substate_of{ st_parent }, "child_1" }; state_t st_child_2{ initial_substate_of{ st_child_1 }, "child_2" }; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { this >>= st_child_2; st_parent.event( [this](mhood_t< sig_1 >) { std::cout << "--- sig_1 handler from st_parent ---" << std::endl; so_5::send< sig_2 >( *this ); } ); st_child_1.event( [this](mhood_t< sig_2 >) { std::cout << "--- sig_2 handler from st_child_1 ---" << std::endl; so_5::send< sig_3 >( *this ); } ); st_child_2.event( [this](mhood_t< sig_3 >) { std::cout << "--- sig_3 handler from st_child_2 ---" << std::endl; so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { std::cout << so_current_state().query_name() << std::endl; so_5::send< sig_1 >( *this ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for event handlers from parent state" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/state_comparison/000077500000000000000000000000001523065210100231445ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/state_comparison/CMakeLists.txt000066400000000000000000000001421523065210100257010ustar00rootroot00000000000000set(UNITTEST _unit.test.state.state_comparison) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/state_comparison/main.cpp000066400000000000000000000026761523065210100246070ustar00rootroot00000000000000/* * A very simple test case for checking operator== and operator!= * for agent states. */ #include #include #include #include class a_test_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; state_t st_three{ this, "three" }; public : a_test_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) {} virtual void so_evt_start() override { ensure_or_die( st_one == st_one, "expects st_one == st_one" ); ensure_or_die( st_one != st_two, "expects st_one != st_two" ); ensure_or_die( st_one != st_three, "expects st_one != st_three" ); ensure_or_die( st_one != so_current_state(), "st_one is not the current state" ); this >>= st_one; ensure_or_die( st_one == so_current_state(), "st_one is the current state now" ); so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/state_history/000077500000000000000000000000001523065210100224735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/state_history/CMakeLists.txt000066400000000000000000000001371523065210100252340ustar00rootroot00000000000000set(UNITTEST _unit.test.state.state_history) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/state_history/main.cpp000066400000000000000000000054711523065210100241320ustar00rootroot00000000000000/* * A very simple test case for state history. */ #include #include #include class a_test_t final : public so_5::agent_t { state_t s1{ this, "1" }; state_t s1_1{ initial_substate_of{ s1 }, "1", deep_history }; state_t s1_1_1{ initial_substate_of{ s1_1 }, "1" }; state_t s1_1_1_1{ initial_substate_of{ s1_1_1 }, "1", shallow_history }; state_t s1_1_1_1_1{ initial_substate_of{ s1_1_1_1 }, "1" }; state_t s1_1_1_1_2{ substate_of{ s1_1_1_1 }, "2" }; state_t s1_1_1_1_2_1{ initial_substate_of{ s1_1_1_1_2 }, "1" }; state_t s1_1_1_1_2_2{ substate_of{ s1_1_1_1_2 }, "2" }; state_t s1_1_1_1_2_3{ substate_of{ s1_1_1_1_2 }, "3" }; state_t s1_1_1_1_3{ substate_of{ s1_1_1_1 }, "3" }; state_t s1_1_1_2{ substate_of{ s1_1_1 }, "2" }; state_t s1_1_1_3{ substate_of{ s1_1_1 }, "3" }; state_t s1_2{ substate_of{ s1 }, "2" }; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { this >>= s1; } virtual void so_evt_start() override { this >>= s1_1; expect_current_state( s1_1_1_1_1 ); this >>= s1_2; expect_current_state( s1_2 ); this >>= s1_1_1_1_3; expect_current_state( s1_1_1_1_3 ); this >>= s1_2; expect_current_state( s1_2 ); this >>= s1_1_1_1; expect_current_state( s1_1_1_1_3 ); this >>= s1_2; expect_current_state( s1_2 ); this >>= s1_1_1; expect_current_state( s1_1_1_1_3 ); this >>= s1_1_1_1_2_2; expect_current_state( s1_1_1_1_2_2 ); this >>= s1_2; expect_current_state( s1_2 ); this >>= s1_1; expect_current_state( s1_1_1_1_2_2 ); this >>= s1_1_1_1_3; expect_current_state( s1_1_1_1_3 ); this >>= s1_1_1_1_2_3; expect_current_state( s1_1_1_1_2_3 ); this >>= s1_2; expect_current_state( s1_2 ); this >>= s1_1_1_1; expect_current_state( s1_1_1_1_2_1 ); this >>= s1_1_1_1_2_3; expect_current_state( s1_1_1_1_2_3 ); this >>= s1_2; expect_current_state( s1_2 ); this >>= s1_1; expect_current_state( s1_1_1_1_2_3 ); so_deregister_agent_coop_normally(); } private : void expect_current_state( const state_t & expected ) const { if( !(expected == so_current_state()) ) throw std::runtime_error( "expected state: " + expected.query_name() + ", actual state: " + so_current_state().query_name() ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for state history" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/state_history_clear/000077500000000000000000000000001523065210100236415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/state_history_clear/CMakeLists.txt000066400000000000000000000001451523065210100264010ustar00rootroot00000000000000set(UNITTEST _unit.test.state.state_history_clear) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/state_history_clear/main.cpp000066400000000000000000000043321523065210100252730ustar00rootroot00000000000000/* * A very simple test case for clear state history. */ #include #include #include #include class a_test_t final : public so_5::agent_t { state_t A{ this, "A", deep_history }; state_t B{ initial_substate_of{ A }, "B", shallow_history }; state_t C{ initial_substate_of{ B }, "C" }; state_t D{ substate_of{ B }, "D" }; state_t E{ this, "E" }; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { this >>= A; // The current state is A.B.C. } virtual void so_evt_start() override { expect_current_state( C ); this >>= D; // The current state is "A.B.D". expect_current_state( D ); this >>= E; // The current state is "E". expect_current_state( E ); this >>= A; // The current state is "A.B.D" because deep history of A. expect_current_state( D ); this >>= E; expect_current_state( E ); A.clear_history(); this >>= A; // The current state is "A.B.D" because: // B is the initial substate of A and B has shallow history; // D is the last active substate of B. expect_current_state( D ); this >>= E; expect_current_state( E ); A.clear_history(); B.clear_history(); this >>= A; // The current state is "A.B.C" because: // B is initial substate of A and // C is initial substate of B. expect_current_state( C ); so_deregister_agent_coop_normally(); } private : void expect_current_state( const state_t & expected ) const { ensure( expected == so_current_state(), "expected state: " + expected.query_name() + ", actual state: " + so_current_state().query_name() ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for clear state history" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/state_switch_guard/000077500000000000000000000000001523065210100234555ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/state_switch_guard/CMakeLists.txt000066400000000000000000000001441523065210100262140ustar00rootroot00000000000000set(UNITTEST _unit.test.state.state_switch_guard) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/state_switch_guard/main.cpp000066400000000000000000000033031523065210100251040ustar00rootroot00000000000000/* * Test for checking state_switch_guard. */ #include #include #include #include class a_test_t final : public so_5::agent_t { struct sig_1 : public so_5::signal_t {}; struct finish : public so_5::signal_t {}; state_t st_one{ this }; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { // This approach can lead to infinite loop. st_one.on_enter( [this] { bool exception_thrown = false; try { so_default_state().activate(); } catch( const so_5::exception_t & x ) { if( so_5::rc_another_state_switch_in_progress == x.error_code() ) exception_thrown = true; } ensure_or_die( exception_thrown, "an exception with error_code " "rc_another_state_switch_in_progress must be thrown" ); so_5::send< finish >( *this ); } ) .event( [this](mhood_t< finish >) { so_deregister_agent_coop_normally(); } ); so_default_state().event( [this](mhood_t< sig_1 >) { st_one.activate(); } ); } virtual void so_evt_start() override { so_5::send< sig_1 >( *this ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for state_switch_guard" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/suppress_event/000077500000000000000000000000001523065210100226575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/suppress_event/CMakeLists.txt000066400000000000000000000001401523065210100254120ustar00rootroot00000000000000set(UNITTEST _unit.test.state.suppress_event) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/suppress_event/main.cpp000066400000000000000000000037121523065210100243120ustar00rootroot00000000000000/* * A very simple test case for suppressing event in child substate. */ #include #include #include class a_test_t final : public so_5::agent_t { struct sig_1 : public so_5::signal_t {}; struct sig_2 : public so_5::signal_t {}; struct sig_3 : public so_5::signal_t {}; struct abort_app : public so_5::signal_t {}; state_t st_parent{ this, "parent" }; state_t st_child_1{ initial_substate_of{ st_parent }, "child_1" }; state_t st_child_2{ initial_substate_of{ st_child_1 }, "child_2" }; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { this >>= st_child_2; st_parent.event( [this](mhood_t< sig_1 >) { std::cout << "--- sig_1 handler from st_parent ---" << std::endl; so_5::send< sig_2 >( *this ); } ) .event( [](mhood_t< abort_app >){ std::abort(); } ); st_child_1.event( [this](mhood_t< sig_2 >) { std::cout << "--- sig_2 handler from st_child_1 ---" << std::endl; so_5::send< sig_3 >( *this ); } ) .suppress< abort_app >(); st_child_2.event( [this](mhood_t< sig_3 >) { std::cout << "--- sig_3 handler from st_child_2 ---" << std::endl; so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { std::cout << so_current_state().query_name() << std::endl; so_5::send< abort_app >( *this ); so_5::send< sig_1 >( *this ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for suppressing event in child state" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/time_limit/000077500000000000000000000000001523065210100217265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/time_limit/CMakeLists.txt000066400000000000000000000003001523065210100244570ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(reset_limit) add_subdirectory(many_switches) add_subdirectory(cancel_on_dereg) add_subdirectory(two_time_limits) add_subdirectory(two_time_limits_2) sobjectizer-5.8.6/dev/test/so_5/state/time_limit/cancel_on_dereg/000077500000000000000000000000001523065210100250155ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/time_limit/cancel_on_dereg/CMakeLists.txt000066400000000000000000000001541523065210100275550ustar00rootroot00000000000000set(UNITTEST _unit.test.state.time_limit.cancel_on_dereg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/time_limit/cancel_on_dereg/main.cpp000066400000000000000000000033431523065210100264500ustar00rootroot00000000000000/* * A test case for canceling time_limit on agent's deregistration. */ #include #include #include class a_test_t final : public so_5::agent_t { state_t first{ this, "first" }; struct finish : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { first .time_limit( std::chrono::seconds{5}, so_default_state() ) .event( [this](mhood_t< finish >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { this >>= first; so_5::send< finish >( *this ); } }; class a_pauser_t final : public so_5::agent_t { struct stop : public so_5::signal_t {}; public : a_pauser_t( context_t ctx ) : so_5::agent_t{ ctx } { so_default_state().event( [this](mhood_t< stop >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { std::cout << "Work will be finished after 5s" << std::endl; so_5::send_delayed< stop >( *this, std::chrono::seconds{5} ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { for( std::size_t i = 0u; i != 50000u; ++i ) env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_pauser_t >(); } ); }, []( so_5::environment_params_t & params ) { params.timer_thread( so_5::timer_list_factory() ); } ); }, 240, "test for canceling time_limit on deregistration" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/time_limit/many_switches/000077500000000000000000000000001523065210100246035ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/time_limit/many_switches/CMakeLists.txt000066400000000000000000000001521523065210100273410ustar00rootroot00000000000000set(UNITTEST _unit.test.state.time_limit.many_switches) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/time_limit/many_switches/main.cpp000066400000000000000000000057541523065210100262460ustar00rootroot00000000000000/* * A test case with many switches between states. * This test must not lead to memory consumption, data damages or * any other negative consequences. */ #include #include #include #if defined(__clang__) && (__clang_major__ >= 16) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif class a_test_t final : public so_5::agent_t { state_t first{ this, "first" }; state_t second{ this, "second" }; state_t fatal{ this, "fatal" }; struct change_state : public so_5::signal_t {}; public : a_test_t( context_t ctx, unsigned long long switch_count ) : so_5::agent_t{ ctx } , m_switch_count{ switch_count } { first .time_limit( std::chrono::seconds{1}, fatal ) .event( [this](mhood_t< change_state >) { try_change_state_to( second ); } ); second .event( [this](mhood_t< change_state >) { first.activate(); so_5::send< change_state >( *this ); } ); fatal .on_enter( [this]{ std::cerr << "Agent is switched to the fatal state!" << std::endl; std::cerr << "Switch passed: " << m_switch_passed << std::endl; throw std::runtime_error( "Should not be in this state!" ); } ); } virtual void so_evt_start() override { m_started_at = std::chrono::steady_clock::now(); do_switch( first ); } virtual void so_evt_finish() override { std::cout << "Total switches: " << m_switch_passed << std::endl; } private : const unsigned long long m_switch_count; unsigned long long m_switch_passed{ 0ull }; std::chrono::steady_clock::time_point m_started_at; void do_switch( const so_5::state_t & to ) { this >>= to; so_5::send< change_state >( *this ); } void finish_work() { std::cout << "Work will be finished" << std::endl; so_deregister_agent_coop_normally(); } void try_change_state_to( const so_5::state_t & to ) { ++m_switch_passed; if( 0ull != m_switch_count ) { if( m_switch_passed >= m_switch_count ) finish_work(); else do_switch( to ); } else { auto now = std::chrono::steady_clock::now(); if( now > (m_started_at + std::chrono::seconds{1}) ) finish_work(); else do_switch( to ); } } }; int main( int argc, char ** argv ) { try { unsigned long long switch_count = 0ull; if( 2 == argc ) { switch_count = std::stoull( argv[ 1 ] ); std::cout << "Expected switch count: " << switch_count << std::endl; } run_with_time_limit( [switch_count]() { so_5::launch( [switch_count]( so_5::environment_t & env ) { env.introduce_coop( [switch_count]( so_5::coop_t & coop ) { coop.make_agent< a_test_t >( switch_count ); } ); }, []( so_5::environment_params_t & params ) { params.timer_thread( so_5::timer_list_factory() ); } ); }, 86400, "test for many switches from state to state" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/time_limit/reset_limit/000077500000000000000000000000001523065210100242465ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/time_limit/reset_limit/CMakeLists.txt000066400000000000000000000001501523065210100270020ustar00rootroot00000000000000set(UNITTEST _unit.test.state.time_limit.reset_limit) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/time_limit/reset_limit/main.cpp000066400000000000000000000036161523065210100257040ustar00rootroot00000000000000/* * A test case for reseting time limit for a state. */ #include #include #include class a_test_t final : public so_5::agent_t { state_t first{ this, "first" }; state_t second{ this, "second" }; struct sig_1 : public so_5::signal_t {}; struct sig_2 : public so_5::signal_t {}; struct finish : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { first .on_enter( []{ std::cout << "first on_enter" << std::endl; } ) .on_exit( [this] { std::cout << "first on_exit" << std::endl; so_5::send< finish >( *this ); } ) .time_limit( std::chrono::milliseconds{100}, second ) .event( [this](mhood_t< sig_1 >) { std::cout << "first sig_1, dropping time_limit" << std::endl; first.drop_time_limit(); so_5::send_delayed< sig_2 >( *this, std::chrono::milliseconds{200} ); } ) .event( [this](mhood_t< sig_2 >) { std::cout << "first sig_2, resetting time_limit" << std::endl; first.time_limit( std::chrono::milliseconds{50}, second ); } ); second .on_enter( []{ std::cout << "second on_enter" << std::endl; } ) .event( [this](mhood_t< finish >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { this >>= first; so_5::send< sig_1 >( *this ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 20, "test for resetting state's time_limit" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/time_limit/simple/000077500000000000000000000000001523065210100232175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/time_limit/simple/CMakeLists.txt000066400000000000000000000001431523065210100257550ustar00rootroot00000000000000set(UNITTEST _unit.test.state.time_limit.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/time_limit/simple/main.cpp000066400000000000000000000026371523065210100246570ustar00rootroot00000000000000/* * A very simple test case for checking time limit for agent state. */ #include #include #include class a_test_t final : public so_5::agent_t { state_t first{ this, "first" }; state_t second{ this, "second" }; struct finish : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } { first .on_enter( []{ std::cout << "first on_enter" << std::endl; } ) .on_exit( [this]{ std::cout << "first on_exit" << std::endl; so_5::send< finish >( *this ); } ) .time_limit( std::chrono::milliseconds{20}, second ); second .on_enter( []{ std::cout << "second on_enter" << std::endl; } ) .event( [this](mhood_t< finish >) { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { this >>= first; } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); }, 20, "simple test for state's time_limit" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/time_limit/two_time_limits/000077500000000000000000000000001523065210100251365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/time_limit/two_time_limits/CMakeLists.txt000066400000000000000000000001541523065210100276760ustar00rootroot00000000000000set(UNITTEST _unit.test.state.time_limit.two_time_limits) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/time_limit/two_time_limits/main.cpp000066400000000000000000000047011523065210100265700ustar00rootroot00000000000000/* * Test for checking time_limit for parent and child states. */ #include #include #include namespace test { class a_test_t final : public so_5::agent_t { state_t top_one{ this, "top_one" }; state_t child_one{ initial_substate_of{ top_one }, "child_one" }; state_t child_two{ substate_of{ top_one }, "child_two" }; state_t top_two{ this, "top_two" }; struct make_pause final : public so_5::signal_t {}; struct finish final : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } {} void so_define_agent() override { top_one .on_enter( [this]{ std::cout << "top_one on_enter" << std::endl; so_5::send< make_pause >( *this ); } ) .on_exit( []{ std::cout << "top_one on_exit" << std::endl; } ) .time_limit( std::chrono::milliseconds{ 200 }, top_two ) .event( []( mhood_t ) { // Suspend the agent for some time. std::this_thread::sleep_for( std::chrono::milliseconds{ 250 } ); } ) ; child_one .on_enter( []{ std::cout << "child_one on_enter" << std::endl; } ) .on_exit( []{ std::cout << "child_one on_exit" << std::endl; } ) .time_limit( std::chrono::milliseconds{ 100 }, child_two ) ; child_two .on_enter( []{ std::cout << "child_two on_enter" << std::endl; throw std::runtime_error{ "unexpected enter into child_two state" }; } ) .on_exit( []{ std::cout << "child_two on_exit" << std::endl; } ) ; top_two .on_enter( [this]{ std::cout << "top_two on_enter" << std::endl; so_5::send< finish >( *this ); } ) .event( [this](mhood_t< finish >) { so_deregister_agent_coop_normally(); } ) ; } void so_evt_start() override { this >>= top_one; } }; } /* namespace test */ using namespace test; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }, []( so_5::environment_params_t & params ) { // More precise timer than timer_wheel. params.timer_thread( so_5::timer_list_factory() ); #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/time_limit/two_time_limits_2/000077500000000000000000000000001523065210100253575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/time_limit/two_time_limits_2/CMakeLists.txt000066400000000000000000000001561523065210100301210ustar00rootroot00000000000000set(UNITTEST _unit.test.state.time_limit.two_time_limits_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/time_limit/two_time_limits_2/main.cpp000066400000000000000000000067351523065210100270220ustar00rootroot00000000000000/* * Test for checking time_limit for parent and child states. */ #include #include #include namespace test { class a_test_t final : public so_5::agent_t { state_t top_one{ this, "top_one" }; state_t child_one{ initial_substate_of{ top_one }, "child_one" }; state_t child_two{ substate_of{ top_one }, "child_two" }; state_t top_two{ this, "top_two" }; state_t top_three{ this, "top_three" }; struct make_pause final : public so_5::signal_t {}; struct finish final : public so_5::signal_t {}; std::chrono::steady_clock::time_point m_top_two_enter_at; static const std::chrono::steady_clock::duration top_two_time_limit; public : a_test_t( context_t ctx ) : so_5::agent_t{ ctx } {} void so_define_agent() override { top_one .on_enter( [this]{ std::cout << "top_one on_enter" << std::endl; so_5::send< make_pause >( *this ); } ) .on_exit( []{ std::cout << "top_one on_exit" << std::endl; } ) .time_limit( std::chrono::milliseconds{ 200 }, top_two ) .event( []( mhood_t ) { // Suspend the agent for some time. std::this_thread::sleep_for( std::chrono::milliseconds{ 250 } ); } ) ; child_one .on_enter( []{ std::cout << "child_one on_enter" << std::endl; } ) .on_exit( []{ std::cout << "child_one on_exit" << std::endl; } ) .time_limit( std::chrono::milliseconds{ 100 }, child_two ) ; child_two .on_enter( []{ std::cout << "child_two on_enter" << std::endl; throw std::runtime_error{ "unexpected enter into child_two state" }; } ) .on_exit( []{ std::cout << "child_two on_exit" << std::endl; } ) ; top_two .on_enter( [this]{ std::cout << "top_two on_enter" << std::endl; m_top_two_enter_at = std::chrono::steady_clock::now(); } ) .on_exit( [this] { const auto now = std::chrono::steady_clock::now(); std::cout << "top_two on_exit" << std::endl; if( m_top_two_enter_at + top_two_time_limit > now ) { const auto elapsed = now - m_top_two_enter_at; using namespace std::chrono; const std::string message = "timeout for top_two is too small: " + std::to_string( duration_cast< microseconds >( elapsed ).count() ) + "us"; std::cout << message << std::endl; throw std::runtime_error{ message }; } } ) .time_limit( top_two_time_limit, top_three ) ; top_three .on_enter( [this]{ std::cout << "top_three on_enter" << std::endl; so_5::send< finish >( *this ); } ) .event( [this](mhood_t< finish >) { so_deregister_agent_coop_normally(); } ) ; } void so_evt_start() override { this >>= top_one; } }; const std::chrono::steady_clock::duration a_test_t::top_two_time_limit = std::chrono::milliseconds{ 30 }; } /* namespace test */ using namespace test; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_test_t >(); } ); }, []( so_5::environment_params_t & params ) { // More precise timer than timer_wheel. params.timer_thread( so_5::timer_list_factory() ); #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 20 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/transfer_to_state/000077500000000000000000000000001523065210100233205ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/transfer_to_state/CMakeLists.txt000066400000000000000000000001431523065210100260560ustar00rootroot00000000000000set(UNITTEST _unit.test.state.transfer_to_state) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/transfer_to_state/main.cpp000066400000000000000000000065241523065210100247570ustar00rootroot00000000000000/* * A very simple test case for checking transfer_to_state. */ #include #include #include class a_transfer_message_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; state_t st_three{ this, "three" }; struct message { int m_value; }; struct finish : public so_5::signal_t {}; public : a_transfer_message_t( context_t ctx ) : so_5::agent_t{ ctx } { so_default_state() .event( &a_transfer_message_t::evt_finish ); st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ) .transfer_to_state< message >( st_two ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ) .transfer_to_state< message >( st_three ); st_three .on_enter( [this] { m_log += "+3"; } ) .on_exit( [this] { m_log += "-3"; } ) .event( [this]( const message & msg ) { m_log += "{m:" + std::to_string( msg.m_value ) + "}"; so_default_state().activate(); so_5::send< finish >( *this ); } ); } virtual void so_evt_start() override { this >>= st_one; so_5::send< message >( *this, 42 ); } private : std::string m_log; void evt_finish(mhood_t< finish >) { const std::string expected = "+1-1+2-2+3{m:42}-3"; if( expected != m_log ) throw std::runtime_error( expected + " != " + m_log ); so_deregister_agent_coop_normally(); } }; class a_transfer_signal_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; state_t st_three{ this, "three" }; struct signal : public so_5::signal_t {}; struct finish : public so_5::signal_t {}; public : a_transfer_signal_t( context_t ctx ) : so_5::agent_t{ ctx } { so_default_state() .event( &a_transfer_signal_t::evt_finish ); st_one .on_enter( [this] { m_log += "+1"; } ) .on_exit( [this] { m_log += "-1"; } ) .transfer_to_state< signal >( st_two ); st_two .on_enter( [this] { m_log += "+2"; } ) .on_exit( [this] { m_log += "-2"; } ) .transfer_to_state< signal >( st_three ); st_three .on_enter( [this] { m_log += "+3"; } ) .on_exit( [this] { m_log += "-3"; } ) .event( [this](mhood_t< signal >) { m_log += "{s}"; so_default_state().activate(); so_5::send< finish >( *this ); } ); } virtual void so_evt_start() override { this >>= st_one; so_5::send< signal >( *this ); } private : std::string m_log; void evt_finish(mhood_t< finish >) { const std::string expected = "+1-1+2-2+3{s}-3"; if( expected != m_log ) throw std::runtime_error( expected + " != " + m_log ); so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_transfer_message_t >(); } ); env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< a_transfer_signal_t >(); } ); }/*, []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); }*/ ); }, 20, "simple test for transfer_to_state" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/state/transfer_to_state_loop/000077500000000000000000000000001523065210100243515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/state/transfer_to_state_loop/CMakeLists.txt000066400000000000000000000001501523065210100271050ustar00rootroot00000000000000set(UNITTEST _unit.test.state.transfer_to_state_loop) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/state/transfer_to_state_loop/main.cpp000066400000000000000000000066101523065210100260040ustar00rootroot00000000000000/* * A very simple test case for checking transfer_to_state. */ #include #include #include #include class special_exception_logger_t : public so_5::event_exception_logger_t { so_5::optional & m_storage; so_5::event_exception_logger_unique_ptr_t m_prev; public: special_exception_logger_t( so_5::outliving_reference_t< so_5::optional > storage) : m_storage( storage.get() ) {} void log_exception( const std::exception & ex, const so_5::coop_handle_t & coop ) noexcept override { if( m_prev ) m_prev->log_exception( ex, coop ); const auto p = dynamic_cast(&ex); if( p ) m_storage = p->error_code(); } void on_install( so_5::event_exception_logger_unique_ptr_t prev ) noexcept override { m_prev = std::move(prev); } }; template void run_with_expected_error( int expected_error ) { so_5::optional actual_error; run_with_time_limit( [&]() { so_5::launch( []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< Agent_Type >(); } ); }, [&]( so_5::environment_params_t & params ) { params.exception_reaction( so_5::exception_reaction_t::shutdown_sobjectizer_on_exception ); params.event_exception_logger( std::make_unique< special_exception_logger_t >( so_5::outliving_mutable(actual_error) ) ); params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } ); ensure_or_die( actual_error && expected_error == *actual_error, "transfer_to_state must fail with error: " + std::to_string(expected_error) ); }, 20 ); } class a_simple_case_t final : public so_5::agent_t { state_t st_base{ this, "base" }; state_t st_disconnected{ initial_substate_of{st_base}, "disconnected" }; state_t st_connected{ substate_of{st_base}, "connected" }; struct message {}; public : a_simple_case_t(context_t ctx) : so_5::agent_t{ctx} { this >>= st_base; st_base.transfer_to_state(st_disconnected); } virtual void so_evt_start() override { so_5::send(*this); } }; class a_tricky_loop_t final : public so_5::agent_t { state_t st_base{ this, "base" }; state_t st_first{ initial_substate_of{st_base}, "first" }; struct message {}; public : a_tricky_loop_t(context_t ctx) : so_5::agent_t{ctx} { this >>= st_first; st_base.transfer_to_state(st_base); } virtual void so_evt_start() override { so_5::send(*this); } }; class a_two_state_loop_t final : public so_5::agent_t { state_t st_one{ this, "one" }; state_t st_two{ this, "two" }; struct message {}; public : a_two_state_loop_t(context_t ctx) : so_5::agent_t{ctx} { this >>= st_one; st_one.transfer_to_state(st_two); st_two.transfer_to_state(st_one); } virtual void so_evt_start() override { so_5::send(*this); } }; int main() { try { run_with_expected_error< a_simple_case_t >( so_5::rc_transfer_to_state_loop ); run_with_expected_error< a_tricky_loop_t >( so_5::rc_transfer_to_state_loop ); run_with_expected_error< a_two_state_loop_t >( so_5::rc_transfer_to_state_loop ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/000077500000000000000000000000001523065210100201275ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/CMakeLists.txt000066400000000000000000000000451523065210100226660ustar00rootroot00000000000000project(tests) add_subdirectory(v1) sobjectizer-5.8.6/dev/test/so_5/testing/v1/000077500000000000000000000000001523065210100204555ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/CMakeLists.txt000066400000000000000000000014701523065210100232170ustar00rootroot00000000000000project(tests) add_subdirectory(testenv_empty_scenario) add_subdirectory(testenv_ping_pong) add_subdirectory(testenv_simple_one_step) add_subdirectory(testenv_simple_two_steps) add_subdirectory(testenv_wait_completion_simple) add_subdirectory(testenv_stored_state_name) add_subdirectory(testenv_simple_when_any) add_subdirectory(testenv_simple_when_all) add_subdirectory(testenv_simple_when_all_2) add_subdirectory(testenv_simple_when_all_3) add_subdirectory(testenv_mpmc_mbox_ignore) add_subdirectory(testenv_constraints) add_subdirectory(testenv_constructors) add_subdirectory(impact_as_message) add_subdirectory(inline_ns) add_subdirectory(wait_evt_handler_completion) add_subdirectory(no_msg_from_envelope) add_subdirectory(msg_from_envelope) add_subdirectory(mbox_receives_msg) add_subdirectory(inspect_msg) sobjectizer-5.8.6/dev/test/so_5/testing/v1/impact_as_message/000077500000000000000000000000001523065210100241215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/impact_as_message/CMakeLists.txt000066400000000000000000000001501523065210100266550ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.impact_as_message) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/impact_as_message/main.cpp000066400000000000000000000042071523065210100255540ustar00rootroot00000000000000#include #include #include #include namespace tests = so_5::experimental::testing::v1; struct hello final : public so_5::signal_t {}; struct so_msg final : public so_5::message_t { int m_value; so_msg( int v ) : m_value(v) {} }; struct usr_msg final { int m_value; }; class test_agent_t final : public so_5::agent_t { public : test_agent_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self() .event( &test_agent_t::on_hello ) .event( &test_agent_t::on_so_msg_1 ) .event( &test_agent_t::on_so_msg_2 ) .event( &test_agent_t::on_usr_msg_1 ) .event( &test_agent_t::on_usr_msg_2 ) ; } private : void on_hello( mhood_t ) {} void on_so_msg_1( mhood_t ) {} void on_so_msg_2( mutable_mhood_t ) {} void on_usr_msg_1( mhood_t ) {} void on_usr_msg_2( mutable_mhood_t ) {} }; template< typename Msg, typename... Args > void define_step( tests::testing_env_t & env, std::string step_name, so_5::agent_t & agent, Args &&... args ) { env.scenario().define_step( std::move(step_name) ) .impact< Msg >( agent, std::forward(args)... ) .when( agent & tests::reacts_to< Msg >() ); } UT_UNIT_TEST( impact_as_message ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); define_step< hello >( env, "hello", *test_agent ); define_step< so_msg >( env, "so_msg", *test_agent, 1 ); define_step< so_5::mutable_msg >( env, "mutable", *test_agent, 2 ); define_step< usr_msg >( env, "usr_msg", *test_agent, 1 ); define_step< so_5::mutable_msg >( env, "mutable", *test_agent, 2 ); env.scenario().run_for( std::chrono::seconds(1) ); UT_CHECK_EQ( tests::completed(), env.scenario().result() ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( impact_as_message ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/inline_ns/000077500000000000000000000000001523065210100224335ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/inline_ns/CMakeLists.txt000066400000000000000000000001401523065210100251660ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.inline_ns) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/inline_ns/main.cpp000066400000000000000000000070441523065210100240700ustar00rootroot00000000000000#include #include #include #include namespace tests = so_5::experimental::testing; class worker_t final : public so_5::agent_t { const state_t st_free{ this, "free" }; const state_t st_busy{ this, "busy" }; public : struct acquire final : public so_5::signal_t {}; struct release final : public so_5::signal_t {}; worker_t( context_t ctx, const so_5::mbox_t & control_mbox ) : so_5::agent_t{ std::move(ctx) } { st_free.event( control_mbox, [this](mhood_t) { st_busy.activate(); } ); st_busy.event( control_mbox, [this](mhood_t) { st_free.activate(); } ); this >>= st_free; } }; class manager_t final : public so_5::agent_t { const so_5::mbox_t m_control_mbox; struct start_work final : public so_5::signal_t {}; struct finish_work final : public so_5::signal_t {}; public : manager_t( context_t ctx, so_5::mbox_t control_mbox ) : so_5::agent_t{ std::move(ctx) } , m_control_mbox{ std::move(control_mbox) } { so_subscribe_self() .event( [this](mhood_t) { so_5::send< worker_t::acquire >( m_control_mbox ); so_5::send_delayed< finish_work >( *this, std::chrono::milliseconds(150) ); } ) .event( [this](mhood_t) { so_5::send< worker_t::release >( m_control_mbox ); } ); } void so_evt_start() override { so_5::send_delayed< start_work >( *this, std::chrono::milliseconds(75) ); } }; UT_UNIT_TEST( workers_and_manager ) { run_with_time_limit( []() { tests::testing_env_t env{ []( so_5::environment_params_t & params ) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_clog_tracer() ); #endif } }; const auto control_mbox = env.environment().create_mbox(); auto [first_worker, second_worker] = env.environment().introduce_coop( [&](so_5::coop_t & coop) { coop.make_agent< manager_t >( control_mbox ); return std::make_tuple( coop.make_agent< worker_t >( control_mbox ), coop.make_agent< worker_t >( control_mbox ) ); } ); auto scenario = env.scenario(); scenario.define_step( "acquire" ) .when_all( *first_worker & tests::reacts_to< worker_t::acquire >( control_mbox ) & tests::store_state_name( "first" ), *second_worker & tests::reacts_to< worker_t::acquire >( control_mbox ) & tests::store_state_name( "second" ) ) .constraints( tests::not_before( std::chrono::milliseconds(50) ), tests::not_after( std::chrono::milliseconds(1000) ) ) ; scenario.define_step( "release" ) .when_all( *first_worker & tests::reacts_to< worker_t::release >( control_mbox ) & tests::store_state_name( "first" ), *second_worker & tests::reacts_to< worker_t::release >( control_mbox ) & tests::store_state_name( "second" ) ) .constraints( tests::not_before( std::chrono::milliseconds(75) ) ); scenario.run_for( std::chrono::seconds(4) ); UT_CHECK_EQ( tests::completed(), scenario.result() ); UT_CHECK_EQ( "busy", scenario.stored_state_name("acquire", "first") ); UT_CHECK_EQ( "busy", scenario.stored_state_name("acquire", "second") ); UT_CHECK_EQ( "free", scenario.stored_state_name("release", "first") ); UT_CHECK_EQ( "free", scenario.stored_state_name("release", "second") ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( workers_and_manager ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/inspect_msg/000077500000000000000000000000001523065210100227705ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/inspect_msg/CMakeLists.txt000066400000000000000000000001421523065210100255250ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.inspect_msg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/inspect_msg/main.cpp000066400000000000000000000172211523065210100244230ustar00rootroot00000000000000#include #include #include #include namespace so5_tests = so_5::experimental::testing::v1; namespace test { struct so_msg final : public so_5::message_t { int m_value; so_msg( int v ) : m_value(v) {} }; struct usr_msg final { int m_value; }; class agent_that_handles_messages_t final : public so_5::agent_t { public : agent_that_handles_messages_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self() .event( &agent_that_handles_messages_t::on_so_msg_1 ) .event( &agent_that_handles_messages_t::on_so_msg_2 ) .event( &agent_that_handles_messages_t::on_usr_msg_1 ) .event( &agent_that_handles_messages_t::on_usr_msg_2 ) ; } private : void on_so_msg_1( mhood_t ) {} void on_so_msg_2( mutable_mhood_t ) {} void on_usr_msg_1( mhood_t ) {} void on_usr_msg_2( mutable_mhood_t ) {} }; const std::string inspection_result_tag{ "inspection-result" }; template< typename Msg, typename... Args > void define_reacts_to_step( so5_tests::testing_env_t & env, std::string step_name, so_5::agent_t & agent, int expected_value, Args &&... args ) { using message_to_receive_type = typename so_5::message_payload_type< Msg >::payload_type; env.scenario().define_step( std::move(step_name) ) .impact< Msg >( agent, std::forward(args)... ) .when( agent & so5_tests::reacts_to< Msg >() & so5_tests::inspect_msg( inspection_result_tag, [expected_value]( const message_to_receive_type & msg ) -> std::string { return expected_value == msg.m_value ? "OK" : "MISSMATCH"; } ) ); } class agent_that_ignores_messages_t final : public so_5::agent_t { const state_t st_inactive{ this, "inactive" }; public : agent_that_ignores_messages_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().in( st_inactive ) .event( &agent_that_ignores_messages_t::on_so_msg_1 ) .event( &agent_that_ignores_messages_t::on_so_msg_2 ) .event( &agent_that_ignores_messages_t::on_usr_msg_1 ) .event( &agent_that_ignores_messages_t::on_usr_msg_2 ) ; } private : void on_so_msg_1( mhood_t ) {} void on_so_msg_2( mutable_mhood_t ) {} void on_usr_msg_1( mhood_t ) {} void on_usr_msg_2( mutable_mhood_t ) {} }; template< typename Msg, typename... Args > void define_ignores_step( so5_tests::testing_env_t & env, std::string step_name, so_5::agent_t & agent, int expected_value, Args &&... args ) { using message_to_receive_type = typename so_5::message_payload_type< Msg >::payload_type; env.scenario().define_step( std::move(step_name) ) .impact< Msg >( agent, std::forward(args)... ) .when( agent & so5_tests::ignores< Msg >() & so5_tests::inspect_msg( inspection_result_tag, [expected_value]( const message_to_receive_type & msg ) -> std::string { return expected_value == msg.m_value ? "OK" : "MISSMATCH"; } ) ); } class agent_with_state_t final : public so_5::agent_t { const state_t st_normal{ this, "normal" }; public : agent_with_state_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) {} void so_define_agent() override { this >>= st_normal; so_subscribe_self().in( st_normal ) .event( &agent_with_state_t::on_so_msg_1 ) ; } private : void on_so_msg_1( mhood_t ) {} }; } /* namespace test */ using namespace test; UT_UNIT_TEST( inspect_when_reacts ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< agent_that_handles_messages_t >(); } ); define_reacts_to_step< so_msg >( env, "so_msg", *test_agent, 1, 1 ); define_reacts_to_step< so_5::mutable_msg >( env, "mutable", *test_agent, 2, 2 ); define_reacts_to_step< usr_msg >( env, "usr_msg", *test_agent, 1, 1 ); define_reacts_to_step< so_5::mutable_msg >( env, "mutable", *test_agent, 2, 2 ); env.scenario().run_for( std::chrono::seconds(1) ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "so_msg", inspection_result_tag ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "mutable", inspection_result_tag ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "usr_msg", inspection_result_tag ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "mutable", inspection_result_tag ) ); }, 5 ); } UT_UNIT_TEST( inspect_when_reacts_with_state ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< agent_with_state_t >(); } ); env.scenario().define_step( "first" ) .impact< so_msg >( *test_agent, 1 ) .when( *test_agent & so5_tests::reacts_to< so_msg >() & so5_tests::inspect_msg( inspection_result_tag, []( const so_msg & msg ) -> std::string { return 1 == msg.m_value ? "OK" : "MISSMATCH"; } ) & so5_tests::store_state_name( "state" ) ); env.scenario().define_step( "second" ) .impact< so_msg >( *test_agent, 2 ) .when( *test_agent & so5_tests::reacts_to< so_msg >() & so5_tests::store_state_name( "state" ) & so5_tests::inspect_msg( inspection_result_tag, []( const so_msg & msg ) -> std::string { return 1 == msg.m_value ? "OK" : "MISSMATCH"; } ) ); env.scenario().run_for( std::chrono::seconds(1) ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "first", inspection_result_tag ) ); UT_CHECK_EQ( "normal", env.scenario().stored_state_name( "first", "state" ) ); UT_CHECK_EQ( "MISSMATCH", env.scenario().stored_msg_inspection_result( "second", inspection_result_tag ) ); UT_CHECK_EQ( "normal", env.scenario().stored_state_name( "second", "state" ) ); }, 5 ); } UT_UNIT_TEST( inspect_when_ignores ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< agent_that_ignores_messages_t >(); } ); define_ignores_step< so_msg >( env, "so_msg", *test_agent, 1, 1 ); define_ignores_step< so_5::mutable_msg >( env, "mutable", *test_agent, 2, 2 ); define_ignores_step< usr_msg >( env, "usr_msg", *test_agent, 1, 1 ); define_ignores_step< so_5::mutable_msg >( env, "mutable", *test_agent, 2, 2 ); env.scenario().run_for( std::chrono::seconds(1) ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "so_msg", inspection_result_tag ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "mutable", inspection_result_tag ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "usr_msg", inspection_result_tag ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "mutable", inspection_result_tag ) ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( inspect_when_reacts ) UT_RUN_UNIT_TEST( inspect_when_reacts_with_state ) UT_RUN_UNIT_TEST( inspect_when_ignores ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/mbox_receives_msg/000077500000000000000000000000001523065210100241555ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/mbox_receives_msg/CMakeLists.txt000066400000000000000000000001501523065210100267110ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.mbox_receives_msg) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/mbox_receives_msg/main.cpp000066400000000000000000000227601523065210100256140ustar00rootroot00000000000000#include #include #include #include using namespace std::chrono_literals; namespace so5_tests = so_5::experimental::testing::v1; namespace test { struct test_signal final : public so_5::signal_t {}; struct test_msg final : public so_5::message_t {}; struct test_msg_with_content final : public so_5::message_t { int m_value; test_msg_with_content( int v ) : m_value{ v } {} }; template< typename Msg > class a_initiator_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: a_initiator_t( context_t ctx, so_5::mbox_t dest ) : so_5::agent_t{ std::move(ctx) } , m_dest{ std::move(dest) } {} void so_evt_start() override { so_5::send< Msg >( m_dest ); } }; } /* namespace test */ using namespace test; UT_UNIT_TEST( signal ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::mbox_t dest = env.environment().create_mbox(); env.environment().introduce_coop( [dest](so_5::coop_t & coop) { return coop.make_agent< a_initiator_t< test_signal > >( dest ); } ); env.scenario().define_step( "single" ) .when( dest & so5_tests::receives< test_signal >() ) ; env.scenario().run_for( 1000ms ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( immutable_msg ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::mbox_t dest = env.environment().create_mbox(); env.environment().introduce_coop( [dest](so_5::coop_t & coop) { return coop.make_agent< a_initiator_t< test_msg > >( dest ); } ); env.scenario().define_step( "single" ) .when( dest & so5_tests::receives< test_msg >() ) ; env.scenario().run_for( 1000ms ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( mutable_msg ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::mbox_t dest = so_5::make_unique_subscribers_mbox( env.environment() ); env.environment().introduce_coop( [dest](so_5::coop_t & coop) { return coop.make_agent< a_initiator_t< so_5::mutable_msg< test_msg > > >( dest ); } ); env.scenario().define_step( "single" ) .when( dest & so5_tests::receives< so_5::mutable_msg< test_msg > >() ) ; env.scenario().run_for( 1000ms ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( receives_with_inspect_immutable ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::mbox_t dest = so_5::make_unique_subscribers_mbox( env.environment() ); env.scenario().define_step( "single" ) .impact< test_msg_with_content >( dest, 42 ) .when( dest & so5_tests::receives< test_msg_with_content >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 42 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().run_for( 1000ms ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "single", "inspection" ) ); }, 5 ); } UT_UNIT_TEST( receives_with_inspect_mutable ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::mbox_t dest = so_5::make_unique_subscribers_mbox( env.environment() ); env.scenario().define_step( "single" ) .impact< so_5::mutable_msg >( dest, 42 ) .when( dest & so5_tests::receives< so_5::mutable_msg >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 42 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().run_for( 1000ms ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "single", "inspection" ) ); }, 5 ); } UT_UNIT_TEST( several_receives_with_inspect_immutable ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::mbox_t dest = env.environment().create_mbox(); env.scenario().define_step( "one" ) .impact< test_msg_with_content >( dest, 1 ) .when( dest & so5_tests::receives< test_msg_with_content >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 1 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().define_step( "two" ) .impact< test_msg_with_content >( dest, 2 ) .when( dest & so5_tests::receives< test_msg_with_content >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 2 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().define_step( "three" ) .impact< test_msg_with_content >( dest, 3 ) .when( dest & so5_tests::receives< test_msg_with_content >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 3 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().run_for( 1000ms ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "one", "inspection" ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "two", "inspection" ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "three", "inspection" ) ); }, 5 ); } UT_UNIT_TEST( several_receives_with_inspect_mutable ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::mbox_t dest = so_5::make_unique_subscribers_mbox( env.environment() ); using msg_to_use = so_5::mutable_msg< test_msg_with_content >; env.scenario().define_step( "one" ) .impact< msg_to_use >( dest, 1 ) .when( dest & so5_tests::receives< msg_to_use >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 1 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().define_step( "two" ) .impact< msg_to_use >( dest, 2 ) .when( dest & so5_tests::receives< msg_to_use >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 2 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().define_step( "three" ) .impact< msg_to_use >( dest, 3 ) .when( dest & so5_tests::receives< msg_to_use >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 3 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().run_for( 1000ms ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "one", "inspection" ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "two", "inspection" ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "three", "inspection" ) ); }, 5 ); } UT_UNIT_TEST( receives_in_when_any_immutable ) { class a_starter_t final : public so_5::agent_t { const so_5::mbox_t m_dest; public: struct msg_start final : public so_5::signal_t {}; a_starter_t( context_t ctx, so_5::mbox_t dest ) : so_5::agent_t{ std::move(ctx) } , m_dest{ std::move(dest) } {} void so_define_agent() override { so_subscribe_self().event( [this]( mhood_t ) { so_5::send< test_msg_with_content >( m_dest, 2 ); } ); } }; run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::mbox_t dest = env.environment().create_mbox(); auto & starter = env.environment().introduce_coop( [dest](so_5::coop_t & coop) -> decltype(auto) { return *(coop.make_agent< a_starter_t >( dest )); } ); env.scenario().define_step( "one" ) .impact< a_starter_t::msg_start >( starter ) .when_any( starter & so5_tests::reacts_to< a_starter_t::msg_start >(), dest & so5_tests::receives< test_msg_with_content >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 1 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().define_step( "two" ) .when( dest & so5_tests::receives< test_msg_with_content >() & so5_tests::inspect_msg( "inspection", []( const test_msg_with_content & msg ) -> std::string { return 2 == msg.m_value ? "OK" : "FAIL"; } ) ) ; env.scenario().run_for( 1000ms ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( false, env.scenario().has_stored_msg_inspection_result( "one", "inspection" ) ); UT_CHECK_EQ( true, env.scenario().has_stored_msg_inspection_result( "two", "inspection" ) ); UT_CHECK_EQ( "OK", env.scenario().stored_msg_inspection_result( "two", "inspection" ) ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( signal ) UT_RUN_UNIT_TEST( immutable_msg ) UT_RUN_UNIT_TEST( mutable_msg ) UT_RUN_UNIT_TEST( receives_with_inspect_immutable ) UT_RUN_UNIT_TEST( receives_with_inspect_mutable ) UT_RUN_UNIT_TEST( several_receives_with_inspect_immutable ) UT_RUN_UNIT_TEST( several_receives_with_inspect_mutable ) UT_RUN_UNIT_TEST( receives_in_when_any_immutable ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/msg_from_envelope/000077500000000000000000000000001523065210100241635ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/msg_from_envelope/CMakeLists.txt000066400000000000000000000001501523065210100267170ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.msg_from_envelope) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/msg_from_envelope/main.cpp000066400000000000000000000055421523065210100256210ustar00rootroot00000000000000#include #include #include #include namespace so5_tests = so_5::experimental::testing::v1; namespace test { class test_envelope_t : public so_5::enveloped_msg::envelope_t { std::atomic & m_how_many_times_handled; so_5::message_ref_t m_payload; public: test_envelope_t( std::atomic & how_many_times_handled, so_5::message_ref_t payload ) : m_how_many_times_handled{ how_many_times_handled } , m_payload{ std::move(payload) } {} void access_hook( access_context_t context, handler_invoker_t & invoker ) noexcept override { switch( context ) { case access_context_t::handler_found: ++m_how_many_times_handled; invoker.invoke( payload_info_t{ m_payload } ); break; case access_context_t::transformation: invoker.invoke( payload_info_t{ m_payload } ); break; case access_context_t::inspection: invoker.invoke( payload_info_t{ m_payload } ); break; } } }; struct hello final : public so_5::signal_t {}; class a_receiver_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( [](mhood_t) {} ); } }; class a_sender_t final : public so_5::agent_t { std::atomic & m_how_many_times_handled; const so_5::mbox_t m_target_mbox; public: a_sender_t( context_t ctx, std::atomic & how_many_times_handled, so_5::mbox_t target_mbox ) : so_5::agent_t{ std::move(ctx) } , m_how_many_times_handled{ how_many_times_handled } , m_target_mbox{ std::move(target_mbox) } {} void so_evt_start() override { m_target_mbox->do_deliver_message( so_5::message_delivery_mode_t::ordinary, so_5::message_payload_type< hello >::subscription_type_index(), std::make_unique< test_envelope_t >( m_how_many_times_handled, so_5::message_ref_t{} /* null for a signal */ ), 1u ); } }; } /* namespace test */ using namespace test; UT_UNIT_TEST( reacts_to ) { std::atomic how_many_times_handled{ 0 }; run_with_time_limit( [&]() { so5_tests::testing_env_t env; a_receiver_t * receiver = env.environment().introduce_coop( [&how_many_times_handled](so_5::coop_t & coop) { a_receiver_t * r = coop.make_agent< a_receiver_t >(); coop.make_agent< a_sender_t >( how_many_times_handled, r->so_direct_mbox() ); return r; } ); env.scenario().define_step( "hello-at-start" ) .when( *receiver & so5_tests::reacts_to< hello >() ) ; env.scenario().run_for( std::chrono::milliseconds(200) ); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); }, 5 ); UT_CHECK_EQ( 1, how_many_times_handled.load() ); } int main() { UT_RUN_UNIT_TEST( reacts_to ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/no_msg_from_envelope/000077500000000000000000000000001523065210100246575ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/no_msg_from_envelope/CMakeLists.txt000066400000000000000000000001531523065210100274160ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.no_msg_from_envelope) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/no_msg_from_envelope/main.cpp000066400000000000000000000106251523065210100263130ustar00rootroot00000000000000#include #include #include #include namespace so5_tests = so_5::experimental::testing::v1; namespace test { class test_envelope_t : public so_5::enveloped_msg::envelope_t { std::atomic & m_how_many_times_handled; std::atomic & m_how_many_times_inspected; public: test_envelope_t( std::atomic & how_many_times_handled, std::atomic & how_many_times_inspected ) : m_how_many_times_handled{ how_many_times_handled } , m_how_many_times_inspected{ how_many_times_inspected } {} void access_hook( access_context_t context, handler_invoker_t & /*invoker*/ ) noexcept override { switch( context ) { case access_context_t::handler_found: ++m_how_many_times_handled; break; case access_context_t::transformation: // Nothing to do. break; case access_context_t::inspection: ++m_how_many_times_inspected; break; } // Do not call invoker.invoke! } }; struct hello final : public so_5::signal_t {}; class a_receiver_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().event( [](mhood_t) { // This should not happen! std::abort(); } ); } }; class a_noop_t final : public so_5::agent_t { state_t st_unused{ this, "unused" }; public: using so_5::agent_t::agent_t; void so_define_agent() override { so_subscribe_self().in( st_unused ) .event( [](mhood_t) { // This should not happen! std::abort(); } ); } }; class a_sender_t final : public so_5::agent_t { std::atomic & m_how_many_times_handled; std::atomic & m_how_many_times_inspected; const so_5::mbox_t m_target_mbox; public: a_sender_t( context_t ctx, std::atomic & how_many_times_handled, std::atomic & how_many_times_inspected, so_5::mbox_t target_mbox ) : so_5::agent_t{ std::move(ctx) } , m_how_many_times_handled{ how_many_times_handled } , m_how_many_times_inspected{ how_many_times_inspected } , m_target_mbox{ std::move(target_mbox) } {} void so_evt_start() override { m_target_mbox->do_deliver_message( so_5::message_delivery_mode_t::ordinary, so_5::message_payload_type< hello >::subscription_type_index(), std::make_unique< test_envelope_t >( m_how_many_times_handled, m_how_many_times_inspected ), 1u ); } }; } /* namespace test */ using namespace test; UT_UNIT_TEST( reacts_to ) { std::atomic how_many_times_handled{ 0 }; std::atomic how_many_times_inspected{ 0 }; run_with_time_limit( [&]() { so5_tests::testing_env_t env; a_receiver_t * receiver = env.environment().introduce_coop( [&how_many_times_handled, &how_many_times_inspected] (so_5::coop_t & coop) { a_receiver_t * r = coop.make_agent< a_receiver_t >(); coop.make_agent< a_sender_t >( how_many_times_handled, how_many_times_inspected, r->so_direct_mbox() ); return r; } ); env.scenario().define_step( "hello-at-start" ) .when( *receiver & so5_tests::reacts_to< hello >() ) ; env.scenario().run_for( std::chrono::milliseconds(200) ); UT_CHECK_NE( so5_tests::completed(), env.scenario().result() ); }, 5 ); UT_CHECK_EQ( 1, how_many_times_handled.load() ); UT_CHECK_EQ( 0, how_many_times_inspected.load() ); } UT_UNIT_TEST( ignores ) { std::atomic how_many_times_handled{ 0 }; std::atomic how_many_times_inspected{ 0 }; run_with_time_limit( [&]() { so5_tests::testing_env_t env; a_noop_t * receiver = env.environment().introduce_coop( [&how_many_times_handled, &how_many_times_inspected] (so_5::coop_t & coop) { auto * r = coop.make_agent< a_noop_t >(); coop.make_agent< a_sender_t >( how_many_times_handled, how_many_times_inspected, r->so_direct_mbox() ); return r; } ); env.scenario().define_step( "hello-at-start" ) .when( *receiver & so5_tests::ignores< hello >() ) ; env.scenario().run_for( std::chrono::milliseconds(200) ); UT_CHECK_NE( so5_tests::completed(), env.scenario().result() ); }, 5 ); UT_CHECK_EQ( 0, how_many_times_handled.load() ); UT_CHECK_EQ( 1, how_many_times_inspected.load() ); } int main() { UT_RUN_UNIT_TEST( reacts_to ) UT_RUN_UNIT_TEST( ignores ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_constraints/000077500000000000000000000000001523065210100245745ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_constraints/CMakeLists.txt000066400000000000000000000001521523065210100273320ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_constraints) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_constraints/main.cpp000066400000000000000000000126471523065210100262360ustar00rootroot00000000000000#include #include #include #include using namespace std::chrono_literals; namespace tests = so_5::experimental::testing::v1; struct hello_1 final : public so_5::signal_t {}; struct hello_2 final : public so_5::signal_t {}; class test_agent_t final : public so_5::agent_t { public : test_agent_t( context_t ctx, std::chrono::steady_clock::duration pause ) : so_5::agent_t( std::move(ctx) ) , m_pause( pause ) { so_subscribe_self().event( &test_agent_t::on_hello_1 ); so_subscribe_self().event( &test_agent_t::on_hello_2 ); } private : const std::chrono::steady_clock::duration m_pause; void on_hello_1( mhood_t ) { so_5::send_delayed( *this, m_pause ); } void on_hello_2( mhood_t ) { } }; void define_hello_1_step( tests::testing_env_t & env, so_5::agent_t & agent ) { env.scenario().define_step( "hello_1" ) .impact( [&agent] { so_5::send< hello_1 >( agent ); } ) .when( agent & tests::reacts_to< hello_1 >() ); } UT_UNIT_TEST( not_before_1 ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [&](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >( 200ms ); } ); define_hello_1_step( env, *test_agent ); env.scenario().define_step( "hello_2" ) .constraints( tests::not_before( 500ms ) ) .when( *test_agent & tests::reacts_to() ); env.scenario().run_for( 1000ms ); UT_CHECK_NE( tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( not_before_2 ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >( 700ms ); } ); define_hello_1_step( env, *test_agent ); env.scenario().define_step( "hello_2" ) .constraints( tests::not_before( 300ms ) ) .when( *test_agent & tests::reacts_to() ); env.scenario().run_for( 1000ms ); UT_CHECK_EQ( tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( not_after_1 ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >( 200ms ); } ); define_hello_1_step( env, *test_agent ); env.scenario().define_step( "hello_2" ) .constraints( tests::not_after( 400ms ) ) .when( *test_agent & tests::reacts_to() ); env.scenario().run_for( 1000ms ); UT_CHECK_EQ( tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( not_after_2 ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >( 500ms ); } ); define_hello_1_step( env, *test_agent ); env.scenario().define_step( "hello_2" ) .constraints( tests::not_after( 250ms ) ) .when( *test_agent & tests::reacts_to() ); env.scenario().run_for( 1000ms ); UT_CHECK_NE( tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( not_before_not_after_1 ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >( 200ms ); } ); define_hello_1_step( env, *test_agent ); env.scenario().define_step( "hello_2" ) .constraints( tests::not_before( 400ms ), tests::not_after( 600ms ) ) .when( *test_agent & tests::reacts_to() ); env.scenario().run_for( 1000ms ); UT_CHECK_NE( tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( not_before_not_after_2 ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >( 400ms ); } ); define_hello_1_step( env, *test_agent ); env.scenario().define_step( "hello_2" ) .constraints( tests::not_before( 150ms ), tests::not_after( 200ms ) ) .when( *test_agent & tests::reacts_to() ); env.scenario().run_for( 1000ms ); UT_CHECK_NE( tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( not_before_not_after_3 ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >( 250ms ); } ); define_hello_1_step( env, *test_agent ); env.scenario().define_step( "hello_2" ) .constraints( tests::not_before( 100ms ), tests::not_after( 500ms ) ) .when( *test_agent & tests::reacts_to() ); env.scenario().run_for( 1000ms ); UT_CHECK_EQ( tests::completed(), env.scenario().result() ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( not_before_1 ) UT_RUN_UNIT_TEST( not_before_2 ) UT_RUN_UNIT_TEST( not_after_1 ) UT_RUN_UNIT_TEST( not_after_2 ) UT_RUN_UNIT_TEST( not_before_not_after_1 ) UT_RUN_UNIT_TEST( not_before_not_after_2 ) UT_RUN_UNIT_TEST( not_before_not_after_3 ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_constructors/000077500000000000000000000000001523065210100247755ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_constructors/CMakeLists.txt000066400000000000000000000001531523065210100275340ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_constructors) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_constructors/main.cpp000066400000000000000000000041371523065210100264320ustar00rootroot00000000000000#include #include #include #include namespace tests = so_5::experimental::testing::v1; class test_agent_t final : public so_5::agent_t { public : struct hello final : public so_5::signal_t {}; test_agent_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( [](mhood_t) {} ); } }; UT_UNIT_TEST( env_params_tuner ) { run_with_time_limit( []() { tests::testing_env_t env{ []( so_5::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } }; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); env.scenario().define_step( "hello" ) .impact( [test_agent] { so_5::send< test_agent_t::hello >( *test_agent ); } ) .when( *test_agent & tests::reacts_to< test_agent_t::hello >() ); env.scenario().run_for( std::chrono::milliseconds(500) ); UT_CHECK_EQ( tests::completed(), env.scenario().result() ); }, 5 ); } UT_UNIT_TEST( prepared_params ) { const auto params_maker = [] { so_5::environment_params_t params; params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); return params; }; run_with_time_limit( [=]() { tests::testing_env_t env{ params_maker() }; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); env.scenario().define_step( "hello" ) .impact( [test_agent] { so_5::send< test_agent_t::hello >( *test_agent ); } ) .when( *test_agent & tests::reacts_to< test_agent_t::hello >() ); env.scenario().run_for( std::chrono::milliseconds(500) ); UT_CHECK_EQ( tests::completed(), env.scenario().result() ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( env_params_tuner ) UT_RUN_UNIT_TEST( prepared_params ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_empty_scenario/000077500000000000000000000000001523065210100252465ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_empty_scenario/CMakeLists.txt000066400000000000000000000001551523065210100300070ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_empty_scenario) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_empty_scenario/main.cpp000066400000000000000000000034301523065210100266760ustar00rootroot00000000000000#include #include #include #include namespace tests = so_5::experimental::testing::v1; UT_UNIT_TEST( empty_scenario_with_one_agent ) { run_with_time_limit( []() { tests::testing_env_t env; env.environment().introduce_coop( [](so_5::coop_t & coop) { class actor_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { std::cout << "Hello, World!" << std::endl; } }; coop.make_agent< actor_t >(); } ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); } UT_UNIT_TEST( empty_scenario_without_agents ) { run_with_time_limit( []() { tests::testing_env_t env; env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); } UT_UNIT_TEST( one_agent_no_call_to_run_for ) { run_with_time_limit( []() { tests::testing_env_t env; env.environment().introduce_coop( [](so_5::coop_t & coop) { class actor_t final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void so_evt_start() override { std::cout << "Hello, World!" << std::endl; } }; coop.make_agent< actor_t >(); } ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( empty_scenario_with_one_agent ) UT_RUN_UNIT_TEST( empty_scenario_without_agents ) UT_RUN_UNIT_TEST( one_agent_no_call_to_run_for ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_mpmc_mbox_ignore/000077500000000000000000000000001523065210100255515ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_mpmc_mbox_ignore/CMakeLists.txt000066400000000000000000000001571523065210100303140ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_mpmc_mbox_ignore) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_mpmc_mbox_ignore/main.cpp000066400000000000000000000024351523065210100272050ustar00rootroot00000000000000#include #include #include namespace tests = so_5::experimental::testing::v1; struct hello final : public so_5::signal_t {}; class test_agent_t final : public so_5::agent_t { public : test_agent_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( &test_agent_t::on_hello ); } private : void on_hello( mhood_t ) { std::cout << "Hello!" << std::endl; } }; int main() { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [&](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); const auto broadcast = env.environment().create_mbox(); env.scenario().define_step( "hello_received" ) .impact( [broadcast] { so_5::send< hello >( broadcast ); } ) .when_any( *test_agent & tests::reacts_to< hello >( broadcast ), *test_agent & tests::ignores< hello >( broadcast ) ); env.scenario().run_for( std::chrono::milliseconds(250) ); ensure_or_die( tests::completed() != env.scenario().result(), "scenario should not complete its work" ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_ping_pong/000077500000000000000000000000001523065210100242055ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_ping_pong/CMakeLists.txt000066400000000000000000000001501523065210100267410ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_ping_pong) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_ping_pong/main.cpp000066400000000000000000000036401523065210100256400ustar00rootroot00000000000000#include #include #include #include namespace tests = so_5::experimental::testing::v1; struct ping final : public so_5::signal_t {}; struct pong final : public so_5::signal_t {}; class pinger_t final : public so_5::agent_t { public : pinger_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [](mhood_t) { std::cout << "pong!" << std::endl; } ); } void set_target( const so_5::mbox_t & m ) { m_target = m; } void so_evt_start() override { so_5::send< ping >( m_target ); } private : so_5::mbox_t m_target; }; class ponger_t final : public so_5::agent_t { public : ponger_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [this](mhood_t) { std::cout << "ping!" << std::endl; so_5::send< pong >( m_target ); } ); } void set_target( const so_5::mbox_t & m ) { m_target = m; } private : so_5::mbox_t m_target; }; UT_UNIT_TEST( ping_pong ) { run_with_time_limit( []() { tests::testing_env_t env; pinger_t * pinger{}; ponger_t * ponger{}; env.environment().introduce_coop( [&](so_5::coop_t & coop) { pinger = coop.make_agent< pinger_t >(); ponger = coop.make_agent< ponger_t >(); pinger->set_target( ponger->so_direct_mbox() ); ponger->set_target( pinger->so_direct_mbox() ); } ); env.scenario().define_step( "ping" ) .when( *ponger & tests::reacts_to< ping >() ); env.scenario().define_step( "pong" ) .when( *pinger & tests::reacts_to< pong >() ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( ping_pong ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_one_step/000077500000000000000000000000001523065210100254125ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_one_step/CMakeLists.txt000066400000000000000000000001561523065210100301540ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_simple_one_step) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_one_step/main.cpp000066400000000000000000000036551523065210100270530ustar00rootroot00000000000000/* * A simple test for testing::v1::test_env_t. */ #include #include #include #include namespace tests = so_5::experimental::testing::v1; struct hello final : public so_5::signal_t {}; class test_agent_t final : public so_5::agent_t { public : test_agent_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( &test_agent_t::on_hello ); } private : void on_hello( mhood_t ) { std::cout << "Hello!" << std::endl; } }; UT_UNIT_TEST( impact_as_lambda ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); env.scenario().define_step( "hello_received" ) .impact( [test_agent] { so_5::send< hello >( *test_agent ); } ) .when( *test_agent & tests::reacts_to< hello >() ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); } UT_UNIT_TEST( impact_as_message ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); env.scenario().define_step( "hello_received" ) .impact< hello >( *test_agent ) .when( *test_agent & tests::reacts_to< hello >() ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( impact_as_lambda ) UT_RUN_UNIT_TEST( impact_as_message ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_two_steps/000077500000000000000000000000001523065210100256255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_two_steps/CMakeLists.txt000066400000000000000000000001571523065210100303700ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_simple_two_steps) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_two_steps/main.cpp000066400000000000000000000031161523065210100272560ustar00rootroot00000000000000/* * A simple test for testing::v1::test_env_t with two steps. */ #include #include #include namespace tests = so_5::experimental::testing::v1; struct dummy final : public so_5::signal_t {}; struct hello final : public so_5::signal_t {}; class test_agent_t final : public so_5::agent_t { state_t st_dummy{ this }; public : test_agent_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( &test_agent_t::on_hello ); st_dummy.event( &test_agent_t::on_dummy ); } private : void on_hello( mhood_t ) { std::cout << "Hello!" << std::endl; } void on_dummy( mhood_t ) { std::cout << "Dummy!" << std::endl; } }; int main() { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); env.scenario().define_step( "dummy_ignored" ) .impact( [test_agent] { so_5::send< dummy >( *test_agent ); } ) .when( *test_agent & tests::ignores< dummy >() ); env.scenario().define_step( "hello_received" ) .impact( [test_agent] { so_5::send< hello >( *test_agent ); } ) .when( *test_agent & tests::reacts_to< hello >() ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_all/000077500000000000000000000000001523065210100253675ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_all/CMakeLists.txt000066400000000000000000000001561523065210100301310ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_simple_when_all) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_all/main.cpp000066400000000000000000000041141523065210100270170ustar00rootroot00000000000000/* * A simple test for testing::v1::test_env_t. */ #include #include #include namespace tests = so_5::experimental::testing::v1; struct hello final : public so_5::signal_t {}; struct confirm final : public so_5::signal_t {}; struct dummy final : public so_5::signal_t {}; class test_agent_t final : public so_5::agent_t { state_t st_dummy{ this }; public : test_agent_t( context_t ctx, const so_5::mbox_t & broadcast ) : so_5::agent_t( std::move(ctx) ) { so_subscribe( broadcast ).event( &test_agent_t::on_hello ); so_subscribe_self().event( &test_agent_t::on_confirm ); st_dummy.event( &test_agent_t::on_dummy ); } private : void on_hello( mhood_t ) { so_5::send< confirm >( *this ); } void on_confirm( mhood_t ) { so_5::send< dummy >( *this ); } void on_dummy( mhood_t ) { std::cout << "Dummy!" << std::endl; } }; int main() { run_with_time_limit( []() { tests::testing_env_t env; const auto broadcast = env.environment().create_mbox(); auto [first, second, third, fourth] = env.environment().introduce_coop( so_5::disp::active_obj::make_dispatcher( env.environment() ).binder(), [&](so_5::coop_t & coop) { return std::make_tuple( coop.make_agent< test_agent_t >( broadcast ), coop.make_agent< test_agent_t >( broadcast ), coop.make_agent< test_agent_t >( broadcast ), coop.make_agent< test_agent_t >( broadcast ) ); } ); env.scenario().define_step( "test" ) .impact( [broadcast] { so_5::send< hello >( broadcast ); } ) .when_all( *first & tests::reacts_to< hello >(broadcast), *second & tests::reacts_to< hello >(broadcast), *third & tests::reacts_to< confirm >(), *fourth & tests::ignores< dummy >() ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_all_2/000077500000000000000000000000001523065210100256105ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_all_2/CMakeLists.txt000066400000000000000000000001601523065210100303450ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_simple_when_all_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_all_2/main.cpp000066400000000000000000000033361523065210100272450ustar00rootroot00000000000000/* * A simple test for testing::v1::test_env_t. */ #include #include #include namespace tests = so_5::experimental::testing::v1; struct msg_take final : public so_5::message_t { const so_5::mbox_t m_reply_to; msg_take( so_5::mbox_t reply_to ) : m_reply_to{ std::move(reply_to) } {} }; struct msg_taken final : public so_5::signal_t {}; class first_t final : public so_5::agent_t { const state_t st_taken{ this, "taken" }; public : first_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [this](mhood_t cmd) { st_taken.activate(); so_5::send( cmd->m_reply_to ); } ); } }; class second_t final : public so_5::agent_t { public : second_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [](mhood_t) {} ); } }; int main() { run_with_time_limit( []() { tests::testing_env_t env; auto [first, second] = env.environment().introduce_coop( [&](so_5::coop_t & coop) { return std::make_tuple( coop.make_agent< first_t >(), coop.make_agent< second_t >() ); } ); env.scenario().define_step( "test" ) .impact( [f=first, s=second] { so_5::send< msg_take >( *f, s->so_direct_mbox() ); } ) .when_all( *first & tests::reacts_to< msg_take >() & tests::store_state_name( "first" ), *second & tests::reacts_to< msg_taken >() ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_all_3/000077500000000000000000000000001523065210100256115ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_all_3/CMakeLists.txt000066400000000000000000000001601523065210100303460ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_simple_when_all_3) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_all_3/main.cpp000066400000000000000000000032261523065210100272440ustar00rootroot00000000000000#include #include #include namespace tests = so_5::experimental::testing::v1; struct start final : public so_5::signal_t {}; class first_t final : public so_5::agent_t { const state_t st_dummy{ this, "dummy" }; public : first_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { st_dummy.event( [](mhood_t) {} ); } }; class second_t final : public so_5::agent_t { public : second_t( context_t ctx, const so_5::mbox_t & dest ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [dest](mhood_t) { so_5::send( dest ); std::this_thread::sleep_for( std::chrono::milliseconds(200) ); } ); } }; int main() { run_with_time_limit( []() { tests::testing_env_t env; auto [first, second] = env.environment().introduce_coop( so_5::disp::active_obj::make_dispatcher( env.environment() ).binder(), [](so_5::coop_t & coop) { auto first_agent = coop.make_agent< first_t >(); return std::make_tuple( first_agent, coop.make_agent< second_t >( first_agent->so_direct_mbox() ) ); } ); env.scenario().define_step( "test" ) .impact( [s=second] { so_5::send< start >( *s ); } ) .when_all( *first & tests::ignores< start >(), *second & tests::reacts_to< start >() & tests::store_state_name( "second" ) ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_any/000077500000000000000000000000001523065210100254065ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_any/CMakeLists.txt000066400000000000000000000001561523065210100301500ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_simple_when_any) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_simple_when_any/main.cpp000066400000000000000000000062541523065210100270450ustar00rootroot00000000000000/* * A simple test for testing::v1::test_env_t. */ #include #include #include #include namespace tests = so_5::experimental::testing::v1; struct hello_1 final : public so_5::signal_t {}; struct hello_2 final : public so_5::signal_t {}; struct dummy final : public so_5::signal_t {}; class test_agent_t final : public so_5::agent_t { state_t st_dummy{ this }; public : test_agent_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( &test_agent_t::on_hello_1 ); so_subscribe_self().event( &test_agent_t::on_hello_2 ); st_dummy.event( &test_agent_t::on_dummy ); } private : void on_hello_1( mhood_t ) { std::cout << "Hello(1)!" << std::endl; } void on_hello_2( mhood_t ) { std::cout << "Hello(2)!" << std::endl; } void on_dummy( mhood_t ) { std::cout << "Dummy!" << std::endl; } }; template< typename Step > void setup_triggers( so_5::agent_t & agent, Step && step ) { step.when_any( agent & tests::reacts_to< hello_1 >(), agent & tests::reacts_to< hello_2 >(), agent & tests::ignores< dummy >() ); } UT_UNIT_TEST( send_hello_1 ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); setup_triggers( *test_agent, env.scenario().define_step( "hello_received" ) .impact( [test_agent] { so_5::send< hello_1 >( *test_agent ); } ) ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); } UT_UNIT_TEST( send_hello_2 ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); setup_triggers( *test_agent, env.scenario().define_step( "hello_received" ) .impact( [test_agent] { so_5::send< hello_2 >( *test_agent ); } ) ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); } UT_UNIT_TEST( send_dummy ) { run_with_time_limit( []() { tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [](so_5::coop_t & coop) { return coop.make_agent< test_agent_t >(); } ); setup_triggers( *test_agent, env.scenario().define_step( "dummy_ignored" ) .impact( [test_agent] { so_5::send< dummy >( *test_agent ); } ) ); env.scenario().run_for( std::chrono::seconds(1) ); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( send_hello_1 ) UT_RUN_UNIT_TEST( send_hello_2 ) UT_RUN_UNIT_TEST( send_dummy ) return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_stored_state_name/000077500000000000000000000000001523065210100257255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_stored_state_name/CMakeLists.txt000066400000000000000000000001601523065210100304620ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_stored_state_name) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_stored_state_name/main.cpp000066400000000000000000000053711523065210100273630ustar00rootroot00000000000000#include #include #include namespace tests = so_5::experimental::testing::v1; struct hello_one final : public so_5::signal_t {}; struct hello_two final : public so_5::signal_t {}; class first_agent_t final : public so_5::agent_t { const state_t st_main{ this, "main" }; public : first_agent_t( context_t ctx, so_5::mbox_t second_mbox ) : so_5::agent_t( std::move(ctx) ) , m_second_mbox( std::move(second_mbox) ) { so_subscribe_self().event( &first_agent_t::on_hello_one ); } private : const so_5::mbox_t m_second_mbox; void on_hello_one( mhood_t ) { so_5::send< hello_two >( m_second_mbox ); std::cout << "Before sleep..." << std::endl; std::this_thread::sleep_for( std::chrono::milliseconds(250) ); std::cout << "After sleep..." << std::endl; this >>= st_main; } }; class second_agent_t final : public so_5::agent_t { public : second_agent_t( context_t ctx, const so_5::mbox_t & from ) : so_5::agent_t( std::move(ctx) ) { so_subscribe( from ).event( &second_agent_t::on_hello_two ); } private : void on_hello_two( mhood_t ) {} }; int main() { run_with_time_limit( []() { tests::testing_env_t env; auto second_mbox = env.environment().create_mbox(); auto [first, second] = env.environment().introduce_coop( so_5::disp::active_obj::make_dispatcher( env.environment() ).binder(), [&](so_5::coop_t & coop) { return std::make_tuple( coop.make_agent< first_agent_t >( second_mbox ), coop.make_agent< second_agent_t >( second_mbox ) ); } ); env.scenario().define_step( "hello_one_received" ) .impact( [f=first] { so_5::send< hello_one >( *f ); } ) .when( *first & tests::reacts_to< hello_one >() & tests::store_state_name( "first" ) ); env.scenario().define_step( "hello_two_received" ) .when( *second & tests::reacts_to< hello_two >( second_mbox ) ); using clock = std::chrono::steady_clock; const auto started_at = clock::now(); env.scenario().run_for( std::chrono::seconds(1) ); const auto finished_at = clock::now(); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); ensure_or_die( (finished_at - started_at) >= std::chrono::milliseconds(250), "test should take more than 250ms" ); ensure_or_die( env.scenario().has_stored_state_name( "hello_one_received", "first" ), "there should be a stored state name" ); ensure_or_die( "main" == env.scenario().stored_state_name( "hello_one_received", "first" ), "`main` is expected to be stored" ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_wait_completion_simple/000077500000000000000000000000001523065210100267735ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_wait_completion_simple/CMakeLists.txt000066400000000000000000000001651523065210100315350ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.testenv_wait_completion_simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/testenv_wait_completion_simple/main.cpp000066400000000000000000000046211523065210100304260ustar00rootroot00000000000000#include #include #include namespace tests = so_5::experimental::testing::v1; struct hello_one final : public so_5::signal_t {}; struct hello_two final : public so_5::signal_t {}; class first_agent_t final : public so_5::agent_t { public : first_agent_t( context_t ctx, so_5::mbox_t second_mbox ) : so_5::agent_t( std::move(ctx) ) , m_second_mbox( std::move(second_mbox) ) { so_subscribe_self().event( &first_agent_t::on_hello_one ); } private : const so_5::mbox_t m_second_mbox; void on_hello_one( mhood_t ) { so_5::send< hello_two >( m_second_mbox ); std::cout << "Before sleep..." << std::endl; std::this_thread::sleep_for( std::chrono::milliseconds(250) ); std::cout << "After sleep..." << std::endl; } }; class second_agent_t final : public so_5::agent_t { public : second_agent_t( context_t ctx, const so_5::mbox_t & from ) : so_5::agent_t( std::move(ctx) ) { so_subscribe( from ).event( &second_agent_t::on_hello_two ); } private : void on_hello_two( mhood_t ) {} }; int main() { run_with_time_limit( []() { tests::testing_env_t env; auto second_mbox = env.environment().create_mbox(); auto [first, second] = env.environment().introduce_coop( so_5::disp::active_obj::make_dispatcher( env.environment() ).binder(), [&](so_5::coop_t & coop) { return std::make_tuple( coop.make_agent< first_agent_t >( second_mbox ), coop.make_agent< second_agent_t >( second_mbox ) ); } ); env.scenario().define_step( "hello_one_received" ) .impact( [f=first] { so_5::send< hello_one >( *f ); } ) .when( *first & tests::reacts_to< hello_one >() & tests::store_state_name( "first" ) ); env.scenario().define_step( "hello_two_received" ) .when( *second & tests::reacts_to< hello_two >( second_mbox ) ); using clock = std::chrono::steady_clock; const auto started_at = clock::now(); env.scenario().run_for( std::chrono::seconds(1) ); const auto finished_at = clock::now(); ensure_or_die( tests::completed() == env.scenario().result(), "scenario should complete its work" ); ensure_or_die( (finished_at - started_at) >= std::chrono::milliseconds(250), "test should take more than 250ms" ); }, 5 ); return 0; } sobjectizer-5.8.6/dev/test/so_5/testing/v1/wait_evt_handler_completion/000077500000000000000000000000001523065210100262255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/testing/v1/wait_evt_handler_completion/CMakeLists.txt000066400000000000000000000001621523065210100307640ustar00rootroot00000000000000set(UNITTEST _unit.test.testing.v1.wait_evt_handler_completion) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/testing/v1/wait_evt_handler_completion/main.cpp000066400000000000000000000133401523065210100276560ustar00rootroot00000000000000#include #include #include #include using namespace std::chrono_literals; namespace so5_tests = so_5::experimental::testing::v1; namespace test { constexpr std::chrono::milliseconds expected_delay{ 200 }; struct start final : public so_5::signal_t {}; class test_agent_no_delay_t final : public so_5::agent_t { public : test_agent_no_delay_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { so_subscribe_self().event( []( mhood_t ) { /* Do nothing */ } ); } }; class test_agent_with_delay_t final : public so_5::agent_t { so_5::state_t st_working{ this, "working" }; public : test_agent_with_delay_t( context_t ctx ) : so_5::agent_t( std::move(ctx) ) { } void so_define_agent() override { this >>= st_working; so_subscribe_self().in( st_working ).event( []( mhood_t ) { std::this_thread::sleep_for( expected_delay + 10ms ); } ); } }; } /* namespace test */ using namespace test; UT_UNIT_TEST( no_delay_in_agent ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [&](so_5::coop_t & coop) { return coop.make_agent< test_agent_no_delay_t >(); } ); env.scenario().define_step( "start" ) .impact( [test_agent] { so_5::send< start >( *test_agent ); } ) .when( *test_agent & so5_tests::reacts_to< start >() & so5_tests::wait_event_handler_completion() ) ; const auto t1 = std::chrono::steady_clock::now(); env.scenario().run_for( 1000ms ); const auto t2 = std::chrono::steady_clock::now(); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( true, std::chrono::duration_cast(t2 - t1) < expected_delay ); }, 5 ); } UT_UNIT_TEST( no_wait_event_handler_completion ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [&](so_5::coop_t & coop) { return coop.make_agent< test_agent_with_delay_t >(); } ); env.scenario().define_step( "start" ) .impact( [test_agent] { so_5::send< start >( *test_agent ); } ) .when( *test_agent & so5_tests::reacts_to< start >() ) ; const auto t1 = std::chrono::steady_clock::now(); env.scenario().run_for( 1000ms ); const auto t2 = std::chrono::steady_clock::now(); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( true, std::chrono::duration_cast(t2 - t1) < expected_delay ); }, 5 ); } UT_UNIT_TEST( wait_event_handler_completion ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [&](so_5::coop_t & coop) { return coop.make_agent< test_agent_with_delay_t >(); } ); env.scenario().define_step( "start" ) .impact( [test_agent] { so_5::send< start >( *test_agent ); } ) .when( *test_agent & so5_tests::reacts_to< start >() & so5_tests::wait_event_handler_completion() ) ; const auto t1 = std::chrono::steady_clock::now(); env.scenario().run_for( 1000ms ); const auto t2 = std::chrono::steady_clock::now(); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( true, std::chrono::duration_cast(t2 - t1) >= expected_delay ); }, 5 ); } UT_UNIT_TEST( wait_and_store_state_name_1 ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [&](so_5::coop_t & coop) { return coop.make_agent< test_agent_with_delay_t >(); } ); env.scenario().define_step( "start" ) .impact( [test_agent] { so_5::send< start >( *test_agent ); } ) .when( *test_agent & so5_tests::reacts_to< start >() & so5_tests::wait_event_handler_completion() & so5_tests::store_state_name( "agent" ) ) ; const auto t1 = std::chrono::steady_clock::now(); env.scenario().run_for( 1000ms ); const auto t2 = std::chrono::steady_clock::now(); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( true, std::chrono::duration_cast(t2 - t1) >= expected_delay ); UT_CHECK_EQ( "working", env.scenario().stored_state_name( "start", "agent" ) ); }, 5 ); } UT_UNIT_TEST( wait_and_store_state_name_2 ) { run_with_time_limit( []() { so5_tests::testing_env_t env; so_5::agent_t * test_agent = env.environment().introduce_coop( [&](so_5::coop_t & coop) { return coop.make_agent< test_agent_with_delay_t >(); } ); env.scenario().define_step( "start" ) .impact( [test_agent] { so_5::send< start >( *test_agent ); } ) .when( *test_agent & so5_tests::reacts_to< start >() & so5_tests::store_state_name( "agent" ) & so5_tests::wait_event_handler_completion() ) ; const auto t1 = std::chrono::steady_clock::now(); env.scenario().run_for( 1000ms ); const auto t2 = std::chrono::steady_clock::now(); UT_CHECK_EQ( so5_tests::completed(), env.scenario().result() ); UT_CHECK_EQ( true, std::chrono::duration_cast(t2 - t1) >= expected_delay ); UT_CHECK_EQ( "working", env.scenario().stored_state_name( "start", "agent" ) ); }, 5 ); } int main() { UT_RUN_UNIT_TEST( no_delay_in_agent ) UT_RUN_UNIT_TEST( no_wait_event_handler_completion ) UT_RUN_UNIT_TEST( wait_event_handler_completion ) UT_RUN_UNIT_TEST( wait_and_store_state_name_1 ) UT_RUN_UNIT_TEST( wait_and_store_state_name_2 ) return 0; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/000077500000000000000000000000001523065210100211215ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/CMakeLists.txt000066400000000000000000000006321523065210100236620ustar00rootroot00000000000000add_subdirectory(single_delayed) add_subdirectory(single_periodic) add_subdirectory(single_timer_zero_delay) add_subdirectory(timers_cancelation) add_subdirectory(overloaded_mchain) add_subdirectory(overloaded_mchain_2) add_subdirectory(resend_periodic_signal_via_mhood) add_subdirectory(resend_periodic_via_mhood_to_mchain) add_subdirectory(resend_delayed_via_mhood_to_mchain) add_subdirectory(negative_args) sobjectizer-5.8.6/dev/test/so_5/timer_thread/negative_args/000077500000000000000000000000001523065210100237375ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/negative_args/CMakeLists.txt000066400000000000000000000001461523065210100265000ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.negative_args) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/negative_args/main.cpp000066400000000000000000000040311523065210100253650ustar00rootroot00000000000000/* * Test of using negative values for send_delayed and send_periodic. */ #include #include #include #include #include #include class a_test_t final : public so_5::agent_t { struct sig_A : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) {} virtual void so_evt_start() override { try { (void)so_5::send_delayed( *this, -std::chrono::milliseconds(200) ); ensure_or_die( false, "an exeption must be thrown!" ); } catch( const so_5::exception_t & x ) { ensure_or_die( x.error_code() == so_5::rc_negative_value_for_pause, "rc_negative_value_for_pause expected!" ); } try { (void)so_5::send_periodic( *this, -std::chrono::milliseconds(200), std::chrono::milliseconds(300) ); ensure_or_die( false, "an exeption must be thrown!" ); } catch( const so_5::exception_t & x ) { ensure_or_die( x.error_code() == so_5::rc_negative_value_for_pause, "rc_negative_value_for_pause expected!" ); } try { (void)so_5::send_periodic( *this, std::chrono::milliseconds(200), -std::chrono::milliseconds(300) ); ensure_or_die( false, "an exeption must be thrown!" ); } catch( const so_5::exception_t & x ) { ensure_or_die( x.error_code() == so_5::rc_negative_value_for_period, "rc_negative_value_for_pause expected!" ); } so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } ); }, 10 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/overloaded_mchain/000077500000000000000000000000001523065210100245645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/overloaded_mchain/CMakeLists.txt000066400000000000000000000001521523065210100273220ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.overloaded_mchain) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/overloaded_mchain/main.cpp000066400000000000000000000035021523065210100262140ustar00rootroot00000000000000/* * Test of sending delayed message into overloaded mchain. */ #include #include #include #include #include #include class a_test_t final : public so_5::agent_t { struct sig_A : public so_5::signal_t {}; struct sig_B : public so_5::signal_t {}; struct sig_C : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_mchain( create_mchain( ctx.environment(), std::chrono::seconds(1), 1u, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::drop_newest ) ) { so_subscribe_self().event( &a_test_t::on_sig_C ); } virtual void so_evt_start() override { so_5::send< sig_A >( m_mchain ); so_5::send_delayed< sig_B >( m_mchain, std::chrono::milliseconds(250) ); m_sent_at = std::chrono::steady_clock::now(); so_5::send_delayed< sig_C >( *this, std::chrono::milliseconds(260) ); } private : so_5::mchain_t m_mchain; std::chrono::steady_clock::time_point m_sent_at; void on_sig_C( mhood_t< sig_C > ) { const auto now = std::chrono::steady_clock::now(); ensure_or_die( now - m_sent_at < std::chrono::seconds(1), "time interval must be less than 1s" ); so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } ); }, 10, "check delayed message for overloaded mchain" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/overloaded_mchain_2/000077500000000000000000000000001523065210100250055ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/overloaded_mchain_2/CMakeLists.txt000066400000000000000000000001541523065210100275450ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.overloaded_mchain_2) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/overloaded_mchain_2/main.cpp000066400000000000000000000036031523065210100264370ustar00rootroot00000000000000/* * Test of sending delayed message into overloaded mchain with * throw exception overflow reaction. */ #include #include #include #include #include #include class a_test_t final : public so_5::agent_t { struct sig_A : public so_5::signal_t {}; struct sig_B : public so_5::signal_t {}; struct sig_C : public so_5::signal_t {}; public : a_test_t( context_t ctx ) : so_5::agent_t( ctx ) , m_mchain( create_mchain( ctx.environment(), 1u, so_5::mchain_props::memory_usage_t::preallocated, so_5::mchain_props::overflow_reaction_t::throw_exception ) ) { so_subscribe_self().event( &a_test_t::on_sig_C ); } virtual void so_evt_start() override { so_5::send< sig_A >( m_mchain ); so_5::send_delayed< sig_B >( m_mchain, std::chrono::milliseconds(250) ); m_sent_at = std::chrono::steady_clock::now(); so_5::send_delayed< sig_C >( *this, std::chrono::milliseconds(260) ); } private : so_5::mchain_t m_mchain; std::chrono::steady_clock::time_point m_sent_at; void on_sig_C( mhood_t< sig_C > ) { const auto now = std::chrono::steady_clock::now(); ensure_or_die( now - m_sent_at < std::chrono::seconds(1), "time interval must be less than 1s" ); so_deregister_agent_coop_normally(); } }; int main() { try { run_with_time_limit( []() { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >() ); } ); }, 10, "check delayed message for overloaded mchain" " (with throw_exception overflow reaction)" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/resend_delayed_via_mhood_to_mchain/000077500000000000000000000000001523065210100301365ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/resend_delayed_via_mhood_to_mchain/CMakeLists.txt000066400000000000000000000001731523065210100326770ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.resend_delayed_via_mhood_to_mchain) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/resend_delayed_via_mhood_to_mchain/main.cpp000066400000000000000000000114571523065210100315760ustar00rootroot00000000000000/* * A test for resending delayed messages/signals via mhood to mchain. * This test is for various formats of send_delayed template functions. */ #include #include #include #include using namespace std; class first_tester final : public so_5::agent_t { struct hello final : public so_5::signal_t {}; struct mchain_has_messages final : public so_5::signal_t {}; public : first_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { m_mchain = so_environment().create_mchain( so_5::make_unlimited_mchain_params().not_empty_notificator( [this]{ so_5::send< mchain_has_messages >(*this); } ) ); so_subscribe_self() .event( &first_tester::on_hello ) .event( &first_tester::on_mchain_has_messages ); } virtual void so_evt_start() override { so_5::send_delayed< so_5::immutable_msg >( *this, std::chrono::milliseconds(50) ); } private : so_5::mchain_t m_mchain; void on_hello( mhood_t< hello > cmd ) { so_5::send_delayed( m_mchain, std::chrono::milliseconds(25), std::move(cmd) ); } void on_mchain_has_messages( mhood_t< mchain_has_messages > ) { so_5::receive( from(m_mchain).handle_n(1), [this]( mhood_t< hello > ) { so_deregister_agent_coop_normally(); } ); } }; class second_tester final : public so_5::agent_t { struct hello final : public so_5::message_t { std::string m_greeting; hello( std::string greeting ) : m_greeting(std::move(greeting)) {} // Copy or move constructors should not be invoked during // redirection. hello( const hello & ) = delete; hello( const hello && ) = delete; }; struct mchain_has_messages final : public so_5::signal_t {}; public : second_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { m_mchain = so_environment().create_mchain( so_5::make_unlimited_mchain_params().not_empty_notificator( [this]{ so_5::send< mchain_has_messages >(*this); } ) ); so_subscribe_self() .event( &second_tester::on_hello ) .event( &second_tester::on_mchain_has_messages ); } virtual void so_evt_start() override { so_5::send_delayed< so_5::immutable_msg >( *this, std::chrono::milliseconds(50), "hello" ); } private : so_5::mchain_t m_mchain; void on_hello( mhood_t< hello > cmd ) { so_5::send_delayed( m_mchain, std::chrono::milliseconds(25), std::move(cmd) ); } void on_mchain_has_messages( mhood_t< mchain_has_messages > ) { so_5::receive( from(m_mchain).handle_n(1), [this]( mhood_t< hello > cmd ) { ensure_or_die( "hello" == cmd->m_greeting, "'hello' is expected in the message" ); so_deregister_agent_coop_normally(); } ); } }; class third_tester final : public so_5::agent_t { struct hello final : public so_5::message_t { std::string m_greeting; hello( std::string greeting ) : m_greeting(std::move(greeting)) {} // Copy or move constructors should not be invoked during // redirection. hello( const hello & ) = delete; hello( const hello && ) = delete; }; struct mchain_has_messages final : public so_5::signal_t {}; public : third_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { m_mchain = so_environment().create_mchain( so_5::make_unlimited_mchain_params().not_empty_notificator( [this]{ so_5::send< mchain_has_messages >(*this); } ) ); so_subscribe_self() .event( &third_tester::on_hello ) .event( &third_tester::on_mchain_has_messages ); } virtual void so_evt_start() override { so_5::send_delayed< so_5::mutable_msg >( *this, std::chrono::milliseconds(50), "hello" ); } private : so_5::mchain_t m_mchain; void on_hello( mutable_mhood_t< hello > cmd ) { so_5::send_delayed( m_mchain, std::chrono::milliseconds(25), std::move(cmd) ); } void on_mchain_has_messages( mhood_t< mchain_has_messages > ) { so_5::receive( from(m_mchain).handle_n(1), [this]( mutable_mhood_t< hello > cmd ) { ensure_or_die( "hello" == cmd->m_greeting, "'hello' is expected in the message" ); so_deregister_agent_coop_normally(); } ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent< first_tester >()); env.register_agent_as_coop( env.make_agent< second_tester >()); env.register_agent_as_coop( env.make_agent< third_tester >()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/resend_periodic_signal_via_mhood/000077500000000000000000000000001523065210100276415ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/resend_periodic_signal_via_mhood/CMakeLists.txt000066400000000000000000000001711523065210100324000ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.resend_periodic_signal_via_mhood) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/resend_periodic_signal_via_mhood/main.cpp000066400000000000000000000046371523065210100313030ustar00rootroot00000000000000/* * A test for resending periodic signals via mhood. */ #include #include #include #include using namespace std; class first_tester final : public so_5::agent_t { struct hello final : public so_5::signal_t {}; public : first_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &first_tester::on_hello ); } virtual void so_evt_start() override { m_timer = so_5::send_periodic< so_5::immutable_msg >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds::zero() ); } private : so_5::timer_id_t m_timer; int m_received{ 0 }; void on_hello( mhood_t< hello > cmd ) { ++m_received; if( 1 == m_received ) { m_timer = so_5::send_periodic( so_direct_mbox(), std::chrono::milliseconds(25), std::chrono::milliseconds::zero(), std::move(cmd) ); } else { so_deregister_agent_coop_normally(); } } }; class second_tester final : public so_5::agent_t { struct hello final : public so_5::signal_t {}; public : second_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event( &second_tester::on_hello ); } virtual void so_evt_start() override { m_timer = so_5::send_periodic< hello >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds::zero() ); } private : so_5::timer_id_t m_timer; int m_received{ 0 }; void on_hello( mhood_t< so_5::immutable_msg > cmd ) { ++m_received; if( 1 == m_received ) { m_timer = so_5::send_periodic( so_direct_mbox(), std::chrono::milliseconds(25), std::chrono::milliseconds::zero(), std::move(cmd) ); } else { so_deregister_agent_coop_normally(); } } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent< first_tester >()); env.register_agent_as_coop( env.make_agent< second_tester >()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/resend_periodic_via_mhood_to_mchain/000077500000000000000000000000001523065210100303255ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/resend_periodic_via_mhood_to_mchain/CMakeLists.txt000066400000000000000000000001741523065210100330670ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.resend_periodic_via_mhood_to_mchain) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/resend_periodic_via_mhood_to_mchain/main.cpp000066400000000000000000000131251523065210100317570ustar00rootroot00000000000000/* * A test for resending periodic messages/signals via mhood to mchain. * This test is for various formats of send_periodic template functions. */ #include #include #include #include using namespace std; class first_tester final : public so_5::agent_t { struct hello final : public so_5::signal_t {}; struct mchain_has_messages final : public so_5::signal_t {}; public : first_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { m_mchain = so_environment().create_mchain( so_5::make_unlimited_mchain_params().not_empty_notificator( [this]{ so_5::send< mchain_has_messages >(*this); } ) ); so_subscribe_self() .event( &first_tester::on_hello ) .event( &first_tester::on_mchain_has_messages ); } virtual void so_evt_start() override { m_timer = so_5::send_periodic< so_5::immutable_msg >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds::zero() ); } private : so_5::timer_id_t m_timer; so_5::mchain_t m_mchain; int m_received{ 0 }; void on_hello( mhood_t< hello > cmd ) { ++m_received; if( 1 == m_received ) { m_timer = so_5::send_periodic( m_mchain, std::chrono::milliseconds(25), std::chrono::milliseconds::zero(), std::move(cmd) ); } else { throw std::runtime_error( "m_received != 1" ); } } void on_mchain_has_messages( mhood_t< mchain_has_messages > ) { so_5::receive( from(m_mchain).handle_n(1), [this]( mhood_t< hello > ) { so_deregister_agent_coop_normally(); } ); } }; class second_tester final : public so_5::agent_t { struct hello final : public so_5::message_t { std::string m_greeting; hello( std::string greeting ) : m_greeting(std::move(greeting)) {} // Copy or move constructors should not be invoked during // redirection. hello( const hello & ) = delete; hello( const hello && ) = delete; }; struct mchain_has_messages final : public so_5::signal_t {}; public : second_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { m_mchain = so_environment().create_mchain( so_5::make_unlimited_mchain_params().not_empty_notificator( [this]{ so_5::send< mchain_has_messages >(*this); } ) ); so_subscribe_self() .event( &second_tester::on_hello ) .event( &second_tester::on_mchain_has_messages ); } virtual void so_evt_start() override { m_timer = so_5::send_periodic< so_5::immutable_msg >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds::zero(), "hello" ); } private : so_5::timer_id_t m_timer; so_5::mchain_t m_mchain; int m_received{ 0 }; void on_hello( mhood_t< hello > cmd ) { ++m_received; if( 1 == m_received ) { m_timer = so_5::send_periodic( m_mchain, std::chrono::milliseconds(25), std::chrono::milliseconds::zero(), std::move(cmd) ); } else { throw std::runtime_error( "m_received != 1" ); } } void on_mchain_has_messages( mhood_t< mchain_has_messages > ) { so_5::receive( from(m_mchain).handle_n(1), [this]( mhood_t< hello > cmd ) { ensure_or_die( "hello" == cmd->m_greeting, "'hello' is expected in the message" ); so_deregister_agent_coop_normally(); } ); } }; class third_tester final : public so_5::agent_t { struct hello final : public so_5::message_t { std::string m_greeting; hello( std::string greeting ) : m_greeting(std::move(greeting)) {} // Copy or move constructors should not be invoked during // redirection. hello( const hello & ) = delete; hello( const hello && ) = delete; }; struct mchain_has_messages final : public so_5::signal_t {}; public : third_tester(context_t ctx) : so_5::agent_t(std::move(ctx)) { m_mchain = so_environment().create_mchain( so_5::make_unlimited_mchain_params().not_empty_notificator( [this]{ so_5::send< mchain_has_messages >(*this); } ) ); so_subscribe_self() .event( &third_tester::on_hello ) .event( &third_tester::on_mchain_has_messages ); } virtual void so_evt_start() override { m_timer = so_5::send_periodic< so_5::mutable_msg >( *this, std::chrono::milliseconds(50), std::chrono::milliseconds::zero(), "hello" ); } private : so_5::timer_id_t m_timer; so_5::mchain_t m_mchain; int m_received{ 0 }; void on_hello( mutable_mhood_t< hello > cmd ) { ++m_received; if( 1 == m_received ) { m_timer = so_5::send_periodic( m_mchain, std::chrono::milliseconds(25), std::chrono::milliseconds::zero(), std::move(cmd) ); } else { throw std::runtime_error( "m_received != 1" ); } } void on_mchain_has_messages( mhood_t< mchain_has_messages > ) { so_5::receive( from(m_mchain).handle_n(1), [this]( mutable_mhood_t< hello > cmd ) { ensure_or_die( "hello" == cmd->m_greeting, "'hello' is expected in the message" ); so_deregister_agent_coop_normally(); } ); } }; int main() { try { run_with_time_limit( []() { so_5::launch( [&](so_5::environment_t & env) { env.register_agent_as_coop( env.make_agent< first_tester >()); env.register_agent_as_coop( env.make_agent< second_tester >()); env.register_agent_as_coop( env.make_agent< third_tester >()); }, [](so_5::environment_params_t & params) { (void)params; #if 0 params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); #endif } ); }, 5, "simple agent"); } catch(const exception & ex) { cerr << "Error: " << ex.what() << endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/single_delayed/000077500000000000000000000000001523065210100240715ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/single_delayed/CMakeLists.txt000066400000000000000000000001471523065210100266330ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.single_delayed) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/single_delayed/main.cpp000066400000000000000000000024351523065210100255250ustar00rootroot00000000000000/* * A test for delayed message. */ #include #include #include #include #include struct test_message : public so_5::signal_t {}; class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_t( so_5::environment_t & env ) : base_type_t( env ), m_test_mbox( so_environment().create_mbox() ) { } ~test_agent_t() override { } void so_define_agent() override; void so_evt_start() override { // Send a delayed for 1 second message. m_timer_ref = so_5::send_periodic< test_message >( m_test_mbox, std::chrono::seconds(1), std::chrono::seconds(0) ); } void evt_test( mhood_t< test_message > ); private: so_5::mbox_t m_test_mbox; so_5::timer_id_t m_timer_ref; }; void test_agent_t::so_define_agent() { so_subscribe( m_test_mbox ) .event( &test_agent_t::evt_test ); } void test_agent_t::evt_test(mhood_t< test_message >) { so_environment().stop(); } void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); } int main() { try { so_5::launch( &init ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/single_periodic/000077500000000000000000000000001523065210100242605ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/single_periodic/CMakeLists.txt000066400000000000000000000001501523065210100270140ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.single_periodic) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/single_periodic/main.cpp000066400000000000000000000044541523065210100257170ustar00rootroot00000000000000/* * A test for scheduling and canceling delayed/periodic message. */ #include #include #include #include #include struct test_message : public so_5::signal_t {}; struct stop_message : public so_5::signal_t {}; class test_agent_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public: test_agent_t( so_5::environment_t & env ) : base_type_t( env ), m_test_mbox( so_environment().create_mbox() ) { } ~test_agent_t() override { } void so_define_agent() override; void so_evt_start() override { // Schedule periodic message. m_timer_ref = so_5::send_periodic< test_message >( m_test_mbox, std::chrono::milliseconds(300), std::chrono::milliseconds(200) ); } void evt_test( mhood_t< test_message > msg ); void evt_stop( mhood_t< stop_message > msg ); static int m_evt_count; static const int m_test_evt_count; private: so_5::mbox_t m_test_mbox; so_5::timer_id_t m_timer_ref; }; int test_agent_t::m_evt_count = 0; const int test_agent_t::m_test_evt_count = 5; void test_agent_t::so_define_agent() { so_subscribe( m_test_mbox ) .event( &test_agent_t::evt_test ); so_subscribe( m_test_mbox ) .event( &test_agent_t::evt_stop ); } void test_agent_t::evt_test( mhood_t< test_message > ) { if( m_test_evt_count == ++m_evt_count ) { // Reschedule message. Old timer event should be released. m_timer_ref = so_5::send_periodic< stop_message >( m_test_mbox, std::chrono::milliseconds(800), std::chrono::milliseconds(0) ); } } void test_agent_t::evt_stop( mhood_t< stop_message > ) { so_environment().stop(); } void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< test_agent_t >() ); } int main() { try { so_5::launch( &init ); if( test_agent_t::m_test_evt_count != test_agent_t::m_evt_count ) { std::cerr << "test_agent_t::m_test_evt_count =" << test_agent_t::m_test_evt_count << "\n" << "test_agent_t::m_evt_count = " << test_agent_t::m_evt_count << std::endl; throw std::runtime_error( "test_agent_t::m_test_evt_count != test_agent_t::m_evt_count" ); } } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/single_timer_zero_delay/000077500000000000000000000000001523065210100260175ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/single_timer_zero_delay/CMakeLists.txt000066400000000000000000000001601523065210100305540ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.single_timer_zero_delay) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/single_timer_zero_delay/main.cpp000066400000000000000000000041161523065210100274510ustar00rootroot00000000000000/* * Test of scheduling timer with zero milliseconds delay. */ #include #include #include #include struct msg_test : public so_5::message_t {}; struct msg_do_resend : public so_5::signal_t {}; struct msg_stop : public so_5::signal_t {}; class a_test_t : public so_5::agent_t { typedef so_5::agent_t base_type_t; public : a_test_t( so_5::environment_t & env, int & message_counter ) : base_type_t( env ) , m_message_counter( message_counter ) , m_mbox( env.create_mbox() ) { } void so_define_agent() override { so_subscribe( m_mbox ) .event( &a_test_t::evt_delayed_message ); so_subscribe( m_mbox ) .event( &a_test_t::evt_do_resend ); so_subscribe( m_mbox ) .event( &a_test_t::evt_stop ); } void so_evt_start() override { so_5::send_delayed< msg_stop >( m_mbox, std::chrono::milliseconds(1000) ); so_5::send_delayed< msg_test >( m_mbox, std::chrono::milliseconds(0) ); so_5::send< msg_do_resend >( m_mbox ); } void evt_delayed_message( mhood_t< msg_test > ) { m_message_counter += 1; } void evt_do_resend( mhood_t< msg_do_resend > ) { so_5::send_delayed< msg_test >( m_mbox, std::chrono::milliseconds(0) ); } void evt_stop( mhood_t< msg_stop > ) { so_environment().stop(); } private : int & m_message_counter; so_5::mbox_t m_mbox; }; struct test_env_t { int m_message_counter; test_env_t() : m_message_counter( 0 ) {} void init( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( m_message_counter ) ); } }; int main() { try { test_env_t test_env; so_5::launch( [&]( so_5::environment_t & env ) { test_env.init( env ); } ); if( 2 != test_env.m_message_counter ) { std::cerr << "expected and actual message_counter value mismatch, " "actual value: " << test_env.m_message_counter << std::endl; std::abort(); } return 0; } catch( const std::exception & x ) { std::cerr << "Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/timer_thread/timers_cancelation/000077500000000000000000000000001523065210100247645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/timer_thread/timers_cancelation/CMakeLists.txt000066400000000000000000000001531523065210100275230ustar00rootroot00000000000000set(UNITTEST _unit.test.timer_thread.timers_cancelation) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/timer_thread/timers_cancelation/main.cpp000066400000000000000000000055151523065210100264220ustar00rootroot00000000000000/* * Test of canceling delayed/period timers by timer_id_t::release() * method and timer_id_t destructor. */ #include #include #include #include #include struct msg_test : public so_5::message_t { int & m_counter; msg_test( int & counter ) : m_counter( counter ) {} ~msg_test() override { ++m_counter; } }; void ensure_counter_value( int counter, int expected_value ) { ensure( counter == expected_value, "actual(" + std::to_string(counter) + ") != expected(" + std::to_string(expected_value) + ")" ); } class a_test_t : public so_5::agent_t { public : a_test_t( so_5::environment_t & env, int & message_counter ) : so_5::agent_t( env ) , m_message_counter( message_counter ) { } void so_evt_start() override { using namespace std::chrono_literals; ensure_counter_value( m_message_counter, 0 ); m_id1 = so_5::send_periodic< msg_test >( *this, 6000ms, 0ms, std::ref(m_message_counter) ); m_id2 = so_5::send_periodic< msg_test >( *this, 6000ms, 1000ms, std::ref(m_message_counter) ); m_id3 = so_5::send_periodic< msg_test >( *this, 6000ms, 0ms, std::ref(m_message_counter) ); m_id4 = so_5::send_periodic< msg_test >( *this, 6000ms, 1000ms, std::ref(m_message_counter) ); m_id1.release(); m_id2.release(); ensure_counter_value( m_message_counter, 2 ); so_deregister_agent_coop_normally(); } private : int & m_message_counter; so_5::timer_id_t m_id1; so_5::timer_id_t m_id2; so_5::timer_id_t m_id3; so_5::timer_id_t m_id4; }; void do_test( so_5::timer_thread_factory_t factory ) { int counter = 0; so_5::launch( [&counter]( so_5::environment_t & env ) { env.register_agent_as_coop( env.make_agent< a_test_t >( counter ) ); }, [&factory]( so_5::environment_params_t & params ) { params.timer_thread( factory ); } ); ensure_counter_value( counter, 4 ); } void check_factory( const std::string & factory_name, so_5::timer_thread_factory_t factory ) { std::cout << "Checking " << factory_name << " factory..." << std::flush; do_test( factory ); std::cout << "OK" << std::endl; } int main() { try { check_factory( "timer_wheel_factory", so_5::timer_wheel_factory() ); check_factory( "timer_wheel_factory(20,1s)", so_5::timer_wheel_factory( 20, std::chrono::seconds(1) ) ); check_factory( "timer_list_factory", so_5::timer_list_factory() ); check_factory( "timer_heap_factory", so_5::timer_heap_factory() ); check_factory( "timer_heap_factory(2048)", so_5::timer_heap_factory( 2048 ) ); return 0; } catch( const std::exception & x ) { std::cerr << "Exception caught: " << x.what() << std::endl; } return 2; } sobjectizer-5.8.6/dev/test/so_5/wrapped_env/000077500000000000000000000000001523065210100207645ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/wrapped_env/CMakeLists.txt000066400000000000000000000004031523065210100235210ustar00rootroot00000000000000add_subdirectory(simple) add_subdirectory(external_stop_then_join) add_subdirectory(async_start) add_subdirectory(simple_sync_start) add_subdirectory(exception_from_sync_start) add_subdirectory(add_coop_after_start) add_subdirectory(work_thread_activity_on) sobjectizer-5.8.6/dev/test/so_5/wrapped_env/add_coop_after_start/000077500000000000000000000000001523065210100251325ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/wrapped_env/add_coop_after_start/CMakeLists.txt000066400000000000000000000001541523065210100276720ustar00rootroot00000000000000set(UNITTEST _unit.test.wrapped_env.add_coop_after_start) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/wrapped_env/add_coop_after_start/main.cpp000066400000000000000000000025251523065210100265660ustar00rootroot00000000000000/* * A test for adding a coop to wrapped_env_t after start. */ #include #include using namespace std::chrono_literals; int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env; std::cout << "Before adding a coop" << std::endl; env.environment().introduce_coop( []( so_5::coop_t & coop ) { class actor_t final : public so_5::agent_t { struct hello_sig : public so_5::signal_t {}; public : using so_5::agent_t::agent_t; void so_evt_start() override { so_subscribe_self().event( [this](mhood_t) { std::cout << "Hello for agent" << std::endl; so_5::send_delayed< hello_sig >( *this, 100ms ); } ); so_5::send_delayed< hello_sig >( *this, 25ms ); } void so_evt_finish() override { std::cout << "Bye from agent" << std::endl; } }; coop.make_agent< actor_t >(); } ); std::cout << "Coop added" << std::endl; std::this_thread::sleep_for( std::chrono::milliseconds(250) ); std::cout << "Finishing test..." << std::endl; }, 20, "test for add a coop to wrapped_env_t" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/wrapped_env/async_start/000077500000000000000000000000001523065210100233165ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/wrapped_env/async_start/CMakeLists.txt000066400000000000000000000001431523065210100260540ustar00rootroot00000000000000set(UNITTEST _unit.test.wrapped_env.async_start) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/wrapped_env/async_start/main.cpp000066400000000000000000000020041523065210100247420ustar00rootroot00000000000000/* * A test for async start of the wrapped_env. */ #include #include using namespace std::chrono_literals; int main() { try { run_with_time_limit( []() { std::mutex lock; lock.lock(); std::promise< void > init_functor_started; so_5::wrapped_env_t env{ [&]( so_5::environment_t & /*env*/ ) { // Inform that init-functor has started. init_functor_started.set_value(); // Try to acquire lock. It can be possible only // when the main thread released the lock. std::lock_guard< std::mutex > l{ lock }; std::cout << "Lock acquired in the init-functor" << std::endl; } }; init_functor_started.get_future().get(); // Now we can release the lock. lock.unlock(); std::cout << "Finishing test..." << std::endl; }, 10 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/wrapped_env/exception_from_sync_start/000077500000000000000000000000001523065210100262565ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/wrapped_env/exception_from_sync_start/CMakeLists.txt000066400000000000000000000001611523065210100310140ustar00rootroot00000000000000set(UNITTEST _unit.test.wrapped_env.exception_from_sync_start) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/wrapped_env/exception_from_sync_start/main.cpp000066400000000000000000000024501523065210100277070ustar00rootroot00000000000000/* * A test for exception from sync-start. */ #include #include #include using namespace std::chrono_literals; class test_exception final : public std::runtime_error { int m_code; public: test_exception( const char * description, int code ) : std::runtime_error{ description } , m_code{ code } {} [[nodiscard]] int code() const noexcept { return m_code; } }; int main() { try { run_with_time_limit( []() { bool exception_thrown = false; try { so_5::wrapped_env_t env{ so_5::wrapped_env_t::wait_init_completion, [&]( so_5::environment_t & /*env*/ ) { std::this_thread::sleep_for( std::chrono::milliseconds{ 150 } ); throw test_exception{ "just a test", 42 }; } }; } catch( const test_exception & x ) { ensure_or_die( 42 == x.code(), "unexpected x.code() value" ); exception_thrown = true; } ensure_or_die( exception_thrown, "exception has to be throw from wrapped_env_t constructor" ); }, 10 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/wrapped_env/external_stop_then_join/000077500000000000000000000000001523065210100257105ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/wrapped_env/external_stop_then_join/CMakeLists.txt000066400000000000000000000001571523065210100304530ustar00rootroot00000000000000set(UNITTEST _unit.test.wrapped_env.external_stop_then_join) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/wrapped_env/external_stop_then_join/main.cpp000066400000000000000000000024211523065210100273370ustar00rootroot00000000000000/* * A test for stopping wrapped_env_t. */ #include #include using namespace std::chrono_literals; int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t sobj{ []( so_5::environment_t & env ) { env.introduce_coop( []( so_5::coop_t & coop ) { class actor_t final : public so_5::agent_t { struct hello_sig : public so_5::signal_t {}; public : using so_5::agent_t::agent_t; void so_evt_start() override { so_subscribe_self().event( [this](mhood_t) { std::cout << "Hello for agent" << std::endl; so_5::send_delayed< hello_sig >( *this, 100ms ); } ); so_5::send_delayed< hello_sig >( *this, 25ms ); } }; coop.make_agent< actor_t >(); } ); } }; std::this_thread::sleep_for( std::chrono::milliseconds(250) ); sobj.stop(); std::cout << "Stop signal is sent" << std::endl; sobj.join(); std::cout << "Joined" << std::endl; }, 20, "test for stopping wrapped_env_t" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/wrapped_env/simple/000077500000000000000000000000001523065210100222555ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/wrapped_env/simple/CMakeLists.txt000066400000000000000000000001361523065210100250150ustar00rootroot00000000000000set(UNITTEST _unit.test.wrapped_env.simple) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/wrapped_env/simple/main.cpp000066400000000000000000000007241523065210100237100ustar00rootroot00000000000000/* * A simple test for wrapped_env_t. */ #include #include int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env; std::cout << "Print from primary working thread" << std::endl; }, 20, "simple test for wrapped_env_t" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/wrapped_env/simple_sync_start/000077500000000000000000000000001523065210100245265ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/wrapped_env/simple_sync_start/CMakeLists.txt000066400000000000000000000001511523065210100272630ustar00rootroot00000000000000set(UNITTEST _unit.test.wrapped_env.simple_sync_start) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/wrapped_env/simple_sync_start/main.cpp000066400000000000000000000021761523065210100261640ustar00rootroot00000000000000/* * A simple test for sync start of the wrapped_env. */ #include #include #include using namespace std::chrono_literals; int main() { try { run_with_time_limit( []() { std::atomic< int > check_flag{ 0 }; const auto t1 = std::chrono::steady_clock::now(); so_5::wrapped_env_t env{ so_5::wrapped_env_t::wait_init_completion, [&]( so_5::environment_t & /*env*/ ) { std::this_thread::sleep_for( std::chrono::milliseconds{ 100 } ); check_flag = 42; } }; const auto t2 = std::chrono::steady_clock::now(); const auto d = std::chrono::duration_cast< std::chrono::milliseconds >( t2 - t1 ); ensure_or_die( d >= std::chrono::milliseconds{ 100 }, "the constructor of wrapped_env_t finished too fast" ); ensure_or_die( 42 == check_flag.load( std::memory_order_acquire ), "unexpected value in check_flag!" ); }, 10 ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/dev/test/so_5/wrapped_env/work_thread_activity_on/000077500000000000000000000000001523065210100257055ustar00rootroot00000000000000sobjectizer-5.8.6/dev/test/so_5/wrapped_env/work_thread_activity_on/CMakeLists.txt000066400000000000000000000001571523065210100304500ustar00rootroot00000000000000set(UNITTEST _unit.test.wrapped_env.work_thread_activity_on) include(${CMAKE_SOURCE_DIR}/cmake/unittest.cmake) sobjectizer-5.8.6/dev/test/so_5/wrapped_env/work_thread_activity_on/main.cpp000066400000000000000000000015061523065210100273370ustar00rootroot00000000000000/* * A simple test for wrapped_env_t. */ #include #include #include int main() { try { run_with_time_limit( []() { so_5::wrapped_env_t env{ []( auto & ) {}, []( auto & params ) { params.turn_work_thread_activity_tracking_on(); } }; ensure_or_die( so_5::work_thread_activity_tracking_t::on == env.environment().work_thread_activity_tracking(), "work_thread_activity_tracking should be 'on' at this point" ); std::cout << "Print from primary working thread" << std::endl; }, 20, "simple test for wrapped_env_t" ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } sobjectizer-5.8.6/doxygen/000077500000000000000000000000001523065210100155255ustar00rootroot00000000000000sobjectizer-5.8.6/doxygen/Doxyfile000066400000000000000000003743121523065210100172450ustar00rootroot00000000000000# Doxyfile 1.12.0 # This file describes the settings to be used by the documentation system # Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). # # Note: # # Use Doxygen to compare the used configuration file with the template # configuration file: # doxygen -x [configFile] # Use Doxygen to compare the used configuration file with the template # configuration file without replacing the environment variables or CMake type # replacement variables: # doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the configuration # file that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = SObjectizer # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = 5.8 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # With the PROJECT_ICON tag one can specify an icon that is included in the tabs # when the HTML document is shown. Doxygen will copy the logo to the output # directory. PROJECT_ICON = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = ../dev/doc # If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 # sub-directories (in 2 levels) under the output directory of each output format # and will distribute the generated files over these directories. Enabling this # option can be useful when feeding Doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to # control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO # Controls the number of sub-directories that will be created when # CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every # level increment doubles the number of directories, resulting in 4096 # directories at level 8 which is the default and also the maximum value. The # sub-directories are organized in 2 levels, the first level always has a fixed # number of 16 directories. # Minimum value: 0, maximum value: 8, default value: 8. # This tag requires that the tag CREATE_SUBDIRS is set to YES. CREATE_SUBDIRS_LEVEL = 8 # If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, # Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English # (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, # Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with # English messages), Korean, Korean-en (Korean with English messages), Latvian, # Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, # Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, # Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = YES # If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be # interpreted by Doxygen. # The default value is: NO. JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # By default Python docstrings are displayed as preformatted text and Doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the # Doxygen's special commands can be used and the contents of the docstring # documentation blocks is shown as Doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 2 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". Note that you cannot put \n's in the value part of an alias # to insert newlines (in the resulting output). You can put ^^ in the value part # of an alias to insert a newline as if a physical newline was in the original # file. When you need a literal { or } or , in the value part of an alias you # have to escape them by means of a backslash (\), this can lead to conflicts # with the commands \{ and \} for these it is advised to use the version @{ and # @} or use a double escape (\\{ and \\}) ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice # sources only. Doxygen will then generate output that is more tailored for that # language. For instance, namespaces will be presented as modules, types will be # separated into more groups, etc. # The default value is: NO. OPTIMIZE_OUTPUT_SLICE = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, # VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make Doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by Doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by Doxygen, so you can # mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 6. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 # The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to # generate identifiers for the Markdown headings. Note: Every identifier is # unique. # Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a # sequence number starting at 0 and GITHUB use the lower case version of title # with any whitespace replaced by '-' and punctuation characters removed. # The default value is: DOXYGEN. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. MARKDOWN_ID_STYLE = DOXYGEN # When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also makes the inheritance and # collaboration diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://www.riverbankcomputing.com/software) sources only. Doxygen will parse # them like normal C++ but will assume all classes use public instead of private # inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # Doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then Doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small # Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use # during processing. When set to 0 Doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, # which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 # If the TIMESTAMP tag is set different from NO then each generated page will # contain the date or date and time when the page was generated. Setting this to # NO can help when comparing the output of multiple runs. # Possible values are: YES, NO, DATETIME and DATE. # The default value is: NO. TIMESTAMP = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = YES # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. # The default value is: NO. EXTRACT_PRIV_VIRTUAL = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = YES # If this flag is set to YES, the name of an unnamed parameter in a declaration # will be determined by the corresponding definition. By default unnamed # parameters remain unnamed in the output. # The default value is: YES. RESOLVE_UNNAMED_PARAMS = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # will also hide undocumented C++ concepts if enabled. This option has no effect # if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # With the correct setting of option CASE_SENSE_NAMES Doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that # are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and macOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # Possible values are: SYSTEM, NO and YES. # The default value is: SYSTEM. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_HEADERFILE tag is set to YES then the documentation for a class # will show which file needs to be included to use the class. # The default value is: YES. SHOW_HEADERFILE = YES # If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # Doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents Doxygen's defaults, run Doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. See also section "Changing the # layout of pages" for information. # # Note that if you run Doxygen from a directory containing a file called # DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = # The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH # environment variable) so that external tools such as latex and gs can be # found. # Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the # path already specified by the PATH variable, and are added in the order # specified. # Note: This option is particularly useful for macOS version 14 (Sonoma) and # higher, when running Doxygen from Doxywizard, because in this case any user- # defined changes to the PATH are ignored. A typical example on macOS is to set # EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin # together with the standard path, the full search path used by doxygen when # launching external tools will then become # PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin EXTERNAL_TOOL_PATH = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for # potential errors in the documentation, such as documenting some parameters in # a documented function twice, or documenting parameters that don't exist or # using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete # function parameter documentation. If set to NO, Doxygen will accept that some # parameters have no documentation without warning. # The default value is: YES. WARN_IF_INCOMPLETE_DOC = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, Doxygen will only warn about wrong parameter # documentation, but not about the absence of documentation. If EXTRACT_ALL is # set to YES then this flag will automatically be disabled. See also # WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO # If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about # undocumented enumeration values. If set to NO, Doxygen will accept # undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: NO. WARN_IF_UNDOC_ENUM_VAL = NO # If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the Doxygen process Doxygen will return with a non-zero status. # If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves # like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not # write the warning messages in between other messages but write them at the end # of a run, in case a WARN_LOGFILE is defined the warning messages will be # besides being in the defined file also be shown at the end of a run, unless # the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case # the behavior will remain as with the setting FAIL_ON_WARNINGS. # Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that Doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # In the $text part of the WARN_FORMAT command it is possible that a reference # to a more specific place is given. To make it easier to jump to this place # (outside of Doxygen) the user can define a custom "cut" / "paste" string. # Example: # WARN_LINE_FORMAT = "'vi $file +$line'" # See also: WARN_FORMAT # The default value is: at line $line of file $file. WARN_LINE_FORMAT = "at line $line of file $file" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). In case the file specified cannot be opened for writing the # warning and error messages are written to standard error. When as file - is # specified the warning and error messages are written to standard output # (stdout). WARN_LOGFILE = doxygen.log #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = ./dox \ ../dev/so_5 # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. # See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = CP1251 # This tag can be used to specify the character encoding of the source files # that Doxygen parses The INPUT_FILE_ENCODING tag can be used to specify # character encoding on a per file pattern basis. Doxygen will compare the file # name with each pattern and apply the encoding instead of the default # INPUT_ENCODING) if there is a match. The character encodings are a list of the # form: pattern=encoding (like *.php=ISO-8859-1). # See also: INPUT_ENCODING for further information on supported encodings. INPUT_FILE_ENCODING = # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by Doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, # *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, # *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, # *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to # be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which Doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = ../dev/sample \ .. # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = *.?pp # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = YES # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that Doxygen will use the data processed and written to standard output # for further processing, therefore nothing else, like debug statements or used # commands (so in case of a Windows batch file always use @echo OFF), should be # written to standard output. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the Doxygen output. USE_MDFILE_AS_MAINPAGE = # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The # setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can # be processed before the automatic comment starts. # Minimum value: 7, maximum value: 10000, default value: 72. FORTRAN_COMMENT_AFTER = 72 #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body of functions, # multi-line macros, enums or list initialized variables directly into the # documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of Doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = NO # If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the # clang parser (see: # http://clang.llvm.org/) for more accurate parsing at the cost of reduced # performance. This can be particularly helpful with template rich C++ code for # which Doxygen's built-in parser lacks the necessary type information. # Note: The availability of this option depends on whether or not Doxygen was # generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = YES # If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS # tag is set to YES then Doxygen will add the directory of each input to the # include path. # The default value is: YES. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_ADD_INC_PATHS = YES # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by Doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = -I../dev \ -std=c++17 # If clang assisted parsing is enabled you can provide the clang parser with the # path to the directory containing a file called compile_commands.json. This # file is the compilation database (see: # http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the # options used when the source files were built. This is equivalent to # specifying the -p option to a clang tool, such as clang-check. These options # will then be passed to the parser. Any options specified with CLANG_OPTIONS # will be added as well. # Note: The availability of this option depends on whether or not Doxygen was # generated with the -Duse_libclang=ON option for CMake. CLANG_DATABASE_PATH = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) # that should be ignored while generating the index headers. The IGNORE_PREFIX # tag works for classes, function and member names. The entity will be placed in # the alphabetical list under the first letter of the entity name that remains # after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). # Note: Since the styling of scrollbars can currently not be overruled in # Webkit/Chromium, the styling will be left out of the default doxygen.css if # one or more extra stylesheets have been specified. So if scrollbar # customization is desired it has to be added explicitly. For an example see the # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE tag can be used to specify if the generated HTML output # should be rendered with a dark or light theme. # Possible values are: LIGHT always generates light mode output, DARK always # generates dark mode output, AUTO_LIGHT automatically sets the mode according # to the user preference, uses light mode if no preference is set (the default), # AUTO_DARK automatically sets the mode according to the user preference, uses # dark mode if no preference is set and TOGGLE allows a user to switch between # light and dark mode via a button. # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE = AUTO_LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML # page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be # dynamically folded and expanded in the generated HTML source code. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_CODE_FOLDING = YES # If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in # the top right corner of code and text fragments that allows the user to copy # its content to the clipboard. Note this only works if supported by the browser # and the web page is served via a secure context (see: # https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: # protocol. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COPY_CLIPBOARD = YES # Doxygen stores a couple of settings persistently in the browser (via e.g. # cookies). By default these settings apply to all HTML pages generated by # Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store # the settings under a project specific key, such that the user preferences will # be stored separately. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_PROJECT_COOKIE = # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To # create a documentation set, Doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag determines the URL of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDURL = # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # on Windows. In the beginning of 2021 Microsoft took the original page, with # a.o. the download links, offline the HTML help workshop was already many years # in maintenance mode). You can download the HTML help workshop from the web # archives at Installation executable (see: # http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo # ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # The SITEMAP_URL tag is used to specify the full URL of the place where the # generated documentation will be placed on the server by the user during the # deployment of the documentation. The generated sitemap is called sitemap.xml # and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL # is specified no sitemap is generated. For information about the sitemap # protocol see https://www.sitemaps.org # This tag requires that the tag GENERATE_HTML is set to YES. SITEMAP_URL = # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path # including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine tune the look of the index (see "Fine-tuning the output"). As an # example, the default style sheet generated by Doxygen has an example that # shows how to put an image at the root of the tree instead of the PROJECT_NAME. # Since the tree basically has the same information as the tab index, you could # consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the # FULL_SIDEBAR option determines if the side bar is limited to only the treeview # area (value NO) or if it should extend to the full height of the window (value # YES). Setting this to YES gives a layout similar to # https://docs.readthedocs.io with more room for contents, but less room for the # project logo, title, and description. If either GENERATE_TREEVIEW or # DISABLE_INDEX is set to NO, this option has no effect. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. FULL_SIDEBAR = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # When the SHOW_ENUM_VALUES tag is set doxygen will show the specified # enumeration values besides the enumeration mnemonics. # The default value is: NO. SHOW_ENUM_VALUES = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email # addresses. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. OBFUSCATE_EMAILS = YES # If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. # Possible values are: png (the default) and svg (looks nicer but requires the # pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # With MATHJAX_VERSION it is possible to specify the MathJax version to be used. # Note that the different versions of MathJax have different requirements with # regards to the different settings, so it is possible that also other MathJax # settings have to be changed when switching between the different MathJax # versions. # Possible values are: MathJax_2 and MathJax_3. # The default value is: MathJax_2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_VERSION = MathJax_2 # When MathJax is enabled you can set the default output format to be used for # the MathJax output. For more details about the output format see MathJax # version 2 (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 # (see: # http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best # compatibility. This is the name for Mathjax version 2, for MathJax version 3 # this will be translated into chtml), NativeMML (i.e. MathML. Only supported # for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This # is the name for Mathjax version 3, for MathJax version 2 this will be # translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. The default value is: # - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 # - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # for MathJax version 2 (see # https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): # MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled Doxygen will generate a search box for # the HTML output. The underlying search engine uses JavaScript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /Node, # Edge and Graph Attributes specification You need to make sure dot is able # to find the font, which can be done by putting it in a standard location or by # setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the # directory containing the font. Default graphviz fontsize is 14. # The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" # DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can # add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about # arrows shapes. # The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" # DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes # around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification # The default value is: shape=box,height=0.2,width=0.4. # This tag requires that the tag HAVE_DOT is set to YES. DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" # You can set the path where dot can find font specified with fontname in # DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = # If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then Doxygen will # generate a graph for each documented class showing the direct and indirect # inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and # HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case # the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the # CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. # If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance # relations will be shown as texts / links. Explicit enabling an inheritance # graph or choosing a different representation for an inheritance graph of a # specific class, can be accomplished by means of the command \inheritancegraph. # Disabling an inheritance graph can be accomplished by means of the command # \hideinheritancegraph. # Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then Doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the # class with other documented classes. Explicit enabling a collaboration graph, # when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the # command \collaborationgraph. Disabling a collaboration graph can be # accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then Doxygen will generate a graph for # groups, showing the direct groups dependencies. Explicit enabling a group # dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means # of the command \groupgraph. Disabling a directory graph can be accomplished by # means of the command \hidegroupgraph. See also the chapter Grouping in the # manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES, Doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. UML_LOOK = NO # If the UML_LOOK tag is enabled, the fields and methods are shown inside the # class node. If there are many fields or methods and many nodes the graph may # become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the # number of items for each type to make the size more manageable. Set this to 0 # for no limit. Note that the threshold may be exceeded by 50% before the limit # is enforced. So when you set the threshold to 10, up to 15 fields may appear, # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. # This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 # If the DOT_UML_DETAILS tag is set to NO, Doxygen will show attributes and # methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS # tag is set to YES, Doxygen will add type and arguments for attributes and # methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, Doxygen # will not generate fields with class member information in the UML graphs. The # class diagrams will look similar to the default class diagrams but using UML # notation for the relationships. # Possible values are: NO, YES and NONE. # The default value is: NO. # This tag requires that the tag UML_LOOK is set to YES. DOT_UML_DETAILS = NO # The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters # to display on a single line. If the actual line length exceeds this threshold # significantly it will be wrapped across multiple lines. Some heuristics are # applied to avoid ugly line breaks. # Minimum value: 0, maximum value: 1000, default value: 17. # This tag requires that the tag HAVE_DOT is set to YES. DOT_WRAP_THRESHOLD = 17 # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then Doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented # files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, # can be accomplished by means of the command \includegraph. Disabling an # include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then Doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented # files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set # to NO, can be accomplished by means of the command \includedbygraph. Disabling # an included by graph can be accomplished by means of the command # \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH tag is set to YES then Doxygen will generate a call # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected # functions only using the \callgraph command. Disabling a call graph can be # accomplished by means of the command \hidecallgraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. CALL_GRAPH = NO # If the CALLER_GRAPH tag is set to YES then Doxygen will generate a caller # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected # functions only using the \callergraph command. Disabling a caller graph can be # accomplished by means of the command \hidecallergraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY tag is set to YES then Doxygen will graphical # hierarchy of all classes instead of a textual one. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then Doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the # files in the directories. Explicit enabling a directory graph, when # DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command # \directorygraph. Disabling a directory graph can be accomplished by means of # the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES # The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels # of child directories generated in directory dependency graphs by dot. # Minimum value: 1, maximum value: 25, default value: 1. # This tag requires that the tag DIRECTORY_GRAPH is set to YES. DIR_GRAPH_MAX_DEPTH = 1 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: # https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). # Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, # png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and # png:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. DOT_IMAGE_FORMAT = png # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. # # Note that this requires a modern browser other than Internet Explorer. Tested # and working are Firefox, Chrome, Safari, and Opera. # Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make # the SVG files visible. Older versions of IE do not have SVG support. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. INTERACTIVE_SVG = NO # The DOT_PATH tag can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. # This tag requires that the tag HAVE_DOT is set to YES. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile # command). # This tag requires that the tag HAVE_DOT is set to YES. DOTFILE_DIRS = # You can include diagrams made with dia in Doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile # command). DIAFILE_DIRS = # When using PlantUML, the PLANTUML_JAR_PATH tag should be used to specify the # path where java can find the plantuml.jar file or to the filename of jar file # to be used. If left blank, it is assumed PlantUML is not used or called during # a preprocessing step. Doxygen will generate a warning when it encounters a # \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = # When using PlantUML, the PLANTUML_CFG_FILE tag can be used to specify a # configuration file for PlantUML. PLANTUML_CFG_FILE = # When using PlantUML, the specified paths are searched for files specified by # the !include statement in a PlantUML block. PLANTUML_INCLUDE_PATH = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes # larger than this value, Doxygen will truncate the graph, which is visualized # by representing a node as a red box. Note that if the number of direct # children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that # the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. # Minimum value: 0, maximum value: 10000, default value: 50. # This tag requires that the tag HAVE_DOT is set to YES. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs # generated by dot. A depth value of 3 means that only nodes reachable from the # root by following a path via at most 3 edges will be shown. Nodes that lay # further from the root node will be omitted. Note that setting this option to 1 # or 2 may greatly reduce the computation time needed for large code bases. Also # note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. # Minimum value: 0, maximum value: 1000, default value: 0. # This tag requires that the tag HAVE_DOT is set to YES. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support # this, this feature is disabled by default. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES Doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. # Note: This tag requires that UML_LOOK isn't set, i.e. the Doxygen internal # graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES, Doxygen will remove the intermediate # files that are used to generate the various graphs. # # Note: This setting is not only used for dot files but also for msc temporary # files. # The default value is: YES. DOT_CLEANUP = YES # You can define message sequence charts within Doxygen comments using the \msc # command. If the MSCGEN_TOOL tag is left empty (the default), then Doxygen will # use a built-in version of mscgen tool to produce the charts. Alternatively, # the MSCGEN_TOOL tag can also specify the name an external tool. For instance, # specifying prog as the value, Doxygen will call the tool as prog -T # -o . The external tool should support # output file formats "png", "eps", "svg", and "ismap". MSCGEN_TOOL = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the \mscfile # command). MSCFILE_DIRS = sobjectizer-5.8.6/doxygen/dox/000077500000000000000000000000001523065210100163175ustar00rootroot00000000000000sobjectizer-5.8.6/doxygen/dox/license.dox000066400000000000000000000001171523065210100204540ustar00rootroot00000000000000/*! * \page so_5__license SObjectizer License * * \verbinclude LICENSE */ sobjectizer-5.8.6/doxygen/dox/mainpage.dox000066400000000000000000000001101523065210100206040ustar00rootroot00000000000000/*! * \mainpage * * \ref so_5__version * * \ref so_5__license */ sobjectizer-5.8.6/doxygen/dox/so_5/000077500000000000000000000000001523065210100171645ustar00rootroot00000000000000sobjectizer-5.8.6/doxygen/dox/so_5/led_indicator_statechart.png000066400000000000000000000155621523065210100247250ustar00rootroot00000000000000PNG  IHDR#5tEXtcopyleftGenerated by http://plantuml.sourceforge.net:gUzTXtplantumlx]Kk@ sL э5>r(RI@RDVB2+ n[0y,ZlSqr X 05x.zƻ=@"I3_X٦H"NV33<KA| 4K-aēI·M,R-uf#wE I5V)q/zʫ&M"(q``:Q fH 5IDATx Ts+kZ^C[]^(>O겖Qz|Bն"D-< 3A&E(BW|!"TER0sBIHH2V7d{f7}$$H$ q@B$ q@" 6lѩS'LO ̒࠺z…d޼ydl1s7,kMzyq890NvT*?`РA2c_h4D8/sQ 잃s=@.9@*< +7&s0k֬8J(:1&s0LZZ)r0qD%#K ϟ$&&"K@h 'n߾q!::ZPdddk7:;;,KH>LW۷\.'AQQQn SN-FEͿ=zٸvZڍb㠸}رz 9rݻFA\@DHb^=''Wv@@Ia{|Xs΀+\͸Jell,l K3:+.pg$6`Ex[ea(D6 *;;=.}p@@ ~@ϡ\< ڄ `+K'< `fp>C XD_s"w 038E|En@ȴq@q@q@q@q@q@q@q@q@q@q@q@q@q@q@q@q@q@q@q@q@āJr=4DW*DihsۅWQ/yK.ocRRR+ m$,(u5p%A&X+<ܯo߿8::s׮5uQ[>Ga DlEEBӧ<bc2?vr똘Ĭ,DQ[*Ìrw/fdQ78\!8 8 8 ln_BzB[ **8h') e,R 뻒8h/|/ XN?8 8 8 8 8IL~86./^ަ8se&,./Ig?MgS.a;cāe} pbƄNεT} ̲,q`)srָ&r&NF qr_ )p%c{h'*f-%اfā~ok-7҉BwfbQsJS;Z%!܃VzvhO6:0 C{M.Ƨ#S,ꑃYD!SN [9g i 8fׁW8k#sҴ}nވup iU᣶1X=yPPytَvhLe_0C' 4N)r*bqFoQ__ߋ+M2nw:}: " .ϳ'7vl}:=\ATиyKZ7|)em-ұ|u!q#>fiǰ;?4)r>Rvxi50 'LRګ2d{W!5A_0IJpg'/.nc"Xy Oi\]mA!3hF(ry#(S%f]p6wpYeS6ip|^CóBCPOFxF]PϵLߍ( b`va5o49S>0F ѐD#R,|GZq(:P=6]`GvJp AָŹ>l :K^0UT;k׷<ZR.#1N`{@rr4҄mp*Fy1ק{/1q;fd{-(fEǡ:{|{w^8>.'@Pale\yۃ_Е:h "?A'_Nujvt"6AihAv4i]e \X[ocpP֨]_UtRBpMbwO=FKrESp<A āL;j;,BZ:ā)hG LFspngbp 8мɷ"!o+{Y#OFA](ārEyľ~^=9mWH{-AU |%۳`3}=O+- N,(b-Ƌڊ _y)0qʵTZTuMm_JjMK JָWGrjՎ[Uj6-krāVH)w@|ؤUQ;: Tuyh4JGܺZL-lL֤ )qȑ#Bg0^"8S8`VqqrrYqLۉ=O\;y.v&r}?S#8@D@\n;߶msň>}8::kNs4uuu(`f7"kAA gj:z\;3f̸s8mڴ}ӦMs4+Vm &`ѼTePhi4yqn\ںpWGW^݈~Tk gџqy"7N N`Eqrv6""ft~Ͳ8ߙA樅HHF]/8p˖-P̢+W"!A݈|:Y6џq.R-(]Hhq ݹsYиh"FY`-L@j/x4o"IGq*n$)@@Ўb*͂N™qX}$:\eBm18{7%l5XK9-<^ HdJ2qlQh@T5)nKB$<@}$7Eҫ+#v]eMrhÐ3A SD31IO{"38@l3c'/2$ )Lh@Z*tjM!D쮤? ZUJeZ^bWeYO폃ǎMZr<br䛙杏TY}d~>3=elΔOK+\;Pic99wIrw"oH>pX&Qt"%?=ztˍryZ/T@ (\۹d-Kx}ΣYmbmmm}}MpySBV]=L Idv.oaDWWW 9@bU4ş'q~~~YYY D3@bH&V9K ygTNNN ӕ,CZ֍l!TAALaݒ>~(yl\JJ roJHe~}vd={8%|չɣ2NQ!IKK+((@ r.)՚B2}+>>>ІՓ@!6+@Mtn>:g vcLLLbbbVV\BS2O`3/wbFU_юn+q"##r9\@wohwJ:BA7_cXR8"_08CE@wAԅq9`7EEEEGG+ ārBu|@rF:q f MP!T* -rP`-0U4׌l+jG7 XPb (?8 8 $ʁa-LZa!KϷމ`˖}H.W)'Ӧ}(p>}^{͍{8L&VuJgg:tx[[gdn&fnAjWV_頠%z8w'w,Q}s?ۣ:gOXh 74D} /^zk.-9-t{ݷ._>zY~$POSeAI"d y !蚑 ¿MKa~ 68}uȲO5$d9f ; d HrHV phJ^@vE ́ˀdL8d|MT%"DNr Ad}{?[RԼN\1% bӱwyDۃW.G%ZsSW^/v/ sZO\__AKr3gz, [7G~ Dss͖:咒6 u-+ۯ%Z{r:WN9AٻvAcEq%Z`LLXu8({zN_jcQ\;ztv9/BdxeAB&79 kQ}|ş3#g,k5.Ƨ#U$R_ʙM$њǁ7w!3HLh:&O'&r/#1U=*7$b 8 C{gIENDB`sobjectizer-5.8.6/doxygen/dox/so_5/led_indicator_statechart_plantuml.txt000066400000000000000000000002171523065210100266630ustar00rootroot00000000000000[*] --> off state off { off --> on : turn_on_off } state on { on --> off : turn_on_off on: enter/turn_led_on on: exit/turn_led_off } sobjectizer-5.8.6/doxygen/dox/so_5/namespaces.dox000066400000000000000000000343271523065210100220300ustar00rootroot00000000000000/*! * \brief Main SObjectizer-5 namespace */ namespace so_5 { /*! * \brief Public API functions of SObjectizer-5. */ namespace api { /*! * \brief Internal implemetation details of public API functions. */ namespace impl { } /* namespace so_5::api::impl */ } /* namespace so_5::api */ /*! * \brief Some reusable and low-level classes/functions which can * be used in public header files. * \since * v.5.5.4 */ namespace details { /*! * \brief Helper templates for detection of lambda-type traits. * \since * v.5.5.4 */ namespace lambda_traits { /*! * \brief Internal implementation details. * \since * v.5.5.4 */ namespace impl { } /* namespace impl */ } /* namespace lambda_traits */ /*! * \brief Implementation details for implementation of rollback on * exception helper. * \since * v.5.5.4 */ namespace rollback_on_exception_details { } /* namespace rollback_on_exception_details */ /*! * \brief Helpers for manipulation with standard C++ I/O streams. * \since * v.5.5.4 */ namespace ios_helpers { } /* namespace ios_helpers */ } /* namespace details */ /*! * \brief Event dispatchers. */ namespace disp { /*! * \brief Various stuff related to MPMC event queue implementation and tuning. * \since * v.5.5.11 */ namespace mpmc_queue_traits { /*! * \brief Implementation of combined lock for MPMC event queue. * \since * v.5.5.11 */ namespace combined_lock { } /* namespace combined_lock */ /*! * \brief Implementation of simple lock for MPMC event queue. * \since * v.5.5.11 */ namespace simple_lock { } /* namespace simple_lock */ } /* namespace mpsc_queue_traits */ /*! * \brief Various stuff related to MPSC event queue implementation and tuning. * \since * v.5.5.10 */ namespace mpsc_queue_traits { /*! * \brief Implementation details for MPSC event queue stuff. * \since * v.5.5.10 */ namespace impl { } /* namespace impl */ } /* namespace mpsc_queue_traits */ /*! * \brief Active groups dispatcher. */ namespace active_group { /*! * \brief Active groups dispatcher implemetation details. */ namespace impl { } /* namespace impl */ } /* namespace active_group */ /*! * \brief Active objects dispatcher. */ namespace active_obj { /*! * \brief Active objects dispatcher implemetation details. */ namespace impl { } /* namespace impl */ } /* namespace active_obj */ /*! * \brief Advanced thread pool dispatcher. */ namespace adv_thread_pool { /*! * \brief Internal implementation details of advanced thread pool dispatcher. */ namespace impl { } /* namespace impl */ } /* namespace adv_thread_pool */ /*! * \brief Dispatcher with single working thread. */ namespace one_thread { /*! * \brief Implementation details for dispatcher with * single working thread. */ namespace impl { } /* namespace impl */ } /* namespace one_thread */ /*! * \brief Reusable components for dispatchers. * * This namespace contains classes and functions which can * be reused by various types of dispatchers. */ namespace reuse { /*! * \brief Implementation of various types of locks to be used * inside dispatchers. */ namespace locks { } /* namespace locks */ /*! * \brief Helper tools for implementation of run-time monitoring * for thread-pool-like dispatchers. * \since * v.5.5.4 */ namespace thread_pool_stats { } /* namespace thread_pool_stats */ /*! * \brief Implemetation details of dispatcher's working thread. */ namespace work_thread { } /* namespace work_thread */ } /* namespace reuse */ /*! * \brief Thread pool dispatcher. */ namespace thread_pool { /*! * \brief Reusable implementation of some thread pool dispatcher * functionality which can be used in other thread-pool-like dispatchers. */ namespace common_implementation { } /* namespace common_implementation */ /*! * \brief Internal implementation details of thread pool dispatcher. */ namespace impl { } /* namespace impl */ } /* namespace thread_pool */ /*! * \brief Dispatchers with dedicated threads for every priority. * \since * v.5.5.8 */ namespace prio_dedicated_threads { /*! * \brief Dispatcher which creates exactly one thread per priority. * \since * v.5.5.8 */ namespace one_per_prio { /*! * \brief Implementation details for dispatcher with one thread per priority. * \since * v.5.5.8 */ namespace impl { } /* namespace impl */ } /* namespace one_per_prio */ } /* namespace prio_dedicated_threads */ /*! * \brief Dispatcher with one working thread for events of all priorities. * \since * v.5.5.8 */ namespace prio_one_thread { /*! * \brief Dispatcher which handles events of different priorities in * round-robin maner. * \since * v.5.5.8 */ namespace quoted_round_robin { /*! * \brief Implementation details for dispatcher with round-robin * policy of handling prioritized events. * \since * v.5.5.8 */ namespace impl { } /* namespace impl */ } /* namespace quoted_round_robin */ /*! * \brief Reusable code for dispatchers with one working thread for * events of all priorities. * \since * v.5.5.8 */ namespace reuse { } /* namespace reuse */ /*! * \brief Dispatcher which handles events in strict order (from * highest priority to lowest). * \since * v.5.5.8 */ namespace strictly_ordered { /*! * \brief Implementation details for dispatcher which handles * prioritized events in strict order. * \since * v.5.5.8 */ namespace impl { } /* namespace impl */ } /* namespace strictly_ordered */ } /* namespace prio_one_thread */ } /* namespace disp */ /*! * \brief Implementation details of error_logging facility. */ namespace log_msg_details { } /* namespace log_msg_details */ /*! * \brief Details of SObjectizer run-time implementations. */ namespace impl { /*! * \brief Implementation details related to cooperations and * agents repository. */ namespace agent_core_details { } /* namespace agent_core_details */ /*! * \brief Details of cooperation deregistration implementation */ namespace coop_dereg { } /* namespace coop_dereg */ /*! * \brief Various helpers for message delivery tracing mechanism. * \since * v.5.5.9 */ namespace msg_tracing_helpers { /*! * \brief Low-level details of message delivery tracing * implementation. * \since * v.5.5.9 */ namespace details { } /* namespace details */ } /* namespace msg_tracing_helpers */ /*! * \brief Implementation details for MPMC mboxes. * \since * v.5.5.9 */ namespace local_mbox_details {} } /* namespace impl */ /*! * \brief Enumeration of cooperation deregistration reasons. * \since * v.5.2.3 */ namespace dereg_reason { } /* namespace dereg_reason */ /*! * \brief Some low-level implemetation details which can be used * in public header files. */ namespace details { } /* namespace details */ /*! * \brief All stuff related to message limits. * \since * v.5.5.4 */ namespace message_limit { /*! * \brief Internal implementation of message limits related stuff. * \since * v.5.5.4 */ namespace impl { } /* namespace impl */ } /* namespace message_limit */ /*! * \brief All stuff related to run-time monitoring and statistics. * \since * v.5.5.4 */ namespace stats { /*! * \brief Internal implementation of run-time monitoring * and statistics related stuff. * \since * v.5.5.4 */ namespace impl { } /* namespace impl */ /*! * \brief Declarations of messages used by run-time monitoring * and statistics. * \since * v.5.5.4 */ namespace messages { } /* namespace messages */ /*! * \brief Predefined prefixes of data-sources. * \since * v.5.5.4 */ namespace prefixes { } /* namespace prefixes */ /*! * \brief Predefined suffixes of data-sources. * \since * v.5.5.4 */ namespace suffixes { } /* namespace suffixes */ } /* namespace stats */ /*! * \brief All code related to implementation of SObjectizer run-time. * \deprecated Obsolete and will be removed in v.5.6.0. */ namespace rt { /*! * \brief Details of SObjectizer run-time implementations. * \deprecated Obsolete and will be removed in v.5.6.0. */ namespace impl { /*! * \brief Implementation details related to cooperations and * agents repository. * \deprecated Obsolete and will be removed in v.5.6.0. */ namespace agent_core_details { } /* namespace agent_core_details */ /*! * \brief Details of cooperation deregistration implementation * \deprecated Obsolete and will be removed in v.5.6.0. */ namespace coop_dereg { } /* namespace coop_dereg */ /*! * \brief Various helpers for message delivery tracing mechanism. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.5.9 */ namespace msg_tracing_helpers { /*! * \brief Low-level details of message delivery tracing * implementation. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.5.9 */ namespace details { } /* namespace details */ } /* namespace msg_tracing_helpers */ } /* namespace impl */ /*! * \brief Enumeration of cooperation deregistration reasons. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.2.3 */ namespace dereg_reason { } /* namespace dereg_reason */ /*! * \brief Some low-level implemetation details which can be used * in public header files. * \deprecated Obsolete and will be removed in v.5.6.0. */ namespace details { } /* namespace details */ /*! * \brief All stuff related to message limits. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.5.4 */ namespace message_limit { /*! * \brief Internal implementation of message limits related stuff. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.5.4 */ namespace impl { } /* namespace impl */ } /* namespace message_limit */ /*! * \brief All stuff related to run-time monitoring and statistics. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.5.4 */ namespace stats { /*! * \brief Internal implementation of run-time monitoring * and statistics related stuff. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.5.4 */ namespace impl { } /* namespace impl */ /*! * \brief Declarations of messages used by run-time monitoring * and statistics. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.5.4 */ namespace messages { } /* namespace messages */ /*! * \brief Predefined prefixes of data-sources. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.5.4 */ namespace prefixes { } /* namespace prefixes */ /*! * \brief Predefined suffixes of data-sources. * \deprecated Obsolete and will be removed in v.5.6.0. * \since * v.5.5.4 */ namespace suffixes { } /* namespace suffixes */ /*! * \brief Various stuff for simplify implementation of * work thread activity tracking. */ namespace activity_tracking_stuff { } /* namespace activity_tracking_stuff */ } /* namespace stats */ } /* namespace rt */ /*! * \deprecated * \brief Old timer thread related stuff. * * This namespace is kept for compatibility with previous versions. */ namespace timer_thread { } /* namespace timer_thread */ /*! * \brief Timers implementation details. * \since * v.5.5.0 */ namespace timers_details { } /* namespace timers_details */ /*! * \brief Helpers for working with priorities. * \since * v.5.5.8 */ namespace prio { } /* namespace prio */ /*! * \brief Public part of message delivery tracing mechanism. * \since * v.5.5.9 */ namespace msg_tracing { /*! * \brief Implementation details of message delivery tracing mechanism. * \since * v.5.5.9 */ namespace impl { } /* namespace impl */ } /* namespace msg_tracing */ /*! * \brief Various properties and parameters of message chains. * \since * v.5.5.13 */ namespace mchain_props { /*! * \brief Implementation details. * * \since * v.5.5.13 */ namespace details { } /* namespace details */ } /* namespace mchain_props */ /*! * \brief Various implementations of environment_infrastructure. * * \since * v.5.5.19 */ namespace env_infrastructures { /*! * \brief Default multi-threaded environment infrastructure. * * \since * v.5.5.19 */ namespace default_mt { /*! * \brief Implementation details. * * \since * v.5.5.19 */ namespace impl {} } /* namespace default_mt */ /*! * \brief Simple single-threaded environment infrastructure with * thread safety. * * \since * v.5.5.19 */ namespace simple_mtsafe { /*! * \brief Implementation details. * * \since * v.5.5.19 */ namespace impl {} } /* namespace simple_mtsafe */ /*! * \brief Simple single-threaded environment infrastructure * without thread safety. * * \since * v.5.5.19 */ namespace simple_not_mtsafe { /*! * \brief Implementation details. * * \since * v.5.5.19 */ namespace impl {} } /* namespace simple_not_mtsafe */ /*! * \brief Various reusable stuff which can be used in implementation * of single-threaded environment infrastructures. * * \since * v.5.5.19 */ namespace st_reusable_stuff { /*! * \brief Implementation details. * * \since * v.5.5.19 */ namespace impl {} } /* namespace st_reusable_stuff */ } /* namespace env_infrastructures */ } /* namespace so_5 */ // vim:ft=cpp.doxygen sobjectizer-5.8.6/doxygen/dox/so_5/samples.dox000066400000000000000000000060731523065210100213520ustar00rootroot00000000000000/*! * \example so_5/adv_thread_pool_fifo/main.cpp * \example so_5/agent_name/main.cpp * \example so_5/bind_transformer/main.cpp * \example so_5/blinking_led/main.cpp * \example so_5/chameneos_prealloc_msgs/main.cpp * \example so_5/chameneos_simple/main.cpp * \example so_5/child_soenv/main.cpp * \example so_5/chstate/main.cpp * \example so_5/chstate_msg_tracing/main.cpp * \example so_5/coop_listener/main.cpp * \example so_5/coop_notification/main.cpp * \example so_5/coop_user_resources/main.cpp * \example so_5/custom_direct_mbox/main.cpp * \example so_5/custom_error_logger/main.cpp * \example so_5/custom_work_thread_factory/main.cpp * \example so_5/deadletter_handler/main.cpp * \example so_5/default_message_limits/main.cpp * \example so_5/delivery_filters/main.cpp * \example so_5/disp/main.cpp * \example so_5/dispatcher_for_children/main.cpp * \example so_5/dispatcher_for_children_2/main.cpp * \example so_5/dispatcher_hello/main.cpp * \example so_5/dispatcher_restarts/main.cpp * \example so_5/exception_logger/main.cpp * \example so_5/exception_reaction/main.cpp * \example so_5/hardwork_imit/main.cpp * \example so_5/hello_all/main.cpp * \example so_5/hello_delay/main.cpp * \example so_5/hello_evt_handler/main.cpp * \example so_5/hello_evt_lambda/main.cpp * \example so_5/hello_periodic/main.cpp * \example so_5/hello_world/main.cpp * \example so_5/hello_world_simple_not_mtsafe/main.cpp * \example so_5/individual_msg_tracing/main.cpp * \example so_5/intercom_statechart/main.cpp * \example so_5/introduce_named_mbox/main.cpp * \example so_5/machine_control/main.cpp * \example so_5/make_agent_ref/main.cpp * \example so_5/make_new_direct_mbox/main.cpp * \example so_5/make_pipeline/main.cpp * \example so_5/many_timers/main.cpp * \example so_5/mchain_empty_notificator/main.cpp * \example so_5/mchain_empty_notificator_2/main.cpp * \example so_5/mchain_fibonacci/main.cpp * \example so_5/mchain_handler_formats/main.cpp * \example so_5/mchain_multi_consumers/main.cpp * \example so_5/mchain_select/main.cpp * \example so_5/modify_resend_as_immutable/main.cpp * \example so_5/mutable_msg_agents/main.cpp * \example so_5/named_dispatchers_layer/main.cpp * \example so_5/news_board/main.cpp * \example so_5/nohandler_msg_tracing/main.cpp * \example so_5/parent_coop/main.cpp * \example so_5/ping_pong/main.cpp * \example so_5/ping_pong_minimal/main.cpp * \example so_5/ping_pong_with_owner/main.cpp * \example so_5/prio_work_stealing/main.cpp * \example so_5/producer_consumer_mchain/main.cpp * \example so_5/queue_size_stats/main.cpp * \example so_5/redirect_and_transform/main.cpp * \example so_5/selective_msg_tracing/main.cpp * \example so_5/simple_message_deadline/main.cpp * \example so_5/single_sink_binding/main.cpp * \example so_5/state_deep_history/main.cpp * \example so_5/stop_guard/main.cpp * \example so_5/subscriptions/main.cpp * \example so_5/two_handlers/main.cpp * \example so_5/unique_subscribers_mbox/main.cpp * \example so_5/wrapped_env_demo_2/main.cpp * \example so_5/wrapped_env_demo_3/main.cpp */ // vim:ft=cpp.doxygen sobjectizer-5.8.6/doxygen/dox/so_5/so_5_2_3__cooperation_controlled_resources.dox000066400000000000000000000105401523065210100302700ustar00rootroot00000000000000/*! \page so_5_2_3__cooperation_controlled_resources so-5.2.3: Cooperation controlled resources \section so_5_2_3__cooperation_controlled_resources__the_problem The problem Sometimes it is necessary to provide some resource to all cooperation's agents. For example: DB connection object or logger object. And that resource must live longer than the lifetime of agents of cooperation. One obvious solution is to use std::shared_ptr or another kind of smart pointer: \code so_5::rt::so_environment_t & env = ...; auto coop = env.create_coop( "sample" ); std::shared_ptr< logger_t > logger( new some_task_specific_logger_t() ); coop->add_agent( new first_agent_t( env, logger, ... ) ); coop->add_agent( new second_agent_t( env, logger, ... ) ); \endcode It guarantees that logger will be destroyed only after destroying all cooperation agents. But what if you should use agent which requires reference to logger, not smart pointer? How to control lifetime of that object? Version 5.2.3 provides two answers for this question. \section so_5_2_3__cooperation_controlled_resources__the_solutions The solutions \subsection so_5_2_3__cooperation_controlled_resources__the_solutions__user_coop A descendant from agent_coop_t class Since v.5.2.3 it is possible to make a derived class from so_5::rt::agent_coop_t. The only condition for derived classes: its object should be dynamically allocated. It is necessary because SObjectizer Environment takes those objects under control and destroys them by calling \c delete. Under this approach cooperation could control object lifetime this way: \code class coop_with_logger_t : public so_5::rt::agent_coop_t { typedef so_5::rt::agent_coop_t base_type_t; public : coop_with_logger_t( // Cooperation name. const so_5::rt::nonempty_name_t & name, // Default dispatcher binding. so_5::rt::disp_binder_unique_ptr_t coop_disp_binder, // SObjectizer Environment. so_environment_t & env ) : base_type_t( name, coop_disp_binder, env ) {} ... logger_t & logger() { return m_logger; } private : // Logger for cooperation. some_task_specific_logger_t m_logger; }; ... so_5::rt::so_environment_t & env = ...; so_5::rt::agent_coop_unique_ptr_t coop( new coop_with_logger_t( "sample", so_5::rt::create_default_disp_binder(), env ) ); coop->add_agent( new first_agent_t( env, coop->logger(), ... ) ); coop->add_agent( new second_agent_t( env, coop->logger(), ... ) ); \endcode And because cooperation lifetime is longer than agents lifetime it is safe to pass reference to logger to agents. \subsection so_5_2_3__cooperation_controlled_resources__the_solutions__take_under_control Usage of take_under_control method A new method for so_5::rt::agent_coop_t class is introduced in v.5.2.3: take_under_control(). It accepts raw or unique_ptr pointer to dynamically allocated objects and takes this object under control of cooperation. Non limited count of objects could be taken under control of cooperation. They will be destroyed only after destroying agents of cooperation. With this approach the sample with logger object could be rewritten in the following way: \code so_5::rt::so_environment_t & env = ...; so_5::rt::agent_coop_unique_ptr_t coop = env.create_coop( "sample" ); // take_under_control returns raw pointer to object. // So its could be stored for further use. logger_t & logger = *(coop->take_under_control( new some_task_specific_logger_t() )); coop->add_agent( new first_agent_t( env, logger, ... ) ); coop->add_agent( new second_agent_t( env, logger, ... ) ); \endcode \section so_5_2_3__cooperation_controlled_resources__technical_notes Some technical notes Strictly speaking the destruction of agents in cooperation destructor depends on agents reference counters. Cooperation holds smart references to its agents. Cooperation decrements reference counters inside smart references in the cooperation’s destructor. In normal situation those counter receives zero value and this leads to agents destruction. But there is a possibility that user receive and store smart reference to an agent somewhere else. In that situation agent will not be destroyed with its cooperation. If the agent will access some external resource via reference/pointer (like logger from example above) anything might happen. But this situation is not under control of SObjectizer. And user should provide appropriate resource lifetime policy in such cases. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_2_3__cooperation_notificators.dox000066400000000000000000000153571523065210100265500ustar00rootroot00000000000000/*! \page so_5_2_3__cooperation_notificators so-5.2.3: Cooperation notifications Version 5.2.3 introduces some features for informing agents about cooperation related events. \section so_5_2_3__cooperation_notificators__dereg_reason Cooperation deregistration reason Very important change in v.5.2.3 is the introduction of so_5::rt::coop_dereg_reason_t structure and so_5::rt::dereg_reason namespace. The so_5::rt::coop_dereg_reason_t describes the reason of cooperation deregistration. For example: normal deregistration due to application logic (value so_5::rt::dereg_reason::normal) or deregistration as a reaction to unhandled exception (value so_5::rt::dereg_reason::unhandled_exception) or because the parent cooperation is being deregistered (value so_5::rt::dereg_reason::parent_deregistration). At the moment coop_dereg_reason_t contains only one value -- integer deregistration reason code which is accessible via so_5::rt::coop_dereg_reason_t::reason() method. But in the future SObjectizer versions the content of coop_dereg_reason_t could be expanded. The format of so_5::rt::so_environment_t::deregister_coop() is changed. Now this method requires not only the cooperation name but also an integer deregistration reason code. So the code which uses deregister_coop() should be adapted when switching to SObjectizer v.5.2.3. The deregistration reason codes are divided into two parts: values below so_5::rt::dereg_reason::user_defined_reason is reserved for SObjectizer. The user could use these values (like mentioned above normal, unhandled_exception and parent_deregistration), but must not change its meaning. Values from so_5::rt::dereg_reason::user_defined_reason and above could be used for user purposes. So it is possible to define and use domain specific reason codes, like: \code const int timeout = so_5::rt::dereg_reason::user_defined_reason + 1; const int task_completed = so_5::rt::dereg_reason::user_defined_reason + 2; class domain_specific_task_t : public so_5::rt::agent_t { ... void evt_request_processed( const so_5::rt::event_data_t< msg_result > & evt ) { handle_result(*evt); so_environment().deregister_coop(so_coop_name(), task_completed); } void evt_processing_timedout( const so_5::rt::event_data_t< msg_timeout > & ) { so_environment().deregister_coop(so_coop_name(), timeout); } }; \endcode \section so_5_2_3__cooperation_notificators__reason_to_listeners Deregistration reason passed to coop_listener The format of so_5::rt::coop_listener_t::on_deregistered() is changed: now it receives not only name of deregistered cooperation but also the cooperation deregistration reason: \code virtual void on_deregistered( so_environment_t & so_env, const std::string & coop_name, const coop_dereg_reason_t & reason); \endcode It is incompatible with previous version and appropriate changes must be applied to cooperation listeners. \section so_5_2_3__cooperation_notificators__reg_notificators Cooperation registration notificators A new thing is introduced in v.5.2.3: this is cooperation registration notificators. Notificator is a functional object with prototype: \code void reg_notificator( so_5::rt::so_environment_t & env, const std::string & coop_name ); \endcode The precise type of registration notificator is defined in so_5::rt::coop_reg_notificator_t typedef. Notificators should be bound to cooperation object before cooperation registration: \code so_5::rt::agent_coop_unique_ptr_t coop = ...; coop->add_reg_notificator( []( so_5::rt::so_environment_t &, const std::string & name ) { std::cout << "cooperation registered: " << name << std::endl; } ); \endcode There is no limit for cooperation reg_notificator count. All of them will be called by SObjectizer Environment inside so_5::rt::so_environment_t::register_coop() method right after all registration specific actions will be complete. \section so_5_2_3__cooperation_notificators__dereg_notificators Cooperation deregistration notificators Cooperation deregistration noitificators are introduced together with registration notificators. They are also functional objects but with prototype: \code void dereg_notificator( so_5::rt::so_environment_t & env, const std::string & coop_name, const so_5::rt::coop_dereg_reason_t & reason ); \endcode The precise type of registration notificator is defined in so_5::rt::coop_reg_notificator_t typedef. Notificators should be bound to cooperation object before cooperation registration: \code so_5::rt::agent_coop_unique_ptr_t coop = ...; coop->add_dereg_notificator( []( so_5::rt::so_environment_t &, const std::string & name, const so_5::rt::coop_dereg_reason_t & reason ) { std::cout << "cooperation deregistered(" << reason.reason() << "): " << name << std::endl; } ); \endcode There is no limit for cooperation dereg_notificator count. All of them will be called by SObjectizer Environment method right after final cooperation deregistration actions. Cooperation object will be destroyed before invoking deregistration notificators. \section so_5_2_3__cooperation_notificators__std_notificators Standard registration and deregistration notificators implementation SObjectizer provides standard implementation for \c reg_ and \c dereg_notificators. They just send so_5::rt::msg_coop_registered and so_5::rt::msg_coop_deregistered to the mbox specified. Standard \c reg_notificator is created by so_5::rt::make_coop_reg_notificator() function. And standard dereg_notificator is created by so_5::rt::make_coop_dereg_notificator() function. They could be used for controlling lifetime of child cooperation, for example: \code class a_parent_t : public so_5::rt::agent_t { public : void so_define_agent() { so_subscribe( m_mbox ).event( &a_parent_t::evt_coop_registered ); so_subscribe( m_mbox ).event( &a_parent_t::evt_coop_deregistered ); } void so_evt_start() { create_child_cooperation(); } void evt_coop_registered( const so_5::rt::event_data_t< so_5::rt::msg_coop_registered > & evt ) { // Child cooperation started. Some amount of work should be // scheduled to it. ... // Some actions. } void evt_coop_deregistered( const so_5::rt::event_data_t< so_5::rt::msg_coop_deregistered > & evt ) { // If child cooperation destroyed because of exception it // should be registered again. if( so_5::rt::dereg_reason::unhandled_exception == evt->m_reason.reason() ) create_child_cooperation(); } private : void create_child_cooperation() { auto coop = so_environment().create_coop( "child" ); coop->set_parent_coop_name( so_coop_name() ); coop->add_reg_notificator( so_5::rt::make_coop_reg_notificator( m_mbox ) ); coop->add_dereg_notificator( so_5::rt::make_coop_dereg_notificator( m_mbox ) ); so_environment().register_coop( std::move(coop) ); } const so_5::rt::mbox_ref_t m_mbox; }; \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_2_3__exception_reaction.dox000066400000000000000000000070011523065210100253070ustar00rootroot00000000000000/*! \page so_5_2_3__exception_reaction so-5.2.3: Reaction to unhandled exceptions \note There is an important change to that mechanism in v.5.3.0, see \ref so_5_3_0__exception_reaction. SObjectizer agents should provide no-throw guarantee: e.g. all exception should be caught and handled inside agent's event handlers. But if some exception is went out from event handler by some reason (by mistake for example) then SObjectizer should known what to do in such situation. In version 5.2.3 a new approach of handling exceptions is implemented. The enumeration so_5::rt::exception_reaction_t is introduced. And new virtual method so_exception_reaction() is added to so_5::rt::agent_t class. When SObjectizer caught an exception from agent's event handler it calls so_exception_reaction() method on problematic agent. Then SObjectizer analyzes return value and does appropriate actions: - so_5::rt::abort_on_exception -- terminates application immediately by calling abort() function. That return code means that agent does not provide any exception safety guarantee. Agent or some part of application is in incorrect state and application execution cannot be continued. - so_5::rt::shutdown_sobjectizer_on_exception -- switches agent to the special state and initiates shutdown of SObjectizer Environment. In that state agent cannot receive messages. That return code means that agent provides basic exception safety guarantee (no leak or damage of any resources), but application's state is not logically correct and it is not desirable to continue application execution. - so_5::rt::deregister_coop_on_exception -- switches agent to the special state and initiates cooperation deregistration. Only cooperation to which agent belongs to will be deregistered (if there are some children cooperation they will be deregistered too). In that special state agent cannot receive messages. That return code means that agent provides basic exception safety guarantee (no leak or damage of any resources), but agent's state is not logically correct. Agent's inconsistency has no influence on whole application and application work could be continued without that agent (and its cooperation). - so_5::rt::ignore_exception -- just ignores exceptions and continues application execution. That return code means that agent provides strong exception safety guarantee. Default implementation of agent_t::so_exception_reaction() returns so_5::rt::deregister_coop_on_exception. It means that SObjectizer expects that agents provide basic exception guarantee even in case of unhandled exceptions. If agent wants a different reaction from SObjectizer it should override so_exception_reaction() method. For example, a stateless agent which only do some transformation of message could return so_5::rt::ignore_exception: \code class a_currency_changer_t : public so_5::rt::agent_t { ... virtual so_5::rt::exception_reaction_t so_exception_reaction() const { return so_5::rt::ignore_exception; } ... // Exceptions here do not damage the agent. void evt_transform_message( const so_5::rt::event_data_t< payment_request_t > evt ) { if( "RUR" == evt->currency_code() ) { // Currency code should be changed to RUB. std::unique_ptr< paymet_request_t > new_request( new payment_request( *evt ) ); new_request->set_currency_code( "RUB" ); m_target_mbox->deliver_message( std::move( new_request ) ); } else // Resend unmodified message. m_target_mbox->deliver_message( evt.make_reference() ); } }; \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_2_3__parent_child_coops.dox000066400000000000000000000063271523065210100252760ustar00rootroot00000000000000/*! \page so_5_2_3__parent_child_coops so-5.2.3: Parent-child cooperation relationship \section so_5_2_3__parent_child_coops__general General idea Parent-child cooperation relationship from SObjectizer-4 is implemented in version 5.2.3. It allows to specify the parent cooperation for new cooperation before new cooperation registration. Then if the parent cooperation deregistered all its children cooperations will be deregistered automatically. If some child cooperation has its own children cooperations they will be deregistered as well. The parent-child relation serves for two main purposes: 1. Simplification for management of child agent which perform some part of parent cooperation tasks. For example parent cooperation could receipt user requests and create child cooperations for serving those requests. When parent cooperation is being deregistered all its children cooperations should be deregistered too. And since v.5.2.3 it is done automatically. 2. Simplification for resource management. For example an agent from parent cooperation could create some resource like DB connection object. And then pass reference to that object to agents from child cooperation. It is safe to use such reference because SObjectizer guarantees that children cooperations will be destroyed before its parent cooperation. So it is necessary to do resource control only in parent cooperation. To specify parent cooperation new method so_5::rt::agent_coop_t::set_parent_coop_name() was introduced. It must be applied with the name of already registered cooperation in order to set parent-child relationship. The usage of that method looks like: \code so_5::rt::so_environment_t & env = ...; auto coop = env.create_coop( "child_coop" ); coop->set_parent_coop_name( "parent_coop" ); ... // Add agents to cooperation. env.register_coop( std::move(coop) ); \endcode \section so_5_2_3__parent_child_coops__details Some technical details Name of parent cooperation is checked inside the so_5::rt::so_environment_t::register_coop() method. The cooperation with specified name must be registered at that moment and must not be under deregistration process. If cooperation name is unknown or parent cooperation is being deregistered then an error is raised. When name of a parent cooperation is passed to so_5::rt::so_environment_t::deregister_coop() then SObjectizer makes a full list of all children cooperations (e.g. direct children cooperations, children of children and so on). And then initiates deregistration for all of them at once. It means that all cooperations will receive deregistration signal at the same time. But because cooperation deregistration could take some time some of cooperations could finish their deregistration process earlier and some later. Because of that agents of the parent cooperation could finish their work before agents from children cooperations. But the destruction of cooperation will be done in particular order. Cooperations without any children will be deregistered at first turn. Then the parents of already deregistered cooperations will be deregistered. Then parents of those parents and so on. The root cooperation which name was specified in so_5::rt::so_environment_t::deregister_coop() will be destroyed the last. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_3_0__exception_reaction_inheritance.dox000066400000000000000000000031051523065210100276570ustar00rootroot00000000000000/*! \page so_5_3_0__exception_reaction so-5.3.0: Exception reaction inheritance A mechanism for reacting to non-handled exception was introduced in v.5.2.3, pleas see \ref so_5_2_3__exception_reaction. It has been improved in v.5.3.0. A new value has been introduced into so_5::rt::exception_reaction_t enumeration: so_5::rt::inherit_exception_reaction. It means that reaction to non-handled exception must be derived from agents cooperation. An so_5::rt::agent_coop_t now has methods so_5::rt::agent_coop_t::set_exception_reaction() and so_5::rt::agent_coop_t::exception_reaction(). The first one allows to set an exception reaction flag for whole cooperation. The second one returns the exception reaction flag value. But there is a trick: if exception reaction flag for cooperation is inherit_exception_reaction (it is default value) then exception_reaction() for parent cooperation is called. If there is no parent cooperation then so_5::rt::so_environment::exception_reaction() is called. An exception reaction flag for the whole SO Environment could be set by so_5::rt::so_environment_params_t::exception_reaction() method on environment creation. All of those mean that now exception reaction could be set on different levels: - at the most top level for SO Environment; - at the level of a cooperation; - at the level of an agent. By default exception reaction for SO Environment is abort_on_exception, e.g. application will be aborted if any non-handled exception occurs. For agent and agent cooperation the default exception reaction is inherit_exception_reaction. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_3_0__new_event_handlers_formats.dox000066400000000000000000000046411523065210100270370ustar00rootroot00000000000000/*! \page so_5_3_0__new_event_handlers_formats so-5.3.0: New event handlers formats New format for event- and signal handlers are supported since v.5.3.0. The first major change: handlers could return values now. So it is possible to write a handler like this: \code std::string evt_get_status( const so_5::rt::event_data_t< msg_get_status > & ) { return "Ready"; } int evt_convert( const so_5::rt::event_data_t< msg_convert > & evt ) { return std::atoi( evt->m_value ); } \endcode It allows to use handler as traditional event-handler and as service request handler. The second major change: handlers could receive arguments without so_5::rt::event_data_t wrapper. It especially useful for event handlers: \code int evt_convert( const msg_convert & msg ) { return std::atoi( msg.m_value ); } \endcode Such event handler is subscribed as usual: \code virtual void my_agent_t::so_define_agent() { so_subscribe(mbox).event( &my_agent_t::evt_convert ); } \endcode Signal handler also could has a new form: \code std::string evt_get_status() { return "Ready"; } \endcode But is must be subscribed by new version of so_subscribe().event() chain: \code virtual void my_agent_t::so_define_agent() { so_subscribe(mbox).event( so_5::signal< msg_get_status >, &my_agent_t::evt_get_status ); } \endcode The main difference between old and new form is impossibility to redirect the same message to another mbox. Old form allows this by make_reference method of event_data_t: \code void evt_some_event( const so_5::rt::event_data_t< msg_with_very_big_data_inside > & evt ) { // The same message must be sent to another agent. some_another_mbox->deliver_message( evt.make_reference() ); } \endcode New form handlers cannot do that. So if it is necessary to redirects the same message to another mbox than old form event handler must be used. The third major change: lambda-functions could be used as event- and signal-handlers: \code virtual void my_agent_t::so_define_agent() { so_subscribe(mbox).event( [](const msg_convert & m) { return std::atoi(m.m_value); } ); so_subscribe(mbox).event( so_5::signal< msg_get_status >, []() -> std::string { return "Ready"; } ); } \endcode Note. The lambda-functions with event_data_t-wrapper as argument are not supported now. Old-format event handlers must be used instead. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_3_0__new_run_so_environment.dox000066400000000000000000000037721523065210100262400ustar00rootroot00000000000000/*! \page so_5_3_0__new_run_so_environment so-5.3.0: New run_so_environment() variants The two new form of so_5::api::run_so_environment() routines introduced. One of them receives lambda or std::function object as initialization function. It allows to write like this: \code int main( int argc, char ** argv ) { try { so_5::api::run_so_environment( [argc, argv]( so_5::rt::so_environment_t & env ) { const int meetings = 2 == argc ? std::atoi( argv[1] ) : 10; init( env, meetings ); }, std::move( so_5::rt::so_environment_params_t() .add_named_dispatcher( "active_obj", so_5::disp::active_obj::create_disp() ) ) ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } \endcode The another allows to edit semiprepared so_5::rt::so_environment_params_t object by function (or lambda, or std::function object): \code int main( int argc, char ** argv ) { try { so_5::api::run_so_environment( // // This is a lambda for starting actions. // [argc, argv]( so_5::rt::so_environment_t & env ) { const std::size_t ITERATIONS = 2 == argc ? static_cast< std::size_t >(std::atoi( argv[1] )) : 10u; auto coop = env.create_coop( so_5::rt::nonempty_name_t( "test_coop" ), so_5::disp::active_obj::create_disp_binder( "active_obj" ) ); coop->add_agent( new a_runner_t( env, ITERATIONS ) ); env.register_coop( std::move( coop ) ); }, // // This is a lambda for tuning SO Environment parameters. // []( so_5::rt::so_environment_params_t & p ) { p.add_named_dispatcher( "active_obj", so_5::disp::active_obj::create_disp() ); p.exception_reaction( so_5::rt::shutdown_sobjectizer_on_exception ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_3_0__synchronous_interaction.dox000066400000000000000000000234551523065210100264270ustar00rootroot00000000000000/*! \page so_5_3_0__synchronous_interaction so-5.3.0: Synchronous interaction with agents \section so_5_3_0__synchronous_interaction__intro Introduction There is a new major change introduced in v.5.3.0 -- a possibility to synchronous wait for result of message processing. An agent, called service client, could ask another agent (called service_handler) to do something by asynchronously sending message to service_handler and then synchronously waiting for result of that request. All parameters for service request must be defined as ordinary message or signal type: \code // Message for service request with parameters. struct msg_convert : public so_5::rt::message_t { // String to conversion to int. std::string m_value; msg_convert( const std::string & v ) : m_value( v ) {} }; // Signal for service request without parameters. struct msg_get_status : public so_5::rt::signal_t {} \endcode Service_handler is an ordinary agent which defines its service_request handling methods by traditional way. The only change with previous version is that event handlers could return values since v.5.3.0: \code // service_handler event for msg_convert request. int a_my_service_t::evt_convert( const msg_convert & msg ) { std::istringstream s( msg.m_value ); int result; s >> result; if( s.fail() ) throw std::invalid_argument( "unable to convert to int: '" + msg.m_value + "'" ); return result; } // service_handler event for msg_get_status request. std::string a_my_service_t::evt_get_status() { return "Ready"; } \endcode Event handler methods are subscribed as usual: \code class a_my_service_t : public so_5::rt::agent_t { public : virtual void so_define_agent() { so_subscribe( mbox ).event( &a_my_service_t::evt_convert ); so_subscribe( mbox ).event( so_5::signal< msg_get_status >, &a_my_service_t::evt_get_status ); } ... }; \endcode All those mean that there is almost no differences for service_handler: it always works as ordinary agent. And all event handlers for that agent are invoked on agents working thread. All requests are delivered to service_handlers agent as ordinary messages via agents event queue. The mechanism for event handler invocation for service requests is the same as for traditional asynchronous messages. To send service request it is necessary to invoke mboxs get_one template method. It means that exactly one request of the specified type will be requested: \code const so_5::rt::mbox_ref_t & mbox = ...; // Mbox representing service. // The start part of service request initiation: mbox->get_one< std::string >()... // other stuff will go here \endcode Then it is necessary to declare how the waiting of service request will be performed. There are the three variants. The first one, the most simple and the most dangerous, is wait service request result infinitely long: \code // Initiate the service request and wait for result infinitely. int v = mbox->get_one< int >().wait_forever().sync_get( new msg_convert( "42" ) ); // Invocation of service request by a signal. std::string s = mbox->get_one< std::string >().wait_forever().sync_get< msg_get_status >(); \endcode The second one, simple and one of the most safe, is wait service request result for the specified amount of time: \code // Initiate the service request and wait for 300ms. int v = mbox->get_one< int >() .wait_for( std::chrono::milliseconds(300) ) .sync_get( new msg_convert( "42" ) ); // Invocation of service request by a signal and wait for 5s. std::string s = mbox->get_one< std::string >() .wait_for( std::chrono::seconds(5) ) .sync_get< msg_get_status >(); \endcode The third one, the most complex and the most flexible, is receive std::future object associated with service request and then perform any operation user wish: \code std::future< int > v = mbox->get_one< int >.async( new msg_convert( "42" ) ); std::future< std::string > s = mbox->get_one< std::string >.async< msg_get_status >(); // Do some useful stuff until conversion result get ready. while( true ) { ... // Do something here. // Check the future status. if( std::future_status::ready == v.wait_for( std::chrono::seconds(0) ) ) break; } // Conversion result is ready and could be obtained without waiting. std::cout << "Conversion result: " << v.get() << std::endl; // Wait for get_status request infinitely. std::cout << "Service status: " << s.get() << std::endl; \endcode \section so_5_3_0__synchronous_interaction__deadlocks Deadlocks There is no any help to user from SObjectizer to avoid or to diagnose deadlocks when synchronous interactions are used. It is the user task to guarantee that service_handler and service client are working on different threads and there is no any complex internal interactions like: agent A1 calls A2 which calls A3 which calls A1. It is the reason why the wait_forever() in a dangerous form. If there is a deadlock the application will hang forever. The safest way -- is to use wait_for() with the reasonable timeout. Even if a deadlock is here it will be automatically destroyed after timeout expiration. \section so_5_3_0__synchronous_interaction__exceptions Exceptions There is a strong possibility of exceptions appearance during synchronous interactions. Some exceptions could be raised by SObjectizer. Those are: - exceptions related to wrong number of actual service_handlers. When service request is initiated by get_one method the one and only one service_handler must exists (e.g. one and only one agent must be subscribed to the specified message type from the specified mbox). If there are more than one agent-subscriber or if there isnt any of them then an exception will be raised; - exceptions related to ignorance of service request. For example if service_handler receives service request but not handled it because event handler is disabled in the current agent state then an exception will be raised; - exceptions related to timeouts during wait for result. Those exceptions are raised when wait_for() method is used and timeout elapsed without receiving result of service request. In such cases SObjectizer raises object of so_5::exception_t class with the appropriate error_code inside. The exceptions could be raised by event handlers itself. As in the sample of evt_convert event above. Those exceptions are intercepted by SObjectizer and then transferred to the std::future connected with the service request. It means that those exceptions will be reraised during std::future::get() invocation (as in the case of direct call by used and in the case of calling get() inside of wait_forever() and wait_for()). So the application exception which are went out from event handler will be propagated to service client. The dialing with non-handled application exception is the main difference between ordinary event-handling and service_request-handling. When agent lets the exception go out during ordinary event handler invocation then SObjectizer calls agents so_exception_reaction() method and does appropriate actions. But when agent lets the exception go out during service_request handling then the exception is propagated to service client and so_exception_reaction() is not called. \section so_5_3_0__synchronous_interaction__usage_simplification Usage simplifications \subsection so_5_3_0__synchronous_interaction__usage_simplification__variadic_templates Use of variadic templates If the compiler supports variadic templates then make_async and make_sync_get methods could be used instead of async and sync_get. It allows to write: \code mbox.get_one< int >().make_async< msg_convert >( "42" ); \endcode instead of \code mbox.get_one< int >().wait_forever().make_sync_get< msg_convert >( "42" ); \endcode \subsection so_5_3_0__synchronous_interaction__usage_simplification__run_one run_one as simplier form of get_one There could be cases where service_handler returns void. For example: \code // A signal to flushing memory buffers to the disk. struct msg_flush : public so_5::rt::signal_t {}; class a_membuffer_manager_t : public so_5::rt::agent_t { public : ... virtual void so_define_agent() { so_subscribe( mbox ).event( so_5::signal< msg_flush >, &a_membuffer_manager_t::evt_flush ); ... } ... void evt_flush() { // Do buffer content flushing... } }; \endcode That service could be called by ordinary get_one method chain: \code // Returns when flushing is done. mbox.get_one< void >().wait_forever().sync_get< msg_flush >(); \endcode But instead of get_one it is possible to use run_one method that is a synonym for get_one: \code // Returns when flushing is done. mbox.run_one().wait_forever().sync_get< msg_flush >(); \endcode Or: \code auto f = mbox.run_one().async< msg_flush >(); ... f.get(); // Returns when flushing is done. \endcode \subsection so_5_3_0__synchronous_interaction__usage_simplification__use_auto Use auto for temporary proxy objects Method chains get_one(), get_one().wait_forever() and get_one().wait_for() produces temporary proxy objects which could be captured as local variable by auto keyword. It would lead to shorter form of service calling: \code auto svc = mbox.get_one< int >().wait_for( std::chrono::seconds(5) ); auto v1 = svc.make_sync_request< msg_convert >( "42" ); auto v2 = svc.make_sync_request< msg_convert >( "43" ); ... \endcode */ // vim:ft=cpp:ts=2:sts=2:expandtab:sw=2 sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__add_dispatcher_if_not_exists.dox000066400000000000000000000024331523065210100273230ustar00rootroot00000000000000/*! \page so_5_4_0__add_dispatcher_if_not_exists so-5.4.0: A possibility to add a dispatcher after start of SObjectizer Environment Since v.5.4.0 it is possible to add a new dispatcher to SObjectizer Environment after start of the Environment. New method so_5::rt::so_environment_t::add_dispatcher_if_not_exists() added for this purpose: \code void my_agent::evt_long_request( const request & evt ) { // New child cooperation is necessary for handling the request. // An instance of dispatcher is required for the cooperation. // Create a dispatcher if it not exists. so_environment().add_dispatcher_if_not_exists( "long_request_handler_disp", []() { return so_5::disp::active_group::create_disp(); } ); // Agents of child cooparation will be bound to that dispatcher. auto coop = so_environment().create_coop( "long_request_handler", so_5::disp::active_group::create_disp_binder( "long_request_handler_disp", "child_coop" ) ); coop->set_parent_coop_name( so_coop_name() ); ... } \endcode \note There is a possibility to add new dispatcher at run-time but there is no possibility to remove it. This feature can be added in future versions of SObjectizer if there will be a necessity for it. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__adv_thread_pool_dispatcher.dox000066400000000000000000000067771523065210100270070ustar00rootroot00000000000000/*! \page so_5_4_0__adv_thread_pool_dispatcher so-5.4.0: New adv_thread_pool dispatcher \section so_5_4_0__adv_thread_pool_dispatcher__intro Intro Another new dispatcher in v.5.4.0 is adv_thread_pool dispatcher. It is a more advanced and complex variant of thread_pool dispatcher (see \ref so_5_4_0__thread_pool_dispatcher for the details). There are two major differences between adv_thread_pool and thread_pool dispatcher. 1) adv_thread_pool dispatcher analyzes thread_safety flag for event handler and can start several thread_safe handlers for a single agent in parallel on different threads. Wherein the dispatcher guarantees that a not_thread_safe event handler cant be started in parallel with any other event handler for the same agent. 2) adv_thread_pool has no parameter max_demands_at_once for handling messages from agents queue and uses a different scheme. A working threads gets reference to an agents queue from the dispatcher thread and looks at the top message in the queue. Then working thread checks the possibility of handling of that message. Handling is possible if: * there is no any working event handler for the agents or * there are working thread_safe event handlers and this message will be handled by thread_safe handler. If handling is possible message will be extracted from agents queue and processed. If not, the message will stay at the top of agents queue. Its processing will be initiated later when all currently working event handlers finish their work. In the case when most of agents event handlers are not_thread_safe adv_thread_pool dispatcher will work not as efficient as thread_pool dispatcher. It means that adv_thread_pool dispatcher is appropriate for the situations when there are few huge agents with very heavy, but stateless event handlers. \section so_5_4_0__adv_thread_pool_dispatcher__synopsis Synopsis To create adv_thread_pool dispatcher it is necessary to call so_5::disp::adv_thread_pool::create_disp() function. It receives one argument -- count of working thread. All those threads will be created during start of dispatcher. \code #include ... so_5::api::run_so_environment( []( so_5::rt::so_environment_t & env ) { ... }, []( so_5::rt::so_environment_params_t & params ) { params.add_named_dispatcher( "my_adv_thread_pool", so_5::disp::adv_thread_pool::create_disp( 16 ) ); } ); \endcode If create_disp() called without argument the dispatcher will try to detect count of thread by calling std::thread::hardware_concurrency() method. To create dispatcher binder for thread_pool dispatcher it is necessary to use one of so_5::disp::adv_thread_pool::create_disp_binder() functions: \code so_5::rt::so_environment_t & env = ...; auto coop = env.create_coop( "my_coop", // Use binder with default params: cooperation FIFO. so_5::disp::adv_thread_pool::create_disp_binder( "my_adv_thread_pool" ) ); // Use this binder parameters for several cooperations. using namespace so_5::disp::adv_thread_pool; params_t binding_params; binding_params.fifo( fifo_t::individual ); auto child1 = env.create_coop( "my_coop_child_1", create_disp_binder( "my_adv_thread_pool", params ) ); ... auto child2 = env.create_coop( "my_coop_child_2", create_disp_binder( "my_adv_thread_pool", params ) ); // Configure binding parameters during create_disp_binder() call: auto child3 = env.create_coop( "my_coop_child_3", create_disp_binder( "my_adv_thread_pool", []( params_t & p ) { p.fifo( fifo_t::cooperation ); } ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__autoshutdown_mode.dox000066400000000000000000000016001523065210100251730ustar00rootroot00000000000000/*! \page so_5_4_0__autoshutdown_mode so-5.4.0: Autoshutdown mode for SObjectizer Environment An autoshutdown mode is introduced in SObjectizer v.5.4.0. This mode is on by default. In this mode SObjectizer Environment automatically shuts down its work after deregistering the last working cooperation. This simplifies implementation of small programs with few cooperations inside (like small command-line utilities or tests): every cooperation deregisters iteself after finishing its work and SObjectizer is finished automatically. If autoshutdown mode is not appropriate for the application it can be disabled by so_5::rt::so_environment_params_t::disable_autoshutdown() method: \code so_5::api::run_so_environment( []( so_5::rt::so_environment_t & env ) { ... }, []( so_5::rt::so_environment_params_t & params ) { params.disable_autoshutdown(); } ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__coop_dereg_helper_methods.dox000066400000000000000000000022531523065210100266200ustar00rootroot00000000000000/*! \page so_5_4_0__coop_dereg_helper_methods so-5.4.0: Helper methods for cooperation deregistration There are two new methods in agent_t class: so_deregister_agent_coop() and so_deregister_agent_normally(). They allow to write more compact and readable code. Just for a comparison: _Before v.5.4.0_ \code void my_agent::evt_operation_fail() { // Deregistering cooperation due to inability to complete operation. so_environment().deregister_coop( so_coop_name(), so_5::rt::dereg_reason::user_defined_reason + 100 ); } void my_agent::evt_operation_success() { // Deregistering cooperation because operation successfully completed. so_environment().deregister_coop( so_coop_name(), so_5::rt::dereg_reason::normal ); } \endcode _Since v.5.4.0_ \code void my_agent::evt_operation_fail() { // Deregistering cooperation due to inability to complete operation. so_deregister_agent_coop( so_5::rt::dereg_reason::user_defined_reason + 100 ); } void my_agent::evt_operation_success() { // Deregistering cooperation because operation successfully completed. so_deregister_agent_coop_normally(); } \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__event_method_chaining.dox000066400000000000000000000025501523065210100257510ustar00rootroot00000000000000/*! \page so_5_4_0__event_method_chaining so-5.4.0: so_subscribe().event() methods chaining Calls to event() methods during event subscription can be chained now: \code class my_agent : public so_5::rt::agent_t { public : void so_define_agent() override { so_subscribe( so_direct_mbox() ).in( st_disconnected ) .event( so_5::signal< msg_reconnect >, &my_agent::evt_reconnect ) .event( &my_agent::evt_send_when_disconnected ) .event( so_5::signal< msg_get_status >, []() -> std::string { return "disconnected"; } ); ... } ... }; \endcode \note In SObjectizer v.5.4.0 call to event() create subscription only for the states, specified by previous calls to in() in the same chain. For example in the chain so_subscribe(mbox).in(s1).in(s2).event(e1).in(s3).event(e2) event e1 will be subscribed for states s1 and s2, but not for state s3. Event e2 will be subscribed for the all states: s1, s2, s3. But the chain like so_subscribe(mbox).event(e3).in(s4) has no sence: event e3 will be subscribed only for the default state and the call to in(s4) will be completely ignored. There is no guarantee that this approach will be saved for the future SObjectizer's version. So it is recommended not to mix calls in() and event() in subscription chain -- all calls to in() must precede to calls to event(). */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__method_so_make_state.dox000066400000000000000000000010571523065210100256070ustar00rootroot00000000000000/*! \page so_5_4_0__method_so_make_state so-5.4.0: New helper method so_make_state of agent_t class New helper method so_5::rt::agent_t::so_make_state() has been added to so_5::rt::agent_t class. It allows to initialize state_t object immediately after declaration: \code class my_agent_t : public so_5::rt::agent_t { private : // Anonymous state. const so_5::rt::state_t st_first = so_make_state(); // Named state. const so_5::rt::state_t st_second = so_make_state( "second" ); ... }; \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__new_format_of_add_agent.dox000066400000000000000000000025021523065210100262400ustar00rootroot00000000000000/*! \page so_5_4_0__new_format_of_add_agent so-5.4.0: New format of add_agent method of agent_coop_t class Method so_5::rt::agent_coop_t::add_agent() now returns pointer to the agent that has been added to cooperation. That pointer remains valid until cooperation object is live. This method format simplifies access to agent's direct mbox (see \ref so_5_4_0__new_mpsc_mbox for the details about direct mboxes). The following example shows exchange of references to direct mboxes in ping-pong sample: \code class pinger_t : public so_5::rt::agent_t { public : ... void set_ponger_mbox( const so_5::rt::mbox_ref_t & mbox ) { m_ponger_mbox = mbox; } ... private : so_5::rt::mbox_ref_t m_ponger_mbox; }; class ponger_t : public so_5::rt::agent_t { public : ... void set_pinger_mbox( const so_5::rt::mbox_ref_t & mbox ) { m_pinger_mbox = mbox; } ... private : so_5::rt::mbox_ref_t m_pinger_mbox; }; ... so_5::rt::so_environment_t & env = ...; auto coop = env.create_coop( "ping_pong" ); auto pinger = coop->add_agent( new pinger_t(env) ); auto ponger = coop->add_agent( new ponger_t(env) ); pinger->set_ponger_mbox( ponger->so_direct_mbox() ); ponger->set_pinger_mbox( pinger->so_direct_mbox() ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__new_header_all_hpp.dox000066400000000000000000000007621523065210100252330ustar00rootroot00000000000000/*! \page so_5_4_0__new_header_all_hpp so-5.4.0: New header file all.hpp A new helper header file `so_5/all.hpp` has been added in v.5.4.0. It allows to include just one header file in your .cpp-file and gets access to all SObjectizer's public stuff. For example: \code #include \endcode instead of \code #include #include #include #include \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__new_mpsc_mbox.dox000066400000000000000000000074351523065210100242770ustar00rootroot00000000000000/*! \page so_5_4_0__new_mpsc_mbox so-5.4.0: New type of mbox -- multi-producer and single-consumer Until v.5.4.0 there was only one type of mbox in SObjectizer: multi-producer/multi-consumer (MPMC). Mboxes of those types are created by create_local_mbox() and create_local_mbox(name) methods of class so_5::rt::so_environment_t. MPMC mbox is like a bulletin board: everyone can pin a message to it and everyone can read a pinned message. MPMC mboxes are used to implement publish-subscribe model in SObjectizer. A message type is used as a topic. MPMC mboxes are good when 1-to-many or many-to-many agents interaction is required. But they are too expensive for the case of 1-to-1 interaction. It is because message passing via MPMC mbox consists of two phases: * on the first phase there is a lookup for subscriber list for the specified message type; * on the second phase there is a check for possibility of handling this message type in the current subscribers state. For the 1-to-1 agents interaction the first phase is redundant. The 1-to-1 interaction will be more efficient if a message is directly passed to the subscribers queue without looking up the subscriber list for the message type. This is a goal for a new type of mboxes in SObjectizer 5.4.0 -- direct and efficient support for 1-to-1 agents interaction. New type of mbox is a multi-producer/single-consumer (MPSC) message box. Those mboxes are automatically created by SObjectizer for every agent. When a constructor of agent_t finishes its work there is a MPSC mbox for the agent. That mbox can be accessed via so_5::rt::agent_t::so_direct_mbox() method. That mbox belongs to the agent for which mbox is created. That agent is the owner of the MPSC mbox. New MPSC mbox implements so_5::rt::mbox_t interface just as existing MPMC mbox. References to the MPSC mboxes can be stored by so_5::rt::mbox_ref_t objects. References to MPSC mboxes in form of so_5::rt::mbox_ref_t can be passed to other agents or can be stored anywhere user wants. The main difference between MPSC and MPMC mboxes is the inability to subscribe to the messages from the mbox except for the owner of mbox. There is a one significant trait of MPSC mbox in comparison with MPMC mbox: because there is no the first phase (lookup for list of subscribers by the message type) it is possible to push a message to agents queue even if the agent is not subscribed to it. This message will be rejected on the second phase when the checking for ability of message processing is performed. After introduction of MPSC mboxes there is a question for a SObjectizers user: which type of mboxes should be used and for which purposes? Old MPMC mboxes must be used when an agent is a producer of some information which could be processed by several different agents. Or when there are several producers for information and several consumers for that information. New MPSC mboxes must be used when agent is a receiver of some agent-specific information and/or agent-specific commands. For example the message about the need to reconfigure agent is a agent-specific and it is better to send it to agents MPSC mbox than to common MPMC mbox shared between several agents. Another case for MPSC mboxes is a creation of channel for information processing. For example agent-meter receives a data about air temperature and sends it directly to agent-controller. Agent-controller collects data from several meters and then sends data package to agent-analyzer. Agent-analyzer performs the initial information checking and normalizing. And then sends preprocessed data directly to agent-processor. There could be several groups of meter agents, controller agents and so on. But because they use different mboxes for information exchange they will work independently without any influence from one the another. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__no_anonymous_states.dox000066400000000000000000000011461523065210100255370ustar00rootroot00000000000000/*! \page so_5_4_0__no_anonymous_states so-5.4.0: There is no more true anonymous agent's states Every object of so_5::rt::state_t now has a name. Even if it is not specified by the user the name will be generated automatically. This helps to diagnose errors in event subscription and changing agent states. The automatically generated name can be up to 40-60 bytes long (it depends on 32- or 64-bit more and on features of pointer to string conversion in standard library). So it is recommended not to use anonymous agent's states if count of agents is significant (hundreds of thousands). */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__thread_pool_dispatcher.dox000066400000000000000000000141011523065210100261310ustar00rootroot00000000000000/*! \page so_5_4_0__thread_pool_dispatcher so-5.4.0: New thread_pool dispatcher \section so_5_4_0__thread_pool_dispatcher__intro Intro There is a new dispatcher in SObjectizer v.5.4.0 -- thread_pool. It starts a number of working threads and then distributes agents among those threads. New thread_pool dispatcher doesn't distinguish between not_thread_safe and thread_safe event handlers. All event handlers treated as not_thread_safe. It means that at any moment there could be only one running event handler for an agent. But, unlike one_thread, active_obj and active_group dispatchers agent is not bound to a single working thread for all the time. Under thread_pool dispatcher an agent can handle one event on one working thread, then next event -- on another thread. \section so_5_4_0__thread_pool_dispatcher__work_principle Work Principle Thread_pool dispatcher uses two types of queues. The first one -- is an agent's event queue. When agent receives a message it is placed into agent's event queue. An agent's queue can contain multiple messages for the agent. Thread_pool dispatcher can have multiple agent's queue (as a corner case -- one queue for every agent). The second queue type -- is the dispatcher's queue. There is only one dispatcher's queue in thread_pool dispatcher. This queue holds references to not-empty agents queues. When a message is stored to the empty agents queue a reference to that queue is pushed into dispatchers queue. Working thread gets a reference to an agents queue from the top of dispatchers queue, handles some messages from that agents queue and then, if the agents queue is not empty, returns a reference to the agents queue into the dispatchers queue. When the dispatchers queue are empty all working threads sleep on it. When a reference is pushed to dispatchers queue one of the working thread awakes and gets that reference. If references are stored into the dispatchers queue quicker than working threads process them then dispatchers queue stores them until some working thread gets them out. When the working thread gets a reference to an agents queue it tries to handle at most as max_demands_at_once messages from the queue. The parameter max_demands_at_once is specified during binding agent to the dispatcher. By default it has value 4. It means that if there are 4 or more messages in the queue working thread will handle the first four of them and only then will go to the dispatchers queue for the next waiting agents queue. Because a working thread can sequentially handle more than one message from agents queue it is important how agents from the same cooperation will process the same message. For example, let there are two agents (A and B) in the same cooperation. Both are subscribed to messages M1 and M2. When someone sends M1 and M2 two event handlers for both of agents must be called: A(M1), B(M1), A(M2) and B(M2). But if A and B use different agents queues there is a possibility for different order of event handlers invocation. Like: A(M1), A(M2), B(M1), B(M2). Or: A(M1), B(M1), B(M2), A(M2). Or: A1(M1), B1(M1), A(M2), B(M2). Sometimes it is required to have a strict order in which event handlers invocation for agents from the same cooperation. If A and B use one agents queue then the order will be A(M1), B(M1), A(M2), B(M2). Thread_pool dispatcher allows to specify which agents queue will be used by an agent -- it is called FIFO mechanism and user can specify type of FIFO mechanism for every agent. By default all agents use cooperation FIFO. It means that all agents from the same cooperation will use the same agents queue. And sequence for event handlers calls for agents from that cooperation will be as for one_thread dispatcher (for the example above: A(M1), B(M1), A(M2), B(M2)). But user can specify individual FIFO as FIFO mechanism. It that case an agent will have its own agents queue. That queue will be handled independently from agents queues for other agents from the same cooperation. Recommended Scenarios Described working principle makes thread_pool dispatcher appropriate for the following use cases: * there are many (hundreds of thousands) small agents with lightweight event handlers and messages for every agents occurs episodically by small groups; * there are very few huge agents with very heavy event handlers; \section so_5_4_0__thread_pool_dispatcher__synopsis Synopsis To create thread_pool dispatcher is necessary to call so_5::disp::thread_pool::create_disp() function. It receives one argument -- count of working thread. All those threads will be created during start of dispatcher. \code #include ... so_5::api::run_so_environment( []( so_5::rt::so_environment_t & env ) { ... }, []( so_5::rt::so_environment_params_t & params ) { params.add_named_dispatcher( "my_thread_pool", so_5::disp::thread_pool::create_disp( 16 ) ); } ); \endcode If create_disp() called without argument the dispatcher will try to detect count of threads by calling std::thread::hardware_concurrency() method. To create dispatcher binder for thread_pool dispatcher it is necessary to use one of so_5::disp::thread_pool::create_disp_binder() functions: \code so_5::rt::so_environment_t & env = ...; auto coop = env.create_coop( "my_coop", // Use binder with default params: cooperation FIFO and max_demands_at_once = 4. so_5::disp::thread_pool::create_disp_binder( "my_thread_pool" ) ); // Use this binder parameters for several cooperations. using namespace so_5::disp::thread_pool; params_t binding_params; binding_params.fifo( fifo_t::individual ); binding_params.max_demands_at_once( 1 ); auto child1 = env.create_coop( "my_coop_child_1", create_disp_binder( "my_thread_pool", params ) ); ... auto child2 = env.create_coop( "my_coop_child_2", create_disp_binder( "my_thread_pool", params ) ); // Configure binding parameters during create_disp_binder() call: auto child3 = env.create_coop( "my_coop_child_3", create_disp_binder( "my_thread_pool", []( params_t & p ) { p.fifo( fifo_t::cooperation ); p.max_demands_at_once( 128 ); } ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_4_0__thread_safety_for_events.dox000066400000000000000000000101211523065210100264750ustar00rootroot00000000000000/*! \page so_5_4_0__thread_safety_for_events so-5.4.0: thread_safety flag for event handlers By default in SObjectizer all event handlers are assumed to be not thread safe. Standard dispatchers guarantee that not_thread_safe event handlers will work alone and only on the context of one working thread. A new feature in v.5.4.0 allows to specify that event handler is a thread safe handler. It means that event handler doesn't change state of agent (or does it in thread safe manner). If user specifies a event handler as thread_safe handler it gives to SObjectizer's dispatcher a right of running thread-safe handlers of the same agent in parallel on the different working threads. But with the following conditions: * a not_thread_safe event handler can't be started until there is any other running event handler of the agent. A start of not_thread_safe event handler will be delayed until all already running event handlers finish their work; * no one event handler can't be started until there is a working not_thread_safe event handler; * no one thread_safe event handler can't be started until there is a working not_thread_safe event handler. To specify thread_safe event handler it is necessary to pass additional argument to event() method in subscription chain: \code class my_agent : public so_5::rt::agent_t { public : void so_define_agent() override { so_subscribe( so_direct_mbox() ).in( st_disconnected ) // thread_safety flag is not specified. // This is not_thread_safe event handler and it // can't work in parallel with other event handlers // because it changes agent's state. .event( so_5::signal< msg_reconnect >, &my_agent::evt_reconnect ) // This is thread_safe agent and it doesn't change agent's state. .event( &my_agent::evt_send_when_disconnected, so_5::thread_safe ) // This is thread_safe agent and it doesn't change agent's state. .event( so_5::signal< msg_get_status >, []() -> std::string { return "disconnected"; }, so_5::thread_safe ); ... } ... } \endcode As a example of thread_safe and not_thread_safe event handler lets see an agent for performing cryptography operations: encrypting and decrypting. This operations are stateless and can be done in parallel. But the agent must be reconfigured from time to time. The reconfiguration operation is stateful and event handler for reconfigure message is not_thread_safe. User must only specify the thread_safety flag for those event handlers. All other work will be done by SObjectizer. It means that evt_encrypt and evt_decrypt will be running in parallel until an occurrence of msg_reconfigure. SObjectizer will wait until all previously started evt_encrypt/evt_decrypt finish their work and only then will start evt_reconfigure. While evt_reconfigure is working all other event handlers will be delayed and will be started only after return from evt_reconfigure: \code class a_cryptographer_t : public so_5::rt::agent_t { public : virtual void so_define_agent() override { // not_thread_safe event handler. so_subscribe( so_direct_mbox() ) .event( &a_cryptographer_t::evt_reconfigure ); // thread_safe event handlers. so_subscribe( so_direct_mbox() ) .event( &a_cryptographer_t::evt_encrypt, so_5::thread_safe ) .event( &a_cryptographer_t::evt_decrypt, so_5::thread_safe ); } void evt_reconfigure( const msg_reconfigure & evt ) { ... } encrypt_result_t evt_encrypt( const msg_encrypt & evt ) { ... } decrypt_result_t evt_decrypt( const msg_decrypt & evt ) { ... } }; \endcode Because event handlers can be either ordinary event handlers or synchronous service requests handlers (it depends not on handler but on message sender or service caller) it means that thread_safe event handler can be used for parallel processing of several parallel service requests. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_0__error_logger.dox000066400000000000000000000033601523065210100241210ustar00rootroot00000000000000/*! \page so_5_5_0__error_logger so-5.5.0: An error_logger interface Previous versions of SObjectizer use ACE_Logger facility from ACE Framework. In the case of an error SObjectizer core used one of ACE_Logger macros (e.g. ACE_DEBUG) to place error message to appropriate error stream or/and log file. If user want to store error messages to some specific destination he/she had to tune ACE_Logger (at the start of application for example). But since v.5.5.0 SObjectizer doesn't use ACE Framework any more. So if user wants to store SObjectizer's error messages to some location he/she must define and specify its own error_logger to the SObjectizer Environment parameters. Something like this: \code class custom_error_logger : public so_5::rt::error_logger_t { public : // Constructor. custom_error_logger( some_log_interface log ) : log_(log) {} // Error message handler. virtual void log( const char * file, unsigned int line, const std::string & message ) override { log_.store_message( message ); } private : some_log_interface log_; }; ... // Tuning SObjectizer Environment parameters at start. some_log_interface log = create_log(...); ... so_5::launch( []( so_5::rt::environment_t & env ) { // Some initialization stuff... }, [log]( so_5::rt::environment_params_t & params ) { // Setting logger. params.error_logger( so_5::rt::error_logger_shptr_t( new custom_error_log( log ) ) ); ... } ); \endcode If custom error logger is not specified by the user then a default error logger will be used. SObjectizer creates default error logger by so_5::create_stderr_logger(). This logger uses std::cerr as error stream. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_0__native_stdchrono_support.dox000066400000000000000000000021021523065210100265670ustar00rootroot00000000000000/*! \page so_5_5_0__native_stdchrono_support so-5.5.0: Native support for std::chrono Since v.5.5.0 it is possible to _specify_ pause and _period_ parameters for single_timer and schedule_timer methods as std::chrono-values: \code class my_agent_t : public so_5::rt::agent_t { public : virtual void so_evt_start() override { m_hello_timer_id = so_environment() .schedule_timer( std::move( msg ), so_direct_mbox(), // Delay for a second. std::chrono::seconds(1), // Repeat every 1.25 of seconds. std::chrono::milliseconds(1250) ); m_stop_timer_id = so_environment() .schedule_timer< msg_stop_signal >( m_shutdowner_mbox, // Delay for two seconds. std::chrono::seconds(2), // Not a periodic. std::chrono::seconds::zero() ); ... } ... private : so_5::timer_id_t m_hello_timer_id; so_5::timer_id_t m_stop_timer_id; }; \endcode Old single_timer/schedule_timer are still here and can be used too. But they are marked as deprecated and can be removed in next major SObjectizer updates. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_0__timer_thread_factories.dox000066400000000000000000000024311523065210100261350ustar00rootroot00000000000000/*! \page so_5_5_0__timer_thread_factories so-5.5.0: Timer thread factories Since v.5.5.0 the Timer Thread Template library is used for timers implementation. The timertt provides several timer mechanisms and user can choose which one will be used with SObjectizer Environment. There are several factory function for timer mechanism creation: so_5::timer_wheel_factory, so_5::timer_list_factory and so_5::timer_heap_factory. Any of them can be used in tuning SObjectizer Environment parameters. For example: \code so_5::launch( []( so_5::rt::environment_t & env ) { // Some initialization stuff... }, // SObjectizer Environment parameters tuning. []( so_5::rt::environment_params_t & params ) { // Use timer_wheel mechanism with wheel size 10000 // and timer step size of 5ms. params.timer_thread( so_5::timer_wheel_factory( 10000, std::chrono::milliseconds(5) ) ); ... } ); \endcode By default the timer_heap mechanism is used. But if your application uses a lot of timers (millions or tens of millions or even more) then timer_wheel must be considered as more appropriate for big amount of timers. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_11__event_queue_lock_factories.dox000066400000000000000000000154411523065210100271120ustar00rootroot00000000000000/*! \page so_5_5_12__event_queue_lock_factories so-5.5.12: Factories for event queue locks All SObjectizer's dipatchers use two kinds of event queues: multi-producer/single-consumer and multi-producer/multi-consumer queues. Those queues require synchronization for protection of queues' data from access from different threads. Synchronization objects are necessary not only for data protection but also for notification of consumers about appearance of new events (a customer sleeps when event queue is empty and must be awakened on arrival of new event). Until v.5.5.10 combined locking scheme with spinlock and mutex/condition_variable was used. This locking scheme works as described below. There is a busy waiting part: - event producer acquires spinlock and stores new event into the queue. Then event producer releases spinlock; - consumer thread does an busy waiting loop. On each iteration consumer acquires spinlock and checks queue. If queue is empty then consumer releases spinlock and calls `std::this_thread::yield` method. Busy waiting works great when message passing is intensive. But if there are some pauses in generation of events then locking scheme switches to usage of mutex and condition variable: - consumer thread breaks busy waiting loop, acquires mutex and goes to sleep on conditon variable; - event producer acquires spinlock and see that event queue is empty and consumer is sleeping on condition variable. Producer stores new event into the queue, then acquires mutex and sets up condition variable; - consumer thread wakes up and returns to event processing with busy waiting loop for new events. There is a time limit for busy waiting. If there is no new events during some time then consumer thread switches from busy waiting on spinlock to ordinary waiting on mutex/condition_variable. This time limit was one millisecond. This scheme was implemented as combined_lock abstraction and was used in all dispatchers util v.5.5.10. Locking with combined_lock is efficient if application is working under the heavy load. But not effiecient on some specific load profiles. Lets imagine active agent which initiates several periodic messages. All actions are performed by that agent only on arrival of periodic messages. All other time agent do nothing and its working thread is sleeping on empty event queue. If an application uses just one such active agent the overhead cost of busy waiting is relative small and could be ignored. But if there are several dozens of such agents the overhead cost could be relative high: 3-4% of CPU usage even if application do nothing and all working threads just do busy waiting periodically. Situation could be more dramatic if there are several such application on the same server. To solve this problem v.5.5.10 introduces concept of lock factoris for MPSC queues and v.5.5.11 expands this concept for MPMC queues. An user can specify lock factory during the creation of a dispatcher. Dispatcher will use a lock created by that factory. There are two lock factories: - combined_lock_factory which creates combined_lock (described above); - simple_lock_factory which creates very simple lock with mutex and condition_variable without any complex schemes with busy waiting or something else. New factories can be added in the future versions of SObjectizer. combined_lock_factory is still used by default. If this locking scheme is not appropriate for your application it is possible to specify different locking factory (or to specify combined_lock_factory with different busy waiting time). To specify lock factory it is necessary to use `disp_params_t` object and the corresponding `create_disp` or `create_private_disp` functions for dispatcher creation. Since v.5.5.11 there are appropriate definitions of `disp_params_t` types is the dispatcher's namespaces. There are also namespaces `queue_traits` with definitions of lock factory functions and other queue-related stuff in dispatchers' namespaces (like `so_5::disp::one_thread::queue_traits` or `so_5::disp::prio_one_thread::strictly_ordered::queue_traits`). Technicaly speaking those `queue_traits` namespaces are just an alias for so_5::disp::mpsc_queue_traits or so_5::disp::mpmc_queue_traits namespace. Because of that the preparation of `disp_params_t` for a dispatcher look similar for different dispatcher types. For example that is for so_5::disp::one_thread dispatcher: \code using namespace so_5::disp::one_thread; auto disp = create_private_disp( env, disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & queue_params ) { queue_params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); \endcode And this is for so_5::disp::active_obj dispatcher: \code using namespace so_5::disp::active_obj; auto disp = create_private_disp( env, disp_params_t{}.tune_queue_params( []( queue_traits::disp_params_t & queue_params ) { queue_params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); \endcode And this is for so_5::disp::thread_pool dispatcher: \code using namespace so_5::disp::thread_pool; auto disp = create_private_disp( env, disp_params_t{} .thread_count(16) .set_queue_params( queue_traits::queue_params_t{} .lock_factory( queue_traits::simple_lock_factory() ) ), "db_operations" ); \endcode Please note that lock_factory can be specified only at the moment of the creation of a dispatcher. Lock cannot be changed after the creation of a dispatcher. Lock factory can be specified for the default dispatcher too. That dispatcher is created automatically by SObjectizer Environment. To specify lock factory for the default dispacher it is necessary to use so_5::rt::environment_params_t: \code so_5::launch( []( so_5::rt::environment_t & env ) {...}, []( so_5::rt::environment_params_t & env_params ) { using namespace so_5::disp::one_thread; env_params.default_disp_params( disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & queue_params ) { queue_params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); } ); \endcode At mentioned above the combined_lock_factory is still used by default. Default waiting time for busy waiting is specified by `default_combined_lock_waiting_time()` function. In v.5.5.10 it is one millisecond. It is possible to set different waiting time by using `combined_lock_factory(duration)` function: \code using namespace so_5::disp::active_group; auto disp = create_private_disp( env, disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & queue_params ) { // Set up combined_lock with 0.5 second busy waiting time. queue_params.lock_factory( queue_traits::combined_lock_factory( std::chrono::milliseconds(500) ) ); } ) ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_13__so_rt_ns_deprecared.dox000066400000000000000000000025131523065210100255200ustar00rootroot00000000000000/*! \page so_5_5_13__so_rt_ns_deprecated so-5.5.13: Deprecation of %so_5::rt namespace Since v.5.5.13 namespace so_5::rt is deprecated. It now contains only several typedefs for compatibility with previous versions. All the stuff from so_5::rt is moved into so_5 namespace. There are almost no changes in names during moving content from so_5::rt to so_5, except the following cases: - so_5::coop_t must be used instead of so_5::rt::agent_coop_t; - so_5::coop_unique_ptr_t must be used instead of so_5::rt::agent_coop_unique_ptr_t; - so_5::layer_t must be used instead of so_5::rt::so_layer_t (there are also new names: so_5::layer_unique_ptr_t, so_5::layer_ref_t and so_5::layer_map_t instead of so_5::rt::so_layer_unique_ptr_t, so_5::rt::so_layer_ref_t, so_5::rt::so_layer_map_t). Namespace so_5::rt will be removed from SObjectizer in v.5.6.0. It means that code which uses SObjectizer must be modified. In most cases modification will be very simple: a change of so_5::rt prefix to so_5 prefix. \attention Some code cannot be compiled after switching to v.5.5.13 if the following instructions are used together: \code using namespace so_5; using namespace so_5::rt; \endcode It is because the same names (like agent_t) are now defined in both namespaces. It this case using namespace so_5::rt must be removed. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_13__timed_send_deprecared.dox000066400000000000000000000021131523065210100260010ustar00rootroot00000000000000/*! \page so_5_5_13__timed_send_deprecated so-5.5.13: Deprecation of send_delayed_to_agent and send_periodic_to_agent Functions `so_5::send_delayed_to_agent()` and `so_5::send_periodic_to_agent()` are deprecated and will be removed in v.5.6.0. New version of `so_5::send_delayed()` and `so_5::send_periodic()` which receve a reference to `so_5::agent_t` and `so_5::adhoc_agent_definition_proxy_t` must be used instead. Two old forms of send_delayed/send_periodic functions which receive a referent to an agent and destination mbox are deprecated also. It means that the following code will not work in v.5.6.0: \code void some_agent::some_event() { ... so_5::send_delayed< notify >( *this, m_target_mbox, pause, ... ); so_5::send_periodic< retry >( *this, m_target_mbox, pause, period, ... ); ... } \endcode Such code must be rewritten this way: \code void some_agent::some_event() { ... so_5::send_delayed< notify >( so_environment(), m_target_mbox, pause, ... ); so_5::send_periodic< retry >( so_environment(), m_target_mbox, pause, period, ... ); ... } \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_14__mhood.dox000066400000000000000000000072031523065210100226240ustar00rootroot00000000000000/*! \page so_5_5_14__mhood so-5.5.14: mhood_t and event handler formats An event handler for agent can be specified as ordinary method: \code class my_agent : public so_5::agent_t { public : virtual void so_define_agent() override { so_subscribe_self().event( &my_agent::some_handler ); ... } ... void some_handler( const some_message & msg ) {...} }; \endcode Also an event handler for agent can be specified as lambda-function: \code class my_agent : public so_5::agent_t { public : virtual void so_define_agent() override { so_subscribe_self().event( [&]( const some_message & msg ) {...} ); ... } }; \endcode Please note that event handlers for ad-hoc agents can be specified only as lambda-functions. But in versions prior to v.5.5.14 there was some discrimination for lambda-like event handlers: it was impossible to pass an `so_5::event_data_t` as argument for lambda-function. This flaw in event handlers design had two major negative consequences. There was no possibitity to resend the same message object or to store it for the future use. Event handler in the form of agent's method can receive message as `so_5::event_data_t` and call `make_reference()` method. But lambda-style event handler couldn't do that. There was some difficulty in writing template code. For example it was not a simple task to write something like this: \code template< typename M > void define_reaction_for( so_5::agent_t & receiver ) { receiver.so_subscribe_self().event( []( const M & ) {...) ); } \endcode It is because if M is a signal then event handler must have different format. Since v.5.5.14 lambda-style event handlers can have the same format as method-style event handler. In v.5.5.14 any event handler (as lambda-style as well as method-style) can have one of the following formats: \code // Applicable for messages and signals. return_type handler( so_5::mhood_t< message_type > msg ); return_type handler( const so_5::mhood_t< message_type > & msg ); // Applicable for messages only. return_type handler( const message_type & msg ); return_type handler( message_type msg ); // Applicable for signals only. return_type handler(); \endcode Old name `so_5::event_data_t` is now just an alias for `so_5::mhood_t`. Name `event_data_t` is deprecated and will be removed in v.5.6.0. Name `mhood_t` must be used instead. Method `mhood_t::make_reference` allows to receive smart pointer to message object even if message type is not derived from `so_5::message_t`. It makes possible to write something like this: \code so_subscribe_self().event( [&]( mhood_t< std::string > msg ) { // Redirecting message to different mbox. m_next_target->deliver_message( msg.make_reference() ); } ); \endcode If M is not derived from `so_5::message_t` then `mhood_t::make_reference` returns `so_5::intrusive_ptr_t>`. NOTE! In previous versions it was possible to write: \code void some_handler( const so_5::event_data_t< so_5::user_type_message_t< M > > & msg ) { ... } \endcode Now it is prohibited. Such code must be rewritten as: \code void some_handler( mhood_t< M > msg ) { ... } // Or: void some_handler( const mhood_t< M > & msg ) { ... } \endcode NOTE! There is an alias `so_5::agent_t::mhood_t`. It allows to use short name `mhood_t` instead of `so_5::mhood_t` in the scope of a class which is derived from `so_5::agent_t`. For example: \code class my_agent : public so_5::agent_t { ... private: void some_handler( mhood_t< some_message > msg ) {...} void another_handler( mhood_t< another_message > msg ) {...} ... }; \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_16__auto_close_mchains.md000066400000000000000000000113211523065210100251610ustar00rootroot00000000000000# so-5.5.16: New helper function auto_close_mchains {#so_5_5_16__auto_close_mchains} New helper function `so_5::auto_close_mchains` has been added in v.5.5.16. It helps to close some mchains at scope exit. When mchains are used for inter thread communication special care must be taken because there could be deadlocks. For example let see the simplest case: ~~~~~{.cpp} using namespace std; using namespace so_5; void do_something(environment_t & env) { auto ch = create_mchain(env); // Launch child thread which will read messages and do some processing. thread child{ [ch]{ // Return from receive will be on close of ch. receive(from(ch), [](const some_task & t) {...}); } }; // Do generation of tasks for child thread. while(has_tasks()) { ... // generation of next task. send(ch, ...); } // Child thread must be joined before return from do_something. child.join(); } ~~~~~ This is an error in this example: `do_something` will not return. It is becase child thread will never finished. Because mchain `ch` will not be closed explicitely. It easy to fix that example: ~~~~~{.cpp} // Do generation of tasks for child thread. while(has_tasks()) { ... // generation of next task. send(ch, ...); } // Mchain must be closed to force finish of child thread. close_retain_content(ch); // Child thread must be joined before return from do_something. child.join(); ~~~~~ But there could be more complex cases related to exception safety. If an exception is thrown after construction of `child` but before call to `close_retain_content` there could be a deadlock (see above). To make this code exception safe we need two helpers: one of them should close chain, another should call join for threads. Something like that: ~~~~~{.cpp} class auto_closer { mchain_t & ch_; public : auto_closer(mchain_t & ch) : ch_{ch} {} ~auto_closer() { close_retain_content(ch_); } }; class auto_joiner { thread & thr_; public : auto_joiner(thread & thr) : thr_{thr} {} ~auto_joiner() { if(thr_.joinable()) thr_.close(); } }; ~~~~~ With the help of these classes we can make `do_something` more exception safe: ~~~~~{.cpp} void do_something(environment_t & env) { auto ch = create_mchain(env); // Launch child thread which will read messages and do some processing. thread child{ [ch]{ // Return from receive will be on close of ch. receive(from(ch), [](const some_task & t) {...}); } }; // There is a trick: auto_joiner must be created before auto_closer. // It is because we need the destructor of auto_closer be called first. auto_joiner child_joiner{ child }; auto_closer ch_closer{ ch }; // Do generation of tasks for child thread. while(has_tasks()) { ... // generation of next task. send(ch, ...); } // There is no need for calling close() and join() anymore. } ~~~~~ But writting ad-hoc classes like `auto_closer` is a boring and error-prone tasks. Helper function `so_5::auto_close_mchains` do it for you: ~~~~~{.cpp} void do_something(environment_t & env) { auto ch = create_mchain(env); // Launch child thread which will read messages and do some processing. thread child{ [ch]{ // Return from receive will be on close of ch. receive(from(ch), [](const some_task & t) {...}); } }; auto_joiner child_joiner{ child }; auto ch_closer = auto_close_mchains(mchain_props::close_mode_t::retain_content, ch); // Do generation of tasks for child thread. while(has_tasks()) { ... // generation of next task. send(ch, ...); } // There is no need for calling close() and join() anymore. } ~~~~~ Function `auto_close_mchains` returns object very similar to `auto_closer` shown above. The destructor of that object will close all mchains passed to `auto_close_mchains`. It is possible to pass several mchains to one call of `auto_close_mchains`: ~~~~~{.cpp} auto command_ch = create_mchain(env); auto reply_ch = create_mchain(env); auto status_ch = create_mchain(env); auto log_ch = create_mchain(env); auto ch_closer = auto_close_mchains(mchain_props::close_mode_t::drop_content, command_ch, reply_ch, status_ch, log_ch); ... ~~~~~ Because usage of `auto_close_mchains` is verbose there are two shorthands: `auto_close_retain_content` and `auto_close_drop_content`. So instead of writting: ~~~~~{.cpp} auto ch_closer = auto_close_mchains(mchain_props::close_mode_t::drop_content, command_ch, reply_ch, status_ch, log_ch); ~~~~~ it is possible to write: ~~~~~{.cpp} auto ch_closer = auto_close_drop_content(command_ch, reply_ch, status_ch, log_ch); ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_16__auto_join.md000066400000000000000000000055411523065210100233200ustar00rootroot00000000000000# so-5.5.16: New helper function auto_join {#so_5_5_16__auto_join} Mchains allow to use SObjectizer for writing concurrent programms even without agents. Just mchains and raw std::threads. But usage of std::thread requires some attention becase if std::thread is started as joinable thread it must be joined. Lets see a simple case where two thread will read and process messages from a mchain: ~~~~~{.cpp} using namespace std; using namespace so_5; void worker_thread(mchain_t ch) { // Read all messages until ch will be closed. receive(from(ch), handler1, handler2, ...); } void sample(environment_t & env) { auto ch = create_mchain(env); thread first_thread{worker_thread, ch}; thread second_thread{worker_thread, ch}; while(has_some_work()) send(ch, ...); close_retain_content(ch); } ~~~~~ This sample has serious errors. Threads must be joined. But simple modification like: ~~~~~{.cpp} ... while(has_some_work()) send(ch, ...); close_retain_content(ch); second_thread.join(); first_thread.join(); ~~~~~ is not enough. Because the resulting code is not exception safe. To make it exception safe we need to do some additional work: ~~~~~{.cpp} // Small helper class for join thread. class auto_joiner { thread & thr_; public : auto_joiner(thread & thr) : thr_{thr} {} ~auto_joiner() { if(thr_.joinable()) // Note this check! thr_.join(); } }; ... void sample(environment_t & env) { thread first_thread, second_thread; auto_joiner first_joiner{first_thread}, second_joiner{second_thread}; auto ch = create_mchain(env); // Use auto_closer feature from SO-5.5.16. auto ch_closer = auto_close_retain_content(ch); first_thread = thread{worker_thread, ch}; second_thread = thread{worker_thread, ch}; while(has_some_work()) send(ch, ...); } ~~~~~ Now `sample` function is exception safe. It closes mchain automatically and joins all threads automatically. But it requires addtional class `auto_joiner` and implementation of that class is not as simple as it seems. If you write such class the first time you probably forget to check `thr_.joinable()` condition. And your `auto_joiner` class will crash your application if thread is not started yet. To make things simpler v.5.5.16 add helper function `so_5::auto_join`. This function allows to rewrite `sample` such way: ~~~~~{.cpp} void sample(environment_t & env) { thread first_thread, second_thread; auto thr_joiner = auto_join(first_thread, second_thread); auto ch = create_mchain(env); // Use auto_closer feature from SO-5.5.16. auto ch_closer = auto_close_retain_content(ch); first_thread = thread{worker_thread, ch}; second_thread = thread{worker_thread, ch}; while(has_some_work()) send(ch, ...); } ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_16__create_mchain_functions.md000066400000000000000000000033651523065210100262050ustar00rootroot00000000000000# so-5.5.16: create_mchain helper functions {#so_5_5_16__create_mchain_functions} Several new helper functions `so_5::create_mchain` have been added in v.5.5.16. They simplify creation of mchains. For example, creation of size-unlimited mchain can looks like: ~~~~~{.cpp} so_5::environment_t & env = ...; auto ch = create_mchain( env ); ~~~~~ instead of: ~~~~~{.cpp} so_5::environment_t & env = ...; auto ch = env.create_mchain( so_5::make_unlimited_mchain_params() ); ~~~~~ There are also `create_mchain` functions for creation of size-limited mchains (with or without waiting on overflow): ~~~~~{.cpp} so_5::environment_t & env = ...; // Creation of size-limited mchain without waiting on overflow. auto ch1 = create_mchain( env, // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest ); // Creation of size-limited mchain with waiting on overflow. auto ch2 = create_mchain( env, // Wait for 150ms. std::chrono::milliseconds{150}, // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest ); ~~~~~ Please note that the first argument for `create_mchain` function can be a reference to `environment_t` or to `wrapped_env_t` object. It allows to write: ~~~~~{.cpp} so_5::wrapped_env_t sobj{...}; auto ch = create_mchain(sobj); ~~~~~ instead of: ~~~~~{.cpp} so_5::wrapped_env_t sobj{...}; auto ch = create_mchain(sobj.environment()); ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_16__mchain_select.md000066400000000000000000000123471523065210100241310ustar00rootroot00000000000000# so-5.5.16: New select functions for receive messages from several mchains {#so_5_5_16__mchain_select} Since v.5.5.16 there are two new `so_5::select` functions which allow to receive and handle messages from several mchains. The first variant of `select` takes a timeout and a list of mchains and message handlers for them: ~~~~~{.cpp} so_5::mchain_t ch1 = env.create_mchain(...); so_5::mchain_t ch2 = env.create_mchain(...); so_5::mchain_t ch3 = env.create_mchain(...); ... so_5::select(std::chrono::milliseconds(500), case_(ch1, [](const reply1 & r) {...}), case_(ch2, [](const reply2 & r) {...}), case_(ch2, [](const reply3 & r) {...}, [](const reply4 & r) {...})); ~~~~~ It checks the mchains and wait for no more than 500ms if all mchains are empty. If some mchain is not empty it extracts just one message from that mchain and tries to find an appropriate handler for it. If handler is found it is called. If a handler is not found then the message extracted will be thrown out without any processing. If all mchains are empty even after the specified timeout then `select` will do nothing. Simple form of `select` extract just one message from a mchain. Which non-empty mchain will be used for message extraction is not determined. Please note that if there is just one non-empty mchain then simple `select` will return after extraction on just one message. This message could not be processed if there is no appropriate handler for it. As with `receive` there are two special values which can be used as timeout: * value `so_5::no_wait` specifies zero timeout. It means that receive will not wait if the mchain is empty; * value `so_5::infinite_wait` specifies unlimited waiting. The return from receive will be on arrival of any message. Or if the mchain is closed explicitly. There is also more advanced version of `select` which can receive and handle more than one message from mchains. It receives a `so_5::mchain_select_params_t` objects with list of conditions and returns control if any of those conditions becomes true. For example: ~~~~~{.cpp} using namespace so_5; mchain_t ch1 = env.create_mchain(...); mchain_t ch2 = env.create_mchain(...); // Receive and handle 3 messages. // It could be 3 messages from ch1. Or 2 messages from ch1 and 1 message // from ch2. Or 1 message from ch1 and 2 messages from ch2. And so on... // // If there is no 3 messages in mchains the select will wait infinitely. // A return from select will be after handling of 3 messages or // if all mchains are closed explicitely. select( from_all().handle_n( 3 ), case_( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), case_( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); // Receive and handle 3 messages. // If there is no 3 messages in chains the select will wait // no more that 200ms. // A return from select will be after handling of 3 messages or // if all mchains are closed explicitely, or if there is no messages // for more than 200ms. select( from_all().handle_n( 3 ).empty_timeout( milliseconds(200) ), case_( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), case_( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); // Receive all messages from mchains. // If there is no message in any of mchains then wait no more than 500ms. // A return from select will be after explicit close of all mchains // or if there is no messages for more than 500ms. select( from_all().empty_timeout( milliseconds(500) ), case_( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), case_( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); // Receve any number of messages from mchains but do waiting and // handling for no more than 2s. select( from_all().total_time( seconds(2) ), case_( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), case_( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); // Receve 1000 messages from chains but do waiting and // handling for no more than 2s. select( from_all().extract_n( 1000 ).total_time( seconds(2) ), case_( ch1, []( const first_message_type & msg ) { ... }, []( const second_message_type & msg ) { ... } ), case_( ch2, []( const third_message_type & msg ) { ... }, handler< some_signal_type >( []{ ... ] ), ... ) ); ~~~~~ Both `select` versions return an object of `so_5::mchain_receive_result_t` type. Methods of that object allow to get number of extracted and handled messages, and also the status of `select` operation. It is possible to use a mchain in several `select` on different threads at the same time. **NOTE!** A mchain can be used inside just one `case_` statement in `select`. If the same mchain is used in several `case_` statements then the behaviour of `select` is undefined. Please note also that `select` do not checks uniqueness of mchains in `case_` statements (for performance reasons). sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_16__mchain_uses_mpmc_queue.md000066400000000000000000000016151523065210100260450ustar00rootroot00000000000000# so-5.5.16: mchains use MPMC queue inside {#so_5_5_16__mchains_are_mpmc} Since v.5.5.16 mchains use Multi-Producer/Multi-Consumer queue inside. It makes possible to use the same mchain in several parallel `receive` on different threads. It could be used for simple load balancing scheme, for example: ~~~~~{.cpp} void worker_thread(so_5::mchain_t ch) { // Handle all messages until mchain will be closed. receive(from(ch), handler1, handler2, handler3, ...); } ... // Message chain for passing messages to worker threads. auto ch = create_mchain(env); // Worker thread. thread worker1{worker_thread, ch}; thread worker2{worker_thread, ch}; thread worker3{worker_thread, ch}; // Send messages to workers. while(has_some_work()) { so_5::send< some_message >(ch, ...); ... } // Close chain and finish worker threads. close_retain_content(ch); worker3.join(); worker2.join(); worker1.join(); ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_16__next_thread_wakeup_threshold.md000066400000000000000000000061001523065210100272560ustar00rootroot00000000000000# so-5.5.16: New next_thread_wakeup_threshold param for thread-pool dispatchers {#so_5_5_16__next_thread_wakeup_threshold} Until v.5.5.16 thread-pool dispatchers (`thread_pool` and `adv_thread_pool`) used very simple working scheme: when new message was stored in demand queue and there was some sleeping worker thread this sleeping thread was woken up. This scheme works well if message processing is long action. In that case the cost of wakening of sleeping thread in much lower than cost of message processing. But there could be scenarios where message processing is very quick and cheep. In such cases can be more efficient not to wake up a sleeping working thread but to allow to hold a message in demand queue for some time. Lets see small example: ~~~~~{.cpp} using namespace so_5; // The first agent in message handling chain. Do parameters checking. class params_checker final : public agent_t { const mbox_t m_next; public : params_checker(context_t ctx, mbox_t next) : agent_t{ctx}, m_next{move(next)} { so_subscribe_self().event([this](const initial_request & r) { if(valid_request(r)) // Send request to the next stage. send(m_next, r); }); ... } ... }; // Next agent in message handling chain. Do some parameters transformation. class params_transformer final : public agent_t { const mbox_t m_next; public : params_transformer(context_t ctx, mbox_t next) : agent_t{ctx}, m_next{move(next)} { so_subscribe_self().event([this](const checked_request & r) { send(m_next, transform(r)); }); ... } ... }; ~~~~~ Suppose that `params_checker` and `params_transformer` are part of one coop and are bound to `thread_pool` dispatcher. When `params_checker` sends `checked_request` message to `params_transformer` it might be better to keep this message in demand queue for some time and handle in on the same working thread after return from `params_checker`s event handler. Since v.5.5.16 there is such parameter as `next_thread_wakeup_threshold` for `thread_pool` and `adv_thread_pool` dispatchers. This paraments holds a value of demand queue size. When actual demand queue size of dispatcher becomes greater than `next_thread_wakeup_threshold` then a sleeping working thread will be woken up (if there is any sleeping thread for the dispatcher). By default `next_thread_wakeup_threshold` is 0. It means that `thread_pool` and `adv_thread_pool` works like in previous versions. But this value can be changed. For example: ~~~~~{.cpp} using namespace so_5; using namespace so_5::disp::thread_pool; environment_t & env = ...; auto disp = create_private_disp( env, "my-thread-pool", disp_params_t{} .thread_count( 16 ) .tune_queue_params( []( queue_traits::queue_params_t & qp ) { // A sleeping thread will be woken up if there are 3 or more // demands in the dispatcher's demands queue. qp.next_thread_wakeup_threshold( 2 ); } ) ); ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_17__mchain_master_handle.md000066400000000000000000000034131523065210100254530ustar00rootroot00000000000000# so-5.5.17: mchain_master_handler_t helper class {#so_5_5_17__mchain_master_handle} There are cases when a mchain is created at the beginning of a scope and the mchain must be automatically closed at the end of the scope. Something like: ~~~~~{.cpp} void some_work_distributor(so_5::environment_t & env) { auto ch = create_mchain(env); ... // Some stuff. close_drop_content(ch); } ~~~~~ The code shown above is not good and it can lead to errors in the case if some exception is raised between `create_mchain` and `close_drop_content` calls. It is better to use RAII idiom. Since v.5.5.16, there are `auto_close_mchains` helpers which allows us to write as follows: ~~~~~{.cpp} void some_work_distributor(so_5::environment_t & env) { auto ch = create_mchain(env); auto ch_closer = auto_close_mchains( so_5::mchain_props::close_mode_t::drop_content, ch); ... // Some stuff. } ~~~~~ It is better but still it has some drawbacks: a user can forget to call `auto_close_mchains` or can forget to store the value returned. Which is why a new helper class `mchain_master_handle_t` was introduced in v.5.5.17. This class is intended to be used in the cases described above: ~~~~~{.cpp} void some_work_distributor(so_5::environment_t & env) { so_5::mchain_master_handle_t ch( so_5::mchain_props::close_mode_t::drop_content, so_5::create_mchain(env) ); ... // Some stuff. } ~~~~~ Note that `auto_close_mchains` helpers are good tools for cases when it is necessary to work with already created mchains. For example: ~~~~~{.cpp} // Mchain is created by someone else. void some_work_distributor(so_5::mchain_t ch) { auto ch_closer = auto_close_mchains( so_5::mchain_props::close_mode_t::drop_content, ch); ... // Some stuff. } ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_17__on_close_handlers_for_adv_receive.md000066400000000000000000000023451523065210100302140ustar00rootroot00000000000000# so-5.5.17: on_close handlers for advanced receive and select functions {#so_5_5_17__on_close_handlers_for_adv_receive} An advancend version of mchain's `receive` and `select` functions are extended: currently it is possible to specify a user-defined function to handle a close event for a mchain. Let's imagine a situation when several mchains must be handled in `select` function. But the handling must be stopped if one of the chains is closed. With a close event handler it could be done in the following way: ~~~~~{.cpp} void read_and_handle_several_chains( so_5::mchain_t main_chain, so_5::mchain_t aux_chain1, so_5::mchain_t aux_chain2 ) { bool main_chain_closed = false; so_5::select( so_5::from_all() // Stop handling if main_chain_closes becomes true. .stop_on( [&main_chain_closed]{ return main_chain_closed; } ) // Handle chain close events. .on_close( [main_chain, &main_chain_closed]( const so_5::mchain_t & ch ) { if( ch == main_chain ) main_chain_closed = true; } ), // Message handlers for chains... case_( main_chain, ... ), case_( aux_chain1, ... ), case_( aux_chain2, ... ) ); } ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_17__prepare_receive.md000066400000000000000000000023621523065210100244700ustar00rootroot00000000000000# so-5.5.17: prepare_receive and prepare_select functions {#so_5_5_17__prepare_receive} Usage of ordinary forms of `so_5::receive` functions inside loops could be inefficient because of wasting resources on constructions of internal objects with descriptions of handlers on each `receive()` call. More efficient way is the preparation of all receive params and reusing them later. A combination of `so_5::prepare_receive()` and `so_5::receive()` allows us to do that. Usage example: ~~~~~{.cpp} auto prepared = so_5::prepare_receive( so_5::from(ch).extract_n(10).empty_timeout(200ms), some_handlers... ); ... while( !some_condition ) { auto r = so_5::receive( prepared ); ... } ~~~~~ The exactly same situation is for `so_5::select` functions: some resources are allocated on each call of ordinary `so_5::select()` function and deallocated at exit. This reallocation can be avoided by using `so_5::prepare_select()` and appropriate `so_5::select()` function: ~~~~~{.cpp} auto prepared = so_5::prepare_select( so_5::from_all().extract_n(10).empty_timeout(200ms), case_( ch1, some_handlers... ), case_( ch2, more_handlers... ), case_( ch3, yet_more_handlers... ) ); ... while( !some_condition ) { auto r = so_5::select( prepared ); ... } ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_17__so_as_static_lib.md000066400000000000000000000022521523065210100246270ustar00rootroot00000000000000# so-5.5.17: SObjectizer as static library {#so_5_5_17__so_as_static_lib} Since v.5.5.17, it is possible to build SObjectizer as a shared and as a static library. When SObjectizer is built via default build scripts (`build.rb` for Mxx_ru or `CMakeLists.txt` for CMake) both versions of the library are built: so.5.5.17.dll and so_s.5.5.17.lib on Windows platform, libso.5.5.17.so and libso_s.5.5.17.a on Unixes. To use SObjectizer as a static library via Mxx_ru it is necessary to add `so_5/prj_s.rb` into your project file: ~~~~~{.rb} require 'mxx_ru/cpp' MxxRu::Cpp::exe_target { required_prj 'so_5/prj_s.rb' target 'sample.so_5.hello_world_s' cpp_source 'main.cpp' } ~~~~~ To use SObjectizer as a static library via Mxx_ru it is necessary to link your target against `so_s.5.5.17` library: ~~~~~{.txt} set(SAMPLE_S sample.so_5.hello_world_s) add_executable(${SAMPLE_S} main.cpp) target_link_libraries(${SAMPLE_S} so_s.${SO_5_VERSION}) ~~~~~ **NOTE** If you are building SObjectizer by different build tool it is necessary to define symbols `SO_5_PRJ` and `SO_5_STATIC_LIB`. Symbol `SO_5_STATIC_LIB` must also be defined if building projects use SObjectizer as a static library. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_18__global_lock_factories.md000066400000000000000000000103121523065210100256320ustar00rootroot00000000000000# so-5.5.18: Global lock_factories {#so_5_5_18__global_lock_factories} Message queues in SObjectizer dispatchers use synchronization objects to protect the contents of the queue when working with it from several threads. Synchronization objects are created with the help of special factories (so-called lock_factory). The developer can point which particular lock_factory should be used when creating specific dispatcher. This feature is available since 5.5.10 and 5.5.11 versions. Also since then two types of lock_factories were implemented: * `combined_lock_factory` creates combined synchronization objects. Such object consists of two parts - spin-lock and simple mutex. The capture of such object is performed in a tricky way: first there are several attempts to capture spin-lock with call to `std::this_thread::yield` between unsuccessful attempts. If spin-lock is not captured then there is an attempt to capture mutex. Doing this during prolonged waiting the thread, which tries to capture the synchronization object, provides operation system an opportunity to fall asleep until the resource is available. Such combined synchronization objects show good results in heavy loads when there is an intensive message exchange and no one waits long to get some work; * `simple_lock_factory` creates synchronization objects based on simple mutex. That is the thread, which attempts to capture the resource obtained by someone, will simply fall asleep waiting for mutex to be released. When the mutex is released the operation system will wake the sleeping thread and push it to execution. Such synchronization objects are more expensive but the application consumes almost no resources in case message chains are empty (nobody is using cores for nothing on spin-locks). By default `combined_lock_factory` is used. This can be changed in message queue properties to `simple_lock_factory` if needed. Before version 5.5.18 it was possible to replace `combined_lock_factory` with `simple_lock_factory` only for one separate dispatcher. That is when creating dispatcher in its queue_traits `simple_lock_factory` is selected. Which was uncomfortable if the application created more than one dispatcher (or even more than ten): one had to remember to configure the queue_traits for each of them. Even worse if the application uses ready-made library of agents inside of which its own dispatcher instances are created. Likely this library doesn't provide an opportunity to configure queue_traits for its own dispatchers but uses the default parameters of queue_traits, i.e. `combined_lock_factory` will be used. In version 5.5.18 this defect was fixed by adding the feature to set the default lock_factories for the whole SObjectizer Environment. That is if all application's dispatchers in their queues should use only `simple_lock_factory` by default this can be configured via `environment_params_t` since version 5.5.18. For example: ~~~~~{.cpp} so_5::launch( []( so_5::environment_t & env ) { ... // Some initial actions. }, []( so_5::environment_params_t & params ) { // Use simple_lock_factory for event queues by default. params.queue_locks_defaults_manager( so_5::make_defaults_manager_for_simple_locks() ); ... } ); ~~~~~ After that for dispatcher creation the following scenario is used: 1. If the developer in queue_traits set the specific lock_factory for dispatcher then when creating queues of this dispatcher the specific lock_factory will be used. Global parameters of SObjectizer Environment are ignored. 2. Ohterwise when creating queues of this dispatcher the lock_factory will be used from SObjectizer Environment parameters. In the example above it is `simple_lock_factory`. Herewith the application doesn't care where particular the dispatcher is created: in the application code or in the third-party library. If the lock_factory is not set explicitly when creating dispatcher then the lock_factory will be used by default. If the developer didn't set queue_locks_defaults_manager explicitly during SObjectizer Environment start then `combined_lock_factory` will be used by default. So the default behavior of dispatchers and queues in SObjectizer in version 5.5.18 is not changed. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_18__nested_state_switch_disabled.md000066400000000000000000000047031523065210100272240ustar00rootroot00000000000000# so-5.5.18: Nested state switch is explicitly disabled {#so_5_5_18__nested_state_switch} After adding handlers of entry/exit in/out agents state to SObjectizer there is an opportunity to make an error: in `on_enter/on_exit` the developer can initiate the switch-over to another state. Something similar to: ~~~~~{.cpp} class demo : public so_5::agent_t { state_t st_one{ this }; state_t st_two{ this }; ... virtual void so_define_agent() override { st_one.on_enter( [this]{ st_two.activate(); } ); ... } }; ~~~~~ Such behavior is wrong and can lead to serious problems: First, there can be wrong call order of `on_enter/on_exit`. Because states can be nested, the procedure of changing state can lead to calling of several different `on_enter/on_exit` handlers. Repeated state changing doesnt interrupt this chain but only "embeds" one more chain of calls. When the new "embedded" chain finishes working the execution will be returned to the original chain of events which cant be treated as actual. Second, such state changing doesnt lead to the desired result. If the transfer procedure to state A is being performed and a nested switch to state B is initiated then the agent will have state A after completion of both operations. It is because the transfer to state B doesnt interrupt the transfer procedure to state A, but performs sort of "embedding" to this transfer. Third, such nested state changes can lead to infinite loop. For example, `on_enter` for A performs changing to B while `on_enter` for B performs changing to A. In this case the application will abort due to stack overflow. An attempt to change agent's state inside `on_enter/on_exit` is the developer's mistake. One shouldnt change agents state inside `on_enter/on_exit`. But before version 5.5.18 SObjectizer didnt consider this case. Since version 5.5.18 when changing agents state SObjectizer verifies if such an operation is already being run. If there is such an operation then the exception is generated. So now SObjectizer will explicitly notify the developer if he tries to change agents state in `on_enter/on_exit` handlers. **Note**: `on_enter/on_exit` handlers should be noexcept-functions. It means that if inside such handler SObjectizer throws an exception due to attempt of nested state change the application is likely to abort calling `std::terminate` (due to attempt of throwing an exception from noexcept-function). So now the code above will crash the application. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_18__new_msg_distribution_started_finished.md000066400000000000000000000102331523065210100311620ustar00rootroot00000000000000# so-5.5.18: New messages distribution_started and distribution_finished {#so_5_5_18__new_msg_distrib_started_finished} [The feature of getting monitoring information](https://sourceforge.net/p/sobjectizer/wiki/so-5.5%20In-depth%20-%20Run-Time%20Monitoring/) about the events inside SObjectizer Environment was implemented long ago since version 5.5.4. The mechanism works on clock cycle: user is able to set the rate of receiving the monitoring information. For example, once in 250ms. This means that every 250ms SObjectizer will send message pack with current values to special mbox. The minor defect was found out in this mechanism with time: for the series of processing monitoring information scenarios it was very desirable to know that the next cycle of data distribution started or finished. In version 5.5.18 this defect was fixed. New types of messages: `distribution_started` and `distribution_finished` were added to the `so_5::stats::messages` namespace. The message `distribution_started` is sent in the beginning of each cycle of monitoring information distribution. Actually its the first message being sent by stats_controller in the cycle begin. Then follow messages from data sources inside SObjectizer Environment. The cycle is closed by `distribution_finished` message. This is the last message and there will not be any more messages related to the closed cycle. These two messages simplify the scenarios processing which require information about the moments of cycle begin/end. For example to update information on the charts or to fixate the transaction with new data in DB. Below is the example of agent that initiates the distribution of monitoring information 3 times per second and then processes only messages with information about request queues length on worker threads running in SObjectizer Environment dispatchers (to filter the required messages the [delivery_filters](https://sourceforge.net/p/sobjectizer/wiki/so-5.5.5%20Message%20delivery%20filters/) mechanism is used). ~~~~~{.cpp} // Agent for receiving run-time monitoring information. class a_stats_listener_t : public so_5::agent_t { public : a_stats_listener_t( // Environment to work in. context_t ctx, // Address of logger. so_5::mbox_t logger ) : so_5::agent_t( ctx ) , m_logger( std::move( logger ) ) {} virtual void so_define_agent() override { using namespace so_5::stats; auto & controller = so_environment().stats_controller(); // Set up a filter for messages with run-time monitoring information. so_set_delivery_filter( // Message box to which delivery filter must be set. controller.mbox(), // Delivery predicate. []( const messages::quantity< std::size_t > & msg ) { // Process only messages related to dispatcher's queue sizes. return suffixes::work_thread_queue_size() == msg.m_suffix; } ); // We must receive messages from run-time monitor. so_default_state() .event( // This is mbox to that run-time statistic will be sent. controller.mbox(), &a_stats_listener_t::evt_quantity ) .event( controller.mbox(), [this]( const messages::distribution_started & ) { so_5::send< log_message >( m_logger, "--- DISTRIBUTION STARTED ---" ); } ) .event( controller.mbox(), [this]( const messages::distribution_finished & ) { so_5::send< log_message >( m_logger, "--- DISTRIBUTION FINISHED ---" ); } ); } virtual void so_evt_start() override { // Change the speed of run-time monitor updates. so_environment().stats_controller().set_distribution_period( std::chrono::milliseconds( 330 ) ); // Turn the run-timer monitoring on. so_environment().stats_controller().turn_on(); } private : const so_5::mbox_t m_logger; void evt_quantity( const so_5::stats::messages::quantity< std::size_t > & evt ) { std::ostringstream ss; ss << "stats: '" << evt.m_prefix << evt.m_suffix << "': " << evt.m_value; so_5::send< log_message >( m_logger, ss.str() ); } }; ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_18__overload_mchains_and_timers.md000066400000000000000000000017421523065210100270540ustar00rootroot00000000000000# so-5.5.18: Overloaded mchains and delayed/periodic messages delivery {#so_5_5_18__overloaded_mchains_and_timers} Since v.5.5.18 there is a new behavior in delivery of delayed and periodic messages to mchains if these mchains are overloaded: - if mchains is configured for waiting if is it full then this waiting is not preformed for delayed/periodic messages. It is because the sending of such messages is performed on context of timer thread. All operations on this context must be done as fast as possible. It means impossibility to wait for some time on overloaded mchain. It means that the overflow reaction for that mchain will be performed immediately; - the so_5::mchain_props::overflow_reaction_t::throw_exception is replaced to so_5::mchain_props::overflow_reaction::drop_newest. It means a slient drop of delayed/peridic message without raising any exception. It is because the context of timer thread is a special context: no exceptions should be thrown here. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_18__work_thread_activity_tracking.md000066400000000000000000000142771523065210100274500ustar00rootroot00000000000000# so-5.5.18: Work thread activity tracking {#so_5_5_18__work_thread_activity_tracking} Amount of monitoring information from dispatchers has been expanded in v.5.5.18. It is now possible to receive information about time spent inside event-handlers and time spent during waiting for next event. Collecting of such information must be turned on explicitely. This information is not collected by default to avoid negative impact on dispatchers' performance. Work thread activity tracking can be turned on for an individual dispatcher: ~~~~~{.cpp} using namespace so_5::disp::active_obj; auto disp = create_private_disp( env, "my_disp", // Collecting of work thread activity is turned on // in the dispatcher's parameters. disp_params_t{}.turn_work_thread_activity_tracking_on() ); ~~~~~ It can be also done for the whole SObjectizer Environment: ~~~~~{.cpp} so_5::launch( []( so_5::environment_t & env ) { ... // some intitial actions. } []( so_5::environment_params_t & params ) { // Turn work thread activity statistics collection on explicitely. params.turn_work_thread_activity_tracking_on(); ... } ); ~~~~~ In that case collecting of information of work thread activity will be turned on for all dispatchers inside SObjectizer Environment. If it is not appropriate for some dispatcher then thread activity tracking can be turned off in parameters of that dispatcher: ~~~~~{.cpp} so_5::launch( []( so_5::environment_t & env ) { ... // Some initial actions. // Create dispatcher and turn off work thread activity tracking for it. auto my_disp = so_5::disp::one_thread::create_private_disp( env, "my_disp", so_5::disp::one_thread::disp_params_t{} .turn_work_thread_activity_tracking_off() ); ... []( so_5::environment_params_t & params ) { // Work thread activity tracking will be turned on for the whole environment. params.turn_work_thread_activity_tracking_on(); ... } ); ~~~~~ Distribution of information about work thread activity is done the usual way -- by messages which are sent to message box of stats_collector. These messages have type `work_thread_activity` which is defined in `so_5::stats::messages` namespace. ~~~~~{.cpp} class activity_listener final : public so_5::agent_t { public : activity_listener( context_t ctx ) : so_5::agent_t( ctx ) { so_default_state().event( so_environment().stats_controller().mbox(), &activity_listener::evt_monitor_activity ); } private : void evt_monitor_activity( const so_5::stats::messages::work_thread_activity & evt ) { namespace stats = so_5::stats; std::cout << evt.m_prefix << evt.m_suffix << " [" << evt.m_thread_id << "] ->\n" << " working: " << evt.m_stats.m_working_stats << "\n" << " waiting: " << evt.m_stats.m_waiting_stats << std::endl; ... } }; ~~~~~ There ara well known fields `m_prefix` and `m_suffix` inside `work_thread_activity`. Also there are additional fields: * `m_thread_id` with ID of working thread (activity stats information is related to thread with this ID); * `m_working_stats` with info about time spent in event-handlers; * `m_waiting_stats` with info about time spent on waiting for new events. Fields `m_working_stats` and `m_waiting_stats` have type `so_5::stats::activity_stats_t`: ~~~~~{.cpp} struct activity_stats_t { // Count of events in that period of time. std::uint_fast64_t m_count{}; // Total time spent for events in that period of time. duration_t m_total_time{}; // Average time for one event. duration_t m_avg_time{}; }; ~~~~~ Where `duration_t` is `std::chrono::high_resolution_clock::duration` or `std::chrono::steady_lock::duration`. Message `work_thread_activity` allows to know how many events have been handled by a work thread (from the very beginning of its work), how much time have been spent inside event-handlers (from the very beginning of its work), average time of event-handler's work. Similary for waiting of new events (e.g. count of waitings, total time spent in waiting, average waiting time). This is a small example of how that information can looks like: ~~~~~ disp/atp/0x2b7fb70/threads.count: 4 disp/atp/0x2b7fb70/agent.count: 5 disp/atp/0x2b7fb70/wt-14/thread.activity [14] -> working: [count=5651;total=3.86549ms;avg=0.000655ms] waiting: [count=5652;total=492.831ms;avg=3.60857ms] disp/atp/0x2b7fb70/wt-15/thread.activity [15] -> working: [count=62394;total=40.4094ms;avg=0.000644ms] waiting: [count=62395;total=363.346ms;avg=0.201897ms] disp/atp/0x2b7fb70/wt-16/thread.activity [16] -> working: [count=69073;total=46.2095ms;avg=0.000637ms] waiting: [count=69073;total=361.668ms;avg=0.000813ms] disp/atp/0x2b7fb70/wt-17/thread.activity [17] -> working: [count=80587;total=52.904ms;avg=0.000656ms] waiting: [count=80588;total=325.136ms;avg=0.003536ms] disp/atp/0x2b7fb70/cq/__so5_au...e_2__/agent.count: 5 disp/atp/0x2b7fb70/cq/__so5_au...e_2__/demands.count: 4 ~~~~~ This is a dump of monitoring info for adv_thread_pool-dispatcher with four work threads. A single coop with five agents inside works on that dispatcher. It is possible to see info for every work thread (there is thread ID in square brackets). For this particular case it is possible to see that work threads spend more time during waiting for new events that for event-handling. Some words about performance impact. It highly depends of dispatcher's type and load profile. For one_thread-dispatcher and heavy message-stream this impact is negligible. But for active_obj-dispatcher with occasional messages performance can degraded for 3-4 times. Becase of that it impossible to provide some numbers of cummulative performance loss: there is too big variety in results. In any cases this mechanism is regarded as auxilary. It indended to debugging and profiling. Because of that it is necessary to check performance impact of work thread activity tracking on your application. And only then the decision about turning that tracking on in production evironment must be taken. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_19_environment_infrastructures.md000066400000000000000000000246621523065210100270710ustar00rootroot00000000000000# so-5.5.19 Environment Infrastructures {#so_5_5_19__environment_infrastructures} # Introduction Every instance of SObjectizer's environment needs some infrastructure for handling several important tasks like: * completion of cooperation deregistration procedure; * dealing with timers; * dispatching events for agents which are bound to the default dispatcher. Until v.5.5.19 there was only one type of SObjectizer's environment infrastructure: the multithreaded one. It means that SObjectizer environment creates at least three work thread: one for the default dispatcher, one for timer (it called timer thread) and yet another for completion of deregister operations. These three theads are not a big overhead for complex multithreaded applications where dozens of work threads are used. But there are cases where: * usage of SObjectizer is valuable, but * it is better to use just one work thread for the whole application For example: a small application which works on a tiny computer like Orange PI and connects to MQTT broker, publishes some messages into several topics and then waits for some messages from other topics. For such cases creation of three additional threads for the needs of SObjectizer's environment infrastructure is just an overkill. It is better to have single-threaded SObjectizer environment infrastructures. Since v.5.5.19 there is a possibilty to specify which kind of environment infrastructure will be used by SObjectizer environment. And there are three implementations of infrastructure: * default multi threaded infrastructure. It is old one which was the single infrastructure in older SObjectizer's versions; * thread-safe single threaded infrastructure. It does all SObjectizer-specific actions on single thread. But allows to interact with SObjectizer environment from different threads inside an application; * not thread-safe single threaded infrastructure. It assumes that there is only one thread in the whole application and does all SObjectizer-related activities on it. # How to Specify Environment Infrastructure A classical multi threaded infrastructure is used by default. If it is necessary to use another type of environment infrastructure the required infrastructure must be set via `so_5::environment_params_t` before start of SObjectizer environment. For example, when SObjectizer is started by `so_5::launch`: ~~~~~{.cpp} so_5::launch([](so_5::environment_t & env) { ... // Starting actions. }, [](so_5::environment_params_t & params) { // Change environment infrastructure. params.infrastructure_factory( // Simple thread-safe single threaded infrastructure will be used. so_5::env_infrastructures::simple_mtsafe::factory()); }); ~~~~~ Or, in the case of `so_5::wrapped_env_t`: ~~~~~{.cpp} so_5::wrapped_env_t sobj([](so_5::environment_t & env) { ... // Starting actions. }, [](so_5::environment_params_t & params) { // Change environment infrastructure. params.infrastructure_factory( // Simple thread-safe single threaded infrastructure will be used. so_5::env_infrastructures::simple_mtsafe::factory()); }); ~~~~~ Please note that environment infrastructure can't be changed after start of SObjectizer environment. # More About Standard Environment Infrastructures ## default_mt It is the classical multi threaded environment infrastructure which is used by default. If there is a need to set it up manually then `factory()` function from `so_5::env_infrastructures::default_mt` namespace must be used. For example: ~~~~~{.cpp} so_5::environment_params_t make_env_params(const app_config & cfg) { so_5::environment_params_t result; // Single threaded infrastructure is necessary for most cases. result.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory()); ... // Some other tuning. if(cfg.need_more_threads()) // In that case default multi threaded environment must be used. result.infrastructre_factory( so_5::env_infrastructures::default_mt::factory()); ... return result; } ~~~~~ Please note that in the case of `default_mt` environment infrastructure all values from `environment_params_t` are used for tuning environment behavior. When single threaded infrastructures are used then some of values from `environment_params_t` can be ignored or superseded by params from an infrastructure factory. ## simple_mtsafe Simple thread safe single threaded environment infrastructure is intended to be used in multi-threaded application where it is necessary to reduce overhead from SObjectizer-related things. For example it can be a simple GUI application in which a single separate thread will be dedicated to all agents and SObjectizer's activities. This type of environment infrastructure provides full thread safety for SObjectizer's environment. It means that new cooperations can be created or some messages can be sent from other application threads. Something like: ~~~~~{.cpp} int main() { so_5::mbox_t mbox; // Mbox of the main agent will be stored here. // Launch SObjectizer on the context of new dedicated thread. // This thread is created and destroyed by wrapped_env_t itself. so_5::wrapped_env_t sobj([&](so_5::environment_t & env) { // Create a coop with main agent inside. env.introduce_coop([&](so_5::coop_t & coop) { mbox = coop.make_agent(...)->so_direct_mbox(); ... }); }, [](so_5::environment_params_t & params) { // All SObjectizer-related activities will be performed // on the context of the single work thread. params.infrastructure_factory( so_5::env_infrastructures::simple_mtsafe::factory()); }); ... // Now we can send messages to the main agent. so_5::send(mbox, ...); ... // We can also create new coops. sobj.environment().introduce_coop("new_coop", [](so_5::coop_t & coop) { coop.make_agent(...); ... }); ... // We can also deregister existing coops. sobj.environment().deregister_coop("new_coop", so_5::dereg_reason::normal); ... } ~~~~~ Thread safety of this infrastructure type allows even to launch additional dispatchers (public or private). It is normal because this infrastructure type just reduced SObjectizer-related overhead, but not reduced SObjectizer features. It means that default dispatcher, timers and final deregistration steps will be performed on the context of a single work thread, but additional work threads from additional dispatchers can also be used by application. ### Selecting of Timer Implementation Because `simple_mtsafe` environment infrastructure doesn't use timer thread then timer thread settings in `environment_params_t` will just ignored. It is possible to set a timer implementation to be used via parameters to `factory()` function. For example: ~~~~~{.cpp} so_5::launch([](so_5::environment_t & env) {...}, [](so_5::environment_params_t & params) { namespace st_env = so_5::env_infrastructures::simple_mtsafe; params.infrastructure_factory( st_env::factory( // Timer_list implementation will be used. st_env::params_t{}.timer_manager(so_5::timer_list_manager_factory()))); }); ~~~~~ ### Long-running Init Actions Init actions for SObjectizer's environment should not be long-running. It is because all SObjectizer-related activities (like timers handling) will be started only after exit from init function. Do not do long-lasting actions like interaction with user in init function for `simple_mtsafe` environment. It will block SObjectizer activities. ## simple_not_mtsafe Simple not thread safe single threaded environment infrastructure is intended to be used in single threaded applications where the main application thread must be used for everything. For example in a small network unitilities like custom version of ping or traceroute programs. Usage of `simple_not_mtsafe` infrastructure has several limitations and specific features: There must not be any attempts to use SObjectizer environment outside of work thread where SObjectizer has been launched. It is because there is no thread safety and attempt to create a new coop or to send a new message from outside can lead to data corruption and other errors. SObjectizer environment automatically finishes its work when there is no any waiting events or timers. It means that SObjectizer environment will be automatically shut down is the following scenario: ~~~~~{.cpp} int main() { so_5::launch([](so_5::environment_t & env) { env.intruduce_coop([](so_5::coop_t & coop) { coop.define_agent().on_start([]{ std::cout << "Hello, World!" << std::endl; }); }); }, [](so_5::environment_params_t & params) { params.infrastructure_factory( so_5::env_infrastructures::simple_not_mtsafe::factory()); }); // SObjectizer will finish its work just after printing "Hello, World!" // to the standard output stream. } ~~~~~ It is because there is no a way to initiate some activity from outside of SObjectizer environment. Because this infrastructure is not thread safe there is no sense to use `so_5::wrapped_env_t` or to create additional dispatchers when `simple_not_mtsafe` is used. ### Selecting of Timer Implementation Because `simple_not_mtsafe` environment infrastructure doesn't use timer thread then timer thread settings in `environment_params_t` will just ignored. It is possible to set a timer implementation to be used via parameters to `factory()` function. For example: ~~~~~{.cpp} so_5::launch([](so_5::environment_t & env) {...}, [](so_5::environment_params_t & params) { namespace st_env = so_5::env_infrastructures::simple_not_mtsafe; params.infrastructure_factory( st_env::factory( // Timer_list implementation will be used. st_env::params_t{}.timer_manager(so_5::timer_list_manager_factory()))); }); ~~~~~ ### Long-running Init Actions Init actions for SObjectizer's environment should not be long-running. It is because all SObjectizer-related activities (like timers handling) will be started only after exit from init function. Do not do long-lasting actions like interaction with user in init function for `simple_not_mtsafe` environment. It will block SObjectizer activities. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_19_has_subscription.md000066400000000000000000000045161523065210100245550ustar00rootroot00000000000000# so-5.5.19 Checking the Presence of a Subscription {#so_5_5_19__has_subscription} Sometimes it can be necessary to check the presense of a subscription. For example there can be an observer agent which receives messages with mboxes inside. This observer must make subscription for messages from that mbox: ~~~~~{.cpp} struct start_listen { const so_5::mbox_t from_; ... }; class observer : public so_5::agent_t { ... void on_start_listen(mhood_t cmd) { so_subscribe(cmd->from_).event(&observer::on_data); } void on_data(mhood_t cmd) {...} }; ~~~~~ The problem is: if `start_listen` contain a mbox to which the observer is already subscribed the an exception will be throw in in `so_subscribe`. Since v.5.5.19.5 there is a way to check presence of subscription. This check can be done by `so_5::agent_t::so_has_subscription` method: ~~~~~{.cpp} class observer : public so_5::agent_t { ... void on_start_listen(mhood_t cmd) { // Make subscription only if there is no subscription yet. if(!so_has_subscription(cmd->from_, &obsever::on_data)) so_subscribe(cmd->from_).event(&observer::on_data); } void on_data(mhood_t cmd) {...} }; ~~~~~ Method `so_has_subscription` has several formats: ~~~~~{.cpp} // Check subscription for message Msg in the default state. so_has_subscription(mbox); // The same check as above but the state is specified explicitly. so_has_subscription(mbox, so_default_state()); // Check subscription for message which is handled by // event-handler. Subscription is checked for the default state. so_has_subscription(mbox, &my_agent::event_handler); // The same check as above but the state is specified explicitly. so_has_subscription(mbox, so_default_state(), &my_agent::event_handler); ~~~~~ Class `so_5::state_t` now also has a couple of `has_subscription` methods which allows to check the presence of a subscription: ~~~~~{.cpp} class observer : public so_5::agent_t { state_t st_working{this}; ... void on_start_listen(mhood_t cmd) { // Make subscription only if there is no subscription yet. if(!st_working.has_subscription(cmd->from_, &obsever::on_data)) st_working.event(cmd->from_, &observer::on_data); } void on_data(mhood_t cmd) {...} }; ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_19_make_custom_mbox.md000066400000000000000000000040171523065210100245260ustar00rootroot00000000000000# so-5.5.19 Custom mbox Creation Procedure {#so_5_5_19__custom_mbox} Sometimes it is necessary to create and use a custom implementation of `so_5::abstract_message_box_t`. For example, so-5-extra project contains several custom implementations of mboxes inside. But there was a problem with custom mboxes: every mbox must have an unique ID (this ID is a long integer represented by `so_5::mbox_id_t` type). These IDs are distributed by SObjectizer Environment. If someone will try to use its own values for mbox ID there can be a conflict between IDs from SObjectizer Environment and someone's IDs. Another problem is related to message tracing mechanism. Mboxes must take into account message tracing stuff and must implement message delivery tracing if such tracing is used. It means that custom mbox should receive a pointer to optional `so_5::msg_tracing::tracer_t` object. Since v.5.5.19.2 there is a unified custom mbox creation procedure. It is a template method `so_5::environment_t::make_custom_mbox()`. This template method receives a lambda-function or a functional object which is called inside `make_custom_mbox()`. A single agrument of type `so_5::mbox_creation_data_t` is passed to that lambda-function. That argument contains mbox ID generated for the new mbox by SObjectizer Environment. It also contains an optional pointer to message tracer. The lambda-function should return a new message box (in for of `so_5::mbox_t`). For example: ~~~~~{.cpp} // A custom implementation of message box. class my_special_mbox : public so_5::abstract_message_box_t { public: // Constructor receives several important arguments... my_special_mbox( // Unique ID for that mbox. so_5::mbox_id_t id, // An optional message tracer. so_5::msg_tracing::tracer_t * tracer) {...} ... }; // Create an instance of my_special_mbox. auto mbox = so_environment().make_custom_mbox( [](const so_5::mbox_creation_data_t & data) { return so_5::mbox_t(new my_special_mbox(data.m_id, data.m_tracer)); }); ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_19_mutable_messages.md000066400000000000000000000312111523065210100245060ustar00rootroot00000000000000# so-5.5.19 Mutable Messages {#so_5_5_19__mutable_messages} # Until v.5.5.19 All Messages Were Immutable From the very first version of SObjectizer-5 all messages inside a SObjectizer-application were immutable. There was no possibility to receve a message via non-const reference. Because of that all event handlers had one of the following formats: ~~~~~{.cpp} return_type event_handler(const message & msg); return_type event_handler(mhood_t cmd); return_type event_handler(const mhood_t & cmd); ~~~~~ The main reason for immutability was the way of message distribution: there was only one type of mbox in the first version of SObjectizer-5 -- multi-producer/multi-consumer one. It means that when a message has been sent to such mbox there can be several subscribers which can receive that message at the same time on the context of different work threads. ## When Immutability Is Not A Good Thing? Immutability of messages is a very good thing in 1:N or N:M interaction. In such cases you can't know how many receivers will handle your message. You also can't know will some receiver redirect your message or store it to process later. Immutability of messages makes such complex scenarios possible. But there can be cases where only 1:1 interaction is necessary. For example if agents work as a pipeline: a message goes from one agent to another and there is only one receiver for the message. In some of such cases immutability can create difficulties if a message passing through a pipeline must be modified. It can be a very big message and it is not efficient to create a modifyable copy of it for every agent in the pipeline. ### The Workaround. Very Dangerous Workaround If all messages are immutable the only way to pass mutable value inside a message is usage of `mutable` keyword. For example, something like: ~~~~~{.cpp} struct heavy_message : public so_5::message_t { mutable std::array payload_; ... }; ... class agent_in_pipeline : public so_5::agent_t { ... void on_heavy_message(mhood_t cmd) { cmd->payload_[10] = 0xf7; cmd->payload_[11] = 0xf8; ... } }; ~~~~~ But this is a very dangerous approach because there is no a guarantee that the message sent will be received and processed only by one receiver. When you call `send` function like this: ~~~~~{.cpp} so_5::send(next_stage_mbox, ...); ~~~~~ you can pass MPMC mbox as `next_stage_mbox` and your message can be received by several receivers at the same time. It would lead to errors which are very hard to detect and repair. ## Real Solutions: Mutable And Immutable Messages Since v.5.5.19 there is a real solution for such problem in SObjectizer: there are immutable messages and mutable ones. Immutable messages can be used like in previous SObjectizer's versions (moreover: all messages are still immutable by default). But there is a possibility to send a mutable message. And SObjectizer guarantees that mutable message can be sent only to the single subscriber. Thus mutable message can't be sent into MPMC mbox. Only MPSC mboxes and mchains can be used as the destination to a mutable message. Mutable message can be redirected to another MPSC mbox or mchain. In this case mutable message behaves just like unique_ptr: you can pass it to someone else but you lose your pointer to it. Mutable message can be converted to immutable message. But once mutable message lost its mutability and became immutable the mutability can't be returned back. # More Details About Mutable Message ## Sending Of Mutable Messages A message of type M can be sent as immutable message as well as mutable one. To send an instance of M as mutable message the special type wrapper `mutable_msg` must be used: ~~~~~{.cpp} struct classical_message final : public so_5::message_t {...}; struct user_type_message final {...}; // Sending classical_message as mutable message. so_5::send< so_5::mutable_msg >(dest, ...); // Sending classical_message as immutable. so_5::send< classical_message >(dest, ...); // Sending user_type_message as mutable message. so_5::send< so_5::mutable_msg >(dest, ...); // Sending user_type_message as immutable. so_5::send< user_type_message >(dest, ...); ~~~~~ There is also another type wrapper `immutable_msg` which can be used for sending of immutable messages: ~~~~~{.cpp} // These all are sending of immutable messages: so_5::send< so_5::immutable_msg >(dest, ...); so_5::send< classical_message >(dest, ...); so_5::send< so_5::immutable_msg >(dest, ...); so_5::send< user_type_message >(dest, ...); ~~~~~ Please note that in `send>(dest,...)` the `dest` must be a MPSC mbox or mchain. Otherwise the exception will be thrown: ~~~~~{.cpp} class some_agent : public so_5::agent_t { public : ... virtual void so_evt_start() override { // Sending of mutable message into direct mbox (MPSC mbox). so_5::send>(*this, ...); // Sending of mutable message into mchain. auto ch = create_mchain(so_environment()); so_5::send>(ch, ...); // An attempt to send mutable message into MPMC mbox. // An exception will be thrown in send(). auto mbox = so_environment().create_mbox(); so_5::send>(mbox, ...); // DON'T DO THAT!!! } }; ~~~~~ ## Receiving Of Mutable Messages To receive a mutable message an event handler must have the following format: ~~~~~{.cpp} return_type event_handler(so_5::mhood_t>); ~~~~~ or ~~~~~{.cpp} return_type event_handler(so_5::mutable_mhood_t); ~~~~~ where `M` is a message type. For example: ~~~~~{.cpp} class demo final : public so_5::agent_t { struct A final : public so_5::message_t { ... }; struct B final { ... }; public : ... virtual void so_define_agent() override { so_subscribe_self().event(&demo::on_A); so_subscribe_self().event([](mutable_mhood_t cmd){...}); } ... private : void on_A(mhood_t> cmd) {...} }; ~~~~~ Note that an agent can have different event handlers for immutable and mutable message of the same type. For example: ~~~~~{.cpp} class two_handlers final : public so_5::agent_t { struct M final {}; public : two_handlers(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self() .event(&two_handlers::on_immutable_M) .event(&two_handlers::on_mutable_M); } virtual void so_evt_start() override { so_5::send(*this); // Immutable message is sent. so_5::send>(*this); // Mutable message is sent. } private : void on_immutable_M(mhood_t) { std::cout << "on immutable" << std::endl; } void on_mutable_M(mhood_t>) { std::cout << "on mutable" << std::endl; } }; ~~~~~ ## Mutable Service Requests A mutable message can be used for service requests (e.g. for synchronous interactions). For example: ~~~~~{.cpp} class service_provider final : public so_5::agent_t { public : service_provider(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self().event([](mutable_mhood_t cmd) { *cmd = "<" + *cmd + ">"; // Modify incoming message. return std::move(*cmd); // Return modified value. }); } ... }; ... so_5::mbox_t provider_mbox = ...; auto r = so_5::request_value>( provider_mbox, so_5::infinite_wait, "hello"); ~~~~~ Note that service request with mutable message can't be sent into MPMC mbox: only MPSC mboxes or mchains can be used for `request_value` and `request_future` with mutable messages as request's parameter. ## Redirecting Of Mutable Messages When mutable messages are used for interaction of agents in a pipeline then there is a need for redirection of the same message object to the next agent in the pipeline. Special versions of `send` functions must be used for this task: ~~~~~{.cpp} class pipeline_part final : public so_5::agent_t { const so_5::mbox_t next_; public : pipeline_part(context_t ctx, so_5::mbox_t next) : so_5::agent_t(std::move(ctx)) , next_(std::move(next)) {} ... private : void event_handler(mutable_mhood_t cmd) { // Modify message. cmd->some_data = some_new_value; ... // Redirect the message instance to next stage of the pipeline. so_5::send(next_, std::move(cmd)); // NOTE: cmd is a nullptr now. It can't be used anymore. ... } }; ~~~~~ There are two things which must be mentioned: * there is no need to specify a type of message to be redirected: a call in form `send(dest,...)` is used instead of `send(dest,...)`. Type of redirected message is infered automatically; * content of `mutable_mhood_t` is moved into `send`. The source message hood object becomes a nullptr and can't be used anymore. In this case mutable message hood works like `std::unique_ptr`. Functions `request_value` and `request_future` also have new overloads and accept message hood as a parameter. It means that received message can be redirected as service request. For example: ~~~~~{.cpp} class pipeline_part final : public so_5::agent_t { const so_5::mbox_t next_; const so_5::mbox_t svc_provider_; public : pipeline_part(context_t ctx, so_5::mbox_t next, so_5::mbox_t svc_provider) : so_5::agent_t(std::move(ctx)) , next_(std::move(next)) , svc_provider_(std::move(svc_provider)) {} ... private : void event_handler(mutable_mhood_t cmd) { // Modify message. cmd->some_data = some_new_value; ... // Redirect the message instance as mutable service request. // Note that only result type is specified for request_value call. auto svc_result = so_5::request_value( svc_provider_, std::move(cmd)); // NOTE: cmd is a nullptr now. It can't be used anymore. ... } }; ~~~~~ ## Conversion Into An Immutable Message Please note that when a mutable message is received via `mutable_mhood_t` and then redirected via `send` or `request_value/future` then redirected message will also be a mutable message. It means that redirected message can be sent only to one subscriber and can be handled only via `mutable_mhood_t`. Sometimes it is necessary to remove mutability of a message and send the message as immutable one. It can be done via `to_immutable` helper function. For example: ~~~~~{.cpp} void some_agent::on_some_message(mutable_mhood_t cmd) { ... // Some actions with the content of cmd. // Now the mutable message will be resend as immutable one. so_5::send(another_mbox, so_5::to_immutable(std::move(cmd))); // NOTE: cmd is a nullptr now. It can't be used anymore. ... } ~~~~~ Helper function `to_immutable` converts its argument from `mhood_t>` into `mhood_t>` and returns message hood to immutable message. This new message hood can be used as parameter for `send`, `request_value` or `receive_future`. Old mutable message hood becomes a nullptr and can't be used anymore. Note: a mutable message can be converted to immutable message only once. An immutable message can't be converted into mutable one. ## Signals Cannot Be Mutable Signals can't be sent nor received as mutable. It means that an attempt to write something like: ~~~~~{.cpp} struct my_signal final : public so_5::signal_t {}; ... void some_agent::on_my_signal(mutable_mhood_t) {...} ... so_5::send>(dest); ~~~~~ will lead to compile-time errors because `mutable_msg` can't be used with signals. ## Mutable Messages And Timers Mutable messages can't be sent as periodic messages. It means that `send_periodic` can be used with `mutable_msg` only if a `period` parameter is zero. For example: ~~~~~{.cpp} // It is a valid call: auto timer = so_5::send_periodic>( so_environment(), dest_mbox, std::chrono::milliseconds(200), // Delay before message appearance. std::chrono::milliseconds::zero(), // Period is zero. ...); // It ins't a valid call. An exception will be thrown at run-time. auto timer = so_5::send_periodic>( so_environment(), dest_mbox, std::chrono::milliseconds(200), // Delay before message appearance. std::chrono::milliseconds(150), // Period is not zero. ...); ~~~~~ Helper function `so_5::send_delayed` can be used for sending delayed mutable message (but destination must be MPSC mbox or mchain as usual for mutable messages). sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_19_new_format_of_send.md000066400000000000000000000046721523065210100250370ustar00rootroot00000000000000# so-5.5.19 New Format of Send Functions to Simplify Message Redirection {#so_5_5_19__new_format_of_send_functions} New overloads for `send`, `send_delayed`, `send_periodic`, `request_future` and `request_value` have been added in v.5.5.19. These overloads simplify redirection of messages. They accept `mhood_t`, `mhood_t>` and `mhood_t>` arguments. For example: ~~~~~{.cpp} class demo final : public so_5::agent_t { ... void first_event_handler(mhood_t cmd) { // Redirection of immutable message. // After call to `send` cmd still contains valid pointer to the message. so_5::send(another_mbox, cmd); } void second_event_handler(mhood_t> cmd) { // Redirection of immutable message as delayed message. // After call to `send` cmd still contains valid pointer to the message. so_5::send_delayed(so_environment(), another_mbox, std::chrono::milliseconds(100), cmd); } void third_event_handler(mhood_t cmd) { // Redirection of immutable message as periodic message. // After call to `send` cmd still contains valid pointer to the message. so_5::send_periodic(so_environment(), another_mbox, std::chrono::milliseconds(100), std::chrono::milliseconds(150), cmd); } void forth_event_handler(mutable_mhood_t cmd) { // Redirection of mutable message. // After call to `send` cmd becomes a nullptr. so_5::send(another_mbox, std::move(cmd)); } void fifth_event_handler(mutable_mhood_t cmd) { // Redirection of mutable message as delayed message. // After call to `send` cmd becomes a nullptr. so_5::send_delayed(so_environment(), another_mbox, std::chrono::milliseconds(100), std::move(cmd)); } void sixth_event_handler(mhood_t cmd) { // Redirection of immutable message as service request. // After call to `request_future` cmd still contains valid pointer to the message. auto f = so_5::request_future(another_mbox, cmd); } void seventh_event_handler(mhood_t> cmd) { // Redirection of mutable message as service request. // After call to `request_value` cmd becomes a nullptr. auto f = so_5::request_value(another_mbox, so_5::infinite_wait, std::move(cmd)); } }; ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_19_stop_guards.md000066400000000000000000000121661523065210100235300ustar00rootroot00000000000000# so-5.5.19 Stop Guards {#so_5_5_19__stop_guards} [TOC] # The Problem Sometimes an agent can want to prevent the shutdown of SObjectizer for some time. For example if the agent is caching some data in memory and it is necessary to store that data to the disk when the application finishes its work. But there is a problem: if someone calls `so_5::environment_t::stop()` method then there was no possibility to start some rather long operation like data saving. There was just one solution for that problem in versions prior to 5.5.19.2: an application-wise policy to not to call so_5::environment_t::stop() directly. Some shutdown-coordinator can be used instead. For example: an agent-coordinator which receives shutdown-requests, makes shutdown-announces, collects shutdown-acks and performs actual shutdown. This scheme is rather simple but is has a major drawback: every agent in an application must follow this scheme. It is very hard to integrate several different agents libraries into one application if they uses different shutdown prevention schemes. # The Solution Since v.5.5.19.2 SObjectizer provides the basic building blocks for uniform shutdown prevention scheme: stop_guards. Stop_guard is an object which implements `so_5::stop_guard_t` interface. A stop_guard must be set into SObjectizer Environment by method `so_5::environment_t::setup_stop_guard()`. When someone starts the shutdown by calling `so_5::environment_t().stop()` SObjectizer Environment calls `so_5::stop_guard_t::stop()` method for every installed stop_guard and waits until all stop_guards will be removed. An user then must remove all installed stop_guards. When all installed stop_guards are removed SObjectizer Environment starts the shutdown operation. At this point it is impossible to prevent the shutdown (just like in previous versions of SObjectizer). ## An Example The is a very simple example of preventing SObjectizer's shutdown until all cached data will be stored: ~~~~~{.cpp} // An agent which caches data and needs some form of shutdown prevention. class cache_agent : public so_5::agent_t { // A stop guard type for that agent. class shutdown_guard final : public so_5::stop_guard_t { public: // A signal to be sent when shutdown is requested. struct shutdown_requested final : public so_5::signal_t {}; // Constructor receives direct mbox of cache_agent. shutdown_agent(so_5::mbox_t cache) : m_cache(std::move(cache)) {} virtual void stop() noexcept override { // Inform cache_agent about shutdown request. so_5::send(m_cache); } private: const so_5::mbox_t m_cache; }; // A pointer to cache's stop_guard. const so_5::stop_guard_shptr_t m_shutdown_guard; ... public: cache(context_t ctx, ...) : so_5::agent_t(std::move(ctx)) , m_shutdown_guard(std::make_shared(so_direct_mbox())) ... { // Shutdown guard must be installed into SObjectizer Environment. so_environment().setup_stop_guard(m_shutdown_guard); ... } virtual void so_define_agent() override { // Cache agent must be subscribed to shutdown request. so_subscribe_self().event(&cache_agent::on_shutdown_request); ... } private: void on_shutdown_request(mhood_t) { // Shutdown has been requested. Cache data must be stored to disk. ... // Initiate a data storing process. } void on_data_store_completed(mhood_t) { // All data stored. It is time to enable the shutdown operation. // Just remove out stop_guard. so_environment().remove_stop_guard(m_shutdown_guard); } ... }; ~~~~~ ## Some Technical Notes ### Stop_guards Can't Be Installed If stop() Is Already Called It is possible to install stop_guards only before the first call to `so_5::environment_t::stop()` method. If `so_5::environment_t::stop()` is already called all subsequent calls to `so_5::environment_t::setup_stop_guard()` will fail. By default method `setup_stop_guard()` throws an exception if `stop()` is already called. But if such policy is not appropriate it can be changed by using second argument to `setup_stop_guard()`: ~~~~~{.cpp} const auto result = so_environment().setup_stop_guard( my_guard, so_5::stop_guard_t::what_if_stop_in_progress_t::return_negative_result ); if(so_5::stop_guard_t::setup_result_t::ok != result) ... // Handle setup error here. ~~~~~ ### Why stop_guard_t::stop() is noexcept? Method `so_5::stop_guard_t::stop()` shouldn't throw. It is because there is no way to rollback shutdown-specific actions which were done before an exception. It means that shutdown preparation procedure can be broken without a chance to recover. This is the main reason why `so_5::stop_guard_t::stop()` is marked as `noexcept`: it shouldn't throw. If there can be some exceptions inside user's stop_guard `stop()` method which do not prevent shutdown operation they must be caught and handled inside that `stop()` method. Otherwise the only way is to terminate the current application. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_1__autoname_for_cooperations.dox000066400000000000000000000015031523065210100266730ustar00rootroot00000000000000/*! \page so_5_5_1__autoname_for_cooperations so-5.5.1: Automatic generation of cooperation names SObjectizer requires an unique name for every cooperation. But sometimes the selection of unique cooperation name is a very boring task for user. Since v.5.5.1 there is a possibility to specify `so_5::autoname` market to `create_coop` method. In that case SObjectizer RunTime will create an unique name for new cooperation: \code void init( so_5::rt::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); ... auto another_coop = env.create_coop( so_5::autoname, so_5::disp::active_obj::create_disp_binder( "active_obj" ) ); ... } \endcode Note. A SObjectizer generated name will start from `__so_5` prefix. So it is better to avoid using this prefix for user's cooperations. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_1__change_agent_state.dox000066400000000000000000000027531523065210100252420ustar00rootroot00000000000000/*! \page so_5_5_1__change_agent_state so-5.5.1: Two new methods for changing an agent state Two new methods for changing agent's state are added in v.5.5.1. First one uses new so_5::rt::state_t::activate() method. It means that agent's state can be changed this way: \code class my_agent_t : public so_5::rt::agent_t { const so_5::rt::state_t st_normal = so_make_state(); const so_5::rt::state_t st_busy = so_make_state(); ... public : virtual void so_define_agent() override { st_normal.activate(); ... } void evt_request( const msg_start_request & evt ) { st_busy.activate(); ... } void evt_complete_request( const msg_processing_result & evt ) { ... st_normal.activate(); } }; \endcode The second one uses overloaded `>>=` operator: \code class my_agent_t : public so_5::rt::agent_t { const so_5::rt::state_t st_normal = so_make_state(); const so_5::rt::state_t st_busy = so_make_state(); ... public : virtual void so_define_agent() override { this >>= st_normal; ... } void evt_request( const msg_start_request & evt ) { this >>= st_busy; ... } void evt_complete_request( const msg_processing_result & evt ) { ... this >>= st_normal; } }; \endcode The old method so_5::rt::agent_t::so_change_state() is still here. It is not deprecated and will be supported by the future versions of SObjectizer. New methods are introduced to allow a developer to write more concise and readable code. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_1__new_event_method_overloads.dox000066400000000000000000000026601523065210100270440ustar00rootroot00000000000000/*! \page so_5_5_1__new_event_method_overloads so-5.5.1: New overloads for agent subscription methods There are two new `event` methods for subscription of an event to a signal. The first one related to event subscription for ordinary agents. It is possible to subscribe event that way: \code so_subscribe_self().event< msg_get_status >( &my_agent::evt_get_status ); \endcode instead of: \code so_subscribe_self().event( so_5::signal< msg_get_status >, &my_agent::evt_get_status ); \endcode The second one related to ad-hoc agents: \code coop->define_agent().event< msg_get_status >( mbox, [] { return "ready"; } ); \endcode instead of: \code coop->define_agent().event( mbox, so_5::signal< msg_get_status >, [] { return "ready"; } ); \endcode The old form with `so_5::signal` marker is still supported and will be supported in the future versions of SObjectizer. Moreover the `so_5::signal` marker is necessary for event unsubscription: \code // Event subscription. // New way... so_subscribe_self().event< msg_get_status >( &my_agent::evt_get_status ); // Old way (verbose, but still pretty usable)... so_subscribe_self().event( so_5::signal< msg_shutdown >, &my_agent::evt_shutdown ); // Event unsubscription. // Can be done only via so_5::signal marker. so_drop_subscription( so_direct_mbox(), so_5::signal< msg_get_status > ); so_drop_subscription( so_direct_mbox(), so_5::signal< msg_shutdown > ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_1__new_send_function_family.dox000066400000000000000000000055321523065210100265050ustar00rootroot00000000000000/*! \page so_5_5_1__new_send_function_family so-5.5.1: New send function family The traditional way of sending messages and signals have two small drawbacks. 1. There is some level of verbosity in preparing and sending messages: \code // In C++11, where there is no std::make_unique from C++14. auto msg = std::unique_ptr< my_message >( new my_message( some_args ) ); mbox->deliver_message( std::move( msg ) ); \endcode Especially when sending a delayed messages: \code auto msg = std::unique_ptr< my_message >( new my_message( some_args ) ); so_environment().single_timer( std::move( msg ), mbox, pause ); \endcode 2. There is a difference in sending messages and signals: \code // Message sending. mbox->deliver_message( msg ); // Signal sending. mbox->deliver_signal< my_signal >(); \endcode This makes template-based programming a little bit difficult. To simplify SObjectizer usage and reduce verbosity of the code a new family of `so_5::send` functions is introduced. The simple message sending can be written as: \code so_5::send< my_message >( mbox, some_args ); \endcode It is an equivalent of: \code auto msg = std::unique< my_message >( new my_message( some_args ) ); mbox->deliver_message( std::move( msg ) ); \endcode By utilizing variadic templates of C++11 the `some_args` can me a sequence of arguments: \code so_5::send< msg_connect >( mbox, ip_address, ip_port, waiting_time ); \endcode The same form of `so_5::send` is used for signal sending: \code so_5::send< my_signal >( mbox ); \endcode Usually it is necessary to send messages to the agent's direct mbox. So instead of writing: \code so_5::send< msg >( agent.so_direct_mbox(), ... ); \endcode it is possible to use `so_5::send_to_agent` function: \code so_5::send_to_agent< msg >( agent, ... ); \endcode There are `so_5::send_delayed` functions for sending delayed messages/signals: \code // Sending delayed message. so_5::send_delayed< my_message >( mbox, pause, some_args ); // Sending delayed signal. so_5::send_delayed< my_signal >( mbox, pause ); \endcode There are `so_5::send_delayed_to_agent` functions for sending delayed messages/signals to an agent's direct mbox: \code so_5::send_delayed_to_agent< my_message >( agent, pause, some_args ); so_5::send_delayed_to_agent< my_signal >( agent, pause ); \endcode There also are `so_5::send_periodic` functions for sending periodic messages/signals: \code // Sending periodic message. so_5::send_periodic< my_message >( mbox, pause, period, some_args ); // Sending periodic signal. so_5::send_delayed< my_signal >( mbox, pause, period ); \endcode There are `so_5::send_periodic_to_agent` functions for sending periodic messages/signals to an agent's direct mbox: \code so_5::send_periodic_to_agent< my_message >( agent, pause, period, some_args ); so_5::send_periodic_to_agent< my_signal >( agent, pause, period ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_1__subscribe_event_via_state.dox000066400000000000000000000041241523065210100266520ustar00rootroot00000000000000/*! \page so_5_5_1__subscribe_event_via_state so-5.5.1: Subscribing an agent events via state_t::event method The traditional way for subscribing an agent events is `so_subscribe().in().event()` chain. But sometimes this way is too verbose. Because of that since v.5.5.1 it is possible to subscribe an agent's event via new so_5::rt::state_t::event() methods. For example those two `so_define_agent()` methods do exactly the same things. A traditional approach: \code void my_agent::so_define_agent() { so_subscribe_self().in( st_not_connected ) .event( &my_agent::evt_connected ) .event( &my_agent::evt_connect_failed ); so_subscribe_self().in( st_connected ) .event( &my_agent::evt_close_connection ) .event( &my_agent::evt_incoming_data ) .event( &my_agent::evt_disconnected ); so_subscribe( m_configurator_mbox ).in( st_not_connected ) .event( &my_agent::evt_change_config_when_disconnected ); so_subscribe( m_configurator_mbox ).in( st_connected ) .event( &my_agent::evt_change_config_when_connected ); } \endcode A new approach: \code void my_agent::so_define_agent() { st_not_connected .event( &my_agent::evt_connected ) .event( &my_agent::evt_connect_failed ); st_connected .event( &my_agent::evt_close_connection ) .event( &my_agent::evt_incoming_data ) .event( &my_agent::evt_disconnected ); st_not_connected .event( m_configurator_mbox, &my_agent::evt_change_config_when_disconnected ); st_connected .event( m_configurator_mbox, &my_agent::evt_change_config_when_connected ); } \endcode The main difference in those methods is inability to subscribe the one event for several states by state_t::event() method chain. A traditional approach must be used in such case: \code void my_agent::so_define_agent() { so_subscribe_self() .in( st_not_connected ) .in( st_waiting_auth_result ) .in( st_waiting_reconnect ) .in( st_reconfig_failed ) .event( &my_agent::evt_get_status_when_no_connection ); st_connected.event( &my_agent::evt_get_status_when_connected ); } \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_21__deadletter_handlers.md000066400000000000000000000326161523065210100253250ustar00rootroot00000000000000# so-5.5.21 Deadletter Handlers {#so_5_5_21__deadletter_handlers} [TOC] # The Problem There is a SObjectizer's key feature: states of an agent. An agent can have several states. It allows to handle the same message in every state differently. For example: ~~~~~{.cpp} class demo : public so_5::agent_t { state_t st_first{this}, st_second{this}, st_third{this}; ... virtual void so_define_agent() override { st_first.event([this](mhood_t) { std::cout << "1!" << std::endl; }); st_second.event([this](mhood_t) { std::cout << "2!" << std::endl; }); st_third.event([this](mhood_t) { std::cout << "3!" << std::endl; }); ... } }; ~~~~~ It you send a `msg` to an agent of type `demo` then this agent will print different messages in the dependence of the current agent's state. This feature is very useful and it is used extensively in the complex applications. But there is the dark side: if an agent has several states and has to handle some message regardless of the current state then it requires much more work from a developer. There were two ways for implementation of message handling in every state of an agent: The first one requires subscription of the same event for every state of the agent. Something like: ~~~~~{.cpp} class demo : public so_5::agent_t { state_t st_first{this}, st_second{this}, st_third{this}; ... void on_some_message(mhood_t cmd) {...} virtual void so_define_agent() override { st_first.event(&demo::on_some_message); st_second.event(&demo::on_some_message); st_third.event(&demo::on_some_message); ... } }; ~~~~~ Or: ~~~~~{.cpp} virtual void so_define_agent() override { so_subscribe_self() .in(st_first) .in(st_second) .in(st_third) .event(&demo::on_some_message); } ~~~~~ But this approach is boring and it is easy to forget to subscribe the event handler in a new state when this state will be added to agent. The second approach is more flexible and powerful. It is based on hierarchical state machines. There should be one parent state and several child states. An event handler is subscribed only for the parent state: ~~~~~{.cpp} class demo : public so_5::agent_t { state_t st_parent{this}; state_t st_first{ initial_substate_of{st_parent} }, st_second{ substate_of{st_parent} }, st_third{ substate_of{st_parent} }; void on_some_message(mhood_t cmd) {...} virtual void so_define_agent() override { st_parent.event(&demo::on_some_message); ... } }; ~~~~~ This approach will work if an user add or remove child states to agent of type `demo`. It also allows to have different event handlers for the same message in different states: ~~~~~{.cpp} class demo : public so_5::agent_t { state_t st_parent{this}; state_t st_first{ initial_substate_of{st_parent} }, st_second{ substate_of{st_parent} }, st_third{ substate_of{st_parent} }; void on_some_message_default(mhood_t cmd) {...} virtual void so_define_agent() override { st_parent.event(&demo::on_some_message_default); st_second.event([this](mhood_t cmd) { ... /* Different code. */ }); ... } }; ~~~~~ But this approach is not always applicable. For example you can have a base agent class which we can't modify and you have to write a derived class with a default event handler for the some message: ~~~~~{.cpp} // Some base class. Maybe it is defined in third-party library. class base : public so_5::agent_t { state_t st_first{...}, ... /* some other states */ ...; ... }; // Derived class. Adds some its own states. And needs to install a default event-handler. class derived : public base { state_t st_another_state{...}, st_yet_another_state{...}, ...; ... void some_default_handler(mhood_t cmd) {...} ... }; ~~~~~ It is possible that the base class doesn't use HSM and don't have the top-level parent state. Or all states of base class can be declared as private attributes and you have no access to it in your derived class. # Deadletter Handlers as a Solution A new thing was introduced in SObjectizer v.5.5.21: a deadletter handler. Deadletter handler is a like an ordinary message handler. But it is subscribed and unsubscribed by different methods. And the key moment: deadletter is called only when there is no any other event handlers for a message from a specific mbox. For example: ~~~~~{.cpp} class demo : public so_5::agent_t { struct first_signal final : public so_5::signal_t {}; struct second_signal final : public so_5::signal_t {}; ... virtual void so_define_agent() override { so_subscribe_self().event([](mhood_t) { std::cout << "first_signal: ordinary handler" << std::endl; }); so_subscribe_deadletter_handler( so_direct_mbox(), [](mhood_t) { std::cout << "second_signal: deadletter handler" << std::endl; }); } virtual void so_evt_start() override { so_5::send(*this); so_5::send(*this); } }; ~~~~~ This example will print: ~~~~~ first_signal: ordinary handler second_signal: deadletter handler ~~~~~ It is because there is no more event handlers for `second_signal` from agent's direct mbox except the deadletter handler. But if we modify the example such way: ~~~~~{.cpp} class demo : public so_5::agent_t { struct first_signal final : public so_5::signal_t {}; struct second_signal final : public so_5::signal_t {}; ... virtual void so_define_agent() override { so_subscribe_self().event([](mhood_t) { std::cout << "first_signal: ordinary handler" << std::endl; }); so_subscribe_self().event([](mhood_t) { std::cout << "second_signal: ordinary handler" << std::endl; }); so_subscribe_deadletter_handler( so_direct_mbox(), [](mhood_t) { std::cout << "second_signal: deadletter handler" << std::endl; }); } virtual void so_evt_start() override { so_5::send(*this); so_5::send(*this); } }; ~~~~~ The output will be: ~~~~~ first_signal: ordinary handler second_signal: ordinary handler ~~~~~ It is because the SObjectizer will find an ordinary handler for `second_signal` from agent's direct mbox and will call it. The deadletter handler for `second_signal` won't be called because there is an ordinary handler. In other worlds since v.5.5.21 SObjectizer implements a modified algorithm for searching event handlers for a message/signal. SObjectizer does old-way search for a message handler first with respect to the current agent's state. If an event handler is found it will be called and message processing will be finished. But if an event handler for the current state (and all of its parent states) won't be found then SObjectizer will search for deadletter handler for that message and that mbox. It a deadletter handler will be found it will be called for handling of message. # An API for Dealing with Deadletter Handlers An API for working with deadletter handlers was introduced in v.5.5.21. Class `so_5::agent_t` was extended with the following methods. ## so_subscribe_deadletter_handler This method is intended to be used for subscription of deadletter handler. For example: ~~~~~{.cpp} virtual void my_agent::so_define_agent() override { // Create a deadleatter handler for messages from the direct mbox. // Deadletter handler will be subscribed as thread-unsafe handler. so_subscribe_deadletter_handler(so_direct_mbox(), &my_agent::some_handler); // Lambda-function can be used as event-handler too. so_subscribe_deadletter_handler(some_mbox, [](mhood_t cmd) {...}); // Thread-safety flag for event-handler can be specified too. so_subscribe_deadletter_handler(some_mbox, &my_agent::some_thread_safe_handler, so_5::thread_safe); } ~~~~~ A event handler which will be passed to `so_subscribe_deadletter_handler` must be a pointer to method of agent's class or lambda-function with one of the following formats: ~~~~~{.cpp} ret_type handler(message_type); ret_type handler(message_type) const; ret_type handler(const message_type &); ret_type handler(const message_type &) const; ret_type handler(mhood_t); ret_type handler(mhood_t) const; ret_type handler(const mhood_t &); ret_type handler(const mhood_t &) const; ~~~~~ It means that if a deadletter handler has to handle signals then it should has the following format: ~~~~~{.cpp} ret_type handler(mhood_t); ~~~~~ For example: ~~~~~{.cpp} class demo : public so_5::agent_t { struct demo_sig final : public so_5::signal_t {}; ... virtual void so_define_agent() override { ... so_subscribe_deadletter_handler( so_direct_mbox(), [this](mhood_t) {...}); } }; ~~~~~ A message type for a deadletter handler can also be a mutable message. For example: ~~~~~{.cpp} class demo : public so_5::agent_t { void on_some_mutable_msg(mutable_mhood_t cmd) {...} ... virtual void so_define_agent() override { ... so_subscribe_deadletter_handler( so_direct_mbox(), &demo::on_some_mutable_msg); ... } virtual void so_evt_start() override { ... so_5::send< so_5::mutable_msg >(*this, ...); } }; ~~~~~ **NOTE.** `so_subscribe_deadletter_handler` throws if a deadletter handler for a specific message type from a specific mbox is already defined. ## so_drop_deadletter_handler This method should be used for unsubscription of deadletter handler. For example: ~~~~~{.cpp} struct completion_signal final : public so_5::signal_t {}; class demo : public so_5::agent_t { so_5::mbox_t completion_mbox_; void actual_completion_handler(mhood_t) { ... /* Do some stuff. */ // Default completion handler is not needed and can be removed. so_drop_deadletter_handler(completion_mbox_); } void default_completion_handler(mhood_t) { ... /* Do some default stuff. */ // Default completion handler is not needed and can be removed. so_drop_deadletter_handler(completion_mbox_); } void on_some_request(mhood_t cmd) { // Store mbox from which a completion signal is expected. completion_mbox_ = cmd->operation_mbox(); // Create subscription for completion signal. // An actual handler for the state in which we expect the completion signal. so_subscribe(completion_mbox_) .in(one_selected_state) .event(&demo::actual_completion_handler); // For all other states a completion signal will be handled by a deadletter handler. so_subscribe_deadletter_handler(completion_mbox_, &demo::default_completion_handler); ... } }; ~~~~~ It is safe to call `so_drop_deadletter_handler` for deadletter handler which is already removed. There won't be an exception. **NOTE.** A deadletter handler will also be removed by `so_drop_subscription_for_all_states` (see the corresponding section below). ## so_has_deadletter_handler This method is intended to be used for checking the presence of a deadletter handler for a specific type of message from a specific mbox. For example: ~~~~~{.cpp} void my_agent::on_request(mhood_t cmd) { if(!so_has_deadletter_handler(cmd->operation_mbox())) { // There is no default completion handler yet. // It should be created. so_subscribe_deadletter_handler( cmd->operation_mbox(), &my_agent::default_completion_handler); } ... } ~~~~~ ## Other Methods There are also `so_create_deadletter_subscription` and `so_destroy_deadletter_subscription`. But they are low-level methods intended to be used by libraries writers. Do not use it directly if you don't understand what they are doing and which arguments they require. # Some Additional Information About Deadletter Handlers ## Deadletter Handlers and Service Requests Deadletter handlers are just ordinary message handlers. Because of that they can handle service requests. For example: ~~~~~{.cpp} class my_device_handler : public some_3rd_party::lib::basic_device { state_t st_working{this}; public: struct current_status final : public so_5::signal_t {}; ... virtual void so_define_agent() override { // Allow base class to initialize itself. some_3rd_party::lib::basic_device::so_define_agent(); // Make own subscriptions. // Ordinary handler for our own state. st_working.event( [](mhood_t) -> std::string { return "working"; }); // And deadletter handler for all other states from base type. so_subscribe_deadletter_handler( so_direct_mbox(), [](mhood_t) -> std::string { return "unknown"; }, so_5::thread_safe); ... } ... }; ... // Somewhere is the program. auto status = so_5::request_value( my_device_mbox, so_5::infinite_wait); ~~~~~ ## Deadletter Handlers are Removed by so_drop_subscription_for_all_states A deadletter handler can be removed (unsubscribed) not only by `so_drop_deadletter_handler` but also by `so_drop_subscription_for_all_states`. It means that in that case: ~~~~~{.cpp} class drop_all_demo : public so_5::agent_t { state_t st_working{this}, st_waiting{this}, st_busy{this}; void make_handlers() { so_subscribe(some_mbox) .in(st_working).in(st_busy) .event(&drop_all_demo::on_some_message); so_subscribe_deadletter_handler( some_mbox, &drop_all_demo::default_handler); } ... void on_some_message(mhood_t cmd) { ... // Do some stuff. // Drop all subscriptions for some_message. so_drop_subscription_for_all_states(some_mbox); // Now there is no any subscription to some_message from some_mbox. // Event deadletter for that message was removed. } }; ~~~~~ sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_23__enveloped_msg.md000066400000000000000000000241231523065210100241530ustar00rootroot00000000000000# so-5.5.23 Enveloped Messages {#so_5_5_23__enveloped_msg} [TOC] A new feature was introduced in v.5.5.23: envelopes with messages inside. This feature allows to send a message or signal that enveloped in a special object called *envelope*. An envelope can carry some additional information and can perform some actions when message/signal is delivered to a receiver. # An Important Note This new feature is a low-level feature that intended to be used by SObjectizer's developers and those developers who want to extend SObjectizer functionality. It seems that ordinary users will be protected from envelope-related details by some high-level code (something like a special tools from so_5_extra). Because of that the sense of reading the following text exists only if you want to write your own type of an envelope. # What Is An Envelope An envelope is a special case of a message. An envelope carries a message or signal (or another envelope) inside. An envelope is implemented as class derived from `so_5::enveloped_msg::envelope_t`. # Delivery Process For An Envelope Because an envelope is a message the delivery of an envelope is almost the same. For example, dispatchers have no knowledge about envelopes at all -- they see just instances of `so_5::message_t` type. Key differences in delivery process can be found in two places: * passing of an envelope to an mbox or mchain for a delivery; * extraction of envelope's payload for calling an event handler or for transformation of payload (for example in the case of `limit_then_transform`). ## Passing An Envelope To A Mbox A new method has been added to `so_5::abstract_message_box_t` type: `do_deliver_enveloped_msg`. This method has the same semantic as `do_deliver_message` and `do_deliver_service_request`, but intended to be used with enveloped messages only. If you want to send an envelope you have to pass your envelope to `do_deliver_enveloped_msg` method. For example: ```{.cpp} class my_envelope final : public so_5::enveloped_msg::envelope_t { const so_5::message_ref_t payload_; public: my_envelope(so_5::message_ref_t payload) : payload_{std::move(payload)} {...} ... }; void make_and_send_envelope( // The destination for an envelope. const so_5::mbox_t & to, // Type of message to be delivered inside an envelope. const std::type_index & msg_type, // Message to send inside an envelope. so_5::message_ref_t what) { to->do_deliver_enveloped_msg( msg_type, // Envelope instance. std::make_unique(std::move(what)), 1u); } ``` ## msg_type For An Envelope New method `do_deliver_enveloped_msg` requires `msg_type` argument. This is a type of envelope's payload, not a type of envelope itself. For example: ```{.cpp} struct my_message final : public so_5::message_t {...}; class my_envelope final : public so_5::enveloped_msg::envelope_t {...}; // Create a payload of type my_message. auto payload = std::make_unique(...); // Create an envelope with payload inside. auto envelope = std::make_unique(std::move(payload)); // Send envelope with my_message inside. mbox->do_deliver_enveloped_msg( so_5::message_payload_type::subscription_type_index(), std::move(envelope), 1u); ``` ## Extraction Of Envelope's Payload There are several points in message delivery process where the payload should be extracted from an envelope: * delivery of an envelope to a receiver. In this case payload is extracted to be passed to a receiver for normal processing of a message; * transformation of message. For example, if `limit_then_transform` is used for overload control and count of messages of some type is exceeded the specified limit then the payload of envelope should be extracted and transformed to another message or signal; * inspection of message content during delivery procedure. For example, a delivery filter need to analyze the payload's content to enable or disable message delivery to a particular receiver. To extract the payload the corresponding evelope's hook is called. An envelope can decide should the payload be extracted or not. It is possible that an envelope won't provide access to the payload for some reason. In that case the payload will be ignored. It means that if an envelope is delivered to a receiver but don't provide access to the payload then an event handler for the receiver won't be called (as if message was lost). The same is for transformation case: if an envelope don't provide access to the payload for `limit_then_transform` then message won't be transformed and will be simple thrown out. This is the main feature of an envelope: the possibility to "hide" the payload from a receiver (or transformation procedure). # Envelope's Hook The `access_hook()` is called when SObjectizer need to access the payload of an enveloped message. `access_hook()` can be called in different cases: when a message is delivered to a receiver, when overload reaction must be performed, when a delivery filter has to analyze the message and so on. The context of `access_hook()` call is identified by `context` argument of `access_hook()`. That argument can have the following values: * `access_context_t::handler_found`. It means that an enveloped message is delivered to a receiver and tha payload is necessary to call an event handler; * `access_context_t::transformation`. It means that the payload of an enveloped message should be transformed to another representation. For example in the case of `limit_then_transform` overload reaction; * `access_context_t::inspection`. It means that the payload of an enveloped message should be analyzed. For example, by a delivery filter. If the envelope decided that the access to the payload can be provided it should call `invoke()` method for `invoker` object (the `invoker` object is passed as argument to `access_hook()`): ```{.cpp} class my_envelope final : public so_5::enveloped_msg::envelope_t { so_5::message_ref_t payload_; ... public: void access_hook(access_context_t context, handler_invoker_t & invoker) const noexcept { if(access_can_be_granted(context)) invoker.invoke(payload_info_t{payload_}); } ... }; ``` Please note that `access_hook()` is marked as `noexcept`. It must not allow exceptions to leave the hook. ## Multiple Invocations Of Hooks If an enveloped is passed to MPMC-mbox then hook methods can be called more than once. It is because here can be several receivers of the message inside the envelope. # Envelope And Multithreading Because envelopes can be sent to MPMC-mboxes there is a possibility that envelope's hooks will be called at the same time on different threads. It means that envelope classes should be thread safe and should allow invocation of hooks on several threads simultaneously. # Envelope And Message Limits Message limits know almost nothing about envelopes. Strictly speaking only `limit_then_transform` knows that envelopes exist and knows how to extract of envelope's payload from an envelope. But no more. It means that behaviour of an envelope have no influence on message limits. For example let's consider an envelope that allows to revoke a message. Something like: ```{.cpp} // A special envelope with atomic revocation flag. class revocable_envelope_t final : so_5::enveloped_msg::envelope_t { friend class revocable_msg_id_t; so_5::message_ref_t payload_; std::atomic_bool revoked_; public: ... void access_hook(access_context_t, handler_invoker_t & invoker) const noexcept { if(!revoked_.load(std::memory_order_acquire)) invoker.invoke(payload_info_t{payload_}); } ... }; // A special handler that allows to revoke envelope with a message. class revocable_msg_id_t { so_5::intrusive_ptr_t envelope_; public: ... // Constructor/destructor and other stuff. void revoke() { envelope_->revoked_.store(true, std::memory_order_release); } }; // A special helper function to make and send a message inside revocable message. template revocable_msg_id_t make_and_send( const so_5::mbox_t & destination, Args && ...args) { so_5::intrusive_ptr_t envelope{ std::make_unique( std::make_unique(std::forward(args)...))); revocable_msg_id_t result{envelope}; destination->do_deliver_enveloped_msg( so_5::message_payload_type::subscription_type_index(), envelope, 1u); return result; } // Send a revocable message. auto id = make_and_send(to, ...); ... // Revoke a message. id.revoke(); ``` If `revoke()` method is called when message is waiting in receiver's queue then the message won't be delivered to the receiver (because envelope won't provide access to the payload). But the calling to `revoke()` do not decrement the quantity of messages on type `my_message` in receiver's queue. Quantity of messages will be decremented only after extraction of an envelope from receiver's queue. It means that `revoke()` have no influence on message limits. # Nested Envelopes It is possible to create envelopes that holds another envelopes. There is no need to know a type of the payload. For example this simple envelope can hold envelopes as well as messages or signals: ```{.cpp} class demo_envelope final : public so_5::enveloped_msg::envelope_t { so_5::message_ref_t payload_; public: demo_envelope(so_5::message_ref_t payload) : payload_{std::move(payload)} {} void access_hook(access_context_t, handler_invoker_t & invoker) const noexcept { invoker.invoke(payload_info_t{payload_}); } }; ``` This is because the standard implementations of `handler_invoker_t` check the kind of payload. And if a payload is another envelope these implementation call appropriate hook for that envelope recursively. An user should take care of payload kind only if he or she implements own `handler_invoker_t` class. In that case `so_5::message_t::kind_t` and `so_5::message_kind()` should be used. # Ready To Use Envelopes SObjectizer v.5.5.23 doesn't provide ready to use envelopes. SObjectizer core only implements basic functionality for envelopes. Ready to use envelopes can be found in so_5_extra library that is built on top of SObjectizer. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_24__event_queue_hook.md000066400000000000000000000103241523065210100246700ustar00rootroot00000000000000# so-5.5.24 event_queue_hook {#so_5_5_24__event_queue_hook} [TOC] # What Is It? SObjectizer-5.5.24 contains another feature for customization of SObjectizer Run-Time behaviour. It is an *event_queue_hook*. Since v.5.5.24 there is a single instance of event_queue_hook in SObjectizer Environment. By default it is no-op hook (nothing is happened when this no-op hook is invoked). But user can set his/her own instance can in `environment_params_t` before launching of SObjectizer Environment. Since v.5.5.24 every agent calls event_queue_hook two times: * the first call to event_queue_hook is performed when agent is being bound to a particular event queue. Agent receives a pointer to event_queue provided by corresponding dispatcher and passes that pointer to `event_queue_hook_t::on_bind` method. As result agent receives a new pointer and this new pointer will be used by agent for enqueuing new demands; * the second call to event_queue_hook is performed when agent is being unbound from the dispatcher. Agent passes a pointer to event_queue returned from `event_queue_hook_t::on_bind` to `event_queue_hook_t::on_unbind` method. The event_queue_hook mechanism allows to make specialized wrappers around actual event queues. These wrappers can be used for different tasks. For example for tracing purposes or for gathering some run-time stats. Please note that this is a low-level mechanism intended to be used for very specific tasks. Because its low-level nature the details and behavior of that mechanism can be changed dramatically in future versions of SObjectizer without any prior notice. # A Simple Example As a very simple example we provide a rather trivial implementation of event_queue_hook. This implementation wraps every event_queue in a special proxy object: ```{.cpp} class demo_event_queue_hook final : public so_5::event_queue_hook_t { logger & sink_; public: demo_event_queue_hook(logger & sink) : sink_{sink} {} SO_5_NODISCARD so_5::event_queue_t * on_bind( so_5::agent_t * /*agent*/, so_5::event_queue_t * original_queue ) SO_5_NOEXCEPT override { return new event_queue_logging_proxy{original_queue, sink_}; } void on_unbind( so_5::agent_t * /*agent*/, so_5::event_queue_t * queue ) SO_5_NOEXCEPT override { delete queue; } }; ``` Where `event_queue_logging_proxy` can looks like: ```{.cpp} class event_queue_logging_proxy final : public so_5::event_queue_t { so_5::event_queue_t * actual_queue_; logger & sink_; public: event_queue_logging_proxy( so_5::event_queue_t * actual_queue, logger & sink) : actual_queue_{actual_queue} , sink_{sink} { sink_.log("logging_proxy=", this, " created"); } ~event_queue_logging_proxy() override { sink_.log("logging_proxy=", this, " destroyed"); } void push(so_5::execution_demand_t demand) override { sink_.log("logging_proxy=", this, ", receiver_ptr=", demand.m_receiver, ", mbox_id=", demand.m_mbox_id, ", msg_type=", demand.m_msg_type.name(), ", limit_block=", demand.m_limit, ", msg_ptr=", demand.m_message_ref.get()); actual_queue_->push(std::move(demand)); } }; ``` This event_queue_hook can be passed to SObjectizer Environment this way: ```{.cpp} int main() { logger sink("_event_queue_trace.log"); demo_event_queue_hook hook{sink}; so_5::launch([](so_5::environment_t & env) { env.introduce_coop([](so_5::coop_t & coop) { coop.make_agent(); }); }, [&](so_5::environment_params_t & params) { params.event_queue_hook( so_5::event_queue_hook_unique_ptr_t{ &hook, so_5::event_queue_hook_t::noop_deleter}); }); } ``` In that case instance of `demo_event_queue_hook` is created on stack and because of that `noop_deleter` will be used with `event_queue_hook_unique_ptr_t`. We can also create an instance of `demo_event_queue_hook` as dynamic object. In that case we can write: ```{.cpp} int main() { logger sink("_event_queue_trace.log"); so_5::launch([](so_5::environment_t & env) { env.introduce_coop([](so_5::coop_t & coop) { coop.make_agent(); }); }, [&](so_5::environment_params_t & params) { params.event_queue_hook( so_5::make_event_queue_hook( so_5::event_queue_hook_t::default_deleter, sink)); }); } ``` sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_3__subscr_storage_selection.dox000066400000000000000000000106431523065210100265300ustar00rootroot00000000000000/*! \page so_5_5_3__subscr_storage_selection so-5.5.3: Subscription storage type selection There are some places in the SObjectizer where there is no "one right implementation" which will give the best possible results for all scenarios. Version 5.5.3 introduces an approach for tuning agents and underlying data structures/algorithm for user-specific scenarios. The first step is support for various type of *subscription storages*. Subscription storage is a data structure for storing and manipulating information about an agent's subscriptions. Every agent has its own private subscription storage. When an agent creates subscription like: \code void some_agent::so_define_agent() { so_default_state().event( &some_agent::evt_do_some_work ); } \endcode this subscription is stored in the agent's subscription storage. When agent receives a messages the handler for that message will be searched in this storage. The problem is selection of the appropriate data structure for that storage. When an agent uses small amount of subscription (like one or two subscriptions) then a very simple vector-based implementation will be the most efficient. When an agent uses several dozens subscriptions then vector-based implementation becomes inefficient and map-based storage will be more appropriate. But when an agent uses several hundreds or even thousands of subscriptions then hash-table-based implementation will be more efficient. Since v.5.5.3 a user can specify which subscription storage should be used for an agent. Do do so it is necessary to specify tuning_options-object to constructor of base class: \code using namespace so_5::rt; class my_agent : public agent_t { public : my_agent( environment_t & env ) : agent_t( env, tuning_options() ) {} ... }; \endcode The new static method so_5::rt::agent_t::tuning_options() creates an so_5::rt::agent_tuning_options_t object with default values. This values can be changed by calling so_5::rt::agent_tuning_options_t's methods. For example to change type of subscription storage it is necessary to call so_5::rt::agent_tuning_options_t::subscription_storage_factory() method: \code using namespace so_5::rt; class my_agent : public agent_t { public : my_agent( environment_t & env ) : agent_t( env, tuning_options().subscription_storage_factory( vector_based_subscription_storage(4) ) ) {} ... }; \endcode There are several implementations of subscription storage: - vector-based implementation. Uses std::vector and simple linear search. Very efficient on small numbers of subscriptions. Function so_5::rt::vector_based_subscription_storage_factory() creates factory for this type of the storage; - map-based implementation. Uses std::map and is very efficient when count of subscriptions is greater than 10-20 and less than 100-200. Function so_5::rt::map_based_subscription_storage_factory() creates factory for this type of the storage; - hash-table-based implementation. Uses std::unordered_map and is most efficient when count of subscription is exceed several hundreds. Function so_5::rt::hash_table_based_subscription_storage_factory() creates factory for this type of the storage; - adaptive storage. Uses two storage objects. The first one is used when the count of subscriptions is small. The second is used when the count of subscription exceeds some threshold. This storage dynamically changes implementations -- switches from small storage to the big one when new subscriptions are created and switches back when subscriptions are erased. By default adaptive storage uses vector-based storage as small one, and map-based storage as big one. But this can be changed at the moment of storage creation. The adaptive storage is created by so_5::rt::adaptive_subscription_storage_factory() functions. By default all agents use adaptive subscription storage. It means that if an agent creates very few subscriptions it will use very small and very fast vector-based storage. But if count of subscription grows then agent will switch to more expensive but more appropriate for big amount of subscriptions map-based storage. But if user knows what count of subscriptions an actor will use then an appropriate storage can be created once and never switches from one implementation to another. \note The type of subscription storage can be specified only once during agent creation. After creation the subscription storage cannot be changed. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_4__agent_context.dox000066400000000000000000000124101523065210100242730ustar00rootroot00000000000000/*! \page so_5_5_4__agent_context so-5.5.4: Another constructor for agent_t with context_t argument Since v.5.5.3 there is a possibility to tune agent options. For example, if it is required to change subscription storage for an agent it could be written as: \code class my_agent_with_many_subscriptions : public so_5::rt::agent_t { public : my_agent_with_many_subscriptions( so_5::rt::environment_t & env, some_agent_specific_params params ) : so_5::rt::agent_t( // Passing env to the parent. env, // Passing agent options to the parent. tuning_options().subscription_storage_factory( so_5::rt::hash_table_based_subscription_factory() ) ) , ... {...} ... }; \endcode But what if we want to create a derived class from `my_agent_with_many_subscriptions` and specify additional agent's options in the constructor of the derived class? How can we do that? The only way for that in v.5.5.3 was the definition of another constructor for base class. That constructor must receive so_5::rt::agent_tuning_options_t object: \code // Our base class. class my_agent_with_many_subscriptions : public so_5::rt::agent_t { public : // Constructor for the case when there are no additional options. my_agent_with_many_subscriptions( so_5::rt::environment_t & env, some_agent_specific_params params ) : // Delegate work to different constructor. my_agent_with_many_subscriptions( env, // Default tuning options. tuning_options(), // Other arguments... params ) {} // Constructor for the case when there are some additional options. my_agent_with_many_subscriptions( so_5::rt::environment_t & env, so_5::rt::agent_tuning_options_t options, some_agent_specific_params params ) : so_5::rt::agent_t( // Passing env to the parent. env, // Passing agent options to the parent. options.subscription_storage_factory( so_5::rt::hash_table_based_subscription_factory() ) ) , ... {...} ... }; // Our derived class. class my_agent_with_limits : public my_agent_with_many_subscriptions { public : my_agent_with_limits( so_5::rt::environment_t & env, some_agent_specific_params params ) : my_agent_with_many_subscriptions( env, // Our part of agent options. tuning_options().message_limits( limit_then_drop< request_one >( 100 ), limit_then_drop< request_two >( 100 ), ... ), params ) {...} ... }; \endcode But this approach requires definition of yet another constructor for an agent. And yet another constructor of agent of derived class and so on. And the definition of another constructor in base class is not always possible if that class is coming from 3rd-party library and we don't want to patch the library source code. Version 5.5.4 provides the solution for that problem. Since v.5.5.4 it is possible to define agent's constructors which accept argument of the type `so_5::rt::agent_t::context_t` (which is an alias for `so_5::rt::agent_context_t`). This type can be seen as a pair of reference to SObjectizer Environment object and so_5::rt::agent_tuning_option_t object. The context_t instance can be used instead of environment_t reference. It allows to write like this: \code // Our base class. class my_agent_with_many_subscriptions : public so_5::rt::agent_t { public : // Constructor for both cases: either there are additional options or not. my_agent_with_many_subscriptions( context_t ctx, some_agent_specific_params params ) : // We can simply pass new context to the base class. so_5::rt::agent_t( // Tune our agent by adding appropriate factory. ctx + so_5::rt::hash_table_based_subscription_factory() ) , ... {...} ... }; // Our derived class. class my_agent_with_limits : public my_agent_with_many_subscriptions { public : my_agent_with_limits( context_t ctx, some_agent_specific_params params ) : my_agent_with_many_subscriptions( // Our part of agent options. ctx + limit_then_drop< request_one >( 100 ) + limit_then_drop< request_two >( 100 ) + ..., params ) {...} ... }; \endcode Please note two important features of `context_t`: - because this type is defined inside `so_5::rt::agent_t` it is possible to use short form (e.g. `context_t` instead of `so_5::rt::agent_t::context_t` or `so_5::rt::agent_context_t`) inside any class derived from `so_5::rt::agent_t`; - helper methods like `agent_coop_t::make_agent()` and `environment_t::make_agent()` will work with agent classes for those the first argument of a constructor has type `context_t`. It is because `context_t` is implicitly constructed from reference to `environment_t`. Because of that the usage of `context_t` instead `environment_t` is recommended since v.5.5.4. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_4__make_agent_methods.dox000066400000000000000000000043121523065210100252510ustar00rootroot00000000000000/*! \page so_5_5_4__make_agent_methods so-5.5.4: New make_agent methods The creation of agent instances was one of the few places where usage of operator new() was necessary. The agents creation could look like: \code // With unique_ptr... std::unique_ptr< my_agent > a{ new my_agent{ env, params } }; // Or with unique_ptr, but different way... auto a = std::unique_ptr< my_agent >{ new my_agent{ env, params } }; // Or directly during adding agent to a coop. coop->add_agent( new my_agent{ env, params } ); \endcode This approach of agent creation was inherited from the times when advanced C++11 features like variadic templates and rvalue references were not supported in all major C++ compilers at the required quality level. But times have changed. Now SObjectizer-5.5 actively uses variadic templates and doesn't support old compilers without this very important C++11 feature. Because of that a new, more convenient way for agent creation has been introduced in version 5.5.4. Just compare: \code // The pre-5.5.4 way: auto a = std::unique_ptr< my_agent >{ new my_agent{ env, params } }; // The 5.5.4 way: auto a = env.make_agent< my_agent >( params ); \endcode The same make_agent has been added to so_5::rt::agent_coop_t class: \code // The pre-5.5.4 way: auto a = std::unique_ptr< my_agent >{ new my_agent{ env, params } }; coop->add_agent( std::move( a ) ); coop->add_agent( new your_agent{ env, different_params } ); // The 5.5.4 way: auto a = env.make_agent< my_agent >( params ); coop->add_agent( std::move( a ) ); coop->make_agent< your_agent >( different_params ); \endcode There is also so_5::rt::agent_coop_t::make_agent_with_binder() method for the case when new agent should be bound to the specific dispatcher: \code auto disp = so_5::disp::active_obj::create_private_disp( env ); coop->make_agent_with_binder< my_agent >( disp->binder(), params ); coop->make_agent_with_binder< your_agent >( disp->binder(), different_params ); \endcode **Attention!** Please note that new make_agent and make_agent_with_binder methods require that the first argument of agent's constructor must be a reference to so_5::rt::environment_t object. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_4__message_limits.dox000066400000000000000000000354771523065210100244600ustar00rootroot00000000000000/*! \page so_5_5_4__message_limits so-5.5.4: Message Limits \section so_5_5_4__message_limits__what_is What Is Message Limits? Message limits are a tool for defense of an agent from too intensive message flow. Message limits is not a full fledged overload control mechanism. It solves the only one problem -- limitation of quantity of messages of particular type in the agent's event queue. No more. No less. Construction of the appropriate overload control mechanism for a particular problem is not an easy task. It requires accounting of specific features of the problem. For example: what to do with messages which do not fit into size-limited event queue? Should they be silently discarded? Or there must be some logging? Or they must be stored in some persistent queue and replayed after some time? So the right overload control tool almost always will be a very problem-specific tool. Which will not work for different problem with different conditions. Because of that SObjectizer has no a ready-to-use overload control tool out-of-box yet. And it is a big question will it be sometime in the future. A user can use collector-performer idiom and can implement its own overload control mechanism. Which will be adapted to the user's problem. But it requires some programming. It is not always appropriate. There could be a very simple cases when extra messages can be ignored or work of the application must be simply aborted. In such cases message limits could be used as very simple but ready-to-use tool for defense of agents. \section so_5_5_4__message_limits__how_works How Message Limits Work? First of all message limits are optional. It is not required to define message limits for every agent in an application. Message limits can be defined only for agents those require such defense. But if message limits are defined for some agent then SObjectizer will count all messages which are sent to that agent. Message limit is specified for a particular message type. For example if an agent receives messages of type M and of type N, then message limits are specified separately for M and N. There is no such thing as common limit for the size of the agent's queue. It is impossible to limit queue's size to 100 items regardless the type of the messages. It is necessary to define the limit for messages of type M and, separately, the limit for messages of type N. When message limit for some type of message is defined then SObjectizer will count instances of messages waiting for processing by this agent. An attempt to send a message over that limit will be detected and SObjectizer will perform some action as a reaction to limit's overflow. SObjectizer supports the four types of reaction to exceeding the limit of the number of messages: - dropping the message without any kind of processing. The message will be ignored silently; - abortion of the whole application by calling `std::abort()`; - redirection of the message instance to another message box; - transformation of the message instance to a new message instance and sending the new instance to some message box (may be same mbox, may be not). \section so_5_5_4__message_limits__how_to_define How To Define Message Limits For An Agent? Message limits are defined via agent tuning options. It means that message limits must be prepared and passed as an argument to a constructor of `so_5::rt::agent_t`. The limits defined will be stored inside `agent_t` object and **cannot be changed or removed or added later**. An important note: If an agent defines a limit for one message type it must also define limits for all other message types it receives. An attempt to subscribe to message without predefined limit will lead to an error at runtime (a subscription method will throw an exception). But note: this rule is only for agents which use message limits. Base class `so_5::rt::agent_t` provides several helper methods each of them defines a limit and a particular reaction to exceeding of the limit. Method `limit_then_drop` defines a limit and dictates to SObjectizer to silently drop exceeding messages. For example: \code class request_processor : public so_5::rt::agent_t { public : request_processor( context_t ctx ) : so_5::rt::agent_t( ctx // No more than 20 messages with dropping of extra messages. + limit_then_drop< request >( 20 ) // No more than 1 message with dropping of extra messages. + limit_then_drop< get_status >( 1 ) ) ... }; \endcode Method `limit_then_drop` defines a limit and dictates to SObjectizer to call `std::abort()` and terminate application in the case the limit exceeded: \code class hardware_interface : public so_5::rt::agent_t { public : hardware_interface( context_t ctx ) : so_5::rt::agent_t( ctx // Usually there must be no more than 10 waiting commands. // But if something went wrong and hardware part doesn't respond // it is better to terminate the whole application. + limit_then_abort< outgoing_apdu_command >( 500 ) ) ... }; \endcode Method `limit_then_redirect` defines a limit and dictates to SObjectizer to redirect the message instance to another mbox. The second argument for limit_then_redirect is a lambda/functional object which returns a mbox for message redirection. \code // Agent for 'normal' processing of requests. class normal_request_processor : public so_5::rt::agent_t { public : normal_request_processor( context_t ctx, // Message box of agent for handling requests in overload mode. // In this mode requests are not processed but negative response // with the appropriate result code is sent back // (and this fact is stored in the application log). so_5::rt::mbox_t overload_mode_processor ) : so_5::rt::agent_t( ctx // We can hold no more that 10 requests in queue. // All extra messages must be redirected to overload-mode processor // for generation of negative replies. + limit_then_redirect< request >( 10, [overload_mode_processor] { return overload_mode_processor; } ) ... }; \endcode It is not always possible to know the target mbox in the constructor of an agent. Sometimes this mbox is defined some time later. For example: \code // Coop for request processing. auto coop = env.register_coop( so_5::autoname ); // Agent for normal processing. auto normal_processor = coop->make_agent< normal_request_processor >(...); // Agent for overload-mode processing. auto overload_processor = coop->make_agent< overload_mode_request_processor >( // This agent must know mbox of normal processor. normal_processor->so_direct_mbox() ); // Bind normal processor with overload-mode processor. normal_processor->set_overload_mode_processor( overload_processor->so_direct_mbox() ); \endcode It is not a big problem because limit_then_redirect receives lambda/functional object but not the exact mbox. That allows to write: \code // Agent for 'normal' processing of requests. class normal_request_processor : public so_5::rt::agent_t { public : normal_request_processor( context_t ctx, : so_5::rt::agent_t( ctx // We can hold no more that 10 requests in queue. // All extra messages must be redirected to overload-mode processor // for generation of negative replies. + limit_then_redirect< request >( 10, [this] { return this->overload_mode_processor; } ) {} ... // Message box of agent for handling requests in overload mode. // In this mode requests are not processed but negative response // with the appropriate result code is sent back // (and this fact is stored in the application log). void set_overload_mode_processor( const so_5::rt::mbox_t & overload_mode_processor ) { this->overload_mode_processor = overload_mode_processor; } private: so_5::rt::mbox_t overload_mode_processor; ... }; \endcode But there is a very important rule: **the target mbox for message redirection must be known at the moment of agent registration in the SObjectizer Environment**. If lambda/functional object passed to limit_then_redirect return a null mbox at runtime it will lead to the application crash (because of attempt to dereference null-pointer). Method `limit_then_transform` defines a limit and dictates to SObjectizer to transform the message instance to another message/signal and send the new message/signal to some mbox. The second argument for limit_then_transform is a lambda/functional object which returns a pair of values: a target mbox for new message and the new message itself: \code class request_processor : public so_5::rt::agent_t { public : normal_request_processor( context_t ctx ) : so_5::rt::agent_t( ctx // We can hold no more that 10 requests in queue. // For all extra messages negative replies must be generated. + limit_then_transform( 10, [](const request & evt) { return make_transformed< negative_reply >( // Mbox for sending reply is got from original request. evt.reply_to(), // All other arguments are passed to negative_reply constructor. error_code::processor_is_busy ); } ) ) ... {} ... }; \endcode There are two forms of limit_then_transform. One is shown above. It is intended to use with messages. The type of the message is deduced from argument of transformation lambda. Like in the previous example. There is another form of limit_then_transform for signals. It receives type of the signal as template parameter. And the transformation lambda must be a lambda without arguments: \code class long_operation_performer : public so_5::rt::agent_t { public : long_operation_performer( context_t ctx, so_5::rt::mbox_t mbox_for_notifications ) : so_5::rt::agent_t( ctx // If we cannot process previous get_status signal // then we are busy and can tell about this. + limit_then_transform< get_status >( 1, [this] { // The result of the transformation is another signal // and because of that there is only one argument for // make_transform (the target mbox). return make_transformed< status_busy >( this->notification_mbox ); } ) ) , notification_mbox( std::move( mbox_for_notifications ) ) {} ... private : const so_5::rt::mbox_t notification_mbox; ... }; \endcode As for limit_then_redirect there is the same important rule for limit_then_transform: **the target mbox for the transformed message must be known at the moment of agent registration in the SObjectizer Environment**. If lambda/functional object passed to limit_then_transform return a null mbox at runtime it will lead to the application crash (because of an attempt to dereference null-pointer). \section so_5_5_4__message_limits__technical_details Some Technical Details \subsection so_5_5_4__message_limits__technical_details__ad_hoc_agents Ad-Hoc Agents Message limits can be used with ordinary agents only. Ad-hoc agents do not support message limits in v.5.5.4. \subsection so_5_5_4__message_limits__technical_details__inc_and_dec Increment and Decrement of Message Counter SObjectizer does message counting only if there is a limit for that message type. SObjectizer increments message counter during dispatching of message: SObjectizer walks through subscribers of message and checks if there a predefined limit for the next subscriber. If there is a limit then SObjectizer tries to increment message counter of that type for that agent. If it is impossible then reaction to exceeding of the limit is performed. If the message counter is successfully incremented then the message is stored to the appropriate event queue. The counter for the message is decremented when message is taken from queue and scheduled to processing by agent. It means that message counter is decremented before invocation of event handler for that message instance. Please note that no counters are incremented/decremented when there is no message limit defined for an agent. It means that some performance penalty is paid only when an agent uses message limits. If an agent doesn't use limits there is no significant slowdown in comparison with previous SObjectizer versions. \subsection so_5_5_4__message_limits__technical_details__too_deep_recursion Too Deep Recursion of Overload Reactions There could be situation when reactions to an extra message are bound to a very long chain. For example: an extra message M is redirected to agent A2, but the queue for A2 is full and message is redirected to A3, but the queue for A3 is full and message is redirected to A4... Also there could be user mistakes. For example: agent redirects an extra message to itself -- there will be infinite redirection loop. There is a hardcoded limit of reaction recursion deep in the SObjectizer v.5.5.4. When this limit is reached then a problematic message will be logged and discarded. \subsection so_5_5_4__message_limits__technical_details__calling_context When and Where Redirection/Transformation Lambdas are Called A lambda/functional object that specified as part of reaction in `limit_then_redirect` and `limit_then_transform` is called inside `send`/`deliver_message` functions on the context of message sender. It means that lambda can be concurrently called on the different working contexts. A developer must understand that and write redirection and transformation lambdas in thread safe manner. The easiest way to do that is to write such lambdas as stateless, side effect free functions. They must access only constant, immutable data. If this is impossible and some kind of data synchronization required here then this should be done will additional care. Yet another note for a developer. It is better to make redirection/transformation lambdas as small and as fast as possible. Because they are called inside message dispatching procedure they could affect SObjectizer performance significantly. \subsection so_5_5_4__message_limits__technical_details__side_effects Redirection/Transformation Lambdas and Side Effects It is strongly recommended to make redirection/transformation lambda/functional objects as stateless, side effect free functions. **It is not recommended to perform any actions those can change state of SObjectizer Environment (like registration of new cooperations or deregistration of existing cooperations).** It is because those lambdas are called under some locks (mbox's lock in particular) and an attempt of changing the SObjectizer Environment state can lead to deadlock on those locks. \subsection so_5_5_4__message_limits__technical_details__service_requests Redirection and Transformation of Service Requests Service requests can be redirected to another mbox. Service requests cannot be transformed to something else (including different service requests). An attempt to transform service request will lead to raising an exception at runtime. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_4__private_dispatchers.dox000066400000000000000000000073021523065210100255000ustar00rootroot00000000000000/*! \page so_5_5_4__private_dispatchers so-5.5.4: Private dispatchers From the very beginning of SObjectizer-4 and SObjectizer-5 dispatchers could be added only before the start of SObjectizer Run-Time. There was no a possibility to add new dispatcher when SObjectizer Run-Time is running. The situation is slightly changed in v.5.4.0 where add_dispatcher_if_not_exist() method was added to so_5::rt::environment_t class. It added the possibility to introduce new dispatcher instance when it necessary. But there was not a way to remove a dispatcher which is no more used by anyone. This problem has been solved in v.5.5.4 by introducing a new conception -- private dispatcher. Private dispatcher is an instance of dispatcher that has no name and accessible only by a special handle for it. While there is a handle, there is the dispatcher. When all handles are destroyed the dispatcher is destroyed too. Private dispatcher handle is in fact a smart intrusive pointer. So the concept of private dispatcher handles should be natural to the C++ programmers because it is a form of resources management via ordinary smart pointers. Since v.5.5.4 every dispatcher type defines the following things: - an abstract type private_dispatcher_t with one or several binder() methods for creating binders to the private dispatcher of that type; - a handle type private_dispatcher_handle_t which can be seen as a smart pointer for private_dispatcher_t (and in fact is); - a function %create_private_disp() for creating an instance of the private dispatcher. So there are so_5::disp::one_thread::private_dispatcher_t, so_5::disp::one_thread::private_dispatcher_handle_t, so_5::disp::one_thread::create_private_disp(). There also are so_5::disp::active_obj::private_dispatcher_t, so_5::disp::active_obj::private_dispatcher_handle_t, so_5::disp::active_obj::create_private_disp() and so on. There is an example of usage of private dispatcher of various types: \code std::vector< so_5::rt::mbox_t > create_processing_coops( so_5::rt::environment_t & env ) { std::vector< so_5::rt::mbox_t > result; std::size_t capacities[] = { 25, 35, 40, 15, 20 }; // Private dispatcher for receivers. auto receiver_disp = so_5::disp::thread_pool::create_private_disp( env, 2 ); // And private dispatcher for processors. auto processor_disp = so_5::disp::active_obj::create_private_disp( env ); int i = 0; for( auto c : capacities ) { auto coop = env.create_coop( so_5::autoname ); auto receiver = coop->make_agent_with_binder< a_receiver_t >( receiver_disp->binder( so_5::disp::thread_pool::params_t{} ), "r" + std::to_string(i), c ); const auto receiver_mbox = receiver->so_direct_mbox(); result.push_back( receiver_mbox ); coop->make_agent_with_binder< a_processor_t >( processor_disp->binder(), "p" + std::to_string(i), receiver_mbox ); env.register_coop( std::move( coop ) ); ++i; } return result; } void init( so_5::rt::environment_t & env ) { auto receivers = create_processing_coops( env ); // A private dispatcher for generators cooperation. auto generators_disp = so_5::disp::thread_pool::create_private_disp( env, 3 ); auto coop = env.create_coop( so_5::autoname, generators_disp->binder( []( so_5::disp::thread_pool::params_t & p ) { p.fifo( so_5::disp::thread_pool::fifo_t::individual ); } ) ); for( int i = 0; i != 3; ++i ) { coop->make_agent< a_generator_t >( "g" + std::to_string(i), receivers ); } // Registration of generator will start example. env.register_coop( std::move( coop ) ); // Taking some time for the agents. std::this_thread::sleep_for( std::chrono::seconds( 10 ) ); env.stop(); } \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_5__introduce_coop.dox000066400000000000000000000114671523065210100244610ustar00rootroot00000000000000/*! \page so_5_5_5__introduce_coop so-5.5.5: introduce_coop and introduce_child_coop helpers Version 5.5.5 introduces two useful helpers for simplification of cooperation creation: - so_5::rt::environment_t::introduce_coop() template method and - so_5::rt::introduce_child_coop() template function. Just for comparison of new cooperation creation and registration: \code // Old approach (before v.5.5.5): auto coop = env.create_coop( so_5::autoname ); coop->make_agent< first_agent >( some_params() ); coop->make_agent< second_agent >( some_params() ); env.register_coop( std::move( coop ) ); // New approach (since v.5.5.5, in C++11): env.introduce_coop( []( so_5::rt::agent_coop_t & coop ) { coop.make_agent< first_agent >( some_params() ); coop.make_agent< second_agent >( some_params() ); } ); // New approach (since v.5.5.5, in C++14): env.introduce_coop( []( auto & coop ) { coop.make_agent< first_agent >( some_params() ); coop.make_agent< second_agent >( some_params() ); } ); \endcode Similar comparison for creation of a child cooperation: \code // Old approach (before v.5.5.5): void parent_coop::so_evt_start() override { auto coop = so_5::rt::create_child_coop( *this, so_5::autoname ); coop->make_agent< first_agent >( some_params() ); coop->make_agent< second_agent >( some_params() ); env.register_coop( std::move( coop ) ); } // New approach (since v.5.5.5, in C++11): void parent_coop::so_evt_start() override { so_5::rt::introduce_child_coop( *this, [this]( so_5::rt::agent_coop_t & coop ) { coop.make_agent< first_agent >( some_params() ); coop.make_agent< second_agent >( some_params() ); } ); } // New approach (since v.5.5.5, in C++14): void parent_coop::so_evt_start() override { so_5::rt::introduce_child_coop( *this, [this]( auto & coop ) { coop.make_agent< first_agent >( some_params() ); coop.make_agent< second_agent >( some_params() ); } ); } \endcode New `introduce_coop` and `introduce_child_coop` are just thing wrappers around `create_coop` and `create_child_coop`. Because of that new method/function accepts all combination of parameters appropriate for create_coop/create_child_coop. It means that it is possible to write: \code // Automatic generation of cooperation name. // Default dispatcher as a main dispatcher for cooperation. env.introduce_coop( so_5::autoname, []( so_5::rt::agent_coop_t & coop ) {...} ); // Explicitly specified cooperation name. // Default dispatcher as a main dispatcher for cooperation. env.introduce_coop( "db_workers", []( so_5::rt::agent_coop_t & coop ) {...} ); // Automatic generation of cooperation name. // Explicitly specified main dispatcher. env.introduce_coop( so_5::autoname, so_5::disp::one_thread::create_private_disp()->binder(), []( so_5::rt::agent_coop_t & coop ) {...} ); // Explicitly specified cooperation name. // Explicitly specified main dispatcher. env.introduce_coop( "logger", so_5::disp::one_thread::create_private_disp()->binder(), []( so_5::rt::agent_coop_t & coop ) {...} ); \endcode But the usage of `so_5::autoname` could be omitted: \code // Automatic generation of cooperation name. // Default dispatcher as a main dispatcher for cooperation. env.introduce_coop( []( so_5::rt::agent_coop_t & coop ) {...} ); // Automatic generation of cooperation name. // Explicitly specified main dispatcher. env.introduce_coop( so_5::disp::one_thread::create_private_disp()->binder(), []( so_5::rt::agent_coop_t & coop ) {...} ); \endcode The same rules are for `introduce_child_coop`: \code // Automatic generation of cooperation name. // Default dispatcher as a main dispatcher for cooperation. so_5::rt::introduce_child_coop( *this, []( so_5::rt::agent_coop_t & coop ) {...} ); // Automatic generation of cooperation name. // Default dispatcher as a main dispatcher for cooperation. so_5::rt::introduce_child_coop( *this, so_5::autoname, []( so_5::rt::agent_coop_t & coop ) {...} ); // Explicitly specified cooperation name. // Default dispatcher as a main dispatcher for cooperation. so_5::rt::introduce_child_coop( *this, "db_workers", []( so_5::rt::agent_coop_t & coop ) {...} ); // Automatic generation of cooperation name. // Default dispatcher as a main dispatcher for cooperation. so_5::rt::introduce_child_coop( *this, so_5::disp::one_thread::create_private_disp()->binder(), []( so_5::rt::agent_coop_t & coop ) {...} ); // Automatic generation of cooperation name. // Explicitly specified main dispatcher. so_5::rt::introduce_child_coop( *this, so_5::autoname, so_5::disp::one_thread::create_private_disp()->binder(), []( so_5::rt::agent_coop_t & coop ) {...} ); // Explicitly specified cooperation name. // Explicitly specified main dispatcher. so_5::rt::introduce_child_coop( *this, "logger", so_5::disp::one_thread::create_private_disp()->binder(), []( so_5::rt::agent_coop_t & coop ) {...} ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_8__new_form_introduce_child_coop.dox000066400000000000000000000017551523065210100275220ustar00rootroot00000000000000/*! \page so_5_5_8__new_form_introduce_child_coop so-5.5.8: New form of introduce_child_coop A new form of so_5::rt::introduce_child_coop() function has been added in v.5.5.8. It accepts reference to agent_coop_t object instead of name of parent coop. This new form will be useful in the following cases: \code env.introduce_coop( []( so_5::rt::agent_coop_t & parent ) { coop.define_agent().on_start( [&parent] { so_5::rt::introduce_child_coop( parent, []( so_5::rt::agent_coop_t & child ) { child.make_agent< worker >(); ... } ); ... } ); ... } ); \endcode \note There also a new variant of so_5::rt::create_child_coop() function: \code env.introduce_coop( []( so_5::rt::agent_coop_t & coop ) { coop.define_agent().on_start( [&coop] { auto child = so_5::rt::create_child_coop( coop, so_5::autoname ); child->make_agent< worker >(); ... coop.environment().register_coop( std::move( child ) ); } ); ... } ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_8__new_limit_then_abort.dox000066400000000000000000000026661523065210100256450ustar00rootroot00000000000000/*! \page so_5_5_8__new_limit_then_abort so-5.5.8: New variant of limit_then_abort Method so_5::rt::agent_t::limit_then_abort allow to specify message limit with very tough overlimit reaction -- application abortion. But there was an inconvenience in limit_then_abort usage: it was hard to detect what message/signal caused the abortion. To simplify this task a new variant of limit_then_abort was introduced in v.5.5.8. This variant allows to set an action to be preformed before calling std::abort(). This action can be used, for example, for logging: \code class my_agent : public so_5::rt::agent_t { public : my_agent( context_t ctx ) : so_5::rt::agent_t( ctx + // Specifying limit for a message and pre-abort action. limit_then_abort< request >( 10, // Pre-abort action as lambda. []( const so_5::rt::agent_t & agent, const request & req ) { std::cerr << "Overlimit of requests for: " << (dynamic_cast< const my_agent & >(agent)).query_name() << ", request ID: " << req.query_id() << std::endl; } ) ) ... const std::string & query_name() const { ... } }; \endcode That variant of limit_then_abort can be used for messages (as in example abort) and for signals. But for signals pre-abort action must have different format: \code return_type action(const so_5::rt::agent_t &); \endcode \attention Pre-abort actions must be noexcept-functions or lambda. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_8__new_method_agent_coop_deregister.dox000066400000000000000000000025671523065210100302150ustar00rootroot00000000000000/*! \page so_5_5_8__new_method_agent_coop_deregister so-5.5.8: New methods of agent_coop_t for deregistration For simplification of coop deregistration in presence of reference to agent_coop_t two new methods have been added in v.5.5.8. The first method, so_5::rt::agent_coop_t::deregister(), allows to specify reason of cooperation deregistration: \code so_5::rt::environment_t & env = ...; env.introduce_coop( []( so_5::rt::agent_coop_t & coop ) { coop.define_agent() .event< some_signal >( some_mbox, [&coop] { ... coop.deregister( so_4::rt::dereg_reason::user_defined_reason + 100 ); } ); } ); \endcode Please note that deregister() is just a shorhand for: \code so_5::rt::agent_coop_t & coop = ...; coop.environment().deregister_coop( coop.query_coop_name(), reason ); \endcode The second method, so_5::rt::agent_coop_t::deregister_normally(), allows to deregister coop with reason so_5::rt::dereg_reason::normal: \code so_5::rt::environment_t & env = ...; env.introduce_coop( []( so_5::rt::agent_coop_t & coop ) { coop.define_agent() .event< some_signal >( some_mbox, [&coop] { ... coop.deregister_normally(); } ); } ); \endcode Please note that deregister_normally() is just a shorhand for: \code so_5::rt::agent_coop_t & coop = ...; coop.deregister( so_5::rt::dereg_reason::normal ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_9__new_send_format.dox000066400000000000000000000014661523065210100246210ustar00rootroot00000000000000/*! \page so_5_5_9__new_send_format so-5.5.9: New format of send function Since v.5.5.9 the first argument to so_5::send() function can be either a reference to so_5::rt::mbox_t, a reference to so_5::rt::agent_t or a reference to ad-hoc definition proxy: \code so_5::rt::mbox_t mbox = env.create_local_mbox(); auto engine = coop.make_agent(...); auto indicator = coop.define_agent(); ... // Old way of sending message via so_5::send(): so_5::send< get_status >(mbox); so_5::send< get_status >(engine->so_direct_mbox()); so_5::send< get_status >(indicator.direct_mbox()); // New way: so_5::send< get_status >(mbox); so_5::send< get_status >(*engine); so_5::send< get_status >(indicator); \endcode It means that since v.5.5.9 so_5::send() can be used instead of so_5::send_to_agent(). */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_9__new_wrapped_env_class.dox000066400000000000000000000145061523065210100260160ustar00rootroot00000000000000/*! \page so_5_5_9__wrapped_env so-5.5.9: New wrapped_env class \section so_5_5_9__wrapped_env__purpose Purpose A traditional way of starting SO Environment is to use so_5::launch free function: \code so_5::launch( []( so_5::rt::environment_t & env ) { ... // Some app initializing code. } ); \endcode This way is useful if all application logic is implemented by SObjectizers agents and there is only one SO Environment in the application. But sometimes it is not the case. For example if an application has to have GUI-event handling loop. In that case applications main() function could look like: \code int main() { ... // Launching SO Environment. ... // Some application-specific initialization code. while(!GetMessage(...)) ProcessMessage(...); ... // Stopping SO Environment. ... // Some application-specific deinitialization code. } \endcode Usage of so_5::launch() in such cases is not as straightforward as intended to be. It could look like: \code int main() { so_5::rt::environment_t * env_ptr = nullptr; // To be set inside launch(); so_5::launch( [&]( so_5::rt::environment_t & env ) { env_ptr = &env; ... // Some SO Environment initialization code. } ); ... // Some application-specific initialization code. while(!GetMessage(...)) ProcessMessage(...); env_ptr->stop(); // Stopping SO Environment. // NOTE: there is no way to wait for a complete finish of SO Environment work! ... // Some application-specific deinitialization code. } \endcode It is not a simple approach and it could lead to mistakes. To simplify a development of applications with some GUI-event loops a new class has been added in v.5.5.9: so_5::wrapped_env_t. With that class the example above can be rewritten that way: \code int main() { so_5::wrapped_env_t env; // Empty SO Environment will be started here. env.environment().introduce_coop( []( so_5::rt::coop_t & coop ) { ... // Some SO Environment initialization code. } ); ... // Some application-specific initialization code. while(!GetMessage(...)) ProcessMessage(...); env.stop_then_join(); // Stopping SO Environment and wait for complete finish. ... // Some application-specific deinitialization code. } \endcode \section so_5_5_9__wrapped_env__work_principle Working Principle Working principle of so_5::wrapped_env_t is very simple: - a thread is created inside a constructor of wrapped_env_t; - an analog of so_5::launch is called on the context of that thread; - pointer to actual so_5::rt::environment_t instance is stored inside wrapped_env_t; - this thread is stopped and joined automatically in the destructor of so_5::wrapped_env_t. This way of working guarantees that a reference to actual so_5::rt::environment_t instance will be valid for the whole lifetime of wrapped_env_t object. \note If wrapped_env_t::join() or wrapped_env_t::stop_then_join() is called explicitely then the helper thread will be finished before exit from join()/stop_then_join() methods. A reference to so_5::rt::environment_t instance still will be valid, but this SO Environment will be stopped and cannot be used later. \note There is no way to restart SO Environment controlled by wrapped_env_t object after call to stop() or stop_then_join() methods. If such logic is necessary then consider destroyment and recreation of the whole wrapped_env_t object: \code while( some_condition ) { so_5::wrapped_env_t env{ ... }; ... // Some usage of SO Environment. env.stop_then_join(); // SO Environment completely stopped. ... // Some application-specific code. } \endcode \section so_5_5_9__wrapped_env__usage_examples Usage Examples There are several constructors for so_5::wrapped_env_t. The simplest one is the default constructor: \code // No any initial actions. Only default parameters will be used. so_5::wrapped_env_t env; \endcode New coops could be added later via traditional approaches: \code // No any initial actions. Only default parameters will be used. so_5::wrapped_env_t env; ... // Filling the environment with coops. // Via introduce_coop... env.environment().introduce_coop( []( so_5::rt::coop_t & coop ) {...} ); // Via create_coop/register_coop. auto coop = env.environment().create_coop( so_5::autoname ); coop->make_agent<...>(...); ... env.environment().register_coop( std::move(coop) ); \endcode There is also a constructor which receives a lambda-function or a functional object for doing initial actions right after start of SO Environment (just like in the case of so_5::launch): \code so_5::wrapped_env_t env{ // Lambda with initial actions. []( so_5::rt::environment_t & env ) { ... // Some initial actions like adding a new coop: env.introduce_coop(...); } }; \endcode There are also two constructors which allows to tune SO Environment parameters. One of them receives a rvalue reference to so_5::rt::environment_params_t object: \code so_5::rt::environment_params_t prepare_params() { so_5::rt::environment_params_t params; params.exception_reaction( so_5::rt::shutdown_sobjectizer_on_exception ); params.timer_thread( so_5::timer_list_factory() ); ... return params; } ... so_5::wrapped_env_t env{ // Initialization lambda. // Can be empty if no starting actions needed. []( so_5::rt::environment_t & ) {}, // Parameters for SO Environment. prepare_params() }; \endcode The second receives parameters' tuner lambda or functional object: \code so_5::rt::environment_params_t prepare_params() { so_5::rt::environment_params_t params; params.exception_reaction( so_5::rt::shutdown_sobjectizer_on_exception ); params.timer_thread( so_5::timer_list_factory() ); ... return params; } ... so_5::wrapped_env_t env{ // Initialization lambda. // Can be empty if no starting actions needed. []( so_5::rt::environment_t & ) {}, // Parameters tuner for SO Environment. []( so_5::rt::environment_params_t & params ) { params.exception_reaction( so_5::rt::shutdown_sobjectizer_on_exception ); params.timer_thread( so_5::timer_list_factory() ); } }; \endcode \section so_5_5_9__wrapped_env__autoshutdown Wrapped_env and Autoshutdown Option Class wrapped_env_t always adds disable_autoshutdown option to SO Environment parameters. It means that SO Environment will not be stopped after deregistration of the last coop. Only explicit call to wrapped_env_t::stop() or wrapped_ent_t::stop_then_join() stops SO Environment. SO Environment also automatically stopped in the wrapped_envs destructor. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5_9__request_future.dox000066400000000000000000000061131523065210100245230ustar00rootroot00000000000000/*! \page so_5_5_9__request_future so-5.5.9: New helper functions request_future and request_value A synchronous interaction between agents is a very useful mechanism for some situations. But usage of long method call chaings for that interaction is sometimes complex and confusing. For example: \code std::string v = converter_mbox->get_one() .wait_forever() .make_sync_get(42); \endcode Such long method call chains are there because there was a necessity to support of compilers without proper implementation of C++11 variadic templates. But such compilers are not supported now. It means that synchronous interactions can be simplified. Two new helper functions have been introduced in v.5.5.9: - so_5::request_future() for making synchronous request and receive the result in form of std::future without any waiting inside request_future(); - so_5::request_value() for making synchronous request and receive the result in form of value of T with waiting inside request_value(). The usage of request_future() and request_value() can be shown in comparison with old approach: \code // Old way of getting std::future: auto f1 = converter_mbox->get_one().make_async(42); // New way: auto f2 = so_5::request_future(converter_mbox, 42); // Old way of getting a value with infinite waiting: auto v1 = converter_mbox->get_one() .wait_forever() .make_sync_get(42); // New way: auto v2 = so_5::request_value(converter_mbox, so_5::infinite_wait, 42); // Old way of getting a value with finite waiting time: auto v3 = converter_mbox->get_one() .wait_for(std::chrono::milliseconds(150)) .make_sync_get(42); // New way: auto v4 = so_5::request_value(converter_mbox, std::chrono::milliseconds(150), 42); \endcode New functions also support signals as requests: \code struct get_status : public so_5::rt::signal_t {}; ... auto f1 = so_5::request_future(mbox); auto v1 = so_5::request_value(mbox, so_5::infinite_wait); \endcode The first argument for request_future() and request_value() can be either a so_5::rt::mbox_t, a reference to so_5::rt::agent_t or a reference to ad-hoc agent proxy. For example: \code env.introduce_coop( []( so_5::rt::agent_coop_t & coop ) { auto engine = coop.make_agent_with_binder< engine_agent >( so_5::disp::one_thread::create_private_disp()->binder(), ... ); auto indicator = coop.define_agent( so_5::disp::one_thread::create_private_disp()->binder() ); ... auto starter = coop.define_agent().on_start( [indicator, engine]{ // Synchronous signal to turn engine on. so_5::request_value( *engine ); // Synchronous signal to show engine status. so_5::request_value( indicator ); } ); } ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth__mchains.dox000066400000000000000000000454731523065210100246600ustar00rootroot00000000000000/*! \page so_5_5__in_depth__mchains so-5.5 In Depth - Message Chains \section so_5_5__in_depth__mchains__purpose Purpose The main purpose of message chain (or just mchain) mechanism is providing a way for interacting between SObjectizer- and non-SObjectizer-part of an application. The interaction in opposite direction is very simple: usual message passing via mboxes is used. For example: \code class worker : public so_5::agent_t { public: ... virtual void so_define_agent() override { // A mbox to be used to receive requests. auto mbox = so_environment().create_mbox("requests"); // Subscription to messages from that mbox... so_subscribe(mbox).event(&worker::request_handler1) .event(&worker::request_handler2) .event(&worker::request_handler3) ...; } ... }; ... // Somewhere in the main thread... so_5::wrapped_env_t sobj; launch_sobjectizer(...); auto mbox = sobj.environment().create_mbox("requests"); ... // Sending requests to the SObjectizer-part of the application. so_5::send(mbox, ...); so_5::send(mbox, ...); ... \endcode But how to receive some messages from an agent back to non-SObjectizer-part of the application? Message chain is the answer. Message chain looks almost like mbox for agents. An agent can send messages to mchain exactly the same way as for mbox. So mchain can be passed to agent and agent will use it as destination for reply messages. On the other side of a mchain will be non-SObjectizer message handler. This handler will receive messages from the mchain by using special API functions and handle them appropriately. \code class worker : public so_5::agent_t { public: worker(context_t ctx, so_5::mbox_t reply_to) : so_5::agent_t{ctx}, m_reply_to{std::move(reply_to)} {...} ... virtual void so_define_agent() override { // A mbox to be used to receive requests. auto mbox = so_environment().create_mbox("requests"); // Subscription to messages from that mbox... so_subscribe(mbox).event(&worker::request_handler1) .event(&worker::request_handler2) .event(&worker::request_handler3) ...; } private: // A mbox for sending responses. const so_5::mbox_t m_reply_to; ... // Request handler. void request_handler1(const request1 & r) { ... // Some processing. // Sending response back as a message. so_5::send(m_reply_to, ...); } ... }; ... // Somewhere in the main thread... so_5::wrapped_env_t sobj; // Chain for receiving responses. so_5::mchain_t reply_ch = sobj.environment().create_mchain( // Parameters for the simplest chain without any limitations. so_5::make_unlimited_mchain_params()); launch_sobjectizer( // Passing mchain as a parameter. // Cast it to mbox to look exactly like ordinary mbox. reply_ch.as_mbox(), ...); auto mbox = sobj.environment().create_mbox("requests"); ... // Sending requests to the SObjectizer-part of the application. // And receive responses. so_5::send(mbox, ...); receive(reply_ch, so_5::infinite_wait, [](const reply1 &) {...}); so_5::send(mbox, ...); receive(reply_ch, so_5::infinite_wait, [](const reply2 &) {...}); ... \endcode \section so_5_5__in_depth__mchains__more More Details \subsection so_5_5__in_depth__mchains__more__types Types of Mchains There are two types of mchains: 1. Unlimited mchains. Those mchains have no limitation for number of messages to be stored inside mchain. This number is limited only by the amount of available memory and by common sense of a developer. 2. Limited mchains. Those mchains have strict limit for number of messages. It is impossible to just push yet another message into full size-limited mchain. Type of mchain is specified at the creation time. Once created the type of mchain cannot be changed. Type of mchain is specified by content of `so_5::mchain_params_t` instance passed to `so_5::environment_t::create_mchain` method. There are three helper functions which return properly initialized `mchain_params_t` instances: \code // Create size-unlimited mchain. so_5::mchain_t m1 = env.create_mchain(so_5::make_unlimited_mchain_params()); // Create size-limited mchain without waiting on attempt of pushing new message // to the full mchain. so_5::mchain_t m2 = env.create_mchain( so_5::make_limited_without_waiting_mchain_params( 200, so_5::mchain_props::memory_usage_t::dynamic, so_5::mchain_props::overflow_reaction_t::drop_newest)); // Create size-limited mchain with waiting on attempt of pushing new message // to the full mchain. so_5::mchain_t m3 = env.create_mchain( so_5::make_limited_with_waiting_mchain_params( 200, so_5::mchain_props::memory_usage_t::dynamic, so_5::mchain_props::overflow_reaction_t::drop_newest, std::chrono::milliseconds(500))); \endcode The type `so_5::mchain_t` is like the type `so_5::mbox_t` -- it is an alias for smart intrusive pointer to `so_5::abstract_message_chain_t`. It means that mchain created by `create_mchain` will be destroyed automatically after destruction of the last mchain_t object pointed to it. \subsubsection so_5_5__in_depth__mchains__more__types__size_limited More About Size-limited Mchain Size-limited mchains have a serious difference from size-unlimited mchains: a size-limited mchain can't contain more message than the max capacity of the mchain. So there should be some reaction on attempt to add another message to full mchain. There could be size-limited mchains which will perform waiting for some time on attempt of pushing new message to full mchain. If there is a free place in the mchain after that waiting then new message will be stored into the mchain. An appropriate overload reaction will be performed otherwise. There also could be size-limited mchains without any waiting on full mchain. If there is no free room in the mchain then an appropriate overload reaction will be performed immediately. There are four overload reactions which can be selected for an mchain at the moment of mchain creation: 1. Dropping the new message. It means that new message will be simply ignored. 2. Removing of the oldest message from the mchain. It means that oldest message in the mchain will be removed and it will not be processed. 3. Throwing an exception. An exception `so_5::exception_t` with error code `so_5::rc_msg_chain_overflow` will be raised as result of attempt of pushing new message to full mchain. 4. Aborting the whole application by calling `std::abort()`. All those variants are described as items of enumeration `so_5::mchain_props::overflow_reaction_t`. There is yet another important property which must be specified for size-limited mchain at the creation time: the type of memory usage. Memory for storing messages inside mchain can be used dynamically: it would be allocated when mchain grows and deallocated when mchain shrinks. Or memory for mchain could be preallocated and there will be fixed-size buffer for messages. Size of that buffer will not change during growth and shrinking of the mchain. Types of memory usage are described as items of enumeration `so_5::mchain_props::memory_usage_t`. All those traits of size-limited mchains are controlled by `mchain_params_t` object passed to `create_mchain` method. The simplest and recommended way of preparing the corresponding `mchain_params_t` is usage of `make_limited_without_waiting_mchain_params` and `make_limited_with_waiting_mchain_params` helper functions: \code so_5::environment_t & env = ...; so_5::mchain_t chain1 = env.create_mchain( so_5::make_limited_without_waiting_mchain_params( // No more than 200 messages in the chain. 200, // Memory will be allocated dynamically. so_5::mchain_props::memory_usage_t::dynamic, // New messages will be ignored on chain's overflow. so_5::mchain_props::overflow_reaction_t::drop_newest)); so_5::mchain_t chain2 = env.create_mchain( so_5::make_limited_with_waiting_mchain_params( // No more than 200 messages in the chain. 200, // Memory will be preallocated. so_5::mchain_props::memory_usage_t::preallocated, // The oldest message will be removed on chain's overflow. so_5::mchain_props::overflow_reaction_t::remove_oldest, // But before dropping a new message there will be 500ms timeout std::chrono::milliseconds(500))); \endcode \subsection so_5_5__in_depth__mchains__more__receive How To Receive And Handle Messages From Mchain? There are two variants of `so_5::receive` function which allows to receive and handle messages from a mchain. The first variant of `receive` takes a `mchain_t`, a timeout and a list of message handlers: \code so_5::mchain_t ch = env.create_mchain(...); ... receive(ch, std::chrono::milliseconds(500), [](const reply1 & r) {...}, [](const reply2 & r) {...}, ... [](const replyN & r) {...}); \endcode It checks the mchain and wait for no more than 500ms if the mchain is empty. If the mchain is not empty it extracts just one message from the mchain and tries to find an appropriate handler for it. If handler is found it is called. If a handler is not found then the message extracted will be thrown out without any processing. If the mchain is empty even after the specified timeout then `receive` will do nothing. There are two special values which can be used as timeout: - value `so_5::no_wait` specifies zero timeout. It means that `receive` will not wait if the mchain is empty; - value `so_5::infinite_wait` specifies unlimited waiting. The return from `receive` will be on arrival of any message. Or if the mchain is closed explicitly. There is also more advanced version of `so_5::receive` which can receive and handle more than one message from mchain. It receives a `so_5::mchain_receive_params_t` objects with list of conditions and returns control if any of those conditions becomes true. For example: \code // Handle 3 messages. // If there are no 3 messages in the mchan then wait for them. // The receive returns control when 3 messages will be processed or when the mchain // will be closed explicitly. receive( from(chain).handle_n( 3 ), handlers... ); // Handle 3 messages but wait no more than 200ms on empty mchain. // If mchain is empty for more than 200ms then receive returns control even // if less than 3 messages were handled. receive( from(chain).handle_n( 3 ).empty_timeout( milliseconds(200) ), handlers... ); // Handle all messages which are going to the mchain. But handling will be finished // if a pause between arrivals of messages exceeds 500ms. receive( from(chain).empty_timeout( milliseconds(500) ), handlers... ); // Handle all messages for 2s. // The return from receive will be after 2s or if the mchain is closed explicitly. receive( from(chain).total_time( seconds(2) ), handlers... ); // The same as previous but the return can occurs after extraction of 1000 messages. receive( from(chain).extract_n( 1000 ).total_time( seconds(2) ), handlers... ); \endcode There is a difference between a number of extracted messages and a number of handled messages. A message is extracted from a mchain and count of extracted messages increases. But the count of handled messages is increased only if a handler for that message type if found and called. It means that the number of extracted messages can be greater than number of handled messages. Both `receive` return an object of `so_5::mchain_receive_result_t` type. Methods of that object allow to get number of extracted and handled messages, and also the status of `receive` operation. The usage of advanced version of `receive` could look like: \code so_5::send< a_supervisor::ask_status >( req_mbox ); auto r = receive( from( chain ).empty_timeout( milliseconds(200) ), []( a_supervisor::status_idle ) { cout << "status: IDLE" << endl; }, []( a_supervisor::status_in_progress ) { cout << "status: IN PROGRESS" << endl; }, []( a_supervisor::status_finished v ) { cout << "status: finished in " << v.m_ms << "ms" << endl; } ); if( !r.handled() ) cout << "--- no response from supervisor ---" << endl; \endcode \subsubsection so_5_5__in_depth__mchains__more__receive__handlers Message Handlers Message handlers which are passed to `receive` functions family must be lambda-functions or functional objects with format: \code return_type handler(const message_type&); return_type handler(message_type); \endcode For example: \code struct classical_message : public so_5::message_t { ... }; struct user_message { ... }; ... receive(chain, so_5::infinite_wait, [](const classical_message & m) {...}, [](const user_message & m) {...}, [](const int & m) {...}, [](long m) {...}); \endcode A handler for signal of type `signal_type` can be created by the help of `so_5::handler()` function: \code struct stopped : public so_5::signal_t {}; struct waiting : public so_5::signal_t {}; ... receive(chain, so_5::infinite_wait, [](const classical_message & m) {...}, [](const user_message & m) {...}, so_5::handler( []{...} ), so_5::handler( []{...} )); \endcode NOTE! All message handlers must handle different message types. It is an error if some handlers are defined for the same message type. \subsection so_5_5__in_depth__mchains__more__send_funcs Usage Of Mchains With Send Functions All traditional send-functions like `so_5::send`, `so_5::send_delayed` and `so_5::send_periodic` work with mchains the same way they work with mboxes. It allows to write code the traditional way: \code so_5::mchain_t ch = env.create_mchain(...); ... // Sending a message. so_5::send(ch, ... ); // some args for my_message's constructor. // Sending a delayed message. so_5::send_delayed(ch, std::chrono::milliseconds(200), ... ); // some args for my_message's constructor // Sending a periodic message. auto timer_id = so_5::send_periodic(ch, std::chrono::milliseconds(200), std::chrono::milliseconds(250), ... ); // some args for my_message's constructor \endcode The functions for performing service request, `so_5::request_value` and `so_5::request_future`, work with mchain too. It means that an agent can make a service request to non-SObjectizer part of an application and receive the result of that request as usual: \code // Somewhere in SObjectizer part of an application... class worker : public so_5::agent_t { ... const so_5::mchain_t m_request_ch; void some_event_handler() { auto f = so_5::request_future(m_request_ch, ... ); // some args for request's constructor ... handle_response(f.get()); } }; ... // Somewhere in non-SObjectizer part of an application... const so_5::mchain_t request_ch = ...; ... receive(from(request_ch).handle_n(1000), [](const request & req) -> response {...}, ...); \endcode \subsection so_5_5__in_depth__mchains__more__as_mbox Mchains as Mboxes There is a method `so_5::abstract_message_chain_t::as_mbox()` which can represent mchain as almost ordinary mbox. This method returns `so_5::mbox_t` and this mbox can be used for sending messages and performing service request to the mchain. Method `as_mbox()` can be useful if there is a necessity to hide the fact of mchain existence. For example, an agent inside SObjectizer part of an application can receive mbox and think that there is another agent on the opposite side: \code // SObjectizer part of an application. class worker : so_5::agent_t { public: worker(context_t ctx, so_5::mbox_t request_mbox) : so_5::agent_t{ctx}, m_request_mbox{std::move(request_mbox)} {} ... private: const so_5::mbox_t m_request_mbox; ... void some_event_handler() { auto f = so_5::request_future(m_request_mbox, ... ); // some args for request's constructor ... handle_response(f.get()); } }; ... // Non-SObjectizer part of an application. so_5::mchain_t ch = env.create_mbox(...); env.introduce_coop([&](so_5::coop_t & coop) { coop.make_agent( // Passing mchain as mbox to worker agent. ch->as_mbox()); ... } ); ... receive(from(ch).handle_n(1000), [](const request & req) -> response {...}, ...); \endcode The only difference between ordinary mbox created by `so_5::environment_t::create_mbox()` and mbox created by `as_mbox()` is impossibility of subscriptions creation. It means that agent `worker` from the example above can't subscribe its event handlers to messages from `m_request_mbox`. The only case when mchain can be passed to SObjectizer part directly as mchain (without casting it to mbox by `as_mbox()`) is a necessity of explicit closing of mchain in the SObjectizer part of an application: \code class data_producer : public so_5::agent_t { public: data_producer(context_t ctx, so_5::mchain_t data_ch) : so_5::agent_t{ctx}, m_data_ch{std::move(data_ch)} {} ... private: const so_5::mchain_t m_data_ch; ... void read_data_event() { auto data = read_next_data(); if(data_read()) so_5::send(m_data_ch, data); else { // Data source is closed. No more data can be received. // Close the data chain and finish out work. m_data_ch->close(); so_deregister_coop_normally(); } } }; ... // Non-SObjectizer part of an application. so_5::mchain_t ch = env.create_mbox(...); env.introduce_coop([&](so_5::coop_t & coop) { coop.make_agent( // Passing mchain as mchain to data_reader agent. ch); ... } ); ... // Receive and handle all data samples until the chain will be closed. receive(from(ch), [](const data_sample & data) {...}); \endcode \subsection so_5_5__in_depth__mchains__more__notificator Non-Empty Notificator For Mchain There is a possibility to specify a function which will be called automatically when a message in stored into the empty mchain: \code so_5::environment_t & env = ...; so_5::mchain_t ch = env.create_mchain( so_5::make_unlimited_mchain_params().non_empty_notificator( []{ ... } )); \endcode This feature can be used in GUI applications for example. Some widget can create mchain and needs to know when there are messages in that mchain. Then widget can add notificator to the mchain and send some GUI-message/signal to itself from that notificator. Some of `receive` functions will be called in processing of that GUI-message/signal. \subsection so_5_5__in_depth__mchains__more__mpsc Mchain Has Multi-Producer/Single-Consumer Queue The mchain mechanism introduced in v.5.5.13 uses MPSC queue inside. It means that `receive` on a mchain must be called from one thread. There will be no any damage if several `receive` will be called for one mchain from different threads at the same time (no messages will be lost or processed several times). But there could be not efficient work of that threads (like threads starvation). */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_agent_states.md000066400000000000000000000613561523065210100253640ustar00rootroot00000000000000# so-5.5 In Depth - Agent States {#so_5_5__in_depth__agent_states} # Basic Agent in SObjectizer is a finite-state machine. The behaviour of an agent depends on the current state of the agent and the received message. An agent can receive and process different messages in each state. In other words an agent can receive a message in one state but ignore it in another state. Or, if an agent receives the same message in several states, it can handle the message differently in each state. Lets imagine a simple agent which controls LED indicator on some device. It receives just one signal turn_on_off. When LED indicator is off this signal should turn indicator on. If LED indicator is on this signal should turn indicator off. This logic can be represented by simple statechart: ![](https://svn.code.sf.net/p/sobjectizer/repo/branches/so_5/5.5.15--dev-trunk/doxygen/dox/so_5/led_indicator_statechart.png?p=2181) It's easy to see that led_indicator agent requires two states: `off` and `on`. They can be directly expressed in C++ code via usage of `so_5::state_t` class. The definition of new state for an agent means creation of new instance of state_t. States are usually represented as members of agents class: ~~~~~{.cpp} class led_indicator final : public so_5::agent_t { state_t off{ this }, on{ this }; ~~~~~ A state can have a textual name: ~~~~~{.cpp} class led_indicator final : public so_5::agent_t { state_t off{ this, "off" }, on{ this, "on" }; ~~~~~ It could be useful for debugging and logging. There are several ways of changing agents state: ~~~~~{.cpp} // Very old and basic way. so_change_state( off ); // More modern and short way. off.activate(); // Yet more modern way. this >>= off; ~~~~~ The current state can be obtained via `so_current_state()` method: ~~~~~{.cpp} if( off == so_current_state() ) ... // 'off' is active state ~~~~~ Method `so_is_active_state()` can be used for checking of state activity: ~~~~~{.cpp} if( so_is_active_state(off) ) ... // 'off' is active state ~~~~~ Every agent already has one state: the default one. The default state can be accessed via `so_default_state()` method: ~~~~~{.cpp} // Returning agent to the default state. this >>= so_default_state(); ~~~~~ Even ad-hoc agents have the default state. But it is the only one state they have. Because there is no user-defined class for an ad-hoc agent then there is no possibility to define new states for ad-hoc agent. Thus there is no possibility to change state of ad-hoc agent. The most important part of usage of agents states is subscription to a message with respect to a specific state. The simple usage of so_subscribe() and so_subscribe_self() methods leads to subscription only for the default agents state. It means that: ~~~~~{.cpp} so_subscribe_self().event(...); ~~~~~ is the equivalent of: ~~~~~{.cpp} so_subscribe_self().in( so_default_state() ).event(...); ~~~~~ To make subscription to a message for a specific state it is necessary to use in() method in a subscription chain: ~~~~~{.cpp} so_subscribe_self().in( off ).event(...); so_subscribe_self().in( on ).event(...); ~~~~~ The in() methods can be chained if the same event handler is used in several states: ~~~~~{.cpp} so_subscribe_self() .in( off ) .in( on ) .event< get_name >( [] -> std::string { return "led_indicator"; } ); so_subscribe_self() .in( off ) .event< get_status >( [] -> std::string { return "off"; } ); ~~~~~ There is another way to make subscription for a specific state: ~~~~~{.cpp} off.event< get_name >( [] -> std::string { return "led_indicator"; } ) .event< get_status >( [] -> std::string { return "off"; } ); on.event< get_name >( [] -> std::string { return "led_indicator"; } ) .event(...) ...; ~~~~~ There are also on_enter/on_exit methods in state_t class. Method on_enter sets up an enter handler. Enter handler is automatically called when the state is activated. Contrary on_exit method sets an exit handler. Exit handler is automatically called when the state is activated. Enter and exit handler can be a lambda-function or a pointer to member method of agent's class. **Note**: *enter and exit handlers must be `noexcept` functions.* In the led_indicator example enter and exit handlers are necessary for `on` state: ~~~~~{.cpp} on.on_enter( [this]{ ... } ); // some device-dependent code. .on_exit( [this]{ ... } ); // some device-dependent code. ... ~~~~~ And now we can write the full code of led_indicator example: ~~~~~{.cpp} class led_indicator final : public so_5::agent_t { state_t off{ this, "off" }, on{ this, "on" }; public : struct turn_on_off : public so_5::signal_t {}; led_indicator( context_t ctx ) : so_5::agent_t{ ctx } { // Initial state for any agent is the default one. // We must switch agent to 'off' state manually. this >>= off; off.event< turn_on_off >( [this]{ this >>= on; } ); on.on_enter( [this]{ ... } ); // some device-dependent code .on_exit( [this]{ ... } ); // some device-dependent code .event< turn_on_off >( [this]{ this >>= off; } ); } }; ~~~~~ The led_indicator example demonstrates simple finite-state machine. Since v.5.5.15 SObjectizer supports more advanced features of agents' states like composite states, shallow- and deep-history, time limitations and so on. These advanced features allow to implement agents as hierarchical state machines. Let's see an ability to create hierarchical state machines on a slightly complex example: an agent which implements blinking of LED indicator. This agent receives `turn_on_off` signal for turning blinking on and off. When blinking is turned on then agent switches LED indicator on for 1s then switches it off for 1s then switches on again and so on until the agent receives next `turn_on_off` signal. A statechart for that agent can be represented as (note that this statechart represent yet more complex case which will be described later): ![](https://svn.code.sf.net/p/sobjectizer/repo/branches/so_5/5.5.15--dev-trunk/dev/sample/so_5/blinking_led/statechart.png?p=2176) Agent blinking_led wll have two top level states: `off` and `blinking`. State `blinking` is a composite state with two substates: `blink_on` and `blink_off`. In C++ code this can be expressed this way: ~~~~~{.cpp} class blinking_led final : public so_5::agent_t { state_t off{ this, "off" }, blinking{ this, "blinking" }, blink_on{ initial_substate_of{ blinking }, "on" }, blink_off{ substate_of{ blinking }, "off" }; ~~~~~ Substate `blink_on` is marked as initial substate of composite state blinking. It means that when state `blinking` is activated then substate `blink_on` is activated too. Moreover `so_current_state()` method will return a reference to `blink_on`, not to `blinking`. It is because `so_current_state()` always returns a reference to a leaf state in agent's state tree. Every composite state must have the initial substate. It means that exactly one substate must be declared by using `initial_substate_of` indicator: ~~~~~{.cpp} blink_on{ initial_substate_of{ blinking }, "on" }, blink_off{ substate_of{ blinking }, "off" }; ~~~~~ An attempt to activate a composite state without the initial substate defined will lead to an error at run-time. Definition of behaviour for composite states and substates is done usual way: ~~~~~{.cpp} off .event< turn_on_off >( [this]{ this >>= blinking; } ); blinking .on_enter( [this] { m_timer = so_5::send_periodic< timer >( *this, std::chrono::seconds::zero(), std::chrono::seconds{1} ); } ) .on_exit( [this]{ m_timer.release(); } ) .event< turn_on_off >( [this]{ this >>= off; } ); blink_on .on_enter( &blinking_led::led_on ) .on_exit( &blinking_led::led_off ) .event< timer >( [this]{ this >>= blink_off; } ); blink_off .event< timer >( [this]{ this >>= blink_on; } ); ~~~~~ It's easy to see that many events look like: ~~~~~{.cpp} event([this] { this >>= State; }); ~~~~~ This is very typical case in complex statecharts. There is a special method `just_switch_to()` which can simplify such cases. By using this method we can rewrite states behaviour that way: ~~~~~{.cpp} off .just_switch_to< turn_on_off >( blinking ); blinking .on_enter( [this] { m_timer = so_5::send_periodic< timer >( *this, std::chrono::seconds::zero(), std::chrono::seconds{1} ); } ) .on_exit( [this]{ m_timer.release(); } ) .just_switch_to< turn_on_off >( off ); blink_on .on_enter( &blinking_led::led_on ) .on_exit( &blinking_led::led_off ) .just_switch_to< timer >( blink_off ); blink_off .just_switch_to< timer >( blink_on ); ~~~~~ Note that reaction to `turn_on_off` signal is defined only in `off` and `blinking` states. There are no such handles in substates `blink_on` and `blink_off`. It is not necessary because substates inherit event handlers from their parent states. Inheritance of event handlers means that event handler will be searched in the current state, then in its parent state, then in its parent state and so on... In blinking_led agent an event handler for `turn_on_off` signal will be searched in `blink_on` and `blink_off` states and then in `blinking` state. That is why we don't need to create subscription for `turn_on_off` in `blink_on` and `blink_off` states. Now we can write the full code of blinking_led agent: ~~~~~{.cpp} class blinking_led final : public so_5::agent_t { state_t off{ this, "off" }, blinking{ this, "blinking" }, blink_on{ initial_substate_of{ blinking }, "on" }, blink_off{ substate_of{ blinking }, "off" }; struct timer : public so_5::signal_t {}; public : struct turn_on_off : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off .just_switch_to< turn_on_off >( blinking ); blinking .on_enter( [this] { m_timer = so_5::send_periodic< timer >( *this, std::chrono::seconds::zero(), std::chrono::seconds{1} ); } ) .on_exit( [this]{ m_timer.release(); } ) .just_switch_to< turn_on_off >( off ); blink_on .on_enter( &blinking_led::led_on ) .on_exit( &blinking_led::led_off ) .just_switch_to< timer >( blink_off ); blink_off .just_switch_to< timer >( blink_on ); } private : so_5::timer_id_t m_timer; void led_on() { ... } // some device-dependent code void led_off() { ... } // some device-dependent code }; ~~~~~ But what if we have to change the behaviour of our blinking_led agent? Let's imagine that blinking_led agent have to switch LED on for 1.5s and then switch it off for 0.7s. How can we do that? The obvious way is to use delayed signals with different timeouts in enter handlers for `blink_on` and `blink_off` states. But this way is not very easy in fact... Usage of `time_limit` feature is much simpler. The time_limit feature dictates SObjectizer to limit time spent in the specific state. A clause like: ~~~~~{.cpp} SomeState.time_limit(Timeout, AnotherState); ~~~~~ Tells the SObjectizer that agent must be automatically switched from `SomeState` to `AnotherState` after `Timeout` spent in `SomeState`. With `time_limit` feature the bllinking_led agent's implementation looks simpler and shorter: ~~~~~{.cpp} class blinking_led final : public so_5::agent_t { state_t off{ this, "off" }, blinking{ this, "blinking" }, blink_on{ initial_substate_of{ blinking }, "on" }, blink_off{ substate_of{ blinking }, "off" }; public : struct turn_on_off : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off .just_switch_to< turn_on_off >( blinking ); blinking .just_switch_to< turn_on_off >( off ); blink_on .on_enter( &blinking_led::led_on ) .on_exit( &blinking_led::led_off ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } private : void led_on() { ... } // some device-dependent code void led_off() { ... } // some device-dependent code }; ~~~~~ # More Details ## Do Not Change Agent State From Enter/Exit Handlers **An enter/exit handler should not change state of agent.** ## Class state_t Is Not Thread Safe Class `so_5::state_t` is not thread safe. It is designed to be used inside an owner agent only. For example: ~~~~~{.cpp} class my_agent : public so_5::agent_t { state_t first_state{ this, "first" }; state_t second_state{ this, "second" }; ... public : my_agent( context_t ctx ) : so_5::agent_t{ ctx } { // It is a safe usage of state. first_state.on_enter( &my_agent::first_on_enter ); second_state.on_exit( &my_agent::second_on_exit ); ... } virtual void so_define_agent() override { // It is a safe usage of state. first_state.event( &my_agent::some_event_handler ); second_state.time_limit( std::chrono::seconds{20}, first_state ); second_state.event( [this]( const some_message & msg ) { // It is also safe usage of state because event handler // will be called on the context of agent's working thread. second_state.drop_time_limit(); ... } ); } void some_public_method() { // It is a safe usage if this method is called by the agent itself. // This will be unsafe usage if this method is called from outside of // the agent: a data damage or something like that can happen. second_state.time_limit( std::chrono::seconds{30}, first_state ); } ... }; ~~~~~ Because of that it is necessary to be very careful during manipulation of agent's states outside of agent's event handlers. ## Limitation For Deep Of Substates There is a limitation for deep of substates in SObjectizer v.5.5.15: there can be at most 16 nested states. ## Enter/Exit Handlers And noexcept All enter/exit handlers must be `noexcept` functions. If an enter/exit handler throws an exception the whole application will be aborted. It is because there is no way to revert changes of agent's state change procedure if an enter/exit throws an exception. ## Guarantee For Calling Of Exit Handler If agent `A` is successfully registered and switched to state `S` then SObjectizer is guaranteed the call of exit handler for state `S` in the following cases: * switching to another state; * an invocation of `so_evt_finish()` as result of: deregistration of agent's coop or shutdown of SObjectizer Environment. An exit handler for `S` will be called after return from `so_evt_finish()`. It means that once agent is registered the SObjectizer guaratees the call of exit handler. But exit handler will not be called if agent is not registered. For example: ~~~~~{.cpp} class some_agent : public so_5::agent_t { state_t S{ this }; ... public : some_agent( context_t ctx ) : so_5::agent_t{ ctx } { S.on_exit(...); // some handler this >>= S; } ... }; ... env.introduce_coop( []( so_5::coop_t & coop ) { coop.make_agent< some_agent >(); ... // Some other actions. throw std::runtime_error( "Just a demo!" ); } ); ~~~~~ In this case an agent of type `some_agent` will be created, the initial state for that agent will be `S`, but exit handler for `S` will not be called because the failure of coop registration. What does it mean? It means that if you need to do some important task in exit handler then consider to switch agent to that state only after successful registration of your agent. For example: in `so_evt_start()` method: ~~~~~{.cpp} class some_agent : public so_5::agent_t { state_t S{ this }; ... public : some_agent( context_t ctx ) : so_5::agent_t{ ctx } { S.on_exit(...); // some important code in the handler. } ... virtual void so_evt_start() override { this >>= S; } }; ~~~~~ ## Shallow- And Deep-history SObjectizer supports states with shallow- and deep-histories. To define a state with history it is necessary to use a constructor of `state_t` with argument of type `state_t::history_t`: ~~~~~{.cpp} class demo : public so_5::agent_t { // An anonymous state with shallow history. state_t state1{ this, shallow_history }; // A state with name and shallow history. state_t state2{ this, "two", shallow_history }; // An anonymous state with deep history. state_t state3{ this, deep_history }; // A state with name and deep history. state_t state4{ this, "four", deep_history }; // An initial substate with name and deep history. state_t state5{ initial_substate_of{ state1 }, "five", deep_history }; // An anonymous substate with shallow history. state_t state6{ substate_of{ state1 }, shallow_history }; ... }; ~~~~~ There is `state_t::clear_history()` method. But `S.clear_history()` clears history only for state `S`. History of any substate of `S` remains intact: ~~~~~{.cpp} class demo : public so_5::agent_t { state_t A{ this, "A", deep_history }; state_t B{ initial_substate_of{ A }, "B", shallow_history }; state_t C{ initial_substate_of{ B }, "C" }; state_t D{ substate_of{ B }, "D" }; state_t E{ this, "E" }; ... void some_event() { this >>= A; // The current state is "A.B.C" // Because B is initial substate of A, and // C is initial substate of B. this >>= D; // The current state is "A.B.D". this >>= E; // The current state is "E". this >>= A; // The current state is "A.B.D" because deep history of A. this >>= E; A.clear_history(); this >>= A; // The current state is "A.B.D" because: // B is the initial substate of A and B has shallow history; // D is the last active substate of B. } }; ~~~~~ ## The transfer_to_state Feature Method `state_t::transfer_to_state()` is intended for defering an event to another state. For example: ~~~~~{.cpp} class device : public so_5::agent_t { state_t off{ this }; state_t on{ this }; public : struct volume_up : public so_5::signal_t {}; struct volume_down : public so_5::signal_t {}; ... virtual void so_define_agent() override { off.transfer_to_state< volume_up >( on ) .transfer_to_state< volume_down >( on ); on.event< volume_up >(...) // some reaction .event< volume_down >(...); // some reaction } }; ~~~~~ When agent device in state `off` and receives `volume_up` signal it automatically switches to state `on` and handler for `volume_up` is searched in state `on`. Note that `transfer_to_state` can change agent state several times: ~~~~~{.cpp} class demo : public so_5::agent_t { state_t A{ this }; state_t B{ this }; state_t C{ this }; public : struct do_something : public so_5::signal_t {}; ... virtual void so_define_agent() override { A.transfer_to_state< do_something >( B ); B.transfer_to_state< do_something >( C ); C.event< do_something >( ... ); ... } }; ~~~~~ If agent demo is in state `A` and receives `do_something` signal it switches from `A` to `B` and then to `C`. The `transfer_to_state` is a powerful but dangerous feature. There is no a limitation for the deep of transfers of event from one state to another. Because of that a user can create an infinite loop of transfering event between states. ## The suppress Feature Sometimes it is necessary to disable an event handler defined in parent state. It can be done by `state_t::suppress` method: ~~~~~{.cpp} class device : public so_5::agent_t { state_t off{ this }, on{ this }, number_selection{ initial_substate_of{ on } }, dialling{ substate_of{ on } }; ... public : ... virtual void so_define_agent() override { on.event< key_cancel >(...) // some reaction .event< key_grid >(...) // some reaction .event< key_digit >(...) // some reaction ...; dialling // Only key_cancel can be processed in dialling state. // Other events must be suppressed. .suppress< key_digit >() .suppress< key_grid >() .event< key_cancel >(...); // some reaction } }; ~~~~~ Without suppressing of `key_digit` and `key_grid` events in `dialling` state handlers for them will be automatically found in `on` state (because of inheritance of event handlers). ## Redefenition Of Enter/Exit Handler Redefenition of enter or exit handler can be necessary when class inheritance is used. For example: ~~~~~{.cpp} class basic_device : public so_5::agent_t { protected : state_t activated{ this }; ... public : ... virtual void so_define_agent() override { activated.on_enter( ... // some complex code here ) .event(...) ...; } }; ... class specific_device : public basic_device { public : ... virtual void so_define_agent() override { basic_device::so_define_agent(); ... // Addition of some specific action to activated.on_enter handler. auto old_handler = activated.on_enter(); activated.on_enter( [old_handler] { old_handler(); // Actions from base class. ... // some additional actions. ); } }; ~~~~~ ## Reseting Of time_limit The `state_t::time_limit()` method can be called several times in different places. Usually it is called only once somewhere in constructor or `so_define_agent()`, for example: ~~~~~{.cpp} blinking_led::blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { blink_on.time_limit( std::chrono::milliseconds{1500}, blink_off ) ...; blink_off.time_limit( std::chrono::milliseconds{750}, blink_on ) ...; ... } ~~~~~ But sometimes it is necessary to call `time_limit` in different places. It is possible and allowed. But there is a speciality: if `S.time_limit` is called when agent is already in `S` state then time will ticks on from the beginning. For example: ~~~~~{.cpp} class inactivity_watcher final : public so_5::agent_t { state_t off{ this }, active{ this }; public : inactivity_watcher( context_t ctx, const so_5::mbox_t & input ) : so_5::agent_t{ ctx } { off.transfer_to_state< key_cancel >( input, active ) .transfer_to_state< key_grid >( input, active ) .transfer_to_state< key_digit >( input, active ); // Switching to off state after 30 seconds of inactivity. active.time_limit( std::chrono::seconds{30}, off ) .event< key_cancel >( [this]{ update_timeout(); } ) .event< key_grid >( [this]{ update_timeout(); } ) .event( [this]( const key_digit & ){ update_timeout(); } ); } public : void update_timeout() { active.time_limit( std::chrono::seconds{30}, off ); } }; ~~~~~ ## Lifetime Of state_t Objects Class `state_t` was developed to be used as type of members of some agent class: ~~~~~{.cpp} class basic_device : public so_5::agent_t { state_t deactivated{...}; state_t activated{...}; ... }; class specific_device : public basic_device { state_t paused{...}; state_t stopped{...}; ... }; ~~~~~ It means that lifetime of `state_t` objects will be automatically synchronized with lifetime of their owners. This synchronization of lifetimes significantly simplifies implementation of various aspects of agents in SObjectizer. Because of that the recommended way of agent's state creation is declaration of `state_t` object as member of corresponding agent's class. But it is possible to create and destroy `state_t` objects manually. It is hard to imagine a situation were it can be necessary but it is possible to write something like that: ~~~~~{.cpp} class some_crazy_agent : public so_5::agent_t { std::vector< std::unique_ptr< state_t > > m_additional_states; ... void some_event() { // A bunch of states must be created. for( int i = 0; i < 20; ++i ) { auto s = std::make_unique< state_t >( this ); so_subscribe_self().in( *s ).event< some_signal >( ... ); m_additional_state.push_back( std::move(s) ); } } ... void some_another_event() { // Additional states no more needed. for( auto & s : m_additional_states ) { so_drop_subscription< some_signal >( so_direct_mbox(), *s ); s.reset(); } m_additional_states.clear(); } }; ~~~~~ But this approach is very dangerous because raw pointers to state objects are stored in different places (in agent as a pointer to the current state, in subscription storage, in history of parent state(s) and so on). If a pointer to state which must be destroyed manually is not removed from these places a dangling pointer will be created with corresponding consequences. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_arbitrary_message_type.dox000066400000000000000000000200141523065210100276230ustar00rootroot00000000000000/*! \page so_5_5__in_depth__arbitrary_msg_type so-5.5 In Depth - Messages of Arbitrary Types \section so_5_5__in_depth__arbitrary_msg_type__intro Introduction Prior to v.5.5.9 every message had to be represented by an instance of a class derived from so_5::rt::message_t. It means that if a single integer value need to be sent to an agent the whole dedicated class must have been defined. For example: \code enum class engine_action { turn_on, speed_up, slow_down, turn_off }; struct msg_engine_action : public so_5::rt::message_t { engine_action m_action; msg_engine_action( engine_action action ) : m_action{ action } {} }; ... // Message sending... so_5::send_to_agent< msg_engine_action >( engine_agent, engine_action::turn_on ); ... // Message processing void engine_agent::evt_engine_action( const msg_engine_action & msg ) { switch( msg.m_action ) {...} } \endcode Since v.5.5.9 there is a possibility to send an object of arbitrary type as a message. For example: \code enum class engine_action { turn_on, speed_up, slow_down, turn_off }; ... // Message sending... so_5::send_to_agent< engine_action >( engine_agent, engine_action::turn_on ); ... // Message processing... void engine_agent::evt_engine_action( engine_action msg ) { switch( msg ) {...} } \endcode \section so_5_5__in_depth__arbitrary_msg_type__how_it_works How It Works There is a very simple trick: when user calls send(mbox,args) and T is not derived from message_t then an instance of special template type so_5::rt::user_type_message_t is constructed and delivered as message. When an instance of so_5::rt::user_type_message_t is extracted from an event queue and event handler for it has the signature `R(const T&)` or `R(T)` then this event handler is called and a reference to the content of user_type_message_t is passed to that event handler. So it is possible to say that call to send(mbox, args) where T is not derived from message_t is replaced by that pseudocode: \code // send(mbox, args...): auto real_message = std::make_unique< user_type_message_t >(args...); mbox->deliver_message(std::move(real_message)); \endcode And the event handler in the form `R(const T&)` is replaced by: \code // R some_class::evt_handler(const T &): R some_class::real_evt_handler(const user_type_message_t & evt) { return evt_handler(evt.m_payload); } \endcode The user_type_message_t itself is a very simple template: \code template< typename T > struct user_type_message_t : public message_t { const T m_payload; template< typename... ARGS > user_type_message_t( ARGS &&... args ) : m_payload( T{ std::forward< ARGS >( args )... } ) {} }; \endcode \section so_5_5__in_depth__arbitrary_msg_type__requirements Requirements for Message Types A type T which is used as message type must be MoveConstructible or CopyConstructible. It is because of the way an instance of T is constructed inside user_type_message_t type: \code template< typename... ARGS > user_type_message_t( ARGS &&... args ) : m_payload( T{ std::forward(args)... } ) {} \endcode A temporary object of type T is created from constructor arguments and then this object is passed to constructor of user_type_message_t::m_payload member. \section so_5_5__in_depth__arbitrary_msg_type__how_to_send How to Send Messages of Arbitrary Types Messages of arbitrary types can only be sent via send-family functions: - so_5::rt::send() - so_5::rt::send_to_agent() - so_5::rt::send_delayed() - so_5::rt::send_delayed_to_agent() - so_5::rt::send_periodic() - so_5::rt::send_periodic_to_agent() Methods abstract_message_mbox_t::deliver_message(), environment_t::single_timer() and environment_t::schedule_timer() cannot be used with objects of arbitrary type T. \section so_5_5__in_depth__arbitrary_msg_type__how_to_receive How to Receive Messages of Arbitrary Types Messages of arbitrary type T must be handled by non-static methods with signature like: \code R event_handler(const T & ); // (1) R event_handler(T); // (2) \endcode Or by lambda-function with signatures like: \code R(const T&); // (1) R(T); // (2) \endcode But note that in the case (1) event-handler will receive a reference to the source message instance. In the case (2) a copy of message content will be passed to event handler. It can be costly if T is a heavy object. For example: \code struct new_config { ... }; enum class overload_status { normal_load, high_load }; ... class overload_controller : public so_5::rt::agent_t { public : ... virtual void so_define_agent() override { so_subscribe_self() .event( &overload_controller::evt_new_config ) .event( [this]( overload_status status ) { if( overload_status::high_load == status ) handle_overload(); } ); ... } void evt_new_config( const new_config & cfg ) {...} ... }; \endcode It is impossible to handle message of arbitrary type T by an event handler which receives const reference to event_data_t. \section so_5_5__in_depth__arbitrary_msg_type__syncs Synchronous Interaction via Messages of Arbitrary Types Messages of arbitrary type T can be used for synchronous agents interaction via methods make_async() and make_sync_get(). For example: \code const so_5::rt::mbox_t & converter_mbox = ...; auto f = converter_mbox->get_one().make_async(42); auto v = converter_mbox->get_one().wait_forever().make_sync_get(42); \endcode Helpers so_5::request_future() and so_5::request_value(), introduced in v.5.5.9, can also be used with messages of arbitrary user types: \code auto f = so_5::request_future< int, std::string >(converter_mbox, 42); auto v = so_5::request_value< int, std::string >(converter_mbox, so_5::infinite_wait, 42); \endcode \section so_5_5__in_depth__arbitrary_msg_type__delivery_filters Delivery Filters Delivery filters can be used for messages of arbitrary types. Delivery filter must be a callable object (lambda-function or function) with signatures: \code bool(const T&); // (1) bool(T); // (2) \endcode But note that in the case (1) filter will receive a reference to the source message instance. In the case (2) a copy of message content will be passed to filter. It can be costly if T is a heavy object. \code class sensor_listener : public so_5::rt::agent_t { public : ... virtual void so_define_agent() override { auto mbox = so_environment().create_local_mbox("sensor"); so_set_delivery_filter( mbox, []( int v ) { return v > -10 && v < 65; } ); so_subscribe(mbox).event( &sensor_listener::evt_value ); ... } ... void evt_value( int v ) { ... } }; \endcode \section so_5_5__in_depth__arbitrary_msg_type__limit_then_transform Arbitrary Message Types and limit_then_transform Messages of arbitrary type T can be handled by limit_then_transform helper method. But it is strongly recommended to use make_transformed for new message instance. For example: \code enum class overload_status { normal_load, high_load }; ... class request_processor : public so_5::rt::agent_t { public : request_processor( context_t ctx ) : so_5::rt::agent_t( ctx + limit_then_transform< std::string >( 1000, [this]( const std::string & ) { return make_transformed< overload_status >( overload_controller_mbox, overload_status::high_load ); } ) ) {...} ... }; \endcode \section so_5_5__in_depth__arbitrary_msg_type__event_data Arbitrary Message Types and event_data_t There is a possibility to receive message of arbitrary type T via event handler which accepts event_data_t object. This can be done in the following form: \code using namespace so_5::rt; enum class engine_action { turn_on, speed_up, slow_down, turn_off }; class engine_proxy : public agent_t { public : virtual void so_define_agent() override { so_subscribe_self().event( &engine_proxy::evt_action ); ... } ... private : void evt_action( const event_data_t< user_type_message_t< engine_action > > & evt ) { ... // Some handling. // Resending the same message instance. m_another_mbox->deliver_message( evt.make_reference() ); } }; \endcode Only ordinary agents can have event handles of that type. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_exceptions.dox000066400000000000000000000327701523065210100252540ustar00rootroot00000000000000/*! \page so_5_5__in_depth_exceptions so-5.5 In Depth - Exceptions \section so_5_5__in_depth_exceptions__intro Intro This article is dedicated to usage of exceptions. In particular: - why exceptions are used in SObjectizer Run-Time; - what exceptions are used by SObjectizer-5; - how SObjectizer-5.5 handles exceptions from agents; - how a custom exception logger could be set; - when and why SObjectizer can call std::abort(). \section so_5_5__in_depth_exceptions__why_exceptions Why SObjectizer Run-Time Uses Exceptions Exceptions are the primary way of error reporting in SObjectizer-5. There are no methods/functions which use return codes for indication of success or failure. Any method/function either finishes successfully or throws an exception. If a user does a valid call and here are all necessary resources (like RAM, OS threads and so on) then there is no source for a failure. But if something goes wrong (like insufficient amount of RAM or inability to start yet another OS thread) then returning an error code as indication of failure is not a good idea. Heavy usage of exceptions is a consequence of experience from several years with SObjectizer-4 in production. Error codes were used in SObjectizer-4. And the main lesson learned was: If an error handling could be forgotten then it will be forgotten. The most unpleasant error in SObjectizer-4 was unsuccessful subscription. If a subscription failed but error is not handled then the work of an agent will be continued. But the agent will work incorrectly. And it could take a long time before the incorrectness will be found and fixed. The main purpose of exceptions in SObjectizer-5 is to prevent the disregard of errors. Ignoring error code is easier than ignoring of an exception. Because of that all methods/functions like creating coops or subscribing of events throw exceptions in the case of error. More than four years of working with SObjectizer-5 show that SObjectizer-related exceptions are not an issue. Just because they are thrown when a programmer does something wrong. It is hard to get SObjectizer-5 to throw an exception in normal situation. For example, if agent creates a legal subscription for existing mbox there is no reason for exception except the case of lack of memory. But in this particular case it is better to throw an exception instead of ontinuation of execution without subscription made or abortion of a whole application. That is why SObjectizer-5.5 uses exceptions instead of error codes. \section so_5_5__in_depth_exceptions__what_exceptions What Exceptions Are Used By SObjectizer? SObjectizer-5 doesnt use a complex hierarchy of exception classes. There is only one class for representing SObjectizer-related exceptions: `so_5::exception_t`. It is derived from `std::runtime_error`. Method `exception_t::what()`, inherited from `std::exception`, returns textual description of an error. But there is `exception_t::error_code()` method which returns internal error code represented as int. All SObjectizer error codes are defined inside `so_5` namespace as constants with prefix `rc_` in their names: `rc_disp_start_failed`, `rc_parent_coop_not_found` and so on. So if it is necessary to catch, analyze and handle SObjectizer-related exception it could be done like that: \code try { ... // Some action with SObjectizer. } catch( const so_5::exception_t & x ) { if( so_5::rc_named_disp_not_found == x.error_code() ) ... // Create named dispatcher and try again. else throw; } \endcode \section so_5_5__in_depth_exceptions__from_agents Exceptions From Agents There is one simple rule: Normal agents should provide no-throw guarantee! It means that an exception should not go out of an event-handler. It is because SObjectizer doesnt know what to do with that exception. And doesnt know the actual state of agent: is the agent in the correct state and could process next message or the agents internals are broken and no more messages must be delivered to the agent. So the best way is to catch and handle all exception inside an agents event-handler. But what if an exception is going out anyway? When SObjectizer detects an exception of type `std::exception` (or derived from it) the following actions are performed: - the exception caught is logged by a special exception logger (more about it below); - virtual method `so_exception_reaction()` is called for the agent who threw the exception; - perform action(s) in dependence of `so_exception_reaction()` result. Virtual method so_exception_reaction() can return one of the following values: so_5::rt::abort_on_exception his value means that the whole application must be terminated immediately by calling `std::abort()`. so_5::rt::shutdown_sobjectizer_on_exception This value means that: - the agent who threw the exception will be switched to a special state (in that state it cannot handle other messages); - SObjectizer Environment in which the exception has been caught will be shutdowned the usual way. This value useful if agent provides basic exception guarantee (no resources leaks) and application can be shutdowned normally. so_5::rt::deregister_coop_on_exception This value means that: - the agent who threw the exception will be switched to a special state (in that state it cannot handle other messages); - the coop which holds the problematic agent will be deregistered. This value useful if agent provides basic exception guarantee (no resources leaks) and application can continue its work after deregistration of that coop. For example, the coop will be automatically registered again by some supervisor. so_5::rt::ignore_exception This value means that no more actions should be performed by SObjectizer. This value is useful if agent provides strong exception guarantee. SObjectizer assumes that no damage has been made and work could be safely continued. so_5::rt::inherit_exception_reaction This value means that actual exception reaction needs to be received from agents coop. SObjectizer call `exception_reaction()` method for agents coop. One of the values described above could be received. The appropriate action will be performed. But the value `so_5::rt::inherit_exception_reaction` could be returned by `agent_coop_t::exception_reaction()` method. In that case SObjectizer will call `exception_reaction()` method for the parent coop (if exists). And then for the parent of the parent and so on. If the top-most parent coop returns `inherit_exception_reaction` then `exception_reaction()` method will be called for SObjectizer Exception instance. That method usually returns `abort_on_exception`. By default `agent_t::so_exception_reaction()` and `agent_coop_t::exception_reaction()` return `inherit_exception_reaction` value. By default `environment_t::exception_reaction()` return `abort_on_exception`. So the application will be aborted in the case of an uncaught exception by default. A user can set appropriate exception reaction for the whole coop by `agent_coop_t::set_exception_reaction()` method: \code env.introduce_coop( []( so_5::rt::agent_coop_t & coop ) { coop.set_exception_reaction( so_5::rt::deregister_coop_on_exception ); ... } ); \endcode In that case an exception from any coops agent will lead to deregistration of the coop (but only if agents `so_exception_reaction()` returns `inherit_exception_reaction` value). An exception reaction for the whole SObjectizer Environment can be set via Environments parameters: \code so_5::launch( []( so_5::rt::environment_t & env ) { ... // Starting code. }, []( so_5::rt::environment_params_t & params ) { ... // Environment parameters tuning code. // Setting the exception reaction for the whole Environment. params.exception_reaction( so_5::rt::shutdown_sobjectizer_on_exception ); } ); \endcode \section so_5_5__in_depth_exceptions__coop_dereg_reason Coop Dereg Reason There is a possibility to know what the reason was behind the coop deregistration. When a coop is deregistered a special value must be passed to `environment_t::deregister_coop()`. This value is a *coop dereg reason*. There are several predefined values which are defined as constants in `so_5::rt::dereg_reason` namespace: - *normal*. Coops deregistration is a part of application logic; - *shutdown*. Deregistration is a part of Environments shutdown process; - *parent_deregistration*. Coop is deregistered because its parent coop is deregistered. - *unhandled_exception*. Coop is deregistered because of unhandled exception. There are several additional constants but they intended for use by a programmer, not SObjectizer. Coop dereg reason is passed to coop dereg notificator. So a notificator can use this value for performing some actions. For example for restarting a cooperation in the case of exception: \code #include #include void start_coop( so_5::rt::environment_t & env ) { env.introduce_coop( [&]( so_5::rt::agent_coop_t & coop ) { struct raise_exception : public so_5::rt::signal_t {}; // The single agent of the coop. // Will throw an exception after one second from registration. auto agent = coop.define_agent(); agent.on_start( [agent, &env] { so_5::send_delayed< raise_exception >( env, agent.direct_mbox(), std::chrono::seconds(1) ); } ) .event< raise_exception >( agent.direct_mbox(), [] { throw std::runtime_error( "Just a test exception" ); } ); // Tell SObjectizer to deregister the coop on exception. coop.set_exception_reaction( so_5::rt::deregister_coop_on_exception ); // Add notificator which will initiate reregistration of the coop. coop.add_dereg_notificator( []( so_5::rt::environment_t & env, const std::string & coop_name, const so_5::rt::coop_dereg_reason_t & why ) { std::cout << "Deregistered: " << coop_name << ", reason: " << why.reason() << std::endl; if( so_5::rt::dereg_reason::unhandled_exception == why.reason() ) start_coop( env ); } ); } ); } int main() { so_5::launch( []( so_5::rt::environment_t & env ) { // Register coop the first time. start_coop( env ); // Take some time to the example to work. std::this_thread::sleep_for( std::chrono::seconds( 5 ) ); env.stop(); } ); } \endcode \section so_5_5__in_depth_exceptions__ex_logger Custom Exception Logger Every unhandled exception is logged. Default exception logger uses `std::cerr` as an output stream. A user can set its own custom exception logger. An exception logger must be derived from class `so_5::rt::event_exception_logger_t`. At least one method, `log_exception()`, must be overridden and implemented. It could looks like: \code class spd_exception_logger : public so_5::rt::event_exception_logger_t { public : spd_exception_logger() : logger_( spdlog::rotating_logger_mt( "file_logger", "logs/exceptions", 10*1024*1024 ) ) {} virtual void log_exception( const std::exception & ex, const std::string & coop_name ) override { logger_->alert( "Unhandled exception from coop '{}': {}", coop_name, ex.what() ); } private : std::shared_ptr< spdlog::logger > logger_; }; \endcode There are two ways to set up a custom exception logger: The first one is usage of Environment parameters: \code so_5::launch( []( so_5::rt::environment_t & env ) { ... // Starting code. }, []( so_5::rt::environment_params_t & params ) { ... // Environment parameters tuning code. // Setting the exception logger. params.event_exception_logger( so_5::rt::event_exception_logger_unique_ptr_t{ new spd_exception_logger{} } ); } ); \endcode The second one is usage of Environments `install_exception_logger()` method: \code so_5::launch( []( so_5::rt::environment_t & env ) { env.install_exception_logger( so_5::rt::event_exception_logger_unique_ptr_t{ new spd_exception_logger{} } ); ... // Starting code. } ); \endcode The main difference between those approaches is the ability to change exception logger on working Environment via `install_exception_logger()` method. \section so_5_5__in_depth_exceptions__when_abort_is_called When and why SObjectizer calls std::abort()? There are some situations where SObjectizer cannot throw an exception and has to call `std::abort()` instead. One of them is nested exceptions during handling an uncaught exception from some agents event handler. For example: - SObjectizer calls `log_exception()` method for custom exception logger; - `log_exception()` throws an exception; - SObjectizer calls `std::abort()`. Another one is related to delayed and periodic messages. If the timer thread cant send a message instance then `std::abort()` will be called. It is because inability to send a delayed/periodic message is a violation of an important contract: if delayed/periodic message was successfully scheduled then it must be sent. There is no other way to report a violation of that contract except calling the `std::abort()` function. There are some other situations when SObjectizer calls `std::abort()`. They are related to problems during recovery from some previous errors. There is a possibility of errors during registration of some coop. An attempt to create another working thread in a dispatcher can fail. SObjectizer will revert all changes which have been made before that failure. If some exception is thrown during this recovery SObjectizer has no choice except to call `std::abort()`. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_message_delivery_filters.dox000066400000000000000000000246051523065210100301500ustar00rootroot00000000000000/*! \page so_5_5__in_depth_message_delivery_filters so-5.5 In Depth - Message Delivery Filters There are situations when an agent must filter the messages received. A good example is receiving run-time monitoring information from SObjectizer Environment (see \ref so_5_5__in_depth_runtime_monitoring): there are lot of messages and an agent have to handle only few of them: \code void timer_thread_stats_listener::evt_timer_quantities( const so_5::rt::stats::messages::quantity< std::size_t > & evt ) { // Ignore messages unrelated to timer thread. if( so_5::rt::stats::prefixes::timer_thread() != evt.m_prefix ) return; ... // Processing of related to timer thread messages. } \endcode This kind of filtering of inappropriate messages is not efficient and has significant run-time cost. Every message the agent subscribed for is passed to agent's queue, then enqueued, then passed to agent's event handler. Only to be thrown out. Message delivery filters are more efficient message-filtering mechanism. Message delivery filter -- is a predicate which gets the message instance and returns `true` if message is allowed to be delivered to corresponding recipient or `false` if message must be thrown out. For the example above a message delivery filter can be set as: \code void timer_thread_stats_listener::so_define_agent() { so_set_delivery_filter( so_environment().stats_controller().mbox(), []( const so_5::rt::stats::messages::quantity< std::size_t > & msg ) { return so_5::rt::stats::prefixes::timer_thread() == msg.m_prefix; } ); ... } \endcode In v.5.5.5 only ordinary agents can set delivery filters. And delivery filters must be represented as lambda-function (or functional object) with just one argument -- constant reference to message instance. Delivery filter lambda-function must return `bool`. An agent stores all the delivery filters defined. All delivery filters are destroyed automatically at the end of agent's lifetime. A delivery filter can be explicitly dropped and destroyed by the `so_drop_delivery_filter()` method: \code void timer_thread_stats_listener::evt_some_action() { so_drop_delivery_filter< so_5::rt::stats::messages::quantity< std::size_t > >( so_environment().stats_controller().mbox() ); ... } \endcode If there is need to change the delivery filter it is enough to call `so_set_delivery_filter()` yet another time without the need of calling `so_drop_delivery_filter()`: \code // Setting the filter first time. so_set_delivery_filter( mbox, []( const some_message & msg ) { return ...; // some predicate } ); ... // Some messages could sent to mbox here. // Changing the filter by new one. so_set_delivery_filter( mbox, []( const some_message & msg ) { return ... // another predicate } ); ... // Some messages could sent to mbox here. \endcode Method `so_set_delivery_filter()` sets the reference to delivery filter to the appropriate message mbox. That mbox now knows that before message delivery for the corresponding recipient the delivery filter must be invoked to check the necessity of message delivery. If delivery filter returns `false` message is not stored to the recipient queue at all. Since v.5.5.5 multi-producer multi-consumer mboxes work this way: - there are lists of subscribers for every message type; - every subscriber in that list can have an optional delivery filter; - during message delivery mbox walks through that list and checks delivery filter for every subscriber. If there is a delivery filter and it returns `false` then the message is not delivered to that subscriber. If there is no delivery filter or delivery filter returns `true` then the message is stored to the subscriber's event queue. Please note that delivery filter is applied to a message only once -- during message dispatching inside mbox. When a message is stored in an agent's event queue and then extracted for processing the message instance no more checked. It can be very important if delivery filter is replaced by new one. For example: \code void my_agent::evt_demo() { // All actions are performed inside an event handler. It means // that agent can handle all the messages sent only after finish // of that event handler. // MPMC-mbox is necessary. const auto mbox = so_environment().create_local_mbox(); // Create event subscription for message. so_subscribe( mbox ).event( []( const current_temperature & evt ) { cout << "temperature is: " << evt.value() << endl; } ); // Send the first message. Temperature is 10 degrees. so_5::send< current_temperature >( mbox, 10 ); // Sets the first filter. so_set_delivery_filter( mbox, []( const current_temperature & evt ) { // Allows only messages with values outside of normal range [0,45]. return !( 0 <= evt.value() && evt.value() <= 45 ); } ); // Send the second message. Temperature is -10 degress. so_5::send< current_temperatur >( mbox, -10 ); // Replace the filter with another. so_set_delivery_filter( mbox, []( const current_temperature & evt ) { // Allows only messages with values in very narrow range. return ( 20 <= evt.value() && evt.value() <= 22 ); } ); // Send the third message. Temperature is 21 degrees. so_5::send< current_temperature >( mbox, 21 ); } \endcode In that case an agent will receive all three messages. The first one because there was no any filter during send operation. The second one because it passes the first delivery filter. And the third one because it passes the second delivery filter. This approach could be a surprise for a developer if the developer thinks that delivery filter applies for messages which is already in agent queue. It is not true. Important note. A delivery filter can be set only for MPMC-mbox. It is impossible to set a delivery filter to a MPSC (direct) mbox. It is because main purpose of MPSC-mbox is to be very fast and efficient direct channel to an agent. Any additional checking will slow down the work of MPSC-mbox. There are some demands to delivery filters implementations: - delivery filter must be as fast as possible. It is because a delivery filter is called during message delivery operation and long working time can slow down part or the whole application significantly; - delivery filter must be thread-safe. It is because it will be called on different working threads, probably at the same time; In the ideal case a delivery filter must be represented by a small and light side-effect free lambda-function (like in the example above). The delivery filter setup and event subscription is a different, not tightly related operations. It is possible to set up delivery filter without subscribing to that message. It is also possible to subscribing to message without setting a delivery filter up. In the situation when a delivery filter is set but there are no subscriptions to the message the message box stores only filter. The mbox knows that there are no subscriptions to the message and will not call delivery filter during a message dispatch. A delivery filter and subscription for a message can be handled independently. For example: \code using namespace std; using namespace so_5; using namespace so_5::rt; // Message to be filtered. using msg_sample = tuple_as_message_t< mtag<0>, int, string >; // A signal for doing second part of example. struct msg_second_part : public signal_t {}; // A signal for finish the example. struct msg_shutdown : public signal_t {}; // Main example agent. // Ordinary agent is necessary because a delivery filter can be set // only by ordinary agent. class a_example_t : public agent_t { public : a_example_t( context_t ctx ) : agent_t( ctx ) , m_mbox( so_environment().create_local_mbox() ) {} virtual void so_define_agent() override { so_subscribe_self() .event< msg_second_part >( &a_example_t::evt_second_part ) .event< msg_shutdown >( [this] { so_deregister_agent_coop_normally(); } ); } virtual void so_evt_start() override { // Subscribe for the message. Without filter. so_subscribe( m_mbox ).event( []( const msg_sample & evt ) { cout << "[first]: " << get<0>(evt) << "-" << get<1>(evt) << endl; } ); // Sending several messages... // All of them will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "only-subscription" ); send< msg_sample >( m_mbox, 1, "only-subscription" ); // Setting a delivery filter for message. so_set_delivery_filter( m_mbox, []( const msg_sample & evt ) { return 1 == get<0>(evt); } ); // Sending several messages... // Only one will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "subscription-and-filter" ); send< msg_sample >( m_mbox, 1, "subscription-and-filter" ); // Take time for processing already queued messages. send_to_agent< msg_second_part >( *this ); } void evt_second_part() { // Drop the subscription. so_drop_subscription< msg_sample >( m_mbox ); // Sending several messages... // No one of them will be stored to the agent's queue nor handled. send< msg_sample >( m_mbox, 0, "only-filter" ); send< msg_sample >( m_mbox, 1, "only-filter" ); // Subscribe for the message again. so_subscribe( m_mbox ).event( []( const msg_sample & evt ) { cout << "[second]: " << get<0>(evt) << "-" << get<1>(evt) << endl; } ); // Sending several messages... // Only one will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "subscription-and-filter-2" ); send< msg_sample >( m_mbox, 1, "subscription-and-filter-2" ); // Changing the filter to new one. so_set_delivery_filter( m_mbox, []( const msg_sample & evt ) { return 0 == get<0>(evt); } ); // Sending several messages... // Only one will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "subscription-and-filter-3" ); send< msg_sample >( m_mbox, 1, "subscription-and-filter-3" ); // Dropping the filter. so_drop_delivery_filter< msg_sample >( m_mbox ); // Sending several messages... // All of them will be stored to the agent's queue and handled. send< msg_sample >( m_mbox, 0, "only-subscription-2" ); send< msg_sample >( m_mbox, 1, "only-subscription-2" ); // Example could be finished. send_to_agent< msg_shutdown >( *this ); } private : // Separate MPMC-mbox is necessary for delivery filter. const mbox_t m_mbox; }; \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_msg_tracing.dox000066400000000000000000000334151523065210100253650ustar00rootroot00000000000000/*! \page so_5_5__in_depth_msg_tracing so-5.5 In Depth - Message Delivery Tracing \section so_5_5__in_depth_msg_tracing__what_is_it What Is It? Message delivery tracing is a mechanism for showing details of the process of message dispatching and consuming. This mechanism is disabled by default and must be enabled explicitly. When enabled the message delivery tracing shows every important step of a message delivery. \section so_5_5__in_depth_msg_tracing__purpose Purpose The main purpose of message delivery tracing is a debugging of an application written on top of SObjectizer-5. There are many situations where a programmer can make a mistake during event’s subscriptions or message sending: there could not be a subscription for a message, or subscription can be made for different state, or message can be sent to different message box. As result a message will not be processed. But how to find a root of such error? Until v.5.5.9 there were no any help from SObjectizer in searching such mistakes. Message delivery tracing shows main stages of message delivery process. It makes possible to see is there an appropriate subscriber with an appropriate event handler? Since v.5.5.9 you can get a trace of message deliveries during an application run and analyze those traces. Please note that message delivery tracing has a huge performance impact. Because of that message delivery tracing should be enabled for testing and debugging purposes only. Do not enable message delivery tracing in production. \section so_5_5__in_depth_msg_tracing__usage Usage Message delivery tracing is enabled by passing an instance of so_5::msg_tracing::tracer_t to environment_params during the start of SObjectizer Environment. There are three functions those create ready to use tracer objects: so_5::msg_tracing::std_cout_tracer(), so_5::msg_tracing::std_cerr_tracer() and so_5::msg_tracing::std_clog_tracer(). They use std::cout, std::cerr and std::clog streams for logging traces. The easiest way of enabling message delivery tracing is the usage of one of those functions. For example: \code so_5::launch( []( so_5::rt::environment_t & env ) {...}, []( so_5::rt::environment_params_t & params ) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer() ); } } ); \endcode Please note that message delivery tracing can be enabled only at the start of SObjectizer Environment and can't be disabled during the work of the Environment. \section so_5_5__in_depth_msg_tracing__features Features Message delivery tracing mechanism logs the following stages in the message dispatching process: - absence of subscribers for a message sent to an mbox; - rejection of a message for a particular subscriber (by a delivery filter); - pushing of a message to an event queue for a particular subscriber; - performing a reaction to an overlimit of message's quantity for a particular subscriber; - searching of event handler for a message for a particular subscriber. It is a basic set of information to find a trace of message instance route during delivering and handling. This set can be extended in the future versions of SObjectizer. \section so_5_5__in_depth_msg_tracing__how_to_read How To Read Tracing Data This section contains a short overview of the data included in message delivery tracing. \subsection so_5_5__in_depth_msg_tracing__how_to_read__common_cases Common Cases An output of example chstate_msg_tracing is used for showing most widespead cases (that example is a part of SObjectizer distribution since v.5.5.9). Every trace line contains several tags with values and short stage description. Tags are enclosed in square braces. Stage description is represented in action_name.action_desc form. The first line from output to review is: \verbatim [tid=3][mbox_id=4] deliver_message.push_to_queue [msg_type=N17a_state_swither_t16greeting_messageE] [envelope_ptr=0x836440][payload_ptr=0x836450] [overlimit_deep=1][agent_ptr=0x8379f0] \endverbatim This line tells about pushing a message to event queue inside deliver_message (short description is deliver_message.push_to_queue). This action takes place at thread with ID 3 (tid=3). A message is sent to message box with ID 4 (mbox_id=4). Message type is a_state_switcher_t::greeting_message (its mangled name is in tag msg_type). Tag with msg_ptr and payload_ptr values shows pointer to message object and pointer to actual payload inside that object. This message is an instance of user-type message. It is delivered as an envelope of so_5::rt::user_type_message_t type with an instance of greeting_message inside. Because of that there are two tags with pointers. The first one is a pointer to message envelope (this tag is marked as envelope_ptr). The second one is a pointer to message payload, e.g. to greeting_message instance (this tag is marked as payload_ptr). Tag overlimit_deep shows deep of overlimit actions recursion. Value 1 tells that there is no such recursion and this is the first attempt of message delivery. Tag agent_ptr shows a pointer to the receiver of the message. The message will be pushed to the event queue of that agent. The next line to review is: \verbatim [tid=2][agent_ptr=0x8379f0] demand_handler_on_message.find_handler [mbox_id=4][msg_type=N17a_state_swither_t16greeting_messageE] [envelope_ptr=0x836440][payload_ptr=0x836450] [state=][evt_handler=0x837fe8] \endverbatim This line tells about searching an event handler for a message. The search is performed on different thread (tid=2). It is because the search is performing on the context of agent’s working thread. A pointer to agent is in tag agent_ptr. It is easy to see that this is the same agent as the agent-receiver from the previous trace line. It means that message has been extracted from event queue and passed to the receiver to be processed. The receiver has done search for event handler and the result of that search is shown. The tags mbox_id shows ID of mbox from which the message has been received. Tags envelope_ptr and payload_ptr has the same meaning as described above. Tag state shows the name of the current state of the agent. The value "" means that the agent is in the default state. Tag evt_handler shows a pointer to event handler for the messages. In this case that pointer is not null. That means that event handler is found and the message will be processed. The next line to review is: \verbatim [tid=3][mbox_id=4] deliver_message.push_to_queue [msg_type=N17a_state_swither_t19change_state_signalE] [signal] [overlimit_deep=1][agent_ptr=0x8379f0] \endverbatim This is a trace of deliver_message operation at the stage of pushing the message to an event queue of an agent-receiver. All tags have been described above but there is no envelope_ptr and payload_ptr tags, but there is a \[signal] tag. It is because a_state_switcher_t::change_state_signal is a signal. Signals are delivered without actual message objects. Because of that there is no envelope and there is no any payload. The next line to review is about searching the event handler for change_state_signal: \verbatim [tid=2][agent_ptr=0x8379f0] demand_handler_on_message.find_handler [mbox_id=4][msg_type=N17a_state_swither_t19change_state_signalE] [signal] [state=][evt_handler=0x837fa8] \endverbatim It is easy to see that agent is in the default state and that the event handler for the signal is found. The next line to review is again about dispatching an instance of greeting_message to an agent-receiver: \verbatim [tid=3][mbox_id=4] deliver_message.push_to_queue [msg_type=N17a_state_swither_t16greeting_messageE] [envelope_ptr=0x836440][payload_ptr=0x836450] [overlimit_deep=1][agent_ptr=0x8379f0] \endverbatim All tags are already described above. But it is necessary to note that pointers to the envelope and to the payload are the same as in the very first delivery attempt. It is because greeting_message is a periodic message. For periodic messages the same message envelope is delivered again and again. The next line shows the case when event handler for a message is not found: \verbatim [tid=2][agent_ptr=0x8379f0] demand_handler_on_message.find_handler [mbox_id=4][msg_type=N17a_state_swither_t16greeting_messageE] [envelope_ptr=0x836440][payload_ptr=0x836450] [state=state_1][evt_handler=NONE] \endverbatim Tag state has value "state_1". It is the name of the current state of agent. And tag evt_handler has value "NONE". That value means that there is no event handler for greeting_message in the state "state_1". Because of that the message will be ignored. \subsection so_5_5__in_depth_msg_tracing__how_to_read__special_cases More Special Cases This section breifly explains rare traces which can be seen in complex message delivery scenarios. \subsubsection so_5_5__in_depth_msg_tracing__how_to_read__special_cases__svc_request Service Request This line is an example of trace from delivery of service request: \verbatim [tid=7][mbox_id=4] deliver_service_request.push_to_queue [msg_type=6finish] [envelope_ptr=0x7db460][signal] [overlimit_deep=1][agent_ptr=0x7d82b0] \endverbatim There is a slightly different description: deliver_service_request instead of deliver_message. Service request is delivered as an envelope of some internal SObjectizer's type. Actual message object (if any) is in that envelope as a payload. Because of that there is tag envelope_ptr. Tag signal is there because a signal is used as a service request. In the case of message as service request tag payload_ptr will be used in a trace. \subsubsection so_5_5__in_depth_msg_tracing__how_to_read__special_cases__absence Absence of Subscribers In the case when there is no subscribers for a message or service request the trace could look like: \verbatim [tid=6][mbox_id=4] deliver_service_request.no_subscribers [msg_type=6finish] [envelope_ptr=0x1be3f90][signal] [overlimit_deep=1] \endverbatim \subsubsection so_5_5__in_depth_msg_tracing__how_to_read__special_cases__rejection Rejection of a Message This line shows the case when a message is rejected by a subscriber: \verbatim [tid=3][mbox_id=4] deliver_message.message_rejected [msg_type=N8a_test_t9dummy_msgE] [envelope_ptr=0x5b80e0][payload_ptr=0x5b80ec] [overlimit_deep=1][agent_ptr=0x5b72d0] \endverbatim Message rejection occurs if a message is blocked by a delivery filter defined by a subcriber. \subsubsection so_5_5__in_depth_msg_tracing__how_to_read__special_cases__overlimit Overlimit Reactions There are several examples of traces for cases of overlimit reactions. This is overlimit reaction of type 'abort the application': \verbatim [tid=3][mbox_id=4] deliver_message.overlimit.abort [msg_type=N8a_test_t9dummy_msgE] [envelope_ptr=0x7c9730][payload_ptr=0x7c973c] [overlimit_deep=1][agent_ptr=0x7c98f0] \endverbatim This trace is done just before aborting the application. This is overlimit reaction of type 'drop the message': \verbatim [tid=3][mbox_id=4] deliver_message.overlimit.drop [msg_type=N8a_test_t9dummy_msgE] [envelope_ptr=0x48a470][payload_ptr=0x48a47c] [overlimit_deep=1][agent_ptr=0x489f60] \endverbatim This is overlimit reaction of type 'redirect the message': \verbatim [tid=3][mbox_id=5] deliver_message.overlimit.redirect [msg_type=5hello][signal] [overlimit_deep=1][agent_ptr=0x5a6af0] ==> [mbox_id=4] \endverbatim A signal of type 'hello' is redirected from mbox with ID 5 to mbox with ID 4 (destination mbox is shown in tag mbox_id after separator '==>'). The next line in trace shows the result of that redirection: an attempt to deliver that signal via mbox with ID 4 to different subscriber: \verbatim [tid=3][mbox_id=4] deliver_message.push_to_queue [msg_type=5hello][signal] [overlimit_deep=2][agent_ptr=0x5a69a0] \endverbatim Please note value 2 in overlimit_deep tag. This value means that this is next recursion level in overlimit processing for that signal. This is overlimit reaction of type 'transform the message': \verbatim [tid=3][mbox_id=5] deliver_message.overlimit.transform [msg_type=5hello][envelope_ptr=0x817e50] [payload_ptr=0x817e5c][overlimit_deep=1][agent_ptr=0x818500] ==> [mbox_id=4][msg_type=3bye][envelope_ptr=0x8165f0][payload_ptr=0x816600] \endverbatim This line shows a transformation of message of type 'hello' from mbox with ID 5 to a message of type 'bye' which is redirected to mbox with ID 4 (information about the new message is in tags after separator '==>'). As result of that transformation there is the next line about dispatching the new message: \verbatim [tid=3][mbox_id=4] deliver_message.push_to_queue [msg_type=3bye] [envelope_ptr=0x8165f0][payload_ptr=0x816600] [overlimit_deep=2][agent_ptr=0x816ac0] \endverbatim Again a value of overlimit_deep tag shows that this is the next recursion level in overlimit processing of a message delivery. \section so_5_5__in_depth_msg_tracing__own_tracer Writing Own Tracer A user can write its own implementation of so_5::msg_tracing::tracer_t interface: it is not a hard task because tracer_t contains just one virtual method. For example it is easy to write something like this: \code class demo_file_tracer : public so_5::msg_tracing::tracer_t { private : std::ofstream m_to; std::mutex m_lock; // To protect in the case of traces from different threads std::string make_timestamp() {...} // some timestamp-related stuff public : demo_file_tracer( const char * file_name ) : m_to{ file_name } {} virtual void trace( const std::string & msg ) noexcept override { std::lock_guard< std::mutex > lock{ m_lock }; m_to << make_timestamp() << ": " << msg << std::endl; } }; \endcode This tracer can be set as message delivery tracer during the preparation to the start of SObjectizer Environment: \code so_5::launch( []( so_5::rt::environment_t & env ) { ... }, []( so_5::rt::environment_params_t & params ) { params.message_delivery_tracer( std::make_unique< demo_file_tracer >( "msg_tracing.log" ) ); } ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_mutable_messages.md000066400000000000000000000324241523065210100262150ustar00rootroot00000000000000# so-5.5 In Depth - Mutable Messages {#so_5_5_in_depth__mutable_messages} # Introduction Since the very beginning there were only immutable messages in SObjectizer-5. Immutable message is a very simple and safe approach to implement an interaction in a concurrent application: * a message instance can be received by any number of receivers at the same time; * a message can be redirected to any number of new receivers; * a message can be stored to be processed later... Because of that immutable messages are very useful in 1:N or N:M interactions. And because Publish-Subscribe Model was the first model supported by SObjectizer-5 the interaction via immutable messages is used by default. But there can be cases when immutable message is not a good choice in 1:1 interaction... ## Example 1: Big Messages Let's consider a pipeline of agents which need to modify a big binary object... A message like this: ~~~~~{.cpp} struct raw_image_fragment final : public so_5::message_t { std::array raw_data_; ... }; ~~~~~ That need to be processed by a pipeline like that: ~~~~~{.cpp} class first_modificator final : public so_5::agent_t { void on_fragment(mhood_t cmd) { ... // Some modification of cmd's contents. next_stage_->deliver_message(cmd.make_reference()); // Send to the next. } ... }; class second_modificator final : public so_5::agent_t { void on_fragment(mhood_t cmd) { ... // Some modification of cmd's contents. next_stage_->deliver_message(cmd.make_reference()); // Send to the next. } ... }; ~~~~~ But... It won't be compiled! ~~~~~{.cpp} class first_modificator final : public so_5::agent_t { void on_fragment(mhood_t cmd) { cmd->raw_data_[0] = ...; // WON'T COMPILE! cmd->raw_data_ is const! ... next_stage_->deliver_message(cmd.make_reference()); // Send to the next. } ... }; class second_modificator final : public so_5::agent_t { void on_fragment(mhood_t cmd) { cmd->raw_data_[1] = ...; // WON'T COMPILE! cmd->raw_data_ is const! ... next_stage_->deliver_message(cmd.make_reference()); // Send to the next. } ... }; ~~~~~ A safe way is copy, modify and send modified copy... ~~~~~{.cpp} class first_modificator final : public so_5::agent_t { void on_fragment(mhood_t cmd) { auto cp = std::make_unique(*cmd); cp->raw_data_[0] = ...; ... next_stage_->deliver_message(std::move(cp)); // Send to the next. } ... }; class second_modificator final : public so_5::agent_t { void on_fragment(mhood_t cmd) { auto cp = std::make_unique(*cmd); cp->raw_data_[1] = ...; ... next_stage_->deliver_message(std::move(cp)); // Send to the next. } ... }; ~~~~~ It's obvious that the safe way is a very, very inefficient... ## Example 2: Messages With Moveable Data Inside Let's consider a case where agent Alice opens a file and then transfers opened file to agent Bob: ~~~~~{.cpp} struct process_file final : public so_5::message_t { // A message to transfer opened file. std::ifstream file_; process_file(std::ifstream file) : file_(std::move(file)) {} }; class Alice final : public so_5::agent_t { ... void on_handle_file(mhood_t cmd) { std::ifstream file(cmd->file_name()); // Try to open... if(file) so_5::send(bob_mbox, std::move(file)); // Transfer file to Bob. } }; class Bob final : public so_5::agent_t { ... void on_process_file(mhood_t cmd) { ... // Some file processing code. } }; ~~~~~ But if we try to do something like that: ~~~~~{.cpp} class Bob final : public so_5::agent_t { ... void on_process_file(mhood_t cmd) { std::ifstream file(std::move(cmd->file_)); // (1) ... // Processing file content. } }; ~~~~~ We will get a compile-time error at point (1) because cmd->file\_ is const and can't be moved anywhere... ## There Are Some Workarounds Of Course... You can declare fields of your messages as mutable: ~~~~~{.cpp} struct raw_image_fragment final : public so_5::message_t { mutable std::array raw_data_; ... }; class first_modificator final : public so_5::agent_t { void on_fragment(mhood_t cmd) { cmd->raw_data_[0] = ...; // Now it works. ... next_stage_->deliver_message(cmd.make_reference()); // Send to the next. } ... }; ~~~~~ But what if your message is received by two agents at the same time? There is no any guarantee that message will be delivered only to the single receiver... Or you can use shared_ptr instead of object itself: ~~~~~{.cpp} struct process_file final : public so_5::message_t { // A message to transfer opened file. std::shared_ptr file_; process_file(std::shared_ptr file) : file_(std::move(file)) {} }; class Alice final : public so_5::agent_t { ... void on_handle_file(mhood_t cmd) { auto file = std::make_shared(cmd->file_name()); // Try to open... if(*file) so_5::send(bob_mbox, std::move(file)); // Transfer file to Bob. } }; ~~~~~ But there is additional memory allocation and additional level of data indirection. Overhead can be significant if you need to transfer small objects like mutexes. # The Real Solution: Mutable Messages Since v.5.5.19 a message of type Msg can be sent either as immutable one: ~~~~~{.cpp} so_5::send(dest, ... /* Msg's constructor args */); so_5::send_delayed(dest, pause, ... /* Msg's constructor args */); ~~~~~ and as mutable one: ~~~~~{.cpp} so_5::send>(dest, ... /* Msg's constructor args */); so_5::send_delayed>(dest, pause, ... /* Msg's constructor args */); ~~~~~ To receive and handle a mutable message an event handler must have on of the following formats: ~~~~~{.cpp} result_type handler(so_5::mhood_t>); result_type handler(so_5::mutable_mhood_t); ~~~~~ Note, that `mutable_mhood_t` is just a shorthand for `mhood_t>`. Usage of `mutable_mhood_t` makes code more compact and concise. But `mhood_t>` can be used in templates: ~~~~~{.cpp} template // Can be Msg or mutable_msg class demo : public so_5::agent_t { ... void on_message(mhood_t cmd) { ... } }; ~~~~~ With mutable messages the examples above can be rewritten that way: **An example with big messages:** ~~~~~{.cpp} struct raw_image_fragment final : public so_5::message_t { std::array raw_data_; ... }; class first_modificator final : public so_5::agent_t { void on_fragment(mutable_mhood_t cmd) { cmd->raw_data_[0] = ...; // Now it works. ... so_5::send(next_stage_, std::move(cmd)); // Send to the next. } ... }; class second_modificator final : public so_5::agent_t { void on_fragment(mutable_mhood_t cmd) { cmd->raw_data_[1] = ...; // Now it works. ... so_5::send(next_stage_, std::move(cmd)); // Send to the next. } ... }; ~~~~~ **An example with moveable object inside:** ~~~~~{.cpp} struct process_file final : public so_5::message_t { // A message to transfer opened file. std::ifstream file_; process_file(std::ifstream file) : file_(std::move(file)) {} }; class Alice final : public so_5::agent_t { ... void on_handle_file(mhood_t cmd) { std::ifstream file(cmd->file_name()); // Try to open... if(file) so_5::send>(bob_mbox, std::move(file)); // Transfer file. } }; class Bob final : public so_5::agent_t { ... void on_process_file(mutable_mhood_t cmd) { std::ifstream file(std::move(cmd->file_)); // Now it works because cmd->file_ is not const. ... // Some file processing code. } }; ~~~~~ # Safety Of Mutable Messages But why sending of a mutable message is safer that sending an immutable message with mutable fields inside? Are there some guarantees from SObjectizer? A mutable message can be sent only to MPSC mbox or mchain. It means that there can be at most one receiver of the message. An attempt to send mutable message to MPMC mbox will lead to an exception at run-time. A `mutable_mhood_t` works just like `std::unique_ptr`: *when you redirect your mutable message to someone else your mutable_mhood_t becomes nullptr.* It means that you lost your access to mutable message after redirection: ~~~~~{.cpp} void on_fragment(mutable_mhood_t cmd) { cmd->raw_data_[0] = ...; // Now it works. ... so_5::send(next_stage_, std::move(cmd)); // cmd is a nullptr now! cmd->raw_data_[0] = ...; // It will lead to access violation or something similar. } ~~~~~ These all mean that only one receiver can have access to mutable message instance at some time. This property can't be satisfied for immutable message. And this makes usage of mutable messages safe. # Immutable And Mutable Message Are Different Mutable message of type `M` has different type than immutable message of type `M`. It means that an agent can have different event handlers for mutable and immutable `M`: ~~~~~{.cpp} class two_handlers final : public so_5::agent_t { struct M final {}; public : two_handlers(context_t ctx) : so_5::agent_t(std::move(ctx)) { so_subscribe_self() .event(&two_handlers::on_immutable_M) .event(&two_handlers::on_mutable_M); } virtual void so_evt_start() override { so_5::send(*this); // Immutable message is sent. so_5::send>(*this); // Mutable message is sent. } private : void on_immutable_M(mhood_t) { std::cout << "on immutable" << std::endl; } void on_mutable_M(mhood_t>) { std::cout << "on mutable" << std::endl; } }; ~~~~~ # Mutable Messages In Synchronous Interaction A mutable message can be used for service requests (e.g. for synchronous interactions): ~~~~~{.cpp} class service_provider final : public so_5::agent_t { public : service_provider(context_t ctx) : so_5::agent_t(std::move(ctx)) { // Service request handler. so_subscribe_self().event([](mutable_mhood_t cmd) { *cmd = "<" + *cmd + ">"; // Modify incoming message. return std::move(*cmd); // Return modified value. }); } ... }; ... so_5::mbox_t provider_mbox = ...; // Must be MPSC mbox or mchain. auto r = so_5::request_value>( // Initiate request. provider_mbox, so_5::infinite_wait, "hello"); ~~~~~ But note: mutable service request can be sent only into MPSC-mbox or mchain. # Conversion Into An Immutable Message When a mutable message is received via `mutable_mhood_t` and then redirected via `send` or `request_value/future` then redirected message will also be a mutable message. It means that redirected message can be sent only to one subscriber and can be handled only via `mutable_mhood_t`. Sometimes it is necessary to remove mutability of a message and send the message as immutable one. It can be done via `to_immutable` helper function. Helper function `to_immutable` converts its argument from `mutable_mhood_t` into `mhood_t` and returns message hood to immutable message. This new message hood can be used as parameter for `send`, `request_value` or `receive_future`. Old mutable message hood becomes a nullptr and can't be used anymore. ~~~~~{.cpp} void some_agent::on_some_message(mutable_mhood_t cmd) { ... // Some actions with the content of cmd. // Now the mutable message will be resend as immutable one. so_5::send(another_mbox, so_5::to_immutable(std::move(cmd))); // NOTE: cmd is a nullptr now. It can't be used anymore. ... } ~~~~~ Note: a mutable message can be converted to immutable message only once. An immutable message can't be converted into mutable one. # Mutable Messages And Timers Mutable messages can be sent by send_delayed functions: ~~~~~{.cpp} // It is a valid call: so_5::send_delayed>( so_environment(), dest_mbox, std::chrono::milliseconds(200), // Delay before message appearance. ... // some_message's constructor args. ); ~~~~~ Mutable messages can't be sent as periodic messages. It means that send_periodic can be used with mutable_msg only if a period parameter is zero: ~~~~~{.cpp} // It is a valid call: auto timer = so_5::send_periodic>( so_environment(), dest_mbox, std::chrono::milliseconds(200), // Delay before message appearance. std::chrono::milliseconds::zero(), // Period is zero. ...); // It isn't a valid call. An exception will be thrown at run-time. auto timer = so_5::send_periodic>( so_environment(), dest_mbox, std::chrono::milliseconds(200), // Delay before message appearance. std::chrono::milliseconds(150), // Period is not zero. ...); ~~~~~ # Signals Can't Be Mutable Signals do not carry any information inside. Because of that there is no sense in `mutable_msg` where S is a signal type. Because of that an attempt to use `mutable_msg` in code will lead to compile-time error if S is a signal. sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_priority_of_agents.dox000066400000000000000000000301261523065210100267720ustar00rootroot00000000000000/*! \page so_5_5__in_depth_priority_of_agents so-5.5 In Depth - Priorities of Agents \section so_5_5__in_depth_priority_of_agents__intro Intro A new feature was introduced in v.5.5.8 -- agents priorities. Every agent now has a priority. Priority is just an optional mark for dispatchers. Some dispatcher can use this mark for priority-specific events scheduling, some dispatcher cant. Several new dispatchers with support for agents priorities were added in v.5.5.8. They will be described below. \section so_5_5__in_depth_priority_of_agents__basics Basics Agents priority is represented as enumeration `so_5::priority_t`. There are only 8 priorities: from the lowest (so_5::priority_t::p0) to the highest (so_5::priority_t::p7). A priority for an agent can be specified during agents construction. \code // For ordinary agents: class my_agent : public so_5::rt::agent_t { public : my_agent( context_t ctx, ... ) : so_5::rt::agent_t( ctx + so_5::priority_t::p3 ) ... }; // For ad-hoc agents: coop.define_agent( coop.make_agent_context() + so_5::priority_t::p3 ) ... \endcode Once agent has been constructed the priority cannot be changed. There is a namespace so_5::prio with some useful helper constants and functions. For example instead of writing so_5::priority_t::p3 it is possible to write so_5::prio::p3. Or even p3 under `using namespace so_5::prio` construct. \section so_5_5__in_depth_priority_of_agents__disps Dispatchers With Support For Priorities Version 5.5.8 adds three new dispatchers which support agents priorities: prio_one_thread::strictly_ordered, prio_one_thread::quote_round_robin, prio_dedicated_threads::one_per_prio. They do priority-specific handling of agents events. Old dispatchers from previous versions (one_thread, active_obj, active_group, thread_pool, adv_thread_pool) do not support priorities. \subsection so_5_5__in_depth_priority_of_agents__disps__one_thread_vs_dedicated Common Threads Vs Dedicated Threads There is a principal difference in event handling with respect to agents priorities: will all events for agents with different priorities be handled on the same working thread or events with different priorities will be handled on its own, dedicated threads. Handling of events with different priorities on the context of common working threads allows to do several important things. At first, there is a possibility to strict ordering of events with respect to priorities. It means that event with higher priority will block events with lower priority. At second there is a possibility for easy sharing some data between agents with different priorities. Because all agents will work on the same thread there is no need for synchronize access to shared data. Those things are impossible when events with different priorities are handled on different threads. Because of that new dispatchers with priorities support are grouped into two group: prio_one_thread (it means that all events are processed on the same working thread) and prio_dedicated_thread (it means that events will different priorities will be processed on different threads). \subsection so_5_5__in_depth_priority_of_agents__disps__one_thread Dispatchers With Common Thread For All Priorities Group prio_one_thread consists of two dispatchers: strictly_ordered and quoted_round_robin. \subsubsection so_5_5__in_depth_priority_of_agents__disps__one_thread__so Dispatcher prio_one_thread::strictly_ordered Working Principles This dispatcher allows for events of high priority agents to block events of low priority agents. It means that events queue is always strictly ordered: events for agents with high priority are placed before events for agents with lower priority. For example if event queue is (e1(a7),e2(a6),e3(a4),e4(a4)), where a7 means an agent with priority p7, then events will be handled in exact that order. After handling e1 the queue will be (e2(a6),e3(a4),e4(a4)). If e5 for a5 arrived then the queue will become (e2(a6),e5(a5),e3(a4),e4(a4)). It means that chronological order of events will be preserved only for events of agents with the same priority. This dispatcher could be useful if there is a necessity of handling some messages before other messages. For example there could be a stream of tasks represented by take_job messages. There also could be a special message for task processors reconfiguration: new_config message. It could have a sense to handle new_config as soon as possible. This can be done by two agents which are bound to single prio_one_thread::strictly_ordered dispatcher. One agent will have priority p1 and will handle new_config message. Second agent will have priority p0 and will handle take_job. Both agents will have common shared data (at least configuration parameters, may be something else). Dispatcher prio_one_thread::strictly_ordered guarantees that new_config will be handled as soon as processing of previous message finished. Synopsis The dispatcher is defined in so_5::disp::prio_one_thread::strictly_ordered namespace. There are several traditional functions for dispatcher creation. Function create_disp creates public dispatcher which should be added to list of named dispatchers: \code so_5::launch( []( so_5::rt::environment_t & env ) { env.introduce_coop( so_5::disp::prio_one_thread::strictly_ordered::create_disp_binder( "strict_prio" ), []( so_5::rt::agent_coop_t & coop ) { ... } ); ... }, []( so_5::rt::environment_params_t & params ) { params.add_named_dispatcher( strict_prio", so_5::disp::prio_one_thread::strictly_ordered::create_disp() ); ... } ); \endcode There is also create_private_disp function for creation of private instance of dispatcher: \code so_5::launch( []( so_5::rt::environment_t & env ) { env.introduce_coop( so_5::disp::prio_one_thread::strictly_ordered::create_private_disp(env)->binder(), []( so_5::rt::agent_coop_t & coop ) { ... } ); ... } ); \endcode \subsubsection so_5_5__in_depth_priority_of_agents__disps__one_thread__qrr Dispatcher prio_one_thread::quoted_round_robin Working Principles Dispatcher prio_one_thread::quoted_round_robin works on round-robin principle. It allows to specify maximum count of events to be processed consequently for the specified priority. After processing that count of events dispatcher switches to processing events of lower priority even if there are yet more events of higher priority to be processed. Dispatcher uses very simply working scheme: it handles no more than Q7 events of priority p7, then no more than Q6 events of priority p6, ..., then no more than Q0 events of priority p0. If an event of higher priority is arrived during handling a quote for lower priority no switching is performed. For example if dispatcher handles events of priority p5 and event of priority p7 is arrived the dispatcher will continue to handle events of priority p5, then events of priority p4 (if any), ..., then events of priority p0 (if any). And only then the dispatcher will start to process events of priority p7. Events inside the same priority are handled in chronological order. This working scheme means that agents priorities treated as agents weight. A programmer can set bigger quotes for more prioritized (more heavyweight) agents and that agents will receive more resources than less prioritized (less weighted) agents. A dispatcher of that type can be useful, for example, if there are agents which handles clients of different types. Some clients are VIP clients and they should receive first-class quality of service and there could be other clients with lower demands for service quality. It is possible to assign a high priority to agents for handling VIP-client requests and specify large quote for that priority. All other agents will have lower priority and smaller quote. As result more requests from VIP-clients will be handled but there also will be processing of request from other clients. Synopsis The dispatcher is defined in so_5::disp::prio_one_thread::quoted_round_robin namespace. There are several traditional functions for dispatcher creation. Quotes for priorities must be defined before creation of a dispatcher. It can be done via constructing and tuning of object of type so_5::disp::prio_one_thread::quoted_round_robin::quotes_t. For example: \code // Default quote for all priorities must be specified in constructor. so_5::disp::prio_one_thread::quoted_round_robin::quotes_t quotes{ 10 }; // After that a quote for a particular priority can be changed. quotes.set( so_5::prio::p7, 50 ); quotes.set( so_5::prio::p5, 40 ); ... \endcode Construction of quotes_t object can be done during calling a function for dispatcher creation: \code so_5::disp::prio_one_thread::quoted_round_robin::create_private_disp( env, so_5::disp::prio_one_thread::quoted_round_robin::quotes_t{ 10 } .set( so_5::prio::p7, 50 ).set( so_5::prio::p5, 40 ) ); \endcode Function create_disp creates public dispatcher which should be added to list of named dispatchers: \code so_5::launch( []( so_5::rt::environment_t & env ) { env.introduce_coop( so_5::disp::prio_one_thread::quoted_round_robin::create_disp_binder( "prio_rr" ), []( so_5::rt::agent_coop_t & coop ) { ... } ); ... }, []( so_5::rt::environment_params_t & params ) { params.add_named_dispatcher( prio_rr", so_5::disp::prio_one_thread::quoted_round_robin::create_disp( so_5::disp::prio_one_thread::quoted_round_robin::quotes_t{ 20 } .set( so_5::prio::p7, 45 ).set( so_5::prio::p6, 35 ).set( so_5::prio::p5, 25 ) ) ); ... } ); \endcode There is also create_private_disp function for creation of private instance of dispatcher: \code so_5::launch( []( so_5::rt::environment_t & env ) { env.introduce_coop( so_5::disp::prio_one_thread::quoted_round_robin::create_private_disp( env, so_5::disp::prio_one_thread::quoted_round_robin::quotes_t{ 20 } .set( so_5::prio::p7, 45 ).set( so_5::prio::p6, 35 ).set( so_5::prio::p5, 25 ) )->binder(), []( so_5::rt::agent_coop_t & coop ) { ... } ); ... } ); \endcode \subsection so_5_5__in_depth_priority_of_agents__disps__dedicated Dispatcher With Dedicated Thread For Every Priority \subsubsection so_5_5__in_depth_priority_of_agents__disps__dedicated__opp Dispatcher prio_dedicated_threads::one_per_prio Working Principles This dispatcher creates a single dedicated thread for every priority. It means that events for agents with priority p7 will be handled on different thread than events for agents with, for example, priority p6. Events inside the same priority are handled in chronological order. Priority is assigned to an agent, not to a particular events. It means that all events of one agent have the same priority. Because of that all events of that agent will be handled on the same thread. In other words an agent is bound to its working thread once and do not moved from one thread to another. This working scheme means that agents priority is treated as binding to a particular working thread inside a dispatcher instance. It allows to bound agents with different requirements to different working threads. Synopsis The dispatcher is defined in so_5::disp::prio_dedicated_threads::one_per_prio namespace. There are several traditional functions for dispatcher creation. Function create_disp creates public dispatcher which should be added to list of named dispatchers: \code so_5::launch( []( so_5::rt::environment_t & env ) { env.introduce_coop( so_5::autoname, so_5::disp::prio_dedicated_threads::one_per_prio::create_disp_binder( "prio_threads" ), []( so_5::rt::agent_coop_t & coop ) { ... } ); ... }, []( so_5::rt::environment_params_t & params ) { params.add_named_dispatcher( prio_threads", so_5::disp::prio_dedicated_threads::one_per_prio::create_disp() ); ... } ); \endcode There is also create_private_disp function for creation of private instance of dispatcher: \code so_5::launch( []( so_5::rt::environment_t & env ) { env.introduce_coop( so_5::disp::prio_dedicated_threads::one_per_prio::create_private_disp(env)->binder(), []( so_5::rt::agent_coop_t & coop ) { ... } ); ... } ); \endcode */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_runtime_monitoring.dox000066400000000000000000000406421523065210100270200ustar00rootroot00000000000000/*! \page so_5_5__in_depth_runtime_monitoring so-5.5 In Depth - Run-Time Monitoring \tableofcontents \section so_5_5__in_depth_runtime_monitoring__purpose Purpose Until v.5.5.4 SObjectizer Run-Time was a "black-box". There was no possibility to look inside it and ask for some run-time statistics. How many agents are registered? How many demands in an event queue? How many delayed messages waiting? There were no answers to those questions. That was not a problem for most cases because application level monitoring is much more useful than low-level monitoring of SObjectizer's internals. For example the collecting statistics about time spent on DB-related operation can give you more precise information about problems in your doman-specific code than quantity of delayed messages in SObjectizer's timer thread. But it is a big plus if a framework like SObjectizer can provide some kind of run-time statistics. It could be used for debugging purposes for example. Or for collecting statistics by the tools like [statsd](https://github.com/etsy/statsd/) and [graphite](http://graphite.readthedocs.org/en/latest/index.html) and viewing/analyzing it conjunctions with other information about your application. Because of that a very simple mechanism for collecting and distributing of run-time monitoring information is implemented in v.5.5.4. Please note that it is a very first step in implementation of full-fledged run-time monitoring solution for SObjectizer. Version 5.5.4 provides basic statistics about SObjectizer's internals. There are some plans for extending such functionality in the future versions. \section so_5_5__in_depth_runtime_monitoring__working_principles Working Principles There are several new concepts related to run-time monitoring in v.5.5.4: - *data-source*. This is an object which holds or can obtain some information about some part of SObjectizer's internals. Data-source can distribute that information in the form of an ordinary message which is send to the special mbox; - *stats_controller*. This is an object which knows about all data-sources and tells them to distribute the information they hold. The stats_controller and data-sources are created by SObjectizer automatically, there is no need to do that manually. The stats_controller can be turned on or off. By default it is turned off. In that mode no run-time information collected nor distributed. It means there is no any performance penalty when stats_controller doesn't work. If the stats_controller is turned on it works on separate thread. This thread is created when the stats_controller is turned on and terminated when the stats_controller is turned off. The stats_controller sleeps for some time. Then wakes up and tells every data-source object to distribute the information they currently have. The distribution if performed on the thread of stats_controller. When distribution finishes the stats_controller is going to sleep again. The time the stats_controller sleeps can be adjusted. By default stats_controller distributes run-time statistics every 2 seconds. But is can be changed by so_5::rt::stats::controller_t::set_distribution_period() method. Every instance of SObjectizer Environment has its own stats_controller instance. This instance can be accessed via so_5::rt::environment_t::stats_controller() method. For example: \code void run_time_stats_listener::so_evt_start() override { // Run-time information must be updated every 0.5 second. so_environment().stats_controller().set_distribution_period( std::chrono::milliseconds( 500 ) ); // Turning the run-time monitoring on. so_environment().stats_controller().turn_on(); ... } \endcode Data-sources distribute their values by ordinary messages. Types of that messages are defined in so_5::rt::stats::messages namespace. Those messages are sent to mbox which can be obtained by so_5::rt::stats::controller_t::mbox() method. There is only one message type in so_5::rt::stats::messages namespace in v.5.5.4: it is a template so_5::rt::stats::messages::quantity. This message is used to spread information about quantities of something (like quantity of registered cooperations/agents, quantity of single-shot or periodic timer messages, quantity of working threads for a dispatcher, size of an event queue and so on). Only so_5::rt::stats::messages::quantity type is used in v.5.5.4 for all types of run-time statistics. Other message types could be introduced in future versions. \subsection so_5_5__in_depth_runtime_monitoring__working_principles__data_source_naming Data-Source Naming Scheme Data-sources have unique names. Every name is built from two parts: prefix and suffix. Concatenation of prefix and suffix makes unique name for data-source. Prefix is a size-limited null-terminated string. Prefix defines group of related data-sources. For example all data-sources related to a particular dispatcher instance will have the same prefix. Suffix is a constant pointer to immutable null-terminated string. Suffix identified data-source inside a group of related data-sources. For example here are some full names of data-sources: \verbatim mbox_repository/named_mbox.count: 0 coop_repository/coop.reg.count: 2 coop_repository/coop.dereg.count: 0 coop_repository/agent.count: 41 timer_thread/single_shot.count: 1 timer_thread/periodic.count: 0 disp/ot/DEFAULT/agent.count: 6 disp/ot/DEFAULT/wt-0/demands.count: 23 disp/ot/0x3be520/agent.count: 5 disp/ot/0x3be520/wt-0/demands.count: 19 disp/ao/0x24911b0/agent.count: 5 \endverbatim Substrings like `mbox_repository`, `disp/ot/DEFAULT` and `disp/ao/0x24911b0` are prefixes. Substring like `/named_mbox.count`, `/agent.count` and `demands.count` are suffixes. A name of data-source is included in every message with actual data from that data-source. For example the so_5::rt::stats::messages::quantity is defined as: \code template< typename T > struct quantity : public message_t { prefix_t m_prefix; suffix_t m_suffix; T m_value; quantity( const prefix_t & prefix, const suffix_t & suffix, T value ); }; \endcode Where quantity::m_prefix and quantity::m_suffix are parts of data-source name. There are some predefined prefixes. They could be obtained by functions defined in so_5::rt::stats::prefixes namespace. But all the prefixes for dispatcher-related data-sources are generated at run-time. All suffixes are predefined. They could be obtained by functions defined in so_5::rt::stats::suffixes namespace. \section so_5_5__in_depth_runtime_monitoring__how_to_use How to Use To receive run-time monitoring information it is necessary to do the two things: 1. Create subscription to the appropriate message from the appropriate mbox. 2. Turn the stats_controller on. Those can be done like this: \code class my_run_time_stats_listener : public so_5::rt::agent_t { public : my_run_time_stats_listener( context_t ctx ) : so_5::rt::agent_t( ctx ) {} virtual void so_define_agent() override { so_default_state().event( so_environment().stats_controller().mbox(), &my_run_time_stats_listener::evt_quantity ); } virtual void so_evt_start() override { so_environment().stats_controller().turn_on(); } private : void evt_quantity( const so_5::rt::stats::messages::quantity< std::size_t > & evt ) { // Just show to standard output. std::cout << evt.m_prefix << evt.m_suffix << ": " << evt.m_value << std::endl; } }; \endcode Please note that SObjectizer doesn't provide any ready-to-use tools for working with run-time statistics. If you want to store it anywhere you must write the appropriated code by youself. Please note also that there is no possibility to narrow a stream of monitoring information. If you create subscription to `quantity` message you will receive messages of that type from all data-sources. And if you want to narrow the stream you must filter those messages in your event: \code void timer_thread_stats_listener::evt_timer_quantities( const so_5::rt::stats::messages::quantity< std::size_t > & evt ) { // Ignore messages unrelated to timer thread. if( so_5::rt::stats::prefixes::timer_thread() != evt.m_prefix ) return; ... // Processing of related to timer thread messages. } \endcode \section so_5_5__in_depth_runtime_monitoring__in_v5_5_4 Run-Time Monitoring in v.5.5.4 There are several predefined data-sources in SObjectizer Environment. And there are some data-sources for every dispatcher and every dispatcher's working thread. This section briefly describes those data-sources. \subsection so_5_5__in_depth_runtime_monitoring__in_v5_5_4__agent_repo Count of Agents and Cooperations There are three data-sources related to agents and cooperations repository. They all have prefix defined by so_5::rt::stats::prefixes::coop_repository() function but the suffixes for data-source are different: - data-source with suffix so_5::rt::stats::suffixes::coop_reg_count() informs about quantity of registered cooperations; - data-source with suffix so_5::rt::stats::suffixes::coop_dereg_count() informs about quantity of cooperations which are in deregistration stage now; - data-source with suffix so_5::rt::stats::suffixes::agent_count() informs about quantity of registered agents (this is a total quantity of agents in registered cooperations and in the cooperations which are in deregisteration stage). \subsection so_5_5__in_depth_runtime_monitoring__in_v5_5_4__mbox_repo Count of Named Mboxes There is one data-source that informs about the current quantity of named mboxes. This data-source has prefix so_5::rt::stats::prefixes::mbox_repository() and suffix so_5::rt::stats::suffixes::named_mbox_count(). \subsection so_5_5__in_depth_runtime_monitoring__in_v5_5_4__timer_thread Count of Delayed and Periodic Message There are two data-sources related to timer thread. They have prefix so_5::rt::stats::prefixes::timer_thread() and one of the following suffixes: - so_5::rt::stats::suffixes::timer_single_shot_count(). This data-source informs about quantity of delayed messages; - so_5::rt::stats::suffixes::timer_periodic_count(). This data-source informs about quantity of periodic messages. \subsection so_5_5__in_depth_runtime_monitoring__in_v5_5_4__disp Information from Dispatchers Every dispatcher creates several data-sources. Those data sources can be grouped in several groups. Every group will have its own prefix. There is common rules for creation of prefixes for dispatchers' data-sources: - every prefix starts with `disp`; - then will be abbreviated type of dispatcher: - `/ot` -- for one_thread dispatcher; - `/ao` -- for active_obj dispatcher; - `/ag` -- for active_group dispatcher; - `/tp` -- for thread_pool dispatcher; - `/atp` -- for adv_thread_pool dispatcher; - then will be identification for dispatcher instance. It could be part of dispatcher name (for the case when name if specified for the dispatcher) or hex representation of address of dispatcher object. For example: \verbatim disp/ot/DEFAULT/agent.count disp/ot/DEFAULT/wt-0/demands.count disp/ot/0x3be520/agent.count disp/ot/0x3be520/wt-0/demands.count disp/ao/0x24911b0/agent.count disp/ao/0x24911b0/wt-0x2491260/demands.count disp/ag/0x2495930/group.count disp/ag/0x2495930/wt-group#1/agent.count disp/ot/stats_listener/wt-0/demands.count disp/tp/workers/aq/0x53acf0/demands.count \endverbatim Note. If a dispatcher has been added to the environment as named dispatcher its name will be used for creation of data-source prefixes. A name for private dispatcher can be specified by using the appropriate `create_private_disp` function. \subsubsection so_5_5__in_depth_runtime_monitoring__in_v5_5_4__disp__one_thread one_thread Dispatcher Run-Time Statistics one_thread dispatcher creates two data-sources: - one with prefix like `disp/ot/` and suffix so_5::rt::stats::suffixes::agent_count(). It informs quantity of agents bound to that dispatcher; - another with prefix like `disp/ot//wt-0` and suffix so_5::rt::stats::suffixes::work_thread_queue_size(). It informs event queue length. Example: \verbatim disp/ot/DEFAULT/agent.count: 6 disp/ot/DEFAULT/wt-0/demands.count: 23 disp/ot/0x3be520/agent.count: 5 disp/ot/0x3be520/wt-0/demands.count: 19 \endverbatim \subsubsection so_5_5__in_depth_runtime_monitoring__in_v5_5_4__disp__active_obj active_obj Dispatcher Run-Time Statistics active_obj dispatcher creates two types of data-sources: - with prefix like `disp/ao/` and suffix so_5::rt::stats::suffixes::agent_count(). It informs quantity of agents bound to that dispatcher. This data-source is created always, even if there is no agents bound to the dispatcher; - with prefix like `disp/ao//wt-` and suffix so_5::rt::stats::suffixes::work_thread_queue_size(). It informs event queue length for a working thread (e.g. for one agent bound to the dispatcher). Example: \verbatim disp/ao/0x24911b0/agent.count: 5 disp/ao/0x24911b0/wt-0x2491260/demands.count: 2 disp/ao/0x24911b0/wt-0x2491d30/demands.count: 4 disp/ao/0x24911b0/wt-0x24931f0/demands.count: 2 disp/ao/0x24911b0/wt-0x24934b0/demands.count: 2 disp/ao/0x24911b0/wt-0x24955c0/demands.count: 0 \endverbatim \subsubsection so_5_5__in_depth_runtime_monitoring__in_v5_5_4__disp__active_group active_group Dispatcher Run-Time Statistics active_group dispatcher creates several data-sources: - with prefix like `disp/ag/` and suffix so_5::rt::stats::suffixes::disp_active_group_count(). It informs quantity of active groups handled by that dispatcher. This data-source is created always, even if there is no agents bound to the dispatcher; - two types of data-sources are created for every active group. Each of them has prefix like `disp/ag//wt-` and one of the suffixes: - so_5::rt::stats::suffixes::agent_count(). It informs quantity of agents in that active group; - so_5::rt::stats::suffixes::work_thread_queue_size. It informs event queue length for a working thread (e.g. for that active group). Example: \verbatim disp/ag/0x2495930/group.count: 5 disp/ag/0x2495930/wt-db_handler/agent.count: 3 disp/ag/0x2495930/wt-db_handler/demands.count: 4 disp/ag/0x2495930/wt-logger/agent.count: 1 disp/ag/0x2495930/wt-logger/demands.count: 0 \endverbatim \subsubsection so_5_5__in_depth_runtime_monitoring__in_v5_5_4__disp__thread_pool thread_pool/adv_thread_pool Dispatchers Run-Time Statistics thread_pool/adv_thread_pool dispatchers create several data-sources: - with prefix like `disp/tp/` and suffix so_5::rt::stats::suffixes::disp_thread_count(). It informs about working thread count for the dispatcher; - with prefix like `disp/tp/` and suffix so_5::rt::stats::suffixes::agent_count(). It informs about the quantity of agents bound to that dispatcher; - two types of data-sources are created for every FIFO queue. If FIFO queue is created for a cooperation then the corresponding data-sources will have prefix like `disp/tp//cq/`. If FIFO queue is created for an individual agent then the corresponding data-sources will have prefix like `disp/tp//aq/`. The suffix for those data-sources can be one of the following: - so_5::rt::stats::suffixes::agent_count(). It informs quantity of agents bound to that event queue; - so_5::rt::stats::suffixes::work_thread_queue_size. It informs event queue length. Note. For adv_thred_pool dispatcher all prefixes will start from `disp/atp`. Example: \verbatim disp/tp/0x24974b0/threads.count: 4 disp/tp/0x24974b0/agent.count: 5 disp/tp/0x24974b0/cq/db_ops/agent.count: 5 disp/tp/0x24974b0/cq/db_ops/demands.count: 9 disp/tp/0x2499ab0/threads.count: 4 disp/tp/0x2499ab0/agent.count: 5 disp/tp/0x2499ab0/aq/0x2495670/agent.count: 1 disp/tp/0x2499ab0/aq/0x2495670/demands.count: 4 disp/tp/0x2499ab0/aq/0x2495880/agent.count: 1 disp/tp/0x2499ab0/aq/0x2495880/demands.count: 1 disp/tp/0x2499ab0/aq/0x2495ca0/agent.count: 1 disp/tp/0x2499ab0/aq/0x2495ca0/demands.count: 0 disp/tp/0x2499ab0/aq/0x2495d50/agent.count: 1 disp/tp/0x2499ab0/aq/0x2495d50/demands.count: 1 disp/tp/0x2499ab0/aq/0x24962d0/agent.count: 1 disp/tp/0x2499ab0/aq/0x24962d0/demands.count: 2 disp/atp/0x24a3c10/threads.count: 4 disp/atp/0x24a3c10/agent.count: 5 disp/atp/0x24a3c10/cq/crypto_ops/agent.count: 5 disp/atp/0x24a3c10/cq/crypto_ops/demands.count: 15 disp/atp/0x24971a0/threads.count: 4 disp/atp/0x24971a0/agent.count: 2 disp/atp/0x24971a0/aq/0x24a7e10/agent.count: 1 disp/atp/0x24971a0/aq/0x24a7e10/demands.count: 4 disp/atp/0x24971a0/aq/0x24a8a70/agent.count: 1 disp/atp/0x24971a0/aq/0x24a8a70/demands.count: 4 \endverbatim */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_synchronous_interaction.dox000066400000000000000000000213511523065210100300550ustar00rootroot00000000000000/*! \page so_5_5__in_depth__svc_requests so-5.5 In Depth - Synchronous Interaction \section so_5_5__in_depth__svc_requests__intro Introduction A major change was introduced in v.5.3.0 -- a possibility to synchronously wait for result of message processing. An agent, called service client, could ask another agent (called service_handler) to do something by asynchronously sending message to service_handler and then synchronously waiting for the result of that request. All parameters for service request must be defined as an ordinary message or a signal type: \code // Message for service request with parameters. struct msg_convert { // String to conversion to int. std::string m_value; }; // Signal for service request without parameters. struct msg_get_status : public so_5::rt::signal_t {} \endcode Service request handler is an ordinary agent which defines its service_request handling methods by traditional way. The only change with previous version is that event handlers could return values since v.5.3.0: \code // service_handler event for msg_convert request. int a_my_service_t::evt_convert( const msg_convert & msg ) { std::istringstream s( msg.m_value ); int result; s >> result; if( s.fail() ) throw std::invalid_argument( "unable to convert to int: '" + msg.m_value + "'" ); return result; } // service_handler event for msg_get_status request. std::string a_my_service_t::evt_get_status() { return "Ready"; } \endcode Event handler methods are subscribed as usual: \code class a_my_service_t : public so_5::rt::agent_t { public : virtual void so_define_agent() override { so_subscribe( mbox ).event( &a_my_service_t::evt_convert ); so_subscribe( mbox ).event< msg_get_status >( &a_my_service_t::evt_get_status ); } ... }; \endcode All of these mean that there is almost no differences for service_handler: it always works as an ordinary agent. And all event handlers for that agent are invoked on agents working thread. All requests are delivered to service_handlers agent as ordinary messages via agents event queue. The mechanism for event handler invocation for service requests is the same as for traditional asynchronous messages. To send service request it is necessary to call so_5::request_value template function: \code std::string s = so_5::request_value(processor, timeout); \endcode This call will send `msg_get_status` signal to the target `processor` and will wait for the result for `timeout`. A mbox, a reference to an agent, an ad-hoc agent proxy could be used as target argument for so_5::request_value() function. A caller must ensure that there is only one receiver of msg_get_status behind that argument. The `timeout` argument is very important. The most simple and the most dangerous way, is to wait service request result infinitely long: \code // Initiate the service request and wait for result infinitely. int v = so_5::request_value(mbox, so_5::infinite_wait, "42"); // Invocation of service request by a signal. std::string s = so_5::request_value(processor, so_5::infinite_wait); \endcode The second way, which is much more safer, is to wait service request result for the specified amount of time: \code // Initiate the service request and wait for 300ms. int v = so_5::request_value(mbox, std::chrono::milliseconds(300), "42"); // Invocation of service request by a signal and wait for 5s. std::string s = so_5::request_value(processor, std::chrono::seconds(5)); \endcode There is also template function so_5::request_future() which provides the most complex and the most flexible way for issuing service request. It returns std::future object associated with service request. A user then can perform any operation he wish: \code std::future< int > v = so_5::request_future(mbox, "42"); std::future< std::string > s = so_5::request_future(mbox); // Do some useful stuff until conversion result get ready. while( true ) { ... // Do something here. // Check the future status. if( std::future_status::ready == v.wait_for( std::chrono::seconds(0) ) ) break; } // Conversion result is ready and could be obtained without waiting. std::cout << "Conversion result: " << v.get() << std::endl; // Wait for get_status request infinitely. std::cout << "Service status: " << s.get() << std::endl; \endcode \section so_5_5__in_depth__svc_requests__how_it_works How Does It Work? When a service request is initiated a special envelope with service requests params inside is sent as ordinary message to service request processor. This envelope contains not only request params but also a std::promise object for the response. A value for that promise is set on processors side. A service request issuer waits on std::future object which is bound with std::promise from the envelope sent. It means that so_5::request_value call in form: \code auto r = so_5::request_value(mbox, timeout, params); \endcode is a shorthand for something like that: \code // Envelope will contain Request object and std::promise object. auto env__ = std::make_unique>(params); // Get the future to wait on it. std::future f__ = env__.m_promise.get_future(); // Sending the envelope with request params as async message. mbox->deliver_message(std::move(env__)); // Waiting and handling the result. auto wait_result__ = f__.wait_for(timeout); if(std::future_status::ready != wait_result__) throw exception_t(...); auto r = f__.get(); \endcode Every service request handler in form \code Result event_handler(const Request & svc_req ) { ... } \endcode is automatically transformed to something like this: \code void actual_event_handler(msg_service_request_t & m) { try { m.m_promise.set( event_handler(m.query_param()) ); } catch(...) { m.set_exception(std::current_exception()); } } \endcode This transformation is performed during subscription of event_handler. \section so_5_5__in_depth__svc_requests__deadlocks Deadlocks There is no any help to user from SObjectizer to avoid or to diagnose deadlocks when synchronous interactions are used. It is the user task to guarantee that service_handler and service client are working on different threads and there is no any complex internal interactions like: agent A1 calls A2 which calls A3 which calls A1. It is the reason why the usage of `so_5::infinite_wait` is dangerous. If there is a deadlock the application will hang forever. The safest way -- is to use so_5::request_value with the reasonable timeout. Even if a deadlock is here it will be automatically destroyed after timeout expiration. \section so_5_5__in_depth__svc_requests__exceptions Exceptions There is a huge possibility of exception appearance during synchronous interactions. Some exceptions could be raised by SObjectizer. Those are: - the exceptions related to wrong number of actual service_handlers. When a service request is initiated by request_value/request_future function the one and only one service_handler must exist (e.g. one and only one agent must be subscribed to the specified message type from the specified mbox). If there are more than one agent-subscriber or if there isnt any of them then an exception will be raised; - the exceptions related to ignorance of service request. For example if service_handler receives a service request but not handled it because the event handler is disabled in the current agent state then an exception will be raised; - the exceptions related to timeouts during wait for result. Those exceptions are raised when so_5::request_value() function is used and timeout elapsed without receiving a result of service request. In such cases SObjectizer raises object of so_5::exception_t class with the appropriate error_code inside. Some exceptions could be raised by event handlers itself. As in the sample of evt_convert event above. Those exceptions are intercepted by SObjectizer and then transferred to the std::future connected with the service request. It means that those exceptions will be reraised during std::future::get() invocation. So, the application exception which are went out from event handler will be propagated to the service client. The dialing with non-handled application exception is the main difference between ordinary event-handling and service_request-handling. When agent lets the exception go out during ordinary event handler invocation then SObjectizer calls agents so_exception_reaction() method and does appropriate actions. But when agent lets the exception go out during service_request handling then the exception is propagated to service client and so_exception_reaction() is not called. */ // vim:ft=cpp sobjectizer-5.8.6/doxygen/dox/so_5/so_5_5__in_depth_timers.dox000066400000000000000000000331431523065210100243710ustar00rootroot00000000000000/*! \page so_5_5__in_depth_timers so-5.5 In Depth - Timers \section so_5_5__in_depth_timers__intro Introduction Timers are actively used in typical SObjectizer's based applications. That's why SObjectizer provides easy to use tools for dealing with timers: - delayed messages; - periodic messages. A delayed message is delivered after a specified time interval since the message was sent. It means that if a delayed message is sent at some time point T and the delay is 150ms then it will be pushed to subscribers' event queues at time point (T+150ms). A periodic message is repeatedly delivered after a specified amount of time. It means that if a periodic message is sent at some time point T with delay of 150ms and repetition period of 250ms then it will be pushed to subscribers' event queues first time at time point (T+150ms), then at time point (T+400ms), then at (T+650ms) and so on. A periodic message will be repeated until it canceled. \section so_5_5__in_depth_timers__delayed_msgs Delayed Messages There are three ways for sending a delayed messages. The simplest one is to use so_5::send_delayed() function. A reference to SO Environment instance is necessary for sending a delayed message. That's why the first argument of send_delayed() is reference to so_5::rt::environment_t or to so_5::rt::agent_t. In the later case SO Environment in which the agent is registered will be used. This is an example of sending a delayed message from ordinary agent: \code class my_agent : public so_5::rt::agent_t { ... virtual void so_evt_start() override { so_5::send_delayed< some_message >( *this, // For access to SO Environment. dest, // Destination mbox. std::chrono::milliseconds(125), // Delivery delay in ms. ... ); // Arguments to be forwarded to some_message constructor. } }; \endcode This is an example of sending a delayed message via reference to SO Environment: \code env.introduce_coop( []( so_5::rt::agent_coop_t & coop ) { coop.define_agent().on_start( [&coop] { so_5::send_delayed< some_message >( coop.environment(), // Access to SO Environment. dest, // Destination mbox. std::chrono::milliseconds(125), // Delivery delay in ms. ... ); // Arguments to be forwarded to some_message constructor. ... } ); } ); \endcode Helper function so_5::send_delayed_to_agent() can be used to sending a delayed message to the direct mbox of agent-receiver. SO Environment in which the receiver is registered will be used in that case. This is an example of sending a delayed message to the direct mbox of the agent-receiver: \code class my_agent : public so_5::rt::agent_t { ... void evt_request( const request & evt ) { initiate_request_processing( evt ); so_5::send_delayed_to_agent< check_request >( *this, // Destination for message. std::chrono::milliseconds(125), // Delivery delay in ms. ... ); // Arguments to be forwarded to check_request constructor. } }; \endcode Function so_5::send_delayed_to_agent() also accepts an ad-hoc agent proxy: \code env.introduce_coop( []( so_5::rt::agent_coop_t & coop ) { auto a = coop.define_agent(); a.event( a, [a]( const request & evt ) { initiate_request_processing( evt ); so_5::send_delayed_to_agent< check_request >( a, // Destination for message. std::chrono::milliseconds(125), // Delivery delay in ms. ... ); // Arguments to be forwarded to check_request constructor. } ); } ); \endcode The second way is to use so_5::rt::environment_t::single_timer() method: \code class my_agent : public so_5::rt::agent_t { ... void evt_request( const request & evt ) { initiate_request_processing( evt ); auto delayed_msg = std::make_unique< check_request >( ... ); so_environment().single_timer( std::move(delayed_msg), // Message instance. so_direct_mbox(), // Destination for message. std::chrono::milliseconds(125) ); // Delivery delay in ms. } }; \endcode Usage of single_timer() is not as easy as usage of send_delayed() or send_delayed_to_agent(). But single_timer() can be useful if a message instance is created somewhere else... This is an example of a case where single_timer() can be useful: \code class my_agent : public so_5::rt::agent_t { ... void evt_request( const request & evt ) { initiate_request_processing( evt ); so_environment().single_timer( create_check_request_message( evt ), // Message instance. so_direct_mbox(), // Destination for message. std::chrono::milliseconds(125) ); // Delivery delay in ms. } std::unique_ptr< check_request > create_check_request_message( const request & evt ) { ... // Some complex logic. return std::make_unique< check_request >( ... ); } }; \endcode The third way is to use so_5::rt::environment_t::schedule_timer() method. Method schedule_timer() returns timer ID which can be used for timer cancellation. This is an example of delayed message cancellation: \code class my_agent : public so_5::rt::agent_t { so_5::timer_id_t m_check_timer; ... void evt_request( const request & evt ) { initiate_request_processing( evt ); m_check_timer = so_environment().schedule_timer( create_check_request_message( evt ), // Message instance. so_direct_mbox(), // Destination for message. std::chrono::milliseconds(125), // Delivery delay in ms. std::chrono::milliseconds::zero() ); // No repetition. } void evt_request_processed() { // There is no need for delayed message anymore. m_check_timer.release(); // Cancellation of delivery. ... } std::unique_ptr< check_request > create_check_request_message( const request & evt ) { ... } }; \endcode # Periodic Messages Periodic message are repeated again and again until it will be cancelled. The same message instance is delivered every time. It means that message instance is not deallocated after processing. Deallocation will occur when message will be cancelled. There are two ways for sending a periodic message. The simplest one is to use so_5::send_periodic() function. As for delayed messages the access to SO Environment is necessary for sending a periodic message. That's why the first argument of send_periodic() must be a reference to so_5::rt::environment_t or to so_5::rt::agent_t. In the later case SO Environment in which the agent is registered will be used. This is an example of sending of a periodic message from ordinary agent: \code class my_agent : public so_5::rt::agent_t { so_5::timer_id_t m_status_timer; ... virtual void so_evt_start() override { m_status_timer = so_5::send_periodic< update_status >( *this, // For access to SO Environment. dest, // Destination mbox. std::chrono::milliseconds(125), // First delivery delay in ms. std::chrono::milliseconds(250), // Repetition period in ms. ... ); // Arguments to be forwarded to update_status constructor. } }; \endcode There is also so_5::send_periodic_to_agent() function for sending message to the direct mbox of the agent-receiver: \code class my_agent : public so_5::rt::agent_t { so_5::timer_id_t m_status_timer; ... virtual void so_evt_start() override { m_status_timer = so_5::send_periodic_to_agent< update_status >( *this, // Destination. std::chrono::milliseconds(125), // First delivery delay in ms. std::chrono::milliseconds(250), // Repetition period in ms. ... ); // Arguments to be forwarded to update_status constructor. } }; \endcode The second way is to use so_5::rt::environment_t::schedule_timer() method: \code class my_agent : public so_5::rt::agent_t { so_5::timer_id_t m_status_timer; ... virtual void so_evt_start() override { m_status_timer = so_environment().schedule_timer( create_status_message(), // Message to be sent periodically. so_direct_mbox(), // Destination. std::chrono::milliseconds(125), // First delivery delay in ms. std::chrono::milliseconds(250) ); // Repetition period in ms. } std::unique_ptr< update_status > create_status_message() { ... } }; \endcode The most important moment in periodic messages sending - is storing the result value of send_periodic(), send_periodic_to_agent() and schedule_timer(). If the result value is not saved then periodic message will be cancelled immediately. This is because the destructor of so_5::timer_id_t does timer cancellation. The so_5::timer_id_t class works like a smart pointer. Destruction of the last timer_id_t pointed to a timer will destroy the timer and periodic (or delayed) message will be cancelled. That's why at least one timer_id_t object for periodic message must exist while message delivery is necessary. \section so_5_5__in_depth_timers__msg_cancellation Delayed and Periodic Messages Cancellation There are three ways of delayed/periodic messages cancellation. All of them use so_5::timer_id_t objects. It means that cancellation is only possible for messages sent via send_periodic(), send_periodic_to_agent() or schedule_timer(). The first way is to call so_5::timer_id_t::release() method. \code auto id = so_5::send_periodic< Msg >(...); ... id.release(); // Delivery canceled. \endcode Please note that explicit call of so_5::timer_id_t::release() method cancels a message regardless of count of remaining timer_id_t objects pointed to that timer. The second way is destruction of all timer_id_t objects pointing to the same timer. If so_5::timer_id_t::release() method is not called explicitly it will be called in the destructor of the last timer_id_t object pointing to a timer. This way is often used in ordinary agents: \code class request_processor : public so_5::rt::agent_t { so_5::timer_id_t m_check_request; ... void evt_request( const request & evt ) { m_check_request = so_5::send_periodic_to_agent< check_request >( *this, ...); // Timer will be cancelled automatically in // the destructor of request_processor. ... } }; \endcode The third way is assignment of new value to so_5::timer_id_t object. If this object was the last timer_id_t pointed to a timer then the timer will be destroyed and message will be cancelled: \code auto id = so_5::send_periodic< Msg >(...); ... // Some actions. id = so_5::send_periodic< Sig >(...); // Cancellation of Msg. \endcode There is a trick moment with cancellation of delayed messages... Delayed message will be cancelled only if it is still under control of timer thread. If message already leaved timer thread and is waiting in event queues of recipients then message delivery will not be cancelled and message will be processed by subscribers. For example if delay was 125ms and cancelaction is initiated after 125ms after call to so_5::send_delayed() there is a high probability that message will be delivered anyway. \section so_5_5__in_depth_timers__timer_thread Timer Thread SO Environment starts a special thread for handling timers. This thread is known as timer thread. All timers are controlled and processed by that timer thread. Timer thread can efficiently process big amount of timers: tens and hundreds of millions. Even billions of timers. A user can choose a timer mechanism most appropriate for application needs. Three timer mechanisms are supported. Each has its strengths and weakness: - timer_wheel - timer_list - timer_heap \subsection so_5_5__in_depth_timers__timer_thread__timer_wheel timer_wheel Mechanism Can support very big amount of timers efficiently (tens, hundreds of millions, billions). It also equally efficient for delayed and periodic messages. Because of that timer_wheel mechanism should be used when the application needs a big number of timers. But there are some costs: - this mechanism is not very precise (there is a step of timer wheel which could be configured, but small step decrease effectiveness); - this mechanism consumes some resources even if there are no ready to use timers (this overhead is small but it is still here). \subsection so_5_5__in_depth_timers__timer_thread__timer_list timer_list Mechanism Works very well only if new timers will be added to the end of list of timers. Therefore this mechanism should be used in applications where there are many similar delayed messages with the same delays. This mechanism does not consume resources when there are no ready to use timers. It also handles timers cancellation very efficiently. \subsection so_5_5__in_depth_timers__timer_thread__timer_heap timer_heap Mechanism Has very fluent overall performance, especially on relative small amounts of timers (thousands, tens of thousands timers). It also does not consume resources if there are no ready to use timers. Because of that timer_heap mechanism is used in SO Environment by default. \subsection so_5_5__in_depth_timers__timer_thread__set_mechanism Specifying Timer Mechanism for SO Environment Timer mechanism can be specified in Environment's parameters before start of SO Environment: \code so_5::launch( []( so_5::rt::environment_t & env ) { // Some initialization stuff... }, // SObjectizer Environment parameters tuning. []( so_5::rt::environment_params_t & params ) { // Use timer_wheel mechanism with wheel size 10000 // and timer step size of 5ms. params.timer_thread( so_5::timer_wheel_factory( 10000, std::chrono::milliseconds(5) ) ); ... } ); \endcode \section so_5_5__in_depth_timers__timer_thread__additional Additional Information For more information about timer mechanisms, their strengths and weakness see description of [Timer Template Thread (timertt)](https://sourceforge.net/p/sobjectizer/wiki/Timer%20Thread%20Template/) library. This library is used for implementation of delayed and periodic messages in SO-5.5. */ // vim:ft=cpp