├── .gitignore ├── examples └── ActorThread │ ├── MyLib │ ├── Makefile │ ├── GNUmakefile │ ├── include │ │ └── MyLib │ │ │ ├── Printer.h │ │ │ └── MyLib.h │ └── src │ │ └── MyLib.cpp │ ├── Test │ ├── Makefile │ ├── GNUmakefile │ └── src │ │ ├── Application.h │ │ └── Application.cpp │ ├── HelloWorld │ ├── Makefile │ ├── GNUmakefile │ └── src │ │ ├── Application.h │ │ ├── World.h │ │ ├── World.cpp │ │ ├── Printer.h │ │ └── Application.cpp │ └── MyLibClient │ ├── Makefile │ ├── GNUmakefile │ └── src │ ├── Application.h │ └── Application.cpp ├── Makefile ├── GNUmakefile ├── nmake.mk ├── LICENSE_1_0.txt ├── include └── sys++ │ ├── String.hpp │ └── ActorThread.hpp ├── README.md └── posix.mk /.gitignore: -------------------------------------------------------------------------------- 1 | release/ 2 | debug/ 3 | build/ 4 | .project 5 | .cproject 6 | .settings/ 7 | -------------------------------------------------------------------------------- /examples/ActorThread/MyLib/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR = build 2 | OUT_LIB = MyLib.a 3 | 4 | SRC_DIR = src 5 | INCLUDES = -I../../../include # for 6 | 7 | include ../../../nmake.mk 8 | -------------------------------------------------------------------------------- /examples/ActorThread/Test/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR = build 2 | PATH_BIN = $(BUILD_DIR)/application.exe 3 | 4 | SRC_DIR = src 5 | INCLUDES = -I../../../include # for 6 | 7 | include ../../../nmake.mk 8 | -------------------------------------------------------------------------------- /examples/ActorThread/HelloWorld/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR = build 2 | PATH_BIN = $(BUILD_DIR)/application.exe 3 | 4 | SRC_DIR = src 5 | INCLUDES = -I../../../include # for 6 | 7 | include ../../../nmake.mk 8 | -------------------------------------------------------------------------------- /examples/ActorThread/MyLibClient/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR = build 2 | PATH_BIN = $(BUILD_DIR)/application.exe 3 | SUBPRJS = ../MyLib 4 | 5 | SRC_DIR = src 6 | INCLUDES = -I$(SUBPRJS)/include \ 7 | -I../../../include # for 8 | 9 | LIBS = $(SUBPRJS)/$(BUILD_DIR)/*.a 10 | 11 | include ../../../nmake.mk 12 | -------------------------------------------------------------------------------- /examples/ActorThread/MyLib/GNUmakefile: -------------------------------------------------------------------------------- 1 | ifeq ($(DEBUG), 1) 2 | BUILD_DIR := debug 3 | CXXFLAGS := -O0 -g3 $(CXXFLAGS) 4 | else 5 | BUILD_DIR := release 6 | CXXFLAGS := -O2 $(CXXFLAGS) 7 | endif 8 | 9 | OUT_LIB := MyLib.a 10 | 11 | SRC_DIR := src 12 | INCLUDES := -I../../../include # for 13 | 14 | include ../../../posix.mk 15 | -------------------------------------------------------------------------------- /examples/ActorThread/Test/GNUmakefile: -------------------------------------------------------------------------------- 1 | ifeq ($(DEBUG), 1) 2 | BUILD_DIR := debug 3 | CXXFLAGS := -O0 -g3 $(CXXFLAGS) 4 | else 5 | BUILD_DIR := release 6 | CXXFLAGS := -O2 $(CXXFLAGS) 7 | endif 8 | 9 | PATH_BIN := $(BUILD_DIR)/application 10 | 11 | SRC_DIR := src 12 | INCLUDES := -I../../../include # for 13 | LDLIBS := -lpthread 14 | 15 | include ../../../posix.mk 16 | -------------------------------------------------------------------------------- /examples/ActorThread/HelloWorld/GNUmakefile: -------------------------------------------------------------------------------- 1 | ifeq ($(DEBUG), 1) 2 | BUILD_DIR := debug 3 | CXXFLAGS := -O0 -g3 $(CXXFLAGS) 4 | else 5 | BUILD_DIR := release 6 | CXXFLAGS := -O2 $(CXXFLAGS) 7 | endif 8 | 9 | PATH_BIN := $(BUILD_DIR)/application 10 | 11 | SRC_DIR := src 12 | INCLUDES := -I../../../include # for 13 | LDLIBS := -lpthread 14 | 15 | include ../../../posix.mk 16 | -------------------------------------------------------------------------------- /examples/ActorThread/MyLibClient/GNUmakefile: -------------------------------------------------------------------------------- 1 | ifeq ($(DEBUG), 1) 2 | BUILD_DIR := debug 3 | CXXFLAGS := -O0 -g3 $(CXXFLAGS) 4 | else 5 | BUILD_DIR := release 6 | CXXFLAGS := -O2 $(CXXFLAGS) 7 | endif 8 | 9 | PATH_BIN := $(BUILD_DIR)/application 10 | SUBPRJS := ../MyLib 11 | 12 | SRC_DIR := src 13 | INCLUDES := -I../../../include # for 14 | LDLIBS := -lpthread 15 | 16 | include ../../../posix.mk 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECTS = examples\ActorThread\HelloWorld \ 2 | examples\ActorThread\MyLibClient \ 3 | examples\ActorThread\Test 4 | 5 | all: 6 | @echo off && for %%p in ($(PROJECTS)) do \ 7 | echo. && echo ^>^>^>^>^>^> %%p: \ 8 | && pushd %%p && $(MAKE) /NOLOGO && popd \ 9 | && echo. 10 | 11 | clean: 12 | @echo off && for %%p in ($(PROJECTS)) do \ 13 | echo. && echo ^>^>^>^>^>^> %%p: \ 14 | && pushd %%p && $(MAKE) clean /NOLOGO && popd \ 15 | && echo. 16 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | PROJECTS = examples/ActorThread/HelloWorld \ 2 | examples/ActorThread/MyLibClient \ 3 | examples/ActorThread/Test 4 | 5 | export MK_FULLPATH = 1 6 | export MK_NOHL = 1 7 | 8 | project_build: 9 | @echo 10 | @for prj in $(PROJECTS); do \ 11 | echo ">>>>>> $$prj:" \ 12 | && $(MAKE) -C "$$prj" --no-print-directory $(MAKECMDGOALS) || exit \ 13 | && echo \ 14 | ; done 15 | 16 | .PHONY: project_build $(MAKECMDGOALS) 17 | 18 | $(foreach target, $(MAKECMDGOALS), $(eval $(target): project_build)) 19 | -------------------------------------------------------------------------------- /examples/ActorThread/MyLibClient/src/Application.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef APPLICATION_H 8 | #define APPLICATION_H 9 | 10 | #include 11 | #include 12 | 13 | class Application : public ActorThread 14 | { 15 | friend ActorThread; 16 | 17 | Application(int, char**) : library(MyLib::create()), safeLibrary(library) {} 18 | 19 | void onStart(); 20 | template void onMessage(Any&); 21 | 22 | MyLib::ptr library; 23 | MyLib::Gateway safeLibrary; 24 | Printer::ptr printer; 25 | }; 26 | 27 | #endif /* APPLICATION_H */ 28 | -------------------------------------------------------------------------------- /examples/ActorThread/HelloWorld/src/Application.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef APPLICATION_H 8 | #define APPLICATION_H 9 | 10 | #include 11 | #include "Printer.h" 12 | #include "World.h" 13 | 14 | struct Newspaper { std::string name; }; 15 | struct Picture { int width, height; }; 16 | struct Money { double amount; }; 17 | 18 | class Application : public ActorThread 19 | { 20 | friend ActorThread; 21 | 22 | Application(int, char**); 23 | 24 | void onStart(); 25 | void onMessage(Newspaper&); 26 | void onMessage(Picture&); 27 | void onMessage(Money&); 28 | void onTimer(const int&); 29 | void onStop(); 30 | 31 | Printer::ptr printer; 32 | World::ptr world; 33 | }; 34 | 35 | #endif /* APPLICATION_H */ 36 | -------------------------------------------------------------------------------- /nmake.mk: -------------------------------------------------------------------------------- 1 | # generic nmake Makefile (without dependencies) 2 | 3 | all: subprojects checkdirs output_binary output_library 4 | 5 | clean: 6 | @if exist $(BUILD_DIR) rmdir /S /Q $(BUILD_DIR) 7 | ! ifdef SUBPRJS 8 | @echo off && for %%x in ($(SUBPRJS)) do cd %%x && $(MAKE) /NOLOGO clean 9 | ! endif 10 | 11 | checkdirs: 12 | @if NOT EXIST $(BUILD_DIR) mkdir $(BUILD_DIR) 13 | 14 | {$(SRC_DIR)}.cpp{$(BUILD_DIR)}.obj:: 15 | $(CC) -nologo $(INCLUDES) -c /O2 /W4 /EHsc /Fo$(BUILD_DIR)/ $< 16 | 17 | subprojects: 18 | ! ifdef SUBPRJS 19 | @echo off && for %%x in ($(SUBPRJS)) do cd %%x && $(MAKE) /NOLOGO 20 | ! endif 21 | 22 | output_binary: $(PATH_BIN) 23 | ! ifdef PATH_BIN 24 | $(PATH_BIN): $(BUILD_DIR)/*.obj 25 | LINK /NOLOGO /OUT:$(PATH_BIN) $(BUILD_DIR)/*.obj $(LIBS) 26 | ! endif 27 | 28 | ! ifndef OUT_LIB 29 | output_library: 30 | ! else 31 | output_library: $(BUILD_DIR)/$(OUT_LIB) 32 | INCLUDES = $(INCLUDES) -Iinclude 33 | 34 | $(BUILD_DIR)/$(OUT_LIB): $(BUILD_DIR)/*.obj 35 | LIB /NOLOGO /VERBOSE /OUT:$(BUILD_DIR)/$(OUT_LIB) $(BUILD_DIR)/*.obj 36 | ! endif 37 | -------------------------------------------------------------------------------- /examples/ActorThread/HelloWorld/src/World.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef WORLD_H 8 | #define WORLD_H 9 | 10 | #include 11 | #include 12 | #include "Printer.h" 13 | 14 | struct Kiosk { std::string itemRequest; }; 15 | struct Gallery { std::string pictureName; std::string author; }; 16 | struct Bank { double amount; std::string account; }; 17 | 18 | class World : public ActorThread 19 | { 20 | friend ActorThread; 21 | 22 | World(const std::shared_ptr& myCreator) : app(myCreator) {} 23 | 24 | void onMessage(Printer::ptr&); 25 | void onMessage(int); 26 | void onMessage(Kiosk&); 27 | void onMessage(Gallery&); 28 | void onMessage(Bank&); 29 | 30 | Printer::ptr printer; 31 | std::shared_ptr app; // equivalent to ActorThread::ptr 32 | }; 33 | 34 | #endif /* WORLD_H */ 35 | -------------------------------------------------------------------------------- /examples/ActorThread/HelloWorld/src/World.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | #include "World.h" 9 | #include "Application.h" 10 | 11 | void World::onMessage(Printer::ptr& prn) 12 | { 13 | printer = prn; 14 | printer->send(LINE(" now I can also print!")); 15 | } 16 | 17 | void World::onMessage(int year) 18 | { 19 | printer->send(LINE(" year " << year)); 20 | } 21 | 22 | void World::onMessage(Kiosk& msg) 23 | { 24 | printer->send(LINE(" is requested: " << msg.itemRequest)); 25 | app->send(Newspaper { "The Times" }); 26 | } 27 | 28 | void World::onMessage(Gallery& msg) 29 | { 30 | printer->send(LINE(" is requested: " << msg.pictureName << " (" << msg.author << ")")); 31 | app->send(Picture { 1024, 768 }); 32 | } 33 | 34 | void World::onMessage(Bank& msg) 35 | { 36 | printer->send(LINE(" is requested: " << msg.amount << " euros from " << msg.account)); 37 | app->send(Money { msg.amount }); 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /examples/ActorThread/HelloWorld/src/Printer.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef PRINTER_H 8 | #define PRINTER_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define LINE(text) VA_STR(" " << text) 19 | 20 | class Printer : public ActorThread // a dedicated printing thread prevents a mixed output 21 | { 22 | friend ActorThread; 23 | 24 | Printer() : start(std::chrono::system_clock::now()) 25 | { 26 | LINE(""); // dummy use (otherwise the helgrind tool reports false std::ostream warnings) 27 | } 28 | 29 | void onStart() 30 | { 31 | onMessage(LINE(" power on")); // safe call because is the same thread 32 | } 33 | 34 | void onMessage(const std::string& textLine) // also outputs the relative time 35 | { 36 | auto elapsed = std::chrono::duration(std::chrono::system_clock::now() - start); 37 | std::cout << std::setw(15) << std::fixed << std::setprecision(9) << elapsed.count() 38 | << " " << textLine << std::endl; 39 | } 40 | 41 | std::chrono::system_clock::time_point start; 42 | }; 43 | 44 | #endif /* PRINTER_H */ 45 | -------------------------------------------------------------------------------- /examples/ActorThread/MyLib/include/MyLib/Printer.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef PRINTER_H 8 | #define PRINTER_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define LINE(text) VA_STR(" " << text) 19 | 20 | class Printer : public ActorThread // a dedicated printing thread prevents a mixed output 21 | { 22 | friend ActorThread; 23 | 24 | Printer() : start(std::chrono::system_clock::now()) 25 | { 26 | LINE(""); // dummy use (otherwise the helgrind tool reports false std::ostream warnings) 27 | } 28 | 29 | void onStart() 30 | { 31 | onMessage(LINE(" power on")); // safe call because is the same thread 32 | } 33 | 34 | void onMessage(const std::string& textLine) // also outputs the relative time 35 | { 36 | auto elapsed = std::chrono::duration(std::chrono::system_clock::now() - start); 37 | std::cout << std::setw(15) << std::fixed << std::setprecision(9) << elapsed.count() 38 | << " " << textLine << std::endl; 39 | } 40 | 41 | std::chrono::system_clock::time_point start; 42 | }; 43 | 44 | #endif /* PRINTER_H */ 45 | -------------------------------------------------------------------------------- /examples/ActorThread/HelloWorld/src/Application.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016-2017. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "Application.h" 8 | 9 | int main(int argc, char** argv) 10 | { 11 | return Application::run(argc, argv); // blocking call (Application::weak_from_this() will not expire until stop()) 12 | } 13 | 14 | Application::Application(int /*argc*/, char** /*argv*/) 15 | { 16 | } 17 | 18 | void Application::onStart() 19 | { 20 | printer = Printer::create(); 21 | printer->send(LINE(" print test page")); 22 | 23 | world = World::create(weak_from_this().lock()); // lock() returns a non empty reference (see comment in main()) 24 | world->send(printer); 25 | 26 | world->send(2016); 27 | world->send(Kiosk { "latest newspaper" }); 28 | world->send(Gallery { "La persistencia de la memoria", "Dali" }); 29 | world->send(Bank { 50, "savings" }); 30 | 31 | timerStart(123, std::chrono::seconds(1)); 32 | } 33 | 34 | void Application::onMessage(Newspaper& msg) 35 | { 36 | printer->send(LINE(" is responded: " << msg.name )); 37 | } 38 | 39 | void Application::onMessage(Picture& msg) 40 | { 41 | printer->send(LINE(" is responded: " << msg.width << "x" << msg.height << " picture")); 42 | } 43 | 44 | void Application::onMessage(Money& msg) 45 | { 46 | printer->send(LINE(" is responded: " << msg.amount << " euros")); 47 | } 48 | 49 | void Application::onTimer(const int&) 50 | { 51 | stop(123); // valid call (self-terminate request) from threads started by run(); with optional exit code 52 | } 53 | 54 | void Application::onStop() 55 | { 56 | printer->send(LINE(" exiting")); 57 | printer->waitIdle(); 58 | world.reset(); 59 | } 60 | -------------------------------------------------------------------------------- /examples/ActorThread/MyLib/include/MyLib/MyLib.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef MYLIB_H 8 | #define MYLIB_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include "Printer.h" 14 | 15 | struct WantPrinter {}; 16 | 17 | struct LibraryIsTired 18 | { 19 | bool operator<(const LibraryIsTired&) const { return false; } // ordering is required for timers 20 | }; 21 | 22 | struct RequestA { std::string data; RequestA(const std::string& s) : data(s) {} }; 23 | struct RequestB { std::string data; RequestB(const std::string& s) : data(s) {} }; 24 | struct ReplyA { std::string data; ReplyA(const std::string& s) : data(s) {} }; 25 | struct ReplyB { std::string data; ReplyB(const std::string& s) : data(s) {} }; 26 | struct Info { std::string data; }; 27 | 28 | struct Billing 29 | { 30 | std::atomic count; // shared message (same raw pointer accessed by two threads) 31 | }; 32 | 33 | class MyLib : public ActorThread 34 | { 35 | friend ActorThread; 36 | 37 | MyLib() : printer(Printer::create()), bills(std::make_shared()) {} 38 | 39 | template void onMessage(Any&); 40 | template void onTimer(const Any&); 41 | 42 | Printer::ptr printer; 43 | std::shared_ptr bills; 44 | 45 | public: 46 | 47 | template void basicSubscriptions(const Client& client) // basic data reception by clients 48 | { 49 | connect(client); 50 | connect(client); 51 | connect>(client); 52 | connect>(client); // const only to fulfil the timers signature for the weird example 53 | } 54 | }; 55 | 56 | #endif /* MYLIB_H */ 57 | -------------------------------------------------------------------------------- /examples/ActorThread/MyLib/src/MyLib.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | #include "MyLib/MyLib.h" 9 | 10 | template <> void MyLib::onMessage(WantPrinter&) 11 | { 12 | printer->send(LINE(" sending printer to client")); 13 | publish(printer); 14 | 15 | // some activity to spend ink 16 | timerStart('A', std::chrono::nanoseconds(333333333), TimerCycle::Periodic); 17 | timerStart(std::string("faster event"), std::chrono::seconds(1), TimerCycle::Periodic); // char const* const& is ugly 18 | timerStart("slower event", std::chrono::seconds(2), TimerCycle::Periodic); // alternative syntax 19 | timerStart(LibraryIsTired{}, std::chrono::seconds(8)); 20 | 21 | auto billingAddress = callback>(); // billingAddress(bill) is equivalent to publish(bill) 22 | timerStart(bills, std::chrono::seconds(1), billingAddress, TimerCycle::Periodic); // periodic invocation 23 | } 24 | 25 | template <> void MyLib::onMessage(std::shared_ptr& msg) 26 | { 27 | printer->send(LINE(" received " << msg->data)); 28 | publish(std::make_shared("reply to " + msg->data)); 29 | bills->count++; 30 | } 31 | 32 | template <> void MyLib::onMessage(std::shared_ptr& msg) 33 | { 34 | printer->send(LINE(" received " << msg->data)); 35 | auto reply = std::make_shared("reply to " + msg->data); 36 | publish(std::move(reply)); // example invalidating 'reply' 37 | if (reply) printer->send(LINE(" no subscriber to replies for " << msg->data)); 38 | bills->count++; 39 | } 40 | 41 | template <> void MyLib::onTimer(const std::string& whatEvent) 42 | { 43 | publish(std::unique_ptr(new Info { whatEvent })); 44 | } 45 | 46 | template <> void MyLib::onTimer(const char& acter) 47 | { 48 | printer->send(LINE(" beat " << acter)); 49 | } 50 | 51 | template <> void MyLib::onTimer(const LibraryIsTired& seriously) 52 | { 53 | publish(seriously); 54 | } 55 | -------------------------------------------------------------------------------- /examples/ActorThread/MyLibClient/src/Application.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016-2017. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | #include "Application.h" 9 | 10 | int main(int argc, char** argv) 11 | { 12 | return Application::run(argc, argv); 13 | } 14 | 15 | void Application::onStart() 16 | { 17 | library->basicSubscriptions(weak_from_this()); // all except ReplyA and ReplyB 18 | 19 | library->connect(getChannel>()); 20 | library->connect>(weak_from_this()); // (alternative syntax) 21 | 22 | library->send(WantPrinter{}); 23 | } 24 | 25 | template <> void Application::onMessage(Printer::ptr& msg) 26 | { 27 | printer = msg; 28 | } 29 | 30 | template <> void Application::onMessage(std::unique_ptr& msg) 31 | { 32 | printer->send(LINE(" received " << msg->data)); 33 | 34 | // Programmers not very seasoned managing the objects lifecycle may be concerned 35 | // about the risk of 'library' being potentially deleted at the moment they 36 | // need to invoke its send() method. In the following example, a safeLibrary() 37 | // functor is used instead, which wouldn't crash even in such situation: 38 | 39 | if (msg->data.find("fast") != std::string::npos) 40 | safeLibrary(std::make_shared("RequestA")); // equivalent to library->send() 41 | else 42 | safeLibrary(std::make_shared("RequestB")); 43 | } 44 | 45 | template <> void Application::onMessage(std::shared_ptr& msg) 46 | { 47 | printer->send(LINE(" received " << msg->data)); 48 | } 49 | 50 | template <> void Application::onMessage(std::shared_ptr& msg) 51 | { 52 | printer->send(LINE(" received " << msg->data)); 53 | } 54 | 55 | template <> void Application::onMessage(std::shared_ptr& msg) 56 | { 57 | printer->send(LINE(" owes " << msg->count << " bills")); 58 | } 59 | 60 | template <> void Application::onMessage(LibraryIsTired&) 61 | { 62 | printer->send(LINE(" shutting down")); 63 | printer->waitIdle(); 64 | stop(); 65 | } 66 | -------------------------------------------------------------------------------- /include/sys++/String.hpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef STRING_H_ 8 | #define STRING_H_ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef VA_STR 16 | #define VA_STR(x) static_cast(std::ostringstream().flush() << x).str() 17 | #endif 18 | 19 | struct String // a "namespace" not requiring a cpp nor inlining to avoid "unused function" warnings 20 | { 21 | static void tolower(std::string& str) 22 | { 23 | std::transform(str.begin(), str.end(), str.begin(), ::tolower); 24 | } 25 | 26 | static void toupper(std::string& str) 27 | { 28 | std::transform(str.begin(), str.end(), str.begin(), ::toupper); 29 | } 30 | 31 | static void ltrim(std::string& str) 32 | { 33 | std::string::iterator i = str.begin(); 34 | while (i != str.end()) if (!std::isspace(*i)) break; else ++i; 35 | str.erase(str.begin(), i); 36 | } 37 | 38 | static void rtrim(std::string& str) 39 | { 40 | std::string::iterator i = str.end(); 41 | while (i != str.begin()) if (!std::isspace(*(--i))) { ++i; break; } 42 | str.erase(i, str.end()); 43 | } 44 | 45 | static void trim(std::string& str) 46 | { 47 | rtrim(str); 48 | ltrim(str); 49 | } 50 | 51 | static std::string trimmed(std::string str) 52 | { 53 | trim(str); 54 | return str; 55 | } 56 | 57 | static std::string right(const std::string& str, std::string::size_type count) 58 | { 59 | return str.substr(str.size() - std::min(count, str.size())); 60 | } 61 | 62 | static void replaceAll(std::string& str, const std::string& sWhat, const std::string& sWith) 63 | { 64 | std::string::size_type lookHere = 0; 65 | std::string::size_type foundHere; 66 | if (sWhat.length()) while ((foundHere = str.find(sWhat, lookHere)) != std::string::npos) 67 | { 68 | str.replace(foundHere, sWhat.size(), sWith); 69 | lookHere = foundHere + sWith.size(); 70 | } 71 | } 72 | 73 | template static void split(const std::string& str, const char delimiter, T& result, bool trimmed = true) 74 | { 75 | std::istringstream ss(str); 76 | std::string item; 77 | while (std::getline(ss, item, delimiter)) 78 | { 79 | if (trimmed) trim(item); 80 | result.emplace_back(item); 81 | } 82 | if (str.length() && (str.back() == delimiter)) result.emplace_back(std::string()); 83 | } 84 | }; 85 | 86 | #endif /* STRING_H_ */ 87 | -------------------------------------------------------------------------------- /examples/ActorThread/Test/src/Application.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef APPLICATION_H 8 | #define APPLICATION_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | struct SyncBegin { bool master; }; 15 | struct SyncMsg { int counter; }; 16 | struct SyncEnd { int counter; }; 17 | 18 | struct AsyncBegin {}; 19 | struct AsyncMsg { int counter; bool last; }; 20 | struct AsyncEnd { int counter; }; 21 | 22 | struct MixedBegin {}; 23 | struct A {}; 24 | struct B {}; 25 | struct MixedEnd {}; 26 | struct MixedStats { int sntA, sntB, recvA, recvB; }; 27 | 28 | struct MpscBegin { int id; }; 29 | struct Mpsc { int id; int counter; }; 30 | struct MpscEnd { int id; }; 31 | 32 | struct BreedExplode { int amount; int generation; int maxGenerations; }; 33 | struct BreedImplode { std::shared_ptr child; int implosions; }; 34 | 35 | class Task : public ActorThread 36 | { 37 | friend ActorThread; 38 | 39 | Task(std::shared_ptr parent) 40 | : app(parent), gen(std::random_device{}()), rnd(0,9), 41 | syncTestCompleted(false), mixedTestCompleted(false), mixedTestPaused(false), 42 | fstats { 0, 0, 0, 0 }, implosions(0) {} 43 | 44 | Task(Task::ptr parent) : ancestor(parent), implosions(0) {} // for breeding test 45 | 46 | template void onMessage(Any&); 47 | template void onTimer(const Any&); 48 | void doMixed(); 49 | 50 | std::shared_ptr app; 51 | Task::ptr sibling; 52 | 53 | std::default_random_engine gen; 54 | std::uniform_int_distribution rnd; 55 | 56 | bool syncTestCompleted; 57 | bool mixedTestCompleted; 58 | bool mixedTestPaused; 59 | 60 | MixedStats fstats; 61 | 62 | Task::ptr ancestor; 63 | std::set pendingChilds; 64 | int implosions; 65 | }; 66 | 67 | class Application : public ActorThread 68 | { 69 | friend ActorThread; 70 | 71 | Application(int cmdArgc, char** cmdArgv) : argc(cmdArgc), argv(cmdArgv) {} 72 | 73 | void onStart(); 74 | 75 | template void onMessage(Any&); 76 | template void onTimer(const Any&); 77 | 78 | const int argc; 79 | char** const argv; 80 | 81 | Task::ptr snd1; 82 | Task::ptr snd2; 83 | 84 | std::chrono::steady_clock::time_point tStart; 85 | int repliesCount; 86 | 87 | int count_mpsc1, count_mpsc2, count_mpsc1_lap, count_mpsc2_lap; 88 | double mpsc_elapsed_lap, mpsc_elapsed_sc1, mpsc_elapsed_sc2; 89 | bool crazyScheduler; 90 | }; 91 | 92 | #endif /* APPLICATION_H */ 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ essential additions 2 | 3 | ## ActorThread: Active Object pattern in C++ 4 | 5 | Implementation of the 6 | [Active Object pattern](http://www.drdobbs.com/parallel/prefer-using-active-objects-instead-of-n/225700095) 7 | wrapping a standard C++11 thread. 8 | 9 | ### Simple 10 | * Whole implementation contained in **a single header file!** 11 | * Inherit from a template and you are done. See the tiny example bellow. 12 | 13 | ### Main features 14 | * Exchange messages of any type (does not requires them to derive from a common base class) 15 | * Messages are asynchronously delivered in the same order they were sent 16 | * Allows to invoke callbacks on clients of unknown type (useful for libraries) 17 | * Callbacks on the active object *auto-store themselves* with no boilerplate code 18 | * Timers ability with *client-driven handlers* (no need for handler↔object resolving maps) 19 | 20 | ### Performance 21 | * Internal lock-free MPSC messages queue 22 | * Extensive internal use of move semantics supporting delivery of non-copiable objects 23 | * Several million msg/sec between each two threads (both Linux and Windows) in ordinary hardware 24 | 25 | ### Robustness 26 | * The wrapped thread lifecycle overlaps and is driven by the object existence 27 | * The object is kept alive by smart pointers (whoever has a reference can safely send messages) 28 | * No internal strong references (only the final users determine the destruction/end) 29 | * Nonetheless, callbacks onto already deleted active objects do not crash the application 30 | 31 | ### Minimum compiler required 32 | * Mininum gcc version supported is 4.8.0 (which added the thread_local keyword) 33 | * Works with clang 3.3 and Visual Studio 2015 Update 3 (no previous versions tested on both) 34 | * Clean, standard C++11 (no conditional code, same implementation for all platforms) 35 | 36 | ### Example 37 | 38 | ```cpp 39 | // Linux: g++ -std=c++11 -lpthread demo.cpp -o demo 40 | // Windows: cl.exe demo.cpp 41 | 42 | #include 43 | #include 44 | #include 45 | #include "ActorThread.hpp" 46 | 47 | struct Message { std::string description; }; 48 | struct OtherMessage { double beautifulness; }; 49 | 50 | class Consumer : public ActorThread 51 | { 52 | friend ActorThread; 53 | void onMessage(Message& p) 54 | { 55 | std::cout << "thread " << std::this_thread::get_id() 56 | << " receiving " << p.description << std::endl; 57 | } 58 | void onMessage(OtherMessage& p) 59 | { 60 | std::cout << "thread " << std::this_thread::get_id() 61 | << " receiving " << p.beautifulness << std::endl; 62 | } 63 | }; 64 | 65 | class Producer : public ActorThread 66 | { 67 | friend ActorThread; 68 | std::shared_ptr consumer; 69 | void onMessage(std::shared_ptr& c) 70 | { 71 | consumer = c; 72 | timerStart(true, std::chrono::milliseconds(250), TimerCycle::Periodic); 73 | timerStart(3.14, std::chrono::milliseconds(333), TimerCycle::Periodic); 74 | } 75 | void onTimer(const bool&) 76 | { 77 | std::ostringstream report; 78 | report << "test from thread " << std::this_thread::get_id(); 79 | consumer->send(Message { report.str() }); 80 | } 81 | void onTimer(const double& value) 82 | { 83 | consumer->send(OtherMessage { value }); 84 | } 85 | }; 86 | 87 | class Application : public ActorThread 88 | { 89 | friend ActorThread; 90 | void onStart() 91 | { 92 | auto consumer = Consumer::create(); // spawn new threads 93 | auto producer = Producer::create(); 94 | producer->send(consumer); 95 | std::this_thread::sleep_for(std::chrono::seconds(3)); 96 | stop(); 97 | } 98 | }; 99 | 100 | int main() 101 | { 102 | return Application::run(); // re-use existing thread 103 | } 104 | ``` 105 | Despite received by reference, *a copy* of the original object is delivered to the destination thread. 106 | Alternatively, the carried object can be moved, which is the way to transfer non-copiable objects like a unique_ptr. 107 | Copying is highly efficient with pointers, but note that several threads must not concurrently access a unsafe pointed object. 108 | 109 | See the [*examples*](examples/ActorThread/) folder for more elaborated examples, including a library and its client using the callbacks mechanism. 110 | -------------------------------------------------------------------------------- /posix.mk: -------------------------------------------------------------------------------- 1 | # "Universal C++ POSIX Makefile" v2.1 for GNU make 2 | 3 | # Copyright Ciriaco Garcia de Celis 2011-2016. 4 | # Distributed under the Boost Software License, Version 1.0. 5 | # (See accompanying file LICENSE_1_0.txt or copy at 6 | # http://www.boost.org/LICENSE_1_0.txt) 7 | 8 | # With automatic dependencies generation (gcc/clang) and recursive source directory tree support 9 | # 10 | # In your project(s) Makefile it is only required to define a few settings and include this file AT THE END: 11 | # 12 | # Settings which can be defined in any project (all optional unless otherwise stated): 13 | # BUILD_DIR := debug # mandatory setting 14 | # SRC_DIR := src # mandatory setting 15 | # INCLUDES := 16 | # CXX := g++ # a default value is provided in your system 17 | # CXXFLAGS := -O0 -g3 -DDEBUG # see also ARCHFLAGS and WARNFLAGS bellow 18 | # 19 | # Settings to be defined only for library subprojects 20 | # OUT_LIB := LibName.a # mandatory setting (file will always be placed in BUILD_DIR) 21 | # 22 | # Settings to be defined only for executable projects: 23 | # PATH_BIN := $(BUILD_DIR)/executable # mandatory setting 24 | # SUBPRJS := ../Lib1 ../Lib2 # each require its own Makefile 25 | # LIBS := ../../ExternLib/SomeLib.a # Note: omit subprojects (implicitly linked and included) 26 | # LDLIBS := -lboost_system -lpthread 27 | # LDFLAGS := 28 | # NOTES: 29 | # * The includes exported by library subprojects must be placed in a directory called 'include' 30 | # * When used on a top project, its library subprojects makefiles are automatically invoked in the proper order: 31 | # - Any target will be passed to them (not only 'all' or 'clean') 32 | # - To make only the top project run "DONTLOOP=1 make" 33 | # - If ARCHFLAGS/WARNFLAGS are setup they need to be exported when aiming to override implicit defaults in subprojects 34 | # * To hook 'all' or 'clean' targets in your Makefile use '::' (instead of ':') 35 | # * Other targets than 'all' or 'clean' should be placed after including this file (to keep 'all' as the default target) 36 | 37 | ARCHFLAGS ?= -std=c++11 -march=native # set these variables in your Makefile to override these implicit defaults 38 | 39 | WARNFLAGS ?= -Wall -Wextra -pedantic -Wconversion -Wsign-conversion -Wsign-promo -Wcast-qual -Wfloat-equal \ 40 | -Wpointer-arith -Wnon-virtual-dtor -Woverloaded-virtual -Wshadow -Wundef -Wmissing-include-dirs 41 | 42 | CXXFLAGS += $(ARCHFLAGS) $(WARNFLAGS) 43 | 44 | CXXFLAGS := $(filter-out $(SKIPFLAGS), $(CXXFLAGS)) # allow skipping particular warnings 45 | 46 | INCLUDES := $(foreach dir,$(SUBPRJS),-I$(dir)/include) $(INCLUDES) 47 | LIBS := $(foreach dir,$(SUBPRJS),$(wildcard $(dir)/$(BUILD_DIR)/*.a)) $(LIBS) 48 | 49 | ifdef OUT_LIB 50 | INCLUDES := -Iinclude $(INCLUDES) 51 | endif 52 | 53 | ifndef MK_NOHL 54 | HL_DONE := \e[30;42mDONE 55 | HL_ERROR := \e[30;101mERROR 56 | HL_CMD := \e[35m 57 | HL_OFF := \e[0m 58 | else 59 | HL_DONE := [DONE] 60 | HL_ERROR := [ERROR] 61 | endif 62 | 63 | ifndef SUBPRJS 64 | MAKEACTIVE := 1 65 | else 66 | ifdef DONTLOOP 67 | MAKEACTIVE := 1 68 | else 69 | export DONTLOOP := 1 70 | 71 | reverse_list = $(if $(1),$(call reverse_list,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1)) 72 | 73 | ALL_MK_PROJECTS := $(call reverse_list,$(SUBPRJS)) . 74 | 75 | all:: whichever 76 | 77 | whichever:: 78 | @for prj in $(ALL_MK_PROJECTS); do if ! \ 79 | $(MAKE) -C $$prj --no-print-directory $(MAKECMDGOALS) \ 80 | ; then echo -e "$(HL_ERROR) $$prj$(HL_OFF)" && exit 1; fi; done 81 | 82 | .PHONY: whichever all $(MAKECMDGOALS) 83 | 84 | $(foreach target, $(MAKECMDGOALS), $(eval $(target):: whichever)) 85 | 86 | endif # DONTLOOP 87 | endif # SUBPRJS 88 | 89 | ifdef MAKEACTIVE 90 | 91 | ifdef MK_FULLPATH # useful for IDE diagnostic parsing without requiring to setup the build directory 92 | SRC_DIR := $(shell pwd)/$(SRC_DIR) 93 | endif 94 | 95 | SRC_SUBDIRS := $(shell [ -d $(SRC_DIR) ] && find -L $(SRC_DIR) -type d \ 96 | -not \( -path $(SRC_DIR)/$(BUILD_DIR) -prune \) -not \( -name .git -prune \)) 97 | BUILD_SUBDIRS := $(patsubst $(SRC_DIR)%,$(BUILD_DIR)%,$(SRC_SUBDIRS)) 98 | MKDIR_SUBDIRS := $(sort $(patsubst %/,,$(BUILD_SUBDIRS) $(dir $(PATH_BIN)) $(dir $(PATH_LIB)))) 99 | PATH_LIB ?= $(if $(OUT_LIB),$(BUILD_DIR)/$(OUT_LIB)) 100 | SRC_FILES := $(sort $(foreach sdir,$(SRC_SUBDIRS),$(wildcard $(sdir)/*.cpp)) $(AUTOGEN)) 101 | OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRC_FILES)) 102 | DEPENDENCIES := $(OBJ_FILES:.o=.d) 103 | 104 | vpath %.cpp $(SRC_SUBDIRS) $(BUILD_SUBDIRS) 105 | 106 | .PHONY: all clean checkdirs 107 | 108 | all:: checkdirs $(PATH_BIN) $(PATH_LIB) 109 | @echo -e "$(HL_DONE)$(HL_OFF) $(lastword $(subst /, ,$(CURDIR)))" 110 | 111 | clean:: 112 | @rm -rf $(BUILD_SUBDIRS) 113 | 114 | checkdirs: $(MKDIR_SUBDIRS) 115 | 116 | $(MKDIR_SUBDIRS): 117 | @mkdir -p $@ 118 | 119 | -include $(DEPENDENCIES) 120 | 121 | $(PATH_LIB): $(OBJ_FILES) 122 | @echo -e "$(HL_CMD)$(AR)$(HL_OFF)" -r "$(HL_CMD)$@$(HL_OFF)" $(OBJ_FILES) 123 | @$(AR) -r $@ $(OBJ_FILES) 124 | 125 | $(PATH_BIN): $(OBJ_FILES) $(LIBS) 126 | @echo -e "$(HL_CMD)$(CXX)$(HL_OFF)" $(LDFLAGS) $(OBJ_FILES) $(CXXFLAGS) -MMD "$(HL_CMD)-o $@$(HL_OFF)" $(LIBS) $(LDLIBS) 127 | @$(CXX) $(LDFLAGS) $(OBJ_FILES) $(CXXFLAGS) -MMD -o $@ $(LIBS) $(LDLIBS) 128 | 129 | define build-subdir-goal 130 | $1/%.o: %.cpp 131 | @echo -e "$(HL_CMD)$(CXX)$(HL_OFF)" $(CXXFLAGS) $(INCLUDES) -MMD "$(HL_CMD)-c $$<$(HL_OFF)" -o $$@ 132 | @$(CXX) $(CXXFLAGS) $(INCLUDES) -MMD -c $$< -o $$@ 133 | endef 134 | 135 | $(foreach bdir,$(BUILD_SUBDIRS),$(eval $(call build-subdir-goal,$(bdir)))) 136 | 137 | endif # MAKEACTIVE 138 | -------------------------------------------------------------------------------- /examples/ActorThread/Test/src/Application.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Ciriaco Garcia de Celis 2016-2017. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "Application.h" 13 | 14 | #define DURATION_SYNC std::chrono::seconds(4) 15 | #define DURATION_ASYNC std::chrono::milliseconds(250) // uses a lot of memory 16 | #define DURATION_MIXED std::chrono::seconds(3) 17 | #define DURATION_MPSC std::chrono::seconds(2) 18 | 19 | int main(int argc, char **argv) 20 | { 21 | return Application::run(argc, argv); 22 | } 23 | 24 | template <> void Task::onMessage(Task::ptr& peer) 25 | { 26 | sibling = peer; 27 | } 28 | 29 | template <> void Task::onMessage(SyncBegin& msg) 30 | { 31 | if (msg.master) 32 | { 33 | timerStart('S', DURATION_SYNC); 34 | sibling->send(SyncMsg{ 1 }); 35 | } 36 | } 37 | 38 | template <> void Task::onMessage(SyncMsg& msg) // sends one message after receiving another (note 39 | { // that to a high degree, this test mostly measures 40 | if (!syncTestCompleted) // the OS context switching performance, since the 41 | { msg.counter++; sibling->send(msg); } // threads go idle after each message) 42 | else 43 | app->send(SyncEnd{ msg.counter }); 44 | } 45 | 46 | template <> void Task::onMessage(AsyncBegin&) 47 | { 48 | auto deadline = std::chrono::steady_clock::now() + DURATION_ASYNC; 49 | int counter = 0; 50 | while (std::chrono::steady_clock::now() < deadline) 51 | for (auto cnt = 0; cnt < 10000; cnt++) sibling->send(AsyncMsg { ++counter, false }); 52 | sibling->send(AsyncMsg { ++counter, true }); 53 | } 54 | 55 | template <> void Task::onMessage(AsyncMsg& msg) 56 | { 57 | if (msg.last) app->send(AsyncEnd { msg.counter }); // both threads notify the completion when receiving the last message 58 | } 59 | 60 | void Task::doMixed() 61 | { 62 | auto pending = sibling->pendingMessages(); 63 | if (mixedTestPaused && (pending < 1000)) mixedTestPaused = false; 64 | if (!mixedTestPaused && (pending > 2000)) mixedTestPaused = true; 65 | if (mixedTestPaused) return; 66 | 67 | if (rnd(gen) < 5) 68 | for (auto i = rnd(gen); i >= 0; i--) { sibling->send(A{}); fstats.sntA++; } 69 | else 70 | for (auto i = rnd(gen); i >= 0; i--) { sibling->send(B{}); fstats.sntB++; } 71 | } 72 | 73 | template <> void Task::onMessage(MixedBegin&) 74 | { 75 | timerStart('A', DURATION_MIXED); 76 | doMixed(); 77 | } 78 | 79 | template <> void Task::onMessage(A&) 80 | { 81 | fstats.recvA++; 82 | if (!mixedTestCompleted) doMixed(); 83 | } 84 | 85 | template <> void Task::onMessage(B&) 86 | { 87 | fstats.recvB++; 88 | if (!mixedTestCompleted) doMixed(); 89 | } 90 | 91 | template <> void Task::onMessage(MixedEnd&) 92 | { 93 | app->send(fstats); 94 | } 95 | 96 | template <> void Task::onMessage(MpscBegin& msg) // pendingMessages() == 1 at the beginning (just this one) 97 | { 98 | int counter = 0; 99 | while (pendingMessages() < 2) // while MpscEnd not yet received from parent 100 | app->send(Mpsc { msg.id, ++counter }); // run the most intensive throughput possible 101 | } 102 | 103 | template <> void Task::onMessage(MpscEnd& msg) 104 | { 105 | app->send(Mpsc { msg.id, -1 }); // acknowledge the end 106 | } 107 | 108 | template <> void Task::onMessage(BreedExplode& msg) 109 | { 110 | if (msg.generation <= msg.maxGenerations) 111 | { 112 | for (auto i = 0; i < msg.amount; i++) 113 | { 114 | auto child = Task::create(weak_from_this().lock()); 115 | child->send(BreedExplode { msg.amount, msg.generation+1, msg.maxGenerations }); 116 | pendingChilds.insert(child); // keeps the child thread referenced (and alive) 117 | } 118 | } 119 | else // last generation: trigger the implosion 120 | { 121 | ancestor->send(BreedImplode { weak_from_this().lock(), 1 }); 122 | } 123 | } 124 | 125 | template <> void Task::onMessage(BreedImplode& msg) 126 | { 127 | implosions += msg.implosions; 128 | pendingChilds.erase(msg.child); // not yet deleted nor stopped (at the least referenced from 'msg.child') 129 | 130 | // The following code is intentionally commented to take advantage of the chaotic 131 | // destruction, in order to cause some threads going out of scope while still running, 132 | // which triggers a self-destruction in the last instance: a thread can't join itself, 133 | // and so chooses to detach (to avoid the application crash) and perform an asynchronous 134 | // exiting. In addition, this will allow to demonstrate that, when enough time is left 135 | // before the application ends (because the system overload) these orphan threads still 136 | // manage to delete their own object just before stopping (no memory is leaked): 137 | 138 | // Waiting for the child idle ensures that it has returned from this method besides 139 | // having notified us (thus having removed the reference to the grandchilds still 140 | // pointing it and allowing an ordered top-down destruction): 141 | // 142 | // msg.child->waitIdle(); 143 | 144 | // The following reset alone is also enough to cause an ordered destruction unless there 145 | // were an additional reference to the child preventing its dead. That could have been the 146 | // send() call using a intermediate variable (instead of a temporal as in this example) 147 | // or a previous ActorThread implementation which did not optimized the rvalues movement: 148 | // 149 | // msg.child.reset(); 150 | 151 | if (pendingChilds.empty()) 152 | { 153 | if (ancestor) 154 | ancestor->send(BreedImplode { weak_from_this().lock(), 1 + implosions }); 155 | else 156 | app->send(BreedImplode { weak_from_this().lock(), implosions }); // root thread 157 | } 158 | } 159 | 160 | template <> void Task::onTimer(const char& timer) 161 | { 162 | if (timer == 'S') syncTestCompleted = true; 163 | else 164 | { 165 | sibling->send(MixedEnd{}); 166 | mixedTestCompleted = true; 167 | } 168 | } 169 | 170 | void Application::onStart() 171 | { 172 | std::cout << "testing performance..." << std::endl; 173 | 174 | snd1 = Task::create(weak_from_this().lock()); 175 | snd2 = Task::create(weak_from_this().lock()); 176 | snd1->send(snd2); 177 | snd2->send(snd1); 178 | 179 | tStart = std::chrono::steady_clock::now(); 180 | snd1->send(SyncBegin{ true }); 181 | snd2->send(SyncBegin{ false }); 182 | } 183 | 184 | template <> void Application::onMessage(SyncEnd& msg) 185 | { 186 | auto elapsed = std::chrono::duration(std::chrono::steady_clock::now() - tStart).count(); 187 | std::cout << msg.counter / elapsed << " synchronous messages per second" << std::endl; 188 | 189 | repliesCount = 0; 190 | tStart = std::chrono::steady_clock::now(); 191 | snd1->send(AsyncBegin{}); 192 | snd2->send(AsyncBegin{}); 193 | } 194 | 195 | template <> void Application::onMessage(AsyncEnd& msg) 196 | { 197 | auto elapsed = std::chrono::duration(std::chrono::steady_clock::now() - tStart).count(); 198 | std::cout << msg.counter / elapsed << " asynchronous messages per second and thread" << std::endl; 199 | repliesCount++; 200 | if (repliesCount == 2) 201 | { 202 | repliesCount = 0; 203 | tStart = std::chrono::steady_clock::now(); 204 | snd1->send(MixedBegin{}); 205 | snd2->send(MixedBegin{}); 206 | } 207 | } 208 | 209 | template <> void Application::onMessage(MixedStats& msg) 210 | { 211 | auto elapsed = std::chrono::duration(std::chrono::steady_clock::now() - tStart).count(); 212 | std::cout << (msg.sntA + msg.sntB + msg.recvA + msg.recvB) / elapsed << " msg/sec mixed test" 213 | << " sntA=" << msg.sntA << " sntB=" << msg.sntB 214 | << " recvA=" << msg.recvA << " recvB=" << msg.recvB << std::endl; 215 | repliesCount++; 216 | if (repliesCount == 2) 217 | { 218 | count_mpsc1 = count_mpsc2 = 0; 219 | repliesCount = 0; 220 | tStart = std::chrono::steady_clock::now(); 221 | timerStart(123, DURATION_MPSC); // Multiple Producer Single Consumer (actually 2P1C) test 222 | snd1->send(MpscBegin{ 1 }); // a number is assigned to each producer 223 | snd2->send(MpscBegin{ 2 }); 224 | } 225 | } 226 | 227 | template <> void Application::onMessage(Mpsc& msg) 228 | { 229 | if (msg.counter > 0) // return as fast as possible to cope with the traffic generated from both producers 230 | { 231 | (msg.id == 1? count_mpsc1 : count_mpsc2) = msg.counter; 232 | } 233 | else // last message from a producer 234 | { 235 | auto elapsed = std::chrono::steady_clock::now() - tStart; 236 | (msg.id == 1? mpsc_elapsed_sc1 : mpsc_elapsed_sc2) = std::chrono::duration(elapsed).count(); 237 | repliesCount++; 238 | if (repliesCount == 2) // end of 0P1C phase? 239 | { 240 | auto per_second_produced_2p1c = (count_mpsc1 + count_mpsc2) / mpsc_elapsed_lap; 241 | auto per_second_consumed_2p1c = (count_mpsc1_lap + count_mpsc2_lap) / mpsc_elapsed_lap; 242 | auto sc1 = count_mpsc1 - count_mpsc1_lap; 243 | auto sc2 = count_mpsc2 - count_mpsc2_lap; 244 | auto elapsed_sc_avg = (mpsc_elapsed_sc1 + mpsc_elapsed_sc2) / 2; 245 | 246 | double min_msgs = std::min(std::min(std::min(count_mpsc1, count_mpsc2), count_mpsc1_lap), count_mpsc2_lap); 247 | 248 | double r_2p1c_p = 1.0 * std::max(count_mpsc1, count_mpsc2) / std::min(count_mpsc1, count_mpsc2); 249 | double r_2p1c_c = 1.0 * std::max(count_mpsc1_lap, count_mpsc2_lap) / std::min(count_mpsc1_lap, count_mpsc2_lap); 250 | double r_0p1c_c = 1.0 * std::max(sc1, sc2) / std::min(sc1, sc2); 251 | double max_ratio = std::max(std::max(r_2p1c_p, r_2p1c_c), r_0p1c_c); // hint of smoothness during the contention 252 | 253 | crazyScheduler = (min_msgs < 100) || (max_ratio > 50); 254 | 255 | std::cout << per_second_produced_2p1c << " msg/sec produced (" 256 | << count_mpsc1 / mpsc_elapsed_lap << " + " << count_mpsc2 / mpsc_elapsed_lap 257 | << ") 2P1C test in " << mpsc_elapsed_lap << " seconds" << std::endl; 258 | std::cout << per_second_consumed_2p1c << " msg/sec consumed (" 259 | << count_mpsc1_lap / mpsc_elapsed_lap << " + " << count_mpsc2_lap / mpsc_elapsed_lap 260 | << ") 2P1C test in " << mpsc_elapsed_lap << " seconds" << std::endl; 261 | std::cout << (per_second_produced_2p1c + per_second_consumed_2p1c) / 3 << " msg/sec throughput " 262 | << "per thread 2P1C test (priority inversion hint: " << max_ratio << ")" << std::endl; 263 | std::cout << (sc1 + sc2) / elapsed_sc_avg << " msg/sec consumed (" 264 | << sc1 / mpsc_elapsed_sc1 << " + " << sc2 / mpsc_elapsed_sc2 << ") 0P1C test in " 265 | << elapsed_sc_avg << " seconds" << std::endl; 266 | 267 | repliesCount = 0; 268 | tStart = std::chrono::steady_clock::now(); // start next test 269 | bool haveParameter = argc > 1; 270 | if (haveParameter) 271 | snd1->send(BreedExplode { 2, 1, std::atoi(argv[1]) > 0? std::atoi(argv[1]) : 1 }); 272 | else 273 | snd1->send(BreedExplode { 3, 1, 5 }); // by default not too many (valgrind limits friendly) 274 | } 275 | } 276 | } 277 | 278 | template <> void Application::onMessage(BreedImplode& msg) // last test completed 279 | { 280 | auto elapsed = std::chrono::duration(std::chrono::steady_clock::now() - tStart).count(); 281 | std::cout << msg.implosions << " threads created, communicated and deleted in " << elapsed << " seconds" << std::endl; 282 | timerStart('H', std::chrono::milliseconds(500)); // leave time for detached threads to stop (avoid memory leaks) 283 | } 284 | 285 | template <> void Application::onTimer(const int&) // end of 2P1C phase 286 | { 287 | mpsc_elapsed_lap = std::chrono::duration(std::chrono::steady_clock::now() - tStart).count(); 288 | tStart = std::chrono::steady_clock::now(); 289 | snd1->send(MpscEnd{ 1 }); // signal both producers to stop the message delivery (now starts the 0P1C 290 | snd2->send(MpscEnd{ 2 }); // phase, flushing the messages queued to this thread and not yet processed) 291 | count_mpsc1_lap = count_mpsc1; 292 | count_mpsc2_lap = count_mpsc2; 293 | } 294 | 295 | template <> void Application::onTimer(const char&) // end of application 296 | { 297 | if (crazyScheduler) 298 | std::cout << std::endl 299 | << "Advice: when running under valgrind the \"--fair-sched=yes\" option is recommended" 300 | << std::endl << std::endl; 301 | 302 | snd1->send(Task::ptr()); // remove circular reference (avoid valgrind 303 | snd2->send(Task::ptr()); // "possibly lost" message regarding memory) 304 | snd1->waitIdle(); 305 | snd2->waitIdle(); 306 | 307 | snd1.reset(); // remove them to wipe their reference to us preventing 308 | snd2.reset(); // our deletion (another valgrind "possibly lost") 309 | 310 | stop(); 311 | } 312 | -------------------------------------------------------------------------------- /include/sys++/ActorThread.hpp: -------------------------------------------------------------------------------- 1 | // Active Object pattern wrapping a standard C++11 thread (https://github.com/lightful/syscpp) 2 | // 3 | // Copyright Ciriaco Garcia de Celis 2016-2017. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // http://www.boost.org/LICENSE_1_0.txt) 7 | /* 8 | - Publicly inherit from this template (specializing it for the derived class itself) 9 | - On the derived (active object) class everything should be private: make this base a friend 10 | - Instance the active object by invoking the inherited public static create() or run() methods 11 | - Use send() to send or move messages (of any data type) to the active object 12 | - Use onMessage(AnyType&) methods to implement the messages reception on the active object 13 | - Optionally use a Gateway wrapper or build Channel objects instead of send() 14 | - Optionally override onStart() and onStop() in the active object 15 | - Optionally use connect() from unknown clients to bind callbacks for any data type 16 | - Optionally use publish() from the active object to invoke the binded callbacks 17 | - Optionally use timerStart() / timerStop() / timerReset() from the active object 18 | */ 19 | #ifndef ACTORTHREAD_HPP 20 | #define ACTORTHREAD_HPP 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | template class ActorThread 37 | { 38 | public: 39 | 40 | typedef std::shared_ptr ptr; 41 | 42 | std::weak_ptr weak_from_this() const noexcept { return weak_this; } // shared_from_this() would be unsafe 43 | 44 | template static ptr create(Args&&... args) // spawn a new thread 45 | { 46 | auto task = ptr(new Runnable(std::forward(args)...), actorThreadRecycler); 47 | task->weak_this = task; 48 | task->runner = std::thread(&ActorThread::dispatcher, task.get()); 49 | return task; 50 | } 51 | 52 | template static int run(Args&&... args) // run in the calling thread (e.g. main() thread) 53 | { 54 | struct ActRunTask : public Runnable { ActRunTask(Args&&... arg) : Runnable(std::forward(arg)...) {} }; 55 | auto task = std::make_shared(std::forward(args)...); 56 | task->weak_this = task; 57 | return task->dispatcher(); 58 | } 59 | 60 | template inline void send(Any msg) // polymorphic message passing 61 | { 62 | post, HighPri>(std::move(msg)); // gratis rvalue onwards 63 | } 64 | 65 | template using Channel = std::function; 66 | 67 | template Channel getChannel() const // build a generic movement callback 68 | { 69 | std::weak_ptr weakBind(this->weak_from_this()); // Note: 70 | return Channel([weakBind](Any& data) // std::bind can't store a weak_ptr 71 | { // in std::function (and a shared_ptr 72 | auto aliveTarget = weakBind.lock(); // would prevent objects destruction) 73 | if (aliveTarget) 74 | std::static_pointer_cast(aliveTarget)->template send(std::move(data)); 75 | }); 76 | } 77 | 78 | template void connect(Channel receiver = Channel()) // bind (or unbind) a generic callback 79 | { 80 | post, true>(std::move(receiver)); 81 | } 82 | 83 | template void connect(const std::weak_ptr& receiver) 84 | { 85 | auto aliveTarget = receiver.lock(); 86 | if (aliveTarget) connect(aliveTarget->template getChannel()); // bind another ActorThread 87 | } 88 | 89 | std::size_t pendingMessages() const // amount of undispatched messages in the active object 90 | { 91 | return mboxNormPri.size() + mboxHighPri.size(); 92 | } 93 | 94 | typedef std::chrono::steady_clock TimerClock; 95 | 96 | void waitIdle(TimerClock::duration maxWait = std::chrono::seconds(1)) // blocks until there aren't pending messages 97 | { 98 | std::unique_lock ulock(mtx); 99 | if (!(mboxNormPri.empty() && mboxHighPri.empty())) idleWaiter.wait_until(ulock, TimerClock::now() + maxWait); 100 | } 101 | 102 | void stop(int code = 0) // optional call from ANOTHER thread (suffices deleting the object) or if created from run() 103 | { 104 | if (stop(false)) exitCode = code; // return code for run() function 105 | } 106 | 107 | bool exiting() const // mostly to allow active objects running intensive jobs to poll for a shutdown request 108 | { 109 | return !dispatching; 110 | } 111 | 112 | struct Gateway // safe wrapper for instances of unknown lifecycle 113 | { 114 | Gateway(const std::weak_ptr& actorThread = ptr()) : actor(actorThread) {} 115 | 116 | template inline void operator()(Any&& msg) const // handy function-like syntax 117 | { 118 | auto aliveTarget = get(); 119 | if (aliveTarget) aliveTarget->template send(std::forward(msg)); 120 | } 121 | 122 | void set(const std::weak_ptr& actorThread) { actor = actorThread; } 123 | 124 | inline ptr get() const { return actor.lock(); } 125 | 126 | private: std::weak_ptr actor; 127 | }; 128 | 129 | protected: 130 | 131 | ActorThread() : dispatching(true), externalDispatcher(false), detached(false), exitCode(0), mboxPaused(false) {} 132 | 133 | virtual ~ActorThread() {} // messages pending to be dispatched are discarded 134 | 135 | /* methods invoked on the active object (this default implementation can be "overrided") */ 136 | 137 | void onStart() {} 138 | void onStop() {} 139 | 140 | std::thread::id threadID() const { return id; } 141 | 142 | /* the active object may use this family of methods to perform the callbacks onto connected clients */ 143 | 144 | template inline static void publish(Any msg) 145 | { 146 | auto& bearer = callback(); 147 | if (bearer) bearer(msg); // if binded with getChannel() won't call a deleted peer 148 | } 149 | 150 | template static Channel& callback() // callback storage (per-thread and type) 151 | { 152 | static thread_local Channel bearer; // beware that this callback moves the argument 153 | return bearer; 154 | } 155 | 156 | /* timers facility for the active object (unlimited amount: one per each "payload" instance) */ 157 | 158 | enum class TimerCycle { Periodic, OneShot }; 159 | 160 | template void timerStart(const Any& payload, TimerClock::duration lapse, 161 | Channel event, TimerCycle cycle = TimerCycle::OneShot) 162 | { 163 | std::shared_ptr> timer; 164 | auto& allTimersOfThatType = timerEvents(this); 165 | auto pTimer = allTimersOfThatType.find(payload); 166 | if (pTimer == allTimersOfThatType.end()) 167 | { 168 | timer = std::make_shared>(std::move(event), payload); 169 | allTimersOfThatType.emplace(timer->payload, timer); 170 | } 171 | else // reschedule and reprogram 172 | { 173 | timer = pTimer->second.lock(); 174 | timers.erase(timer); 175 | timer->event = std::move(event); 176 | } 177 | timer->lapse = lapse; 178 | timer->cycle = cycle; 179 | timer->reset(false); 180 | timers.insert(std::move(timer)); 181 | } 182 | 183 | template void timerStart(const Any& payload, TimerClock::duration lapse, // invokes onTimer() methods 184 | TimerCycle cycle = TimerCycle::OneShot) 185 | { 186 | Runnable* runnable = static_cast(this); // safe (a dead 'this' will not dispatch timers) 187 | timerStart(payload, lapse, Channel([runnable](const Any& p) { runnable->onTimer(p); }), cycle); 188 | } 189 | 190 | template void timerReset(const Any& payload) 191 | { 192 | auto const& allTimersOfThatType = timerEvents(this); 193 | auto pTimer = allTimersOfThatType.find(payload); 194 | if (pTimer != allTimersOfThatType.end()) timerReschedule(pTimer->second.lock(), false); 195 | } 196 | 197 | template void timerStop(const Any& payload) 198 | { 199 | auto& allTimersOfThatType = timerEvents(this); 200 | auto pTimer = allTimersOfThatType.find(payload); 201 | if (pTimer != allTimersOfThatType.end()) 202 | { 203 | auto timer = pTimer->second.lock(); 204 | timers.erase(timer); 205 | allTimersOfThatType.erase(pTimer); 206 | if (timer.use_count() > 1) timer->reset(false); // timer "touched" signaling to dispatcher 207 | } 208 | } 209 | 210 | /* the active object may throw this object while processing a message */ 211 | 212 | struct DispatchRetry // the delivery will be retried later 213 | { 214 | DispatchRetry(TimerClock::duration waitToRetry = std::chrono::seconds(1)) : retryInterval(waitToRetry) {} 215 | bool operator<(const DispatchRetry&) const { return false; } // enforce a single instance in the containers 216 | TimerClock::duration retryInterval; // will be shortened on every incoming high priority message 217 | }; 218 | 219 | // The following methods are exclusively intended to *interleave* the ActorThread dispatcher with another 220 | // external dispatcher (e.g. Asio) which will actually be the master dispatcher having the thread control: 221 | // 222 | // - onWaitingEvents() must be used to request the external dispatcher to invoke handleActorEvents() 223 | // - onWaitingTimer() must request the external dispatcher a delayed invocation of handleActorEvents() 224 | // - onWaitingTimerCancel() must request the external dispatcher to cancel any delayed invocation 225 | // - onStopping() must request the external dispatcher to end (not required a synchronous completion wait) 226 | 227 | void acquireDispatcher() // request ActorThread to stop dispatching and invoke onDispatching() 228 | { 229 | std::lock_guard lock(mtx); 230 | externalDispatcher = true; 231 | messageWaiter.notify_one(); 232 | } 233 | 234 | void onDispatching() {} // run the external dispatcher from here (in case it exits, ActorThread resumes again) 235 | 236 | void onWaitingEvents() {} // invoked from another threads (new messages coming) 237 | void onWaitingTimer(TimerClock::duration); // invoked from dispatcher thread (there is only a single timer) 238 | void onWaitingTimerCancel(); // invoked from dispatcher thread (will come even if the timer was not in use) 239 | void onStopping() {} // invoked from another threads (mandatory handling: the object could be about to be deleted) 240 | 241 | void handleActorEvents() // this must be invoked from the external dispatcher as specified above 242 | { 243 | auto status = eventsLoop(); 244 | if (status.first) 245 | static_cast(this)->onWaitingTimer(status.second); 246 | else 247 | static_cast(this)->onWaitingTimerCancel(); 248 | } 249 | 250 | private: 251 | 252 | ActorThread& operator=(const ActorThread&) = delete; 253 | ActorThread(const ActorThread&) = delete; 254 | 255 | template class ActorQueue // FIFO (based on the MPSC queue at https://github.com/mstump/queues) 256 | { 257 | template using Aligned = typename std::aligned_storage::value>::type; 258 | 259 | public: 260 | 261 | struct Linked { std::atomic next; }; 262 | 263 | ActorQueue() : head(reinterpret_cast(new Aligned)), // dummy transient placeholder 264 | tail(head.load(std::memory_order_relaxed)), 265 | count(0), 266 | lastFront(nullptr), 267 | prevFront(tail.load(std::memory_order_relaxed)) 268 | { 269 | prevFront->next.store(nullptr, std::memory_order_relaxed); 270 | } 271 | 272 | void clear() { while (front()) pop_front(); } 273 | 274 | ~ActorQueue() 275 | { 276 | clear(); 277 | ::operator delete(head.load(std::memory_order_relaxed)); // also suitable for a dummy instance 278 | } 279 | 280 | template std::size_t push_back(Linkable* item) // e.g. any type derived from 'Linked' 281 | { 282 | item->next.store(nullptr, std::memory_order_relaxed); 283 | Item* back = head.exchange(item, std::memory_order_acq_rel); 284 | back->next.store(item, std::memory_order_release); 285 | return count.fetch_add(1, std::memory_order_release); // amount of queued items minus 1 286 | } 287 | 288 | inline Item* front() // returns nullptr if empty (must be always invoked just before pop_front()) 289 | { 290 | return lastFront = prevFront->next.load(std::memory_order_acquire); 291 | } 292 | 293 | void pop_front() // also destroys and ultimately deletes the item 294 | { 295 | count.fetch_sub(1, std::memory_order_release); 296 | tail.store(lastFront, std::memory_order_release); 297 | lastFront->~Item(); // (atomic destructor is trivial) memory deletion is actually deferred one step behind 298 | ::operator delete(prevFront); // delete the *previous* item memory no longer needed 299 | prevFront = lastFront; 300 | } 301 | 302 | inline std::size_t size() const { return count.load(std::memory_order_acquire); } 303 | 304 | inline bool empty() const { return !size(); } 305 | 306 | private: 307 | 308 | std::atomic head; // the stored objects hold the linked list pointers (single memory allocation) 309 | std::atomic tail; 310 | std::atomic count; 311 | Item* lastFront; 312 | Item* prevFront; 313 | }; 314 | 315 | protected: 316 | 317 | struct ActorParcel : public ActorQueue::Linked 318 | { 319 | virtual ~ActorParcel() {} 320 | virtual void deliverTo(Runnable* instance) = 0; 321 | }; 322 | 323 | private: 324 | 325 | template struct ActorMessage : public ActorParcel // wraps any type 326 | { 327 | ActorMessage(Any&& msg) : message(std::move(msg)) {} 328 | void deliverTo(Runnable* instance) { instance->onMessage(message); } 329 | Any message; 330 | }; 331 | 332 | template struct ActorCallback : public ActorParcel 333 | { 334 | ActorCallback(Channel&& msg) : message(std::move(msg)) {} 335 | void deliverTo(Runnable*) { callback() = std::move(message); } 336 | Channel message; 337 | }; 338 | 339 | struct ActorTimer : public ActorParcel, public std::enable_shared_from_this 340 | { 341 | virtual ~ActorTimer() {} 342 | void reset(bool incremental) 343 | { 344 | if (!incremental) deadline = TimerClock::now(); 345 | deadline += lapse; // try keeping regular periodic intervals 346 | if (incremental && (deadline < TimerClock::now())) deadline = TimerClock::now() + lapse; // fix lost events 347 | shoot = false; 348 | } 349 | bool operator<(const ActorTimer& that) const // ordering in containers 350 | { 351 | if (deadline < that.deadline) return true; 352 | else if (that.deadline < deadline) return false; 353 | else return this < &that; // obviate the need for a multiset 354 | } 355 | TimerClock::duration lapse; 356 | TimerCycle cycle; 357 | TimerClock::time_point deadline; 358 | bool shoot; 359 | }; 360 | 361 | template struct ActorAlarm : public ActorTimer 362 | { 363 | ActorAlarm(Channel&& fn, const Any& p) : event(std::move(fn)), payload(p) {} 364 | void deliverTo(Runnable* instance) 365 | { 366 | this->shoot = true; 367 | if (event) event(payload); // the invoked function could "touch" (shoot -> false) this very same timer 368 | if (this->shoot) 369 | { 370 | if (this->cycle == TimerCycle::OneShot) 371 | instance->timerStop(payload); 372 | else 373 | instance->timerReschedule(this->shared_from_this(), true); 374 | } 375 | } 376 | Channel event; 377 | Any payload; 378 | }; 379 | 380 | void timerReschedule(std::shared_ptr&& timer, bool incremental) 381 | { 382 | timers.erase(timer); // resetting will require a position change in the set nearly 100% of times 383 | timer->reset(incremental); 384 | timers.insert(std::move(timer)); // emplaced in the new position 385 | } 386 | 387 | template static std::map>>& timerEvents(ActorThread* caller) 388 | { 389 | if (caller->id != std::this_thread::get_id()) throw std::runtime_error("timer setup outside its owning thread"); 390 | static thread_local std::map>> info; // storage 391 | return info; 392 | } 393 | 394 | static void actorThreadRecycler(Runnable* runnable) 395 | { 396 | if (runnable->stop(true)) delete runnable; // deletion is deferred when not possible (detaching the thread) 397 | } 398 | 399 | bool stop(bool forced) try // return false if couldn't be properly stop 400 | { 401 | std::unique_lock ulock(mtx); 402 | if (runner.get_id() == std::this_thread::get_id()) // self-stop? 403 | { 404 | if (forced && dispatching) // from delete? (shared_ptr circular reference just broken) 405 | { 406 | if (runner.joinable()) 407 | { 408 | runner.detach(); 409 | detached = true; 410 | } 411 | dispatching = false; 412 | ulock.unlock(); 413 | static_cast(this)->onStopping(); 414 | } 415 | return false; 416 | } 417 | else // normal stop invoked from another thread (or if started from run()) 418 | { 419 | if (!dispatching) return true; // was already stop 420 | dispatching = false; 421 | bool fromCreate = runner.joinable(); 422 | if (fromCreate) messageWaiter.notify_one(); 423 | ulock.unlock(); 424 | static_cast(this)->onStopping(); 425 | if (!fromCreate) return true; // queues don't require and can't be cleared (potentially inside onMessage()) 426 | runner.join(); 427 | timers.clear(); 428 | mboxNormPri.clear(); // don't wait for this object deletion (the frozen queues 429 | mboxHighPri.clear(); // may store shared_ptr preventing other objects deletion) 430 | return true; 431 | } 432 | } 433 | catch (...) { return false; } 434 | 435 | protected: 436 | 437 | template void post(Any&& msg) // runs on the calling thread 438 | { 439 | auto& mbox = HighPri? mboxHighPri : mboxNormPri; 440 | if (!dispatching) return; // don't store anything in a frozen queue 441 | bool isIdle = mbox.push_back(new Parcelable(std::forward(msg))) == 0; 442 | if (HighPri) mboxPaused = false; 443 | if (!isIdle) return; // if the consumer has pending messages (e.g. under high load) this method returns here 444 | std::lock_guard lock(mtx); // under high load is only acquired in eventsLoop() (no effective lock) 445 | messageWaiter.notify_one(); // wakeup the consumer thread 446 | static_cast(this)->onWaitingEvents(); 447 | } 448 | 449 | private: 450 | 451 | int dispatcher() // runs on the wrapped thread 452 | { 453 | id = std::this_thread::get_id(); 454 | Runnable* runnable = static_cast(this); 455 | runnable->onStart(); 456 | for (;;) 457 | { 458 | burst = 0; 459 | eventsLoop(); 460 | if (!dispatching) break; 461 | runnable->onDispatching(); 462 | externalDispatcher = false; 463 | } 464 | runnable->onStop(); 465 | int code = exitCode; 466 | if (detached) delete runnable; // deferred self-deletion 467 | return code; 468 | } 469 | 470 | void retryMbox(const DispatchRetry&) { mboxPaused = false; } 471 | 472 | std::pair eventsLoop() 473 | { 474 | bool haveTimerLapse = false; 475 | TimerClock::duration timerLapse; 476 | Runnable* runnable = static_cast(this); 477 | bool mustDispatch = true; 478 | while (dispatching && mustDispatch) 479 | { 480 | bool hasHigh = !mboxHighPri.empty(); 481 | bool hasNorm = !mboxNormPri.empty(); 482 | 483 | if (!mboxPaused && (hasHigh || hasNorm)) // consume the messages queue 484 | { 485 | auto& mbox = hasHigh? mboxHighPri : mboxNormPri; 486 | try 487 | { 488 | while (ActorParcel* msg = mbox.front()) 489 | { 490 | msg->deliverTo(runnable); 491 | mbox.pop_front(); 492 | if ((++burst % 64) == 0) 493 | { 494 | if (externalDispatcher) // do not monopolize the CPU on this dispatcher 495 | { 496 | runnable->onWaitingEvents(); // queue a resume request 497 | mustDispatch = false; 498 | } 499 | break; // keep an eye on the timers 500 | } 501 | } 502 | } 503 | catch (const DispatchRetry& retry) 504 | { 505 | auto event = Channel([this](const DispatchRetry& dr) { retryMbox(dr); }); 506 | timerStart(retry, retry.retryInterval, std::move(event)); 507 | mboxPaused = true; 508 | } 509 | } 510 | 511 | auto firstTimer = timers.cbegin(); 512 | if (firstTimer == timers.cend()) 513 | { 514 | std::unique_lock ulock(mtx); // lock required *here* to overcome the sleeping barber problem 515 | if (mboxNormPri.empty() && mboxHighPri.empty() && dispatching) 516 | { 517 | idleWaiter.notify_all(); 518 | if (externalDispatcher) break; 519 | messageWaiter.wait(ulock); // wait for incoming messages 520 | } 521 | } 522 | else 523 | { 524 | auto wakeup = (*firstTimer)->deadline; 525 | if (TimerClock::now() >= wakeup) 526 | { 527 | auto timerEvent = *firstTimer; // this shared_ptr keeps it alive when self-removed from the set 528 | timerEvent->deliverTo(runnable); // here it could be self-removed (timerStop) 529 | } 530 | else // the other timers are scheduled even further 531 | { 532 | std::unique_lock ulock(mtx); // prevent sleeping barber problem 533 | if (dispatching && mboxHighPri.empty() && (mboxNormPri.empty() || mboxPaused)) 534 | { 535 | idleWaiter.notify_all(); 536 | if (externalDispatcher) 537 | { 538 | haveTimerLapse = true; 539 | timerLapse = wakeup - TimerClock::now(); 540 | break; 541 | } 542 | messageWaiter.wait_until(ulock, wakeup); // wait until first timer or for incoming messages 543 | } 544 | } 545 | } 546 | } 547 | return std::make_pair(haveTimerLapse, timerLapse); 548 | } 549 | 550 | template struct ActorPointedKeyComparator 551 | { 552 | inline bool operator()(const std::shared_ptr& key1, const std::shared_ptr& key2) const 553 | { 554 | return *key1 < *key2; 555 | } 556 | }; 557 | 558 | std::atomic dispatching; 559 | std::atomic externalDispatcher; 560 | std::atomic detached; 561 | mutable std::weak_ptr weak_this; 562 | std::thread runner; 563 | std::thread::id id; 564 | int exitCode; 565 | mutable std::mutex mtx; 566 | std::condition_variable messageWaiter; 567 | std::condition_variable idleWaiter; 568 | ActorQueue mboxNormPri; 569 | ActorQueue mboxHighPri; 570 | std::atomic mboxPaused; 571 | uint16_t burst; 572 | std::set, ActorPointedKeyComparator> timers; // ordered by deadline 573 | }; 574 | 575 | #endif /* ACTORTHREAD_HPP */ 576 | --------------------------------------------------------------------------------