Search code examples
c++-actor-framework

Using requests to verify command success


Lets say Typed Actor A needs to command Typed Actor B to do something. Actor A also needs to know if the command ran succesfully or not but does not want to block operation until this response arrives. My current working theory is that this is best satisfied with Requests. More specifically request(...).then

There is a nice example called "request.cpp" that I have been playing with. My challenge is that I don't really need actor B to return any data. I just need to know if the command was successful or not and if not what error was thrown.

So my question is two fold: 1) Am I correct in thinking that request(...).then is the correct mechanism to do what I want and 2) if so then can a request handle a response that has no data?

This is what I'm trying:

#include <chrono>
#include <cstdint>
#include <iostream>
#include <vector>

#include "caf/all.hpp"

using std::endl;
using std::vector;
using std::chrono::seconds;
using namespace caf;

using cell
= typed_actor<result<void>(get_atom, int32_t)>;     

struct cell_state {
    static constexpr inline const char* name = "cell";
    cell::pointer self;

    cell_state(cell::pointer ptr) : self(ptr) {}

    cell_state(const cell_state&) = delete;
    cell_state& operator=(const cell_state&) = delete;
    cell::behavior_type make_behavior() {
        return {
            [=](get_atom, int32_t input) -> result<void> { 
                if (input != 5) {       // Simulate command successful or not
                    return;        // If successful, then return;
                }
                else {
                    return sec::unexpected_message;     // If not then return error.  
                }
            },
        };
    }
};

using cell_impl = cell::stateful_impl<cell_state>;

void multiplexed_testee(event_based_actor* self, vector<cell> cells) {
    for (cell& x : cells) {
        aout(self) << "cell #" << x.id() << " calling" << endl;
        self->request(x, seconds(1), get_atom_v, static_cast<int32_t>(x.id()))
            .then(
                [=](void) {
                    aout(self) << "cell #" << x.id() << " -> " << "success" << endl;
                },
                [=](error& err) {
                    aout(self) << "cell #" << x.id() << " -> " << to_string(err) << endl;
                });
    }
}

void caf_main(actor_system& system) {
    vector<cell> cells;
    for (int32_t i = 0; i < 5; ++i)
        cells.emplace_back(system.spawn<cell_impl>());

    scoped_actor self{ system };
    auto x2 = self->spawn(multiplexed_testee, cells);
    self->wait_for(x2);
}

CAF_MAIN()

When I compile, I get an error on the empty return statement saying "return-statement with no value, in function returning caf::result<void>. Is there a better way to do this?

My backup plan is to change my command definition to just return a standard error and return sec::none if the operation was successful. But I'm afraid that approach violates the spirit of the whole optional-second-parameter for error conditions. How well am I thinking about all this?


Solution

  • Is there a better way to do this?

    You had the right idea. The result<void> expects either an error or a 'void' value. Since void{} isn't a thing in C++, you can do return caf::unit; to tell result<void> to construct an "empty" result. On the receiver's side, you already did the right thing: then with a lambda taking no arguments.

    Minor point:

    [=](void) { ... }
    

    This is a C-ism from the early days where the compiler allowed you to do silly things. Just drop the void, it serves no purpose. :)