方法调用中的 gdbus 信号块

gdbus signal block in method call

我正尝试在方法调用内通过 dbus (g_dbus_connection_emit_signal) 发送信号。我有一个使用 dbus 与服务器程序通信的客户端程序。当我在服务器端调用一个方法时,它会重定向到该方法函数,并且在执行该方法时,我会在两者之间发送信号。但是我在完成该方法后收到信号,那已经很晚了。

我不明白为什么在 dbus 方法调用中会发生这种情况。

问题是两个信号同时到达客户端,

在下面的代码中 -> _apply_ota() 是客户端调用的方法。

**
 * Handle all method calls
 * */
static void handle_method_call (GDBusConnection *connection,
                    const gchar           *sender,
                    const gchar           *object_path,
                    const gchar           *interface_name,
                    const gchar           *method_name,
                    GVariant              *parameters,
                    GDBusMethodInvocation *invocation,
                    gpointer               user_data)
{
#if DBG_SRV_METHOD
    fprintf(stdout,"method_call - %s %s\r\n", method_name,g_variant_get_type_string (parameters));
#endif
    char *response = NULL;
    /*
     * Get Config
     * */
    if (g_strcmp0(method_name, "apply_ota") == 0) {
        cJSON * config = NULL;
        ERROR_CODE status = _apply_ota();
    }else{
        g_dbus_method_invocation_return_dbus_error (invocation,SERVICE_ERROR,"SNAP ! Unhandled Method ..");
    }

    /*Send the response*/
    if(response != NULL){
        g_dbus_method_invocation_return_value(invocation,g_variant_new("(s)", response));
        if (response != NULL)
            free(response);
    }else{
        g_dbus_method_invocation_return_dbus_error (invocation,SERVICE_ERROR,"NULL Response");
    }
}
/*
 * Echo Signal Handlers
 * */
static void service_signal_handler (GDBusConnection  *connection,
                                const gchar      *sender_name,
                                const gchar      *object_path,
                                const gchar      *interface_name,
                                const gchar      *signal_name,
                                GVariant         *parameters,
                                gpointer         user_data)
{
#if DBG_SRV_SIGNALS
    printf("ECHO Signal - %s%s\r\n",signal_name,g_variant_get_type_string(parameters));
#endif

    /*
     * You are hearing your own signals
     * Echo the signals to WEB Clients
     * */
    cJSON * msg = NULL;
    msg = cJSON_CreateObject();
    if (msg != NULL) {
        cJSON_AddItemToObject(msg, KEY_MSG_TYPE,
                cJSON_CreateString(VAL_SIGNAL));
        cJSON_AddItemToObject(msg, KEY_SERVICE_NAME,
                cJSON_CreateString(SERVICE_NAME));
        cJSON_AddItemToObject(msg, KEY_MESSAGE,
                cJSON_CreateString(signal_name));

        /*
         * Check if there are parameters
         * We only expect one parameter in signal
         * */
        if (g_strcmp0(g_variant_get_type_string(parameters), "(s)") == 0) {
            const gchar * param_json_str;
            g_variant_get(parameters, "(&s)", &param_json_str);
            if (param_json_str != NULL) {
                cJSON * param_json = NULL;
                param_json = cJSON_Parse(param_json_str);
                if (param_json != NULL) {
                    cJSON_AddItemToObject(msg, KEY_ARG, param_json);
                } else {
                    fprintf(stderr, "Failed to parse signal parameters \r\n");

                }
            }
        }


        /*Broadcast cast signal to WEB Clients*/
        if (!ws_send_brodcast(msg)) {
            fprintf(stderr, "Failed to send broadcast!\r\n");
        }
        cJSON_Delete(msg);
    }
}
bool service_emit_json_signal(char * signal_name, cJSON * msg_json){
#if DBG_SRV_SIGNALS
    printf("service_emit_json_signal - %s \r\n", signal_name);
    printf_json(msg_json,"msg = \r\n");
#endif
    bool status = false;
    GError *error = NULL;
    GDBusConnection *c = NULL;
    GVariant * parameters;
    char* msg = NULL;
    c = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
    if(c!= NULL){
        msg = cJSON_PrintUnformatted(msg_json);
        parameters = g_variant_new("(s)",msg);
        if (parameters != NULL){
            parameters = g_variant_ref_sink (parameters);
            if(parameters != NULL){
                /*Ready to send Signal*/
                status = g_dbus_connection_emit_signal(c,
                                                NULL,
                                                MAIN_INTERFACE,
                                                SERVICE_NAME,
                                                signal_name,
                                                parameters,
                                                NULL);
                if(!status){
                    fprintf(stderr,"Error Sending  Signal - %s\r\n", error->message);
                }
            }
        }
    }else {
        /*Failed to connect to the DBUS*/
        fprintf(stderr, "Error connecting to BUS - %s\r\n", error->message);
    }

    /*Clean UP*/
    if (error != NULL)
        g_error_free(error);
    if (msg != NULL)
        free(msg);
    if (parameters != NULL)
        g_variant_unref(parameters);
    if (c != NULL)
        g_object_unref(c);
    return status;
}

bool emit_ota_updating_signal(OTA_STATUS status){

    bool ret = false;
    cJSON * signal_json = cJSON_CreateObject();
    if(signal_json != NULL){
        cJSON_AddItemToObject(signal_json, KEY_OTA, cJSON_CreateNumber(status));
        ret = service_emit_json_signal(SIG_OTA_UPDATING,signal_json);
        cJSON_Delete(signal_json);
    }
    return ret;
}

ERROR_CODE _apply_ota(){

    
    if(emit_ota_downloading_signal(OTA_DOWNLOADING)){

        dcc_download_ota_file(ota->ota_file_url);
    }else{
        goto exit;
    }

    if(emit_ota_updating_signal(OTA_UPDATING)){

            dcc_run_ota_update_script();
    }else{
        goto exit;
    }




    return ret;
}

.

您的代码示例不是 minimal reproducer(它非常复杂,但仍然缺少各种符号,因此只有您可以编译和测试它)。

但是,据我所知,代码在处理方法调用时似乎存在阻塞问题。看起来 _apply_ota() 可能需要几秒钟 — dcc_download_ota_file()dcc_run_ota_update_script() 看起来它们是阻塞函数,每个 运行 需要几秒钟(或更长时间)。这些与处理传入的 D-Bus 方法调用在同一个线程中执行。如果他们阻止该线程(阻止其主循环迭代和处理输入),他们可能会阻止传出信号发射。

您可能想阅读这些指南,了解如何使用线程和异步函数调用 GLib/GIO:

这些关于 D-Bus API 设计的指南:

像这样的代码工作的标准方式,其中一个方法调用触发一个长 运行ning 操作,是立即对 return 的方法调用,然后离开操作 运行宁在后台。当操作完成时,它会发出一个信号。这是 what NetworkManager does,例如,使用改变网络状态的方法调用。

如果应向 D-Bus 服务的所有对等方宣布因调用 _apply_ota() 而导致的状态更改,请选择此方法。也许通过“更新到新版本”信号,如果我将“ota”正确解释为“无线更新”。

或者,方法调用可以 return 指向表示正在进行的操作的 D-Bus 对象的新 D-Bus 对象路径。该对象可以提供表示操作进度的属性或取消它的方法。例如,这就是 freedesktop portal API 所做的。

如果调用 _apply_ota() 导致的状态变化仅与 _apply_ota() 的调用者相关,或者如果调用者需要能够知道状态何时从该特定 _apply_ota() 调用已完成。 (一般信号发射,如第一种模式,无法可靠地匹配回特定方法调用。)

所以我建议重构代码以更紧密地遵循这两种设计模式中的一种。但是,这仅基于我在您的问题中看到的内容,并非完整图片。