Commit graph

105 commits

Author SHA1 Message Date
Pauli Virtanen
eaea03c26c spa: export log topic enumerations 2024-01-04 10:02:55 +00:00
Wim Taymans
ee6e7021f0 loop: rate limit xrun messages
When the reader thread locks up for some reason, avoid excessive
logs about the invoke queue being filled.

See #3532
2023-09-30 09:29:20 +02:00
Wim Taymans
efea7ad060 hooks: add and use _fast callback function
Add a _fast callback function that skips the version and method check.
We can use this in places where performance is critical when we do the
check out of the critical loops.

Make all system methods _fast calls. We expect them to exist and have
the right version. If we add new versions we can make them slow.
2023-05-06 00:27:12 +02:00
Wim Taymans
4b5b94303e loop: clear rmask after dispatching all sources
To make the unit tests work again.
2023-05-05 18:36:50 +02:00
Wim Taymans
fbf17cf980 loop: add optimized non-cancellable iterate
Only use the more heavy cancellable loop when the loop.cancel property
was set. Makes pipewire go from 5% to 3% in high frequency wakeups.
2023-05-05 17:41:37 +02:00
Wim Taymans
67c38490a5 move some trace to trace_fp 2023-05-05 17:41:13 +02:00
Wim Taymans
74831aa967 support: add support for checking loop context
Add check for running the the loop context and thread.

Add checks in filter and stream to avoid doing things when not run from
the context main-loop because this can crash things when doing IPC from
concurrent threads.
2023-04-04 16:19:41 +02:00
Wim Taymans
f2be2923e6 thread: use pthread_equal to compare thread ids 2023-04-04 12:43:25 +02:00
Barnabás Pőcze
0e0a2627aa treewide: print pthread_t as a pointer
On glibc, `pthread_t` is `unsigned long int` while on musl
it has a pointer type. To avoid format string warnings,
cast it to `void *` and use the `%p` format specifier.
2023-02-25 20:45:28 +01:00
Barnabás Pőcze
934ab3036e treewide: use SPDX tags to specify copyright information
SPDX tags make the licensing information easy to understand and clear,
and they are machine parseable.

See https://spdx.dev for more information.
2023-02-16 10:54:48 +00:00
Wim Taymans
ddf6e7ae91 loop: don't write from multiple threads
We can only write from one thread to the ringbuffer so bypass the
ringbuffer when doing in-thread invoke. Only flush the current
items so that out-of-thread items don't get inserted.
2022-12-08 08:01:40 +01:00
Wim Taymans
8ecfcbf884 loop: support recursive loop flush
Always append the item to the ringbuffer, even if we are invoking from
the thread itself. This ensure all items are always invoked in the
right order.

If we invoke from the thread, flush all items of the ringbuffer and
return.

Make sure to set the callback to NULL before invoking so that recursive
invoke doesn't call it again.

When while flushing the items we get a recursive invoke, detect this
with a counter and return immediately.
2022-12-07 22:00:58 +01:00
Wim Taymans
97f95f51c5 loop: only flush pending items
Mostly useful for when invoking from the thread itself so that the new
invoke item is executed before new items are added.

Imagine this case with module-loopback:
     - data-loop goes into the capture process function
          - mainloop invokes node remove of capture and waits
     - data-loop invokes trigger -> node remove is first executed, mainloop
                                    is woken up
          - mainloop continues
    	  - mainloop invokes remove of playback and waits
     - data-loop continues flushing the ringbuffer -> playback remove is
                                 executed, mainloop wakes up
    	  - mainloop continues destroying items, frees playback
    	    and capture streams
     - data-loop finaly gets to flush the trigger and crashes because
            streams are gone.
2022-12-07 19:52:13 +01:00
Wim Taymans
61e600970b loop: improve error handling from fds
When we try to read one of the events and there was an error, don't
signal the callback. If the error is something else than EAGAIN log
a warning.

Especially for timerfd, EAGAIN can happen when the timer changed
while polling. This can happen when running the profiler because it
polls and updates the timer from different threads.
2022-12-01 20:03:06 +01:00
Wim Taymans
67dcb72295 loop: don't assert in cleanup
Just issue a warning instead of asserting. Firefox does strange things
to the fds that make things crash otherwise.
2022-11-08 15:45:55 +01:00
Wim Taymans
0ad7cb3298 loop: flush items before stopping
Before leaving the loop, flush out any pending items in the invoke
queue.

See #2631
2022-08-09 20:38:06 +02:00
Barnabás Pőcze
479896279e spa: support: loop: handle cancellation better
Register a pthread cleanup handler to guarantee
that `spa_source::{priv, rmask}` are cleared even
if the thread is cancelled while the loop is dispatching.

This is necessary, otherwise `spa_source::priv` could point
to the stack of the cancelled thread, which will lead to
problems like this later:

  Program terminated with signal SIGSEGV, Segmentation fault.
  #0  0x00007f846b025be2 in detach_source (source=0x7f845f435f60) at ../spa/plugins/support/loop.c:144
  144      e->data = NULL;
  [Current thread is 1 (LWP 5274)]
  (gdb) p e
  $1 = (struct spa_poll_event *) 0x7f845e297820
  (gdb) bt
  #0  0x00007f846b025be2 in detach_source (source=0x7f845f435f60) at ../spa/plugins/support/loop.c:144
  #1  0x00007f846b0276ad in free_source (s=0x7f845f435f60) at ../spa/plugins/support/loop.c:359
  #2  0x00007f846b02a453 in loop_destroy_source (object=0x7f845f3af478, source=0x7f845f435f60) at ../spa/plugins/support/loop.c:786
  #3  0x00007f846b02a886 in impl_clear (handle=0x7f845f3af478) at ../spa/plugins/support/loop.c:859
  #4  0x00007f846b172f40 in unref_handle (handle=0x7f845f3af450) at ../src/pipewire/pipewire.c:211
  #5  0x00007f846b173579 in pw_unload_spa_handle (handle=0x7f845f3af478) at ../src/pipewire/pipewire.c:346
  #6  0x00007f846b15a761 in pw_loop_destroy (loop=0x7f845f434e30) at ../src/pipewire/loop.c:159
  #7  0x00007f846b135d8e in pw_data_loop_destroy (loop=0x7f845f434cb0) at ../src/pipewire/data-loop.c:166
  #8  0x00007f846b12c31c in pw_context_destroy (context=0x7f845f41c690) at ../src/pipewire/context.c:485
  #9  0x00007f846b3ddf9e in jack_client_close (client=0x7f845f3c1030) at ../pipewire-jack/src/pipewire-jack.c:3481
  ...
2022-06-02 00:24:24 +02:00
Wim Taymans
74da804e97 move some debug to fastpath 2022-03-28 16:25:00 +02:00
Wim Taymans
54933b67fd loop: clean polling flag when leaving the loop
When we leave the last recursive enter of the loop, clear the polling
flag.

It might be possible that it was not cleared because the loop might have
been killed with pthread_kill. In any case, the _leave calls need to be
made in this case as well.

This fixes issues when jack clients stop because it triggers and assert
because the polling flag is still active when the object is cleared.

See !1171
2022-03-07 10:27:22 +01:00
Barnabás Pőcze
b12f24efb9 spa: support: loop: don't initialize source->loop
There is no need to initialize the `loop`, `rmask`, or `priv`
members of a `spa_source` because `loop_add_source()` will
take care of that.
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
d6dfa93b40 spa: support: loop: assert source's loop when dispatching 2022-03-06 18:40:43 +00:00
Barnabás Pőcze
235b155b75 spa: support: loop: assert loop is not polling when destroyed
Assert that the loop is not polling when `impl_clear()` is called.
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
616519d704 spa: support: loop: assert loop is not polling when source is removed
`spa_source`s whose backing storage is not managed by the loop
cannot be safely removed while the loop is polling.
Assert that it does not happen.
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
7647ea7c83 spa: support: loop: fix use-after-free when loop is reentered
The core of the issue is the following: what happens if an
active source is destroyed before it could be dispatched?

For loop-managed sources (`struct source_impl`) this was addressed
by storing all destroyed sources in a list, and only freeing them
after dispatching has been finished. (0eb73f0f06)
This approach works for both strictly single-threaded
and `pw_thread_loop` loops assuming the loop is not
reentered.

However, if the loop is reentered, there can still be issues.
Assume that in one iteration sources A and B are active,
and returned from the system call, and source B is destroyed
before the loop starts dispatching. Consider what happens when
"A" is dispatched first, and it reenters the loop with timeout 0.
Imagine there are no new events, so `loop_iterate()` will immediately
return, but it will first destroy everything in the destroy list
(this is done at the end of `loop_iterate()`).
And herein lies the problem. In the previous iteration,
there exists a `spa_poll_event` object which points to source "B",
but that has just been destroyed at the end of the recursive
iteration. This will trigger a use-after-free once the previous
iteration inspects it.

Fix that by processing the destroy list right after first
processing the returned `spa_poll_event` objects, and
"detach" the source from the loop and its iterations
in `process_destroy()` before the source is destroyed.

See #2114 #2147
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
2eb36c00c1 spa: support: loop: add polling flag
Store whether or not the loop is currently polling, i.e.
calling `spa_system_pollfd_wait()`.
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
4ed0365976 spa: support: loop: assert source type 2022-03-06 18:40:43 +00:00
Barnabás Pőcze
cfc8510ce8 spa: support: loop: add some invariant assertions 2022-03-06 18:40:43 +00:00
Barnabás Pőcze
55ee5ec8b2 spa: support: loop: rename variables
It may be a little confusing that both the loop object
and the `source_impl` objects are referred to with variables
named `impl`. For this reason, rename all source_impl objects
named `impl` to `s`.
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
a4e7042176 spa: support: loop: do not return early in case of an error
It is expected that `nfds` is non-negative in the vast majority
of cases, so hopefully the runtime performance will not be
significantly affected by removing the check. This way
it is guaranteed that the destroy list is processed.
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
275e23a34d spa: support: loop: print previous mask when updating
Print the previous event mask in `loop_update_io()`
as well as the new one.
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
8941fc2866 spa: support: loop: get array size using macro 2022-03-06 18:40:43 +00:00
Barnabás Pőcze
cb8c2d8857 spa: support: loop: reset rmask after dispatch
Reset the `rmask` of the sources to zero after
dispatching the callbacks. This way the sources
are always as up-to-date as possible.
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
a22ce76dbf spa: support: loop: initialize rmask
Set `rmask` to zero when a source is added to,
or removed from the loop.
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
e2287f35db spa: support: loop: move struct members
Move the boolean members of `struct source_impl` to the end
of the struct. This changes the size of the struct from
104 bytes to 96 bytes on x86-64.

Before:

struct source_impl {
        struct spa_source          source;               /*     0    48 */
        struct impl *              impl;                 /*    48     8 */
        struct spa_list            link;                 /*    56    16 */
        /* --- cacheline 1 boundary (64 bytes) was 8 bytes ago --- */
        _Bool                      close;                /*    72     1 */

        /* XXX 7 bytes hole, try to pack */

        union {
                spa_source_io_func_t io;                 /*    80     8 */
                spa_source_idle_func_t idle;             /*    80     8 */
                spa_source_event_func_t event;           /*    80     8 */
                spa_source_timer_func_t timer;           /*    80     8 */
                spa_source_signal_func_t signal;         /*    80     8 */
        } func;                                          /*    80     8 */
        _Bool                      enabled;              /*    88     1 */

        /* XXX 7 bytes hole, try to pack */

        struct spa_source *        fallback;             /*    96     8 */

        /* size: 104, cachelines: 2, members: 7 */
        /* sum members: 90, holes: 2, sum holes: 14 */
        /* last cacheline: 40 bytes */
};

After:

struct source_impl {
        struct spa_source          source;               /*     0    48 */
        struct impl *              impl;                 /*    48     8 */
        struct spa_list            link;                 /*    56    16 */
        /* --- cacheline 1 boundary (64 bytes) was 8 bytes ago --- */
        union {
                spa_source_io_func_t io;                 /*    72     8 */
                spa_source_idle_func_t idle;             /*    72     8 */
                spa_source_event_func_t event;           /*    72     8 */
                spa_source_timer_func_t timer;           /*    72     8 */
                spa_source_signal_func_t signal;         /*    72     8 */
        } func;                                          /*    72     8 */
        struct spa_source *        fallback;             /*    80     8 */
        _Bool                      close;                /*    88     1 */
        _Bool                      enabled;              /*    89     1 */

        /* size: 96, cachelines: 2, members: 7 */
        /* padding: 6 */
        /* last cacheline: 32 bytes */
};
2022-03-06 18:40:43 +00:00
Barnabás Pőcze
16f63a3c8f Revert "loop: remove destroy list"
This reverts commit c474846c42.
In addition, `s->loop` is also checked before dispatching a source.

The destroy list is needed in the presence of threads. The
issue is that a source may be destroyed between `epoll_wait()`
returning and thread loop lock being acquired. If this
source is active, then a use-after-free will be triggered
when the thread loop acquires the lock and starts dispatching
the sources.

  thread 1                       thread 2
 ----------                     ----------
                                loop_iterate
                                  spa_loop_control_hook_before
                                    // release lock

 pw_thread_loop_lock

                                  spa_system_pollfd_wait
                                    // assume it returns with source A

 pw_loop_destroy_source(..., A)
  // frees storage of A

 pw_thread_loop_unlock
                                  spa_loop_control_hook_after
                                    // acquire the lock

                                  for (...) {
                                    struct spa_source *s = ep[i].data;
                                    s->rmask = ep[i].events;
                                      // use-after-free if `s` refers to
                                      // the previously freed `A`

Fixes #2147
2022-02-18 20:31:14 +01:00
Barnabás Pőcze
bf886ba209 support: also protect against recursive invocations
Add an extra private field to the source to store the pollevent of
the current iteration. This changes ABI but it seems an embedded source
is not used outside of our own plugins and the unit test doesn't test
this ABI case.

Whenever a source is removed, we can set the data field of the
pollevent to NULL so that it won't be handled in any iteration anymore.

Avoid dispatching the same event multiple times when doing recursive
iterations.

Add some more unit tests for this.

Fixes #2114
2022-02-08 17:21:10 +01:00
Alexandre BIQUE
0b637c3291 support: loop_enter/leave hardening
This commit adds a counter for loop_enter/leave and checks:

 - consecutive enter are used on the same thread
 - leave is used on the same thread as enter
 - at destruction, the enter_count must be 0
2022-02-08 12:02:50 +01:00
Wim Taymans
c474846c42 loop: remove destroy list
Now that sources can't be dispatched anymore after a _remove, we don't
need to keep the destroy_list anymore and we can free the source
immediately.

See #2114
2022-02-08 10:18:02 +01:00
Wim Taymans
45d911641b loop: handle remove while dispatching better
Keep the array of dispatched sources around in the loop. When a source
is removed while dispatching, set the data to NULL so that we don't try
to deref the source again or call its function.

Fixes #2114
2022-02-08 10:17:13 +01:00
Wim Taymans
6ece5d810c loop: invoke immediately when loop is not running
Or else we might never get our callback called or worse, block forever,
waiting for the response.
2022-01-18 20:03:01 +01:00
Wim Taymans
30982775d9 support: use defines for alignment 2022-01-03 11:14:15 +01:00
Peter Hutterer
b3646743c1 spa: sprinkle more log topics into spa 2021-09-28 09:35:39 +02:00
Wim Taymans
78f52a7073 loop: avoid corruption of ringbuffer
The ringbuffer can't be written to from multiple threads.

When both the main loop and data thread do _invoke, they both write to
the ringbuffer and cause it to be corrupted because the ringbuffer is
not multi-writer safe.

Doing invoke from the thread itself is usually done to flush things out
so we really only need to flush the ringbuffer and call the callback.

See #1451
2021-07-26 11:39:48 +02:00
Wim Taymans
a91502b3e0 loop: improve invoke avail check
First calculate the size of the aligned payload and then check if
we can fit this aligned payload in the remaining space in the
ringbuffer.

Otherwise we might be able to fit the item + payload in the remaining
space but then place the alignment bytes at the begginning, which would
break alignment of the next invoke_item struct.
2021-07-19 10:12:15 +02:00
Wim Taymans
7f4fa64291 loop: Fix crash because of overflow
Also check if there is enough space to write the payload bytes.

We check if there is enough space for the invoke_item structure first.
Then we calculate how much bytes we need to use for the payload but we
fail to check if we can actually write that much data, risking
overwriting existing data from the ringbuffer and causing a crash later
when we try to jump to invalid memory.

Add some more comments.
2021-07-19 09:53:23 +02:00
Peter Hutterer
7697ed0757 treewide: replace strcmp() == 0 with spa_streq()
This change is only done in source files for now, header files will be done
separately.
2021-05-18 22:10:27 +10:00
Peter Hutterer
2405f0942b spa/buffer: rename SPA_MEMBER to SPA_PTROFF
SPA_MEMBER is misleading, all we're doing here is pointer+offset and a
type-casting the result. Rename to SPA_PTROFF which is more expressive (and
has the same number of characters so we don't need to re-indent).
2021-05-06 09:39:39 +00:00
Wim Taymans
f26c642055 loop: initialize some variables
Just in case the read fails.
2021-03-27 19:23:34 +01:00
Wim Taymans
4f816c1fb0 loop: never try to block in the thread
When we are calling invoke from the thread, the call will be completed
in the thread and there is no need to block for completion.
2021-03-10 13:01:19 +01:00
Wim Taymans
08ba6097a1 loop: align buffer and invoke_items to 8 bytes
To avoid unaligned access messages from sanitizer

See #497
2020-12-21 20:46:13 +01:00