[asterisk-dev] Poor locking model in Asterisk (I know you can fix it)

Hans Petter Selasky hselasky at c2i.net
Wed Feb 8 13:04:26 MST 2006


Hi,

I am a bit lazy, so I just post this here, leaving it for someone else to make 
a PR on this issue, hence mantis was a little complicated for me, and I don't 
have so much time at the moment.


Number 1)

You should define the locking rules! Otherwise very nasty things may and will 
happen. 

Let me explain. A mutex is something that prevents 
two processes from colliding. Below is what I currently use for 
some channel driver. It does not cover all of Asterisk. The big problem is 
that there are two locks protecting a single channel. My private "call 
descriptor" lock, cd->p_app->lock, and the "Asterisk channel" lock, 
cd->pbx_chan->lock. This leads nowhere else than to trouble. I hope you can 
fix this, so that it is possible to pass a lock to Asterisk, which it should 
lock before calling any callbacks in my channel driver.

/*
 * LOCKING RULES EXAMPLE
 * =====================
 *
 * This channel driver uses several locks. One must be 
 * careful not to reverse the locking order, which will
 * lead to a deadlock. Here is the locking order
 * that must be followed:
 *
 * struct call_desc *cd;
 * struct cc_capi_application *p_app;
 *
 * 1. cc_mutex_lock(&do_periodic_lock);
 *
 * 2. cc_mutex_lock(&cd->pbx_chan->lock); **
 *
 * 3. cc_mutex_lock(&p_app->lock); ***
 *
 * 4. cc_mutex_lock(&capi_global_lock);
 *
 *
 *  ** the PBX will call the callback functions with 
 *     this lock locked. This lock protects the 
 *     structure pointed to by 'cd->pbx_chan'. Also note
 *     that calling some PBX functions will lock
 *     this lock!
 *
 *  *** in case multiple CAPI applications should be locked 
 *      in series, the CAPI application with the lowest 
 *      memory address should be locked first.
 *      (One maybe want to check/use the address of the mutex.)
 */

Number 2)

To get my private "PVT" pointer, I have to do an extensive search, just 
because two different locks are used:

/*---------------------------------------------------------------------------*
 *      cd_by_pbx_chan - find "call descriptor" by PBX channel
 *
 * NOTE: returns a locked call descriptor
 *---------------------------------------------------------------------------*/
static struct call_desc *
cd_by_pbx_chan(struct ast_channel *pbx_chan)
{
#if 0

    /* in the future the PBX should lock our
     * private lock before calling any callbacks !
     */
    struct call_desc *cd = CC_CHANNEL_PVT(pbx_chan);

    cc_mutex_assert(&cd->p_app->lock, MA_OWNED);

#else
    struct call_desc *cd = NULL;
    struct cc_capi_application *p_app;
    u_int16_t n;

    for(n = 0; n < CAPI_MAX_CONTROLLERS; n++) 
    {
        p_app = capi_application[n];

        if (p_app) {

          cc_mutex_lock(&p_app->lock);

          cd = p_app->cd_root_ptr;

          while(cd) {

            if ((cd->pbx_chan == pbx_chan) &&
                (!CD_IS_UNUSED(cd)))
            {
                goto found;
            }
            cd = cd->next;
          }

          cc_mutex_unlock(&p_app->lock);
        }
    }
 found:

    if (cd == NULL) {
        cc_log(LOG_ERROR, "PBX channel has no interface!\n");
    }
#endif

    return cd;
}


I would prefer if Asterisk would wrap all channel driver callbacks like this:

/*---------------------------------------------------------------------------*
 *      chan_capi_write - called from "ast_write()"
 *---------------------------------------------------------------------------*/
static int
chan_capi_write(struct ast_channel *pbx_chan, struct ast_frame *p_frame)
{
    struct call_desc *cd = cd_by_pbx_chan(pbx_chan);
    int error = 0;

    if (cd == NULL) {
        return -1;
    }

    cd_verbose(cd, 3, 1, 2, "\n");

    /* call descriptor is still valid */

    error = __chan_capi_write(cd, p_frame);

    cd_unlock(cd);

    return error;
}

Number 3)

All channel driver locking functions, should take two arguments, to easily 
handle locking of two different channels at the same time.

/*---------------------------------------------------------------------------*
 *      chan_capi_bridge - called from "ast_channel_bridge()"
 *---------------------------------------------------------------------------*/
static CC_BRIDGE_RETURN
chan_capi_bridge(struct ast_channel *pbx_chan0,
                 struct ast_channel *pbx_chan1,
                 int flags, 
                 struct ast_frame **fo,
                 struct ast_channel **rc
#ifdef CC_AST_BRIDGE_WITH_TIMEOUTMS
                 , int timeoutms
#endif
                 )
{
    struct call_desc *cd0;
    struct call_desc *cd1;
    int error = AST_BRIDGE_COMPLETE;

    if(cd_mutex_lock_double_pbx_chan(pbx_chan0,pbx_chan1,&cd0,&cd1)) {
        return AST_BRIDGE_FAILED_NOWARN;
    }

    cd_verbose(cd0, 3, 0, 2, "\n");
    cd_verbose(cd1, 3, 0, 2, "\n");

    /* call descriptors are still valid */
    error = __chan_capi_bridge(cd0,cd1,flags,fo,rc
#ifdef CC_AST_BRIDGE_WITH_TIMEOUTMS
                               , timeoutms
#endif
                               );
    cd_mutex_unlock_double(cd0, cd1);
    return error;
}

#define cd_lock(cd) cc_mutex_lock(&(cd)->p_app->lock)
#define cd_unlock(cd) cc_mutex_unlock(&(cd)->p_app->lock)

/*---------------------------------------------------------------------------*
 *      cd_mutex_unlock_double - unlock two call descriptors
 *---------------------------------------------------------------------------*/
static void
cd_mutex_unlock_double(struct call_desc *cd0,
                       struct call_desc *cd1)
{
    if (cd0 == cd1) {

        cc_mutex_unlock(&cd0->p_app->lock);

    } else if (cd0->p_app < cd1->p_app) {

        cc_mutex_unlock(&cd0->p_app->lock);
        cc_mutex_unlock(&cd1->p_app->lock);

    } else {

        cc_mutex_unlock(&cd1->p_app->lock);
        cc_mutex_unlock(&cd0->p_app->lock);

    }
    return;
}

/*---------------------------------------------------------------------------*
 *      cd_mutex_lock_double - lock two call descriptors
 *---------------------------------------------------------------------------*/
static void
cd_mutex_lock_double(struct call_desc *cd0,
                     struct call_desc *cd1)
{
    if (cd0 == cd1) {

        cc_mutex_lock(&cd0->p_app->lock);

    } else if (cd0->p_app < cd1->p_app) {

        cc_mutex_lock(&cd0->p_app->lock);
        cc_mutex_lock(&cd1->p_app->lock);

    } else {

        cc_mutex_lock(&cd1->p_app->lock);
        cc_mutex_lock(&cd0->p_app->lock);

    }
    return;
}

/*---------------------------------------------------------------------------*
 *      cd_mutex_lock_double_pbx_chan - lock two PBX channels
 *---------------------------------------------------------------------------*/
static u_int8_t
cd_mutex_lock_double_pbx_chan(struct ast_channel *pbx_chan0,
                              struct ast_channel *pbx_chan1,
                              struct call_desc **pp_cd0,
                              struct call_desc **pp_cd1)
{
    struct call_desc *cd0 = CC_CHANNEL_PVT(pbx_chan0);
    struct call_desc *cd1 = CC_CHANNEL_PVT(pbx_chan1);

#if 0
    cc_mutex_assert(&pbx_chan0->lock, MA_OWNED);
    cc_mutex_assert(&pbx_chan1->lock, MA_OWNED);
#endif

    if (cd0 == cd1)
    {
        cd1 = cd0 = cd_by_pbx_chan(pbx_chan0);
    }
    else if (cd0->p_app < cd1->p_app)
    {
        cd0 = cd_by_pbx_chan(pbx_chan0);
        cd1 = cd_by_pbx_chan(pbx_chan1);
    }
    else
    {
        cd1 = cd_by_pbx_chan(pbx_chan1);
        cd0 = cd_by_pbx_chan(pbx_chan0);
    }

    if (cd1 && cd0) 
    {
        /* success */
        *pp_cd0 = cd0;
        *pp_cd1 = cd1;
        return 0;
    }

    /* failure */

    if (cd0) 
    {
        cd_unlock(cd0);
    }

    if (cd1)
    {
        cd_unlock(cd1);
    }

    /* no channel interface */
    cc_log(LOG_ERROR, "PBX channel has no interface!\n");
    return 1;
}

That was it. Hope you can do it like this. Then it will fit my "channel 
driver" very well, and it will solve some problems, and remove extra code.
I am also very sure that it will make Asterisk more stable.

Please feel free to send me your comments :-)

--HPS



More information about the asterisk-dev mailing list