The Refresh Daemon Package Architecture and Code Description ============================================================ 0) Introduction ---------------- This document is intended to help those who want to understand the architecture and the source code of the refreshd package. Here is a short summary: o Section 1) shows what the refresh daemon does, o Section 2) describes the abstract concept of biditext key o Section 3) gives an overview of the refresh daemon operation o Section 4) gives an overview of the individual components of the refreshd package o Section 5) presents the code of the refreshd components in some detail. It is recommended to read this section in parallel with the code o Section 6) conclusion and author contact information 1) Problem Statement -------------------- One of the problems with the current version of biditext library is that after changing the biditext state (i.e. creating/deleting the .rev file), the application windows are not automatically refreshed. This means that after the biditext state has changed, the user has to manually refresh the application windows (either by minimizing/restoring the windows or by covering/uncovering them with other windows). The goal of the refresh daemon package is to overcome this problem by automatically refreshing the biditext enabled application windows whenever the biditext state changes. 2) The BidiText Keys Model -------------------------- Each biditext enabled application is associated with a (key, value) pair. The key can be an arbitrary string, while the value, which is boolean, indicates the current biditext state (enabled or disabled). In the current implementation, the key names a file from the file system and the value is implicitly encoded by the file's existence or absence, but this might change in future implementations. Several biditext applications can share the same biditext key. This means that whenever the value of the key changes all the windows from the applications that share the same key have to be refreshed. 3) Overview of the Refresh Daemon Operation ------------------------------------------- The refresh daemon maintains a database of all the biditext keys and the application windows which are associated with the biditext keys. The refresh daemon has two modes of operation: `automatic' and `manual'. In the automatic mode, a thread periodically wakes up and scans all the keys from the database. The thread uses the r2l_query_state() from the r2l library to check the value of a biditext key. If there is a change since the last scan, all the windows associated with the key are refreshed using the XClearArea() Xlib call. The manual mode of operation is similar to the automatic mode, except for the fact that the scan is not performed periodically; instead it is initiated by receiving an appropriate message (REFRESHD_REFRESH_NOW) from another application. This mode of operation is more suitable for GUIs that modify the biditext state. Whenever these GUIs modify the biditext state, they should send a refresh request message to the refresh daemon in order to refresh all the windows of the affected applications. 4) The Components of the Refresh Daemon Package ----------------------------------------------- The refresh daemon package consists of four components: 1) the refresh daemon hook library 2) the refresh daemon proper 3) the refresh daemon client library 4) the biditext application used to launch biditext-enabled applications Here is an overview of the components: 4.1) The Refresh Daemon Hook Library (refreshd_hook.so) ------------------------------------------------------- This is a shared library is preloaded in the address space of the biditext enabled application. Its task is to intercept the calls to XCreateWindow() and XCreateSimpleWindow() Xlib functions, stash the Display parameter and the return value and pass them to the refresh daemon. 4.2) The Refresh Daemon (refreshd) ---------------------------------- The refresh daemon maintains a database of all the windows of the applications that have been hooked by refreshd_hook.so. It consists of three threads: 1) a communication thread which listens on a UNIX domain socket, decodes messages, and performs the appropriate actions 2) an auto-refresh thread which periodically wakes up, checks if the state of any r2l tokens has changed and if so refreshes the appropriate windows. This thread does not run if the --no_auto option was passed to refreshd 3) a scavenger thread which periodically wakes up and checks whether the windows from the database are still valid. It does this by trying to get the window geometry with XGetGeometry(). If a window is invalid, it will be removed from the database. The daemon understands the following types of messages: add_window (replies with an ack message) ping (replies with an ack message) refresh_now 4.3) The Refresh Daemon Client Library (librefreshd_cnt.a) ---------------------------------------------------------- This library is used in order to hide the underlying communication with the daemon. Applications should communicate with the daemon only via this library as the communication layer might change in future releases. 4.4) The biditext Application ----------------------------- This application is used to launch a biditext enabled application. This application uses r2llib in order to locate the biditext key (.rev file), sets the BIDITEXT_FILENAME to the name of the biditext key, and launches the application via execvp() by preloading the refreshd_hook.so and biditext.so libraries. 5) Detailed Description of the Source Code ------------------------------------------ 5.1 The Refresh Daemon Hook Library (refreshd_hook.so) ------------------------------------------------------ There is one source file refreshd_hook.c under src/refreshd_hook. The code here is not particularly interesting. It provides its own versions of XCreateWindow() and XCreateSimpleWindow(). The code of these two functions is very similar. They call the old versions of the functions and pass the window ID to the refresh daemon. The library is automatically initialized through the _init() function. The loader calls this function whenever the library is initialized. 5.2 The Refresh Daemon (refreshd) --------------------------------- 5.2.1 The Window Database (class WindowDB) ------------------------------------------ The central part of the refresh daemon is its window database. This component is coded in the following source files under src/refreshd: o window_db.h/window_db.cc and o window_db_iterator.h/window_db_iterator.cc The window database (class WindowDB) has a hierarchical tree-like structure with three levels: Level 1 Level 2 Level 3 (biditext keys) (displays) (windows) ---+---biditext_key1------+---dpy1----+----wnd1 | | | | | +----wnd2 | | | | | +----wnd2 | | | +---dpy2----+----wnd3 | | | +----wnd4 | | +---biditext_key2-----+---dpy3-----+----wnd5 | | | | . +----wnd6 | . | . . . . The topmost level contains the biditext keys. The following data are stored together with the keys: an r2l token (of type r2llib_t) and the state of the r2llib_t token during the last scan (stored as a r2lstate structure). All this data is stored inside the PerBidiTextKeyData structure. If during a refresh traversal a difference between the current state of the r2l token and the state stored during the previous scan is detected, the windows associated with this key (i.e. all the leaf nodes) are refreshed. The intermediate level consists of the displays which the application windows are associated with. A Window ID can be used only in conjunction with an X display connection. refreshd tries to minimize the actual connections to the X server, by attempting to share X connections to the same display. See the description of the AutoDisplay class below. This is more or less the data structure used. Next I am going to describe the operations on this data structure. add() as you probably guessed adds items to this data structure. In order to traverse the window database, several iterators are used. A WindowDBBidiTextKeyIterator iterates over all the biditext keys (level 1) from the database. This iterator provides the following operations: o getBidiTextKey() -- returns the biditext key o getLastState() -- returns the value of the biditext key before the current scan o getCurrentState() -- returns the current value of the biditext key. o setLastState() -- set the value of the last biditext key o operator ==, !=, ++ (both prefix and postfix) with their usual meanings. The bidiTextKeyBegin() function from WindowDB returns an iterator to the first biditext key while bidiTextKeyEnd() returns an iterator which is 'one beyond the last' key. The DisplayMapIterator iterates over all the displays that are controlled by the same biditext key (r2l token serialization in the current implementation). The current version of refreshd supports applications that might use several X connections (displays) at the same time which can be all controlled by the same biditext key. This is an STL map iterator. This means that its unary * operator will return a pair. If dpmIt is a DisplayMapIterator (*dpmIt).first will return the pointer to AutoDisplay (AutoDisplay is described later), and (*dpmit).second will return the set of windows associated with that display. This iterator supports the usual ++, ==, != operators. WindowDB::dpyMapBegin() returns an iterator to the first display controlled by a biditext key. The biditext key is passed indirectly through the BidiTextKeyIterator& parameter. dpyMapEnd() returns an iterator `one beyond the last' display controlled by the key. The WindowSetIterator is an iterator on all the windows controlled by a display. It is an STL set iterator. Its unary * operator returns a window ID. It supports the usual ++, == and != operators. WindowDB::wndsBegin() returns an iterator to the first window controlled by a display. The display is passed indirectly through the DisplayMapIterator& parameter. WindowDB::wndsEnd() returns an iterator to 'one beyond the last window' controlled by the display. WindowDB has also a member function for removing elements. It is used by the refresh thread and the scavenger thread. What!? the refresh thread is removing elements from the database!? Yes, if the window IDs are no longer valid. The database is accessed and modified by the communication, refresh. and scavenger threads. In order to prevent corruption, the lock() and unlock() member function provide mutual exclusion. Whenever a thread tries to access the database, it acquires its lock first by calling lock(). When the thread is done accessing the database, it releases it by calling unlock(). 5.2.2 The AutoDisplay class ---------------------------- The AutoDisplay class (defined in auto_display.h and auto_display.cc) is a wrapper around an X Display. However, it tries to avoid using multiple connections to the same display. The only way to get an AutoDisplay object is by calling the getInstance() static method. This function checks if there is not already an open X connection on that display and if it is, it returns a pointer to an already created AutoDisplay object (and increments that object's reference count). The only way to destroy an AutoDisplay object is by calling its release() method. This decrements the object's reference count. If the reference count reaches zero, the X connection is also closed. 5.2.3 The Refresh Daemon's Threads ---------------------------------- 5.2.3.1 The Communication Thread -------------------------------- The communication thread (communicator() from communication.cc) handles all the communication with the external world. The communication takes place over UNIX domain datagram sockets. The communication thread waits for messages and processes them. The name of the socket is /tmp/refreshd_svc refreshd tries to use abstract socket names (i.e. socket names not bound to the file system). To do so, #define the ABSTRACT_UNIX_SOCKET_ADDRESSES in the refreshd_params.h file. This is supported only by Linux kernel versions larger than 2.2.0 On earlier versions and on other UNIX flavors, you should not define the macro. The rest of code is not particularly interesting. Just a switch statement to dispatch the various types of messages. Application programs should not send messages directly to the daemon over sockets. They should use the librefreshd_cnt.a library, described further on. The communication layer might change in future releases of refreshd. The REFRESHD_ADD_WINDOW message is synchronous (i.e. an acknowledgement is sent upon its receipt). This is done in order to prevent some race conditions which might cause the refresh daemon to miss refreshing window if its corresponding biditext key value (r2l token state) has changed shortly after the window has been created. The REFRESHD_PING message is also synchronous for obvious reasons. 5.2.3.2 Traversing the Window Database -------------------------------------- In the current implementation of the refreshd, the refresher and scavenger threads perform almost the same task. Both have to traverse the database. This common behavior is captured inside the traverse() function from traverse.cc The function accepts three pointers to callback functions. The function iterates over the window database tree in a DFS order. For each node of the tree the appropriate callback is invoked, depending on the node's level. Depending on the value returned by the callback, the iteration continues at the same level (WDB_IT_CONTINUE), goes on to a level above (WDB_IT_BREAK) or goes on to one level below (WDB_IT_NOTHING). The auto_refresh and scavenger threads call this function with appropriate callbacks. The traverse() function assumes that third level callbacks might cause X errors. Therefore an appropriate X error handler has to be set up that puts the offending window IDs inside a `bad List'. After a complete third level iteration, XSync() is called and all the windows from the `bad list' are removed from the database. 5.2.3.3 The Refresh Thread -------------------------- The refresh thread (refresher() from refresher.cc) performs the actual refreshing of the windows. This code is actually executed from the refreshd application's main thread (i.e. after the application initializes and creates all the other threads, it enters the refresh infinite loop). The refresh thread waits on a conditional variable to be signaled. Two threads can signal that variable. One is the auto-refresh thread when it wakes up and the other is the communication thread upon receiving a REFRESHD_REFRESH_NOW message. The auto-refresh thread (autoRefresher() from refresher.cc) is not created if the --no_auto command line option has been passed to refreshd. In that case, the only way to initiate a refresh is to send a REFRESHD_REFRESH_NOW message to the daemon (e.g. from a GUI). The refresh threads calls traverse with the following callbacks: 1) The first level callback checks if there is a change in the state of the biditext key value (r2l token). If there is no change, the callback returns WDB_IT_CONTINUE (i.e. go to the next biditext key, the iteration should not continue on a lower level). Otherwise (i.e. there is a change) the callback returns WDB_IT_NOTHING. 2) The second level callback is not used, so a NULL is passed. 3) The third level callback calls XClearArea(dpy,w,0,0,0,0,True); in order to perform the actual refresh. The call might fail, in which case the window has to be removed from the database, as described at the end of the previous section. This function always returns WDB_IT_NOTHING. 5.2.3.4 The Scavenger Thread ---------------------------- Consider the following scenario: A biditext enabled process runs for a while and then it dies unexpectedly. All its windows are in the refreshd database. Question: When are these windows going to be removed from the database? Answer: upon the next refresh, because then the windows will not be valid and the refresh thread will remove them. However when is this refresh going to happen? Well, maybe never. If nobody changes the value of the biditext key (state of the r2l token) and nobody sends a REFRESHD_REFRESH_NOW message to the refresh daemon, no refresh will ever happen and the windows that belonged to the dead process will remain in the database indefinitely. Here is where the scavenger comes into play. The scavenger thread periodically wakes up and checks if the windows from the database are still valid. It does this by invoking traverse() with the following callbacks: 1) The first level callback is not used, so a NULL is passed 2) The second level callback is not used either, so a NULL is also passed for it. 3) The third level callback attempts to get the geometry of the window by calling XGetGeometry() for it. The call might fail, in which case the window has to be removed from the database, as described at the end of the "Traversing the Window Database" section above. This function always returns WDB_IT_NOTHING. 5.2.3.5 main() (main.cc) ------------------------ Nothing particularly interesting here, though certain things might be worth noting. After the command line arguments are parsed, reefreshd forks(). The parent process sets up a signal handler that forwards a TERM or INT signal to its child and then waits() for the child process to exit. If the child process has exited, the parent process take care to unlink the server socket from the file system, if file system bound socket names are used. After that the parents exits. The child process initializes the multi-threading support of Xlib, sets up an X error handler, and creates the threads. autoRefresh is not created if --no_auto has been passed. 5.2.3.6 debugging support (debug.c) ----------------------------------- refreshd uses syslog to print various debug information. There are three levels of severity, errors which indicate fatal conditions (such as bugs inside the code) , warnings which indicate abnormal conditions which should not cause the daemon to crash (such as receiving an invalid message) and informational messages. Printing the messages is handled by the ERROR(), WARNING() and MESSAGE() macros. The amount of debug information is controlled by the NOISY and DEBUG_NOISE_LEVEL macros (see debug.h) NOSY is defined in the compilation command line, while DEBUG_NOISE_LEVEL should be defined in debug.h If NOISY is not defined, no debug information is written at all. Otherwise the amount of information is controlled by DEBUG_NOISE_LEVEL. 5.3 The Refresh Daemon Client Library ------------------------------------- This library is used by all the applications to communicate with the refresh daemon. This library hides the underlying communication layer with the daemon. The refreshd_hook.so library is linked with this library and uses it to communicate with the refresh daemon. It also takes care to wait for the acknowledgements from the daemon if it has to (i.e. REFRESHD_ADD_WINDOW) messages. A GUI can also link with this library in order to be able to initiate refreshes calling rdCntRefreshNow(). In order to ensure full multi-threaded support a socket is created for each message sent. The socket is destroyed after the message has been sent and the acknowledgement has been received (if it had to be received). I think that doing otherwise cannot avoid race conditions caused by multiple threads invoking the same function and/or socket descriptors being duplicated by fork(). In order to be able to send a message over a UNIX domain socket, the socket has to be bound to a name. I chose a naming scheme that attempts to minimize collisions. The name is created from the current process id, a time stamp obtained via gettimeofday() and a random number. In order to transmit biditext keys over sockets I use Xlib atoms, in order to avoid imposing an upper bound on the biditext key lengths. Because of this an application linking with refreshd_cnt must also link with Xlib (-lX11). 5.4 The biditext Application ---------------------------- The biditext application supersedes the biditext shell script from the original biditext distribution. It performs all the tasks that shell script used to do (i.e. setting the LD_PRELOAD variable and executing the application). In addition to those tasks, biditext does the following: It creates an r2l token using r2l_init() and then it sets the BIDITEXT_FILE environment variable to the serialized version of the r2l token obtained via r2l_get_text_serialization(). The code adds the refreshd_hook.so and biditext.so libraries into the LD_PRELOAD environment variable and after that execvp()s the application. 6. Conclusion ------------- Version 0.1.0 is a major rewrite of version 0.0.2. While this version does not bring any significantly new features, it has several internal improvements concerning multi-threading, error handling, and integration with r2llib. Despite my best efforts to avoid them, it is likely that some bugs have crept in. Therefore your feedback is very important to me. Feel free to send any comments, suggestions, corrections, feedback or flames regarding both refreshd or this document to me (e-mail: emild@cs.technion.ac.il).