c++c++-actor-framework

How can a class-based actor's state be accessed from a blocking context (C++ Actor Framework)


I'd like to access a class-based actor's state from a blocking context by exposing the current state via regular class methods. What's the best approach? I can think of three main avenues:

  1. Class-based actors should not expose their state except by message passing (i.e. I'm doing something wrong to begin with)
  2. The class method(s) should use a scoped_actor to send a message to the class-based actor and return the response.
  3. The class method(s) should bypass the actor framework and simply access the member variable holding the state.

I attempted approach #2, but while I did figure out how to add the 'mutable' keyword to modify a captured variable, I was unable to successfully delay the method return until the captured variable could be set without creating deadlock.

Approach #3 seems to work for the example I've included below. Both guarded and unguarded access seemed to work, so I'm not sure if access to the state needs to be protected.

using size_atom = atom_constant<atom("size")>;
using done_atom = atom_constant<atom("done")>;
using a_type = typed_actor<
    replies_to<int>::with<void>,
    replies_to<size_atom>::with<size_t>,
    replies_to<done_atom>::with<void>>;

class A : public a_type::base
{
public:
    size_t size_direct_access()
    {
        return this->m_values.size();
    }

    size_t size_protected_access()
    {
        lock_guard<mutex> lock(this->m_mutex);
        return this->m_values.size();
    }
protected:
    behavior_type make_behavior() override
    {
        return
        {
            [this](int value)
            {
                lock_guard<mutex> lock(this->m_mutex);
                this->m_values.push_back(value);
            },
            [this](size_atom) -> size_t
            {
                lock_guard<mutex> lock(this->m_mutex);
                return this->m_values.size();
            },
            [this](done_atom)
            {
                this->quit();
            }
        };
    }
private:
    vector<int> m_values;
    mutex m_mutex;
};

void tester()
{
    a_type testeeActor = spawn_typed<A, detached>();
    abstract_actor* abstractActor = actor_cast<abstract_actor*, a_type>(testeeActor);
    A* a = dynamic_cast<A*>(abstractActor);
    scoped_actor self;
    self->sync_send(testeeActor, 5).await(
        [a, testeeActor, &self]()
        {
            size_t direct_access = a->size_direct_access();
            size_t protected_access = a->size_protected_access();
            aout(self) << "Direct: " << direct_access << " Protected: " << protected_access << endl;

            self->sync_send(testeeActor, 3).await(
                [a, testeeActor, &self]()
            {
                size_t direct_access = a->size_direct_access();
                size_t protected_access = a->size_protected_access();
                aout(self) << "Direct: " << direct_access << " Protected: " << protected_access << endl;

                self->sync_send(testeeActor, 1).await(
                    [a, testeeActor, &self]()
                {
                    size_t direct_access = a->size_direct_access();
                    size_t protected_access = a->size_protected_access();
                    aout(self) << "Direct: " << direct_access << " Protected: " << protected_access << endl;

                    self->sync_send(testeeActor, done_atom::value);
                });
            });
        });
}

int main()
{
    cout << "Spawning actors" << endl;
    tester();
    cout << "Awaiting all actors done" << endl;
    await_all_actors_done();
    cout << "Shutdown" << endl;
    shutdown();
    cout << "Press Enter to continue" << endl;
    cin.get();
}

Solution

  • You're answering your own question right in the beginning:

    Class-based actors should not expose their state except by message passing (i.e. I'm doing something wrong to begin with)

    Actors communicate only via message passing. You should not try to violate this principle. Otherwise all safety guarantees provided by the runtime are gone. If you want shared, mutable state, the actor model is the wrong primitive for you.

    That said, shared immutable state works fine in the actor model. You can pass a const-reference into the actor constructor to shared state with the outer scope, but then have to ensure that the reference remains valid for the lifetime of an actor. Another option would be to just use messages, which encapsulate the notion of shared, immutable data by providing a copy-on-write interface to its elements.

    Upleveling: if you find yourself in need of accessing data from an actor, you should probably revisit the design of your program.