by J. H. Saltzer, April 22, 1996, updated 1998, 1999, 2001, 2002, and April 29, 2003.
be be optimistic if pessimistic if ---------------------------------------------------------------- gap between what you large small might read/write and what you will actually read/write while disconnected chance of conflict while disconnected small large cost of patching low high things up later ----------------------------------------------------------------
storeid
which is a nonce for every
update to a file. Whenever it modifies a file, it creates a new
storeid
for the file. And when a disconnected client
modifies a file, it logs, in addition to the new file contents, both
the previous storeid
and the new one. When it later
reconnects it runs through the log of changes. As part of the proposal
to update the server version of this file, it mentions the previous
storeid
in the log. If that previous storeid
doesn't match the storeid
now on the server, that means
that someone else changed the server's copy while we were disconnected
and we have detected a conflict. If we detect a conflict, we have to
refer the problem to the user to resolve by hand.)
)2. Treatment of reintegration failure has changed.
In the 1991 version of the system, the entire reintegration (all files) was aborted if any part of it failed (the implementation left server state cleanly in the before-attempted-reintegration state). The failed reintegration was bundled up as a tarball (called a "closure") and left in a standard place on the local disk of the client attempting the reintegration. Coda cache state was then flushed and refetched as necessary to match server state.
The rationale for the original "all-or-nothing-within-a-volume" design was as follows:
- an update conflict indicates a higher than average probability that undetected read/write conflicts exist between other operations in the log and operations performed during disconnection at the server. Hence, it is "safer" to reject the whole reintegration of the volume and force manual repair.
- there were so few failed reintegrations in practice that it wasn't worth the sweat to implement anything fancier.
3. Some time in 1994-95, the extension of Coda to exploit weak connectivity (see Mummert et al in the 1995 SOSP) forced us to rethink some aspects of reintegration. In particular, we found it an annoyance to have a reintegration fail after many minutes of use of a slow link, and to discover that absolutely none of the updates had been propagated to the server. This was particular annoying because a common reason for reintegrating over a slow link is to make sure that work has been backed up on a server (so that a client hard disk crash, theft, loss, etc. doesn't cause loss of many hours of work).
We therefore changed reintegration to work as follows: when a conflict occurs, the entire prefix of the CML before the conflict is applied and an integer indicating the point in the CML at which the conflict occurred is returned (once a conflict is detected, the server doesn't bother proceeding further down the CML). This ensures that (a) each reintegration attempt propagates as much "good" work as possible and that (b) the client knows where the earliest conflict in the CML lies. It can try to deal with this conflict (for example by invoking an Application-Specific Resolver on the client) and, if successful, retrying the reintegration from that point on.
[Note: for non-technical reasons, the invocation of ASRs after failed reintegration has not been implemented yet; it was implemented a long time ago for conflicts detected from server replication; the extension poses no real technical problem --- just sweat]
[Note: I think this is yet another example of the "End-to-End Argument" in action :-) When connectivity was LAN, partial reintegration was not worth the sweat --- en masse retry was fine. When connectivity is weak, the cost of en masse retry is prohibitive; partial retry is needed to ensure an acceptable rate of progress. Much like asking whether an ack for the entire file is fine or whether chunks of it have to acked]
A further change we made on the client was the way in which the reintegration failure is reflected. Rather than creating a closure, we represent the conflict "in-place" as a dangling sym link (just as for conflicts arising from server replication). So a reintegration that fails due to a conflict on an object results in the following: updates before the conflict are safely reflected on the server; the conflicting update is marked as such on this client (but not anywhere else); updates after the conflicting update look like un-reintegrated state to this client.
Server Laptop1 Laptop2 Starting file: A L1 syncs with S: A A L1 edits A: A A1>A L2 syncs with L1: A A1 A1 L2 edits A A1 A2>A1 L2 syncs with S A2 A1 A2 Then: L1 syncs with S A2 A2 A2 (works OK, since A2>A1>A is consistent with both prior states) But, if instead of the last L1 syncs: L1 edits A2 A3>A1 A2 L1 syncs with S A2* A3* A2 (shows a conflict since A2>A1>A & A3>A1>A are inconsistent)If there are only 2 machines, then it is sufficient to remember current versions and versions at last sync. In the general case, however, one has to maintain more history, since syncing A with B raises a conflict unless A is an ancestor of B or vice versa. Somebody must know the minimum history requirement to make this determination, but the last common ancestor rule seems sufficient. Its not clear how timestamps generalize to this situation.
(Yes, but the way Coda is organized, it would help only rarely.
Section 4.4.1 of the paper points out that since Coda uses whole-file caching, what is logged is what would be outbound from the cache at close time (the whole file), rather than the list of detailed writes that went in to the cache. So what the reconciliation algorithm has to work with consists of the original file and two log entries. Each log entry contains a complete copy of the changed file. Rerunning those two logs in one order will give you Alice's result, while rerunning the logs in the other order will give you Bob's result. The algorithm doesn't make a mistake, but the only time it reports that the conflict doesn't need human resolution is when Alice and Bob made exactly the same change.
It would be necessary to make a gross modification to the emulation logging system, to log the traffic on the other side of the cache (that is, each write operation) in order for the algorithm ever to discover resolvable conflicts.)
Saltzer@mit.edu