elfo-rs / elfo

Your next actor system
181 stars 11 forks source link

Non-blocking requests #91

Open loyd opened 1 year ago

loyd commented 1 year ago

API for non-blocking requests.

Something like this:

#[message(ret = u32)]
struct SomeRequest(u32);

let request_map = RequestMap::new(); // `SecondaryMap` underhood
let request_id = ctx.request(SomeRequest(42)).id().await;

// Store additional information
request_map.insert(request_id, SomeInfo { name: "kek" });

while let Some(envelope) = ctx.recv().await {
    msg!(match envelope {
        SomeRequest::Response(request_id, ret) => {
            let info = request_map.take(request_id);
            info!(message = "got it", name = %info.name, ret = %ret);
        }
        SomeRequest::Error(err) => { .. }
    });
}

For the receiving part, nothing is changed.

planetoryd commented 10 months ago

It's possible to deadlock. Amiright

loyd commented 10 months ago

What kind of deadlock do you see here?

I'm always planning to handle responses unboundedly (so ctx.respond(token) is non-blocking already), so I don't see where deadlock can occur for now.

Compared to blocking requests (ctx.request(..).resolve().await), where there is a case with two actors requesting each other, the non-blocking variant has no such problem.

planetoryd commented 10 months ago

If one actor requests another actor (send message1, wait for response1), and the other actor requests back in the method, (send message2, wait for response2, send response1). It forms a wait-for cycle, message2 is never processed because the first actor is still in the process of requesting. I'm not sure how you implement it, but it's easy to happen.

loyd commented 10 months ago

Yep, it's a case for blocking requests (ctx.request(..).resolve().await). But in the case of non-blocking requests, all responses are handled using a mailbox in the typical main loop, so there is no point when both actors wait for each other, thus requests can overlap.