-
Notifications
You must be signed in to change notification settings - Fork 103
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
Clarify the allowed usages of clap_plugin_entry's init() and deinit() #366
Conversation
Hi, There is one issue with this specification, which wasn't solved before anyway but it should be addressed if we change this file. Take the following scenario:
Here A has no knowledge that the host had loaded B. Here's one way for the plugin to defensively implement init: static std::mutex g_plugin_init_mutex;
static int g_init_count = 0;
static bool do_init(const char *plugin_path)
{
/* actual init function */
}
static bool init_guard(const char *plugin_path)
{
std::lock_guard<std::mutex> guard(g_plugin_init_mutex);
++g_plugin_init_count;
if (g_plugin_init_count > 1)
return true;
if (do_init(plugin_path))
return true;
g_plugin_init_count = 0;
return false;
}
static void do_deinit()
{
/* actual deinit function */
}
static void deinit_guard()
{
std::lock_guard<std::mutex> guard(g_plugin_init_mutex);
if (g_plugin_init_count == 0)
return;
--g_plugin_init_count;
if (g_plugin_init_count == 0)
do_deinit();
} I think the correct spec would be:
|
That's a good point. I think it's a good idea for plugins to implement However, I don't think we can allow multiple subsequent calls to Moreover, I don't think this solution would help much in the scenario you described, in practice. There are plugins out there that aren't that defensive in their As of now, the only solution for "sub-hosts" like this (that I can think of, at least 🙂), is to spawn their own child process (like Yabridge does) to have their own address space for their plugins. Alternatively, since the root of the issue here is that "A has no knowledge that the host had (un)loaded B", we could perhaps come up with an extension instead, that would allow for the parent host and the sub-host (A) to synchronize their init counters for a specific plugin's DSO, so that a sub-host doesn't double-load (or unload) a plugin that the parent host is using, and vice-versa. I have some ideas, but of course that would be outside the scope of this PR. 🙂 |
We may allow it or not, it may still happen.
It is fine to not be defensive, if you enjoy customer support :)
Indeed, but that's quite a requirement versus make your
Still, it doesn't work. You have a vst2 host, hosting plugin A (vst2) and B (vst2), and both A and B open a plugin C (CLAP). You may also have a plugin that is symlinked into the vst2 and clap plugin folder, then I think the only solution, even today is to have a defensive implementation of the init/deinit thing. |
So all of my I wish we had realized this earlier of course, but I think we have to at least share that defacto reality in the spec. inits and de-inits match and can be called from any thread is, I think, the current state of affairs of what actually happens to a plugin, independent of the spec. Whether we update the documentation to say that is an interesting question about how we manage changes in the spec. |
We could make the spec say that any plugin build with clap >= 1.11.0 should implement the counter behavior. |
That's true, my concern is that it may happen more if we allow it, since changing it that way allows hosts to be less defensive on their end. As of now, hosts that call
True haha. Here I was thinking about what happens if Plugin B isn't updated anymore and can't catch up to the new spec. You won't get more customer support issues if you don't support customers at all in the first place. 😉 (Plugin A will likely get a bunch of compat issues for plugin B though.)
I don't see how making your own
Ah, right, I didn't think about that. I use so little of these MetaPlugin-like wrappers, I didn't even think one could have two of them! 😅 I also forgot about the VST -> CLAP wrappers, yeah I don't see how hosts could possibly defend against double-init in those cases.
Yeah, I agree. We could heavily suggest that plugins should be defensive in their init/deinit implementations, while still forbidding hosts from do calling |
Well I should check for new replies before clicking Send lol. Disregard my previous message, I'll just do that. 😄 |
I think we should make it say "Prior to clap 1.11 our documentation stated that init was called once, and deinit once. Unfortunately in some circumstances it is impossible for a host to realize this, thus with clap 1.11 we are simply making the statement that init calls and deinit calls must match. This means the de-facto practice of protecting your init with a locked counter is now the to-spec practice also." or some such. See what I mean? Don't make it sound like you need "if version < 1.11 then don't lock and count else lock and count" in the docs |
Yes I see what you mean. I think the best way to do this would be to assert that plugins should implement the locked counter defense strategy (since the issue affects all CLAP versions anyway), and that this becomes a hard requirement as of CLAP v1.11. While adding a hard requirement is still a breaking change technically, it doesn't break more compared to the way it is done now, so that works for me. ^^ |
yup that's the spirit of what i was getting at too. Agree. |
…ing called multiple times
I've added that in my latest commit. It's a quite a lot, but I think I've got all of the details and possible side-effects of the issue covered now. 🙂 |
Hi, Thank you very much @prokopyl . I went through it, I think the text is long for my taste, but the history told us that long is better than too short. I think it looks good for the merge, we'll still have the possibility to ajust it in the final 1.11.0 review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am okay with this. It is a detailed explanation of the issues that can arise and why there is some responsibility on both sides - host and plugin.
My style would be more condensed:
My issue with a documentation that is too long is that devs don't read it, and when they reach the bottom of it, they forgot what was in the top, so it requires multiple reads. With that plan you'd get the essential information immediately, and then it is mostly the rationale that takes more line (which you can skip), and the brain already has the big picture when reading it. |
I have some time tomorrow and will peek. I agree it should be as short as it can while being correct, but no shorter :) |
I've implemented the entry init counter in the plugin template. |
Yeah I feel it's very wordy too, while writing it I didn't find any way to condense it while still covering every edge case and the rationale at the same time. There's indeed the issue with it being too long making the brain tune out and miss things, but on the other hand if it's too short you get annoying devs like me who aren't sure what's valid or not and make lengthy discussion posts and PRs, so I guess it's a tradeoff. 😛 But your idea of separating the spec part and the rationale is a good idea I think, I'll try that. 🙂
Neat! I think it could also be worth adding a check for it in |
Took a shot at making the introductory text briefer.
wdyt? |
@baconpaul I like it as part of the rationale; but I miss the short, raw and in your face implementation instructions for the developer who's short on time and isn't interested about why the current design is what it is. |
Right. I agree so let’s have a tldr and move that text to later in the file thentldr is “init and deinit in most cases are called once, in a matched pair, when the dso is loaded / unloaded. In some rare situations it may be called multiple times in a process, so the functions must be defensive, mutex locking and counting calls if undertaking non trivial non idempotent actions.” |
Sorry, I forgot about this PR. |
I've merged it but I wish @prokopyl did the documentation update that was suggested in the discussion. |
My apologies for the delay. I know it's quite a bit late now that 1.2.0 is out, but I can still make another PR to incorporate the suggestions if you wish. |
This PR is a followup to the discussion in #365, and clarifies the following points in the specification of
clap_plugin_entry
:Assert that a CLAP DSO can be re-
init
ialized after it has beendeinit
ialized.The specification currently states that
init
"can only be called once", and thatdeinit
prevents "[any] more calls to into the DSO". These can be interpreted as "init
can only be called once ever" and "nothing in the DSO cannot be used ever again afterdeinit
", which would be an odd requirement, and indeed seems not to be the intent of the spec according to the discussion in Clarification around clap_plugin_entry init/deinit #365.This PR changes those statements to clearly indicate that re-initialization is allowed, while still forbidding to call
init
anddeinit
again if the DSO has already been initialized/de-initialized respectively (which I believe is the intent of the original wording).Replace the assertion that confusingly marks
init
anddeinit
as "thread-safe", with a more specific explanation of the threading rules for these two functions.Using the "thread-safe" term is confusing here because it is usually interpreted as "can be safely called simultaneously from multiple threads", but consecutive calls to these functions are explicitly forbidden, let alone simultaneous ones.
This is replaced with a more detailed definition, which explicitly forbids simultaneous calls, while also explaining that the host may call
init
anddeinit
from any (and different) threads.This PR also makes it explicit that those threading requirements do not apply to
get_factory
, which is thread-safe, and now uses the standard[thread-safe]
thread specification.I also took the liberty to fix a couple of unrelated typos in those specifications. 🙂