3

I am quite new to Rust and have some problems with async and lifetimes. I am trying to read a USB cam in a loop using rscam crate.

The problem is that after some time the camera driver tends to freeze and returns no data. Rscam's capture has no builtin timeout (at least the official release) - so I am trying to wrap it in tokio's timeout.

Because all runs in a loop I do not move the camera struct, but pass a reference. And here is my difficulty, as sending into future requires a static lifetime (which I cannot figure out how to do).

Or maybe there is a completely different solution to adding timeouts for blocking io?

    let mut camera = Camera::new(&settings.device).unwrap();
    camera.set_control(CID_JPEG_COMPRESSION_QUALITY, &95);
    
    camera.start(&Config {
        interval: (1, 30),
        resolution: (settings.width, settings.height),
        format: settings.format.as_bytes(),
        ..Default::default()
    }).unwrap();
    
    let cam_ref = &camera;
    let duration = time::Duration::from_millis(settings.timeout);
    
    loop {
        let loop_start = time::Instant::now();

        let frame;
        let future = task::spawn_blocking(|| {
            cam_ref.capture()
        });
        if let Ok(result) = tokio::time::timeout(duration, future).await {
            frame = result.unwrap();
        } else {
            println!("Restarting camera");     
            break;       
        }

        let buf = decode_jpeg(&frame.unwrap());
        
        // etc
    }
Svetlin Zarev
  • 14,713
  • 4
  • 53
  • 82
  • A tokio timeout will not magically unblock a blocking IO, it still have to wait it to finish the task. But depending on the OS, there may be other ways to interrupt a blocking IO... – rodrigo Feb 05 '22 at 20:07
  • 3
    On the Rust side you should be able to fix the issue by placing `camera` behind an `Arc`: `let camera = Arc::new(Mutex::new(Camera::new(...))); camera.lock().unwrap().start(...)`. Then you call `spawn_blocking()` with a closure that captures a _clone_ of `camera`: `let future = task::spawn_blocking({let camera = camera.clone(); move || { camera.capture() }})`. But that might not fix the underlying issue, as pointed out by rodrigo. – user4815162342 Feb 05 '22 at 20:12
  • 1
    Basically `spawn_blocking()` is just a convenience function that submits the closure to a thread pool, along with a bit of glue code that sends its result (once available) down a one-shot channel. The future it returns is the read side of that one-shot channel. When you "time-out" the read, you just timeout the async read of the channel, and the stuck call to `capture()` still occupies a slot in the thread pool. Even if your approach works, it might fill up the thread pool if it happens enough times. You might be better off spawning your own threads or (ideally) addressing the root cause. – user4815162342 Feb 05 '22 at 20:15
  • Thanks! I am afraid that cloning the camera every frame might be taking up a lot of mem fast. The root cause is an old driver for the old webcam in the kernel, that is not stable - so I don't think I will be able to fix that. But I like the idea of manually spawning a separate thread that operates the camera and communicates results to the main. If data won't come within some time than the thread would be restarted. I was kinda trying to do similar thing with tokio timeout killing the entire program (as it would be restarted by systemd) - but that is not very elegant :) – maciek.glowka Feb 06 '22 at 09:19

0 Answers0