-
Notifications
You must be signed in to change notification settings - Fork 29
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
fix: remove unsoundness with impl Send+Sync for OutputChannel #50
Conversation
I'm conflicted on this, on the one hand, I see the appeal of not having extra unsafe code. On the other hand, the lack of Sync & Send seem to only be present because of android, with no other real problems on other platforms. If there's a way to prohibit explicitly compilation on android, I'd prefer it. Using an extra thread is probably the proper solution, but it introduces a lot more complexity and it goes against how most of the other state is handled in lowfi. As for redirecting alsa, making that compile only on Linux is probably the right call. I tried searching for a safe API to freopen, but found nothing. It's preferable to having alsa output mess up the terminal though. If there's a safe API introduced, I'll definitely switch to it. |
You might be able to do: #[cfg(target_os = "android")]
compile_error!("Android Audio API not supported due to threading shenanigans") but I don't know if the
Well, here we have essentially a system resource (like a file descriptor or socket or something), and not just a bundle of data, so I argue that it's not a good idea to carry that around with the rest of your Maybe it would be better to hide all the threading mess into a new struct, which would spawn the thread on creation and do the rest of the setup, and then your (I'm guessing you aren't interested in my original leaking idea?) |
Update on this after a bit, after ece88de, I was able to get rid of the unsafe implementation for
I think with the changes I made, this could become more practical, but it would still carry the overhead of having a whole extra thread assigned to do nothing but wait.
I'm not entirely sure, but from what I know, the leak would have to be cleaned up by the operating system. Creating an intentional memory leak doesn't really seem all that much better than lying to rust about a trait implementing Leaking does avoid unsafe code entirely and simply however, so I'd still like to keep it for consideration. Anyway, let me know what you think. |
I've taken a look, and generally speaking I agree with your changes, so I'm closing this PR. I still would like to move the logic for swapping around the file descriptors away from your application code. Perhaps I'll write a crate to do it. To make sure that your crate doesn't get unsafely built for Android, I would still add a compile-error for it. I've checked, and You can try that it works by creating a new project and doing:
This should build and run on your regular computer but fail for that |
I created https://crates.io/crates/shaddup, and also found https://crates.io/crates/gag which does the same thing in a different way. Consider using one of those. I haven't looked into exactly how |
Previously the code had this:
Now, there may not be a good reason why
OutputStream
(and particularlycpal::Stream
) are not Send (see RustAudio/cpal#793: they did this in anticipation of Android support which didn't exist at the time).However, it still feels wrong to use
unsafe
for this, as we're essentially lying to the type system.So, if there isn't anything preventing you from building it on Android, and it still has the thread limitation, then (with the Tokio multithread executor) it's going to be very easy to cause mass hysteria and thread model errors.
I identified two solutions:
OutputChannel
at the point of creation. We don't need access to it as such, because all interaction can be done with theOutputChannelHandle
. By leaking it, we make it live forever, which is probably acceptable (as we only ever intend to spawn a singlePlayer
per application instance), but it means that the on-Drop cleanup is now left up to the OS. This works fine on Linux and Windows, but it might be a problem on others. Regardless, leaking memory is a Safe Rust operation, so this should not cause soundness bugs on any platform if the underlying library is sound.OutputChannel
for us. We will talk to the thread with astd::sync::mpsc::channel
(which isSend
able, so it lives in thePlayer
). When thePlayer
is dropped, the channel is too, and that signals the thread to exit, which runs any Drop cleanup that may be needed.Initially I implemented the first approach, but then I realized that the second approach works and is even safer (as the drop-time code is now guaranteed to run).
Both approaches have been tested to work on
x86_64-unknown-linux-gnu
andx86_64-pc-windows-msvc
.In addition, I made it so that the curious code you're using to temporarily swap out the
stderr
file stream is now only compiled in on Linux: it only makes sense to use it there, because it refers to the literal path of/dev/null
.I'm not a big fan of that either, and I would look for a solution that doesn't use
unsafe
in your code.The two
freopen
calls are the only other place whereunsafe
is used, so if those were removed then you could make the entire project#![forbid(unsafe_code)]
.