Skip to content

Windows not freeing ports #37

@MGlolenstine

Description

@MGlolenstine

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions