hawkw / sharded-slab

a lock-free concurrent slab (experimental)
MIT License
269 stars 17 forks source link

Provide `Entry` that can cross the `.await` point #93

Closed loyd closed 6 months ago

loyd commented 7 months ago

Now Entry: !Send; thus, it cannot cross the .await point in multithreaded runtimes like tokio.

I don't see anything in the code that relies on these restrictions, so the main question here is whether Entry can be an instance of Send or not. If not, can you recommend something for the case below?

Use case

I use the sharded-slab as actor storage in elfo, and it has been working fine for years in production, so thanks for the crate. The next version will use cordyceps as part of the mailbox implementation, so thanks for that implementation, too.

In the simplest case, when some actor wants to send a message to another one with a known address and wants async backpressure, it gets a target actor by its address in the slab. In the simplified form, it looks like the following code:

async fn send_to<M: Message>(&self, recipient: usize, message: M) -> Result<(), SendError<M>> {
    let Some(actor) = self.slab.get_owned(recipient) else {
        return Err(SendError(message)
    };
    actor.mailbox.send(message).await
}

The problem is that get_owned clones Arc every time, creating a massive contention on the counter that kills performance on multicore (especially on NUMA) machines. The implementation for sync try_send_to (uses slab.get() inside) doesn't have a similar behavior.

I use get_owned here instead of get only because Entry cannot cross the .await point. Logically, the future cannot overlive the slab.

hawkw commented 7 months ago

I think the Entry type can probably be Send. Off the top of my head, I certainly can't think of any reason why it can't be.