[pulseaudio-discuss] [PATCH v9] pipe-source: [DRAFT] implement autosuspend behavior

Raman Shishniou rommer at ibuffed.com
Mon Feb 26 10:03:08 UTC 2018


On 02/23/2018 05:06 PM, Georg Chini wrote:
> On 23.02.2018 13:54, Raman Shishniou wrote:
>> On 02/23/2018 01:26 PM, Georg Chini wrote:
>>> On 23.02.2018 11:03, Raman Shishniou wrote:
>>>> On 02/23/2018 11:38 AM, Georg Chini wrote:
>>>>
>>>>> But now I have another issue:
>>>>> You are polling the pipe and running the loop even if the source is user suspended.
>>>>> This seems like a waste of CPU (even more than accepting some POLLIN spam
>>>>> during wakeup transition). I know you do it to discard data that is written during
>>>>> suspend, but I wonder if there is no better way to discard that data without running
>>>>> the loop permanently.
>>>>> I am thinking of draining the pipe in the SET_STATE handler. If you are setting
>>>>> events = 0 and open the corkfd on user suspend, nothing except messages
>>>>> should wake us up. Now, when the state changes to running, you can drain the
>>>>> pipe in the SET_STATE handler. The thread loop will just run through on the first
>>>>> iteration after user suspend, because revents = 0 and chunk.length = 0. Now,
>>>>> because the source is open again, you can set events = POLLIN.
>>>>> After that, you are back to normal.
>>>>> You can safely assume writer_connected=false during user suspend (you do
>>>>> not notice when the writer disconnects if you set events = 0). If the writer
>>>>> is still connected after suspend, writer_connected will get set when you read
>>>>> the first data. It will cause an unnecessary unsuspend message, but this will
>>>>> have no effect because neither the suspend cause nor the state change.
>>>>>
>>>>> I would also suggest to use a flag like set_pollin in the comparison, set and reset
>>>>> the flag in the appropriate places and explain why in a comment. This is one of
>>>>> the situations, where a little bit more code could make the concept clearer. I don't
>>>>> mind keeping it as is however, if you think it's not worth the effort.
>>>>>
>>>> We will face two main problems if we do something like that:
>>>>
>>>> First problem - we don't know how writer will react to full pipe.
>>>> If it open pipe in non-blocking mode, it will get EAGAIN on every
>>>> write() while pipe stays full. If it open pipe in blocking mode,
>>>> it will just stuck at write() until user unsuspend the source.
>>>> I think both behaviors are bad for writer - it should contain a
>>>> special code to deal with it.
>>> I guess that should be the problem of the writer. If it is intended
>>> to write to a pipe, it must be able to deal with the situation that
>>> the pipe is full.
>>>
>> Ok, but it can just ignore EAGAIN/EWOULDBLOCK while writing to pipe
>> and we'll can get broken stream when user unsuspend source.
>> Writer should do the same handling - if it got EAGAIN/EWOULDBLOCK,
>> it should drop the buffer keeping the last frame, move it to the head
>> of next chunk and try to write next chunk. Each writer should do
>> something like this in order to be ready for pipe suspend.
>>
>> Actually this it very cheap to do poll, read data, copy some bytes
>> inside a buffer and poll again. This is about several megabits.
>>
>> For example, I do this on my laptop:
>> $ mkfifo testfifo
>> $ dd if=testfifo of=/dev/null bs=512
>>
>> And on second terminal:
>> $ timeout 5 cat /dev/zero > testfifo
>>
>> Results:
>> 6991136+0 records in
>> 6991136+0 records out
>> 3579461632 bytes (3.6 GB) copied, 5.0019 s, 716 MB/s
>>
>> I.e. 6991136 * 8 * 512 / 5 / 1000000000 ~ 5.72 Gbps
>> And 6991136 / 5 ~ 1398227 read() -> write() pairs per second
> 
> This is on your machine. I just talked to somebody who is
> using PA on a 300 MHz single core MIPS system. And there
> I guess the load would be significant.
>>

I did some tests on Allwinner H3 @ 312MHz:

$ cat poll-read-test.c 
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <string.h>

int main(void) {
    struct pollfd pollfd;
    char buffer[4096];
    int cnt = 0;
    ssize_t ret;

    pollfd.fd = open("/tmp/fifo", O_RDONLY);
    pollfd.events = POLLIN;

    if (pollfd.fd < 0) {
        perror("open");
        return 1;
    }

    for (;;) {
        ret = poll(&pollfd, 1, 0);

        if (ret < 0) {
            perror("poll");
            return 1;
        } else if (ret > 0) {
            ret = read(pollfd.fd, buffer, sizeof(buffer));

            if (ret < 0) {
                perror("read");
                return 1;
            } else if (ret == 0)
                break;

            memcpy(buffer, buffer + sizeof(buffer) - 3, 3);

            cnt++;
        }
    }

    printf("Total iterations: %d\n", cnt);
    return 0;
}

# uname -a
Linux orangepiplus2e 3.4.113-sun8i #4 SMP PREEMPT Wed Nov 22 13:45:28 CET 2017 armv7l armv7l armv7l GNU/Linux
# cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
performance
# echo 312000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq 
# echo 312000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq 
# cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq 
312000
312000
312000
312000

$ mkfifo /tmp/fifo
$ timeout 5 cat /dev/zero > /tmp/fifo

$ ./poll-read-test 
Total iterations: 94026

I.e. 94026*4096*8/5 ~ 616 Mb/s

At full speed (1200MHz):

$ ./poll-read-test 
Total iterations: 304572

I.e. 304572*4096*8/5 ~ 1996 Mb/s

So we are talking about 1-2% of CPU for audio speeds (< 10 Mb/s)
even for Arm CPU @312MHz

>>>> The second problem is hidden now because I temporary dropped
>>>> a part of code that keep frame boundaries. If we drain the pipe
>>>> as soon as user resume the source - we'll loose frame boundaries.
>>>> Audio stream will be broken for any case except s8/u8 mono.
>>>> Actually we have to read every time while suspended by user and drop
>>>> whole chunk except for not completely read last frame, move it to
>>>> the head of memchunk and do next read() at position where this frame ends.
>>> We could work around this I think. You just need to have the last
>>> read fragment available in the SET_STATE handler. Then you do
>>> not loose frame boundaries, because you continue to read where
>>> you have stopped.
>>>
>> Yep, but after resume it will be part of another frame. And only
>> if writer does right things while we a suspended.
> 
> No, you pick up reading exactly where you stop. There is no other reader.
> So you can do the same thing you would do when you post the data, which
> is discard full frames starting at the head of the buffer and then move the
> remaining fragment to the head.
> If the writer disconnects during suspend, we can assume that it delivered
> full frames, so in that case there should be no fragment left.
> 
> You are however right that there is a situation where it is possible that we
> loose frame boundaries. Whenever the pipe gets filled completely, the
> writer might do the wrong thing or it might disconnect, leaving some
> fragment in the pipe.
> 
> So, yes, I agree to continue polling during suspend. Or could we make that
> behavior optional? I'm not sure how much more work it is to implement both
> alternatives.
> 
>>
>>>> BTW, currently pipe-source PA just crashed if I try to write s24le to pipe:
>>>>
>>>> I decided to do not open a bug because almost whole pipe-source should
>>>> be rewritten, and this is what I'm doing now.
>>>>
>>> Yes, makes sense. If your patch fixes a crash bug, it has a good
>>> chance to get into 12.0 (which it would not have otherwise).
>> It depends on whether Tanu's patches come to the master or not.
>>
> They surely will because they are fixing a release-blocker bug.

--
Raman


More information about the pulseaudio-discuss mailing list