From 7dac544c27a4220c4c49e20e529f00f4805725c2 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Wed, 26 Apr 2023 16:39:43 -0700 Subject: [PATCH 01/16] initial commit --- stlab/actor.hpp | 182 ++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 15 +++ test/actor_tests.cpp | 231 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 428 insertions(+) create mode 100644 stlab/actor.hpp create mode 100644 test/actor_tests.cpp diff --git a/stlab/actor.hpp b/stlab/actor.hpp new file mode 100644 index 00000000..376b22f4 --- /dev/null +++ b/stlab/actor.hpp @@ -0,0 +1,182 @@ +// +// ADOBE CONFIDENTIAL +// __________________ +// +// Copyright 2023 Adobe +// All Rights Reserved. +// +// NOTICE: All information contained herein is, and remains +// the property of Adobe and its suppliers, if any. The intellectual +// and technical concepts contained herein are proprietary to Adobe +// and its suppliers and are protected by all applicable intellectual +// property laws, including trade secret and copyright laws. +// Dissemination of this information or reproduction of this material +// is strictly forbidden unless prior written permission is obtained +// from Adobe. +// + +#pragma once +#ifndef ARTEMIS_ACTOR_HPP_ +#define ARTEMIS_ACTOR_HPP_ + +// stdc++ +#include +#include + +// stlab +#include +#include + +// lambda pack captures are a C++20 feature, but both clang and msvc support it, so we should be +// okay to use it even under C++17. +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++20-extensions" +#endif // __clang__ + +/**************************************************************************************************/ + +namespace artemis { + +/**************************************************************************************************/ + +namespace detail { + +/**************************************************************************************************/ + +struct temp_thread_name { + explicit temp_thread_name(const char* name) { stlab::set_current_thread_name(name); } + ~temp_thread_name() { stlab::set_current_thread_name(""); } +}; + +/**************************************************************************************************/ + +template +struct value_instance { + std::optional _x; +}; + +template <> +struct value_instance {}; + +/**************************************************************************************************/ + +template +struct actor_instance : public std::enable_shared_from_this> { + template + explicit actor_instance(Executor&& e, std::string&& name) + : _q(std::forward(e)), _name(std::move(name)) {} + + template + void initialize(Args&&... args) { + // We want to construct the object instance in the executor where + // it will be running. We cannot initialize in the constructor because + // `shared_from_this` will throw `bad_weak_ptr`. + _q([_this = this->shared_from_this()](auto&& ...args) mutable { + temp_thread_name ttn(_this->_name.c_str()); + _this->_instance._x = T(std::forward(args)...); + }, std::forward(args)...).detach(); + } + + auto set_name(std::string&& name) { _name = std::move(name); } + + template + auto send(F&& f, Args&&... args) { + auto task = [_f = std::forward(f), + _this = this->shared_from_this()](auto&& ...args) mutable { + temp_thread_name ttn(_this->_name.c_str()); + if constexpr (std::is_same_v) { + return std::move(_f)(std::forward(args)...); + } else { + return std::move(_f)(*(_this->_instance._x), std::forward(args)...); + } + }; + return _q(std::move(task), std::forward(args)...); + } + + template + auto then(stlab::future&& future, F&& f, Args&&... args) { + auto task = [_f = std::forward(f), ... _args = std::forward(args), + _this = this->shared_from_this()](R&& x) mutable { + temp_thread_name ttn(_this->_name.c_str()); + if constexpr (std::is_same_v) { + return std::move(_f)(std::forward(x), std::move(_args)...); + } else { + return std::move(_f)(*(_this->_instance._x), std::forward(x), std::move(_args)...); + } + }; + return std::move(future).then(_q.executor(), std::move(task)); + } + + template + auto then(stlab::future&& future, F&& f, Args&&... args) { + auto task = [_f = std::forward(f), ... _args = std::forward(args), + _this = this->shared_from_this()]() mutable { + temp_thread_name ttn(_this->_name.c_str()); + if constexpr (std::is_same_v) { + return std::move(_f)(std::move(_args)...); + } else { + return std::move(_f)(*(_this->_instance._x), std::move(_args)...); + } + }; + return std::move(future).then(_q.executor(), std::move(task)); + } + + value_instance _instance; + stlab::serial_queue_t _q; + std::string _name; +}; + +/**************************************************************************************************/ + +} // namespace detail + +/**************************************************************************************************/ + +template +class actor { + std::shared_ptr> _impl; + +public: + actor() = default; + + template + actor(Executor&& e, std::string&& name, Args&&... args) + : _impl(std::make_shared>( + std::forward(e), std::move(name))) { + if constexpr (!std::is_same_v) { + _impl->initialize(std::forward(args)...); + } + } + + auto set_name(std::string&& name) { _impl->set_name(std::move(name)); } + + template + auto send(F&& f, Args&&... args) { + return _impl->send(std::forward(f), std::forward(args)...); + } + + template + auto then(stlab::future&& future, F&& f, Args&&... args) { + return _impl->then(std::move(future), std::forward(f), std::forward(args)...); + } + + friend bool operator==(const actor& x, const actor& y) { return x._impl == y._impl; } + friend bool operator!=(const actor& x, const actor& y) { return !(x == y); } +}; + +/**************************************************************************************************/ + +} // namespace artemis + +/**************************************************************************************************/ + +#if __clang__ +#pragma clang diagnostic pop +#endif // __clang__ + +/**************************************************************************************************/ + +#endif // ARTEMIS_ACTOR_HPP_ + +/**************************************************************************************************/ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 704fd998..941230c9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -172,12 +172,26 @@ add_test( COMMAND stlab.test.utility ) +################################################################################ + +add_executable( stlab.test.actor + actor_tests.cpp + main.cpp ) + +target_link_libraries( stlab.test.actor PUBLIC stlab::testing ) + +add_test( + NAME stlab.test.actor + COMMAND stlab.test.actor +) + ################################################################################ # # tests are compiled without compiler extensions to ensure the stlab headers # are not dependent upon any such extension. # set_target_properties( + stlab.test.actor stlab.test.channel stlab.test.future stlab.test.serial_queue @@ -202,6 +216,7 @@ ProcessorCount(nProcessors) if(nProcessors) set_tests_properties( + stlab.test.actor stlab.test.channel stlab.test.executor stlab.test.future diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp new file mode 100644 index 00000000..4f9f65f0 --- /dev/null +++ b/test/actor_tests.cpp @@ -0,0 +1,231 @@ +// +// ADOBE CONFIDENTIAL +// __________________ +// +// Copyright 2022 Adobe +// All Rights Reserved. +// +// NOTICE: All information contained herein is, and remains +// the property of Adobe and its suppliers, if any. The intellectual +// and technical concepts contained herein are proprietary to Adobe +// and its suppliers and are protected by all applicable intellectual +// property laws, including trade secret and copyright laws. +// Dissemination of this information or reproduction of this material +// is strictly forbidden unless prior written permission is obtained +// from Adobe. +// + +// identity +#include + +// boost +#include + +// stlab +#include +#include + +/**************************************************************************************************/ + +void increment_by(int& i, int amount) { i += amount; } + +void increment(int& i) { increment_by(i, 1); } + +template +T get_actor_value(artemis::actor& a) { + return stlab::await(a.send([](auto& x) { return x; })); +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { + artemis::actor a(stlab::default_executor, "actor_int", 42); + stlab::future f = a.send([](auto& i) { BOOST_REQUIRE(i == 42); }); + + stlab::await(f); + + BOOST_REQUIRE(get_actor_value(a) == 42); +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_construct_void) { + artemis::actor a(stlab::default_executor, "actor_void"); + std::atomic_bool sent{false}; + stlab::future f = a.send([&]() { sent = true; }); + + stlab::await(f); + + BOOST_REQUIRE(sent); +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_regularity) { + artemis::actor empty_ctor; + + artemis::actor default_ctor(stlab::default_executor, "foo"); // default construction + default_ctor.send(increment).detach(); + BOOST_REQUIRE(get_actor_value(default_ctor) == 1); + + artemis::actor copy_ctor(default_ctor); // copy construction + copy_ctor.send(increment).detach(); + BOOST_REQUIRE(get_actor_value(copy_ctor) == 2); + + artemis::actor move_ctor(std::move(default_ctor)); // move construction + move_ctor.send(increment).detach(); + BOOST_REQUIRE(get_actor_value(move_ctor) == 3); + + artemis::actor copy_assign = copy_ctor; // copy assignment + copy_assign.send(increment).detach(); + BOOST_REQUIRE(get_actor_value(copy_assign) == 4); + + artemis::actor move_assign = std::move(move_ctor); // move assignment + move_assign.send(increment).detach(); + BOOST_REQUIRE(get_actor_value(move_assign) == 5); + + // equality comparable + artemis::actor a(stlab::default_executor, "a"); + artemis::actor b(stlab::default_executor, "b"); + BOOST_REQUIRE(a != b); // tests operator!= + BOOST_REQUIRE(!(a == b)); // tests operator== + + BOOST_REQUIRE(a == a); // tests operator== +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_regularity_void) { + std::size_t count{0}; + artemis::actor empty_ctor; + + artemis::actor default_ctor(stlab::default_executor, "foo"); // default construction + stlab::await(default_ctor.send([&] { ++count; })); + BOOST_REQUIRE(count == 1); + + artemis::actor copy_ctor(default_ctor); // copy construction + stlab::await(copy_ctor.send([&] { ++count; })); + BOOST_REQUIRE(count == 2); + + artemis::actor move_ctor(std::move(default_ctor)); // move construction + stlab::await(move_ctor.send([&] { ++count; })); + BOOST_REQUIRE(count == 3); + + artemis::actor copy_assign = move_ctor; // copy assignment + stlab::await(copy_assign.send([&] { ++count; })); + BOOST_REQUIRE(count == 4); + + artemis::actor move_assign = std::move(move_ctor); // move assignment + stlab::await(move_assign.send([&] { ++count; })); + BOOST_REQUIRE(count == 5); + + // equality comparable + artemis::actor a(stlab::default_executor, "a"); + artemis::actor b(stlab::default_executor, "b"); + BOOST_REQUIRE(a != b); // tests operator!= + BOOST_REQUIRE(!(a == b)); // tests operator== + + BOOST_REQUIRE(a == a); // tests operator== +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_send_to_void) { + { + artemis::actor a(stlab::default_executor, "send_getting_void"); + stlab::future f = a.send(increment); + + stlab::await(f); + + BOOST_REQUIRE(f.get_try()); + } + + { + artemis::actor a(stlab::default_executor, "send_getting_void"); + std::atomic_bool sent{false}; + stlab::future f = a.send([&] { sent = true; }); + + stlab::await(f); + + BOOST_REQUIRE(sent); + } +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_send_to_value) { + { + artemis::actor a(stlab::default_executor, "send_getting_value", 42); + stlab::future f = a.send([](auto& x) { return x; }); + int result = stlab::await(f); + + BOOST_REQUIRE(result == 42); + } + + { + artemis::actor a(stlab::default_executor, "send_getting_value"); + stlab::future f = a.send([]() { return 42; }); + int result = stlab::await(f); + + BOOST_REQUIRE(result == 42); + } +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_then_from_void) { + { + artemis::actor a(stlab::default_executor, "send_then_from_void"); + stlab::future f0 = a.send(increment_by, 42); + stlab::future f1 = a.then(stlab::future(f0), [](auto& x) { return x; }); + stlab::future f2 = a.then(std::move(f0), increment_by, 4200); + stlab::future f3 = a.then(std::move(f2), [](auto& x) { return x; }); + + int result1 = stlab::await(f1); + int result3 = stlab::await(f3); + + BOOST_REQUIRE(result1 == 42); + BOOST_REQUIRE(result3 == 4242); + } + + { + artemis::actor a(stlab::default_executor, "send_then_from_void"); + stlab::future f0 = a.send([]() { return 42; }); + stlab::future f1 = a.then(std::move(f0), [](auto x) { return 4200 + x; }); + stlab::future f2 = a.then( + std::move(f1), [](auto x, auto y) { return x + y; }, 420000); + + int result = stlab::await(f2); + + BOOST_REQUIRE(result == 424242); + } +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_then_from_value) { + artemis::actor a(stlab::default_executor, "send_then_from_type", 42); + stlab::future f0 = a.send([](auto& x) { return x; }); + stlab::future f1 = a.then(stlab::future(f0), [](auto& x, auto y) { + BOOST_REQUIRE(x == 42); + BOOST_REQUIRE(y == 42); + return x + y; + }); + stlab::future f2 = a.then( + std::move(f0), + [](auto& x, auto y, auto z) { + BOOST_REQUIRE(x == 42); + BOOST_REQUIRE(y == 42); + BOOST_REQUIRE(z == 100); + return x + y + z; + }, + 100); + + int result1 = stlab::await(f1); + int result2 = stlab::await(f2); + + BOOST_REQUIRE(result1 == 84); + BOOST_REQUIRE(result2 == 184); +} + +/**************************************************************************************************/ From 26c6f4efe65a7c525c2e7bfcbe5124a51d223eff Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Wed, 26 Apr 2023 17:05:50 -0700 Subject: [PATCH 02/16] adding docs --- .../actor.hpp/actor3CT3E/f_operator213D.md | 23 +++++ .../actor.hpp/actor3CT3E/f_operator3D3D.md | 23 +++++ .../concurrency/actor.hpp/actor3CT3E/index.md | 85 +++++++++++++++++++ .../actor.hpp/actor3CT3E/m_actor3CT3E.md | 31 +++++++ .../actor.hpp/actor3CT3E/m_send.md | 21 +++++ .../actor.hpp/actor3CT3E/m_set_name.md | 18 ++++ .../actor.hpp/actor3CT3E/m_then.md | 24 ++++++ docs/libraries/concurrency/actor.hpp/index.md | 9 ++ stlab/{ => concurrency}/actor.hpp | 10 +-- test/actor_tests.cpp | 52 ++++++------ 10 files changed, 265 insertions(+), 31 deletions(-) create mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator213D.md create mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator3D3D.md create mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/index.md create mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/m_actor3CT3E.md create mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/m_send.md create mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/m_set_name.md create mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/m_then.md create mode 100644 docs/libraries/concurrency/actor.hpp/index.md rename stlab/{ => concurrency}/actor.hpp (97%) diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator213D.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator213D.md new file mode 100644 index 00000000..33570063 --- /dev/null +++ b/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator213D.md @@ -0,0 +1,23 @@ +--- +layout: function +title: operator!= +owner: fbrereto +brief: Inequality operator +tags: + - function +defined_in_file: concurrency/actor.hpp +overloads: + bool operator!=(const actor &, const actor &): + arguments: + - description: __OPTIONAL__ + name: x + type: const actor & + - description: __OPTIONAL__ + name: y + type: const actor & + description: __OPTIONAL__ + return: __OPTIONAL__ + signature_with_names: bool operator!=(const actor & x, const actor & y) +namespace: + - stlab +--- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator3D3D.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator3D3D.md new file mode 100644 index 00000000..4872fabc --- /dev/null +++ b/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator3D3D.md @@ -0,0 +1,23 @@ +--- +layout: function +title: operator== +owner: fbrereto +brief: Equality operator +tags: + - function +defined_in_file: concurrency/actor.hpp +overloads: + bool operator==(const actor &, const actor &): + arguments: + - description: __OPTIONAL__ + name: x + type: const actor & + - description: __OPTIONAL__ + name: y + type: const actor & + description: __OPTIONAL__ + return: __OPTIONAL__ + signature_with_names: bool operator==(const actor & x, const actor & y) +namespace: + - stlab +--- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/index.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/index.md new file mode 100644 index 00000000..cdb81c04 --- /dev/null +++ b/docs/libraries/concurrency/actor.hpp/actor3CT3E/index.md @@ -0,0 +1,85 @@ +--- +layout: class +title: actor +owner: fbrereto +brief: Serialized, asynchronous access to a resource +tags: + - class +defined_in_file: concurrency/actor.hpp +declaration: "template \nclass stlab::actor;" +dtor: unspecified +fields: + _impl: + annotation: + - private + description: pimpl implementation instance + type: std::shared_ptr> +namespace: + - stlab +--- + +`actor` provides asynchronous, serialized access to an instance of `T`, running on an execution context of choice. Instead of a traditional message-passing actor model implementation, `actor` is given work by way of lambdas, whose results are then optionally extracted by the caller via a `stlab::future`. + +`actor` is a lightweight alternative to a dedicated thread managing some background service for a host application. The problem with background threads is that they consume considerable resources even when they are idle. Furthermore, many background services don't need the "always on" characteristics of a thread, and would be comfortable running only when necessary. + +However, `actor` is not a panacea. There are several caveats to keep in mind: + +1. `thread_local` variables may not retain state from task to task. Given the implementation details of the actor's executor (e.g., it may be scheduled on any number of threads in a thread pool), an actor may jump from thread to thread. Since `thread_local` variables have a per-thread affinity by definition, the variable values may change unexpectedly. +2. The thread cache penalty paid when an actor changes threads may not be suitable for high-performance/low-latency requirements. There is a cost associated with an actor jumping from one thread to another, and as in the previous case, this may happen depending on the implementation of the executor. If this cache penalty is too expensive for your use case, a dedicated worker thread may be a better fit. +3. The tasks given to an actor should not block. If the actor must wait for external input (mouse events, network/file IO, etc.) it should be fed in from outside the actor. Because the context of execution is not "owned" by the actor, it cannot presume to block the context waiting for something else to happen, or else it risks hanging (e.g., an unresponsive main thread) or deadlocking (e.g., waiting for a task that cannot complete until this task completes.) + +## Example + +Say we have a service, `type_rasterizer`, that we'd like to put on a background thread: + +```c++ +class image { + //... +}; + +struct type_rasterizer { + void set_text(std::string&& text); + + image rasterize(); + + // ... +}; +``` + +In our application, then, we will create an actor that manages an instance of this engine. By giving it the `default_executor`, the actor will run on a thread of the OS-provided thread pool (e.g., GCD on macOS/iOS). + +```c++ +struct my_application { + stlab::actor _rasterizer(stlab::default_executor, + "app text rasterizer"); + + // ... +}; +``` + +Then as your application is running, you can send "messages" in the form of lambdas to this actor to perform serialized, asynchronous operations. Note the first parameter of the lambda is the `type_rasterizer` itself: + +```c++ +void my_application::do_rasterize(std::string&& text) { + _rasterizer.send([_text = std::move(text)](type_rasterizer& rasterizer) mutable { + // This lambda will execute on the `default_executor`. Note that while in this + // lambda, the name of the thread will be the name of the actor. In this case, + // "app text rasterizer". + rasterizer.set_text(std::move(_text)); + return rasterizer.rasterize(); + }).then(stlab::main_executor, [](image my_rasterized_text){ + draw_image_to_screen(my_rasterized_text); + }).detach(); +} +``` + +You could also pass the argument to the lambda itself: + +```c++ +_rasterizer.send([](type_rasterizer& rasterizer, std::string text) { + rasterizer.set_text(std::move(text)); + return rasterizer.rasterize(); +}, std::move(text)); +``` + +Note that the actor is not always running. That is, no threads are blocked on behalf of the actor while it waits for tasks to come in. Rather, the actor only schedules itself to run on its executor when it has work to do. Once the work is completed, the actor relinquishes the thread it is running on back to the executor. In this way, actors are considerably less resource-intensive than a dedicated worker thread to some background service. diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_actor3CT3E.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_actor3CT3E.md new file mode 100644 index 00000000..81464e53 --- /dev/null +++ b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_actor3CT3E.md @@ -0,0 +1,31 @@ +--- +layout: method +title: actor +owner: fbrereto +brief: Constructor +tags: + - method +defined_in_file: concurrency/actor.hpp +is_ctor: true +overloads: + actor(): + annotation: + - default + description: default constructor + return: __OPTIONAL__ + signature_with_names: actor() + "template \nactor(Executor &&, std::string &&, Args &&...)": + arguments: + - description: An executor upon which this actor will run when it has tasks. + name: e + type: Executor && + - description: Runtime name of the actor. While the actor is running, its thread will be temporarily given this name. This name can be reconfigured with a call to `set_name`. + name: name + type: std::string && + - description: Initialization arguments for the instance of `T` the actor holds + name: args + type: Args &&... + description: executor-based constructor + return: __OPTIONAL__ + signature_with_names: "template \nactor(Executor && e, std::string && name, Args &&... args)" +--- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_send.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_send.md new file mode 100644 index 00000000..d275a7dd --- /dev/null +++ b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_send.md @@ -0,0 +1,21 @@ +--- +layout: method +title: send +owner: fbrereto +brief: Send tasks for the actor to execute +tags: + - method +defined_in_file: concurrency/actor.hpp +overloads: + "template \nauto send(F &&, Args &&...)": + arguments: + - description: __OPTIONAL__ + name: f + type: F && + - description: __OPTIONAL__ + name: args + type: Args &&... + description: __OPTIONAL__ + return: __OPTIONAL__ + signature_with_names: "template \nauto send(F && f, Args &&... args)" +--- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_set_name.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_set_name.md new file mode 100644 index 00000000..e7394507 --- /dev/null +++ b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_set_name.md @@ -0,0 +1,18 @@ +--- +layout: method +title: set_name +owner: fbrereto +brief: Set the name of the actor +tags: + - method +defined_in_file: concurrency/actor.hpp +overloads: + auto set_name(std::string &&): + arguments: + - description: __OPTIONAL__ + name: name + type: std::string && + description: __OPTIONAL__ + return: __OPTIONAL__ + signature_with_names: auto set_name(std::string && name) +--- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_then.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_then.md new file mode 100644 index 00000000..687f6997 --- /dev/null +++ b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_then.md @@ -0,0 +1,24 @@ +--- +layout: method +title: then +owner: fbrereto +brief: Continue tasks for the actor to execute pending the completion of some future +tags: + - method +defined_in_file: concurrency/actor.hpp +overloads: + "template \nauto then(stlab::future &&, F &&, Args &&...)": + arguments: + - description: The future to contine + name: future + type: stlab::future && + - description: The task to run. The first argument passed to the routine will be the actor. The second argument passed will be the resolved value of the future (if it is not `void`). + name: f + type: F && + - description: Additional arguments to pass to `f` when it is run + name: args + type: Args &&... + description: __OPTIONAL__ + return: __OPTIONAL__ + signature_with_names: "template \nauto then(stlab::future && future, F && f, Args &&... args)" +--- diff --git a/docs/libraries/concurrency/actor.hpp/index.md b/docs/libraries/concurrency/actor.hpp/index.md new file mode 100644 index 00000000..5781f802 --- /dev/null +++ b/docs/libraries/concurrency/actor.hpp/index.md @@ -0,0 +1,9 @@ +--- +layout: library +title: stlab/actor.hpp +owner: fbrereto +brief: Header file for the `actor` +tags: + - sourcefile +library-type: sourcefile +--- diff --git a/stlab/actor.hpp b/stlab/concurrency/actor.hpp similarity index 97% rename from stlab/actor.hpp rename to stlab/concurrency/actor.hpp index 376b22f4..6cbdb0f1 100644 --- a/stlab/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -16,8 +16,8 @@ // #pragma once -#ifndef ARTEMIS_ACTOR_HPP_ -#define ARTEMIS_ACTOR_HPP_ +#ifndef STLAB_CONCURRENCY_ACTOR_HPP +#define STLAB_CONCURRENCY_ACTOR_HPP // stdc++ #include @@ -36,7 +36,7 @@ /**************************************************************************************************/ -namespace artemis { +namespace stlab { /**************************************************************************************************/ @@ -167,7 +167,7 @@ class actor { /**************************************************************************************************/ -} // namespace artemis +} // namespace stlab /**************************************************************************************************/ @@ -177,6 +177,6 @@ class actor { /**************************************************************************************************/ -#endif // ARTEMIS_ACTOR_HPP_ +#endif // STLAB_CONCURRENCY_ACTOR_HPP /**************************************************************************************************/ diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp index 4f9f65f0..687ddd20 100644 --- a/test/actor_tests.cpp +++ b/test/actor_tests.cpp @@ -32,14 +32,14 @@ void increment_by(int& i, int amount) { i += amount; } void increment(int& i) { increment_by(i, 1); } template -T get_actor_value(artemis::actor& a) { +T get_actor_value(stlab::actor& a) { return stlab::await(a.send([](auto& x) { return x; })); } /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { - artemis::actor a(stlab::default_executor, "actor_int", 42); + stlab::actor a(stlab::default_executor, "actor_int", 42); stlab::future f = a.send([](auto& i) { BOOST_REQUIRE(i == 42); }); stlab::await(f); @@ -50,7 +50,7 @@ BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_construct_void) { - artemis::actor a(stlab::default_executor, "actor_void"); + stlab::actor a(stlab::default_executor, "actor_void"); std::atomic_bool sent{false}; stlab::future f = a.send([&]() { sent = true; }); @@ -62,31 +62,31 @@ BOOST_AUTO_TEST_CASE(actor_construct_void) { /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_regularity) { - artemis::actor empty_ctor; + stlab::actor empty_ctor; - artemis::actor default_ctor(stlab::default_executor, "foo"); // default construction + stlab::actor default_ctor(stlab::default_executor, "foo"); // default construction default_ctor.send(increment).detach(); BOOST_REQUIRE(get_actor_value(default_ctor) == 1); - artemis::actor copy_ctor(default_ctor); // copy construction + stlab::actor copy_ctor(default_ctor); // copy construction copy_ctor.send(increment).detach(); BOOST_REQUIRE(get_actor_value(copy_ctor) == 2); - artemis::actor move_ctor(std::move(default_ctor)); // move construction + stlab::actor move_ctor(std::move(default_ctor)); // move construction move_ctor.send(increment).detach(); BOOST_REQUIRE(get_actor_value(move_ctor) == 3); - artemis::actor copy_assign = copy_ctor; // copy assignment + stlab::actor copy_assign = copy_ctor; // copy assignment copy_assign.send(increment).detach(); BOOST_REQUIRE(get_actor_value(copy_assign) == 4); - artemis::actor move_assign = std::move(move_ctor); // move assignment + stlab::actor move_assign = std::move(move_ctor); // move assignment move_assign.send(increment).detach(); BOOST_REQUIRE(get_actor_value(move_assign) == 5); // equality comparable - artemis::actor a(stlab::default_executor, "a"); - artemis::actor b(stlab::default_executor, "b"); + stlab::actor a(stlab::default_executor, "a"); + stlab::actor b(stlab::default_executor, "b"); BOOST_REQUIRE(a != b); // tests operator!= BOOST_REQUIRE(!(a == b)); // tests operator== @@ -97,31 +97,31 @@ BOOST_AUTO_TEST_CASE(actor_regularity) { BOOST_AUTO_TEST_CASE(actor_regularity_void) { std::size_t count{0}; - artemis::actor empty_ctor; + stlab::actor empty_ctor; - artemis::actor default_ctor(stlab::default_executor, "foo"); // default construction + stlab::actor default_ctor(stlab::default_executor, "foo"); // default construction stlab::await(default_ctor.send([&] { ++count; })); BOOST_REQUIRE(count == 1); - artemis::actor copy_ctor(default_ctor); // copy construction + stlab::actor copy_ctor(default_ctor); // copy construction stlab::await(copy_ctor.send([&] { ++count; })); BOOST_REQUIRE(count == 2); - artemis::actor move_ctor(std::move(default_ctor)); // move construction + stlab::actor move_ctor(std::move(default_ctor)); // move construction stlab::await(move_ctor.send([&] { ++count; })); BOOST_REQUIRE(count == 3); - artemis::actor copy_assign = move_ctor; // copy assignment + stlab::actor copy_assign = move_ctor; // copy assignment stlab::await(copy_assign.send([&] { ++count; })); BOOST_REQUIRE(count == 4); - artemis::actor move_assign = std::move(move_ctor); // move assignment + stlab::actor move_assign = std::move(move_ctor); // move assignment stlab::await(move_assign.send([&] { ++count; })); BOOST_REQUIRE(count == 5); // equality comparable - artemis::actor a(stlab::default_executor, "a"); - artemis::actor b(stlab::default_executor, "b"); + stlab::actor a(stlab::default_executor, "a"); + stlab::actor b(stlab::default_executor, "b"); BOOST_REQUIRE(a != b); // tests operator!= BOOST_REQUIRE(!(a == b)); // tests operator== @@ -132,7 +132,7 @@ BOOST_AUTO_TEST_CASE(actor_regularity_void) { BOOST_AUTO_TEST_CASE(actor_send_to_void) { { - artemis::actor a(stlab::default_executor, "send_getting_void"); + stlab::actor a(stlab::default_executor, "send_getting_void"); stlab::future f = a.send(increment); stlab::await(f); @@ -141,7 +141,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_void) { } { - artemis::actor a(stlab::default_executor, "send_getting_void"); + stlab::actor a(stlab::default_executor, "send_getting_void"); std::atomic_bool sent{false}; stlab::future f = a.send([&] { sent = true; }); @@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_void) { BOOST_AUTO_TEST_CASE(actor_send_to_value) { { - artemis::actor a(stlab::default_executor, "send_getting_value", 42); + stlab::actor a(stlab::default_executor, "send_getting_value", 42); stlab::future f = a.send([](auto& x) { return x; }); int result = stlab::await(f); @@ -163,7 +163,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_value) { } { - artemis::actor a(stlab::default_executor, "send_getting_value"); + stlab::actor a(stlab::default_executor, "send_getting_value"); stlab::future f = a.send([]() { return 42; }); int result = stlab::await(f); @@ -175,7 +175,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_value) { BOOST_AUTO_TEST_CASE(actor_then_from_void) { { - artemis::actor a(stlab::default_executor, "send_then_from_void"); + stlab::actor a(stlab::default_executor, "send_then_from_void"); stlab::future f0 = a.send(increment_by, 42); stlab::future f1 = a.then(stlab::future(f0), [](auto& x) { return x; }); stlab::future f2 = a.then(std::move(f0), increment_by, 4200); @@ -189,7 +189,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { } { - artemis::actor a(stlab::default_executor, "send_then_from_void"); + stlab::actor a(stlab::default_executor, "send_then_from_void"); stlab::future f0 = a.send([]() { return 42; }); stlab::future f1 = a.then(std::move(f0), [](auto x) { return 4200 + x; }); stlab::future f2 = a.then( @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_then_from_value) { - artemis::actor a(stlab::default_executor, "send_then_from_type", 42); + stlab::actor a(stlab::default_executor, "send_then_from_type", 42); stlab::future f0 = a.send([](auto& x) { return x; }); stlab::future f1 = a.then(stlab::future(f0), [](auto& x, auto y) { BOOST_REQUIRE(x == 42); From aab8ddc42cb617ee28601fc644d3b8534b0b3392 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Wed, 26 Apr 2023 17:07:35 -0700 Subject: [PATCH 03/16] updates --- stlab/concurrency/actor.hpp | 23 +++++++---------------- test/actor_tests.cpp | 23 +++++++---------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index 6cbdb0f1..1cf4422e 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -1,19 +1,10 @@ -// -// ADOBE CONFIDENTIAL -// __________________ -// -// Copyright 2023 Adobe -// All Rights Reserved. -// -// NOTICE: All information contained herein is, and remains -// the property of Adobe and its suppliers, if any. The intellectual -// and technical concepts contained herein are proprietary to Adobe -// and its suppliers and are protected by all applicable intellectual -// property laws, including trade secret and copyright laws. -// Dissemination of this information or reproduction of this material -// is strictly forbidden unless prior written permission is obtained -// from Adobe. -// +/* + Copyright 2023 Adobe + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +*/ + +/**************************************************************************************************/ #pragma once #ifndef STLAB_CONCURRENCY_ACTOR_HPP diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp index 687ddd20..4fecbb50 100644 --- a/test/actor_tests.cpp +++ b/test/actor_tests.cpp @@ -1,19 +1,10 @@ -// -// ADOBE CONFIDENTIAL -// __________________ -// -// Copyright 2022 Adobe -// All Rights Reserved. -// -// NOTICE: All information contained herein is, and remains -// the property of Adobe and its suppliers, if any. The intellectual -// and technical concepts contained herein are proprietary to Adobe -// and its suppliers and are protected by all applicable intellectual -// property laws, including trade secret and copyright laws. -// Dissemination of this information or reproduction of this material -// is strictly forbidden unless prior written permission is obtained -// from Adobe. -// +/* + Copyright 2015 Adobe + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +*/ + +/**************************************************************************************************/ // identity #include From abbf7652a4ff4f1f3078bc44951b4e0dce0d2083 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Wed, 26 Apr 2023 17:10:01 -0700 Subject: [PATCH 04/16] updates --- test/actor_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp index 4fecbb50..93d38ad4 100644 --- a/test/actor_tests.cpp +++ b/test/actor_tests.cpp @@ -1,5 +1,5 @@ /* - Copyright 2015 Adobe + Copyright 2023 Adobe Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ From 025dbd346dd5328eeef9069f8814767e6ed8855e Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Thu, 27 Apr 2023 09:20:24 -0700 Subject: [PATCH 05/16] formatting --- stlab/concurrency/actor.hpp | 26 ++++++++++++++---------- test/actor_tests.cpp | 40 ++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index 1cf4422e..77b20a99 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -55,18 +55,21 @@ struct value_instance {}; template struct actor_instance : public std::enable_shared_from_this> { template - explicit actor_instance(Executor&& e, std::string&& name) - : _q(std::forward(e)), _name(std::move(name)) {} + explicit actor_instance(Executor&& e, std::string&& name) : + _q(std::forward(e)), _name(std::move(name)) {} template void initialize(Args&&... args) { // We want to construct the object instance in the executor where // it will be running. We cannot initialize in the constructor because // `shared_from_this` will throw `bad_weak_ptr`. - _q([_this = this->shared_from_this()](auto&& ...args) mutable { - temp_thread_name ttn(_this->_name.c_str()); - _this->_instance._x = T(std::forward(args)...); - }, std::forward(args)...).detach(); + _q( + [_this = this->shared_from_this()](auto&&... args) mutable { + temp_thread_name ttn(_this->_name.c_str()); + _this->_instance._x = T(std::forward(args)...); + }, + std::forward(args)...) + .detach(); } auto set_name(std::string&& name) { _name = std::move(name); } @@ -74,7 +77,7 @@ struct actor_instance : public std::enable_shared_from_this> { template auto send(F&& f, Args&&... args) { auto task = [_f = std::forward(f), - _this = this->shared_from_this()](auto&& ...args) mutable { + _this = this->shared_from_this()](auto&&... args) mutable { temp_thread_name ttn(_this->_name.c_str()); if constexpr (std::is_same_v) { return std::move(_f)(std::forward(args)...); @@ -93,7 +96,8 @@ struct actor_instance : public std::enable_shared_from_this> { if constexpr (std::is_same_v) { return std::move(_f)(std::forward(x), std::move(_args)...); } else { - return std::move(_f)(*(_this->_instance._x), std::forward(x), std::move(_args)...); + return std::move(_f)(*(_this->_instance._x), std::forward(x), + std::move(_args)...); } }; return std::move(future).then(_q.executor(), std::move(task)); @@ -132,9 +136,9 @@ class actor { actor() = default; template - actor(Executor&& e, std::string&& name, Args&&... args) - : _impl(std::make_shared>( - std::forward(e), std::move(name))) { + actor(Executor&& e, std::string&& name, Args&&... args) : + _impl(std::make_shared>(std::forward(e), + std::move(name))) { if constexpr (!std::is_same_v) { _impl->initialize(std::forward(args)...); } diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp index 93d38ad4..803e6f05 100644 --- a/test/actor_tests.cpp +++ b/test/actor_tests.cpp @@ -31,7 +31,7 @@ T get_actor_value(stlab::actor& a) { BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { stlab::actor a(stlab::default_executor, "actor_int", 42); - stlab::future f = a.send([](auto& i) { BOOST_REQUIRE(i == 42); }); + stlab::future f = a.send([](auto& i) { BOOST_REQUIRE(i == 42); }); stlab::await(f); @@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(actor_regularity) { // equality comparable stlab::actor a(stlab::default_executor, "a"); stlab::actor b(stlab::default_executor, "b"); - BOOST_REQUIRE(a != b); // tests operator!= + BOOST_REQUIRE(a != b); // tests operator!= BOOST_REQUIRE(!(a == b)); // tests operator== BOOST_REQUIRE(a == a); // tests operator== @@ -92,28 +92,28 @@ BOOST_AUTO_TEST_CASE(actor_regularity_void) { stlab::actor default_ctor(stlab::default_executor, "foo"); // default construction stlab::await(default_ctor.send([&] { ++count; })); - BOOST_REQUIRE(count == 1); + BOOST_REQUIRE(count == 1); stlab::actor copy_ctor(default_ctor); // copy construction stlab::await(copy_ctor.send([&] { ++count; })); - BOOST_REQUIRE(count == 2); + BOOST_REQUIRE(count == 2); stlab::actor move_ctor(std::move(default_ctor)); // move construction stlab::await(move_ctor.send([&] { ++count; })); - BOOST_REQUIRE(count == 3); + BOOST_REQUIRE(count == 3); stlab::actor copy_assign = move_ctor; // copy assignment stlab::await(copy_assign.send([&] { ++count; })); - BOOST_REQUIRE(count == 4); + BOOST_REQUIRE(count == 4); stlab::actor move_assign = std::move(move_ctor); // move assignment stlab::await(move_assign.send([&] { ++count; })); - BOOST_REQUIRE(count == 5); + BOOST_REQUIRE(count == 5); // equality comparable stlab::actor a(stlab::default_executor, "a"); stlab::actor b(stlab::default_executor, "b"); - BOOST_REQUIRE(a != b); // tests operator!= + BOOST_REQUIRE(a != b); // tests operator!= BOOST_REQUIRE(!(a == b)); // tests operator== BOOST_REQUIRE(a == a); // tests operator== @@ -150,7 +150,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_value) { stlab::future f = a.send([](auto& x) { return x; }); int result = stlab::await(f); - BOOST_REQUIRE(result == 42); + BOOST_REQUIRE(result == 42); } { @@ -158,7 +158,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_value) { stlab::future f = a.send([]() { return 42; }); int result = stlab::await(f); - BOOST_REQUIRE(result == 42); + BOOST_REQUIRE(result == 42); } } @@ -175,8 +175,8 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { int result1 = stlab::await(f1); int result3 = stlab::await(f3); - BOOST_REQUIRE(result1 == 42); - BOOST_REQUIRE(result3 == 4242); + BOOST_REQUIRE(result1 == 42); + BOOST_REQUIRE(result3 == 4242); } { @@ -188,7 +188,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { int result = stlab::await(f2); - BOOST_REQUIRE(result == 424242); + BOOST_REQUIRE(result == 424242); } } @@ -198,16 +198,16 @@ BOOST_AUTO_TEST_CASE(actor_then_from_value) { stlab::actor a(stlab::default_executor, "send_then_from_type", 42); stlab::future f0 = a.send([](auto& x) { return x; }); stlab::future f1 = a.then(stlab::future(f0), [](auto& x, auto y) { - BOOST_REQUIRE(x == 42); - BOOST_REQUIRE(y == 42); + BOOST_REQUIRE(x == 42); + BOOST_REQUIRE(y == 42); return x + y; }); stlab::future f2 = a.then( std::move(f0), [](auto& x, auto y, auto z) { - BOOST_REQUIRE(x == 42); - BOOST_REQUIRE(y == 42); - BOOST_REQUIRE(z == 100); + BOOST_REQUIRE(x == 42); + BOOST_REQUIRE(y == 42); + BOOST_REQUIRE(z == 100); return x + y + z; }, 100); @@ -215,8 +215,8 @@ BOOST_AUTO_TEST_CASE(actor_then_from_value) { int result1 = stlab::await(f1); int result2 = stlab::await(f2); - BOOST_REQUIRE(result1 == 84); - BOOST_REQUIRE(result2 == 184); + BOOST_REQUIRE(result1 == 84); + BOOST_REQUIRE(result2 == 184); } /**************************************************************************************************/ From 22bc88b5c043053e1fb27b7213dc333f3c541f1d Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Thu, 27 Apr 2023 17:06:55 -0700 Subject: [PATCH 06/16] Yet another build break --- test/actor_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp index 803e6f05..337f2890 100644 --- a/test/actor_tests.cpp +++ b/test/actor_tests.cpp @@ -7,7 +7,7 @@ /**************************************************************************************************/ // identity -#include +#include // boost #include From 733d7ce0f130fd32948a8f7899f716ad84dd1341 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Thu, 27 Apr 2023 17:58:49 -0700 Subject: [PATCH 07/16] Yet another build break --- stlab/concurrency/actor.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index 77b20a99..34985fad 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -28,6 +28,7 @@ /**************************************************************************************************/ namespace stlab { +inline namespace v1 { /**************************************************************************************************/ @@ -162,6 +163,7 @@ class actor { /**************************************************************************************************/ +} // namespace v1 } // namespace stlab /**************************************************************************************************/ From 9bf9539420ac0ab63a4ad2fd43a0ec1f98c9d34e Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Thu, 27 Jul 2023 15:58:38 -0700 Subject: [PATCH 08/16] adding `this_actor` and unique `actor_id` per actor. Also adding `executor`/`entask` and removing `send`/`then` --- stlab/concurrency/actor.hpp | 289 +++++++++++++++++++++++++++--------- test/actor_tests.cpp | 102 +++++++------ 2 files changed, 277 insertions(+), 114 deletions(-) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index 34985fad..441d461f 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -25,23 +25,48 @@ #pragma clang diagnostic ignored "-Wc++20-extensions" #endif // __clang__ -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ namespace stlab { inline namespace v1 { -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ + +/// @brief Opaque type representing an identifier unique to an actor. +/// @hyde-owner fbrereto +enum class actor_id : std::intptr_t; + +/// @brief Get the `actor_id` of the currently running actor. +/// @hyde-owner fbrereto +actor_id this_actor_id(); + +//------------------------------------------------------------------------------------------------------------------------------------------ + +/// @brief Support class to get details about the currently running actor. +/// @hyde-owner fbrereto +struct this_actor { + /// @brief Get the `actor_id` of the currently running actor. + /// @return The appropriate `actor_id`, or `0` if this is called outside the context of an actor. + static actor_id get_id(); + + /// @brief Get a reference to the currently running actor. + /// @throw `std::runtime_error` if this is called outside the context of an actor. + template + static decltype(auto) get(); +}; + +//------------------------------------------------------------------------------------------------------------------------------------------ namespace detail { -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ struct temp_thread_name { explicit temp_thread_name(const char* name) { stlab::set_current_thread_name(name); } ~temp_thread_name() { stlab::set_current_thread_name(""); } }; -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ template struct value_instance { @@ -51,71 +76,103 @@ struct value_instance { template <> struct value_instance {}; -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ + +struct actor_instance_base { + virtual ~actor_instance_base() = default; + + actor_id id() const { + return static_cast(reinterpret_cast>(this)); + } +}; + +//------------------------------------------------------------------------------------------------------------------------------------------ + +inline actor_instance_base*& this_actor_accessor() { + static thread_local actor_instance_base* this_actor{nullptr}; + return this_actor; +} + +struct temp_this_actor { + temp_this_actor(actor_instance_base* actor) { + assert(actor); + this_actor_accessor() = actor; + } + + ~temp_this_actor() { this_actor_accessor() = nullptr; } +}; + +//------------------------------------------------------------------------------------------------------------------------------------------ template -struct actor_instance : public std::enable_shared_from_this> { +struct actor_instance + : public actor_instance_base + , std::enable_shared_from_this> { template - explicit actor_instance(Executor&& e, std::string&& name) : - _q(std::forward(e)), _name(std::move(name)) {} + explicit actor_instance(Executor&& e, std::string&& name) + : _q(std::forward(e)), _name(std::move(name)) {} template void initialize(Args&&... args) { - // We want to construct the object instance in the executor where - // it will be running. We cannot initialize in the constructor because - // `shared_from_this` will throw `bad_weak_ptr`. - _q( - [_this = this->shared_from_this()](auto&&... args) mutable { - temp_thread_name ttn(_this->_name.c_str()); - _this->_instance._x = T(std::forward(args)...); - }, - std::forward(args)...) - .detach(); + // We want to construct the object instance in the executor where it will be running. We + // cannot initialize in the constructor because `shared_from_this` will throw + // `bad_weak_ptr`. + // + // Unfortunately we cannot use schedule() here as it would dereference the std::optional when it + // doesn't contain a value, and that's UB... + stlab::async(executor(), [_this = this->shared_from_this()](auto&&... args){ + _this->_instance._x = T(std::forward(args)...); + }, std::forward(args)...).detach(); } auto set_name(std::string&& name) { _name = std::move(name); } - template - auto send(F&& f, Args&&... args) { - auto task = [_f = std::forward(f), - _this = this->shared_from_this()](auto&&... args) mutable { - temp_thread_name ttn(_this->_name.c_str()); - if constexpr (std::is_same_v) { - return std::move(_f)(std::forward(args)...); - } else { - return std::move(_f)(*(_this->_instance._x), std::forward(args)...); + template + auto entask(F&& f) { + return [_f = std::forward(f), _this = this->shared_from_this() +#ifndef NDEBUG + , _id = this->id() +#endif // NDEBUG + ](auto&&... args) mutable { + // tasks that are "entasked" for an actor must be executed on that same actor, or + // Bad Things could happen, namely, a data race between this task and any other + // task(s) the original actor may run simultaneously. + // + // If you find yourself here, you have created a task intending it for one actor, + // but have accidentally tried to execute it on another (including no actor). + assert(_id == stlab::this_actor::get_id()); + + try { + if constexpr (std::is_same_v) { + return std::move(_f)(std::forward(args)...); + } else { + return std::move(_f)(const_cast(*(_this->_instance._x)), std::forward(args)...); + } + } catch (const std::exception& error) { + const char* what = error.what(); + (void)what; + assert(!"Uncaught actor exception"); + } catch (...) { + assert(!"Uncaught actor exception"); } }; - return _q(std::move(task), std::forward(args)...); } - template - auto then(stlab::future&& future, F&& f, Args&&... args) { - auto task = [_f = std::forward(f), ... _args = std::forward(args), - _this = this->shared_from_this()](R&& x) mutable { - temp_thread_name ttn(_this->_name.c_str()); - if constexpr (std::is_same_v) { - return std::move(_f)(std::forward(x), std::move(_args)...); - } else { - return std::move(_f)(*(_this->_instance._x), std::forward(x), - std::move(_args)...); - } - }; - return std::move(future).then(_q.executor(), std::move(task)); + template + auto operator()(F&& f, Args&&... args) { + return stlab::async(executor(), entask(std::forward(f)), std::forward(args)...); } - template - auto then(stlab::future&& future, F&& f, Args&&... args) { - auto task = [_f = std::forward(f), ... _args = std::forward(args), - _this = this->shared_from_this()]() mutable { - temp_thread_name ttn(_this->_name.c_str()); - if constexpr (std::is_same_v) { - return std::move(_f)(std::move(_args)...); - } else { - return std::move(_f)(*(_this->_instance._x), std::move(_args)...); - } + auto executor() { + return [_this = this->shared_from_this()](auto&& task) { + _this + ->_q([_t = std::forward(task), _this = _this]() mutable { + temp_this_actor tta(_this.get()); + temp_thread_name ttn(_this->_name.c_str()); + std::move(_t)(); + }) + .detach(); }; - return std::move(future).then(_q.executor(), std::move(task)); } value_instance _instance; @@ -123,57 +180,155 @@ struct actor_instance : public std::enable_shared_from_this> { std::string _name; }; -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ } // namespace detail -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ +/// @hyde-owner fbrereto +/// @brief Serialized, asynchronous access to a resource template class actor { std::shared_ptr> _impl; + /// friend declaration to the free function. + template + friend actor this_actor_instance(); + public: + /// Value type for the class. `actor` will own this instance. If the `T` is `void`, nothing + /// will be instantiated. + using value_type = T; + actor() = default; + /// @param e The executor where actor lambdas will be scheduled + /// @param name The name of the executor. While the actor is running, the thread it is executing on + /// will be temporarily renamed to this value (if the OS supports it.) + /// @param args Additional arguments to be passed to the `value_type` of this instance during its + /// construction. template - actor(Executor&& e, std::string&& name, Args&&... args) : - _impl(std::make_shared>(std::forward(e), - std::move(name))) { - if constexpr (!std::is_same_v) { + actor(Executor&& e, std::string&& name, Args&&... args) + : _impl(std::make_shared>(std::forward(e), + std::move(name))) { + if constexpr (!std::is_same_v) { _impl->initialize(std::forward(args)...); } } + /// @brief Sets the name of the actor to something else. + /// @param name The incoming name to use from here on out. auto set_name(std::string&& name) { _impl->set_name(std::move(name)); } + /// @brief Schedule a task for the actor to execute. + /// @note This routine has identical semantics to `operator()`. + /// @param f The function to execute. Note that the first parameter to this function must be `T&`, + /// and will reference the instance owned by the actor. + /// @param args Additional arguments to pass to `f` at the time it is invoked. template - auto send(F&& f, Args&&... args) { - return _impl->send(std::forward(f), std::forward(args)...); + auto schedule(F&& f, Args&&... args) { + return (*_impl)(std::forward(f), std::forward(args)...); } - template - auto then(stlab::future&& future, F&& f, Args&&... args) { - return _impl->then(std::move(future), std::forward(f), std::forward(args)...); + /// @brief Schedule a task for the actor to execute. + /// @note This routine has identical semantics to `schedule`. + /// @param f The function to execute. Note that the first parameter to this function must be `T&`, + /// and will reference the instance owned by the actor. + /// @param args Additional arguments to pass to `f` at the time it is invoked. + template + auto operator()(F&& f, Args&&... args) { + return (*_impl)(std::forward(f), std::forward(args)...); + } + + /// @brief Get the unique `actor_id` of this actor. + auto get_id() const { return _impl->id(); } + + /// This is a nullary task executor, namely, you will not get a reference to the task + /// local data this actor owns. If you want access that data using this executor, wrap the task + /// via `actor::entask`. For example: + /// + /// ``` + /// auto f = async(actor.executor(), actor.entask([](auto& data){ /*...*/ })); + /// ``` + /// @brief Get a nullary task executor for this actor. + auto executor() { return _impl->executor(); } + + /// @brief Obtain a nullary lambda that can access the `actor`'s task local data. + /// @return a nullary lambda that, when invoked, will receive the `actor`'s task local data as + /// its first argument. + + template + auto entask(F&& f) { + return _impl->entask(std::forward(f)); } friend bool operator==(const actor& x, const actor& y) { return x._impl == y._impl; } friend bool operator!=(const actor& x, const actor& y) { return !(x == y); } }; -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ + +/// In the event the routine is called outside the context of a running actor, this routine will +/// return the `actor_id` equivalent of `0`. +/// @brief Get the `actor_id` of the currently running actor. +/// @hyde-owner fbrereto +inline actor_id this_actor_id() { + detail::actor_instance_base* base = detail::this_actor_accessor(); + return base ? base->id() : actor_id{0}; +} + +/// Get the currently running actor. +/// @hyde-owner fbrereto +/// @throw `std::runtime_error` in the event the routine is called outside the context of a running actor. +template +actor this_actor_instance() { + detail::actor_instance_base* base = detail::this_actor_accessor(); + if (!base) { + throw std::runtime_error("Not in an actor"); + } + detail::actor_instance& instance = dynamic_cast&>(*base); + actor result; + result._impl = instance.shared_from_this(); + return result; +} + +/// @brief Determine if the caller is currently in an `actor` execution context. +/// @hyde-owner fbrereto +template +bool in_actor() { + // I could make this a little faster by imitating `this_actor` right up to the instance cast. + return this_actor_instance() != actor(); +} + +//------------------------------------------------------------------------------------------------------------------------------------------ + +/// @brief Get the `actor_id` of the currently running actor. +/// @hyde-owner fbrereto +/// @note This routine has identical semantics to `this_actor_id`. +inline actor_id this_actor::get_id() { return this_actor_id(); } + +/// @brief Get the currently running actor. +/// @hyde-owner fbrereto +/// @note This routine has identical semantics to `stlab::this_actor()`. +template +decltype(auto) this_actor::get() { + return stlab::this_actor_instance(); +} + +//------------------------------------------------------------------------------------------------------------------------------------------ } // namespace v1 } // namespace stlab -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ #if __clang__ #pragma clang diagnostic pop #endif // __clang__ -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ #endif // STLAB_CONCURRENCY_ACTOR_HPP -/**************************************************************************************************/ +//------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp index 337f2890..b2654744 100644 --- a/test/actor_tests.cpp +++ b/test/actor_tests.cpp @@ -24,14 +24,14 @@ void increment(int& i) { increment_by(i, 1); } template T get_actor_value(stlab::actor& a) { - return stlab::await(a.send([](auto& x) { return x; })); + return stlab::await(a([](auto& x) { return x; })); } /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { stlab::actor a(stlab::default_executor, "actor_int", 42); - stlab::future f = a.send([](auto& i) { BOOST_REQUIRE(i == 42); }); + stlab::future f = a([](auto& i) { BOOST_REQUIRE(i == 42); }); stlab::await(f); @@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { BOOST_AUTO_TEST_CASE(actor_construct_void) { stlab::actor a(stlab::default_executor, "actor_void"); std::atomic_bool sent{false}; - stlab::future f = a.send([&]() { sent = true; }); + stlab::future f = a([&]() { sent = true; }); stlab::await(f); @@ -56,23 +56,23 @@ BOOST_AUTO_TEST_CASE(actor_regularity) { stlab::actor empty_ctor; stlab::actor default_ctor(stlab::default_executor, "foo"); // default construction - default_ctor.send(increment).detach(); + default_ctor(increment).detach(); BOOST_REQUIRE(get_actor_value(default_ctor) == 1); stlab::actor copy_ctor(default_ctor); // copy construction - copy_ctor.send(increment).detach(); + copy_ctor(increment).detach(); BOOST_REQUIRE(get_actor_value(copy_ctor) == 2); stlab::actor move_ctor(std::move(default_ctor)); // move construction - move_ctor.send(increment).detach(); + move_ctor(increment).detach(); BOOST_REQUIRE(get_actor_value(move_ctor) == 3); stlab::actor copy_assign = copy_ctor; // copy assignment - copy_assign.send(increment).detach(); + copy_assign(increment).detach(); BOOST_REQUIRE(get_actor_value(copy_assign) == 4); stlab::actor move_assign = std::move(move_ctor); // move assignment - move_assign.send(increment).detach(); + move_assign(increment).detach(); BOOST_REQUIRE(get_actor_value(move_assign) == 5); // equality comparable @@ -91,23 +91,23 @@ BOOST_AUTO_TEST_CASE(actor_regularity_void) { stlab::actor empty_ctor; stlab::actor default_ctor(stlab::default_executor, "foo"); // default construction - stlab::await(default_ctor.send([&] { ++count; })); + stlab::await(default_ctor([&] { ++count; })); BOOST_REQUIRE(count == 1); stlab::actor copy_ctor(default_ctor); // copy construction - stlab::await(copy_ctor.send([&] { ++count; })); + stlab::await(copy_ctor([&] { ++count; })); BOOST_REQUIRE(count == 2); stlab::actor move_ctor(std::move(default_ctor)); // move construction - stlab::await(move_ctor.send([&] { ++count; })); + stlab::await(move_ctor([&] { ++count; })); BOOST_REQUIRE(count == 3); stlab::actor copy_assign = move_ctor; // copy assignment - stlab::await(copy_assign.send([&] { ++count; })); + stlab::await(copy_assign([&] { ++count; })); BOOST_REQUIRE(count == 4); stlab::actor move_assign = std::move(move_ctor); // move assignment - stlab::await(move_assign.send([&] { ++count; })); + stlab::await(move_assign([&] { ++count; })); BOOST_REQUIRE(count == 5); // equality comparable @@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(actor_regularity_void) { BOOST_AUTO_TEST_CASE(actor_send_to_void) { { stlab::actor a(stlab::default_executor, "send_getting_void"); - stlab::future f = a.send(increment); + stlab::future f = a(increment); stlab::await(f); @@ -134,7 +134,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_void) { { stlab::actor a(stlab::default_executor, "send_getting_void"); std::atomic_bool sent{false}; - stlab::future f = a.send([&] { sent = true; }); + stlab::future f = a([&] { sent = true; }); stlab::await(f); @@ -147,7 +147,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_void) { BOOST_AUTO_TEST_CASE(actor_send_to_value) { { stlab::actor a(stlab::default_executor, "send_getting_value", 42); - stlab::future f = a.send([](auto& x) { return x; }); + stlab::future f = a([](auto& x) { return x; }); int result = stlab::await(f); BOOST_REQUIRE(result == 42); @@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_value) { { stlab::actor a(stlab::default_executor, "send_getting_value"); - stlab::future f = a.send([]() { return 42; }); + stlab::future f = a([]() { return 42; }); int result = stlab::await(f); BOOST_REQUIRE(result == 42); @@ -167,26 +167,21 @@ BOOST_AUTO_TEST_CASE(actor_send_to_value) { BOOST_AUTO_TEST_CASE(actor_then_from_void) { { stlab::actor a(stlab::default_executor, "send_then_from_void"); - stlab::future f0 = a.send(increment_by, 42); - stlab::future f1 = a.then(stlab::future(f0), [](auto& x) { return x; }); - stlab::future f2 = a.then(std::move(f0), increment_by, 4200); - stlab::future f3 = a.then(std::move(f2), [](auto& x) { return x; }); + stlab::future f = + a([](int& x) { x += 42; }).then(a.executor(), a.entask([](int& x) { return x; })); - int result1 = stlab::await(f1); - int result3 = stlab::await(f3); + int result = stlab::await(f); - BOOST_REQUIRE(result1 == 42); - BOOST_REQUIRE(result3 == 4242); + BOOST_REQUIRE(result == 42); } { stlab::actor a(stlab::default_executor, "send_then_from_void"); - stlab::future f0 = a.send([]() { return 42; }); - stlab::future f1 = a.then(std::move(f0), [](auto x) { return 4200 + x; }); - stlab::future f2 = a.then( - std::move(f1), [](auto x, auto y) { return x + y; }, 420000); + stlab::future f = a([]() { return 42; }) + .then(a.executor(), a.entask([](auto x) { return 4200 + x; })) + .then(a.executor(), a.entask([](auto x) { return x + 420000; })); - int result = stlab::await(f2); + int result = stlab::await(f); BOOST_REQUIRE(result == 424242); } @@ -196,27 +191,40 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { BOOST_AUTO_TEST_CASE(actor_then_from_value) { stlab::actor a(stlab::default_executor, "send_then_from_type", 42); - stlab::future f0 = a.send([](auto& x) { return x; }); - stlab::future f1 = a.then(stlab::future(f0), [](auto& x, auto y) { - BOOST_REQUIRE(x == 42); - BOOST_REQUIRE(y == 42); - return x + y; - }); - stlab::future f2 = a.then( - std::move(f0), - [](auto& x, auto y, auto z) { + stlab::future f = + a([](auto& x) { return x; }).then(a.executor(), a.entask([](auto& x, auto y) { BOOST_REQUIRE(x == 42); BOOST_REQUIRE(y == 42); - BOOST_REQUIRE(z == 100); - return x + y + z; - }, - 100); + return x + y; + })); - int result1 = stlab::await(f1); - int result2 = stlab::await(f2); + int result = stlab::await(f); + + BOOST_REQUIRE(result == 84); +} + +/**************************************************************************************************/ - BOOST_REQUIRE(result1 == 84); - BOOST_REQUIRE(result2 == 184); +BOOST_AUTO_TEST_CASE(actor_this_actor) { + stlab::actor a(stlab::default_executor, "this_actor"); + + try { + stlab::this_actor::get(); + BOOST_REQUIRE(false); // "Expected a throw - not run in an actor"; + } catch (...) { } + + BOOST_REQUIRE(stlab::this_actor::get_id() == stlab::actor_id{0}); + + auto f = a([_a = a](){ + auto this_instance = stlab::this_actor::get(); + BOOST_REQUIRE(this_instance == _a); + + auto this_actor_id = stlab::this_actor::get_id(); + BOOST_REQUIRE(this_actor_id == _a.get_id()); + BOOST_REQUIRE(this_actor_id != stlab::actor_id{0}); + }); + + stlab::await(f); } /**************************************************************************************************/ From aa5e487e0bd43a997153a7f887caccde3af3c145 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Thu, 27 Jul 2023 16:15:58 -0700 Subject: [PATCH 09/16] Changes from review comments by @FelixPetriconi and @sean-parent --- stlab/concurrency/actor.hpp | 36 ++++++++++++++++++------------------ test/actor_tests.cpp | 12 ++++++------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index 441d461f..71cdf308 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -109,8 +109,8 @@ struct actor_instance : public actor_instance_base , std::enable_shared_from_this> { template - explicit actor_instance(Executor&& e, std::string&& name) - : _q(std::forward(e)), _name(std::move(name)) {} + actor_instance(Executor e, std::string name) + : _q(std::move(e)), _name(std::move(name)) {} template void initialize(Args&&... args) { @@ -120,8 +120,10 @@ struct actor_instance // // Unfortunately we cannot use schedule() here as it would dereference the std::optional when it // doesn't contain a value, and that's UB... - stlab::async(executor(), [_this = this->shared_from_this()](auto&&... args){ - _this->_instance._x = T(std::forward(args)...); + stlab::async(executor(), [_weak = this->weak_from_this()](auto&&... args){ + if (auto self = _weak.lock()) { + self->_instance._x = T(std::forward(args)...); + } }, std::forward(args)...).detach(); } @@ -158,9 +160,9 @@ struct actor_instance }; } - template - auto operator()(F&& f, Args&&... args) { - return stlab::async(executor(), entask(std::forward(f)), std::forward(args)...); + template + auto operator()(F&& f) { + return stlab::async(executor(), entask(std::forward(f))); } auto executor() { @@ -209,8 +211,8 @@ class actor { /// @param args Additional arguments to be passed to the `value_type` of this instance during its /// construction. template - actor(Executor&& e, std::string&& name, Args&&... args) - : _impl(std::make_shared>(std::forward(e), + actor(Executor e, std::string name, Args&&... args) + : _impl(std::make_shared>(std::move(e), std::move(name))) { if constexpr (!std::is_same_v) { _impl->initialize(std::forward(args)...); @@ -219,26 +221,24 @@ class actor { /// @brief Sets the name of the actor to something else. /// @param name The incoming name to use from here on out. - auto set_name(std::string&& name) { _impl->set_name(std::move(name)); } + auto set_name(std::string name) { _impl->set_name(std::move(name)); } /// @brief Schedule a task for the actor to execute. /// @note This routine has identical semantics to `operator()`. /// @param f The function to execute. Note that the first parameter to this function must be `T&`, /// and will reference the instance owned by the actor. - /// @param args Additional arguments to pass to `f` at the time it is invoked. - template - auto schedule(F&& f, Args&&... args) { - return (*_impl)(std::forward(f), std::forward(args)...); + template + auto schedule(F&& f) { + return (*_impl)(std::forward(f)); } /// @brief Schedule a task for the actor to execute. /// @note This routine has identical semantics to `schedule`. /// @param f The function to execute. Note that the first parameter to this function must be `T&`, /// and will reference the instance owned by the actor. - /// @param args Additional arguments to pass to `f` at the time it is invoked. - template - auto operator()(F&& f, Args&&... args) { - return (*_impl)(std::forward(f), std::forward(args)...); + template + auto operator()(F&& f) { + return (*_impl)(std::forward(f)); } /// @brief Get the unique `actor_id` of this actor. diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp index b2654744..85d8318d 100644 --- a/test/actor_tests.cpp +++ b/test/actor_tests.cpp @@ -24,14 +24,14 @@ void increment(int& i) { increment_by(i, 1); } template T get_actor_value(stlab::actor& a) { - return stlab::await(a([](auto& x) { return x; })); + return stlab::await(a([](auto x) { return x; })); } /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { stlab::actor a(stlab::default_executor, "actor_int", 42); - stlab::future f = a([](auto& i) { BOOST_REQUIRE(i == 42); }); + stlab::future f = a([](auto i) { BOOST_REQUIRE(i == 42); }); stlab::await(f); @@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { BOOST_AUTO_TEST_CASE(actor_construct_void) { stlab::actor a(stlab::default_executor, "actor_void"); - std::atomic_bool sent{false}; + bool sent{false}; stlab::future f = a([&]() { sent = true; }); stlab::await(f); @@ -147,7 +147,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_void) { BOOST_AUTO_TEST_CASE(actor_send_to_value) { { stlab::actor a(stlab::default_executor, "send_getting_value", 42); - stlab::future f = a([](auto& x) { return x; }); + stlab::future f = a([](auto x) { return x; }); int result = stlab::await(f); BOOST_REQUIRE(result == 42); @@ -168,7 +168,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { { stlab::actor a(stlab::default_executor, "send_then_from_void"); stlab::future f = - a([](int& x) { x += 42; }).then(a.executor(), a.entask([](int& x) { return x; })); + a([](int& x) { x += 42; }).then(a.executor(), a.entask([](int x) { return x; })); int result = stlab::await(f); @@ -192,7 +192,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { BOOST_AUTO_TEST_CASE(actor_then_from_value) { stlab::actor a(stlab::default_executor, "send_then_from_type", 42); stlab::future f = - a([](auto& x) { return x; }).then(a.executor(), a.entask([](auto& x, auto y) { + a([](auto x) { return x; }).then(a.executor(), a.entask([](auto x, auto y) { BOOST_REQUIRE(x == 42); BOOST_REQUIRE(y == 42); return x + y; From 5e3d1ad5876b564fce4027580de09cbb9d0e6605 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Thu, 27 Jul 2023 16:32:27 -0700 Subject: [PATCH 10/16] rethrowing at the top of `entask` --- stlab/concurrency/actor.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index 71cdf308..f164cbbd 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -154,8 +154,10 @@ struct actor_instance const char* what = error.what(); (void)what; assert(!"Uncaught actor exception"); + throw; } catch (...) { assert(!"Uncaught actor exception"); + throw; } }; } From a78fa9a7bd0a373b587cfb47f2bf6a00edf66167 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Thu, 27 Jul 2023 16:46:33 -0700 Subject: [PATCH 11/16] adding thread name getter / restorer --- stlab/concurrency/actor.hpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index f164cbbd..1f8e0692 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -61,9 +61,34 @@ namespace detail { //------------------------------------------------------------------------------------------------------------------------------------------ +inline std::string get_current_thread_name() { + char name[64] = {0}; + const std::size_t size{sizeof(name) / sizeof(name[0])}; + +#if STLAB_THREADS(WIN32) + // Nothing +#elif STLAB_THREADS(PTHREAD_EMSCRIPTEN) + emscripten_get_thread_name(pthread_self(), name, size); +#elif STLAB_THREADS(PTHREAD_APPLE) + pthread_getname_np(pthread_self(), name, size); +#elif STLAB_THREADS(PTHREAD) + pthread_getname_np(pthread_self(), name, size); +#elif STLAB_THREADS(NONE) + // Nothing +#else + #error "Unspecified or unknown thread mode set." +#endif + + return std::string(&name[0]); +} + +//------------------------------------------------------------------------------------------------------------------------------------------ + struct temp_thread_name { - explicit temp_thread_name(const char* name) { stlab::set_current_thread_name(name); } - ~temp_thread_name() { stlab::set_current_thread_name(""); } + explicit temp_thread_name(const char* name) : _old_name(get_current_thread_name()) { stlab::set_current_thread_name(name); } + ~temp_thread_name() { stlab::set_current_thread_name(_old_name.c_str()); } +private: + std::string _old_name; }; //------------------------------------------------------------------------------------------------------------------------------------------ From 25b279bc92ae2f4748d20f1b5676a741c541c17c Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Thu, 27 Jul 2023 20:41:29 -0700 Subject: [PATCH 12/16] Yet another build break --- stlab/concurrency/actor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index 1f8e0692..b950aef9 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -68,7 +68,7 @@ inline std::string get_current_thread_name() { #if STLAB_THREADS(WIN32) // Nothing #elif STLAB_THREADS(PTHREAD_EMSCRIPTEN) - emscripten_get_thread_name(pthread_self(), name, size); + // Nothing #elif STLAB_THREADS(PTHREAD_APPLE) pthread_getname_np(pthread_self(), name, size); #elif STLAB_THREADS(PTHREAD) From baa3e95e578a4f2e0711a2bc922fd3945ed8eb61 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Thu, 27 Jul 2023 20:51:00 -0700 Subject: [PATCH 13/16] Yet another build break --- stlab/concurrency/actor.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index b950aef9..f8911ea2 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -63,15 +63,16 @@ namespace detail { inline std::string get_current_thread_name() { char name[64] = {0}; - const std::size_t size{sizeof(name) / sizeof(name[0])}; #if STLAB_THREADS(WIN32) // Nothing #elif STLAB_THREADS(PTHREAD_EMSCRIPTEN) // Nothing #elif STLAB_THREADS(PTHREAD_APPLE) + const std::size_t size{sizeof(name) / sizeof(name[0])}; pthread_getname_np(pthread_self(), name, size); #elif STLAB_THREADS(PTHREAD) + const std::size_t size{sizeof(name) / sizeof(name[0])}; pthread_getname_np(pthread_self(), name, size); #elif STLAB_THREADS(NONE) // Nothing From a6ad76543cd8036d35c3a600736e032a9d34f85e Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Fri, 28 Jul 2023 08:41:44 -0700 Subject: [PATCH 14/16] minor tweaks --- stlab/concurrency/actor.hpp | 105 ++++++++++++++++-------------------- test/actor_tests.cpp | 32 ++++++----- 2 files changed, 63 insertions(+), 74 deletions(-) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index f8911ea2..03f4e0b2 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -61,35 +61,28 @@ namespace detail { //------------------------------------------------------------------------------------------------------------------------------------------ -inline std::string get_current_thread_name() { - char name[64] = {0}; - +struct temp_thread_name { + explicit temp_thread_name(const char* name) { #if STLAB_THREADS(WIN32) - // Nothing + // Nothing #elif STLAB_THREADS(PTHREAD_EMSCRIPTEN) - // Nothing -#elif STLAB_THREADS(PTHREAD_APPLE) - const std::size_t size{sizeof(name) / sizeof(name[0])}; - pthread_getname_np(pthread_self(), name, size); -#elif STLAB_THREADS(PTHREAD) - const std::size_t size{sizeof(name) / sizeof(name[0])}; - pthread_getname_np(pthread_self(), name, size); + // Nothing +#elif STLAB_THREADS(PTHREAD_APPLE) || STLAB_THREADS(PTHREAD) + pthread_getname_np(pthread_self(), _old_name, _old_name_size_k); #elif STLAB_THREADS(NONE) - // Nothing + // Nothing #else - #error "Unspecified or unknown thread mode set." +#error "Unspecified or unknown thread mode set." #endif - return std::string(&name[0]); -} + stlab::set_current_thread_name(name); + } -//------------------------------------------------------------------------------------------------------------------------------------------ + ~temp_thread_name() { stlab::set_current_thread_name(_old_name); } -struct temp_thread_name { - explicit temp_thread_name(const char* name) : _old_name(get_current_thread_name()) { stlab::set_current_thread_name(name); } - ~temp_thread_name() { stlab::set_current_thread_name(_old_name.c_str()); } private: - std::string _old_name; + constexpr static std::size_t _old_name_size_k = 64; + char _old_name[_old_name_size_k] = {0}; }; //------------------------------------------------------------------------------------------------------------------------------------------ @@ -131,12 +124,10 @@ struct temp_this_actor { //------------------------------------------------------------------------------------------------------------------------------------------ template -struct actor_instance - : public actor_instance_base - , std::enable_shared_from_this> { +struct actor_instance : public actor_instance_base, + std::enable_shared_from_this> { template - actor_instance(Executor e, std::string name) - : _q(std::move(e)), _name(std::move(name)) {} + actor_instance(Executor e, std::string name) : _q(std::move(e)), _name(std::move(name)) {} template void initialize(Args&&... args) { @@ -144,13 +135,17 @@ struct actor_instance // cannot initialize in the constructor because `shared_from_this` will throw // `bad_weak_ptr`. // - // Unfortunately we cannot use schedule() here as it would dereference the std::optional when it - // doesn't contain a value, and that's UB... - stlab::async(executor(), [_weak = this->weak_from_this()](auto&&... args){ - if (auto self = _weak.lock()) { - self->_instance._x = T(std::forward(args)...); - } - }, std::forward(args)...).detach(); + // Unfortunately we cannot use schedule() here as it would dereference the `std::optional` + // when it doesn't contain a value, and that's UB... + stlab::async( + executor(), + [_weak = this->weak_from_this()](auto&&... args) { + if (auto self = _weak.lock()) { + self->_instance._x = T(std::forward(args)...); + } + }, + std::forward(args)...) + .detach(); } auto set_name(std::string&& name) { _name = std::move(name); } @@ -161,7 +156,7 @@ struct actor_instance #ifndef NDEBUG , _id = this->id() #endif // NDEBUG - ](auto&&... args) mutable { + ](auto&&... args) mutable { // tasks that are "entasked" for an actor must be executed on that same actor, or // Bad Things could happen, namely, a data race between this task and any other // task(s) the original actor may run simultaneously. @@ -170,20 +165,11 @@ struct actor_instance // but have accidentally tried to execute it on another (including no actor). assert(_id == stlab::this_actor::get_id()); - try { - if constexpr (std::is_same_v) { - return std::move(_f)(std::forward(args)...); - } else { - return std::move(_f)(const_cast(*(_this->_instance._x)), std::forward(args)...); - } - } catch (const std::exception& error) { - const char* what = error.what(); - (void)what; - assert(!"Uncaught actor exception"); - throw; - } catch (...) { - assert(!"Uncaught actor exception"); - throw; + if constexpr (std::is_same_v) { + return std::move(_f)(std::forward(args)...); + } else { + return std::move(_f)(const_cast(*(_this->_instance._x)), + std::forward(args)...); } }; } @@ -233,15 +219,14 @@ class actor { actor() = default; - /// @param e The executor where actor lambdas will be scheduled - /// @param name The name of the executor. While the actor is running, the thread it is executing on - /// will be temporarily renamed to this value (if the OS supports it.) - /// @param args Additional arguments to be passed to the `value_type` of this instance during its - /// construction. + /// @param e The executor where actor lambdas will be scheduled. + /// @param name The name of the executor. While the actor is running, the thread it is executing + /// on will be temporarily renamed to this value (if the OS supports it.) + /// @param args Additional arguments to be passed to the `value_type` of this instance during + /// its construction. template - actor(Executor e, std::string name, Args&&... args) - : _impl(std::make_shared>(std::move(e), - std::move(name))) { + actor(Executor e, std::string name, Args&&... args) : + _impl(std::make_shared>(std::move(e), std::move(name))) { if constexpr (!std::is_same_v) { _impl->initialize(std::forward(args)...); } @@ -253,8 +238,8 @@ class actor { /// @brief Schedule a task for the actor to execute. /// @note This routine has identical semantics to `operator()`. - /// @param f The function to execute. Note that the first parameter to this function must be `T&`, - /// and will reference the instance owned by the actor. + /// @param f The function to execute. Note that the first parameter to this function must be + /// `T&`, and will reference the instance owned by the actor. template auto schedule(F&& f) { return (*_impl)(std::forward(f)); @@ -262,8 +247,8 @@ class actor { /// @brief Schedule a task for the actor to execute. /// @note This routine has identical semantics to `schedule`. - /// @param f The function to execute. Note that the first parameter to this function must be `T&`, - /// and will reference the instance owned by the actor. + /// @param f The function to execute. Note that the first parameter to this function must be + /// `T&`, and will reference the instance owned by the actor. template auto operator()(F&& f) { return (*_impl)(std::forward(f)); @@ -284,7 +269,7 @@ class actor { /// @brief Obtain a nullary lambda that can access the `actor`'s task local data. /// @return a nullary lambda that, when invoked, will receive the `actor`'s task local data as - /// its first argument. + /// its first argument. template auto entask(F&& f) { diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp index 85d8318d..83a2709a 100644 --- a/test/actor_tests.cpp +++ b/test/actor_tests.cpp @@ -27,10 +27,14 @@ T get_actor_value(stlab::actor& a) { return stlab::await(a([](auto x) { return x; })); } +std::string current_test_name() { + return boost::unit_test::framework::current_test_case().full_name(); +} + /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { - stlab::actor a(stlab::default_executor, "actor_int", 42); + stlab::actor a(stlab::default_executor, current_test_name(), 42); stlab::future f = a([](auto i) { BOOST_REQUIRE(i == 42); }); stlab::await(f); @@ -41,7 +45,7 @@ BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_construct_void) { - stlab::actor a(stlab::default_executor, "actor_void"); + stlab::actor a(stlab::default_executor, current_test_name()); bool sent{false}; stlab::future f = a([&]() { sent = true; }); @@ -55,7 +59,7 @@ BOOST_AUTO_TEST_CASE(actor_construct_void) { BOOST_AUTO_TEST_CASE(actor_regularity) { stlab::actor empty_ctor; - stlab::actor default_ctor(stlab::default_executor, "foo"); // default construction + stlab::actor default_ctor(stlab::default_executor, current_test_name()); // default construction default_ctor(increment).detach(); BOOST_REQUIRE(get_actor_value(default_ctor) == 1); @@ -90,7 +94,7 @@ BOOST_AUTO_TEST_CASE(actor_regularity_void) { std::size_t count{0}; stlab::actor empty_ctor; - stlab::actor default_ctor(stlab::default_executor, "foo"); // default construction + stlab::actor default_ctor(stlab::default_executor, current_test_name()); // default construction stlab::await(default_ctor([&] { ++count; })); BOOST_REQUIRE(count == 1); @@ -121,9 +125,9 @@ BOOST_AUTO_TEST_CASE(actor_regularity_void) { /**************************************************************************************************/ -BOOST_AUTO_TEST_CASE(actor_send_to_void) { +BOOST_AUTO_TEST_CASE(actor_schedule_to_void) { { - stlab::actor a(stlab::default_executor, "send_getting_void"); + stlab::actor a(stlab::default_executor, current_test_name()); stlab::future f = a(increment); stlab::await(f); @@ -132,7 +136,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_void) { } { - stlab::actor a(stlab::default_executor, "send_getting_void"); + stlab::actor a(stlab::default_executor, current_test_name()); std::atomic_bool sent{false}; stlab::future f = a([&] { sent = true; }); @@ -144,9 +148,9 @@ BOOST_AUTO_TEST_CASE(actor_send_to_void) { /**************************************************************************************************/ -BOOST_AUTO_TEST_CASE(actor_send_to_value) { +BOOST_AUTO_TEST_CASE(actor_schedule_to_value) { { - stlab::actor a(stlab::default_executor, "send_getting_value", 42); + stlab::actor a(stlab::default_executor, current_test_name(), 42); stlab::future f = a([](auto x) { return x; }); int result = stlab::await(f); @@ -154,7 +158,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_value) { } { - stlab::actor a(stlab::default_executor, "send_getting_value"); + stlab::actor a(stlab::default_executor, current_test_name()); stlab::future f = a([]() { return 42; }); int result = stlab::await(f); @@ -166,7 +170,7 @@ BOOST_AUTO_TEST_CASE(actor_send_to_value) { BOOST_AUTO_TEST_CASE(actor_then_from_void) { { - stlab::actor a(stlab::default_executor, "send_then_from_void"); + stlab::actor a(stlab::default_executor, current_test_name()); stlab::future f = a([](int& x) { x += 42; }).then(a.executor(), a.entask([](int x) { return x; })); @@ -176,7 +180,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { } { - stlab::actor a(stlab::default_executor, "send_then_from_void"); + stlab::actor a(stlab::default_executor, current_test_name()); stlab::future f = a([]() { return 42; }) .then(a.executor(), a.entask([](auto x) { return 4200 + x; })) .then(a.executor(), a.entask([](auto x) { return x + 420000; })); @@ -190,7 +194,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_then_from_value) { - stlab::actor a(stlab::default_executor, "send_then_from_type", 42); + stlab::actor a(stlab::default_executor, current_test_name(), 42); stlab::future f = a([](auto x) { return x; }).then(a.executor(), a.entask([](auto x, auto y) { BOOST_REQUIRE(x == 42); @@ -206,7 +210,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_value) { /**************************************************************************************************/ BOOST_AUTO_TEST_CASE(actor_this_actor) { - stlab::actor a(stlab::default_executor, "this_actor"); + stlab::actor a(stlab::default_executor, current_test_name()); try { stlab::this_actor::get(); From 330c64f22968782cbb4b0709f04f6385c69a4624 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Fri, 28 Jul 2023 11:54:32 -0700 Subject: [PATCH 15/16] Adding `enqueue` and `complete` APIs, though I am not sure I like the implementation. --- stlab/concurrency/actor.hpp | 109 ++++++++++++++++++++++++------------ test/actor_tests.cpp | 87 ++++++++++++++++++++-------- 2 files changed, 139 insertions(+), 57 deletions(-) diff --git a/stlab/concurrency/actor.hpp b/stlab/concurrency/actor.hpp index 03f4e0b2..69ffc836 100644 --- a/stlab/concurrency/actor.hpp +++ b/stlab/concurrency/actor.hpp @@ -15,6 +15,8 @@ #include // stlab +#include +#include #include #include @@ -108,17 +110,21 @@ struct actor_instance_base { //------------------------------------------------------------------------------------------------------------------------------------------ inline actor_instance_base*& this_actor_accessor() { - static thread_local actor_instance_base* this_actor{nullptr}; + thread_local actor_instance_base* this_actor{nullptr}; // `thread_local` implies `static` return this_actor; } struct temp_this_actor { - temp_this_actor(actor_instance_base* actor) { + // Actors can "nest" if the inner one is running on an immediate executor. + temp_this_actor(actor_instance_base* actor) : _old_actor(this_actor_accessor()) { assert(actor); this_actor_accessor() = actor; } - ~temp_this_actor() { this_actor_accessor() = nullptr; } + ~temp_this_actor() { this_actor_accessor() = _old_actor; } + +private: + actor_instance_base* _old_actor{nullptr}; }; //------------------------------------------------------------------------------------------------------------------------------------------ @@ -126,26 +132,29 @@ struct temp_this_actor { template struct actor_instance : public actor_instance_base, std::enable_shared_from_this> { + /// Value which is owned by the instance. If `T` is `void`, nothing will be instantiated. + using value_type = T; + template actor_instance(Executor e, std::string name) : _q(std::move(e)), _name(std::move(name)) {} template void initialize(Args&&... args) { - // We want to construct the object instance in the executor where it will be running. We - // cannot initialize in the constructor because `shared_from_this` will throw - // `bad_weak_ptr`. - // - // Unfortunately we cannot use schedule() here as it would dereference the `std::optional` - // when it doesn't contain a value, and that's UB... - stlab::async( - executor(), - [_weak = this->weak_from_this()](auto&&... args) { - if (auto self = _weak.lock()) { - self->_instance._x = T(std::forward(args)...); - } - }, - std::forward(args)...) - .detach(); + if constexpr (std::is_same_v) { + _hold = stlab::make_ready_future(executor()); + } else { + // We want to construct the object instance in the executor where it will be running. We + // cannot initialize in the constructor because `shared_from_this` will throw + // `bad_weak_ptr`. + // + // Unfortunately we cannot use schedule() here as it would dereference the `std::optional` + // when it doesn't contain a value, and that's UB... + _hold = stlab::async( + executor(), + [_this = this->shared_from_this()](auto&&... args) { + _this->_instance._x = T(std::forward(args)...); + }, std::forward(args)...); + } } auto set_name(std::string&& name) { _name = std::move(name); } @@ -156,7 +165,7 @@ struct actor_instance : public actor_instance_base, #ifndef NDEBUG , _id = this->id() #endif // NDEBUG - ](auto&&... args) mutable { + ](auto&&... args) { // tasks that are "entasked" for an actor must be executed on that same actor, or // Bad Things could happen, namely, a data race between this task and any other // task(s) the original actor may run simultaneously. @@ -166,34 +175,52 @@ struct actor_instance : public actor_instance_base, assert(_id == stlab::this_actor::get_id()); if constexpr (std::is_same_v) { - return std::move(_f)(std::forward(args)...); + return _f(std::forward(args)...); } else { - return std::move(_f)(const_cast(*(_this->_instance._x)), - std::forward(args)...); + return _f(*(_this->_instance._x), + std::forward(args)...); } }; } template auto operator()(F&& f) { - return stlab::async(executor(), entask(std::forward(f))); + auto future = _hold.then(executor(), entask(std::forward(f))); + using result_type = typename decltype(future)::result_type; + + // ERROR: These assignments to `_hold` are not threadsafe, are they? + if constexpr (std::is_same_v) { + _hold = future; + } else { + _hold = future.then([](auto){}); + } + + return future; + } + + template + auto enqueue(F&& f) { + (void)(*this)(std::forward(f)); + } + + void complete() { + stlab::await(_hold); } auto executor() { return [_this = this->shared_from_this()](auto&& task) { - _this - ->_q([_t = std::forward(task), _this = _this]() mutable { - temp_this_actor tta(_this.get()); - temp_thread_name ttn(_this->_name.c_str()); - std::move(_t)(); - }) - .detach(); + _this->_q.executor()([_t = std::forward(task), _this = _this]() mutable { + temp_this_actor tta(_this.get()); + temp_thread_name ttn(_this->_name.c_str()); + std::move(_t)(); + }); }; } value_instance _instance; stlab::serial_queue_t _q; std::string _name; + stlab::future _hold; }; //------------------------------------------------------------------------------------------------------------------------------------------ @@ -206,7 +233,8 @@ struct actor_instance : public actor_instance_base, /// @brief Serialized, asynchronous access to a resource template class actor { - std::shared_ptr> _impl; + using instance = detail::actor_instance; + std::shared_ptr _impl; /// friend declaration to the free function. template @@ -215,7 +243,7 @@ class actor { public: /// Value type for the class. `actor` will own this instance. If the `T` is `void`, nothing /// will be instantiated. - using value_type = T; + using value_type = typename instance::value_type; actor() = default; @@ -227,9 +255,7 @@ class actor { template actor(Executor e, std::string name, Args&&... args) : _impl(std::make_shared>(std::move(e), std::move(name))) { - if constexpr (!std::is_same_v) { - _impl->initialize(std::forward(args)...); - } + _impl->initialize(std::forward(args)...); } /// @brief Sets the name of the actor to something else. @@ -254,6 +280,19 @@ class actor { return (*_impl)(std::forward(f)); } + /// @brief "Fire and forget" a task on the actor. + /// @param f The task to run. Note that the first parameter to this task must be `T&`, and + /// will reference the instance owned by the actor. + template + void enqueue(F&& f) { + _impl->enqueue(std::forward(f)); + } + + /// @brief Block until all scheduled tasks have completed. + void complete() { + _impl->complete(); + } + /// @brief Get the unique `actor_id` of this actor. auto get_id() const { return _impl->id(); } diff --git a/test/actor_tests.cpp b/test/actor_tests.cpp index 83a2709a..7352dc42 100644 --- a/test/actor_tests.cpp +++ b/test/actor_tests.cpp @@ -20,11 +20,15 @@ void increment_by(int& i, int amount) { i += amount; } -void increment(int& i) { increment_by(i, 1); } +void increment(int& i) { + increment_by(i, 1); +} template T get_actor_value(stlab::actor& a) { - return stlab::await(a([](auto x) { return x; })); + return stlab::await(a([](auto x) { + return x; + })); } std::string current_test_name() { @@ -35,11 +39,16 @@ std::string current_test_name() { BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { stlab::actor a(stlab::default_executor, current_test_name(), 42); - stlab::future f = a([](auto i) { BOOST_REQUIRE(i == 42); }); + bool sent{false}; + stlab::future f = a([&](auto i) { + sent = true; + BOOST_REQUIRE(i == 42); + }); stlab::await(f); BOOST_REQUIRE(get_actor_value(a) == 42); + BOOST_REQUIRE(sent); } /**************************************************************************************************/ @@ -47,9 +56,10 @@ BOOST_AUTO_TEST_CASE(actor_construct_with_arguments) { BOOST_AUTO_TEST_CASE(actor_construct_void) { stlab::actor a(stlab::default_executor, current_test_name()); bool sent{false}; - stlab::future f = a([&]() { sent = true; }); - stlab::await(f); + a.enqueue([&]() { sent = true; }); + + a.complete(); BOOST_REQUIRE(sent); } @@ -60,23 +70,23 @@ BOOST_AUTO_TEST_CASE(actor_regularity) { stlab::actor empty_ctor; stlab::actor default_ctor(stlab::default_executor, current_test_name()); // default construction - default_ctor(increment).detach(); + default_ctor.enqueue(increment); BOOST_REQUIRE(get_actor_value(default_ctor) == 1); stlab::actor copy_ctor(default_ctor); // copy construction - copy_ctor(increment).detach(); + copy_ctor.enqueue(increment); BOOST_REQUIRE(get_actor_value(copy_ctor) == 2); stlab::actor move_ctor(std::move(default_ctor)); // move construction - move_ctor(increment).detach(); + move_ctor.enqueue(increment); BOOST_REQUIRE(get_actor_value(move_ctor) == 3); stlab::actor copy_assign = copy_ctor; // copy assignment - copy_assign(increment).detach(); + copy_assign.enqueue(increment); BOOST_REQUIRE(get_actor_value(copy_assign) == 4); stlab::actor move_assign = std::move(move_ctor); // move assignment - move_assign(increment).detach(); + move_assign.enqueue(increment); BOOST_REQUIRE(get_actor_value(move_assign) == 5); // equality comparable @@ -133,6 +143,7 @@ BOOST_AUTO_TEST_CASE(actor_schedule_to_void) { stlab::await(f); BOOST_REQUIRE(f.get_try()); + BOOST_REQUIRE(get_actor_value(a) == 1); } { @@ -152,17 +163,15 @@ BOOST_AUTO_TEST_CASE(actor_schedule_to_value) { { stlab::actor a(stlab::default_executor, current_test_name(), 42); stlab::future f = a([](auto x) { return x; }); - int result = stlab::await(f); - BOOST_REQUIRE(result == 42); + BOOST_REQUIRE(stlab::await(f) == 42); } { stlab::actor a(stlab::default_executor, current_test_name()); stlab::future f = a([]() { return 42; }); - int result = stlab::await(f); - BOOST_REQUIRE(result == 42); + BOOST_REQUIRE(stlab::await(f) == 42); } } @@ -174,9 +183,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { stlab::future f = a([](int& x) { x += 42; }).then(a.executor(), a.entask([](int x) { return x; })); - int result = stlab::await(f); - - BOOST_REQUIRE(result == 42); + BOOST_REQUIRE(stlab::await(f) == 42); } { @@ -185,9 +192,7 @@ BOOST_AUTO_TEST_CASE(actor_then_from_void) { .then(a.executor(), a.entask([](auto x) { return 4200 + x; })) .then(a.executor(), a.entask([](auto x) { return x + 420000; })); - int result = stlab::await(f); - - BOOST_REQUIRE(result == 424242); + BOOST_REQUIRE(stlab::await(f) == 424242); } } @@ -202,9 +207,47 @@ BOOST_AUTO_TEST_CASE(actor_then_from_value) { return x + y; })); - int result = stlab::await(f); + BOOST_REQUIRE(stlab::await(f) == 84); +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_enqueue) { + std::size_t count{0}; + stlab::actor a(stlab::default_executor, current_test_name()); + + a.enqueue([&](int){ + ++count; + }); + + a.enqueue([&](int){ + ++count; + return 42; // does nothing, really. + }); + + a.complete(); + + BOOST_REQUIRE(count == 2); +} + +/**************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(actor_enqueue_void) { + std::size_t count{0}; + stlab::actor a(stlab::default_executor, current_test_name()); + + a.enqueue([&](){ + ++count; + }); - BOOST_REQUIRE(result == 84); + a.enqueue([&](){ + ++count; + return 42; // does nothing, really. + }); + + a.complete(); + + BOOST_REQUIRE(count == 2); } /**************************************************************************************************/ From 8ba89743511a9246fb07045e7ea65e7f525e1493 Mon Sep 17 00:00:00 2001 From: Foster Brereton Date: Fri, 28 Jul 2023 15:19:22 -0700 Subject: [PATCH 16/16] removing docs for now --- .../actor.hpp/actor3CT3E/f_operator213D.md | 23 ----- .../actor.hpp/actor3CT3E/f_operator3D3D.md | 23 ----- .../concurrency/actor.hpp/actor3CT3E/index.md | 85 ------------------- .../actor.hpp/actor3CT3E/m_actor3CT3E.md | 31 ------- .../actor.hpp/actor3CT3E/m_send.md | 21 ----- .../actor.hpp/actor3CT3E/m_set_name.md | 18 ---- .../actor.hpp/actor3CT3E/m_then.md | 24 ------ docs/libraries/concurrency/actor.hpp/index.md | 9 -- 8 files changed, 234 deletions(-) delete mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator213D.md delete mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator3D3D.md delete mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/index.md delete mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/m_actor3CT3E.md delete mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/m_send.md delete mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/m_set_name.md delete mode 100644 docs/libraries/concurrency/actor.hpp/actor3CT3E/m_then.md delete mode 100644 docs/libraries/concurrency/actor.hpp/index.md diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator213D.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator213D.md deleted file mode 100644 index 33570063..00000000 --- a/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator213D.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -layout: function -title: operator!= -owner: fbrereto -brief: Inequality operator -tags: - - function -defined_in_file: concurrency/actor.hpp -overloads: - bool operator!=(const actor &, const actor &): - arguments: - - description: __OPTIONAL__ - name: x - type: const actor & - - description: __OPTIONAL__ - name: y - type: const actor & - description: __OPTIONAL__ - return: __OPTIONAL__ - signature_with_names: bool operator!=(const actor & x, const actor & y) -namespace: - - stlab ---- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator3D3D.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator3D3D.md deleted file mode 100644 index 4872fabc..00000000 --- a/docs/libraries/concurrency/actor.hpp/actor3CT3E/f_operator3D3D.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -layout: function -title: operator== -owner: fbrereto -brief: Equality operator -tags: - - function -defined_in_file: concurrency/actor.hpp -overloads: - bool operator==(const actor &, const actor &): - arguments: - - description: __OPTIONAL__ - name: x - type: const actor & - - description: __OPTIONAL__ - name: y - type: const actor & - description: __OPTIONAL__ - return: __OPTIONAL__ - signature_with_names: bool operator==(const actor & x, const actor & y) -namespace: - - stlab ---- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/index.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/index.md deleted file mode 100644 index cdb81c04..00000000 --- a/docs/libraries/concurrency/actor.hpp/actor3CT3E/index.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -layout: class -title: actor -owner: fbrereto -brief: Serialized, asynchronous access to a resource -tags: - - class -defined_in_file: concurrency/actor.hpp -declaration: "template \nclass stlab::actor;" -dtor: unspecified -fields: - _impl: - annotation: - - private - description: pimpl implementation instance - type: std::shared_ptr> -namespace: - - stlab ---- - -`actor` provides asynchronous, serialized access to an instance of `T`, running on an execution context of choice. Instead of a traditional message-passing actor model implementation, `actor` is given work by way of lambdas, whose results are then optionally extracted by the caller via a `stlab::future`. - -`actor` is a lightweight alternative to a dedicated thread managing some background service for a host application. The problem with background threads is that they consume considerable resources even when they are idle. Furthermore, many background services don't need the "always on" characteristics of a thread, and would be comfortable running only when necessary. - -However, `actor` is not a panacea. There are several caveats to keep in mind: - -1. `thread_local` variables may not retain state from task to task. Given the implementation details of the actor's executor (e.g., it may be scheduled on any number of threads in a thread pool), an actor may jump from thread to thread. Since `thread_local` variables have a per-thread affinity by definition, the variable values may change unexpectedly. -2. The thread cache penalty paid when an actor changes threads may not be suitable for high-performance/low-latency requirements. There is a cost associated with an actor jumping from one thread to another, and as in the previous case, this may happen depending on the implementation of the executor. If this cache penalty is too expensive for your use case, a dedicated worker thread may be a better fit. -3. The tasks given to an actor should not block. If the actor must wait for external input (mouse events, network/file IO, etc.) it should be fed in from outside the actor. Because the context of execution is not "owned" by the actor, it cannot presume to block the context waiting for something else to happen, or else it risks hanging (e.g., an unresponsive main thread) or deadlocking (e.g., waiting for a task that cannot complete until this task completes.) - -## Example - -Say we have a service, `type_rasterizer`, that we'd like to put on a background thread: - -```c++ -class image { - //... -}; - -struct type_rasterizer { - void set_text(std::string&& text); - - image rasterize(); - - // ... -}; -``` - -In our application, then, we will create an actor that manages an instance of this engine. By giving it the `default_executor`, the actor will run on a thread of the OS-provided thread pool (e.g., GCD on macOS/iOS). - -```c++ -struct my_application { - stlab::actor _rasterizer(stlab::default_executor, - "app text rasterizer"); - - // ... -}; -``` - -Then as your application is running, you can send "messages" in the form of lambdas to this actor to perform serialized, asynchronous operations. Note the first parameter of the lambda is the `type_rasterizer` itself: - -```c++ -void my_application::do_rasterize(std::string&& text) { - _rasterizer.send([_text = std::move(text)](type_rasterizer& rasterizer) mutable { - // This lambda will execute on the `default_executor`. Note that while in this - // lambda, the name of the thread will be the name of the actor. In this case, - // "app text rasterizer". - rasterizer.set_text(std::move(_text)); - return rasterizer.rasterize(); - }).then(stlab::main_executor, [](image my_rasterized_text){ - draw_image_to_screen(my_rasterized_text); - }).detach(); -} -``` - -You could also pass the argument to the lambda itself: - -```c++ -_rasterizer.send([](type_rasterizer& rasterizer, std::string text) { - rasterizer.set_text(std::move(text)); - return rasterizer.rasterize(); -}, std::move(text)); -``` - -Note that the actor is not always running. That is, no threads are blocked on behalf of the actor while it waits for tasks to come in. Rather, the actor only schedules itself to run on its executor when it has work to do. Once the work is completed, the actor relinquishes the thread it is running on back to the executor. In this way, actors are considerably less resource-intensive than a dedicated worker thread to some background service. diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_actor3CT3E.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_actor3CT3E.md deleted file mode 100644 index 81464e53..00000000 --- a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_actor3CT3E.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: method -title: actor -owner: fbrereto -brief: Constructor -tags: - - method -defined_in_file: concurrency/actor.hpp -is_ctor: true -overloads: - actor(): - annotation: - - default - description: default constructor - return: __OPTIONAL__ - signature_with_names: actor() - "template \nactor(Executor &&, std::string &&, Args &&...)": - arguments: - - description: An executor upon which this actor will run when it has tasks. - name: e - type: Executor && - - description: Runtime name of the actor. While the actor is running, its thread will be temporarily given this name. This name can be reconfigured with a call to `set_name`. - name: name - type: std::string && - - description: Initialization arguments for the instance of `T` the actor holds - name: args - type: Args &&... - description: executor-based constructor - return: __OPTIONAL__ - signature_with_names: "template \nactor(Executor && e, std::string && name, Args &&... args)" ---- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_send.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_send.md deleted file mode 100644 index d275a7dd..00000000 --- a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_send.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -layout: method -title: send -owner: fbrereto -brief: Send tasks for the actor to execute -tags: - - method -defined_in_file: concurrency/actor.hpp -overloads: - "template \nauto send(F &&, Args &&...)": - arguments: - - description: __OPTIONAL__ - name: f - type: F && - - description: __OPTIONAL__ - name: args - type: Args &&... - description: __OPTIONAL__ - return: __OPTIONAL__ - signature_with_names: "template \nauto send(F && f, Args &&... args)" ---- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_set_name.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_set_name.md deleted file mode 100644 index e7394507..00000000 --- a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_set_name.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -layout: method -title: set_name -owner: fbrereto -brief: Set the name of the actor -tags: - - method -defined_in_file: concurrency/actor.hpp -overloads: - auto set_name(std::string &&): - arguments: - - description: __OPTIONAL__ - name: name - type: std::string && - description: __OPTIONAL__ - return: __OPTIONAL__ - signature_with_names: auto set_name(std::string && name) ---- diff --git a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_then.md b/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_then.md deleted file mode 100644 index 687f6997..00000000 --- a/docs/libraries/concurrency/actor.hpp/actor3CT3E/m_then.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -layout: method -title: then -owner: fbrereto -brief: Continue tasks for the actor to execute pending the completion of some future -tags: - - method -defined_in_file: concurrency/actor.hpp -overloads: - "template \nauto then(stlab::future &&, F &&, Args &&...)": - arguments: - - description: The future to contine - name: future - type: stlab::future && - - description: The task to run. The first argument passed to the routine will be the actor. The second argument passed will be the resolved value of the future (if it is not `void`). - name: f - type: F && - - description: Additional arguments to pass to `f` when it is run - name: args - type: Args &&... - description: __OPTIONAL__ - return: __OPTIONAL__ - signature_with_names: "template \nauto then(stlab::future && future, F && f, Args &&... args)" ---- diff --git a/docs/libraries/concurrency/actor.hpp/index.md b/docs/libraries/concurrency/actor.hpp/index.md deleted file mode 100644 index 5781f802..00000000 --- a/docs/libraries/concurrency/actor.hpp/index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: library -title: stlab/actor.hpp -owner: fbrereto -brief: Header file for the `actor` -tags: - - sourcefile -library-type: sourcefile ----