Any way to make borrowed data `'static`

Backgroud

Assume we have a batch of transactions and the state view,which are immutable(&).Our target is to execute them parallelly,and the result of parallel execution should be consistent with executing them sequentially.

Current implementation

A recent academic paper have sloved above problem,whose implementation consists of a global scheduler module and a concurreny hashmap module.It use rayon as threadpool,each worker thread will continuously loop to process scheduler tasks came from scheduler.However,during the lifetime of scheduler task,it may block due to data dependency,thus waste cpu cycles.I want to dynamicly add/remove temp worker thread to use these cpu cycles.

Problem

I have tried to use scoped threads to implement my idea.
However,temp work thread also maybe block due to data dependency.
Theoretically, once data dependency has been resolve, blocked rayon thread can resume processing.
The Question is, if you have added temp worker thread before,you must wait until they joined(scoped thread's fork-join style),and unfortunately if the temp thread is also blocked because of data dependency,this unlucky rayon thread won't have chance to get scheduler task(waiting scoped thread join),which break the liveness guarantee of the paper's algorithm. This is why I have to use detached thread instead of scoped thread,I can inform detached thread to finish and don't need to wait it finished.
Some background can be found in this rayon issue.

Sorry for initial fuzzy problem statement,maybe this explaination can make everyone more clear about my problem.Thanks for everyone help me!

initial fuzzy description(useless)

In brief, my user case can be simplified to the following code demo.

#[derive(Clone)]
struct ParameterImpl;

trait Trait {
    type Parameter: Send + Clone;
    fn new(parameter: Self::Parameter) -> Self;
}

struct TraitImpl<'a> {
    p: &'a ParameterImpl,
}
impl<'a> Trait for TraitImpl<'a> {
    type Parameter = &'a ParameterImpl;

    fn new(p: Self::Parameter) -> Self {
        Self { p }
    }
}

struct Foo<T: Trait> {
    p: T::Parameter,
    t: T,
}
impl<T: Trait> Foo<T> {
    pub fn new(p: T::Parameter) -> Self {
        let t = T::new(p.clone());
        Self { p, t }
    }
}

struct Bar<T: Trait> {
    p: T::Parameter,
}
impl<T: Trait + 'static> Bar<T> {
    pub fn new(p: T::Parameter) -> Self {
        Self { p }
    }
    pub fn work(&self) {
        let parameter = self.p.clone();
        // I guarantee this thread will return before parameter dropped.(via some sync primitives)
        let _ = std::thread::spawn(move || {
            let _bar = Bar::<T>::new(parameter);
        });
    }
}
fn invoke(p: &ParameterImpl) {
    let bar = Bar::<TraitImpl>::new(p);
    bar.work();
}
fn main() {
    let p = ParameterImpl;
    invoke(&p);
}

Sadly, compiler tell me that it can't build for me because:

error[E0521]: borrowed data escapes outside of function
  --> src/main.rs:47:15
   |
46 | fn invoke(p: &ParameterImpl) {
   |           -  - let's call the lifetime of this reference `'1`
   |           |
   |           `p` is a reference that is only valid in the function body
47 |     let bar = Bar::<TraitImpl>::new(p);
   |               ^^^^^^^^^^^^^^^^^^^^^^^^
   |               |
   |               `p` escapes the function body here
   |               argument requires that `'1` must outlive `'static`

For more information about this error, try `rustc --explain E0521`.
error: could not compile `hello_rust` due to previous error

For some reason,I can't use scoped thread,which can borrow non-static data.
I think the trait bound T: Trait + 'static on Bar is not right.
How can I convince compiler that my code is right(the borrowed data can be safely use) , or any other way to impl this idea?

Scoped threads?

impl<T: Trait> Bar<T> {
    pub fn new(p: T::Parameter) -> Self {
        Self { p }
    }
    pub fn work(&self) {
        let parameter = self.p.clone();
        // I guarantee this thread will return before parameter dropped.(via some sync primitives)
        let _ = std::thread::scope(|s| {
            s.spawn(|| {
                let _bar = Bar::<T>::new(parameter);
            });
        });
    }
}
2 Likes

Sadly,I found that scoped threads will make my real workload deadlock(specifically,the fork-join style),so I have to use detached threads and other sync primitives instead of them.

That's literally what a scoped thread is, so if you cannot use scoped threads, you cannot make this guarantee either. Scoped threads are not a synchronization primitive.

Scoped threads are exactly the a solution to this problem (example), so if you have a different problem, you need to write an example that demonstrates that problem, not this one.

4 Likes

Scoped thread is just a thread that is guaranteed to terminate before the function returns. That's what you are asserting in your code. If you your code deadlocks, it is unrelated to scoped threads, your code is just incorrect. It may be the case that a scoped thread would be more or less likely to hit the bug, depending on the whims of implementation and OS executor, but it cannot cause the bug on its own.

Have you considered using rayon? I don't know what you are trying to do, but if it's simply distributing the workload between several worker thread, then rayon is likely the solution to use. It will handle the hard problems of thread coordination on its own.

3 Likes

I have edit the post,pls check it again. :grinning:

Hi,I have update the post,pls check it again.

It's hard to understand the issue. Regardless, the primary question to ask is "why do you want to use borrowed data to begin with?". The easiest solution to your problem is to use static data, i.e. Box or Arc your data and safely pass it into the thread. Use Arc if you want shared ownership between several threads or plan to clone a lot, use Box if single ownership is sufficient.

1 Like

Thanks for adding more context. At least now the advice of “how to process” is more-or-less clear.

About your primary question: what you are asking to do is not possible in safe Rust and for good reason: if you convert reference which exist for limited lifetime to reference which exist for 'static then you immediately break all safety guarantees the compiler uses to prove that your program is correct.

And sooner or later this would lead to predictable results. I mean primarily social consequences not even technical ones.

Now, it looks as if you can not use joinable threads but still have the other means to create scoped threads. Your own ones. And then, there, you may use that machinery that you designed in place of join.

You probably want to start with implementation of scoped thread in the standard library and go from there. How would you create your API which would uphold the soundness pledge is interesting question, but in the unsafe land your question becomes trivial: just read any book which talks about that topic (e.g. Learn Rust With Entirely Too Many Linked Lists).

P.S Here we need the infamous picture which explains how you don't just turn borrowed data into 'static, but have to use dark magic and appease Miri… but I suspect your use-case actually justifies something like that.

Thanks for everyone!
I have changed above demo to use Arc.( Rust Playground (rust-lang.org))
It seems like the safest solution for my user case.
Specially thanks VorfeedCanal's advice about unsafe world.I know little about them.Currently I don't have much time to explore them,Hope above demo could make my idea make sense.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.