Is crossbeam epoch safe to be used across tokio tasks/awaits?

The current thread is pinned by calling pin, which returns a new guard:

What i understand from the above documentation is, you data will not be garbage collected as long as Guard is pinned to the current thread.

Let's consider the following situations

I have a struct Config and HttpRequest

struct Config{
   version: u32
   // really complex to represent here
}

struct HttpRequest{
   config: Arc<Config>
   // others
}

I have to process http request in multiple phases, for example

  1. Getting request header, body
  2. Talking with database
  3. Talking with file system if needed
  4. Talking with external system if needed
  5. Sending response

All phases of any request, must be processed with same config version. There shouldn’t be any request getting version x config in phase 1 and getting version x+y config in next phases.

Every phase will be scheduled on different tokio tasks(So on different threads possibly). Due to which, HttpRequest struct will be move across threads using move keyword in every next phase.

My question is, if i keep a reference of crossbeam epoch Guard in HttpRequest, will there be any guarantee that memory won’t be reclaimed until the guard is dropped. Considering HttpRequest will be moved across thread and cross beam guards are pinned the calling thread?

Considering that Guard: !Send + !Sync, it wouldn't even be possible for a Guard or &Guard to be sent between threads. Directly addressing your question:

No, it wouldn't be able to, because (&)Guard can't be (because it pins the calling thread).

Yes, the memory will be used correctly. But as said before, that will make HttpRequest impossible to send between threads.

To solve this, consider using tokio::task::LocalSet, or not using crossbeam_epoch at all.

1 Like

I'd expect the attempt would make the created future !Send and give you a somewhat confusing error.

1 Like

Any other data structures that can fit into this situation? checked arc-swap, but that also has similar limitations

I don't see what would be wrong with arc-swap, it actually seems right for your use case. When you initialize an HttpRequest, provide a &ArcSwap<Config>, then load_full the Arc<Config> and store it in HttpRequest. Don't try to use load, as the docs say: "this[1] is suited for local variables on stack, but not in long-living data structures[2]." (emphasis and footnotes mine)

Whenever you need to change the config, you can just do a store to change it for all future requests.


  1. ArcSwap::load ↩︎

  2. like HttpRequest ↩︎

ArcSwapAny in arc_swap - Rust

The method is lock-free and wait-free, but usually more expensive than load.

This was concerning for me, but will benchmark that how much slower it is. Thanks for suggestions