tokio-serial icon indicating copy to clipboard operation
tokio-serial copied to clipboard

Windows not freeing ports

Open MGlolenstine opened this issue 3 years ago • 9 comments

We're using Rust application to talk to other devices using Serial COM ports.

I tried working on a "fast port search", which would connect to all ports asynchronously, write command and read the response to decide if it's the port it's looking for.

code

use futures::stream::{StreamExt, TryStreamExt};
use std::{thread::sleep, time::Duration};
use tokio::{
    io::{AsyncReadExt, AsyncWriteExt},
    time::timeout,
};

use serialport::available_ports;
use tokio_serial::{Serial, SerialPort};

async fn parallel_test(baudrate: u32) -> Option<Serial> {
    let ports = available_ports()
        .unwrap()
        .iter()
        .map(|v| v.port_name.clone())
        .collect::<Vec<_>>();
    let len = ports.len();
    let checked: Vec<_> = tokio::stream::iter(ports)
        .map(|v| async move {
            dbg!(&v);
            establish_connection(&v, baudrate).await
        })
        .buffered(len)
        .collect::<Vec<_>>()
        .await;
    let checked = checked
        .into_iter()
        .filter_map(|v| v.ok())
        .collect::<Vec<_>>();
    for s in &checked {
        dbg!(s.settings());
    }
    checked.into_iter().next()
}

async fn establish_connection(path: &str, baudrate: u32) -> Result<Serial, ()> {
    let mut settings = tokio_serial::SerialPortSettings::default();
    settings.baud_rate = baudrate;
    settings.timeout = Duration::from_millis(200);
    let conn = tokio_serial::Serial::from_path(path, &settings);
    return if let Ok(mut port) = conn {
        dbg!("Connected!");
        port.write_data_terminal_ready(false).unwrap();
        port.write_all(b"*IDN?\r\n").await.unwrap();
        let mut buf = [0u8; 32];
        if let Ok(s) = timeout(Duration::from_millis(1200), port.read(&mut buf)).await {
            if let Ok(_) = s {
                dbg!(String::from_utf8_lossy(&buf).trim());
                return Ok(port);
            }
        }
        println!("Shutting down and freeing {}", path);
        port.shutdown().await.unwrap();
        drop(port);
        Err(())
    } else {
        if let Err(err) = conn {
            println!("Conn err: {:#?}", err);
        }
        eprintln!("Failed to connect to {}!", path);
        Err(())
    };
}

#[cfg(test)]
mod tests {
    use std::{thread::sleep, time::Duration};

    use crate::parallel_test;

    #[tokio::test]
    async fn it_works() {
        parallel_test(115200).await.unwrap();
        sleep(Duration::from_secs(1));
        parallel_test(9600).await.unwrap();
    }
}


The above test should:

  • try to filter out all ports that respond on 115200 baudrate. (successfully)
  • wait for a second to let windows close the ports
  • try to filter out all ports that respond on 9600 baudrate. (unsuccessfully)

I'm not really sure, but I don't think tokio_serial frees COM ports after they're dropped, so we get an Access Denied OS error whenever we try to execute the third point. There's no direct shutdown(there is, but it doesn't seem to work) or close functions that would do what I'd like to do.

I did also try manually dropping it drop(port), but that didn't do the trick either.

MGlolenstine avatar Mar 08 '21 13:03 MGlolenstine

It's certainly possible, though the actual low-level file handle work in windows should be taken care of by upstream mio code.

I'm in the middle of bringing tokio-serial up to date with mio 0.7 and tokio 1.0. Tokio support with windows may have to wait until tokio-rs/tokio#3388 lands, but I plan on trying to have a stab at it with the code in that PR in the next few days.

berkowski avatar Mar 09 '21 23:03 berkowski

There was a possible double-drop in mio-serial for windows that has been addressed in the latest round of cleanup. If you're still interested, please try against the latest 5.4.0-beta on crates.io.

berkowski avatar Jul 16 '21 17:07 berkowski

Thanks for keeping me updated! I'm currently not working on that project anymore, but I can test it out on a "test-bed".

MGlolenstine avatar Jul 16 '21 17:07 MGlolenstine

If you're still curious, sure. No problem otherwise though, with the code you posted earlier I can test it myself at some point.

berkowski avatar Jul 16 '21 17:07 berkowski

I am trying to work out some similar "port-scan" feature in my project. And I get similar "Access Denied" issue on Windows. Just that my case is slightly different in that I am using tokio_util::codec::Framed to wrap tokio_serial::SerialStream and my codec.

Firstly there is not an obvious api in tokio_util::codec::Framed to release the port. There is only a close() method but it does not work.

Then I tried this into_inner method to get tokio_serial::SerialStream from Framed thus I can run shutdown() method on the SerialStream instance. But I see it cannot free the resource.

For now I've workarounded the issue by some design change in my code: Now I pass the same established connection from my "port-scan" logic to where my main logic. This way I avoid creating the tokio_serial::SerialStream connection twice on the same port..

iynehz avatar Sep 18 '22 16:09 iynehz

no way... i closed it~ >_<

image

imxood avatar Dec 07 '22 16:12 imxood

Is there any update to this issue?

daftcube avatar Mar 05 '23 09:03 daftcube

I would also like to know if there are any updates to this issue. =)

stephen-h-d avatar Jul 31 '23 20:07 stephen-h-d

I met the same problem on linux while reading data from a bluetooth serial port. @imxood I've read the bug report you submitted in mio-serial, that's exactly my situation(I use mio-serial the same way as the package given example). @berkowski Could you try to reproduce the bug using mio-serial's example(and a bluetooth serial port created by blueman in my case)? Many thanks.

shilkazx avatar Nov 29 '23 14:11 shilkazx