On the client computer Rover is divided into client applications and the access manager. Client applications are child processes of the access manager. The access manger communicates with its children through pipes. The Rover library compiled into the client application manages communication to the access manager through those pipes in an event driven state machine. If the client application is a tk script, the state machine also interacts with the tk event loop. HTRoverClientMainLoop runs the Rover state machine.
When an application imports an object, Rover first checks the object cache. If the object is resident and the application accepts cached object copies, the application receives a working copy of the cached object. When an application invokes a method on an object, it may either directly invoke the operation on its working copy of the object or it may call Rover to invoke the operation. In the latter case, the operation is performed on the working copy of the object. The operation and the new tentative state of the object are now also stored in the cache. The operation is stored in a log associated with the object. The tentative copy of the object is stored in addition to the permanent state received directly from the server during import. The client then exports the operation log to the server. The permanent state of the object is updated when the server reports the result of performing and reconciling the operation on its copy of the object. By using tentative objects, applications can continue computation even if the mobile host is disconnected. Rover applications typically reflect the tentative state of objects to users (e.g., by displaying them in a different color) so that users can tell that their updates are not yet permanent.
If an object is not present at import time, Rover lazily fetches it from the server using a queued remote procedure call. Rover stores the QRPC in a stable log at the mobile host and returns control to the application. The application can register a callback routine with Rover, which will be called by Rover to notify the application that the object has arrived. If the mobile host is connected, the Rover network scheduler drains the log in the background and forwards QRPCs to the server.
Upon arrival of an import request, the server fetches the requested object and sends it back to the mobile host. If a mobile host is disconnected between sending the request and receiving the reply, Rover will replay the request from its stable log upon reconnection [designed but not implemented]. Upon receiving a fetch reply, Rover inserts the object returned into the cache and the application and deletes the QRPC from the stable log. In addition, if a callback routine is registered, Rover will perform the callback to inform the application that the object has arrived. The application can then invoke methods on the local copy.
When an invoked method modifies a cached object as the result of an export, Rover lazily updates the primary copy at the server by sending the method call in a QRPC to the server, and returns control to the application. When the QRPC arrives at the server, the server invokes the requested method on the primary copy. Typically a method call first checks whether the object has changed since it the client last received an update for the object. Rover maintains version vectors for each object so that methods can easily detect such changes. If the object has not changed, the method modifies the primary copy, logs the operation, and sends a reply back to the mobile host.
If a method call at the server detects an the object has changed since the client last updated the object, the server copy of the object must be reconciled with the operation log sent by the client. The Rover toolkit itself does not enforce any specific concurrency control mechanism or consistency guarantees on copies of objects. Instead, it provides a mechanism for detecting conflicts and maintaining operations logs while leaving it up to applications to reconcile objects. For example, the Rover Ical distributed calendar tool exploits semantic knowledge about calendars, appoinments, and notices to determine whether a change violates consistency. For example, concurrently deleting two different appointments in the same calendar does not result in a conflict. However, the client is informed of the concurrent delete, so that the client copy of the calendar will reflect the second delete. If there is a conflict that cannot be reconciled, the method returns with an error. These errors are reflected to the user so that he or she can resolve the conflict.
In general, the server reply consists of a log of operations which will transform the permanent state of the client copy of the RDO to reflect the newly computed state of the RDO at ther server. The reply may indicate either successful completion of the operation originally submitted by the client or a substitute operation to be performed. Upon arrival of the reply at the client, Rover deletes the QRPC from the stable log, retrieves the permanent copy of the object from the cache, applies the entire log of operations indicated by the server and invokes the callback associated with the original operation (if one is registered). The resulting new permanent state of the object is cached. Rover then replays any other outstanding tentative operations logged on the object, invoking callbacks on error, to update the tentative and working copies of the object to complete the reconciliation process.
The interval between the time an object is imported to an application and the time an operatoin is exported back to the server represents the time during which conflicting updates may occur. Applications can eliminate this window by using application-specific locks or can reduce this window through the use of periodic polling or server callbacks.
The (slightly modified) code for the base application follows. The program accepts the name of and arguments to a Tcl script from the command line. A Tcl interpreter is created and initialized, A custom command is added to the Tcl interpreter. Finally the named script is loaded from the server and run with the provided arguments.
In this example, note the use of HTRoverClientInit, HTRoverClientMainLoop, and global variables. These variable appear: ROVER_CLIENT, RoverDebug, HTLibraryVersion, Tk_Window mainWindow, globalTclInterp, and RoverAppInit.
/********************************************************************** * * * Rover Toolkit * Anthony D. Joseph * Laboratory for Computer Science * Massachusetts Institute of Technology * * Module: base.c * * Description: Client application module * * * Global Functions: int main(int argc, char **argv); * * Global Variables: none * *********************************************************************** * Copyright 1995-1998 * Anthony D. Joseph * Massachusetts Institute of Technology * * Permission to use, copy, modify, and distribute this program * for any purpose and without fee is hereby granted, provided * that this copyright and permission notice appear on all copies * and supporting documentation, the name of M.I.T. not be used * in advertising or publicity pertaining to distribution of the * program without specific prior permission, and notice be given * in supporting documentation that copying and distribution is * by permission of M.I.T. M.I.T. makes no representations about * the suitability of this software for any purpose. It is pro- * vided "as is" without express or implied warranty. ***********************************************************************/ #define ROVER_CLIENT #include "RoverBase.h" #include "HTApplication.h" #include "HTRover.h" #include "HTSession.h" #include "stdio.h" PRIVATE int FolderUnmarshall(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]); PRIVATE void usage(char *arg) { if (arg) fprintf(stderr, "Bad Command Line Argument: %s\n", arg); fprintf(stderr, "Usage: base [-d] [-- ]\n"); RoverExit(0); } PRIVATE void BaseInit(int applID) { Tcl_CreateCommand(globalTclInterp, "FolderUnmarshall", FolderUnmarshall, NULL, NULL); } PUBLIC int main(int argc, char **argv) { int appID, arg, AppArgc = 0, ignore = 0; char **AppArgv; fprintf(stderr, "Rover base.c client (Rover version %s, WWW Library Version %s)\n", RoverLibVersion, HTLibraryVersion); if ((AppArgv = (char **)malloc((argc+1)*sizeof(char*))) == NULL) { /* Allocate space for image name and trailing null argv entry */ fprintf(stderr, "Out of memory [main:base.c]\n"); RoverExit(-1); } /* Check for command line options */ for (arg=0; arg<argc ; arg++) { if (!ignore && (*argv[arg] == '-')) { /* - alone => error */ if (argv[arg][1] == 0) { usage(argv[arg]); /* --: ignore all else (as args); treat as keywords */ } else if (!strcmp(argv[arg], "--")) { ignore = 1; /* -? or -help: show the command line help page */ } else if (!strcmp(argv[arg], "-?") || !strcmp(argv[arg], "-help")) { usage(NULL); /* Debug flag */ } else if (!strcmp(argv[arg], "-d")) { if (arg+1 < argc && *argv[arg+1] != '-') RoverDebug = atoi(argv[++arg]); /* endif long list of argument options */ } else { usage(argv[arg]); } } else { /* If no leading `-' (or ignore is set), then check for main argument */ if (RoverDebug) fprintf(stderr, "Arg %d is |%s|, storing in arg %d\n", arg, argv[arg], AppArgc); AppArgv[AppArgc++] = argv[arg]; } /* Not an option '-'*/ } /* End of argument loop */ AppArgv[AppArgc] = NULL; if (RoverDebug) { int i; fprintf(stderr, "Got %d application args\n", AppArgc); for (i = 0; i < AppArgc; i++) fprintf(stderr, "|%s| ", AppArgv[i]); fprintf(stderr, "\n"); } RoverAppInit = BaseInit; if (RoverDebug) fprintf(stderr, "Initializing client library...\n"); appID = RoverClientInit(TCL_AND_TK, AppArgc, AppArgv); if (RoverDebug) fprintf(stderr, "appID is %d\n", appID); if (appID < 0) exit(-1); RoverClientMainLoop(); /* Run the chooser */ return(0); } /* FolderUnmarshall PRIVATE int FolderUnmarshall(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]) { #define TCL_FLAGS TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG char *self, *user, *ptr, *next, *sp, *msgid, name[1024], number[32], *rv; int num, count = 0; if (argc < 4) { interp->result = "wrong # args: FolderUnmarshall */ "; return TCL_ERROR; } user = argv[1]; self = argv[2]; ptr = argv[3]; while (*ptr != '\0') { if ((next = index(ptr, '\n')) == NULL) break; *next++ = '\0'; if ((sp = index(ptr, ' ')) == NULL) { ptr = next; continue; } *sp = '\0'; msgid = ptr; ptr = sp+1; num = atoi(ptr); sprintf(number, "%d", num); ptr += 5; sprintf(name, "%s(MsgIDs,%d)", self, num); rv = Tcl_SetVar(globalTclInterp, name, msgid, TCL_FLAGS); if (!rv) { sprintf(name, "TCL SETVAR ERR: %s [FolderUnmarshall:base.c]\n", globalTclInterp->result); interp->result = name; return(TCL_ERROR); } sprintf(name, "%s(Currents,%s)", self, msgid); rv = Tcl_SetVar(globalTclInterp, name, " ", TCL_FLAGS); if (!rv) { sprintf(name, "TCL SETVAR ERR: %s [FolderUnmarshall:base.c]\n", globalTclInterp->result); interp->result = name; return(TCL_ERROR); } sprintf(name, "%s(Numbers,%s)", self, msgid); rv = Tcl_SetVar(globalTclInterp, name, number, TCL_FLAGS); if (!rv) { sprintf(name, "TCL SETVAR ERR: %s [FolderUnmarshall:base.c]\n", globalTclInterp->result); interp->result = name; return(TCL_ERROR); } sprintf(name, "%s(Text,%s)", self, msgid); rv = Tcl_SetVar(globalTclInterp, name, ptr, TCL_FLAGS); if (!rv) { sprintf(name, "TCL SETVAR ERR: %s [FolderUnmarshall:base.c]\n", globalTclInterp->result); interp->result = name; return(TCL_ERROR); } sprintf(name, "Rover__Email__%s__Message__%s", user, msgid); rv = Tcl_GetVar(globalTclInterp, name, TCL_FLAGS); sprintf(name, "%s(Status,%s)", self, msgid); if (rv) rv = Tcl_SetVar(globalTclInterp, name, "Loaded", TCL_FLAGS); else rv = Tcl_SetVar(globalTclInterp, name, "Not Loaded", TCL_FLAGS); if (!rv) { sprintf(name, "TCL SETVAR ERR: %s [FolderUnmarshall:base.c]\n", globalTclInterp->result); interp->result = name; return(TCL_ERROR); } ptr = next; count++; } sprintf(number, "%d", count); sprintf(name, "%s(Count)", self); rv = Tcl_SetVar(globalTclInterp, name, number, TCL_FLAGS); if (!rv) { sprintf(name, "TCL SETVAR ERR: %s [FolderUnmarshall:base.c]\n", globalTclInterp->result); interp->result = name; return(TCL_ERROR); } return(TCL_OK); }