On 27.11.2024 02:51, ichthyo wrote:
- most notably, the InterChange is accessed. But this is benign.
It is a communication service, and thus it is made to be accessed
from all threads.
On 02.12.24 12:14, Kristian Amlie wrote:
Possibly, but keep in mind that LV2 hosts may choose to run different
instances in different threads, and may even destroy one while another one
is running. They are meant to be fully isolated instances.
Admitted, but this is exactly my point: a communication service is made
to be accessed concurrently from different threads. A single call
involves only processing of data in the call stack, and then finally
places a message into the ring buffer.
The only possible "risk" with InterChange is that is is a really huge
pile of code and introducing any state /before/ going through the
ringbuffers would be dangerous
On 27.11.2024 02:51, ichthyo wrote:
But if the other thread is already running, we're again on slippery ground.
The C++ standard does not specify how the promise works internally and if
it is safe to just grab it from another thread.
On 02.12.24 12:14, Kristian Amlie wrote:
We are talking about a pointer to either SynthEngine or, if we isolate it,
the rootAnchor. And my point is that this will not change during one plugin
instance. Will it? So grabbing it from another thread should be perfectly
safe, because within one instance, it can essentially be considered a
constant.
This kind of argument is what I meant with "slippery ground"
The truth is, this argument is wrong, because you get no guarantee
that you see the data from the other thread.
To use the catchy phrase: "It is not a question if,
but only when it happens that you get undefined behaviour."
I know this is counter intuitive, thus let's consider the situation...
- Both threads are already running.
- Both threads are assigned to a different core.
Thread-A places a new pointer value to a memory location
But either that memory location, or the memory referred to by the pointer
could be in the CPU cache of the core processing Thread-A
Or the CPU cache of the other core processing Thread-B could just happen
to have this very location sitting in its local cache. Local CPU cache
content can be used right away without having to care if any other core
did something to the very same address in its own local cache.
It is totally up to the actual processor how to handle this situation.
Notably x86_64 performs much more magic here than e.g. ARM, which means,
typically you need to press an x86 system harder than an ARM system
in order to actually observe such effects reproducible in a test.
But no multicore processor with local caches provides any guarantees!
Again, to stress the point: it is not necessarily the SynthEngine* itself,
it can also be memory allocated to the SynthEngine data (e.g. the RootAnchor)
The only way to be sure is to have a *visibility barrier* in between.
Effectively this means that both cores are forced to flush their caches.
Both cores will have to discard their internal instruction pipelines,
do the bus access to main memory and then restart their instruction pipelines.
If you start a new thread, you automatically get such a barrier.
For that reason, you can be sure that you see all data that was in memory
*before starting* that thread. But after that point, such a barrier must be
caused explicitly. Which is done by synchronisation means. Thus, either
by issuing the barrier instructions directly (in assembly), or by using
- a Mutex
- a Semaphore
- a Future/Promise
- an exchange via Atomic variable
Sometimes I wish back the glorious 90ties where we all thought
that concurrent programming is easy. Just because at that time,
PCs did not perform code concurrently at all, but on a single
core with time slices. So the only problem we had at that time
was that your code could be interrupted at any point.
But memory was memory in the 90ies.
On 02.12.24 12:14, Kristian Amlie wrote:
The LV2 plugin depends on the LV2_INSTANCE_ACCESS feature, which guarantees
that a pointer to the core plugin is available, ....
This is true and might be part of the solution
...IOW SynthEngine (directly or indirectly).
No, this would be a too far reaching conclusion.
The fact that you can see the core plugin does not imply that you can see
data the core plugin thread changes during its operation, nor does it mean
that you can see data that is reachable transitively through that path.
This is the rationale why I placed such a barrier explicitly into
the start-up of the GUI plugin. Did you notice that?
With adding that, what guarantees do we have at that point?
- The LV2 standard provides that we can see the Core plugin
- The sync on corePlugin->isReady ensures that we see the changes
made by the corePlugin *before setting this flag*
This means, any data changes made below the call graph of
InstanceManager::startPluginInstance(PluginCreator)
So are we fine now?
It turns out yes, but unfortunately only via an indirect argument:
The point is, we actually launch the GUI from another callback,
which happens *after* initialising the GUI plugin.
Only at that point do we need the contents of the InterfaceAnchor DTO.
We can call a function on the SynthEngine. And because this function
collects data that is *as it stands now* already prepared when the
SynthEngine object is created, we are in safe terrain.
But is this good design? Certainly not.
So I am not really happy with that solution, since all hinges on such
a long and convoluted chain of arguments, with lots of subtle points.
And even more so, since we'd have to build a similar argument for the
stand-alone case too.
But at least it seems better as the current solution, since it is
a direct access and is not related to the timeout any more.
I'll try to code it up to see how it looks.
The actual call should be quite easy to make.
-- Hermann
_______________________________________________
Yoshimi-devel mailing list
Yoshimi-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/yoshimi-devel