Buffer in Rust with Mutex and Condvar - multithreading

I'm trying to implement a buffer with a single consumer and a single producer. I have only used POSIX Semaphores, however, they're not available in Rust and I'm trying to implement a trivial semaphore problem with Rust sync primitives (Mutex, Condvar, Barrier, ...) but I don't want to use channels.
My code behaves too irregularly, with some cases going well and other times it just stops at some number and in other cases it just doesn't start counting.
Things appear to work better if I wait 1 second in the main thread till I send the Condvar notification but it doesn't guarantee that it's not going to enter a deadlock.
How can this program be fixed? Am I understanding Condvars wrong?
use std::thread;
use std::sync::{Arc, Condvar, Mutex};
struct Buffer {
is_data: Mutex<bool>,
is_data_cv: Condvar,
is_space: Mutex<bool>,
is_space_cv: Condvar,
buffer: Mutex<i32>,
}
fn producer(buffer: Arc<Buffer>) {
for i in 0..50 {
loop {
let mut is_space = buffer
.is_space_cv
.wait(buffer.is_space.lock().unwrap())
.unwrap();
if *is_space {
{
let mut hueco = buffer.buffer.lock().unwrap();
*hueco = i;
}
*is_space = false;
{
let mut is_data = buffer.is_data.lock().unwrap();
*is_data = true;
}
buffer.is_data_cv.notify_one();
break;
}
}
}
}
fn consumer(buffer: Arc<Buffer>) {
for i in 0..50 {
loop {
let mut is_data = buffer
.is_data_cv
.wait(buffer.is_data.lock().unwrap())
.unwrap();
if *is_data {
{
let hueco = buffer.buffer.lock().unwrap();
println!("{}", *hueco);
}
*is_data = false;
{
let mut is_space = buffer.is_space.lock().unwrap();
*is_space = true;
}
buffer.is_space_cv.notify_one();
break;
}
}
}
}
fn main() {
let buffer = Arc::new(Buffer {
is_data: Mutex::new(false),
is_data_cv: Condvar::new(),
is_space: Mutex::new(true),
is_space_cv: Condvar::new(),
buffer: Mutex::new(0),
});
let b = buffer.clone();
let p = thread::spawn(move || {
producer(b);
});
let b = buffer.clone();
let c = thread::spawn(move || {
consumer(b);
});
//thread::sleep_ms(1000);
buffer.is_space_cv.notify_one();
c.join();
}

I would encourage you to create smaller methods and reuse existing Rust types such as Option. This will allow you to simplify your code quite a bit — only one Mutex and one Condvar:
use std::thread;
use std::sync::{Arc, Condvar, Mutex};
#[derive(Debug, Default)]
struct Buffer {
data: Mutex<Option<i32>>,
data_cv: Condvar,
}
impl Buffer {
fn insert(&self, val: i32) {
let mut lock = self.data.lock().expect("Can't lock");
while lock.is_some() {
lock = self.data_cv.wait(lock).expect("Can't wait");
}
*lock = Some(val);
self.data_cv.notify_one();
}
fn remove(&self) -> i32 {
let mut lock = self.data.lock().expect("Can't lock");
while lock.is_none() {
lock = self.data_cv.wait(lock).expect("Can't wait");
}
let val = lock.take().unwrap();
self.data_cv.notify_one();
val
}
}
fn producer(buffer: &Buffer) {
for i in 0..50 {
println!("p: {}", i);
buffer.insert(i);
}
}
fn consumer(buffer: &Buffer) {
for _ in 0..50 {
let val = buffer.remove();
println!("c: {}", val);
}
}
fn main() {
let buffer = Arc::new(Buffer::default());
let b = buffer.clone();
let p = thread::spawn(move || {
producer(&b);
});
let b = buffer.clone();
let c = thread::spawn(move || {
consumer(&b);
});
c.join().expect("Consumer had an error");
p.join().expect("Producer had an error");
}
If you wanted to have a bit more performance (benchmark to see if it's worth it), you could have Condvars for the "empty" and "full" conditions separately:
#[derive(Debug, Default)]
struct Buffer {
data: Mutex<Option<i32>>,
is_empty: Condvar,
is_full: Condvar,
}
impl Buffer {
fn insert(&self, val: i32) {
let mut lock = self.data.lock().expect("Can't lock");
while lock.is_some() {
lock = self.is_empty.wait(lock).expect("Can't wait");
}
*lock = Some(val);
self.is_full.notify_one();
}
fn remove(&self) -> i32 {
let mut lock = self.data.lock().expect("Can't lock");
while lock.is_none() {
lock = self.is_full.wait(lock).expect("Can't wait");
}
let val = lock.take().unwrap();
self.is_empty.notify_one();
val
}
}

To improve the concurrency performance, you can add more slots in the buffer. The following example also supports multiple producers & consumers.
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
use std::thread;
const MAX: usize = 10;
struct Buffer {
inner: Mutex<BufferInner>,
fill_cond: Condvar,
empty_cond: Condvar,
}
impl Buffer {
fn new() -> Self {
Buffer {
inner: Mutex::new(BufferInner {
data: [Option::None; MAX],
filled: 0,
used: 0,
count: 0,
}),
fill_cond: Condvar::new(),
empty_cond: Condvar::new(),
}
}
}
struct BufferInner {
data: [Option<i32>; MAX],
filled: usize,
used: usize,
count: usize,
}
impl BufferInner {
fn put(&mut self, value: i32) {
self.data[self.filled] = Some(value);
self.filled = (self.filled + 1) % MAX;
self.count += 1;
}
fn get(&mut self) -> i32 {
let tmp: Option<i32> = self.data[self.used];
self.used = (self.used + 1) % MAX;
self.count -= 1;
tmp.unwrap()
}
}
fn producer(buffer: &Buffer) {
for i in 0..20 {
let mut guard = buffer.inner.lock().unwrap();
while guard.count == MAX {
guard = buffer.empty_cond.wait(guard).unwrap();
}
guard.put(i);
println!("producer: {}", i);
buffer.fill_cond.notify_one();
}
}
fn consumer(buffer: &Buffer) {
for _ in 0..20 {
let mut guard: MutexGuard<BufferInner> = buffer.inner.lock().unwrap();
while guard.count == 0_usize {
guard = buffer.fill_cond.wait(guard).unwrap();
}
let value = guard.get();
println!("consumer: {}", value);
buffer.empty_cond.notify_one();
}
}
fn main() {
let buffer = Arc::new(Buffer::new());
let buffer1 = Arc::clone(&buffer);
let p1 = thread::spawn(move || producer(&buffer));
let c1 = thread::spawn(move || consumer(&buffer1));
p1.join().unwrap();
c1.join().unwrap();
}

Related

How to traverse and consume a vector in given order? [duplicate]

For example, I have a Vec<String> and an array storing indexes.
let src = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let idx_arr = [2_usize, 0, 1];
The indexes stored in idx_arr comes from the range 0..src.len(), without repetition or omission.
I want to move the elements in src to another container in the given order, until the vector is completely consumed. For example,
let iter = into_iter_in_order(src, &idx_arr);
for s in iter {
// s: String
}
// or
consume_vec_in_order(src, &idx_arr, |s| {
// s: String
});
If the type of src can be changed to Vec<Option<String>>, things will be much easier, just use src[i].take(). However, it cannot.
Edit:
"Another container" refers to any container, such as a queue or hash set. Reordering in place is not the answer to the problem. It introduces the extra time cost of O(n). The ideal method should be 0-cost.
Not sure if my algorithm satisfies your requirements but here I have an algorithm that can consume the provided vector in-order without initializing a new temporary vector, which is more efficient for a memory.
fn main() {
let src = &mut vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()];
let idx_arr = [2_usize, 3, 1, 0];
consume_vector_in_order(src, idx_arr.to_vec());
println!("{:?}", src); // d , c , a , b
}
// In-place consume vector in order
fn consume_vector_in_order<T>(v: &mut Vec<T>, inds: Vec<usize>) -> &mut Vec<T>
where
T: Default,
{
let mut i: usize = 0;
let mut temp_inds = inds.to_vec();
while i < inds.to_vec().len() {
let s_index = temp_inds[i];
if s_index != i {
let new_index = temp_inds[s_index];
temp_inds.swap(s_index, new_index);
v.swap(s_index, new_index);
} else {
i += 1;
}
}
v
}
You can use the technique found in How to sort a Vec by indices? (using my answer in particular) since that can reorder the data in-place from the indices, and then its just simple iteration:
fn consume_vec_in_order<T>(mut vec: Vec<T>, order: &[usize], mut cb: impl FnMut(T)) {
sort_by_indices(&mut vec, order.to_owned());
for elem in vec {
cb(elem);
}
}
Full example available on the playground.
Edit:
An ideal method, but needs to access unstable features and functions not exposed by the standard library.
use std::alloc::{Allocator, RawVec};
use std::marker::PhantomData;
use std::mem::{self, ManuallyDrop};
use std::ptr::{self, NonNull};
#[inline]
unsafe fn into_iter_in_order<'a, T, A: Allocator>(
vec: Vec<T, A>,
order: &'a [usize],
) -> IntoIter<'a, T, A> {
unsafe {
let mut vec = ManuallyDrop::new(vec);
let cap = vec.capacity();
let alloc = ManuallyDrop::new(ptr::read(vec.allocator()));
let ptr = order.as_ptr();
let end = ptr.add(order.len());
IntoIter {
buf: NonNull::new_unchecked(vec.as_mut_ptr()),
_marker_1: PhantomData,
cap,
alloc,
ptr,
end,
_marker_2: PhantomData,
}
}
}
struct IntoIter<'a, T, A: Allocator> {
buf: NonNull<T>,
_marker_1: PhantomData<T>,
cap: usize,
alloc: ManuallyDrop<A>,
ptr: *const usize,
end: *const usize,
_marker_2: PhantomData<&'a usize>,
}
impl<T, A: Allocator> Iterator for IntoIter<T, A> {
type Item = T;
#[inline]
fn next(&mut self) -> Option<T> {
if self.ptr == self.end {
None
} else {
let idx = unsafe { *self.ptr };
self.ptr = unsafe { self.ptr.add(1) };
if T::IS_ZST {
Some(unsafe { mem::zeroed() })
} else {
Some(unsafe { ptr::read(self.buf.as_ptr().add(idx)) })
}
}
}
}
impl<#[may_dangle] T, A: Allocator> Drop for IntoIter<T, A> {
fn drop(&mut self) {
struct DropGuard<'a, T, A: Allocator>(&'a mut IntoIter<T, A>);
impl<T, A: Allocator> Drop for DropGuard<'_, T, A> {
fn drop(&mut self) {
unsafe {
// `IntoIter::alloc` is not used anymore after this and will be dropped by RawVec
let alloc = ManuallyDrop::take(&mut self.0.alloc);
// RawVec handles deallocation
let _ = RawVec::from_raw_parts_in(self.0.buf.as_ptr(), self.0.cap, alloc);
}
}
}
let guard = DropGuard(self);
// destroy the remaining elements
unsafe {
while self.ptr != self.end {
let idx = *self.ptr;
self.ptr = self.ptr.add(1);
let p = if T::IS_ZST {
self.buf.as_ptr().wrapping_byte_add(idx)
} else {
self.buf.as_ptr().add(idx)
};
ptr::drop_in_place(p);
}
}
// now `guard` will be dropped and do the rest
}
}
Example:
let src = vec![
"0".to_string(),
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string(),
];
let mut dst = vec![];
let iter = unsafe { into_iter_in_order(src, &[2, 1, 3, 0, 4]) };
for s in iter {
dst.push(s);
}
assert_eq!(dst, vec!["2", "1", "3", "0", "4"]);
My previous answer:
use std::mem;
use std::ptr;
pub unsafe fn consume_vec_in_order<T>(vec: Vec<T>, order: &[usize], mut cb: impl FnMut(T)) {
// Check whether `order` contains all numbers in 0..len without repetition
// or omission.
if cfg!(debug_assertions) {
use std::collections::HashSet;
let n = order.len();
if n != vec.len() {
panic!("The length of `order` is not equal to that of `vec`.");
}
let mut set = HashSet::<usize>::new();
for &idx in order {
if idx >= n {
panic!("`{idx}` in the `order` is out of range (0..{n}).");
} else if set.contains(&idx) {
panic!("`order` contains the repeated element `{idx}`");
} else {
set.insert(idx);
}
}
}
unsafe {
for &idx in order {
let s = ptr::read(vec.get_unchecked(idx));
cb(s);
}
vec.set_len(0);
}
}
Example:
let src = vec![
"0".to_string(),
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string(),
];
let mut dst = vec![];
consume_vec_in_order(
src,
&[2, 1, 3, 0, 4],
|elem| dst.push(elem),
);
assert_eq!(dst, vec!["2", "1", "3", "0", "4"]);

ConnectionReset error message while receiving data through socket

I'm new to Rust and have implemented a network receiver thread where in I'm testing my implementation by sending data to the same socket address the receiver socket is bound to(making use of dynamic binding by binding the socket to port 0). But I'm getting thread 'google' panicked at 'Didn't receive data: Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." }', src\ethernet_interface.rs:62:42. This is my code
src/main.rs
mod ethernet_interface;
mod WWW_sim_interface;
use crate::WWW_sim_interface::WWWSimInterface;
use std::{thread, time};
fn main() {
let www_sim_interface = WWWSimInterface::new(String::from("google"));
let mut www_sim_interface_runnable = www_sim_interface.start();
let two_seconds = time::Duration::from_secs(2);
thread::sleep(two_seconds);
www_sim_interface_runnable.terminate_WWW_sim();
www_sim_interface_runnable.join_handle.join();
}
src/WWW_sim_interface.rs
use std::net::{SocketAddr, IpAddr, Ipv4Addr};
use std::thread;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::mem;
use crate::ethernet_interface::EthernetInterface;
pub struct WWWSimInterface {
target_ID: String,
terminate_flag: AtomicBool,
eth_interface: Option<EthernetInterface>,
}
pub struct RunningWWWSimInterface {
pub join_handle: thread::JoinHandle<()>,
WWW_sim_interface: Arc<WWWSimInterface>,
}
impl WWWSimInterface {
pub fn new(target_ID: String) -> WWWSimInterface {
let mut WWW_sim_interface = WWWSimInterface {
target_ID: target_ID,
terminate_flag: AtomicBool::new(false),
eth_interface: Some(EthernetInterface::new()),
};
WWW_sim_interface.eth_interface.as_mut().expect("Error").setup_receiver(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0));
WWW_sim_interface
}
pub fn start(self) -> RunningWWWSimInterface {
let WWW_sim_interface = Arc::new(self);
let join_handle = {
let WWW_sim_interface = WWW_sim_interface.clone();
thread::Builder::new().name(WWW_sim_interface.target_ID.clone()).spawn(move || WWW_sim_interface.run()).ok().unwrap()
};
RunningWWWSimInterface {
join_handle,
WWW_sim_interface,
}
}
pub fn run(&self) {
let mut buff: [u8; 2048] = [0; 2048];
let mut msg_ID: u16;
println!("started receiver thread");
while !self.terminate_flag.load(Ordering::Relaxed) {
let data_len = self.eth_interface.as_ref().expect("Error").recv_data(&mut buff);
if data_len < 2 {
continue;
}
let vec_buff = buff[..data_len].to_vec();
let (int_bytes, rest) = buff.split_at(mem::size_of::<u16>());
msg_ID = u16::from_be_bytes(int_bytes.try_into().unwrap());
if msg_ID == 1234 {
break;
}
}
}
}
impl RunningWWW_simInterface {
pub fn terminate_WWWSim(&mut self) {
self.WWW_sim_interface.terminate_flag.store(true, Ordering::Relaxed);
let msg_ID: u16 = 1234;
// self.WWW_sim_interface.eth_interface.as_ref().expect("Error").send_data(&msg_ID.to_be_bytes());
self.WWW_sim_interface.eth_interface.as_ref().expect("Error").send_self(&msg_ID.to_be_bytes());
}
}
src/ethernet_interface.rs
use std::net::{UdpSocket, SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr};
use std::net::IpAddr::V4;
use std::net::IpAddr::V6;
pub struct EthernetInterface {
socket: Option<UdpSocket>,
recv_IP_sock_addr: Option<SocketAddr>,
send_IP_sock_addr: Option<SocketAddr>,
}
impl EthernetInterface {
pub fn new() -> EthernetInterface {
EthernetInterface {
socket: None,
recv_IP_sock_addr: None,
send_IP_sock_addr: None,
}
}
pub fn setup_receiver(&mut self, mut recv_IP_sock_addr: SocketAddr) {
let ip_addr = recv_IP_sock_addr.ip();
let first_octect: u8 = match ip_addr {
V4(ip4_addr) => ip4_addr.octets().to_vec()[0],
V6(ip6_addr) => ip6_addr.octets().to_vec()[0]
};
if first_octect > 223 && first_octect < 240 {
match ip_addr {
V4(ip4_addr) => recv_IP_sock_addr.set_ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
V6(ip6_addr) => recv_IP_sock_addr.set_ip(IpAddr::V6(Ipv6Addr::UNSPECIFIED))
};
}
self.recv_IP_sock_addr = Some(recv_IP_sock_addr);
self.socket = Some(UdpSocket::bind(self.recv_IP_sock_addr.unwrap()).unwrap());
if first_octect > 223 && first_octect < 240 {
let ip_addr = self.recv_IP_sock_addr.unwrap().ip();
match ip_addr {
V4(ip4_addr) => self.socket.as_ref().unwrap().join_multicast_v4(&ip4_addr, &Ipv4Addr::UNSPECIFIED).ok(),
V6(ip6_addr) => self.socket.as_ref().unwrap().join_multicast_v6(&ip6_addr, 0).ok()
};
}
}
pub fn setup_sender(&mut self, send_IP_sock_addr: SocketAddr) {
let ip_addr = send_IP_sock_addr.ip();
let first_octect = match ip_addr {
V4(ip4_addr) => ip4_addr.octets().to_vec()[0],
V6(ip6_addr) => ip6_addr.octets().to_vec()[0]
};
self.send_IP_sock_addr = Some(send_IP_sock_addr);
if first_octect > 223 && first_octect < 240 {
self.socket.as_ref().unwrap().set_multicast_loop_v4(false).expect("set_multicast_loop_v4 call failed");
self.socket.as_ref().unwrap().set_multicast_ttl_v4(8).expect("set_multicast_ttl_v4 call failed");
}
}
pub fn recv_data(&self, buff: &mut [u8]) -> usize {
let (number_of_bytes, src_addr) = self.socket.as_ref().unwrap().recv_from(buff)
.expect("Didn't receive data");
println!("recvd data");
number_of_bytes
}
pub fn send_data(&self, buff: &[u8]) {
self.socket.as_ref().unwrap().send_to(buff, self.send_IP_sock_addr.unwrap()).expect("couldn't send data");
}
pub fn get_host_bound_port(&self) -> u16 {
self.socket.as_ref().unwrap().local_addr().unwrap().port()
}
pub fn get_src_addr(&mut self) {
let mut buff: [u8; 2048] = [0; 2048];
let (_, src_addr) = self.socket.as_ref().unwrap().recv_from(&mut buff)
.expect("Didn't receive data");
self.send_IP_sock_addr = Some(src_addr);
}
pub fn send_self(&self, buff: &[u8]) {
self.socket.as_ref().unwrap().send_to(buff, self.recv_IP_sock_addr.unwrap()).expect("couldn't end self data");
println!("sent data");
}
}

rust lockfree ringbuffer can't work on release mode

I implement a lockfree ringbuffer, and then i test for debug is ok, but in release mode it can't work allways.
use std::path::Display;
use std::sync::Arc;
#[derive(Debug)]
pub struct RingBuffer<T, const m_size: usize> {
idx_head: usize,
idx_tail: usize,
m_data: [T; m_size],
}
pub trait Queue<T> {
fn new_empty() -> Self;
fn push(&mut self, value: T) -> bool;
fn pop(&mut self) -> Option<&T>;
fn is_full(&self) -> bool;
fn is_empty(&self) -> bool;
}
impl<T, const Size: usize> Queue<T> for RingBuffer<T, Size>
{
fn new_empty() -> Self {
RingBuffer::<T, Size> {
idx_head: 0,
idx_tail: 0,
m_data: array_init::array_init(|_| {
unsafe {
std::mem::zeroed()
}
}),
}
}
fn push(&mut self, value: T) -> bool {
let mut head = self.idx_head + 1;
if head == Size {
head = 0;
}
if head == self.idx_tail {
return false;
}
self.m_data[self.idx_head] = value;
self.idx_head = head;
return true;
}
fn pop(&mut self) -> Option<&T> {
let mut tail = self.idx_tail;
if self.idx_head == tail {
return None;
}
let res = &self.m_data[tail];
tail += 1;
if tail == Size {
tail = 0;
}
self.idx_tail = tail;
return Some(res);
}
fn is_full(&self) -> bool {
self.idx_tail == (self.idx_head + 1) % Size
}
fn is_empty(&self) -> bool {
self.idx_head == self.idx_tail
}
}
pub struct SharedRingBuffer<T, const m_size: usize> {
pub ringbuffer: Arc<RingBuffer<T, m_size>>,
}
impl<T, const Size: usize> Clone for SharedRingBuffer<T, Size> {
fn clone(&self) -> Self {
Self {
ringbuffer: self.ringbuffer.clone(),
}
}
}
impl<T, const Size: usize, > Queue<T> for SharedRingBuffer<T, Size> {
fn new_empty() -> Self {
Self {
ringbuffer: Arc::new(RingBuffer::<T, Size>::new_empty()),
}
}
fn push(&mut self, value: T) -> bool {
unsafe {
(*Arc::get_mut_unchecked(&mut self.ringbuffer)).push(value)
}
}
fn pop(&mut self) -> Option<&T> {
unsafe {
(*Arc::get_mut_unchecked(&mut self.ringbuffer)).pop()
}
}
fn is_full(&self) -> bool {
self.ringbuffer.is_full()
}
fn is_empty(&self) -> bool {
self.ringbuffer.is_empty()
}
}
////////////////////// for test//////////////////////////
fn test_speed1() {
let mut q: SharedRingBuffer<i32, 8> = SharedRingBuffer::new_empty();
let mut t0 = std::time::SystemTime::now();
let t = {
let mut q = q.clone();
std::thread::spawn(move || {
loop {
let t = match q.pop() {
None => {
// std::thread::sleep(Duration::from_millis(10));
continue;
}
Some(res) => res
};
if *t == -1 {
break;
}
std::thread::sleep(Duration::from_millis(1));
}
let now = std::time::SystemTime::now();
println!("res: {}", now.duration_since(t0).unwrap().as_millis());
})
};
for i in 0..99 {
loop {
if q.push(i) {
// std::thread::sleep(Duration::from_millis(10));
break;
}
}
}
q.push(-1);
t.join().unwrap();
}
When i addition std::thread::sleep(Duration::from_millis(10)) for q.push and q.pop method it is work well.
rustc 1.67.0-nightly (95a3a7277 2022-10-31)
binary: rustc
commit-hash: 95a3a7277b44bbd2dd3485703d9a05f64652b60e
commit-date: 2022-10-31
host: x86_64-pc-windows-msvc
release: 1.67.0-nightly
LLVM version: 15.0.4
I expect the RingBuffer can work well.
The equivalent code is:
fn test_speed2() {
let (send, recv) = channel::<i32>();
let mut is_run = SharedValue::new(true);
let mut t0 = std::time::SystemTime::now();
let t = {
let is_run = is_run.clone();
std::thread::spawn(move || {
loop {
let t = match recv.recv() {
Err(e) => {
break;
}
Ok(res) => res
};
if t == -1 {
break;
}
std::thread::sleep(Duration::from_millis(1));
}
let now = std::time::SystemTime::now();
// println!("res: {}", now.duration_since(t0).unwrap().as_millis());
})
};
for i in 0..99 {
send.send(i).unwrap();
}
send.send(-1).unwrap();
t.join().unwrap();
}
I hope ringbuffer can replace channel to communicate between two threads,Because ringbuffer is lockfree and faster.
Your code causes undefined behavior by creating two mutable references to the same object at the same time via Arc::get_mut_unchecked(). It looks like this was even your intention, but it is blatantly violating Rust's rules. Even when using unsafe, you cannot violate the requirement that mutable references are exclusive.
Running your code with cargo miri reports this undefined behavior:
error: Undefined Behavior: Data race detected between Read on thread `<unnamed>` and Write on thread `main` at alloc1894+0x10
--> bar/src/main.rs:45:12
|
45 | if self.idx_head == tail {
| ^^^^^^^^^^^^^ Data race detected between Read on thread `<unnamed>` and Write on thread `main` at alloc1894+0x10
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `<RingBuffer<i32, 8> as Queue<i32>>::pop` at bar/src/main.rs:45:12
note: inside `<SharedRingBuffer<i32, 8> as Queue<i32>>::pop` at bar/src/main.rs:89:18
--> bar/src/main.rs:89:18
|
89 | unsafe { (*Arc::get_mut_unchecked(&mut self.ringbuffer)).pop() }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside closure at bar/src/main.rs:108:31
--> bar/src/main.rs:108:31
|
108 | let t = match q.pop() {
| ^^^^^^^
You will need to rethink your design. You'll probably need a foundation like this to make it safe to modify between threads:
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::sync::atomic::AtomicUsize;
pub struct RingBuffer<T, const SIZE: usize> {
idx_head: AtomicUsize,
idx_tail: AtomicUsize,
m_data: [UnsafeCell<MaybeUninit<T>>; SIZE],
}
This is actually caused by the CPU cache,The solution is as follows:
fn push(&mut self, value: T) -> bool {
let mut head = unsafe {
std::ptr::read_volatile(&self.idx_head) + 1
};
let tail = unsafe {
std::ptr::read_volatile(&self.idx_tail)
};
if head == Size {
head = 0;
}
if head == tail {
return false;
}
self.m_data[self.idx_head] = value;
unsafe {
std::ptr::write_volatile(&mut self.idx_head, head);
}
return true;
}
fn pop(&mut self) -> Option<&T> {
let mut tail = unsafe {
std::ptr::read_volatile(&self.idx_tail)
};
let head = unsafe {
std::ptr::read_volatile(&self.idx_head)
};
if head == tail {
return None;
}
let res = &self.m_data[tail];
tail += 1;
if tail == Size {
tail = 0;
}
unsafe {
std::ptr::write_volatile(&mut self.idx_tail, tail);
}
return Some(res);
}

add lifetime for a struct that invoke method in multi-threading environment

This is my program:
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
trait Animal: Send + Sync { fn get_id(&self) -> i32; }
struct Cat {}
impl Animal for Cat {
fn get_id(&self) -> i32 { return 0; }
}
struct Thread {
id: i32,
ptr: Arc<dyn Animal>,
}
impl Thread {
pub fn multi_threading(&self) {
let shared_array = Arc::new(Mutex::new([0; 5]));
let mut handles = vec![];
for _ in 0..5 {
let array_ptr = Arc::clone(&shared_array);
let handle = thread::spawn(move ||
self.assign(&mut array_ptr.lock().unwrap())
);
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
pub fn assign(&self, array: &mut MutexGuard<[i32; 5]>) {
array[self.id as usize] = self.id * self.id + self.ptr.get_id();
}
}
unsafe impl Send for Thread {}
fn main() {
let cat = Cat {};
let ptr_cat = Arc::new(cat);
let thread = Thread { id: 0, ptr: ptr_cat.clone() };
thread.multi_threading();
}
struct Thread is defined with a pointer to a trait object, its member method multi_threading does nothing but assigning value to an array that can be accessed by several threads.
When I compile the program, the error says the &self from pub fn multi_threading(&self)
has an anonymous lifetime '_ but it needs to satisfy a 'static lifetime requirement
Now where should I add this 'static lifetime to satisfy the requirement, to get the program complied?
You can wrap your instance in an Arc itself. That way you can send it to your threads:
impl Thread {
pub fn multi_threading(self: &Arc<Self>) {
let shared_array = Arc::new(Mutex::new([0; 5]));
let mut handles = vec![];
for _ in 0..5 {
let array_ptr = Arc::clone(&shared_array);
let s = self.clone();
let handle = thread::spawn(move ||
s.assign(&mut array_ptr.lock().unwrap())
);
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
pub fn assign(&self, array: &mut MutexGuard<[i32; 5]>) {
array[self.id as usize] = self.id * self.id + self.ptr.get_id();
}
}
...
fn main() {
let cat = Cat {};
let ptr_cat = Arc::new(cat);
let thread = Arc::new(Thread { id: 0, ptr: ptr_cat.clone() });
thread.multi_threading();
}
Playground
Notice that you would not need unsafe impl Send for Thread {}, because with Arc it is safe to share it.

Why does this program block until someone connects to the FIFO / named pipe?

I found this script in the post Recommended way of IPC in Rust where a server and client are created with named pipes.
I want to understand how it works so I started debugging. When I start the server with cargo run listen, the program reaches the open function and the following happens. I know this is a feature and not a bug, but I do not understand why it happens.
In the main function the listen function is called and then the listen function calls the open function:
use libc::{c_char, mkfifo};
use serde::{Deserialize, Serialize};
use std::env::args;
use std::fs::{File, OpenOptions};
use std::io::{Error, Read, Result, Write};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
fn main() -> Result<()> {
let mut args = args();
let _ = args.next();
match args.next().as_ref().map(String::as_str) {
Some("listen") => listen()?,
Some("send") => {
let msg = args.next().unwrap();
send(msg)?;
}
_ => {
eprintln!("Please either listen or send.");
}
}
Ok(())
}
pub struct Fifo {
path: PathBuf,
}
impl Fifo {
pub fn new(path: PathBuf) -> Result<Self> {
let os_str = path.clone().into_os_string();
let slice = os_str.as_bytes();
let mut bytes = Vec::with_capacity(slice.len() + 1);
bytes.extend_from_slice(slice);
bytes.push(0); // zero terminated string
let _ = std::fs::remove_file(&path);
if unsafe { mkfifo((&bytes[0]) as *const u8 as *const c_char, 0o644) } != 0 {
Err(Error::last_os_error())
} else {
Ok(Fifo { path })
}
}
/// Blocks until anyone connects to this fifo.
pub fn open(&self) -> Result<FifoHandle> {
let mut pipe = OpenOptions::new().read(true).open(&self.path)?;
let mut pid_bytes = [0u8; 4];
pipe.read_exact(&mut pid_bytes)?;
let pid = u32::from_ne_bytes(pid_bytes);
drop(pipe);
let read = OpenOptions::new()
.read(true)
.open(format!("/tmp/rust-fifo-read.{}", pid))?;
let write = OpenOptions::new()
.write(true)
.open(format!("/tmp/rust-fifo-write.{}", pid))?;
Ok(FifoHandle { read, write })
}
}
impl Drop for Fifo {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
}
}
#[derive(Serialize, Deserialize)]
pub enum Message {
Print(String),
Ack(),
}
pub struct FifoHandle {
read: File,
write: File,
}
impl FifoHandle {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let pid = std::process::id();
let read_fifo_path = format!("/tmp/rust-fifo-write.{}", pid);
let read_fifo = Fifo::new(read_fifo_path.into())?;
let write_fifo_path = format!("/tmp/rust-fifo-read.{}", pid);
let write_fifo = Fifo::new(write_fifo_path.into())?;
let mut pipe = OpenOptions::new().write(true).open(path.as_ref())?;
let pid_bytes: [u8; 4] = u32::to_ne_bytes(pid);
pipe.write_all(&pid_bytes)?;
pipe.flush()?;
let write = OpenOptions::new().write(true).open(&write_fifo.path)?;
let read = OpenOptions::new().read(true).open(&read_fifo.path)?;
Ok(Self { read, write })
}
pub fn send_message(&mut self, msg: &Message) -> Result<()> {
let msg = bincode::serialize(msg).expect("Serialization failed");
self.write.write_all(&usize::to_ne_bytes(msg.len()))?;
self.write.write_all(&msg[..])?;
self.write.flush()
}
pub fn recv_message(&mut self) -> Result<Message> {
let mut len_bytes = [0u8; std::mem::size_of::<usize>()];
self.read.read_exact(&mut len_bytes)?;
let len = usize::from_ne_bytes(len_bytes);
let mut buf = vec![0; len];
self.read.read_exact(&mut buf[..])?;
Ok(bincode::deserialize(&buf[..]).expect("Deserialization failed"))
}
}
fn listen() -> Result<()> {
let fifo = Fifo::new(PathBuf::from("/tmp/rust-fifo"))?;
loop {
let mut handle = fifo.open()?;
std::thread::spawn(move || {
match handle.recv_message().expect("Failed to recieve message") {
Message::Print(p) => println!("{}", p),
Message::Ack() => panic!("Didn't expect Ack now."),
}
#[allow(deprecated)]
std::thread::sleep_ms(1000);
handle
.send_message(&Message::Ack())
.expect("Send message failed.");
});
}
}
fn send(s: String) -> Result<()> {
let mut handle = FifoHandle::open("/tmp/rust-fifo")?;
#[allow(deprecated)]
std::thread::sleep_ms(1000);
handle.send_message(&Message::Print(s))?;
match handle.recv_message()? {
Message::Print(p) => println!("{}", p),
Message::Ack() => {}
}
Ok(())
}

Resources