Análisis sobre la implementación de la invocación de llamadas síncronas en fdbus

fbus admite métodos de llamada de procesos remotos similares a rpc, que se implementa mediante el método invoke. El método invoke proporciona múltiples versiones sobrecargadas con diferentes parámetros, pero al final llama a enviar para enviar el mensaje o el objeto CBasejob a la cola del bucle de eventos.

Enviar es un método proporcionado por CFdbMessage, que implementa específicamente llamadas sincrónicas y llamadas asincrónicas.

A continuación se enumeran las declaraciones relevantes de invocación bajo la clase CFdbBaseObject:

    bool invoke(FdbSessionId_t receiver
                , FdbMsgCode_t code
                , IFdbMsgBuilder &data
                , int32_t timeout = 0
                , EFdbQOS qos = FDB_QOS_DEFAULT);
    bool invoke(FdbSessionId_t receiver
                , CFdbMessage *msg
                , IFdbMsgBuilder &data
                , int32_t timeout = 0);
    bool invoke(FdbMsgCode_t code
                , IFdbMsgBuilder &data
                , int32_t timeout = 0
                , EFdbQOS qos = FDB_QOS_DEFAULT);
    bool invoke(CFdbMessage *msg
                , IFdbMsgBuilder &data
                , int32_t timeout = 0);
    bool invoke(FdbSessionId_t receiver
                , CBaseJob::Ptr &msg_ref
                , IFdbMsgBuilder &data
                , int32_t timeout = 0);
    bool invoke(CBaseJob::Ptr &msg_ref
                , IFdbMsgBuilder &data
                , int32_t timeout = 0);
    bool invoke(FdbSessionId_t receiver
                , FdbMsgCode_t code
                , const void *buffer = 0
                , int32_t size = 0
                , int32_t timeout = 0
                , EFdbQOS qos = FDB_QOS_DEFAULT
                , const char *log_info = 0);
    bool invoke(FdbSessionId_t receiver
                , CFdbMessage *msg
                , const void *buffer = 0
                , int32_t size = 0
                , int32_t timeout = 0);
    bool invoke(FdbMsgCode_t code
                , const void *buffer = 0
                , int32_t size = 0
                , int32_t timeout = 0
                , EFdbQOS qos = FDB_QOS_DEFAULT
                , const char *log_data = 0);
    bool invoke(CFdbMessage *msg
                , const void *buffer = 0
                , int32_t size = 0
                , int32_t timeout = 0);
    bool invoke(FdbSessionId_t receiver
                , CBaseJob::Ptr &msg_ref
                , const void *buffer = 0
                , int32_t size = 0
                , int32_t timeout = 0);
    bool invoke(CBaseJob::Ptr &msg_ref
                , const void *buffer = 0
                , int32_t size = 0
                , int32_t timeout = 0);
    bool invoke(FdbMsgCode_t code
                , IFdbMsgBuilder &data
                , tInvokeCallbackFn callback
                , CBaseWorker *worker = 0
                , int32_t timeout = 0
                , EFdbQOS qos = FDB_QOS_DEFAULT);
    bool invoke(FdbMsgCode_t code
                , tInvokeCallbackFn callback
                , const void *buffer = 0
                , int32_t size = 0
                , CBaseWorker *worker = 0
                , int32_t timeout = 0
                , EFdbQOS qos = FDB_QOS_DEFAULT
                , const char *log_info = 0);

Hay muchos métodos de invocación anteriores, pero funcionalmente se dividen en dos categorías: uno es asíncrono y el otro es sincrónico . Para saber si es sincrónico o asincrónico, basta con mirar el archivo de encabezado. Arriba se enumeran una variedad de interfaces de invocación. Todos estos métodos son llamados por puntos finales y son los puntos de entrada para las llamadas de los usuarios.

Arriba, CFdbBaseObject proporciona muchos métodos de invocación, y CFdbMessage también proporciona tres métodos de invocación. La relación entre ellos, estos métodos de invocación proporcionados por CFdbBaseObject, no importa cuán transformados, finalmente se llaman los tres métodos de invocación proporcionados por CFdbMessage. Los tres métodos de invocación proporcionados por CFdbMessage se declaran de la siguiente manera:

    bool invoke(CBaseJob::Ptr &msg_ref
                , uint32_t tx_flag
                , int32_t timeout);
bool invoke(int32_t timeout = 0);
static bool invoke(CBaseJob::Ptr &msg_ref, int32_t timeout = 0);

El artículo anterior brindó un resumen detallado de la relación entre invocar. De lo anterior podemos concluir que existen dos métodos para llamar a invocar:

  1. Los puntos finales de la red, ya sea cliente o servidor, pueden llamar al mensaje del método de invocación
  2. Al definir una instancia de CFdbMessage, también puede llamar directamente a métodos relacionados, de la siguiente manera
CFdbMessage msg;
msg->invoke(...);

 Bien, vayamos al punto. Cuando miré el código fuente de fdbus, tuve esta pregunta: ¿Cómo se implementa la sincronización de invocación? ¿Qué debo hacer si quiero lograr la sincronización? Con esta pregunta en mente, miré más de cerca el código fuente de fdbus y finalmente entendí cómo se implementa la sincronización en el marco de fdbus. Haré todo lo posible para presentarlo a continuación.

Primero comprenda el significado de llamadas sincrónicas y llamadas asincrónicas:

  • Llamada sincrónica: al llamar a una función, la función que llama debe esperar a que la función llamada complete la ejecución y devuelva el resultado antes de ejecutar la declaración debajo de la función que llama. Antes de que se complete la lógica de ejecución de la función llamada, no regresará, lo que provocará que la función que llama se bloquee y espere. Para tomar un ejemplo extremo, si una función tarda 2 segundos en completarse, entonces la función que llama esperará 2 segundos durante este período. .Ejecutar las sentencias que le siguen. Para dar el ejemplo más simple, todas las llamadas a funciones que realizamos en el mismo hilo son llamadas sincrónicas. Cabe agregar aquí que, en circunstancias normales, las llamadas sincrónicas incluirán un tiempo de bloqueo. Este tiempo es para evitar el bloqueo todo el tiempo. Por supuesto, también puede optar por bloquear todo el tiempo. Si establecer este tiempo de bloqueo lo decide al usuario según sus necesidades de negocio.
  • Llamada asincrónica: al llamar a una función, cuando la función que llama llama a una función para solicitar un determinado resultado, se supone que el resultado llamado tarda 2 segundos en ejecutarse. En el caso de una llamada asincrónica, la función llamada regresará inmediatamente, lo que permitirá que llamando a la función para continuar. Ejecute las declaraciones que le siguen. Luego, una vez completada la ejecución, 2 segundos después, el resultado se devuelve a la persona que llama de alguna manera. Por supuesto, las llamadas asincrónicas generalmente están relacionadas con colas, bucles de eventos y subprocesos múltiples. Es decir, en el caso de subprocesos múltiples o procesos múltiples, el subproceso que llama envía la solicitud a la cola de eventos, y luego el bucle de eventos atraviesa la cola de eventos en secuencia y la envía al subproceso o proceso llamado para su procesamiento de acuerdo con al tipo de evento. Cuando el hilo o proceso llamado recibe Después de recibir los datos del evento, el resultado se devuelve al hilo o proceso que llama de alguna manera. Dicho proceso se denomina colectivamente llamada asincrónica.

Este artículo solo analiza las llamadas síncronas de fdbus.

A continuación se utiliza msg->invoke() como ejemplo para presentar.

bool CFdbMessage::invoke(CBaseJob::Ptr &msg_ref , int32_t timeout)
{
    auto msg = castToMessage<CFdbMessage *>(msg_ref);
    return msg ? msg->invoke(msg_ref, FDB_MSG_TX_SYNC, timeout) : false;
}

En el código anterior, puede ver que la interfaz de llamada síncrona de invocación en CFdbMessage contiene dos parámetros, uno es el puntero compartido que apunta al objeto del mensaje y el otro es el tiempo de espera. Puede ver que se llama a otro método de invocación en CFdbMessage en el código anterior. Me pregunto si ha notado el segundo parámetro. Sí, es el parámetro FDB_MSG_TX_SYNC. Desde el significado literal, todos entienden que esto está configurando el indicador de sincronización . Fuente código de la siguiente manera:

bool CFdbMessage::invoke(CBaseJob::Ptr &msg_ref
                         , uint32_t tx_flag
                         , int32_t timeout)
{
    mType = FDB_MT_REQUEST;
    return submit(msg_ref, tx_flag, timeout);
}

En el código anterior, tx_flag es un indicador de mensaje. Todos los métodos de invocación mencionados anteriormente requieren en última instancia llamar a este método de invocación para el procesamiento lógico.

bool CFdbMessage::submit(CBaseJob::Ptr &msg_ref
                         , uint32_t tx_flag
                         , int32_t timeout)
{
    ....

    bool sync = !!(tx_flag & FDB_MSG_TX_SYNC);
    if (sync && mContext->isSelf())
    {
        setStatusMsg(FDB_ST_UNABLE_TO_SEND, "Cannot send sychronously from context");
        return false;
    }

    if (tx_flag & FDB_MSG_TX_NO_REPLY)
    {
        mFlag |= MSG_FLAG_NOREPLY_EXPECTED;
    }
    else
    {
        mFlag &= ~MSG_FLAG_NOREPLY_EXPECTED;
        if (sync)
        {
            mFlag |= MSG_FLAG_SYNC_REPLY;
        }
        if (timeout > 0)
        {
            mTimer = new CMessageTimer(timeout);
        }
    }

    bool ret = true;
    if (tx_flag & FDB_MSG_TX_NO_QUEUE)
    {
        dispatchMsg(msg_ref);
    }
    else
    {
        setCallable(std::bind(&CFdbMessage::dispatchMsg, this, _1));
        if (sync)
        {
            ret = mContext->sendSync(msg_ref);
        }
        else
        {
            ret = mContext->sendAsync(msg_ref);
        }
    }

    ...
}
bool CBaseWorker::sendSync(CBaseJob::Ptr &job, int32_t milliseconds, bool urgent)
{
    return send(job, milliseconds, urgent);
}
bool CBaseWorker::send(CBaseJob::Ptr &job, int32_t milliseconds, bool urgent)
{
    if (job->mSyncReq)
    {
        return false;
    }

    // now we can assure the job is not in any worker queue
    CBaseJob::CSyncRequest sync_req(job.use_count());
    job->mSyncReq = &sync_req;
    if (!send(job, urgent))
    {
        return false;
    }

    if (job->mSyncReq)
    {
        job->mSyncLock.lock();
        if (job->mSyncReq)
        {
            if (milliseconds <= 0)
            {    // job->mSyncLock will be released
                sync_req.mWakeupSignal.wait(job->mSyncLock);
            }
            else
            {    // job->mSyncLock will be released
                std::cv_status status = sync_req.mWakeupSignal.wait_for(job->mSyncLock,
                                            std::chrono::milliseconds(milliseconds));
                if (status == std::cv_status::timeout)
                { // timeout! nothing to do.
                }
            }
            job->mSyncReq = 0;
        }
        job->mSyncLock.unlock();
    }


    return true;
}

 La variable de condición mWakeupSignal se bloquea mediante el método de espera.

bool CBaseWorker::send(CBaseJob::Ptr &job, bool urgent, bool swap)
{
    bool ret;
    
    if (mExitCode || !mEventLoop)
    {
        return false;
    }

    if (urgent)
    {
        ret = mUrgentJobQueue.enqueue(job, swap);
    }
    else
    {
        ret = mNormalJobQueue.enqueue(job, swap);
    }

    return ret;
}

 Aunque es una llamada sincrónica, el método de envío se llama antes de que se bloquee la variable de condición mWakeupSignal. En el código fuente del método de envío, puede ver que aunque es una llamada sincrónica, los datos del mensaje o el objeto del mensaje aún se envían a la cola de eventos. , Y luego continúa secuencialmente a través del bucle de eventos Procesamiento, la diferencia es que el método de espera se llama a través de la variable de condición mWakeupSignal para bloquear el hilo que llama, y ​​luego espera recibir el mensaje de respuesta y despierta el hilo que llama.

El código anterior es la lógica del enlace de envío. El código fuente clave para desbloquear el hilo de llamada se enumera a continuación:

void CBaseSession::doResponse(CFdbMessageHeader &head)
{
    bool found;
    PendingMsgTable_t::EntryContainer_t::iterator it;
    CBaseJob::Ptr &msg_ref = mPendingMsgTable.retrieveEntry(head.serial_number(), it, found);
    if (found)
    {
        auto msg = castToMessage<CFdbMessage *>(msg_ref);
        auto object_id = head.object_id();
        if (msg->objectId() != object_id)
        {
            LOG_E("CFdbSession: object id of response %d does not match that in request: %d\n",
                    object_id, msg->objectId());
            terminateMessage(msg_ref, FDB_ST_OBJECT_NOT_FOUND, "Object ID does not match.");
            mPendingMsgTable.deleteEntry(it);
            delete[] mPayloadBuffer;
            mPayloadBuffer = 0;
            return;
        }

        auto object = mContainer->owner()->getObject(msg, false);
        if (object)
        {
            msg->update(head, mMsgPrefix);
            msg->decodeDebugInfo(head);
            msg->replaceBuffer(mPayloadBuffer, head.payload_size(), mMsgPrefix.mHeadLength);
            mPayloadBuffer = 0;
            auto type = msg->type();
            doStatistics(type, head.flag(), mStatistics.mRx);
            if (!msg->sync())
            {
                switch (type)
                {
                    case FDB_MT_REQUEST:
                        object->doReply(msg_ref);
                    break;
                    case FDB_MT_SIDEBAND_REQUEST:
                        object->onSidebandReply(msg_ref);
                    break;
                    case FDB_MT_GET_EVENT:
                        object->doReturnEvent(msg_ref);
                    break;
                    case FDB_MT_SET_EVENT:
                    default:
                        if (head.type() == FDB_MT_STATUS)
                        {
                            object->doStatus(msg_ref);
                        }
                        else
                        {
                            LOG_E("CFdbSession: request type %d doesn't match response type %d!\n",
                                  type, head.type());
                        }
                    break;
                }
            }
        }

        msg_ref->terminate(msg_ref);
        mPendingMsgTable.deleteEntry(it);
    }
}

La función del controlador de entrada que recibe la respuesta.

void CBaseJob::terminate(Ptr &ref)
{
    if (!ref)
    {
        return;
    }

    if (mSyncReq)
    {
        mSyncLock.lock();
        if (mSyncReq)
        {
            // Why +1? because the job must be referred locally.
            // Warning: ref count of the job can not be changed
            // during the sync waiting!!!
            if (ref.use_count() == (mSyncReq->mInitSharedCnt + 1))
            {
                mSyncReq->mWakeupSignal.notify_one();
                mSyncReq = 0;
            }
        }
        mSyncLock.unlock();
    }
}

 Verá, la variable de condición aquí se activa a través de notify_one.

El código anterior debería poder ver claramente la implementación de la llamada síncrona de fdbus. En realidad solo son 3 pasos:

  1. El hilo que llama envía el mensaje a la cola de mensajes.
  2. Después de ingresar a la cola de mensajes, bloquee el hilo de llamada a través de la variable de condición y espere a que se active.
  3. Después de recibir el mensaje de respuesta, envíe notify_one para reactivar el hilo bloqueado

Aquí hay un poco más de detalle que creo que es más importante. Todos sabemos que las funciones tienen parámetros de entrada, parámetros de salida y parámetros de entrada y salida. Es decir, los parámetros en una función pueden ser tanto parámetros de entrada como parámetros de salida. Entonces un La llamada sincrónica ocurre en fdbus ¿Cómo compilan los parámetros CBaseJob::Ptr &msg_ref el contenido del mensaje recibido?

Por favor vea el artículo a continuación

Resumir

Al analizar la implementación de sincronización de fdbus, aprendí sobre un método de implementación de sincronización y obtuve una comprensión más profunda de las llamadas sincrónicas. Por supuesto, fdbus se basa en la red, por lo que incluso las llamadas sincrónicas esencialmente deben enviarse a la cola de mensajes o a la cola de eventos. procesarse de forma asincrónica.

Supongo que te gusta

Origin blog.csdn.net/iqanchao/article/details/133375374
Recomendado
Clasificación