A libev based mainloop and lost signals

Thomas Themel thomas at themel.com
Thu Aug 19 02:40:58 PDT 2010


Hi,
Excerpts from Thiago Macieira's message of Wed Aug 11 18:58:43 +0200 2010:
> > Excerpts from Thiago Macieira's message of Tue Aug 10 14:30:30 +0200 2010:
> > > To make this work, you must not use _and_block. You must instead use
> > > send_with_reply and make sure that the other thread doesn't pull your
> > > reply before you do.
> > 
> > Well, send_with_reply is not an overly nice API for a multithreaded setup
> > since I have a race between setting the DBusPendingCall's callback and the
> > dispatching of the reply in the background thread.
> 
> That can easily be solved by taking a mutex lock around the D-Bus dispatcher. 
> The background thread will be woken up out of select(2) or poll(2), but will 
> block on trying to acquire the mutex.
> 
> When the foreground thread is done calling send_with_reply, it only needs to 
> unlock that mutex.

You are of course right that this is easy to do, and I've implemented it this
way now. However, it still feels a little bit like I'm misunderstanding
something.

So, here's what I do:

struct wait_info { 
    pthread_mutex_t nMutex;
    pthread_cond_t nCond;
};

struct wait_info* new_wait_info() { 
    struct wait_info* pRet = malloc(sizeof(struct wait_info));
    assert(pRet);
    pthread_mutex_init(&pRet->nMutex, NULL);
    pthread_cond_init(&pRet->nCond, NULL);
    return pRet;
}

void free_wait_info(struct wait_info* pInfo) { 
    pthread_mutex_destroy(&pInfo->nMutex);
    pthread_cond_destroy(&pInfo->nCond);
    free(pInfo);
}

void pending_call_notifier_function(DBusPendingCall *pending, void *user_data) { 
    struct wait_info* pCtx = user_data;

    pthread_mutex_lock(&pCtx->nMutex);
    pthread_cond_broadcast(&pCtx->nCond);
    pthread_mutex_unlock(&pCtx->nMutex);
}

extern pthread_mutex_t g_nDispatchMutex;

DBusMessage* my_send_with_reply_and_block(DBusConnection* pConn, DBusMessage* pRequest, int nTimeOutMS, DBusError* pError) {
    DBusPendingCall* pPendingCall;
    DBusMessage* pReply = NULL;

    // avoid race in dbus_pending_call_set_notify
    struct wait_info* pInfo = new_wait_info();

    pthread_mutex_lock(&g_nDispatchMutex);
    pthread_mutex_lock(&pInfo->nMutex);

    if(!dbus_connection_send_with_reply(pConn, pRequest, &pPendingCall, 10000)) { 
        fprintf(stderr, "Failed to call DBus method!\n");
        //TODO: Set DBusError!
        pthread_mutex_unlock(&g_nDispatchMutex);
        return NULL;
    }
    dbus_pending_call_set_notify(pPendingCall, pending_call_notifier_function, pInfo, free_wait_info);
    pthread_mutex_unlock(&g_nDispatchMutex);

    pthread_cond_wait(&pInfo->nCond, &pInfo->nMutex);
    pthread_mutex_unlock(&pInfo->nMutex);

    if(dbus_pending_call_get_completed(pPendingCall)) { 
        pReply = dbus_pending_call_steal_reply(pPendingCall);

        if(pReply != NULL &&  pError != NULL && dbus_message_get_type(pReply) == DBUS_MESSAGE_TYPE_ERROR) { 
            const char* message = "";
            dbus_message_get_args(pReply, pError, DBUS_TYPE_STRING, &message, DBUS_TYPE_INVALID);
            dbus_set_error(pError, dbus_message_get_error_name(pReply), message);
            dbus_message_unref(pReply);
            pReply = NULL;
        }


    } else { 
        fprintf(stderr, "pending call notified us but is not completed.\n");
    }

    pthread_mutex_unlock(&pInfo->nMutex);
    dbus_pending_call_unref(pPendingCall);
    return pReply;    
}

While this seems to be rather stable (to the point that I started noticing that
dbus_bus_add_match_rule also uses dbus_connection_send_with_reply_and_block and
thus cause occasional hangs when used in my code :)), I still think I've
essentially reimplemented what I thought dbus_pending_call_block would do here.

I have also tried to actually replace my custom condition wait code with
dbus_pending_call_block, but that seems to produce very similar results to
simply using dbus_connection_send_with_reply_and_block directly.

> Though I agree that libdbus-1 should be doing this on its own. A 
> send_with_reply operation should not be interruptible. And I think it does 
> that, in D-Bus 1.3.x.

Hrm, I'm using what Debian currently ships in experimental, which looks like 
a git snapshot from July: 1.3.2~git20100715.821f99c-1, so I presume that will
not affect my problems.

> Note, however, that each send_with_reply_and_block, like I said above, will 
> try to change states of timers and fd-watchers. Your event loop implementation 
> must support changing state of those from *any* thread, not just the mainloop 
> thread. This was the source of a number of bugs in QtDBus until I finally 
> nailed it down around Qt 4.5.

Hm, really? I have debug output in my add_watch_function, toggle_watch_function
and remove_watch_function, and they never seem to get called when I do a
dbus_connection_send_with_reply_and_block from the main thread.

Now that you gave me the idea, I think this is the actual problem here -
dbus_connection_send_with_reply_and_block will take over the socket, but not
tell the main loop that it does so?

> If this is all that your code is doing, it's fine.
> 
> But what I had understood is that you're making calls from one thread that 
> should be processed by the mainloop thread. That is, you're making calls to 
> yourself.

No, I'm not making calls to myself. I'm calling a DBus method on another process.
Sometimes, this method will also emit a signal that I'm AddMatched to on the client,
and my initial concern was that this signal sometimes seemed to be lost (inside 
lidbus, since I could see it arrive in the process via various means).

ciao,
-- 
[*Thomas  Themel*]  This signature is only valid if displayed
[extended contact]  ~~     in the correct color _red_.     ~~
[info provided in]  Help fight misconfigured news readers,
[*message header*]       use "X-Signature-Color: red"!


More information about the dbus mailing list