Because we now have a dedicated queue per thread, we can simply add our
invoke item to the queue and then flush all the queues when we are
running in the thread of the loop.
This simplifies some things and removes potential out-of-order messages
that got queued while flushing.
Keep a thread local queue. This makes it possible for multiple threads
to write to the ringbuffer.
There is a lock to protect the list of queues. It can only be contended
when new queues are created in the threads but this can be done at
thread startup.
Fixes#3983
Make an internal queue object that implements the invoke queue.
Because we can not do invokes concurrently from different threads, this
is required to make per-thread invoke queues later.
Debug and trace log messages are often written based on the stderr
logging, where code location is always visible.
journalctl does not show the code location without extra tricks,
which makes user-submitted debug logs from journal more cryptic.
Make journal log more similar to stderr logs by prepending the code
location and log level in the log message when the log topic
level is >= DEBUG.
When iterating a pw_array of `T`, the iterator must be of type `T*`.
Otherwise the wrong pointer will be freed.
Fixes: 4a19a76dc1 ("utils: improve pw_strv_parse")
Reported-by: Coverity Scan <scan-admin@coverity.com>
We handeled "arg1 arg2 ... " before and used to split between spaces to
get the arguments for execvp but that doesn't work so well when there
are arguments with spaces.
Instead use JSON parsing to get the array of arguments. This make it
possible to use [ arg1 arg2 .. ] and quote each arg separately. You
can still use the old method and even double escape:
"\"arg1\" \"arg2 with spaces\"" or
[ "arg1" "arg2 with spaces" ]
Move the bits that are used in the realtime thread away from the bits
from the main thread to avoid bitfield races. Move some fields in rt
structs to make it explicit that they are only to be modified from the
realtime threads.
Change the GenericFd data type to SyncObj. It's probably better to
explicitly state the data type than to make something generic. Otherwise
we would need to transfer the specific fd type somewhere else and there
is no room for that in the buffer and the the metadata is not a good idea
either because it can be modified and corrupted at runtime.
Add the SyncTimeline metadata. This contains 2 points on two timelines
(SyncObj datas in the buffer). The buffer can be accessed when the
acquire_point is signaled on the timeline and when the buffer
can be released, the release_point on the timeline should be signaled.
This should be fine because it's not possible to get to the data-loop
without doing acquire.
The main purpose is to make it possible first for modules to be loaded
before the first thread is started.
Each time someone asks for the support, acquire a new data loop and fill
it in the support.
Tweaks things a little so that acquire can be called with NULL dict, in
which case it will always use the first data loop. To be moved and
spread around loops one needs to explicitly pass a node.loop.name or
node.loop.class around.
This ensures that loading a plugin from the adapters and spa plugin
loaders always get the same loop when nothing or a node.loop.name is
given.
It doesn't quite work when a node.loop.class is given because then the loop
could be different between spa node and pipewire node. To fix this, we
need to improve the node loader code to make sure the same loop is used
everywhere.
Expose the acquire_loop/release_loop functions and use them in the
modules.
Make sure the nodes created from the module use the same data loop as
the module. We need to ensure this because otherwise, the nodes might
be scheduled on different data loops and the invoke or timer logic will
fail.
Since we don't follow updates of the params on the mixer but only on the
port, we might get out of sync and fail to negotiate.
Going through the mixers for everything needs some more work.
Fixes#3971
When resample.disabled=true, which is now the default, Format has zero
rate, so latency buffers get zero size. The rate in this case is the
graph rate.
Fix by just using the delay in samples, as all streams must in any case
run at same rate for the combining to work.
Fixes: bff252ce60 ("combine-stream: actually make use of resample.disable")
Add config options to create and start multiple data loops, each with
their own priority and thread affinity if requested.
Make it possible to assign loop.classes to the data-loops. Use the
node.loop.class to find a data-loop for the node of the same class. Try
to evenly spread the nodes over the available matching loops.
With this, it is possible to separate the processing of the nodes
depending on the classes, like audio/video and improve concurency on
the server.
No attempt is done yet to move nodes between loops or to move
independent nodes to separate data loops.
Fixes#3969
We can remove most of the special async handling in adapter, filter and
stream because this is now handled in the core.
Add a node.data-loop property to assign the node to a named data-loop.
Assign the non-rt stream and filter to the main loop. This means that
the node fd will be added to the main-loop and will be woken up directly
without having to wake up the RT thread and invoke the process callback
in the main-loop first. Because non-RT implies async, we can do all of
this like we do our rt processing because the output will only be used
in the next cycle.
When node.async is set, make the node async.
Advertize SPA_IO_AsyncBuffers on mixer ports when supported. Set a new
port flag when AsyncBuffer is supported on the port.
When making a link and if one of the nodes is async and the linked ports
support AsyncBuffer, make the link async and set this as a property on
the link. For async nodes we will use SPA_IO_AsyncBuffers on the mixer
ports.
Nodes that are async will not increment the peer required counters. This
ensures that the peer can start immediately before the async node is
ready.
On an async link, writers will write to the (cycle+1 & 1) async buffers
entry and readers will read from (cycle & 1). This makes the readers read
from the previously filled area.
We need to have two very controlled areas with specific rules for who
reads and who writes where because the two nodes will run concurrently
and no special synchronization is possible otherwise.
These async nodes can be paused and blocked without blocking or xrunning
the rest of graph. If the node didn't produce anything when the next
cycle starts, the graph will run with silence.
See #3509