How to wrap multi-threaded callbacks in Rust? - multithreading

I'm struggling with my soundio crate, which is a wrapper for libsoundio. If you want to play a .wav file, the C library requires you to pass a callback function when you open the output stream. That callback is called in a loop in another thread, and you output the samples in it.
I can do it like this:
struct WavPlayer {
reader: hound::WavReader<BufReader<File>>,
}
impl WavPlayer {
fn write_callback(&mut self, stream: &mut soundio::OutStreamWriter) {
// Copy samples from `self.reader` to `stream`.
}
}
// In main():
let mut player = WavPlayer {
reader: ...,
};
let mut output_stream = output_dev.open_outstream(
...
|x| player.write_callback(x),
)?;
The parameter to open_outstream() is FnMut(&mut OutStreamWriter).
The problem is that output_dev takes ownership of WavPlayer and then you can't control it or monitor it any more. When the wav file ends it is really difficult to tell the main program to exit, and there's no way I can easily pause or seek it. What I really want is an interface like this:
trait SoundPlayer {
fn play(&mut self);
fn stop(&mut self);
fn seek(&mut self, pos: Duration);
fn position(&self) -> Duration;
fn get_write_callback(&self) -> &FnMut(&mut soundio::OutStreamWriter);
}
However, I cannot work out what combination of Mutex, RefCell, Arc, etc. I need for the callback. I must need shared ownership of something because I need access to the SoundPlayer from main() and (indirectly, via the callback) from output_dev, so I guess there must be an Arc in there somewhere.
And I'll need interior mutability because I really want to be able to call play() while output_dev still has a reference to the player.
Here is my current code that works, but doesn't quit when the file ends and you can't pause or seek.
I'm a bit lost here. I know I could use mpsc to send samples but that seems like a cop-out.

Related

Rust member functions as multithreaded mutable callbacks in real-time safe code

I am looking to use a member function of a struct as a mutable callback. The standard way I would do this is by wrapping my struct instance in a mutex and then in an Arc. In this use case however, my callback is a real-time audio callback which needs to be real-time safe and is triggered on a dedicated thread. This means no locking can happen that might delay the callback from occurring.
My actual member callback method must take a mutable reference to the struct (I think this is where things fall apart). The struct does contain members but these can all be atomic variables.
Here is an example of the kind of thing I'm looking for:
use std::sync::Arc;
use clap::error;
use cpal::{traits::{HostTrait, DeviceTrait}, StreamConfig, OutputCallbackInfo, StreamError};
use std::sync::atomic::AtomicI16;
pub struct AudioProcessor {
var: AtomicI16,
}
impl AudioProcessor {
fn audio_block_f32(&mut self, audio: &mut [f32], _info: &OutputCallbackInfo) {
}
fn audio_error(&mut self, error: StreamError) {
}
}
fn initAudio()
{
let out_dev = cpal::default_host().default_output_device().expect("No available output device found");
let mut supported_configs_range = out_dev.supported_output_configs().expect("Could not obtain device configs");
let config = supported_configs_range.next().expect("No available configs").with_max_sample_rate();
let proc = Arc::new(AudioProcessor{var: AtomicI16::new(5)});
let audio_callback_instance = proc.clone();
let error_callback_instance = proc.clone();
out_dev.build_output_stream(&StreamConfig::from(config), move |audio: &mut [f32], info: &OutputCallbackInfo| audio_callback_instance.audio_block_f32(audio, info), move |stream_error| error_callback_instance.clone().audio_error(stream_error));
}
Currently this code fails in the build_output_stream() function with this error message:
cannot borrow data in an Arc as mutable
trait DerefMut is required to modify through a dereference, but it is not implemented for Arc<AudioProcessor>
Are there any standard ways of dealing with problems like this?

How to implement strategies selected at run time in Rust?

I would like to write a simulation algorithm in Rust that has three main parts. The first part is a struct that maintains the current state of the system and associated methods to make allowed state transitions. The second part is a simulation strategy, this tells which state transition(s) to make next (e.g. I will have a slow but accurate simulation strategy and a quick but approximate strategy). Finally, I have a way to serialise the system after a simulation step was taken (e.g. one method writes the system to csv while another one to json).
I would like to choose both the simulation strategy and the serialisation method at run time.
Coming from Python, my first try for the main simulation loop looked like this:
let mut system = System { ... };
let simulator = GillespieAlgorithm { system: &mut system, ... }; // this will be dynamic eventually
let output_formatter = CSVFormatter { system: &system, ... }; // this will be dynamic eventually
output_formatter.start();
for _ in 1..100 {
simulator.step();
output_formatter.write_current_state();
}
output_formatter.stop();
Of course, this doesn't work because I want to borrow system twice with one of them being a mutable reference.
What would be the idiomatic Rust solution to this problem? My understanding is that I should somehow attach a behaviour dynamically to System instead of passing system to other structs.
You've mentioned in the comments, that you want to keep the &mut system in your simulation. Thats fine and you can still use system, as long as you're getting it via the GillespieAlgorithm. If you're fine to pass it to the formatter by method argument rather than constructor, this might be a solution for you (playground)
struct System();
struct GillespieAlgorithm<'a> { system: &'a mut System }
struct CSVFormatter();
fn main() {
let mut system = System();
let mut simulator : Box<dyn Algorithm> = Box::new(GillespieAlgorithm { system: &mut system }); // this will be dynamic eventually
let output_formatter: Box<dyn Formatter> = Box::new(CSVFormatter());
output_formatter.start();
for _ in 1..100 {
simulator.step();
output_formatter.write_current_state(simulator.borrow_system());
}
output_formatter.stop();
}
trait Algorithm {
fn step(&mut self) {}
fn borrow_system(&self) -> &System;
}
impl<'a> Algorithm for GillespieAlgorithm<'a> {
fn step(&mut self) {}
fn borrow_system(&self) -> &System {
self.system
}
}
trait Formatter {
fn start(&self);
fn write_current_state(&self, system: &System);
fn stop(&self);
}
impl Formatter for CSVFormatter {
fn start(&self) {}
fn write_current_state(&self, _system: &System) {}
fn stop(&self) {}
}
If you don't need the entire System in the CSVFormatter, you could also return a dedicated state struct in step() and pass this to your formatter.

AsyncRead wrapper over sync read

I met this problem while implementing AsyncRead over a synchronized read to adjust to the async world in Rust.
The sync read implementation I'm handling is a wrapper over a raw C sync implementation, much like the std::fs::File::read; therefore I would use std::io::Read for simplicity hereafter.
Here's the code:
use futures::{AsyncRead, Future};
use std::task::{Context, Poll};
use std::pin::Pin;
use tokio::task;
use std::fs::File;
use std::io::Read;
use std::io::Result;
struct FileAsyncRead {
path: String
}
impl AsyncRead for FileAsyncRead {
fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<Result<usize>> {
let path = self.path.to_owned();
let buf_len = buf.len();
let mut handle = task::spawn_blocking(move || {
let mut vec = vec![0u8; buf_len];
let mut file = File::open(path).unwrap();
let len = file.read(vec.as_mut_slice());
(vec, len)
});
match Pin::new(&mut handle).poll(cx) {
Poll::Ready(l) => {
let v_l = l.unwrap();
let _c_l = v_l.0.as_slice().read(buf);
Poll::Ready(v_l.1)
}
Poll::Pending => Poll::Pending
}
}
}
The current implementation is creating a new vector of the same size with the outer buf: &mut [u8] each time because of :
`buf` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
buf: &mut [u8],
| --------- this data with an anonymous lifetime `'_`...
My question is:
Is that possible to avoid the vector creation in spwan_blocking and mutate the buf in poll_read? To avoid vector allocation as well as copying?
Is there a better way to express this "wrapper" logic instead of spawn_blocking as well as Pin::new(&mut handle).poll(cx)? What's the more idiomatic way to do this in Rust?
Something is odd about this code:
If this code is called once, it will likely return Poll::Pending, because spawn_blocking takes time to even start a task.
If this is called multiple times, then it creates multiple unrelated tasks reading the same part of the file and potentially ignoring the result due to (1), which is probably not what you want.
What you could do to fix this is to remember the task inside the FileAsyncRead struct first time you create it, and then on the next call only start a new task if needed, and poll the existing task.
With this API you have it doesn't seem possible to avoid double buffering, because since your API is blocking, and the ReadBuf buffer is not shared, you need to do a blocking read into some other buffer, and then copy the data over when a new non-blocking call poll_read() arrives.

impl Stream cannot be unpinned

I'm trying to get data using crates_io_api.
I attempted to get data from a stream, but
I can not get it to work.
AsyncClient::all_crates returns an impl Stream. How do I get data from it? It would be helpful if you provide code.
I checked out the async book but it didn't work.
Thank you.
Here's my current code.
use crates_io_api::{AsyncClient, Error};
use futures::stream::StreamExt;
async fn get_all(query: Option<String>) -> Result<crates_io_api::Crate, Error> {
// Instantiate the client.
let client = AsyncClient::new(
"test (test#test.com)",
std::time::Duration::from_millis(10000),
)?;
let stream = client.all_crates(query);
// what should I do after?
// ERROR: `impl Stream cannot be unpinned`
while let Some(item) = stream.next().await {
// ...
}
}
This looks like a mistake on the side of crates_io_api. Getting the next element of a Stream requires that the Stream is Unpin:
pub fn next(&mut self) -> Next<'_, Self> where
Self: Unpin,
Because Next stores a reference to Self, you must guarantee that Self is not moved during the process, or risk pointer invalidation. This is what the Unpin marker trait represents. crates_io_api does not provide this guarantee (although they could, and should be), so you must make it yourself. To convert a !Unpin type to a Unpin type, you can pin it to a heap allocation:
use futures::stream::StreamExt;
let stream = client.all_crates(query).boxed();
// boxed simply calls Box::pin
while let Some(elem) = stream.next() { ... }
Or you can pin it to the stack with the pin_mut!/pin! macro:
let stream = client.all_crates(query);
futures::pin_mut!(stream);
while let Some(elem) = stream.next() { ... }
Alternatively, you could use a combinator that does not require Unpin such as for_each:
stream.for_each(|elem| ...)

Sharing Mutable Data Between Threads in Rust

I know there are hundreds of questions just like this one, but i'm having trouble wrapping my head around how to do the thing I'm trying to do.
I want an http server that accepts and processes events. On receiving/processing an event, i want the EventManager to send an update to an ApplicationMonitor that is tracking how many events have been accepted/processed. The ApplicationMonitor would also (eventually) handle things like tracking number of concurrent connections, but in this example I just want my EventManager to send an Inc('event_accepted') update to my ApplicationMonitor.
To be useful, I need the ApplicationMonitor to be able to return a snapshot of the stats when the requested through a /stats route.
So I have an ApplicationMonitor which spawns a thread and listens on a channel for incoming Stat events. When it receives a Stat event it updates the stats HashMap. The stats hashmap must be mutable within both ApplicationMonitor as well as the spawned thread.
use std::sync::mpsc;
use std::sync::mpsc::Sender;
use std::thread;
use std::thread::JoinHandle;
use std::collections::HashMap;
pub enum Stat {
Inc(&'static str),
Dec(&'static str),
Set(&'static str, i32)
}
pub struct ApplicationMonitor {
pub tx: Sender<Stat>,
pub join_handle: JoinHandle<()>
}
impl ApplicationMonitor {
pub fn new() -> ApplicationMonitor {
let (tx, rx) = mpsc::channel::<Stat>();
let mut stats: HashMap<&'static str, i32> = HashMap::new();
let join_handle = thread::spawn(move || {
for stat in rx.recv() {
match stat {
Stat::Inc(nm) => {
let current_val = stats.entry(nm).or_insert(0);
stats.insert(nm, *current_val + 1);
},
Stat::Dec(nm) => {
let current_val = stats.entry(nm).or_insert(0);
stats.insert(nm, *current_val - 1);
},
Stat::Set(nm, val) => {
stats.insert(nm, val);
}
}
}
});
let am = ApplicationMonitor {
tx,
join_handle
};
am
}
pub fn get_snapshot(&self) -> HashMap<&'static str, i32> {
self.stats.clone()
}
}
Because rx cannot be cloned, I must move the references into the closure. When I do this, I am no longer able to access stats outside of the thread.
I thought maybe I needed a second channel so the thread could communicate it's internals back out, but this doesn't work as i would need another thread to listen for that in a non-blocking way.
Is this where I'd use Arc?
How can I have stats live inside and out of the thread context?
Yes, this is a place where you'd wrap your stats in an Arc so that you can have multiple references to it from different threads. But just wrapping in an Arc will only give you a read-only view of the HashMap - if you need to be able to modify it, you'll also need to wrap it in something which guarantees that only one thing can modify it at a time. So you'll probably end up with either an Arc<Mutex<HashMap<&'static str, i32>>> or a Arc<RwLock<HashMap<&'static str, i32>>>.
Alternatively, if you're just changing the values, and not adding or removing values, you could potentially use an Arc<HashMap<&static str, AtomicU32>>, which would allow you to read and modify different values in parallel without needing to take out a Map-wide lock, but Atomics can be a little more fiddly to understand and use correctly than locks.

Resources