How to reuse variable that cannot be borrowed inside loop expression?

Take the following as an example

#[tokio::main]
async fn main() {
    let url = url::Url::parse("wss://some-url").unwrap();
    let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");
    let (mut write, _) = ws_stream.split();

    // send heartbeat ping
    tokio::task::spawn_blocking(|| {
        loop {
            std::thread::sleep(std::time::Duration::from_secs(30));
            tokio::task::spawn(send_heartbeat(write));    // <---- I'm trying to solve this issue of reusing write in the loop
        }
    }).await.unwrap();
   ...

as you see above I would like to periodically send the heartbeat ping to websocker server every (let's say) 30 seconds. But write cannot be borrowed, only moved, but there is a complication that I cannot move it again in next iteration of loop.

The error is shown as follows

error[E0382]: use of moved value: `write`
...
value moved here, in previous iteration of loop

Although this relates to tokio stuff, but I believe it still relates to normal code circumstance as well.

What suggestions I would need to resolve this ? Thanks in advance.

I assume you want to write stuff over the channel besides just heartbeats. To do that, you're actually going to have to use channels to merge messages from your application code with the heartbeats. This kind of design is called an actor. You can read about them here.

Also, drop that spawn_blocking loop that just sleeps. That's silly. Just use a tokio::spawn and tokio::time.

Thanks, I will need to read that article first.

One thing, as I would like to have accurate interval wait time before executing something like sending heartbeat so having use tokio::task::spawn would mean it depends on mercy of how many async tasks the runtime has in which the runtime switches between task? so sleeping time would be incrementally counted only when that specific async task has its chance to run?

So from previous paragraph, I decided to use tokio::task::spawn_blocking to spawn a blocking dedicated task.

Am I understand it wrongly? If so I will study more about internals of tokio. Thanks again.

Well, here you're at the mercy of how your OS schedules its threads :woman_shrugging:

If you want the heartbeats to actually happen at a rate of two per minute regardless of how things are scheduled, you'll have to correct the duration to account for time lost due to other reasons such as the tokio::spawn call not being instant, or the OS scheduling you imperfectly. Luckily, Tokio has that built-in already — it's called tokio::time::interval and it should be used in tokio::spawn rather than spawn_blocking.

Oh, I skimmed it when first replying and missed this. No that's not how it works. The way it works is that it computes the instant at which the timer should complete, then it puts it into a data structure inside Tokio that contains all active timers. Then, whenever Tokio has nothing to do it sleeps for the duration of the shortest timer and wakes that particular task aftewards. (plus extra details in case it never has nothing to do and so on) Other times that are not active do not get polled at all until their timer completes.

1 Like

Thanks again for your all your explanation. I'll finish your article.

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.