From: Schmottlach, G. <gle...@ha...> - 2008-12-11 15:33:54
|
I have a few questions regarding the recommend pattern to create a client application that makes several calls to a DBus service while subscribed to signals from that service (or others). In particular I'm interested in being aware of any threading caveats which might cause dead-lock conditions or abnormal/strange behavior. Let me describe what I think a typical client application might look like: static DBus::BusDispatcher dispatcher; static volatile bool gExit(false); void sigHandler(int sig) { dispatcher.leave(); gExit = true; } void* workerThread(void* arg) { DBus::Connection* conn = reinterpret_cast<DBus::Connection*>(arg); try { MediaServiceClient client(*conn, MEDIA_SERVICE_OBJ_PATH, MEDIA_SERVICE_BUS_NAME); while ( !gExit ) { client.PlayAction(NMediaService::PLAY_ACTION_PLAY); sleep(1); client.PlayAction(NMediaService::PLAY_ACTION_PAUSE); } } int main(int argc, char* argv[]) { pthread_attr_t attr; pthread_t worker; bool threadStarted(false); signal(SIGTERM, sigHandler); signal(SIGINT, sigHandler); DBus::_init_threading(); DBus::default_dispatcher = &dispatcher; DBus::Connection conn = DBus::Connection::SessionBus(); pthread_attr_init(&attr); if ( EOK != pthread_create(&worker, &attr, workerThread, &conn) ) { cerr << "Failed to create thread" << endl; } else { threadStarted = true; dispatcher.enter(); } cout << "Terminating client . . ." << endl; if ( threadStarted ) { pthread_join(worker, NULL); } return EXIT_SUCCESS; } The service for which the client (MediaServiceClient) is generated also emits signals which the client receives. The handling of these signals is implemented by providing an implementation for the purely virtual callback methods in the generated client proxy. These implementations are found in the MediaServiceClient (which inherits from the generated client proxy). Here's where the questions related to concurrency and thread-safety enter the picture. The worker thread loops (until the gQuit flag is set) and continuously makes synchronous (blocking) calls to toggle the play state of the media player. Based on my (limited) understanding of the logic, a synchronous method call causes the thread to poll/select on a socket descriptor waiting for a specific reply. If any signals (or unrecognized replies) arrive during this process they're placed on a queue to be process later. So this seems to imply that when the synchronous call is made from the worker thread, the "main" thread that called dispatcher.enter() is also polling/selecting on the same socket descriptor at the same time. This means that when a message does arrive both threads (the "main" and worker thread) are each vying to dispatch the message. Is this the correct interpretation of what is going on internally? How is the message handling arbitrated? What happens if the "main" thread retrieves the expected reply message that the worker thread is waiting on? Things are also a little murky when considering signal handling. In my example above, it appears that that the signal callback methods should be executed on the "main" thread (from inside the dispatcher.enter() function). This means the signals arrive on a thread different than the worker thread that is making requests to the service. In general, is it safe to call methods on the service from these signal handler callbacks? My suspicion is that it's not a wise thing to do. If service methods need to be called as a result of receiving a signal, should the signal handling callbacks do nothing more than bundle the information into a context that is somehow delivered to the worker thread for processing? Can someone offer any suggestions on the "correct" way to implement a client like this? The C++ binding examples don't appear to address any of these issues and the non-existent documentation doesn't help ;-) Thanks . . . |