Child Stdin None using Nix PTY - rust

I was trying to play with PTY and some Unix concept using Rust. I ended up with the following code, unfortunately I can't make it work. I really struggle to find out why the stdin. stdout and stderr of the child process end up being None when assigned to the file descriptor of the PTY's slave.
In the Cargo.toml file I have the following dependencies:
[dependencies]
nix = "0.17.0"
libc = "0.2"
From the debugging I have already made, it seems that the None option is coming from the map of ChildStdin/ChildStdout/ChildStderr done in https://github.com/rust-lang/rust/blob/master/src/libstd/process.rs#L195.
I'm not sure it's a Rust issue, and it might be my understanding of how PTYs works, but I have written the same code (as much as possible) in C and it's behaving as expected.
Could you help me figure out what is wrong
use nix::Error;
use std::path::Path;
use std::os::unix::{
io::{AsRawFd, FromRawFd, RawFd}
};
use nix::sys::stat::Mode;
use nix::fcntl::{OFlag, open};
use std::process::{Command, Stdio};
use std::os::unix::process::CommandExt;
use nix::pty::{PtyMaster, grantpt, posix_openpt, ptsname, unlockpt};
use libc::{self, winsize};
#[macro_use]
extern crate nix;
struct Pty {
master: PtyMaster,
slave: RawFd
}
fn construct_internal_pty() -> Result<Pty, Error> {
ioctl_write_ptr_bad!(pty_io_size, nix::libc::TIOCSWINSZ, winsize);
let master_fd = posix_openpt(OFlag::O_RDWR | OFlag::O_NOCTTY)?;
grantpt(&master_fd)?;
unlockpt(&master_fd)?;
let slave_name = unsafe { ptsname(&master_fd) }?;
let slave_fd = open(
Path::new(&slave_name),
OFlag::O_RDWR | OFlag::O_NOCTTY,
Mode::empty())?;
Ok(Pty {
master: master_fd,
slave: slave_fd
})
}
fn main() -> () {
let pty = construct_internal_pty().expect("Unable to construct PTY");
let mut builder = Command::new("/bin/sh");
let slave = pty.slave;
let master = pty.master.as_raw_fd();
builder.env("LOGNAME", "ex0ns");
builder.env("USER", "ex0ns");
builder.env("SHELL", "/bin/sh");
builder.env("HOME", "/home/ex0ns");
builder.current_dir("/home/ex0ns");
// This is not working.... The resulting fd is None
builder.stdin(unsafe { Stdio::from_raw_fd(slave) });
builder.stderr(unsafe { Stdio::from_raw_fd(slave) });
builder.stdout(unsafe { Stdio::from_raw_fd(slave) });
unsafe {
builder.pre_exec(move || {
libc::setsid();
libc::close(slave);
libc::close(master);
libc::signal(libc::SIGCHLD, libc::SIG_DFL);
libc::signal(libc::SIGHUP, libc::SIG_DFL);
libc::signal(libc::SIGINT, libc::SIG_DFL);
libc::signal(libc::SIGQUIT, libc::SIG_DFL);
libc::signal(libc::SIGTERM, libc::SIG_DFL);
libc::signal(libc::SIGALRM, libc::SIG_DFL);
Ok(())
});
}
match builder.spawn() {
Ok(child) => {
println!("{:?}", child);
println!("{:?}", slave);
assert_eq!(child.stdin.is_none(), false);
unsafe { libc::close(slave) }; // close PTY ?
}
Err(error) => {
println!("Unable to spawn child process {}", error);
::std::process::exit(1);
}
}
}
Thank you

Related

Spawning with TcpStream leads to 10061 ConnectionRefused

I'm trying to make a simple host which can handle multiple streams (similar to the example given in https://tokio.rs/tokio/tutorial):
use std::{error::Error, time::Duration};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpStream},
time::sleep,
};
type GenericResult<T> = Result<T, Box<dyn Error>>;
#[tokio::main]
async fn main() {
const ADDRESS: &str = "127.0.0.1:8080";
let listener = TcpListener::bind(ADDRESS).await.unwrap();
tokio::spawn(async { host(listener) });
let mut stream = TcpStream::connect(ADDRESS).await.unwrap();
stream.write_all(b"testing").await.unwrap();
}
async fn host(listener: TcpListener) -> GenericResult<()> {
loop {
let (stream, _) = listener.accept().await?;
println!("new connection");
tokio::spawn(async { process(stream).await.unwrap() });
}
async fn process(mut stream: TcpStream) -> GenericResult<()> {
// Reads from stream
let mut buffer = Vec::with_capacity(128);
let mut position = 0;
loop {
// Read from stream into buffer
let n = stream.read(&mut buffer[position..]).await?;
// Advance position
position += n;
// Print buffer
println!("buffer: {:?}", buffer);
sleep(Duration::from_millis(100)).await;
}
}
}
But when running this I encounter:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 10061, kind: ConnectionRefused, message: "No connection could be made because the target machine actively refused it." }', src\main.rs:12:56
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Any help would be really appreciated here.
Specifically why my implementation here differs (in functionality) from a working implementation in https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5b944a8c4703d438d48ef5e556f1fc08
cargo --version --verbose:
cargo 1.60.0 (d1fd9fe2c 2022-03-01)
release: 1.60.0
commit-hash: d1fd9fe2c40a1a56af9132b5c92ab963ac7ae422
commit-date: 2022-03-01
host: x86_64-pc-windows-msvc
libgit2: 1.3.0 (sys:0.13.23 vendored)
libcurl: 7.80.0-DEV (sys:0.4.51+curl-7.80.0 vendored ssl:Schannel)
os: Windows 10.0.22000 (Windows 10 Pro) [64-bit]
With tokio = {version="1.18.0",features=["full"]}
Define the stream before you start listening on it.
use std::{error::Error, time::Duration};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpStream},
time::sleep,
};
type GenericResult<T> = Result<T, Box<dyn Error>>;
#[tokio::main]
async fn main() {
const ADDRESS: &str = "127.0.0.1:8080";
let listener = TcpListener::bind(ADDRESS).await.unwrap();
let mut stream = TcpStream::connect(ADDRESS).await.unwrap();
tokio::spawn(async { host(listener) });
stream.write_all(b"testing").await.unwrap();
}
async fn host(listener: TcpListener) -> GenericResult<()> {
loop {
let (stream, _) = listener.accept().await?;
println!("new connection");
tokio::spawn(async { process(stream).await.unwrap() });
}
async fn process(mut stream: TcpStream) -> GenericResult<()> {
// Reads from stream
let mut buffer = Vec::with_capacity(128);
let mut position = 0;
loop {
// Read from stream into buffer
let n = stream.read(&mut buffer[position..]).await?;
// Advance position
position += n;
// Print buffer
println!("buffer: {:?}", buffer);
sleep(Duration::from_millis(100)).await;
}
}
}

How to clear interrupt with perpiheral handle? Embedded Rust

Few weeks ago I started to learn Rust Embedded.
Now I'm stuck, and I would like to ask you for help. So..
I wanted to use TIM3 in my code to change variable (in future peripheral state) and clear (unpend?) interrupt via registers inside ISR.
In C I did something like this inside ISR:
void TIM3_IRQHandler(void)
{
if (TIM3->SR & TIM_SR_UIF)
{
TIM3->SR &= ~(TIM_SR_UIF);
}
}
..and now I'm stuck to do this in Rust.
At first I show what I've done so far.
#![no_std]
#![no_main]
use panic_halt as _;
use cortex_m_rt::entry;
use core::{cell::RefCell};
use core::ops::DerefMut;
use cortex_m::interrupt::{self, Mutex};
use stm32g0::stm32g071::{self, Interrupt, NVIC, TIM3};
static G_TIM: Mutex<RefCell<Option<stm32g071::TIM3>>> =
Mutex::new(RefCell::new(None));
#[entry]
fn main() -> ! {
let p = stm32g071::Peripherals::take().unwrap();
let rcc_r = &p.RCC;
let timer_r = &p.TIM3;
let tim3 = p.TIM3;
unsafe {
NVIC::unmask(Interrupt::TIM3);
};
rcc_r.apbenr1.write(|w| w.tim3en().set_bit());
prepare_timer3(timer_r);
interrupt::free(|cs| {
G_TIM.borrow(cs).replace(Some(tim3))
});
loop {
}
}
fn prepare_timer3(tim3_r_handle: &TIM3) {
tim3_r_handle.cr1.write(|w| w.cen().clear_bit());
tim3_r_handle.psc.write(|w| unsafe { w.psc().bits(16000) });
tim3_r_handle.arr.write(|w| unsafe { w.arr_l().bits(100) });
tim3_r_handle.egr.write(|w| w.ug().set_bit());
tim3_r_handle.dier.write(|w| w.uie().set_bit());
tim3_r_handle.cr1.write(|w| w.cen().set_bit());
}
#[interrupt]
fn TIM3() {
interrupt::free(|cs| {
if let Some(ref mut tim3) = G_TIM.borrow(cs).borrow_mut().deref_mut() {
tim3.sr.write(|w| w.uif().clear_bit());
}
})
}
And I get this compilation error:
error: cannot find attribute `interrupt` in this scope
--> src/main.rs:51:3
|
51 | #[interrupt]
| ^^^^^^^^^
|
= note: consider importing one of these items:
cortex_m_rt::interrupt
crate::stm32g071::interrupt
stm32g0::stm32g071::interrupt
note: `interrupt` is imported here, but it is a module, not an attribute
--> src/main.rs:10:27
|
10 | use cortex_m::interrupt::{self, Mutex};
| ^^^^
error: could not compile `blink-nucleo-g0` due to previous error
I have problem how to resolve those dependency problem.
Could you tell me also that what I did with this Mutex G_TIM is fine?
I mean I did this after read this article: https://docs.rust-embedded.org/book/concurrency/#sharing-peripherals
I also read this https://users.rust-lang.org/t/rust-embedded-stm32f303-timer-interrupt-hanging/40323 but I don't want to use hal crates.
I asked at Rust forum too: https://users.rust-lang.org/t/how-to-clear-interrupt-with-perpiheral-handle/67214
EDIT:
I changed to:
use cortex_m::interrupt::free;
use cortex_m::interrupt::Mutex;
use stm32g0::stm32g071::{self, Interrupt, NVIC, TIM3, interrupt};
and usage of interrupt::free to free.
#[interrupt]
fn TIM2() {
free(|cs| {
if let Some(ref mut tim2) = G_TIM.borrow(cs).borrow_mut().deref_mut() {
tim2.sr.write(|w| w.uif().clear_bit());
}
});
}
I think that my ISR is invkoing in loop. How to clear this interrupt properly?
EDIT:
I changed whole to TIM2.
I cannot reach line tim2.sr.write(|w| w.uif().clear_bit()); with debugger. I think above if let returns false, why?
The macro attribute #[interrupt] is exposed in cortex-m-rt not cortex-m as book chapter Interrupts document:
Similarly to exceptions, the cortex-m-rt crate provides an interrupt attribute to declare interrupt handlers.
I followed instructions in GitHub issue and it worked. I wasn't using Mutex and interrupt::free properly. if let.. in ISR was returning false because interrupt was exectued before replace so... it has value of None in interrupt.
This is my code that works, after fix.
#![no_std]
#![no_main]
use panic_halt as _;
use core::cell::RefCell;
use core::ops::DerefMut;
use cortex_m::interrupt::free;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use stm32g0::stm32g071::{self, interrupt, Interrupt, NVIC};
static G_TIM: Mutex<RefCell<Option<stm32g071::TIM2>>> = Mutex::new(RefCell::new(None));
static G_GPIOA: Mutex<RefCell<Option<stm32g071::GPIOA>>> = Mutex::new(RefCell::new(None));
#[entry]
fn main() -> ! {
let p = stm32g071::Peripherals::take().unwrap();
let gpioa = &p.GPIOA;
let rcc_r = &p.RCC;
// enable Clock for GPIOA
rcc_r.iopenr.modify(|_, w| w.iopaen().set_bit());
let tim2 = p.TIM2;
// Nucleo G071RB LED so need to set as output
gpioa.moder.modify(|_, w| unsafe { w.moder5().bits(0b01) });
rcc_r.apbenr1.write(|w| w.tim2en().set_bit());
free(|cs| {
tim2.cr1.write(|w| w.cen().clear_bit());
tim2.psc.write(|w| unsafe { w.psc().bits(16000) });
tim2.arr.write(|w| unsafe { w.arr_l().bits(1000) });
tim2.egr.write(|w| w.ug().set_bit());
tim2.dier.write(|w| w.uie().set_bit());
tim2.cr1.write(|w| w.cen().set_bit());
G_TIM.borrow(cs).replace(Some(tim2));
});
// NVIC unmask interrupt
unsafe {
NVIC::unmask(Interrupt::TIM2);
};
let gpioa = p.GPIOA;
free(|cs| {
G_GPIOA.borrow(cs).replace(Some(gpioa));
});
let mut increment = 0;
loop {
increment += 1;
if increment > 1000 {
increment = 0;
}
}
}
#[interrupt]
fn TIM2() {
free(|cs| {
if let Some(ref mut tim2) = G_TIM.borrow(cs).borrow_mut().deref_mut() {
tim2.sr.write(|w| w.uif().clear_bit());
}
});
free(|cs| {
if let Some(ref mut gpioa) = G_GPIOA.borrow(cs).borrow_mut().deref_mut() {
if gpioa.odr.read().odr5().bit_is_set() {
gpioa.odr.modify(|_, w| w.odr5().clear_bit());
} else {
gpioa.odr.modify(|_, w| w.odr5().set_bit());
}
}
});
}

Read stdin triggered by key event without dropping first letter

Context
I am working on a pomodoro command line app written in rust, most of it worked well but now I want to edit the text of a pomodoro item in the database. All of the actions in the app are triggered by keystrokes, pausing/resuming, quitting etc. and as well editing the text.
Now I want to read the text from stdin but the key-events are as sourced as well from stdin, but on a different thread. I came up with using a stdin.lock() - which works almost fine.
The problem
How can I read a line from stdin in the main thread, without dropping the first letter, due to the event listener being triggered in its thread, before the lock in the main thread is acquired.
expected behaviour:
press t => print Reading from stdin!
type abc<enter> => print You typed: Some("abc")
actual behaviour:
press t => print Reading from stdin!
type abc<enter> => print You typed: Some("bc")
Minimal non-working example
Here is an example that shows the described behaviour:
use failure;
use std::io::{stdin, stdout};
use std::sync::mpsc;
use std::thread;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use tui::backend::TermionBackend;
use tui::Terminal;
pub enum Event {
Input(Key),
}
#[allow(dead_code)]
pub struct Events {
rx: mpsc::Receiver<Event>,
input_handle: thread::JoinHandle<()>,
}
impl Events {
pub fn new() -> Events {
let (tx, rx) = mpsc::channel();
let input_handle = {
let tx = tx.clone();
thread::spawn(move || {
let stdin = stdin();
for evt in stdin.keys() {
match evt {
Ok(key) => {
if let Err(_) = tx.send(Event::Input(key)) {
return;
}
}
Err(_) => {}
}
}
})
};
Events {
rx,
input_handle,
}
}
pub fn next(&self) -> Result<Event, mpsc::RecvError> {
self.rx.recv()
}
}
pub fn key_handler(key: Key) -> bool {
match key {
Key::Char('t') => {
println!("Reading from stdin!");
let stdin = stdin();
let mut handle = stdin.lock();
let input = handle.read_line().unwrap();
println!("You typed: {:?}", input);
}
_ =>{
println!("no thing!");
}
};
key == Key::Char('q')
}
fn main() -> Result<(), failure::Error> {
let stdout = stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
terminal.clear()?;
terminal.hide_cursor()?;
let events = Events::new();
loop {
match events.next()? {
Event::Input(key) => {
if key_handler(key) {
break;
}
}
}
}
terminal.clear()?;
terminal.show_cursor()?;
Ok(())
}
Update
Cargo.toml
[package]
name = "mnwe"
version = "1.1.0"
edition = "2018"
autotests = false
[[bin]]
bench = false
path = "app/main.rs"
name = "mnwe"
[dependencies]
failure = "0.1"
termion = "1.5.3"
tui = "0.7"
The problem, as correctly identified, is the race on stdin().lock() between stdin.keys() in the events thread and stdin.lock() in key_handler (which the events thread tends to win, and eat one key).
For the sake of the bounty, I see four possible approaches:
At easiest, you can avoid having threads at all, and instead regularly poll for new input with termion::async_std. (It's what I ended up doing for my own tui application. In the end, you're likely to be polling the event receiver anyway, so why not poll stdin directly.)
If your problem allows it, you could do the stdin reading directly on the event thread. Instead of sending key events over the channel, you would send something you could call "user commands" over the channel:
// Channel data:
pub enum Command {
StdinInput(String),
Quit,
// Add more commands
Unknown(Key),
}
// Send side
thread::spawn(move || {
for evt in stdin().keys() {
match evt {
Ok(key) => {
let cmd = match key {
Key::Char('t') => {
println!("Reading from stdin!");
let input = stdin().lock().read_line().unwrap();
Command::StdinInput(input.unwrap_or(String::new()))
}
Key::Char('q') => Command::Quit,
_ => Command::Unknown(key),
};
if let Err(_) = tx.send(cmd) {
return;
}
}
Err(_) => {}
}
}
})
// Receive side
loop {
match events.next()? {
Command::StdinInput(input) => println!("You typed: {}", input),
Command::Quit => break,
Command::Unknown(k) => println!("no thing: {:?}", k),
}
}
If you must absolutely have access stdin from to threads, I would not recommend using a CondVar, but passing the sender of another channel through the event channel. Why? Because it's much harder to get wrong. Any channel will do, but I think oneshot::channel() is most suitable here:
// Channel data
pub enum Event {
Input(Key, oneshot::Sender<()>),
}
// Send side
for evt in stdin.keys() {
let (shot_send, shot_recv) = oneshot::channel();
match evt {
Ok(key) => {
if let Err(_) = tx.send(Event::Input(key, shot_send)) {
return;
}
shot_recv.recv().ok();
}
Err(_) => {}
}
}
// Receive side
loop {
match events.next()? {
Event::Input(key, done) => {
match key {
Key::Char('t') => {
println!("Reading from stdin!");
let stdin = stdin();
let mut handle = stdin.lock();
let input = handle.read_line().unwrap();
println!("You typed: {:?}", input);
// It doesn't really matter whether we send anything
// but using the channel here avoids mean surprises about when it gets dropped
done.send(()).ok();
}
Key::Char('q') => break,
_ => println!("no thing!"),
};
}
}
}
You could also not do the stdin().lock().read_line() at all, but reassemble the user input line from the key stroke events. I wouldn't do that.

How to add special NotReady logic to tokio-io?

I'm trying to make a Stream that would wait until a specific character is in buffer. I know there's read_until() on BufRead but I actually need a custom solution, as this is a stepping stone to implement waiting until a specific string in in buffer (or, for example, a regexp match happens).
In my project where I first encountered the problem, problem was that future processing just hanged when I get a Ready(_) from inner future and return NotReady from my function. I discovered I shouldn't do that per docs (last paragraph). However, what I didn't get, is what's the actual alternative that is promised in that paragraph. I read all the published documentation on the Tokio site and it doesn't make sense for me at the moment.
So following is my current code. Unfortunately I couldn't make it simpler and smaller as it's already broken. Current result is this:
Err(Custom { kind: Other, error: Error(Shutdown) })
Err(Custom { kind: Other, error: Error(Shutdown) })
Err(Custom { kind: Other, error: Error(Shutdown) })
<ad infinum>
Expected result is getting some Ok(Ready(_)) out of it, while printing W and W', and waiting for specific character in buffer.
extern crate futures;
extern crate tokio_core;
extern crate tokio_io;
extern crate tokio_io_timeout;
extern crate tokio_process;
use futures::stream::poll_fn;
use futures::{Async, Poll, Stream};
use tokio_core::reactor::Core;
use tokio_io::AsyncRead;
use tokio_io_timeout::TimeoutReader;
use tokio_process::CommandExt;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
struct Process {
child: tokio_process::Child,
stdout: Arc<Mutex<tokio_io_timeout::TimeoutReader<tokio_process::ChildStdout>>>,
}
impl Process {
fn new(
command: &str,
reader_timeout: Option<Duration>,
core: &tokio_core::reactor::Core,
) -> Self {
let mut cmd = Command::new(command);
let cat = cmd.stdout(Stdio::piped());
let mut child = cat.spawn_async(&core.handle()).unwrap();
let stdout = child.stdout().take().unwrap();
let mut timeout_reader = TimeoutReader::new(stdout);
timeout_reader.set_timeout(reader_timeout);
let timeout_reader = Arc::new(Mutex::new(timeout_reader));
Self {
child,
stdout: timeout_reader,
}
}
}
fn work() -> Result<(), ()> {
let window = Arc::new(Mutex::new(Vec::new()));
let mut core = Core::new().unwrap();
let process = Process::new("cat", Some(Duration::from_secs(20)), &core);
let mark = Arc::new(Mutex::new(b'c'));
let read_until_stream = poll_fn({
let window = window.clone();
let timeout_reader = process.stdout.clone();
move || -> Poll<Option<u8>, std::io::Error> {
let mut buf = [0; 8];
let poll;
{
let mut timeout_reader = timeout_reader.lock().unwrap();
poll = timeout_reader.poll_read(&mut buf);
}
match poll {
Ok(Async::Ready(0)) => Ok(Async::Ready(None)),
Ok(Async::Ready(x)) => {
{
let mut window = window.lock().unwrap();
println!("W: {:?}", *window);
println!("buf: {:?}", &buf[0..x]);
window.extend(buf[0..x].into_iter().map(|x| *x));
println!("W': {:?}", *window);
if let Some(_) = window.iter().find(|c| **c == *mark.lock().unwrap()) {
Ok(Async::Ready(Some(1)))
} else {
Ok(Async::NotReady)
}
}
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => Err(e),
}
}
});
let _stream_thread = thread::spawn(move || {
for o in read_until_stream.wait() {
println!("{:?}", o);
}
});
match core.run(process.child) {
Ok(_) => {}
Err(e) => {
println!("Child error: {:?}", e);
}
}
Ok(())
}
fn main() {
work().unwrap();
}
This is complete example project.
If you need more data you need to call poll_read again until you either find what you were looking for or poll_read returns NotReady.
You might want to avoid looping in one task for too long, so you can build yourself a yield_task function to call instead if poll_read didn't return NotReady; it makes sure your task gets called again ASAP after other pending tasks were run.
To use it just run return yield_task();.
fn yield_inner() {
use futures::task;
task::current().notify();
}
#[inline(always)]
pub fn yield_task<T, E>() -> Poll<T, E> {
yield_inner();
Ok(Async::NotReady)
}
Also see futures-rs#354: Handle long-running, always-ready futures fairly #354.
With the new async/await API futures::task::current is gone; instead you'll need a std::task::Context reference, which is provided as parameter to the new std::future::Future::poll trait method.
If you're already manually implementing the std::future::Future trait you can simply insert:
context.waker().wake_by_ref();
return std::task::Poll::Pending;
Or build yourself a Future-implementing type that yields exactly once:
pub struct Yield {
ready: bool,
}
impl core::future::Future for Yield {
type Output = ();
fn poll(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> core::task::Poll<Self::Output> {
let this = self.get_mut();
if this.ready {
core::task::Poll::Ready(())
} else {
cx.waker().wake_by_ref();
this.ready = true; // ready next round
core::task::Poll::Pending
}
}
}
pub fn yield_task() -> Yield {
Yield { ready: false }
}
And then use it in async code like this:
yield_task().await;

How can I force a thread that is blocked reading from a file to resume in Rust?

Because Rust does not have have the built-in ability to read from a file in a non-blocking manner, I have to spawn a thread which reads the file /dev/input/fs0 in order to get joystick events. Suppose the joystick is unused (nothing to read), so the reading thread is blocked while reading from the file.
Is there a way for the main thread to force the blocking read of the reading thread to resume, so the reading thread may exit cleanly?
In other languages, I would simply close the file in the main thread. This would force the blocking read to resume. But I have not found a way to do so in Rust, because reading requires a mutable reference to the file.
The idea is to call File::read only when there is available data. If there is no available data, we check a flag to see if the main thread requested to stop. If not, wait and try again.
Here is an example using nonblock crate:
extern crate nonblock;
use std::fs::File;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use nonblock::NonBlockingReader;
fn main() {
let f = File::open("/dev/stdin").expect("open failed");
let mut reader = NonBlockingReader::from_fd(f).expect("from_fd failed");
let exit = Arc::new(Mutex::new(false));
let texit = exit.clone();
println!("start reading, type something and enter");
thread::spawn(move || {
let mut buf: Vec<u8> = Vec::new();
while !*texit.lock().unwrap() {
let s = reader.read_available(&mut buf).expect("io error");
if s == 0 {
if reader.is_eof() {
println!("eof");
break;
}
} else {
println!("read {:?}", buf);
buf.clear();
}
thread::sleep(Duration::from_millis(200));
}
println!("stop reading");
});
thread::sleep(Duration::from_secs(5));
println!("closing file");
*exit.lock().unwrap() = true;
thread::sleep(Duration::from_secs(2));
println!("\"stop reading\" was printed before the main exit!");
}
fn read_async<F>(file: File, fun: F) -> thread::JoinHandle<()>
where F: Send + 'static + Fn(&Vec<u8>)
{
let mut reader = NonBlockingReader::from_fd(file).expect("from_fd failed");
let mut buf: Vec<u8> = Vec::new();
thread::spawn(move || {
loop {
let s = reader.read_available(&mut buf).expect("io error");
if s == 0 {
if reader.is_eof() {
break;
}
} else {
fun(&buf);
buf.clear();
}
thread::sleep(Duration::from_millis(100));
}
})
}
Here is an example using poll binding of nix crate. The function poll waits (with timeout) for specific events:
extern crate nix;
use std::io::Read;
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use nix::poll;
fn main() {
let mut f = std::fs::File::open("/dev/stdin").expect("open failed");
let mut pfd = poll::PollFd {
fd: f.as_raw_fd(),
events: poll::POLLIN, // is there input data?
revents: poll::EventFlags::empty(),
};
let exit = Arc::new(Mutex::new(false));
let texit = exit.clone();
println!("start reading, type something and enter");
thread::spawn(move || {
let timeout = 100; // millisecs
let mut s = unsafe { std::slice::from_raw_parts_mut(&mut pfd, 1) };
let mut buffer = [0u8; 10];
loop {
if poll::poll(&mut s, timeout).expect("poll failed") != 0 {
let s = f.read(&mut buffer).expect("read failed");
println!("read {:?}", &buffer[..s]);
}
if *texit.lock().unwrap() {
break;
}
}
println!("stop reading");
});
thread::sleep(Duration::from_secs(5));
println!("closing file");
*exit.lock().unwrap() = true;
thread::sleep(Duration::from_secs(2));
println!("\"stop reading\" was printed before the main exit!");
}

Resources