-
Notifications
You must be signed in to change notification settings - Fork 102
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
Add clarification note about threading while GUI is alive #255
Add clarification note about threading while GUI is alive #255
Conversation
On Windows and Mac, it has to always be on the OS main thread, not only when the GUI is shown, because if you are using an OS timer you want it to tick on the main thread, even if the GUI isn't shown. |
317816a
to
a79ea43
Compare
I just modified it to make it if the GUI is ever shown, then all calls must happen on the main thread. But maybe even if the gui is never shown we also need the calls on the main thread? This would seem to make background thread processing hard for hosts and would disagree with @baconpaul's comment about the "without gui" case here: #195 (comment) |
I don't know about other hosts, but for clap-host and bitwig studio, we have just one main thread, it is actually the one in which the program's I'd like to read what other thinks, but I believe that we should be even stricter:
|
I don't see how this clarifications clarifies anything. All functions marked |
Anklang has one [main-thread] (the one int main() was called from) which is also where it loads, activates/deactivates plugins from. Also, keep in mind the use case of a simple script (e.g. python) making use of CLAP plugins. Here, [main-thread] will remain stable but [audio-thread] will always equal [main-thread]. |
I've tried to think this through, here is why I'm not sure that's a good idea:
IIRC, the Windows UI thread cannot change, but it doesn't have to be the one where main() got called, right? |
I think the most important thing is to have a clear set of rules such that race conditions can't happen when a conforming host loads a conforming plug-in. This is an area where existing plug-in formats take different approaches, so there's not necessarily an "obvious" way to resolve this. For example, in AU, the host is explicitly allowed to call the equivalent of In VST3 the opposite choice is made and most calls must happen on the OS main thread on mac and windows. I'm sure I've seen hosts bend these rules a little, but they are explicitly stated. I'm not sure what approach is right for CLAP, but I do think the project should make the rules explicit so there is no confusion. I'm happy to attempt to write up any consensus decision the project comes up with - is this PR a good place to discuss this? |
I wonder if gui.h is the right place for describing the thread rules. |
I think, that any thread specification should be added to Makes sense? |
Makes sense to me! I’ll wait for a decision on what the thread rules should actually be and then modify this PR to put the rules in the proper place. |
a79ea43
to
14251f8
Compare
Happy new year! I've modified the rules to try to match @abique's suggestion. I think this is similar to the VST3 specification. The trade-off as I see it is this: doing things this way makes it tricker for the host to correctly load and host CLAP plug-ins, and it makes loading sessions take longer for the user. But, it makes it easier to write plug-ins. I'm not really qualified to know how best to make this trade-off for the CLAP project. |
Please excuse me, I now realize that I did not pay enough attention to your original issue. My biggest concern is that indeed, you can't run the initialization phase in a background thread with that spec. I've thought a lot about this today and as soon as the plugin starts to use OS events which happens on the process's "main-thread", then it can modify its own state and callback into the host at anytime, out of control and lead to concurrency issues if you decide to load a state in background. I like the current setting where we avoid the concurrency issues. One option would be to add a flag to the plugin: "thread-safe state loading and saving"
Then for the other issues: having one main-thread for each plugin instances, I think that the way out would be to introduce a way to flag the plugin as "symbolic main-thread ready". This would imply a set of rules:
What do you think? |
Maybe the background thread can also apply to plugin activation? |
There can be different event-loops and threads for different GUI implementations in a program, at least on Linux. We're best off leaving the GUI thread specification out of this, we cannot foresee future GUI/host threading capabilities anyway.
|
Main thread migration is much harder than allowing to load a state in background. You may have timers, I/O, gui, ... which you'd need to cancel/move. |
Ok, then to clarify the idea here:
|
Because that could break the entire plugin on Windows or macOS. That seems like a valid enough reason for me. You cannot migrate the main thread on Windows and macOS. If you have created a window and a timer on thread X, then there simply is no way to move those to thread Y. And macOS is even more strict about this than Windows. |
I think I understand this proposal enough to write up a draft except for a few points of clarification (most are related to the points that @tim-janik brought up in this comment). To summarize the proposal as I understand it: Two new interfaces are added:
A few new rules are added:
The things I still don't fully understand yet: |
Yes, we are in agreement, I can see that ;-) |
I'm not sure we have a strong use case for main-thread migration atm. For now it could suffice to document that main-thread == gui-thread on mac & windows out of necessity and that this cannot be relied on on linux/unix.
We need @abique's idea on this I guess. Note, there is also http://github.com/free-audio/clap/blob/main/include/clap/ext/draft/state-context.h i.e. a "replacement" draft for the current state load/save API. That could be a place to add async state IO opt-in hints if people think that is really a good idea. |
I just re-read the discussion and think I have some answers now. ;-)
As said previously, most straight forward could be to extend /state-context.h to indicate that load/save from a background thread are ok.
As a host writer, I'd say most definitely not all main-thread APIs. E.g. adding timers currently relies on being called on the main-thread, b/c the timer is then also added to the current threads (main-thread) timer event loop.
Yes, it should be allowed. If the plugin cannot handle main-thread execution and background state IO concurrently, it MUST NOT advertise being async state IO capable.
Yes. First, the plugin only gets to call into the host in the main-thread, because the host called into the plugin from the main-thread before or after async state IO was triggered. The host MUST NOT use the state IO API from a background thread, if it cannot support concurrent main thread execution. |
LGTM |
Thanks @tim-janik, that all makes sense and I think is quite coherent with the existing threading design of clap. I think this PR is good to go as-is, I'll open a separate PR for the async loading extension and we can continue to discuss the details there. At some point I probably will also open another PR for the "non-gui main thread" extension we discussed although it seems like there's some disagreement about whether that one is worth it. |
I think there are two things worth doing in a background thread:
The plugin shall implement bg thread if there is an actual benefit (ie the load/activate is very long). |
I've opened #310 as a first draft, although it doesn't yet support activating |
Can you give an example for a lengthy activate() call that would warrant moving into a bg thread? |
Fixes #195.