Neverlord on neverlord
Remove dead code Improve coverage on the concat … (compare)
Neverlord on master
Add 0.18.7 release to changelog (compare)
Neverlord on 0.18.7
Neverlord on 0.18
Change version to 0.18.7 (compare)
Neverlord on master
Add latest API additions to the… (compare)
Neverlord on 0.18
Enable CI via Cirrus Handle void results in fan_out_… (compare)
Neverlord on neverlord
Add test suite for flow::op::ce… Fix subscription and event hand… More op::buffer coverage; make … (compare)
Neverlord on master
Add flag for net module Merge pull request #1374 (compare)
Neverlord on add-net-module-flag
josephnoir on add-net-module-flag
Add flag for net module (compare)
--my-list
argument)
void foo(typed_actor parent) {
auto spawned = parent->spawn(new_actor);
parent->request(std::move(spawned), int{5}).then([]{});
}
is such function legal? It seems to me that the spawned actor may die and "then" will never be called. Is the lifetime somehow preserved here or are my worries justified?
server_impl
, which sound like a good approximation what you're trying to do?
IIUC @patszt's problem correctly then the problem actually occurs when you return the response handle from his example function, because the response handle does not keep a strong reference to the destination actor. We ran into this when upgrading from CAF 0.17 to CAF 0.18 (yes we're finally getting to it), and were wondering if this was a change in behavior. The behavior we see is that a .then(...)
on the response handle returned from this function was always replied to with a caf::sec::broken_promise
. The requested handler of the destination actor always created a response promise.
Do you know if it's feasible to backport the CAF 0.18 response promise to CAF 0.17 to make this part of the transition easier?
What is the "destination actor" in your case? Let's say we have a communication pattern like A -> B -> C. A sends a request to B. B sends another request to C, holding onto a response promise. C responds normally.
B has a response promise, which holds a strong_actor_ptr
back to A (https://github.com/actor-framework/actor-framework/blob/master/libcaf_core/caf/response_promise.hpp#L201). C has a message in its mailbox, so CAF should hold an implicit reference to it at least until it has responded. That's how I understand it. Where are my assumptions wrong?
broken_promise
from my understanding, because the message in C should keep B alive (first the request message in the mailbox of C itself and then of course the response message in B).
.then
on the response handle.
.then
.
Hm. Without code, I'm not quite sure how B is supposed to deliver its promise if it's not doing any messaging after that.
@dominiklohmann Different question. :)
Are you guys are using operator*
for actors anywhere outside of pre-0.19 streams? This 'pipelining operator' was added to make the "auto-magic" stream handshakes work, so I'm wondering whether there are any use cases left after pulling out the streams. Getting rid of this feature would go a long way in reducing complexity in the messaging layer again.
@dominiklohmann here's the modified example I got to run:
struct bar_state {
static constexpr const char* name = "bar";
typed_response_promise<int> rp = {};
};
CAF_TEST(broken_promise_repro) {
using namespace std::literals;
actor_system_config cfg;
put(cfg.content, "caf.logger.file.verbosity", "trace");
actor_system sys{cfg};
auto baz_fun = [](event_based_actor* self) -> behavior {
self->set_exit_handler([=](const exit_msg& msg) {
CAF_MESSAGE("6': " << msg.reason);
self->quit(msg.reason);
});
return {
[](int) {},
};
};
auto bar_fun = [](stateful_actor<bar_state>* self) -> behavior {
self->set_down_handler([=](const down_msg& msg) {
CAF_MESSAGE("6: " << msg.reason);
self->state.rp.deliver(42);
});
return {
[=](actor baz) -> typed_response_promise<int> {
CAF_MESSAGE("5");
self->monitor(baz);
self->state.rp = self->make_response_promise<int>();
return self->state.rp;
},
};
};
using foo_actor = typed_actor<result<int>(open_atom), result<void>(actor)>;
auto foo_fun = [=](foo_actor::pointer self,
actor baz) -> foo_actor::behavior_type {
return {
[=](open_atom) -> typed_response_promise<int> {
CAF_MESSAGE("2");
auto request_temporary_bar = [=](actor baz) {
CAF_MESSAGE("3");
auto bar = self->spawn(bar_fun);
return self->request(bar, infinite, baz);
};
self->delayed_send(self, 3s, baz);
auto rp = self->make_response_promise<int>();
request_temporary_bar(baz).then(
[rp](int x) mutable {
CAF_MESSAGE("7: " << x);
rp.deliver(x);
},
[rp](const error& err) mutable { rp.deliver(err); });
return rp;
},
[=](actor baz) {
CAF_MESSAGE("4");
self->send_exit(baz, exit_reason::normal);
},
};
};
CAF_MESSAGE("1");
auto baz_hdl = sys.spawn(baz_fun);
auto f = make_function_view(sys.spawn(foo_fun, std::move(baz_hdl)));
CAF_CHECK_EQUAL(f(open_atom_v), 42);
CAF_MESSAGE("8");
}
I do see the broken_promise
here, but I'm not sure whether there's something CAF could safely do. The bar
actor triggers no messaging. So after returning from the handler, there is nothing referencing the actor anymore and it gets cleaned up as unreachable. I get where you're coming from, though. The response promise could hold a strong reference to self
here to make this example work, but then the actor would hold onto a strong reference to itself. This could open the door for surprising leaks. Not sure what's the better tradeoff.