-
Notifications
You must be signed in to change notification settings - Fork 243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Randomly when playing a sound (wav) a very large memory allocation happens, on Linux Raspberry Pi #606
Comments
5404319552844632832 is about 2^62 the code is trying to allocate/use a huge amount of memory in one go. It sounds to me like there might be an underflow somewhere. Regarding it only happening on your pi, there can be a ton of reasons. If the program contains unsafe code that can trigger wildly different behavior depending on the system. I can not really help without a minimal example or the code. I do not mind browsing through it? I need to know if it is a problem caused by:
There are tree things you can try:
|
This is the code for playing an audio file on a specific audio device at a specific volume. I had some problems where "sleep_until_end" would end up getting a poll error from alsa, I think. So my log was full of: "an error occurred on output stream: A backend-specific error has occurred: async fn play_sound_on_device(device: String,
volume: f32,
file_path: String,
default_sound_duration: u64) -> anyhow::Result<()> {
// IMPORTANT STUFF!
// Note that there are functions used here that uses std io functions and thread-blocking
// functions, i.e. NOT using Tokio tasks.
// Which means that other tasks running on the same thread will be blocked.
// The solution to this is to put them all in a task where it is ok to block the thread,
// ie. by using spawn_blocking.
let mut set = tokio::task::JoinSet::new();
set.spawn_blocking(move || {
// TODO check file exists first
let file = match std::fs::File::open(&file_path) {
Ok(file) => {
let metadata_string = if let Ok(metadata) = file.metadata() {
format!("{:?}", metadata)
} else {
"Unkown".to_owned()
};
info!("Successfully read file to play, path: '{file_path}', metadata: {metadata_string}");
file
},
Err(e) => {
warn!("Failed to open file, file path: '{file_path}', error: {e}");
bail!("Failed to open file, error: {e}");
}
};
let decoder = match rodio::Decoder::new(BufReader::new(file)) {
Ok(decoder) => {
info!("Successfully created decoder");
decoder
},
Err(e) => {
error!("Failed to create decoder, file path: '{file_path}', error: {e}");
bail!("Failed to create decoder, file path: '{file_path}', error: {e}");
}
};
debug!("Source has {} channels", &decoder.channels());
// Keep _stream on the stack, as it is used during playback and should only be dropped when leaving the scope.
let (_stream, device_handle) = match Self::get_output_stream(&device) {
Ok((_stream, device_handle)) => {
info!("Successfully created stream and device handle");
(_stream, device_handle)
},
Err(e) => {
error!("Failed to get output stream, device: '{device}', error: {e}");
bail!("Failed to get output stream, device: '{device}', error: {e}");
}
};
let sink = match rodio::Sink::try_new(&device_handle) {
Ok(sink) => {
info!("Successfully created sink");
sink
},
Err(e) => {
error!("Failed to create new sink, device: '{device}', error: {e}");
bail!("Failed to create new sink, device: '{device}', error: {e}");
}
};
let duration = decoder.total_duration();
sink.set_volume(volume);
sink.append(decoder);
match duration {
Some(mut dur) => {
debug!("Will play sound '{file_path}' on device '{device}' for a duration of {} ms, at volume: {volume}", dur.as_millis());
dur = dur + Duration::from_millis(50);
thread::sleep(dur);
if sink.empty() {
info!("Played sound '{file_path}' on device '{device}' for {} ms, at volume: {volume}", dur.as_millis());
} else {
error!("The callout should have been done by now, will stop it, number of sounds left: {}", sink.len());
sink.stop();
}
},
None => {
warn!("Will play sound '{file_path}' on device '{device}'. Didn't find an duration, will shut it down after a maximum of {default_sound_duration} s");
sink.sleep_until_end();
}
}
Ok(())
});
set.spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_millis(default_sound_duration)).await;
bail!("Sound playing reach maximum duration of {default_sound_duration} s, will shut down tasks")
});
while let Some(res) = set.join_next().await {
return res?;
}
Ok(())
} |
How often is this function called? If a lot of sinks are created that might explain the ALSA error. I do not think the spawn_blocking code is actually aborted when the JoinSet is dropped. Its rather hidden in their docs:
Still this function as is should never create that large a memory allocation. If it does then thats on rodio. Could you run your program using gdb (this might be a reasonable tutorial)? |
At most at an 2 min interval. More commonly like every 10 minutes.
When you are using jointset, I think you can abort blocking, but I have to run some tests to actually see if it works.
I'll try to set up gdb, the problem is that the error happens sporadic. |
Do run the test, however reading the source code of tokio it seems they can not be aborted. Joinset is just a Set in which the JoinHandles are put with the added benefit that it calls abort on all the handles when the set drops |
You can see if you can change something to your code to make it happen all the time. But that is really hard. At this point I would just rewrite it a bit and hope the error goes away. |
Thanks for pointing that out for me. I wrote a test and you are correct. It is just really weird that the tokio documentation is that contradictory ... I'll rewrite my code, to handle it differently. |
I managed to get some more logs via journalctl:
Which seems to point to cpal? |
I see nothing suspicious here, could you point it out for me? |
These three lines:
I do not know what "kernel: audit" means, but both lines mentions "comm="cpal_alsa_out". |
I noticed some confusion when users interact with JoinSet. While task::spawn_blocking notes that blocking code can not be aborted that same warning is not present in the JoinSet. While this makes perfect sense when you know how async works in Rust it is difficult for users less experienced with the inner workings of async. Example: RustAudio/rodio#606 (comment)
Sometimes when my program is going to play an audio file (wav), my program crashes with the error:
memory allocation of 5404319552844632832 bytes failed
After that happens I have to reboot the computer to make audio work again. I have searched and found nothing on this issue.
Unfortunately I do not have a small code example, and I have it happened only when my program is running on a Raspberry Pi . I have not been able to reproduce it on my laptop.
I would love to get a hint on where I should start looking for a solution.
The text was updated successfully, but these errors were encountered: