[pulseaudio-discuss] Tracking lifetime of pa_context objects from API level

Marcel Müller mueller at maazl.de
Tue Dec 28 13:52:54 PST 2010


Colin Guthrie wrote:
> As someone who has written/adapted a couple of C++ related PA apps, I
> can say that a good, generic, libpulse++ would be quite desirable. I'd
> even be tempted to help out here if possible to help unify some of the
> other stuff I've done.
 >
> One thing I would suggest doing (if you really are looking at a generic
> lib) is to have a synchronous "test" method, to check if PA support is
> available (e.g. try to connect to the server with a standard mainloop
> that you run manually and then disconnect and reconnect with a proper
> mainloop if PA support is available.

Well, this test should not be related to C++.


>> But also I have to stop the main loop when it is no longer needed. This
>> is not trivial, because the pa_context might only be referenced
>> internally by other PA objects. So an instance counter of the C++
>> context wrappers is not sufficient, because they might have gone out of
>> scope while the mainloop is still needed for processing.
>>
>> However, if I could track the total number of existing pa_context
>> objects, I could safely stop the main loop after the last pa_context
>> died. Without any pa_context object it is most likely no longer needed.
> 
> At what point do you need to cache the context objects?

It is up to the application to keep the wrapper classes alive as long as 
it likes. Destroying might in fact be mainly like calling unref. OK, in 
case of a context, disconnect could be more intuitive on destruction.

> Most (all?)
> callbacks include the context in them and as such you don't really need
> to keep the context around as a variable in it's own right.

This won't help for C++ since in general the callbacks have to be 
wrapped with some oberservable classes. First of all this allows 
multiple subscribers, secondly a reasonable observer implementation 
would disconnect from dieing observers automatically to avoid undefined 
behavior.


>> The only available observable is the stat callback. But tracking changes
>> to PA_CONTEXT_FAILED and PA_CONTEXT_TERMINATED is not sufficient. First
>> of all a context might not have been used at all, Secondly a terminated
>> connection might become connected later.
> 
> Contexts can only be used once, so if your context is TERMINATED or
> FAILED, then it should be discarded and a new one used.

Ah! I wasn't aware of that. In this case the state transition of the 
last context to TERMINATED or FAILED should be sufficient.


>> Any ideas how to terminate the mainloop?
> 
> If you really do want to terminate the mainloop, then you should tear
> down all contexts using it also I think.

I'd like to do it the other way around. Once all contexts are 
disconnected the main loop should terminate itself.


> Not sure if anything I've said helps specifically but perhaps seeing
> some code would help to illustrate the problem better.

Well, I just have started. And since I am not on an officially 
unsupported platform (eComStation) it might not become as generic as you 
like.

class PAContext
{private:
   static volatile unsigned              InstCount;
   static pa_threaded_mainloop* volatile Mainloop;
   pa_context*        Context;
   pa_context_state_t State;

  public:
   PAContext(const char* appname);
   ~PAContext();
   operator pa_context*()                        { return Context; }

   int Connect(const char* server, pa_context_flags_t flags = 
PA_CONTEXT_NOFLAGS)
                                                 { return 
pa_context_connect(Context, server, flags, NULL); }
   void Disconnect()                             { 
pa_context_disconnect(Context); }

  private: // non-copyable
   PAContext(const PAContext& r);
   void operator=(const PAContext& r);
  private:
   bool Init();
   void Uninit();
   static void StateCB(pa_context *c, void *userdata);
};


PAContext::PAContext(const char* appname)
: State(PA_CONTEXT_UNCONNECTED)
{ if (!Init())
     return;

   Context = pa_context_new(pa_threaded_mainloop_get_api(Mainloop), 
appname);
   if (!Context)
   { Uninit();
     return;
   }

   pa_context_set_state_callback(Context, &PAContext::StateCB, this);
}

PAContext::~PAContext()
{ if (Context)
     pa_context_unref(Context);
   // TODO: defer termination of mainloop until the context is really 
destroyed.
   Uninit();
}

bool PAContext::Init()
{ if (InterlockedXadd(&InstCount, 1) == 0)
   { // First instance => start main loop.
     Mainloop = pa_threaded_mainloop_new();
     if (!Mainloop)
       return false;
     pa_threaded_mainloop_start(Mainloop);
   } else
   { // Initialized by another instance
     // Dirty spin-lock to synchronize the other thread.
     // TODO: deadlock in case of error.
     while (Mainloop == NULL)
       DosSleep(1);
   }
}

void PAContext::Uninit()
{ pa_threaded_mainloop* mainloop = Mainloop;
   if (InterlockedDec(&InstCount))
     return;
   // Do not detach a mainloop that might be instantiated by another thread.
   InterlockedCmpxch((volatile unsigned*)&Mainloop, (unsigned)mainloop, 
NULL);
   // destroy mainloop
   pa_threaded_mainloop_stop(mainloop);
   pa_threaded_mainloop_free(mainloop);
}




More information about the pulseaudio-discuss mailing list