I am trying to implement a exercise with child process and pipes in rust. The exercise consist in child process 2 send message to child process 1, the message that I recieved in c1 I have to return to c2, I have to do it 10 times every 2 seconds. I have a code that might to do it, but when I want to run it, it's still waiting and I can,t see the messages.
This is my code:
use nix::sys::wait::wait;
use nix::unistd::ForkResult::{Child, Parent};
use nix::unistd::fork;
use std::io::prelude::*;
use std::time::Duration;
fn main() {
let (mut reader, mut writer) = os_pipe::pipe().unwrap();
let (mut reader_2, mut writer_2) = os_pipe::pipe().unwrap();
let pid = fork();
match pid.expect("Error during creating child") {
Parent { child } => {
drop(reader);
drop(writer_2);
let pid_2 = fork();
match pid_2.expect("Error creating child 2") {
Parent { child } => {}
Child => {
for _ in 1..10 {
writer.write("C1".as_bytes()).unwrap();
let mut data = String::new();
reader_2.read_to_string(&mut data).unwrap();
println!("Data from C1, {}", data);
std::thread::sleep(Duration::from_millis(2000));
}
}
}
}
Child => {
drop(writer);
drop(reader_2);
for _ in 1..10 {
let mut data = String::new();
reader.read_to_string(&mut data).unwrap();
println!("Data from C2, {}", data);
writer_2.write(data.to_string().as_bytes()).unwrap();
}
}
}
wait().unwrap();
}
Thanks!!!
CodePudding user response:
The problem of your code waiting comes from using read_to_string
, the docs of which say (my emphasis):
Read all bytes until EOF in this source, appending them to
buf
.
The first child sends some data down the pipe, but doesn’t close it. The second child receives the data, but it wants all the data, so it just waits for the other side to send. It will only continue when it receives EOF but that never comes, so you have deadlock.
To fix it you need to define some kind of protocol between the two communicating processes so that each side can tell when the other has completed it’s message.
In this case the protocol could simply be “send a newline after each message”, and that would let you use read_line
to easily implement it. If the data isn’t text based you would need something else of course.
First you would need to use BufReader
:
use std::io::BufReader;
Then wrap the two readers in a BufReader
:
let mut reader = BufReader::new(reader);
let mut reader_2 = BufReader::new(reader_2);
Now you can swap your calls to read_to_string
with read_line
:
reader.read_line(&mut data).unwrap();
When you are sending the data, be sure to add the newline, else you will deadlock again:
writer.write("C1\n".as_bytes()).unwrap();
Now when a receiver gets a message it will see there is a newline, and will continue without waiting for more bytes that will never arrive.
Note that read_line
returns the line including the newline character, so you might want to remove it.
CodePudding user response:
You can change the data to a u8
slice of size 2 and use read_exact
instead of read_to_string
:
Parent { child } => {
drop(reader);
drop(writer_2);
let pid_2 = unsafe{ fork() };
match pid_2.expect("Error creating child 2") {
Parent { child } => {}
Child => {
let mut data: [u8;2] = [0;2];
for _ in 1..10 {
writer.write("C1".as_bytes()).unwrap();
reader_2.read_exact(&mut data).unwrap();
println!("Data from C1, {}", std::str::from_utf8(&data).unwrap());
std::thread::sleep(Duration::from_millis(2000));
}
}
}
}
Child => {
drop(writer);
drop(reader_2);
let mut data: [u8;2] = [0;2];
for _ in 1..10 {
reader.read_exact(&mut data).unwrap();
println!("Data from C2, {}", std::str::from_utf8(&data).unwrap());
writer_2.write(&data).unwrap();
}
}
}