The Breaking Cycles Manually section of the User Manual talks about actors having references to each other and how to avoid potential pitfalls in those scenarios. My question is how do you create a cycle to begin with? I frequently pass handles created by one spawn function into the parameters of another spawn function, but I'm struggling to figure out how to give two actors each other's handles:
#include<chrono>
#include<iostream>
#include <vector>
#include <string>
#include "caf/typed_event_based_actor.hpp"
#include "caf/scoped_actor.hpp"
#include "caf/caf_main.hpp"
#include "CustomMessages.h"
#include "../DuckParty/Displayable.h"
#include "../DuckParty/Duck.h"
#include "../DuckLibrary/Mallard.h"
#include "../DuckLibrary/TerminalDisplayer.h"
using namespace std::chrono;
using namespace std;
using DisplayActor = caf::typed_actor<
caf::result<void>(display_behavior, time_point<system_clock>, string)>;
using DuckActor = caf::typed_actor<
caf::result<void>(do_duck_behavior)>;
class DisplayState {
private:
unique_ptr<Displayable> displayable_;
public:
explicit DisplayState(unique_ptr<Displayable> displayable) : displayable_(move(displayable)) {}
DisplayActor::behavior_type make_behavior() {
return {
[this](display_behavior, time_point<system_clock> quack_time, string behavior) {
displayable_->DisplayBehavior(quack_time, behavior);
}
};
}
};
using DisplayImpl = DisplayActor::stateful_impl<DisplayState>;
class DuckState {
private:
DuckActor::pointer self_;
unique_ptr<Duck> duck_;
int milliseconds_;
DisplayActor display_actor_;
public:
explicit DuckState(DuckActor::pointer self, unique_ptr<Duck> duck, int milliseconds, DisplayActor display_actor) :
self_(self),
duck_(move(duck)),
milliseconds_(milliseconds),
display_actor_(display_actor) {}
DuckActor::behavior_type make_behavior() {
self_->send(self_, do_duck_behavior_v);
return {
[this](do_duck_behavior) {
self_->delayed_send(self_, std::chrono::milliseconds(milliseconds_), do_duck_behavior_v);
time_point<system_clock> quackTime = system_clock::now();
self_->send(display_actor_, display_behavior_v, quackTime, duck_->GetFlyBehavior() + " " + duck_->GetNoiseBehavior());
}
};
}
};
using DuckImpl = DuckActor::stateful_impl<DuckState>;
void caf_main(caf::actor_system& sys) {
unique_ptr<Duck> duck = make_unique<Mallard>("Howard the Duck");
unique_ptr<Displayable> display = make_unique<TerminalDisplayer>();
DisplayActor display_actor = sys.spawn<DisplayImpl>(move(display)); // How to give this actor a strong static reference to duck_actor?
DuckActor duck_actor = sys.spawn<DuckImpl>(move(duck), 500, display_actor);
}
CAF_MAIN(caf::id_block::duck_msg_types)
You can see in my main
function, that I can easily give DuckActor a handle to DisplayActor but how do I also give DisplayActor a handle to DuckActor? Do you have any examples or advice regarding how to create a reference cycle? I'm afraid I'm missing something obvious.
Do you have any examples or advice regarding how to create a reference cycle?
The general advise is to not create them. ;)
You are already structuring your application with state classes, which is the recommended way to avoid the problem. To be clear, two actors having handles to each other is not an issue in itself and happens all the time. Messages hold a reference to the sender, which usually leads to two actors now holding references to each other. The cycles the manual is warning against are permanent cycles that persist even after an actor terminates.
If you are using stateful actors, there are no permanent cycles. The state gets destroyed when terminating, e.g., due to calling self->quit()
. However, the actor object itself cannot be destroyed at that point. Actors are reference counted, hence the underlying object lives on until no more reference to it remains. If two actors hold a reference to each other via member variables, you have a memory leak. You can only run into this problem if you implement an actor by deriving from one of the actor types directly, e.g., by inheriting from event_based_actor
. The CAF manual and other resources always advocate not inheriting from actor types but if you do (and you don't since you follow the best practice of using state classes), you also need to worry about potential cycles.
As to how actors can end up refererring to each other: there are a couple of ways. Messages point back to the sender, so by storing the sender information in some way you have created a cycle if the sender also still holds a reference to the receiver. You can of course also include an actor handle in a message and then store it this way. In your example above, you start both actors in main. However, in a server-worker relation, you often have the server start the worker. The server can pass a handle to itself to the worker if for some reason the workers need to know their parent. So in your case, you could have the display actor spawn the duck and then store the duck actor handle.
Just to re-iterate, there's no need to think about cycles in your application unless you implement actors by inheriting from one the CAF actor types directly and you store references to other actors in member variables. As long as you don't do that, you can safely skip the manual section that talks about breaking cycles.