Hi,

I emailed alsa-devel yesterday reporting a possible deadlock in the
alsa-pulse plugin where a call to snd_pcm_writei would block forever after
pausing and then resuming with snd_pcm_pause.  The original email is here:
http://mailman.alsa-project.org/pipermail/alsa-devel/2010-November/033242.html

I've since managed to write a testcase that reproduces the same problem
using the PulseAudio client API directly.  The testcase is attached.  If you
can reproduce the problem, it'll eventually stop making progess and
continuously print 'wsize=3000, need >= 6000, corked=0'.  The testcase may
look unusual, but I think it's an accurate minimization of the problem I
originally encountered with the alsa-pulse plugin.

It seems that corking and uncorking the stream when the writable_size is
less than minreq can result in playback not resuming after uncorking.  The
testcase tries to exacerbate the problem by writing additional data to the
stream just before pausing so that writable_size is less than minreq would
be.  After uncorking, is_corked returns false and writable_size never grows.

Thanks,
-mjg
-- 
Matthew Gregan                     |/
                                  /|                    [email protected]
#include <sys/time.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pulse/pulseaudio.h>

#define RATE 48000
#define WRITESZ 6000U

struct state {
  pa_threaded_mainloop * mainloop;
  pa_context * context;
  pa_stream * stream;
};

void context_state_cb(pa_context * c, void * m)
{
  assert(PA_CONTEXT_IS_GOOD(pa_context_get_state(c)));
  pa_threaded_mainloop_signal(m, 0);
}

void stream_success_cb(pa_stream * stream, int success, void * m)
{
  pa_threaded_mainloop_signal(m, 0);
}

void stream_cb(pa_stream * stream, void * m)
{
  assert(PA_STREAM_IS_GOOD(pa_stream_get_state(stream)));
  pa_threaded_mainloop_signal(m, 0);
}

void stream_request_cb(pa_stream * stream, size_t length, void * m)
{
#if 0
  fprintf(stderr, "stream_request %zu\n", length);
#endif
}

void pause_stream(struct state * s, int pause)
{
  pa_operation * o;

  pa_threaded_mainloop_lock(s->mainloop);
  o = pa_stream_cork(s->stream, pause, stream_success_cb, s->mainloop);
  for (;;) {
    if (pa_operation_get_state(o) != PA_OPERATION_RUNNING)
      break;
    pa_threaded_mainloop_wait(s->mainloop);
  }
  pa_threaded_mainloop_unlock(s->mainloop);
}

void state_wait(struct state * s, pa_context_state_t wanted)
{
  pa_threaded_mainloop_lock(s->mainloop);
  for (;;) {
    pa_context_state_t state = pa_context_get_state(s->context);
    assert(PA_CONTEXT_IS_GOOD(state));
    if (state == wanted)
      break;

    pa_threaded_mainloop_wait(s->mainloop);
  }
  pa_threaded_mainloop_unlock(s->mainloop);
}

void s_state_wait(struct state * s, pa_stream_state_t wanted)
{
  pa_threaded_mainloop_lock(s->mainloop);
  for (;;) {
    pa_stream_state_t state = pa_stream_get_state(s->stream);
    assert(PA_STREAM_IS_GOOD(state));
    if (state == wanted)
      break;

    pa_threaded_mainloop_wait(s->mainloop);
  }
  pa_threaded_mainloop_unlock(s->mainloop);
}

void setup(struct state * s, int early_request)
{
  pa_sample_spec ss;
  pa_buffer_attr battr;
  pa_stream_flags_t flags;

  ss.format = PA_SAMPLE_S16LE;
  ss.channels = 1;
  ss.rate = RATE;

  battr.maxlength = 4 * 1024 * 1024;
  battr.tlength = 12000 * 2;
  battr.prebuf = battr.tlength - (battr.tlength / 4);
  battr.minreq = battr.tlength / 4;
  battr.fragsize = battr.tlength / 4;

  s->mainloop = pa_threaded_mainloop_new();
  s->context = pa_context_new(pa_threaded_mainloop_get_api(s->mainloop), "foo test");
  pa_context_set_state_callback(s->context, context_state_cb, s->mainloop);
  pa_threaded_mainloop_start(s->mainloop);

  pa_threaded_mainloop_lock(s->mainloop);
  pa_context_connect(s->context, NULL, 0, NULL);
  pa_threaded_mainloop_unlock(s->mainloop);

  state_wait(s, PA_CONTEXT_READY);

  pa_threaded_mainloop_lock(s->mainloop);
  s->stream = pa_stream_new(s->context, "foo playback", &ss, NULL);
  pa_stream_set_state_callback(s->stream, stream_cb, s->mainloop);
  pa_stream_set_latency_update_callback(s->stream, stream_cb, s->mainloop);
  pa_stream_set_write_callback(s->stream, stream_request_cb, s->mainloop);
  flags = PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING;
  if (early_request) {
    fprintf(stderr, "early requests enabled\n");
    flags |= PA_STREAM_EARLY_REQUESTS;
  }

  pa_stream_connect_playback(s->stream, NULL, &battr, flags, NULL, NULL);
  pa_threaded_mainloop_unlock(s->mainloop);

  state_wait(s, PA_CONTEXT_READY);
  s_state_wait(s, PA_STREAM_READY);
}

void bwrite(struct state * s, size_t towrite)
{
  int r;
  char * garbage;

  garbage = calloc(1, towrite);
  pa_threaded_mainloop_lock(s->mainloop);
  r = pa_stream_write(s->stream, garbage, towrite, NULL, 0, 0);
  pa_threaded_mainloop_unlock(s->mainloop);
  free(garbage);
  assert(r == 0);
}

size_t avail(struct state * s)
{
  size_t wsize;

  pa_threaded_mainloop_lock(s->mainloop);
  wsize = pa_stream_writable_size(s->stream);
  pa_threaded_mainloop_unlock(s->mainloop);

  return wsize;
}

void fill(struct state * s)
{
  bwrite(s, avail(s));
}

int corked(struct state * s)
{
  int cork;

  pa_threaded_mainloop_lock(s->mainloop);
  cork = pa_stream_is_corked(s->stream);
  pa_threaded_mainloop_unlock(s->mainloop);

  return cork;
}

int main(int argc, char * argv[])
{
  struct state s;
  int pause = 0, count = 0, wrotelast = 0;

  if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'r') {
    setup(&s, 0);
  } else {
    setup(&s, 1);
  }

  fill(&s);

  while (++count) {
    size_t wsize;
    if (wrotelast && count % 3 == 0) {
      fprintf(stderr, pause ? "resuming playback\n" : "pausing playback\n");
      pause = !pause;
      if (pause && avail(&s) > WRITESZ) {
        bwrite(&s, avail(&s) - (WRITESZ / 2));
      }
      pause_stream(&s, pause);
      if (!pause) {
        wrotelast = 0;
      }
    }

    if (!pause) {
      wsize = avail(&s);

      if (wsize >= WRITESZ) {
        bwrite(&s, WRITESZ);
        fprintf(stderr, "wsize=%zu, wrote=%u\n", wsize, WRITESZ);
        wrotelast = 1;
      } else {
        fprintf(stderr, "wsize=%zu, need >= %u, corked=%d\n", wsize, WRITESZ, corked(&s));
      }
    }
    usleep(100000);
  }

  return 0;
}
_______________________________________________
pulseaudio-discuss mailing list
[email protected]
https://tango.0pointer.de/mailman/listinfo/pulseaudio-discuss

Reply via email to