[alsa-devel] [PATCH] conf: don't check if config files are up to date

Takashi Iwai tiwai at suse.de
Mon May 16 07:47:58 CEST 2016


On Sun, 15 May 2016 20:32:09 +0200,
Alexander E. Patrakov wrote:
> 
> 13.05.2016 20:04, Takashi Iwai пишет:
> > On Fri, 13 May 2016 16:07:12 +0200,
> > Takashi Iwai wrote:
> >>
> >> On Thu, 12 May 2016 15:45:23 +0200,
> >> Takashi Iwai wrote:
> >>>
> >>> On Tue, 10 May 2016 10:40:08 +0200,
> >>> Alexander E. Patrakov wrote:
> >>>>
> >>>> 09.05.2016 17:44, Takashi Iwai wrote:
> >>>>> On Sun, 01 May 2016 14:43:42 +0200,
> >>>>> Alexander E. Patrakov wrote:
> >>>>>> Even if no files have been changed, the config may become outdated
> >>>>>> due to hot-plugged devices.
> >>>>>>
> >>>>>> Note: this patch has a huge potential to crash badly-written applications
> >>>>>> that keep config pointers and strings for too long. But I see no other
> >>>>>> way to support hot-pluggable devices.
> >>>>> Can we opt in this feature, e.g. by enabling it via some new API
> >>>>> function?  Enabling this blindly appears too risky.
> >>>>
> >>>> Hello Takashi,
> >>>>
> >>>> I have read your remark, and your concern does make sense. However, I
> >>>> have some doubts whether any new API is needed, and some suggestions
> >>>> for further changes. Here are my ideas.
> >>>>
> >>>> 1. We already have such API. It is called snd_config_top(),
> >>>> snd_config_update_r() and snd_pcm_open_lconf(). Would you accept a
> >>>> documentation patch that says that snd_pcm_open cannot deal with
> >>>> hot-plugged devices, and directs users to snd_pcm_open_lconf()? Of
> >>>> course, this depends on Tanu's willingness to accept conversion to
> >>>> snd_pcm_open_lconf() in PulseAudio.
> >>>
> >>> Yeah, it'd be good to mention there.
> >>>
> >>>> 2. Even if a user calls snd_pcm_open_lconf(), currently, when
> >>>> attempting to figure out the driver, mixer configuration is updated
> >>>> with snd_config_update() - i.e. global configuration is updated, and
> >>>> this IMHO defeats the point of having a separate configuration. To fix
> >>>> the bug, I would need to make sure that the mixer configuration is
> >>>> read from the same snd_config_t structure that was passed to
> >>>> snd_pcm_open_lconf(). I am not sure about the difficulty of this task.
> >>>>
> >>>> 3. One of my early attempts of the patch (without the hunk about
> >>>> snd_ctl_open_lconf()) was crashing due to bad pointers. Now I realize
> >>>> that it is already a bug in alsa-lib - the current code would crash,
> >>>> too, if the configuration file has changed between the calls to
> >>>> snd_pcm_update() in snd_pcm_open() and in snd_ctl_open() (which is
> >>>> called from a hook that determines the driver). Again, I would need to
> >>>> make sure that the mixer configuration is read from the same
> >>>> snd_config_t structure that was passed to snd_pcm_open_noupdate(), see
> >>>> above.
> >>>
> >>> Right.  The crash doesn't happen so often because the update of config
> >>> tree doesn't happen also so often.
> >>>
> >>>> 4. I still think that the file mtime check in snd_config_update_r does
> >>>> not make any sense. It fails to notice changes in included files, as
> >>>> well as in the list of plugged-in devices. In other words, it never
> >>>> worked except for the initial read, and it's too risky to make it
> >>>> actually work as documented. How about making it read the
> >>>> configuration from files only on the first call for a given config,
> >>>> even if they have changed?
> >>>
> >>> Yes, that sounds more reasonable as a first move.  It doesn't work
> >>> 100% reliably, and if you really want a proper update, you can do it
> >>> by calling snd_config_update_free_global() beforehand.
> >>>
> >>> There is a minimal protection by pthread mutex in conf.c, but it's
> >>> only at regenerating the config tree.  We need refcounting by
> >>> referrer for a proper protection.  Once after having the refcount
> >>> protection, we can safely update the config tree unconditionally.
> >>
> >> So here is a quick hack.  It adds a simple refcount in snd_config_t.
> >> snd_config_update_get() gives a top config with a refcount so that it
> >> won't be deleted until disposed by snd_config_put().
> >>
> >> In this patch, the config update itself is kept in traditional way,
> >> i.e. lazy update with mtime check.  For the forced refresh, I added
> >> snd_config_refresh() which removes snd_config_global_update so that at
> >> the next snd_config_update() will reread properly.  PA can call this
> >> function appropriately, e.g. when hotplugged.
> >>
> >> The patch is just a proof of concept and even untested.  Once when
> >> confirmed that it's the way to go, I'll cook up more.
> >
> > There was a typo indeed.  The revised patch is below.
> > aplay worked at least :)  And now it has comments.
> 
> I have read the patch, and also tested with aplay, speaker-test and
> pulseaudio.
> 
> Here are my thoughts so far.
> 
> First, the patch combines two separate logical changes: proper locking
> and refcounting.
> 
> Locking looks almost OK (one small issue with snd_config_delete() and
> two "maybe" suggestions, see below). The mutex is recursive, so
> e.g. calling snd_ctl_open() from PCM hooks (and thus from inside
> snd_pcm_open()) is not a problem.

There is nothing new in regard of locking.  It existed before the
patch, snd_config_update() and snd_config_update_free_global() already
took it.  So, this must be pretty safe even in the new code.

> Regarding the new snd_config_refresh() API, do we really need it?
> There are precedents of applications (e.g. Phonon) using
> snd_config_update_free_global() for the same purpose. The only new
> thing that snd_config_refresh() brings is the guarantee that it either
> does not exist or does proper locking.

OK, this can be removed.  I thought it would be nicer, but reducing
the unneeded API would be better indeed.

> It may be, instead, a good idea to add the same kind of locking to
> snd_config_update_free_global(). And maybe to snd_config_update()?
> Ideally, I would like to be able to safely call
> snd_config_update_free_global() at any time in a multithreaded
> application that does not call any other snd_config_*() functions
> directly.

As said, snd_config_update() and snd_config_update_free_global() have
already locks and they can be called safely.  The problem is that the
tree itself that is being used in snd_*_open() (i.e. snd_config global
tree) isn't race-free.  The introduction of refcount helps for that.

> Now to refcounting.
> 
> The new functions, snd_config_update_get()/snd_config_put(), as a new
> public API, have received zero testing from me. With this patch,
> snd_config_delete() will be called sometimes on snd_config, but so far
> in my testing with aplay and speaker-test the relevant refcount is >
> 0. So it looks like there is no danger for existing applications.
> 
> As snd_config_delete() is still public API, it looks like there is a
> (bad) code path that accesses refcount without locking. Fixing it
> would require adding a lock to snd_config_delete(), but then what's
> the point of snd_config_put()?

The difference is that snd_config_delete() can't be protected with
mutex, as this function itself is called recursively from the
destructor in the mutex.  So, we may see snd_config_delete() as a
function to delete the local tree while snd_config_put() (or maybe
better named as snd_config_unref() or such) is a function to
unreference (and eventually delete) the tree in a thread safe way.


Takashi

> I have also investigated the hooks PCM specifically for the question
> whether it is safe to refresh the config while such PCM is open, and
> found that, eventually, the control name is copied. This is done in
> snd_ctl_elem_id_set_name. So this particular PCM type seems to be
> safe.
> 
> Also, I have tried a patched pulseaudio. The patch adds a call to
> snd_config_refresh() in pa_alsa_open_by_device_string. It now
> interacts with a hot-plugged USB device successfully. But so it does
> if I call snd_config_update_free_global() (except for locking).
> 
> And I still think that I need to do come more testing...
> 
> -- 
> Alexander E. Patrakov
> 
> >
> >
> > Takashi
> >
> > ---
> > diff --git a/include/conf.h b/include/conf.h
> > index 087c05dc6bcf..72fd7c06a8d9 100644
> > --- a/include/conf.h
> > +++ b/include/conf.h
> > @@ -94,6 +94,10 @@ int snd_config_update_r(snd_config_t **top, snd_config_update_t **update, const
> >  int snd_config_update_free(snd_config_update_t *update);
> >  int snd_config_update_free_global(void);
> >
> > +void snd_config_refresh(void);
> > +int snd_config_update_get(snd_config_t **top);
> > +void snd_config_put(snd_config_t *top);
> > +
> >  int snd_config_search(snd_config_t *config, const char *key,
> >  		      snd_config_t **result);
> >  int snd_config_searchv(snd_config_t *config,
> > diff --git a/src/conf.c b/src/conf.c
> > index f8b7a6686529..1386a38b0b27 100644
> > --- a/src/conf.c
> > +++ b/src/conf.c
> > @@ -434,6 +434,7 @@ static pthread_once_t snd_config_update_mutex_once = PTHREAD_ONCE_INIT;
> >  struct _snd_config {
> >  	char *id;
> >  	snd_config_type_t type;
> > +	int refcount; /* default = 0 */
> >  	union {
> >  		long integer;
> >  		long long integer64;
> > @@ -1833,6 +1834,10 @@ int snd_config_remove(snd_config_t *config)
> >  int snd_config_delete(snd_config_t *config)
> >  {
> >  	assert(config);
> > +	if (config->refcount > 0) {
> > +		config->refcount--;
> > +		return 0;
> > +	}
> >  	switch (config->type) {
> >  	case SND_CONFIG_TYPE_COMPOUND:
> >  	{
> > @@ -3851,6 +3856,60 @@ int snd_config_update(void)
> >  	return err;
> >  }
> >
> > +/**
> > + * \brief Clear the local config update cache
> > + *
> > + * This function clears the local config update cache to guarantee that
> > + * the next call of #snd_config_update will reread the config files.
> > + */
> > +void snd_config_refresh(void)
> > +{
> > +	snd_config_lock();
> > +	if (snd_config_global_update)
> > +		snd_config_update_free(snd_config_global_update);
> > +	snd_config_global_update = NULL;
> > +	snd_config_unlock();
> > +}
> > +
> > +/**
> > + * \brief Updates #snd_config and take its reference.
> > + * \return 0 if #snd_config was up to date, 1 if #snd_config was
> > + *         updated, otherwise a negative error code.
> > + *
> > + * Unlike #snd_config_update, this function increases a reference counter
> > + * so that the obtained tree won't be deleted until unreferenced by
> > + * #snd_config_put.
> > + */
> > +int snd_config_update_get(snd_config_t **top)
> > +{
> > +	int err;
> > +
> > +	*top = NULL;
> > +	snd_config_lock();
> > +	err = snd_config_update_r(&snd_config, &snd_config_global_update, NULL);
> > +	if (err >= 0) {
> > +		*top = snd_config;
> > +		if (*top)
> > +			(*top)->refcount++;
> > +	}
> > +	snd_config_unlock();
> > +	return err;
> > +}
> > +
> > +/**
> > + * \brief Unreference the config tree.
> > + *
> > + * Decreases a reference counter of the given config tree.  This is the
> > + * counterpart of #snd_config_update_get.
> > + */
> > +void snd_config_put(snd_config_t *cfg)
> > +{
> > +	snd_config_lock();
> > +	if (cfg)
> > +		snd_config_delete(cfg);
> > +	snd_config_unlock();
> > +}
> > +
> >  /**
> >   * \brief Frees a private update structure.
> >   * \param[in] update The private update structure to free.
> > diff --git a/src/control/control.c b/src/control/control.c
> > index 8a5d530f2674..6078189a6a70 100644
> > --- a/src/control/control.c
> > +++ b/src/control/control.c
> > @@ -968,12 +968,16 @@ static int snd_ctl_open_noupdate(snd_ctl_t **ctlp, snd_config_t *root, const cha
> >   */
> >  int snd_ctl_open(snd_ctl_t **ctlp, const char *name, int mode)
> >  {
> > +	snd_config_t *top;
> >  	int err;
> > +
> >  	assert(ctlp && name);
> > -	err = snd_config_update();
> > +	err = snd_config_update_get(&top);
> >  	if (err < 0)
> >  		return err;
> > -	return snd_ctl_open_noupdate(ctlp, snd_config, name, mode);
> > +	err = snd_ctl_open_noupdate(ctlp, top, name, mode);
> > +	snd_config_put(top);
> > +	return err;
> >  }
> >
> >  /**
> > diff --git a/src/hwdep/hwdep.c b/src/hwdep/hwdep.c
> > index 5dc791c99189..c8b368fa68a2 100644
> > --- a/src/hwdep/hwdep.c
> > +++ b/src/hwdep/hwdep.c
> > @@ -168,12 +168,16 @@ static int snd_hwdep_open_noupdate(snd_hwdep_t **hwdep, snd_config_t *root, cons
> >   */
> >  int snd_hwdep_open(snd_hwdep_t **hwdep, const char *name, int mode)
> >  {
> > +	snd_config_t *top;
> >  	int err;
> > +
> >  	assert(hwdep && name);
> > -	err = snd_config_update();
> > +	err = snd_config_update_get(&top);
> >  	if (err < 0)
> >  		return err;
> > -	return snd_hwdep_open_noupdate(hwdep, snd_config, name, mode);
> > +	err = snd_hwdep_open_noupdate(hwdep, top, name, mode);
> > +	snd_config_put(top);
> > +	return err;
> >  }
> >
> >  /**
> > diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c
> > index 203e7a52491b..42da2c8a8b55 100644
> > --- a/src/pcm/pcm.c
> > +++ b/src/pcm/pcm.c
> > @@ -2288,12 +2288,16 @@ static int snd_pcm_open_noupdate(snd_pcm_t **pcmp, snd_config_t *root,
> >  int snd_pcm_open(snd_pcm_t **pcmp, const char *name,
> >  		 snd_pcm_stream_t stream, int mode)
> >  {
> > +	snd_config_t *top;
> >  	int err;
> > +
> >  	assert(pcmp && name);
> > -	err = snd_config_update();
> > +	err = snd_config_update_get(&top);
> >  	if (err < 0)
> >  		return err;
> > -	return snd_pcm_open_noupdate(pcmp, snd_config, name, stream, mode, 0);
> > +	err = snd_pcm_open_noupdate(pcmp, top, name, stream, mode, 0);
> > +	snd_config_put(top);
> > +	return err;
> >  }
> >
> >  /**
> > diff --git a/src/rawmidi/rawmidi.c b/src/rawmidi/rawmidi.c
> > index 0c89b8b984b9..d965edaf1f42 100644
> > --- a/src/rawmidi/rawmidi.c
> > +++ b/src/rawmidi/rawmidi.c
> > @@ -305,12 +305,16 @@ static int snd_rawmidi_open_noupdate(snd_rawmidi_t **inputp, snd_rawmidi_t **out
> >  int snd_rawmidi_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
> >  		     const char *name, int mode)
> >  {
> > +	snd_config_t *top;
> >  	int err;
> > +
> >  	assert((inputp || outputp) && name);
> > -	err = snd_config_update();
> > +	err = snd_config_update_get(&top);
> >  	if (err < 0)
> >  		return err;
> > -	return snd_rawmidi_open_noupdate(inputp, outputp, snd_config, name, mode);
> > +	err = snd_rawmidi_open_noupdate(inputp, outputp, top, name, mode);
> > +	snd_config_put(top);
> > +	return err;
> >  }
> >
> >  /**
> > diff --git a/src/seq/seq.c b/src/seq/seq.c
> > index 4405e68a7fe9..c1aa7981d557 100644
> > --- a/src/seq/seq.c
> > +++ b/src/seq/seq.c
> > @@ -974,12 +974,16 @@ static int snd_seq_open_noupdate(snd_seq_t **seqp, snd_config_t *root,
> >  int snd_seq_open(snd_seq_t **seqp, const char *name,
> >  		 int streams, int mode)
> >  {
> > +	snd_config_t *top;
> >  	int err;
> > +
> >  	assert(seqp && name);
> > -	err = snd_config_update();
> > +	err = snd_config_update_get(&top);
> >  	if (err < 0)
> >  		return err;
> > -	return snd_seq_open_noupdate(seqp, snd_config, name, streams, mode, 0);
> > +	err = snd_seq_open_noupdate(seqp, top, name, streams, mode, 0);
> > +	snd_config_put(top);
> > +	return err;
> >  }
> >
> >  /**
> > diff --git a/src/timer/timer.c b/src/timer/timer.c
> > index a25e4f797ce4..6af6d8224df1 100644
> > --- a/src/timer/timer.c
> > +++ b/src/timer/timer.c
> > @@ -201,12 +201,16 @@ static int snd_timer_open_noupdate(snd_timer_t **timer, snd_config_t *root, cons
> >   */
> >  int snd_timer_open(snd_timer_t **timer, const char *name, int mode)
> >  {
> > +	snd_config_t *top;
> >  	int err;
> > +
> >  	assert(timer && name);
> > -	err = snd_config_update();
> > +	err = snd_config_update_get(&top);
> >  	if (err < 0)
> >  		return err;
> > -	return snd_timer_open_noupdate(timer, snd_config, name, mode);
> > +	err = snd_timer_open_noupdate(timer, top, name, mode);
> > +	snd_config_put(top);
> > +	return err;
> >  }
> >
> >  /**
> > diff --git a/src/timer/timer_query.c b/src/timer/timer_query.c
> > index 93d2455d07fc..23bf0711aafe 100644
> > --- a/src/timer/timer_query.c
> > +++ b/src/timer/timer_query.c
> > @@ -159,12 +159,16 @@ static int snd_timer_query_open_noupdate(snd_timer_query_t **timer, snd_config_t
> >   */
> >  int snd_timer_query_open(snd_timer_query_t **timer, const char *name, int mode)
> >  {
> > +	snd_config_t *top;
> >  	int err;
> > +
> >  	assert(timer && name);
> > -	err = snd_config_update();
> > +	err = snd_config_update_get(&top);
> >  	if (err < 0)
> >  		return err;
> > -	return snd_timer_query_open_noupdate(timer, snd_config, name, mode);
> > +	err = snd_timer_query_open_noupdate(timer, top, name, mode);
> > +	snd_config_put(top);
> > +	return err;
> >  }
> >
> >  /**
> >
> 


More information about the Alsa-devel mailing list