Open bluenote10 opened 4 weeks ago
Releated to the issues in https://github.com/godot-rust/gdext/issues/713, in particular https://github.com/godot-rust/gdext/issues/709 (although that one got a tailored fix, which seems to not apply here).
What surprises me: In older versions of gdext this was working without having to enable the
experimental-threads
feature.
This was "working" as a result of us not checking it. But Godot calling into Rust code through different threads can easily introduce unsoundness (one can bypass Send
/Sync
and other safety measures), so we can't safely allow it -- at least not without some sort of synchronization.
In fact experimental-threads
currently just disables these checks, but eventually we need to find a proper solution in https://github.com/godot-rust/gdext/issues/18... Part of that would also be making sure that regular Gd
stays single-threaded, so there's no overhead except in places where multithreaded is actually used.
If you have insights into how concretely Godot's multithreaded audio/video classes concretely interact with Rust, that would be very valuable... Maybe we can also think about specific patterns to make such interactions safer :thinking:
This was "working" as a result of us not checking it.
What I do not understand is that there is no Godot binding activity happening from the audio thread. Adding some Os::singleton().get_thread_caller_id()
debug output to all methods yields:
Demo::init is running on thread 1
CustomAudioStream::new is running on thread 1
Demo::ready is running on thread 1
CustomAudioStream::instantiate_playback is running on thread 1
CustomAudioStreamPlayback::new is running on thread 1
CustomAudioStreamPlayback::mix is running on thread 11
CustomAudioStreamPlayback::mix is running on thread 11
CustomAudioStreamPlayback::mix is running on thread 11
[...]
I.e., it is only that unsafe mix
method that gets called from the audio thread. But this method doesn't call into anything Godot related, no?
This raises the question where the check is actually happening? The traceback doesn't make that very clear -- or it least it is not pointing to anything on user side. So is it just happening on the wrapper side of invoking mix
? Considering that mix
is unsafe
anyway, would it make sense to skip that check for unsafe
methods -- there are not safety guarantees for that method anyway?
The unsafe
is due to a different reason, namely mix
accepting a raw pointer parameter.
It can make sense to have methods that are called from other threads unsafe
(until we have a proper solution), but how do we reliably know this? We'd probably need to hardcode such APIs in a list. I would not want to go about it the other way (turn off checks for those which happen to be unsafe
). It's also not that trivial implementation-wise, as the instance access code knows nothing about the Godot method.
The checks happen here: https://github.com/godot-rust/gdext/blob/19147e8f9cfaf962e3ec01db291e4b41a7b5ee28/godot-ffi/src/binding/single_threaded.rs#L159-L163
The following code used to run fine in older versions of
gdext
, but when switching to a more recent commit it starts panicking with:The example is basically the "hello world" of a custom audio stream:
The full traceback is as follows, but not very insightful:
Full output
``` Demo::init instantiate_playback thread 'It looks like it only panics in debug builds, and everything seems to work fine in release builds.
What surprises me: In older versions of gdext this was working without having to enable the
experimental-threads
feature. In general, I wanted to avoid the full overhead of activating this feature, and it seemed to be possible previously. From the traceback it is actually not quite obvious where the access to the binding from a different thread is happening. Is there a way to use this pattern without having to enable the feature?Isn't this pattern actually valid, considering that the pattern used to work and seems to work fine in release builds?
As far as I can see
instantiate_playback
does run on the main thread, and it is only the unsafemix
function that gets called from the audio thread. However, that function is "pure Rust", and doesn't do anything in terms of calling any binding, no?