Strange behavior of condition variable

Guys, I have difficulty in coordinating two threads by using condition variable.
In the example below first thread that is a thread which has the purpose of copying file is supposed to signal with the use of condition variable that it is spawned and it will proceed to copy a file.
There is a thread working for that signal using the same condition variable but unfortunately it seems to me that the copying of a file blocks (strange, it should only block the thread that call is in) until the copying is finished and only after that the signal is received by the waiting thread. Can somebody can look into that and explain to me what I'm doing incorrectly?
Thank you

fn waiting_for_thread_to_start(selected_item: &str, full_dest_path: &str) {
    let selected_item_clone = String::from(selected_item);
    let full_dest_path_clone = String::from(full_dest_path);
    let full_dest_path_clone_2 = String::from(full_dest_path);
    let (tx, rx) = std::sync::mpsc::sync_channel(1);
    use std::sync::{Arc, Condvar, Mutex};
    use std::thread;

    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pair2 = Arc::clone(&pair);

    // Inside of our lock, spawn a new thread, and then wait for it to start.
    thread::spawn(move || {
        let (lock, cvar) = &*pair2;
        let mut started = lock.lock().unwrap();
        *started = true;
        // We notify the condvar that the value has changed.
        cvar.notify_one();
        match copy_file(&selected_item_clone, &full_dest_path_clone) {
            Ok(_) => {
                eprintln!("Copied");
                tx.send(true);
                return;
            }
            Err(e) => {
                eprintln!("couldn't copy: {e}");
                tx.send(true);
                return;
            }
        }
    });

    // Wait for the thread to start up.
    let (lock, cvar) = &*pair;
    let mut started = lock.lock().unwrap();
    while !*started {
        started = cvar.wait(started).unwrap();
    }
    println!("Copying thread started (But here we arrive only after copying has been finished). Proceeding to spawn watch thread.");
    let _handle_read = std::thread::spawn(move || {
        loop {
            match rx.try_recv() {
                Ok(res) => {
                    if res {
                        eprintln!("Received end of copying msg");
                        break;
                    }
                }
                Err(e) => {
                    eprintln!("Receiving error: {}", e);
                }
            }
            let full_dest_path_clone_2_clone = full_dest_path_clone_2.clone();
            match std::fs::File::open(full_dest_path_clone_2_clone) {
                Ok(f) => {
                    let len = f.metadata().unwrap().len();
                    eprintln!("opened, len: {len}");
                }
                Err(e) => {
                    eprintln!("couldn't open: {e}");
                }
            }

            std::thread::sleep(std::time::Duration::from_millis(250));
        }
    });
}

I'm not sure, but maybe you need to release the lock?

         let (lock, cvar) = &*pair2;
         let mut started = lock.lock().unwrap();
         *started = true;
         // We notify the condvar that the value has changed.
         cvar.notify_one();
+        // Release the lock:
+        drop(started);
         match copy_file(&selected_item_clone, &full_dest_path_clone) {
             /* … */
         }

And I guess you should also release it here:

     // Wait for the thread to start up.
     let (lock, cvar) = &*pair;
     let mut started = lock.lock().unwrap();
     while !*started {
         started = cvar.wait(started).unwrap();
     }
+    drop(started);

I don't think so. The example (with regards to condition variable workings) is verbatim from official example from rust docs.
I don't think that is how you work with condition variables really.

Do you have a link? Maybe the drop happens implicitly in the official example due to a block that ends?

If you are asking about link to the example on ConditionVar, simply go to the definition of it and it will be there. Like type CondVar in IDE and then go to definition of it.

If you look at that example, then you will see the MutexGuard (started) gets dropped right after cvar.notify_one() is invoked:

// Inside of our lock, spawn a new thread, and then wait for it to start.
thread::spawn(move|| {
    let (lock, cvar) = &*pair2;
    let mut started = lock.lock().unwrap();
    *started = true;
    // We notify the condvar that the value has changed.
    cvar.notify_one();
});

If you add anything after cvar.notify_one(), then you should likely drop the guard manually:

// Inside of our lock, spawn a new thread, and then wait for it to start.
thread::spawn(move|| {
    let (lock, cvar) = &*pair2;
    let mut started = lock.lock().unwrap();
    *started = true;
    // We notify the condvar that the value has changed.
    cvar.notify_one();
    // Drop the guard:
    drop(started);
    // Do some more work:
    /* … */
});

Or you could wrap the critical part in another block:

// Inside of our lock, spawn a new thread, and then wait for it to start.
thread::spawn(move|| {
    {
        let (lock, cvar) = &*pair2;
        let mut started = lock.lock().unwrap();
        *started = true;
        // We notify the condvar that the value has changed.
        cvar.notify_one();
    }
    // Do some more work:
    /* … */
});

Hey, I think you maybe on something here!

I want to thank you for your help. I am surprised at the way one has to manually drop it. It is completely against the Rust spirit. I'm going to start new topic and perhaps spark some discussion about it.
Thanks again for your help.

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.