Asynchronous generator helpers rework + PKI work
authorDan Mills <thunder@mozilla.com>
Fri, 07 Mar 2008 01:56:36 -0800
changeset 44364 7fc265bb2933cb63dc6db955c9f0568bfcaced4f
parent 44363 b03427f958ddbd5ccc1fb682d7afc18fe87cb7b3
child 44365 ce5c78ea49fe1abaf2f75cd98747faff453bf382
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
Asynchronous generator helpers rework + PKI work * Async helpers are in a module of their own now * Async routines have simpler semantics now. onComplete handlers are taken care of by the helpers. Exceptions are bubbled up across nested asynchronous generators * Stack traces are automatically logged for unhandled exceptions * Async generators are now allowed to 'bottom out' (StopIteration is ignored) - this is configurable. * RSA key generation fixes * On login we now create an RSA keypair, encrypt the private one with PBE, and upload them to the server * Log files are now limited to 2MB (down from 5)
services/sync/modules/crypto.js
services/sync/modules/dav.js
services/sync/modules/engines.js
services/sync/modules/identity.js
services/sync/modules/log4moz.js
services/sync/modules/service.js
services/sync/modules/syncCores.js
services/sync/modules/util.js
--- a/services/sync/modules/crypto.js
+++ b/services/sync/modules/crypto.js
@@ -40,18 +40,19 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/constants.js");
 Cu.import("resource://weave/util.js");
+Cu.import("resource://weave/async.js");
 
-Function.prototype.async = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
 
 function WeaveCrypto() {
   this._init();
 }
 WeaveCrypto.prototype = {
   _logName: "Crypto",
 
   __os: null,
@@ -205,41 +206,41 @@ WeaveCrypto.prototype = {
     // nsIProcess doesn't support stdin, so we write a file instead
     let passFile = Utils.getTmp("pass");
     let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
     passFOS.writeString(password);
     passFOS.close();
 
     try {
       this._openssl("pkcs8", "-in", "privkey.pem", "-out", "enckey.pem",
-                    "-topk8", "-v2", algorithm, "-pass", "file:pass");
+                    "-topk8", "-v2", algorithm, "-passout", "file:pass");
 
     } catch (e) {
       throw e;
 
     } finally {
       passFile.remove(false);
       privKeyF.remove(false);
     }
 
     let [cryptedKeyFIS] = Utils.open(cryptedKeyF, "<");
     let cryptedKey = Utils.readStream(cryptedKeyFIS);
     cryptedKeyFIS.close();
-    cryptedKey.remove(false);
+    cryptedKeyF.remove(false);
 
     let [pubKeyFIS] = Utils.open(pubKeyF, "<");
     let pubKey = Utils.readStream(pubKeyFIS);
     pubKeyFIS.close();
     pubKeyF.remove(false);
 
     return [cryptedKey, pubKey];
   },
 
   // returns 'input' encrypted with the 'pubkey' public RSA key
-  _opensslRSAEncrypt: function Crypto__opensslRSAEncrypt(input, pubkey) {
+  _opensslRSAencrypt: function Crypto__opensslRSAencrypt(input, pubkey) {
     let inputFile = Utils.getTmp("input");
     let [inputFOS] = Utils.open(inputFile, ">");
     inputFOS.writeString(input);
     inputFOS.close();
 
     let keyFile = Utils.getTmp("key");
     let [keyFOS] = Utils.open(keyFile, ">");
     keyFOS.writeString(pubkey);
@@ -256,17 +257,17 @@ WeaveCrypto.prototype = {
     let output = Utils.readStream(outpusFIS);
     outputFIS.close();
     outputFile.remove(false);
 
     return output;
   },
 
   // returns 'input' decrypted with the 'privkey' private RSA key and password
-  _opensslRSADecrypt: function Crypto__opensslRSADecrypt(input, privkey, password) {
+  _opensslRSAdecrypt: function Crypto__opensslRSAdecrypt(input, privkey, password) {
     let inputFile = Utils.getTmp("input");
     let [inputFOS] = Utils.open(inputFile, ">");
     inputFOS.writeString(input);
     inputFOS.close();
 
     let keyFile = Utils.getTmp("key");
     let [keyFOS] = Utils.open(keyFile, ">");
     keyFOS.writeString(privkey);
@@ -333,136 +334,132 @@ WeaveCrypto.prototype = {
     } break;
     default:
       this._log.warn("Unknown encryption preference changed - ignoring");
     }
   },
 
   // Crypto
 
-  PBEencrypt: function Crypto_PBEencrypt(onComplete, data, identity, algorithm) {
-    let [self, cont] = yield;
-    let listener = new Utils.EventListener(cont);
-    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  PBEencrypt: function Crypto_PBEencrypt(data, identity, algorithm) {
+    let self = yield;
     let ret;
 
-    try {
-      if (!algorithm)
-        algorithm = this.defaultAlgorithm;
+    if (!algorithm)
+      algorithm = this.defaultAlgorithm;
 
-      if (algorithm != "none")
-        this._log.debug("Encrypting data");
+    if (algorithm != "none")
+      this._log.debug("Encrypting data");
 
-      switch (algorithm) {
-      case "none":
-        ret = data;
-        break;
+    switch (algorithm) {
+    case "none":
+      ret = data;
+      break;
 
-      case "XXXTEA": // Weave 0.1.12.10 and below had this typo
-      case "XXTEA": {
-        let gen = this._xxtea.encrypt(data, identity.password);
-        ret = gen.next();
+    case "XXXTEA": // Weave 0.1.12.10 and below had this typo
+    case "XXTEA": {
+      let gen = this._xxtea.encrypt(data, identity.password);
+      ret = gen.next();
+      let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      try {
         while (typeof(ret) == "object") {
-          timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
+          timer.initWithCallback(self.listener, 0, timer.TYPE_ONE_SHOT);
           yield; // Yield to main loop
           ret = gen.next();
         }
         gen.close();
-      } break;
-
-      case "aes-128-cbc":
-      case "aes-192-cbc":
-      case "aes-256-cbc":
-      case "bf-cbc":
-      case "des-ede3-cbc":
-        ret = this._opensslPBE("-e", algorithm, data, identity.password);
-        break;
-
-      default:
-        throw "Unknown encryption algorithm: " + algorithm;
+      } finally {
+        timer = null;
       }
-
-      if (algorithm != "none")
-        this._log.debug("Done encrypting data");
-
-    } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    } break;
 
-    } finally {
-      timer = null;
-      Utils.generatorDone(this, self, onComplete, ret);
-      yield; // onComplete is responsible for closing the generator
+    case "aes-128-cbc":
+    case "aes-192-cbc":
+    case "aes-256-cbc":
+    case "bf-cbc":
+    case "des-ede3-cbc":
+      ret = this._opensslPBE("-e", algorithm, data, identity.password);
+      break;
+
+    default:
+      throw "Unknown encryption algorithm: " + algorithm;
     }
-    this._log.warn("generator not properly closed");
+
+    if (algorithm != "none")
+      this._log.debug("Done encrypting data");
+
+    self.done(ret);
   },
 
-  PBEdecrypt: function Crypto_PBEdecrypt(onComplete, data, identity, algorithm) {
-    let [self, cont] = yield;
-    let listener = new Utils.EventListener(cont);
-    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  PBEdecrypt: function Crypto_PBEdecrypt(data, identity, algorithm) {
+    let self = yield;
     let ret;
 
-    try {
-      if (!algorithm)
-        algorithm = this.defaultAlgorithm;
+    if (!algorithm)
+      algorithm = this.defaultAlgorithm;
 
-      if (algorithm != "none")
-        this._log.debug("Decrypting data");
+    if (algorithm != "none")
+      this._log.debug("Decrypting data");
 
-      switch (algorithm) {
-      case "none":
-        ret = data;
-        break;
+    switch (algorithm) {
+    case "none":
+      ret = data;
+      break;
 
-      case "XXXTEA": // Weave 0.1.12.10 and below had this typo
-      case "XXTEA": {
-        let gen = this._xxtea.decrypt(data, identity.password);
-        ret = gen.next();
+    case "XXXTEA": // Weave 0.1.12.10 and below had this typo
+    case "XXTEA": {
+      let gen = this._xxtea.decrypt(data, identity.password);
+      ret = gen.next();
+      let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      try {
         while (typeof(ret) == "object") {
-          timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
+          timer.initWithCallback(self.listener, 0, timer.TYPE_ONE_SHOT);
           yield; // Yield to main loop
           ret = gen.next();
         }
         gen.close();
-      } break;
-
-      case "aes-128-cbc":
-      case "aes-192-cbc":
-      case "aes-256-cbc":
-      case "bf-cbc":
-      case "des-ede3-cbc":
-        ret = this._opensslPBE("-d", algorithm, data, identity.password);
-        break;
-
-      default:
-        throw "Unknown encryption algorithm: " + algorithm;
+      } finally {
+        timer = null;
       }
-
-      if (algorithm != "none")
-        this._log.debug("Done decrypting data");
-
-    } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    } break;
 
-    } finally {
-      timer = null;
-      Utils.generatorDone(this, self, onComplete, ret);
-      yield; // onComplete is responsible for closing the generator
+    case "aes-128-cbc":
+    case "aes-192-cbc":
+    case "aes-256-cbc":
+    case "bf-cbc":
+    case "des-ede3-cbc":
+      ret = this._opensslPBE("-d", algorithm, data, identity.password);
+      break;
+
+    default:
+      throw "Unknown encryption algorithm: " + algorithm;
     }
-    this._log.warn("generator not properly closed");
+
+    if (algorithm != "none")
+      this._log.debug("Done decrypting data");
+
+    self.done(ret);
   },
 
   PBEkeygen: function Crypto_PBEkeygen() {
-    return this._opensslRand();
+    let self = yield;
+    let ret = this._opensslRand();
+    self.done(ret);
   },
 
   RSAkeygen: function Crypto_RSAkeygen(password) {
-    return this._opensslRSAKeyGen(password);
+    let self = yield;
+    let ret = this._opensslRSAKeyGen(password);
+    self.done(ret);
   },
 
   RSAencrypt: function Crypto_RSAencrypt(data, key) {
-    return this._opensslRSAEncrypt(data, key);
+    let self = yield;
+    let ret = this._opensslRSAencrypt(data, key);
+    self.done(ret);
   },
 
   RSAdecrypt: function Crypto_RSAdecrypt(data, key, password) {
-    return this._opensslRSADecrypt(data, key, password);
+    let self = yield;
+    let ret = this._opensslRSAdecrypt(data, key, password);
+    self.done(ret);
   }
 };
--- a/services/sync/modules/dav.js
+++ b/services/sync/modules/dav.js
@@ -39,18 +39,19 @@ const EXPORTED_SYMBOLS = ['DAVCollection
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/util.js");
+Cu.import("resource://weave/async.js");
 
-Function.prototype.async = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
 
 /*
  * DAV object
  * Abstracts the raw DAV commands
  */
 
 function DAVCollection(baseURL) {
   this._baseURL = baseURL;
@@ -75,124 +76,116 @@ DAVCollection.prototype = {
     this._baseURL = value;
   },
 
   _loggedIn: false,
   get loggedIn() {
     return this._loggedIn;
   },
 
-  _makeRequest: function DC__makeRequest(onComplete, op, path, headers, data) {
-    let [self, cont] = yield;
+  _makeRequest: function DC__makeRequest(op, path, headers, data) {
+    let self = yield;
     let ret;
 
-    try {
-      this._log.debug("Creating " + op + " request for " + this._baseURL + path);
+    this._log.debug("Creating " + op + " request for " + this._baseURL + path);
+
+    let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
+    request = request.QueryInterface(Ci.nsIDOMEventTarget);
   
-      let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
-      request = request.QueryInterface(Ci.nsIDOMEventTarget);
-    
-      request.addEventListener("load", new Utils.EventListener(cont, "load"), false);
-      request.addEventListener("error", new Utils.EventListener(cont, "error"), false);
-      request = request.QueryInterface(Ci.nsIXMLHttpRequest);
-      request.open(op, this._baseURL + path, true);
+    request.addEventListener("load", new Utils.EventListener(self.cb, "load"), false);
+    request.addEventListener("error", new Utils.EventListener(self.cb, "error"), false);
+    request = request.QueryInterface(Ci.nsIXMLHttpRequest);
+    request.open(op, this._baseURL + path, true);
 
 
-      // Force cache validation
-      let channel = request.channel;
-      channel = channel.QueryInterface(Ci.nsIRequest);
-      let loadFlags = channel.loadFlags;
-      loadFlags |= Ci.nsIRequest.VALIDATE_ALWAYS;
-      channel.loadFlags = loadFlags;
+    // Force cache validation
+    let channel = request.channel;
+    channel = channel.QueryInterface(Ci.nsIRequest);
+    let loadFlags = channel.loadFlags;
+    loadFlags |= Ci.nsIRequest.VALIDATE_ALWAYS;
+    channel.loadFlags = loadFlags;
 
-      let key;
-      for (key in headers) {
-        if (key == 'Authorization')
-          this._log.debug("HTTP Header " + key + ": ***** (suppressed)");
-        else
-          this._log.debug("HTTP Header " + key + ": " + headers[key]);
-        request.setRequestHeader(key, headers[key]);
-      }
-  
-      this._authProvider._authFailed = false;
-      request.channel.notificationCallbacks = this._authProvider;
+    let key;
+    for (key in headers) {
+      if (key == 'Authorization')
+        this._log.debug("HTTP Header " + key + ": ***** (suppressed)");
+      else
+        this._log.debug("HTTP Header " + key + ": " + headers[key]);
+      request.setRequestHeader(key, headers[key]);
+    }
 
-      request.send(data);
-      let event = yield;
-      ret = event.target;
+    this._authProvider._authFailed = false;
+    request.channel.notificationCallbacks = this._authProvider;
 
-      if (this._authProvider._authFailed)
-        this._log.warn("_makeRequest: authentication failed");
-      if (ret.status < 200 || ret.status >= 300)
-        this._log.warn("_makeRequest: got status " + ret.status);
+    request.send(data);
+    let event = yield;
+    ret = event.target;
 
-    } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    if (this._authProvider._authFailed)
+      this._log.warn("_makeRequest: authentication failed");
+    if (ret.status < 200 || ret.status >= 300)
+      this._log.warn("_makeRequest: got status " + ret.status);
 
-    } finally {
-      Utils.generatorDone(this, self, onComplete, ret);
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    self.done(ret);
   },
 
   get _defaultHeaders() {
     return {'Authorization': this._auth? this._auth : '',
             'Content-type': 'text/plain',
             'If': this._token?
             "<" + this._baseURL + "> (<" + this._token + ">)" : ''};
   },
 
   // mkdir -p
-  _mkcol: function DC__mkcol(path, onComplete) {
-    let [self, cont] = yield;
-    let ret;
+  _mkcol: function DC__mkcol(path) {
+    let self = yield;
+    let ok = true;
 
     try {
       let components = path.split('/');
       let path2 = '';
-
+  
       for (let i = 0; i < components.length; i++) {
-
-	// trailing slashes will cause an empty path component at the end
-	if (components[i] == '')
-	  break;
-
-	path2 = path2 + components[i];
-
-	// check if it exists first
-	this._makeRequest.async(this, cont, "GET", path2 + "/", this._defaultHeaders);
-	ret = yield;
-	if (!(ret.status == 404 || ret.status == 500)) { // FIXME: 500 is a services.m.c oddity
-	  this._log.debug("Skipping creation of path " + path2 +
-			  " (got status " + ret.status + ")");
-	} else {
-	  this._log.debug("Creating path: " + path2);
-	  let gen = this._makeRequest.async(this, cont, "MKCOL", path2,
-					    this._defaultHeaders);
-	  ret = yield;
-
-	  if (ret.status != 201) {
-	    this._log.debug(ret.responseText);
-	    throw 'request failed: ' + ret.status;
-	  }
-	}
-
-	// add slash *after* the request, trailing slashes cause a 412!
-	path2 = path2 + "/";
+  
+        // trailing slashes will cause an empty path component at the end
+        if (components[i] == '')
+          break;
+  
+        path2 = path2 + components[i];
+  
+        // check if it exists first
+        this._makeRequest.async(this, self.cb, "GET", path2 + "/", this._defaultHeaders);
+        let ret = yield;
+        if (!(ret.status == 404 || ret.status == 500)) { // FIXME: 500 is a services.m.c oddity
+          this._log.debug("Skipping creation of path " + path2 +
+        		  " (got status " + ret.status + ")");
+        } else {
+          this._log.debug("Creating path: " + path2);
+          this._makeRequest.async(this, self.cb, "MKCOL", path2,
+        			  this._defaultHeaders);
+          ret = yield;
+  
+          if (ret.status != 201) {
+            this._log.debug(ret.responseText);
+            throw 'request failed: ' + ret.status;
+          }
+        }
+  
+        // add slash *after* the request, trailing slashes cause a 412!
+        path2 = path2 + "/";
       }
 
     } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+      this._log.error("Could not create directory on server");
+      this._log.error("Exception caught: " + (e.message? e.message : e) +
+                      " - " + (e.location? e.location : ""));
+      ok = false;
+    }
 
-    } finally {
-      Utils.generatorDone(this, self, onComplete, ret);
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    self.done(ok);
   },
 
   GET: function DC_GET(path, onComplete) {
     return this._makeRequest.async(this, onComplete, "GET", path,
                                    this._defaultHeaders);
   },
 
   PUT: function DC_PUT(path, data, onComplete) {
@@ -201,17 +194,17 @@ DAVCollection.prototype = {
   },
 
   DELETE: function DC_DELETE(path, onComplete) {
     return this._makeRequest.async(this, onComplete, "DELETE", path,
                                    this._defaultHeaders);
   },
 
   MKCOL: function DC_MKCOL(path, onComplete) {
-    return this._mkcol.async(this, path, onComplete);
+    return this._mkcol.async(this, onComplete, path);
   },
 
   PROPFIND: function DC_PROPFIND(path, data, onComplete) {
     let headers = {'Content-type': 'text/xml; charset="utf-8"',
                    'Depth': '0'};
     headers.__proto__ = this._defaultHeaders;
     return this._makeRequest.async(this, onComplete, "PROPFIND", path,
                                    headers, data);
@@ -228,221 +221,190 @@ DAVCollection.prototype = {
   UNLOCK: function DC_UNLOCK(path, onComplete) {
     let headers = {'Lock-Token': '<' + this._token + '>'};
     headers.__proto__ = this._defaultHeaders;
     return this._makeRequest.async(this, onComplete, "UNLOCK", path, headers);
   },
 
   // Login / Logout
 
-  login: function DC_login(onComplete, username, password) {
-    let [self, cont] = yield;
+  login: function DC_login(username, password) {
+    let self = yield;
 
-    try {
-      if (this._loggedIn) {
-        this._log.debug("Login requested, but already logged in");
-        return;
-      }
-   
-      this._log.info("Logging in");
+    if (this._loggedIn) {
+      this._log.debug("Login requested, but already logged in");
+      self.done(true);
+      yield;
+    }
+ 
+    this._log.info("Logging in");
 
-      let URI = Utils.makeURI(this._baseURL);
-      this._auth = "Basic " + btoa(username + ":" + password);
+    let URI = Utils.makeURI(this._baseURL);
+    this._auth = "Basic " + btoa(username + ":" + password);
 
-      // Make a call to make sure it's working
-      this.GET("", cont);
-      let resp = yield;
-
-      if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300)
-        return;
-  
-      this._loggedIn = true;
+    // Make a call to make sure it's working
+    this.GET("", self.cb);
+    let resp = yield;
 
-    } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    if (this._authProvider._authFailed ||
+        resp.status < 200 || resp.status >= 300) {
+      self.done(false);
+      yield;
+    }
 
-    } finally {
-      Utils.generatorDone(this, self, onComplete, this._loggedIn);
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    this._loggedIn = true;
+
+    self.done(true);
   },
 
   logout: function DC_logout() {
     this._log.debug("Logging out (forgetting auth header)");
     this._loggedIn = false;
     this.__auth = null;
   },
 
   // Locking
 
-  _getActiveLock: function DC__getActiveLock(onComplete) {
-    let [self, cont] = yield;
+  _getActiveLock: function DC__getActiveLock() {
+    let self = yield;
     let ret = null;
 
-    try {
-      this._log.info("Getting active lock token");
-      this.PROPFIND("",
-                    "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
-                    "<D:propfind xmlns:D='DAV:'>" +
-                    "  <D:prop><D:lockdiscovery/></D:prop>" +
-                    "</D:propfind>", cont);
-      let resp = yield;
-
-      if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300)
-        return;
+    this._log.info("Getting active lock token");
+    this.PROPFIND("",
+                  "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
+                  "<D:propfind xmlns:D='DAV:'>" +
+                  "  <D:prop><D:lockdiscovery/></D:prop>" +
+                  "</D:propfind>", self.cb);
+    let resp = yield;
 
-      let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
-      let token = tokens.iterateNext();
-      ret = token.textContent;
-
-    } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    if (this._authProvider._authFailed ||
+        resp.status < 200 || resp.status >= 300) {
+      self.done(false);
+      yield;
+    }
 
-    } finally {
-      if (ret)
-        this._log.debug("Found an active lock token");
-      else
-        this._log.debug("No active lock token found");
-      Utils.generatorDone(this, self, onComplete, ret);
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
+    let token = tokens.iterateNext();
+    ret = token.textContent;
+
+    if (ret)
+      this._log.debug("Found an active lock token");
+    else
+      this._log.debug("No active lock token found");
+    self.done(ret);
   },
 
-  lock: function DC_lock(onComplete) {
-    let [self, cont] = yield;
+  lock: function DC_lock() {
+    let self = yield;
     this._token = null;
 
-    try {
-      this._log.info("Acquiring lock");
+    this._log.info("Acquiring lock");
 
-      if (this._token) {
-        this._log.debug("Lock called, but we already hold a token");
-        return;
-      }
+    if (this._token) {
+      this._log.debug("Lock called, but we already hold a token");
+      self.done(this._token);
+      yield;
+    }
 
-      this.LOCK("",
-                "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
-                "<D:lockinfo xmlns:D=\"DAV:\">\n" +
-                "  <D:locktype><D:write/></D:locktype>\n" +
-                "  <D:lockscope><D:exclusive/></D:lockscope>\n" +
-                "</D:lockinfo>", cont);
-      let resp = yield;
+    this.LOCK("",
+              "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+              "<D:lockinfo xmlns:D=\"DAV:\">\n" +
+              "  <D:locktype><D:write/></D:locktype>\n" +
+              "  <D:lockscope><D:exclusive/></D:lockscope>\n" +
+              "</D:lockinfo>", self.cb);
+    let resp = yield;
 
-      if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300)
-        return;
-
-      let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
-      let token = tokens.iterateNext();
-      if (token)
-        this._token = token.textContent;
+    if (this._authProvider._authFailed ||
+        resp.status < 200 || resp.status >= 300) {
+      self.done(this._token);
+      yield;
+    }
 
-    } catch (e){
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
+    let token = tokens.iterateNext();
+    if (token)
+      this._token = token.textContent;
 
-    } finally {
-      if (this._token)
-        this._log.info("Lock acquired");
-      else
-        this._log.warn("Could not acquire lock");
-      Utils.generatorDone(this, self, onComplete, this._token);
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    if (this._token)
+      this._log.debug("Lock acquired");
+    else
+      this._log.warn("Could not acquire lock");
+
+    self.done(this._token);
   },
 
-  unlock: function DC_unlock(onComplete) {
-    let [self, cont] = yield;
-    try {
-      this._log.info("Releasing lock");
+  unlock: function DC_unlock() {
+    let self = yield;
+
+    this._log.info("Releasing lock");
 
-      if (this._token === null) {
-        this._log.debug("Unlock called, but we don't hold a token right now");
-        return;
-      }
+    if (this._token === null) {
+      this._log.debug("Unlock called, but we don't hold a token right now");
+      self.done(true);
+      yield;
+    }
 
-      this.UNLOCK("", cont);
-      let resp = yield;
-
-      if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300)
-        return;
+    this.UNLOCK("", self.cb);
+    let resp = yield;
 
-      this._token = null;
-
-    } catch (e){
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    if (this._authProvider._authFailed ||
+        resp.status < 200 || resp.status >= 300) {
+      self.done(false);
+      yield;
+    }
 
-    } finally {
-      if (this._token) {
-        this._log.info("Could not release lock");
-        Utils.generatorDone(this, self, onComplete, false);
-      } else {
-        this._log.info("Lock released (or we didn't have one)");
-        Utils.generatorDone(this, self, onComplete, true);
-      }
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    this._token = null;
+
+    if (this._token)
+      this._log.info("Could not release lock");
+    else
+      this._log.info("Lock released (or we didn't have one)");
+
+    self.done(!this._token);
   },
 
-  forceUnlock: function DC_forceUnlock(onComplete) {
-    let [self, cont] = yield;
+  forceUnlock: function DC_forceUnlock() {
+    let self = yield;
     let unlocked = true;
 
-    try {
-      this._log.info("Forcibly releasing any server locks");
+    this._log.info("Forcibly releasing any server locks");
 
-      this._getActiveLock.async(this, cont);
-      this._token = yield;
-
-      if (!this._token) {
-        this._log.info("No server lock found");
-        return;
-      }
+    this._getActiveLock.async(this, self.cb);
+    this._token = yield;
 
-      this._log.info("Server lock found, unlocking");
-      this.unlock.async(this, cont);
-      unlocked = yield;
-
-    } catch (e){
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    if (!this._token) {
+      this._log.info("No server lock found");
+      self.done(true);
+      yield;
+    }
 
-    } finally {
-      if (unlocked)
-        this._log.debug("Lock released");
-      else
-        this._log.debug("No lock released");
-      Utils.generatorDone(this, self, onComplete, unlocked);
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    this._log.info("Server lock found, unlocking");
+    this.unlock.async(this, self.cb);
+    unlocked = yield;
+
+    if (unlocked)
+      this._log.debug("Lock released");
+    else
+      this._log.debug("No lock released");
+    self.done(unlocked);
   },
 
-  stealLock: function DC_stealLock(onComplete) {
-    let [self, cont] = yield;
+  stealLock: function DC_stealLock() {
+    let self = yield;
     let stolen = null;
 
-    try {
-      this.forceUnlock.async(this, cont);
-      let unlocked = yield;
-
-      if (unlocked) {
-        this.lock.async(this, cont);
-        stolen = yield;
-      }
+    this.forceUnlock.async(this, self.cb);
+    let unlocked = yield;
 
-    } catch (e){
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    if (unlocked) {
+      this.lock.async(this, self.cb);
+      stolen = yield;
+    }
 
-    } finally {
-      Utils.generatorDone(this, self, onComplete, stolen);
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    self.done(stolen);
   }
 };
 
 
 /*
  * Auth provider object
  * Taken from nsMicrosummaryService.js and massaged slightly
  */
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -43,18 +43,19 @@ const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/constants.js");
 Cu.import("resource://weave/util.js");
 Cu.import("resource://weave/crypto.js");
 Cu.import("resource://weave/stores.js");
 Cu.import("resource://weave/syncCores.js");
+Cu.import("resource://weave/async.js");
 
-Function.prototype.async = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
 let Crypto = new WeaveCrypto();
 
 function Engine(davCollection, cryptoId) {
   //this._init(davCollection, cryptoId);
 }
 Engine.prototype = {
   // "default-engine";
   get name() { throw "name property must be overridden in subclasses"; },
@@ -63,16 +64,17 @@ Engine.prototype = {
   get logName() { throw "logName property must be overridden in subclasses"; },
 
   // "user-data/default-engine/";
   get serverPrefix() { throw "serverPrefix property must be overridden in subclasses"; },
 
   // These can be overridden in subclasses, but don't need to be (assuming
   // serverPrefix is not shared with anything else)
   get statusFile() { return this.serverPrefix + "status.json"; },
+  get keysFile() { return this.serverPrefix + "keys.json"; },
   get snapshotFile() { return this.serverPrefix + "snapshot.json"; },
   get deltasFile() { return this.serverPrefix + "deltas.json"; },
 
   __os: null,
   get _os() {
     if (!this.__os)
       this.__os = Cc["@mozilla.org/observer-service;1"]
         .getService(Ci.nsIObserverService);
@@ -115,111 +117,120 @@ Engine.prototype = {
   _init: function Engine__init(davCollection, cryptoId) {
     this._dav = davCollection;
     this._cryptoId = cryptoId;
     this._log = Log4Moz.Service.getLogger("Service." + this.logName);
     this._osPrefix = "weave:" + this.name + ":";
     this._snapshot.load();
   },
 
+  _getSymKey: function Engine__getCryptoId(cryptoId) {
+    let self = yield;
+    let done = false;
+
+    this._dav.GET(this.keysFile, self.cb);
+    let keysResp = yield;
+    Utils.ensureStatus(keysResp.status,
+                       "Could not get keys file.", [[200,300]]);
+    let keys = keysResp.responseText;
+
+    if (!keys[this._userHash]) {
+      this._log.error("Keyring does not contain a key for this user");
+      return;
+    }
+
+    //Crypto.RSAdecrypt.async(Crypto, self.cb,
+    //                        keys[this._userHash],
+                              
+    self.done(done);
+    yield;
+    this._log.warn("_getSymKey generator not properly closed");
+  },
+
   _serializeCommands: function Engine__serializeCommands(commands) {
     let json = this._json.encode(commands);
     //json = json.replace(/ {action/g, "\n {action");
     return json;
   },
   
   _serializeConflicts: function Engine__serializeConflicts(conflicts) {
     let json = this._json.encode(conflicts);
     //json = json.replace(/ {action/g, "\n {action");
     return json;
   },
 
-  _resetServer: function Engine__resetServer(onComplete) {
-    let [self, cont] = yield;
+  _resetServer: function Engine__resetServer() {
+    let self = yield;
     let done = false;
 
     try {
       this._log.debug("Resetting server data");
       this._os.notifyObservers(null, this._osPrefix + "reset-server:start", "");
 
-      this._dav.lock.async(this._dav, cont);
+      this._dav.lock.async(this._dav, self.cb);
       let locked = yield;
-      if (locked)
-        this._log.debug("Lock acquired");
-      else {
-        this._log.warn("Could not acquire lock, aborting server reset");
-        return;        
-      }
+      if (!locked)
+        throw "Could not acquire lock, aborting server reset";
 
       // try to delete all 3, check status after
-      this._dav.DELETE(this.statusFile, cont);
+      this._dav.DELETE(this.statusFile, self.cb);
       let statusResp = yield;
-      this._dav.DELETE(this.snapshotFile, cont);
+      this._dav.DELETE(this.snapshotFile, self.cb);
       let snapshotResp = yield;
-      this._dav.DELETE(this.deltasFile, cont);
+      this._dav.DELETE(this.deltasFile, self.cb);
       let deltasResp = yield;
 
-      this._dav.unlock.async(this._dav, cont);
+      this._dav.unlock.async(this._dav, self.cb);
       let unlocked = yield;
 
-      Utils.checkStatus(statusResp.status,
-                        "Could not delete status file.", [[200,300],404]);
-      Utils.checkStatus(snapshotResp.status,
-                        "Could not delete snapshot file.", [[200,300],404]);
-      Utils.checkStatus(deltasResp.status,
-                        "Could not delete deltas file.", [[200,300],404]);
+      Utils.ensureStatus(statusResp.status,
+                         "Could not delete status file.", [[200,300],404]);
+      Utils.ensureStatus(snapshotResp.status,
+                         "Could not delete snapshot file.", [[200,300],404]);
+      Utils.ensureStatus(deltasResp.status,
+                         "Could not delete deltas file.", [[200,300],404]);
 
       this._log.debug("Server files deleted");
       done = true;
+      this._os.notifyObservers(null, this._osPrefix + "reset-server:success", "");
         
     } catch (e) {
-      if (e != 'checkStatus failed')
-	this._log.error("Exception caught: " + (e.message? e.message : e));
+      this._log.error("Could not delete server files");
+      this._os.notifyObservers(null, this._osPrefix + "reset-server:error", "");
+      throw e;
+    }
 
-    } finally {
-      if (done) {
-        this._log.debug("Server reset completed successfully");
-        this._os.notifyObservers(null, this._osPrefix + "reset-server:success", "");
-      } else {
-        this._log.debug("Server reset failed");
-        this._os.notifyObservers(null, this._osPrefix + "reset-server:error", "");
-      }
-      Utils.generatorDone(this, self, onComplete, done)
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    self.done(done)
   },
 
-  _resetClient: function Engine__resetClient(onComplete) {
-    let [self, cont] = yield;
+  _resetClient: function Engine__resetClient() {
+    let self = yield;
     let done = false;
 
     try {
       this._log.debug("Resetting client state");
       this._os.notifyObservers(null, this._osPrefix + "reset-client:start", "");
 
       this._snapshot.wipe();
       this._store.wipe();
       done = true;
 
     } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+      throw e;
 
     } finally {
       if (done) {
         this._log.debug("Client reset completed successfully");
         this._os.notifyObservers(null, this._osPrefix + "reset-client:success", "");
       } else {
         this._log.debug("Client reset failed");
         this._os.notifyObservers(null, this._osPrefix + "reset-client:error", "");
       }
-      Utils.generatorDone(this, self, onComplete, done);
-      yield; // onComplete is responsible for closing the generator
+      self.done(done);
     }
-    this._log.warn("generator not properly closed");
   },
 
   //       original
   //         / \
   //      A /   \ B
   //       /     \
   // client --C-> server
   //       \     /
@@ -240,41 +251,42 @@ Engine.prototype = {
   // 1.2) Generate single delta from snapshot -> current server status ("B")
   // 2) Generate local deltas from snapshot -> current client status ("A")
   // 3) Reconcile client/server deltas and generate new deltas for them.
   //    Reconciliation won't generate C directly, we will simply diff
   //    server->final after step 3.1.
   // 3.1) Apply local delta with server changes ("D")
   // 3.2) Append server delta to the delta file and upload ("C")
 
-  _sync: function BmkEngine__sync(onComplete) {
-    let [self, cont] = yield;
+  _sync: function BmkEngine__sync() {
+    let self = yield;
     let synced = false, locked = null;
 
     try {
       this._log.info("Beginning sync");
       this._os.notifyObservers(null, this._osPrefix + "sync:start", "");
 
-      this._dav.lock.async(this._dav, cont);
+      this._dav.lock.async(this._dav, self.cb);
       locked = yield;
 
       if (locked)
         this._log.info("Lock acquired");
       else {
         this._log.warn("Could not acquire lock, aborting sync");
         return;
       }
 
       // Before we get started, make sure we have a remote directory to play in
-      this._dav.MKCOL(this.serverPrefix, cont);
+      this._dav.MKCOL(this.serverPrefix, self.cb);
       let ret = yield;
-      Utils.checkStatus(ret.status, "Could not create remote folder.");
+      if (!ret)
+        throw "Could not create remote folder";
 
       // 1) Fetch server deltas
-      this._getServerData.async(this, cont);
+      this._getServerData.async(this, self.cb);
       let server = yield;
 
       this._log.info("Local snapshot version: " + this._snapshot.version);
       this._log.info("Server status: " + server.status);
       this._log.info("Server maxVersion: " + server.maxVersion);
       this._log.info("Server snapVersion: " + server.snapVersion);
 
       if (server.status != 0) {
@@ -282,34 +294,34 @@ Engine.prototype = {
                         "or initial upload failed.  Aborting sync.");
         return;
       }
 
       // 2) Generate local deltas from snapshot -> current client status
 
       let localJson = new SnapshotStore();
       localJson.data = this._store.wrap();
-      this._core.detectUpdates(cont, this._snapshot.data, localJson.data);
+      this._core.detectUpdates(self.cb, this._snapshot.data, localJson.data);
       let localUpdates = yield;
 
       this._log.trace("local json:\n" + localJson.serialize());
       this._log.trace("Local updates: " + this._serializeCommands(localUpdates));
       this._log.trace("Server updates: " + this._serializeCommands(server.updates));
 
       if (server.updates.length == 0 && localUpdates.length == 0) {
         this._snapshot.version = server.maxVersion;
         this._log.info("Sync complete (1): no changes needed on client or server");
         synced = true;
         return;
       }
 	  
       // 3) Reconcile client/server deltas and generate new deltas for them.
 
       this._log.info("Reconciling client/server updates");
-      this._core.reconcile(cont, localUpdates, server.updates);
+      this._core.reconcile(self.cb, localUpdates, server.updates);
       ret = yield;
 
       let clientChanges = ret.propagations[0];
       let serverChanges = ret.propagations[1];
       let clientConflicts = ret.conflicts[0];
       let serverConflicts = ret.conflicts[1];
 
       this._log.info("Changes for client: " + clientChanges.length);
@@ -346,17 +358,17 @@ Engine.prototype = {
         // current tree, not the saved snapshot
 
 	localJson.applyCommands(clientChanges);
         this._snapshot.data = localJson.data;
         this._snapshot.version = server.maxVersion;
         this._store.applyCommands(clientChanges);
         newSnapshot = this._store.wrap();
 
-        this._core.detectUpdates(cont, this._snapshot.data, newSnapshot);
+        this._core.detectUpdates(self.cb, this._snapshot.data, newSnapshot);
         let diff = yield;
         if (diff.length != 0) {
           this._log.warn("Commands did not apply correctly");
           this._log.debug("Diff from snapshot+commands -> " +
                           "new snapshot after commands:\n" +
                           this._serializeCommands(diff));
           // FIXME: do we really want to revert the snapshot here?
           this._snapshot.data = Utils.deepCopy(savedSnap);
@@ -367,17 +379,17 @@ Engine.prototype = {
 
       // 3.2) Append server delta to the delta file and upload
 
       // Generate a new diff, from the current server snapshot to the
       // current client snapshot.  In the case where there are no
       // conflicts, it should be the same as what the resolver returned
 
       newSnapshot = this._store.wrap();
-      this._core.detectUpdates(cont, server.snapshot, newSnapshot);
+      this._core.detectUpdates(self.cb, server.snapshot, newSnapshot);
       let serverDelta = yield;
 
       // Log an error if not the same
       if (!(serverConflicts.length ||
             Utils.deepEquals(serverChanges, serverDelta)))
         this._log.warn("Predicted server changes differ from " +
                        "actual server->client diff (can be ignored in many cases)");
 
@@ -390,42 +402,42 @@ Engine.prototype = {
 
         this._snapshot.data = newSnapshot;
         this._snapshot.version = ++server.maxVersion;
 
         server.deltas.push(serverDelta);
 
         if (server.formatVersion != STORAGE_FORMAT_VERSION ||
             this._encryptionChanged) {
-          this._fullUpload.async(this, cont);
+          this._fullUpload.async(this, self.cb);
           let status = yield;
           if (!status)
             this._log.error("Could not upload files to server"); // eep?
 
         } else {
-	  Crypto.PBEencrypt.async(Crypto, cont,
+	  Crypto.PBEencrypt.async(Crypto, self.cb,
                                   this._serializeCommands(server.deltas),
 				  this._cryptoId);
           let data = yield;
-          this._dav.PUT(this.deltasFile, data, cont);
+          this._dav.PUT(this.deltasFile, data, self.cb);
           let deltasPut = yield;
 
           let c = 0;
           for (GUID in this._snapshot.data)
             c++;
 
           this._dav.PUT(this.statusFile,
                         this._json.encode(
                           {GUID: this._snapshot.GUID,
                            formatVersion: STORAGE_FORMAT_VERSION,
                            snapVersion: server.snapVersion,
                            maxVersion: this._snapshot.version,
                            snapEncryption: server.snapEncryption,
                            deltasEncryption: Crypto.defaultAlgorithm,
-                           itemCount: c}), cont);
+                           itemCount: c}), self.cb);
           let statusPut = yield;
 
           if (deltasPut.status >= 200 && deltasPut.status < 300 &&
               statusPut.status >= 200 && statusPut.status < 300) {
             this._log.info("Successfully updated deltas and status on server");
             this._snapshot.save();
           } else {
             // FIXME: revert snapshot here? - can't, we already applied
@@ -434,34 +446,32 @@ Engine.prototype = {
           }
         }
       }
 
       this._log.info("Sync complete");
       synced = true;
 
     } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+      throw e;
 
     } finally {
       let ok = false;
       if (locked) {
-        this._dav.unlock.async(this._dav, cont);
+        this._dav.unlock.async(this._dav, self.cb);
         ok = yield;
       }
       if (ok && synced) {
         this._os.notifyObservers(null, this._osPrefix + "sync:success", "");
-        Utils.generatorDone(this, self, onComplete, true);
+        self.done(true);
       } else {
         this._os.notifyObservers(null, this._osPrefix + "sync:error", "");
-        Utils.generatorDone(this, self, onComplete, false);
+        self.done(false);
       }
-      yield; // onComplete is responsible for closing the generator
     }
-    this._log.warn("generator not properly closed");
   },
 
   /* Get the deltas/combined updates from the server
    * Returns:
    *   status:
    *     -1: error
    *      0: ok
    * These fields may be null when status is -1:
@@ -478,230 +488,215 @@ Engine.prototype = {
    *   snapshot:
    *     full snapshot of the latest server version (deltas applied)
    *   deltas:
    *     all of the individual deltas on the server
    *   updates:
    *     the relevant deltas (from our snapshot version to current),
    *     combined into a single set.
    */
-  _getServerData: function BmkEngine__getServerData(onComplete) {
-    let [self, cont] = yield;
+  _getServerData: function BmkEngine__getServerData() {
+    let self = yield;
     let ret = {status: -1,
                formatVersion: null, maxVersion: null, snapVersion: null,
                snapEncryption: null, deltasEncryption: null,
                snapshot: null, deltas: null, updates: null};
 
-    try {
-      this._log.debug("Getting status file from server");
-      this._dav.GET(this.statusFile, cont);
-      let resp = yield;
-      let status = resp.status;
-  
-      switch (status) {
-      case 200: {
-        this._log.info("Got status file from server");
-  
-        let status = this._json.decode(resp.responseText);
-        let deltas, allDeltas;
-	let snap = new SnapshotStore();
-  
-        // Bail out if the server has a newer format version than we can parse
-        if (status.formatVersion > STORAGE_FORMAT_VERSION) {
-          this._log.error("Server uses storage format v" + status.formatVersion +
-                    ", this client understands up to v" + STORAGE_FORMAT_VERSION);
-          Utils.generatorDone(this, self, onComplete, ret)
-          return;
-        }
+    this._log.debug("Getting status file from server");
+    this._dav.GET(this.statusFile, self.cb);
+    let resp = yield;
+    let status = resp.status;
 
-        if (status.formatVersion == 0) {
-          ret.snapEncryption = status.snapEncryption = "none";
-          ret.deltasEncryption = status.deltasEncryption = "none";
-        }
-  
-        if (status.GUID != this._snapshot.GUID) {
-          this._log.info("Remote/local sync GUIDs do not match.  " +
-                      "Forcing initial sync.");
-          this._log.debug("Remote: " + status.GUID);
-          this._log.debug("Local: " + this._snapshot.GUID);
-          this._store.resetGUIDs();
-          this._snapshot.data = {};
-          this._snapshot.version = -1;
-          this._snapshot.GUID = status.GUID;
-        }
-  
-        if (this._snapshot.version < status.snapVersion) {
-          if (this._snapshot.version >= 0)
-            this._log.info("Local snapshot is out of date");
-  
-          this._log.info("Downloading server snapshot");
-          this._dav.GET(this.snapshotFile, cont);
-          resp = yield;
-          Utils.checkStatus(resp.status, "Could not download snapshot.");
-          Crypto.PBEdecrypt.async(Crypto, cont,
-                                  resp.responseText,
-				  this._cryptoId,
-				  status.snapEncryption);
-          let data = yield;
-          snap.data = this._json.decode(data);
+    switch (status) {
+    case 200: {
+      this._log.info("Got status file from server");
 
-          this._log.info("Downloading server deltas");
-          this._dav.GET(this.deltasFile, cont);
-          resp = yield;
-          Utils.checkStatus(resp.status, "Could not download deltas.");
-          Crypto.PBEdecrypt.async(Crypto, cont,
-                                  resp.responseText,
-				  this._cryptoId,
-				  status.deltasEncryption);
-          data = yield;
-          allDeltas = this._json.decode(data);
-          deltas = this._json.decode(data);
-  
-        } else if (this._snapshot.version >= status.snapVersion &&
-                   this._snapshot.version < status.maxVersion) {
-          snap.data = Utils.deepCopy(this._snapshot.data);
-  
-          this._log.info("Downloading server deltas");
-          this._dav.GET(this.deltasFile, cont);
-          resp = yield;
-          Utils.checkStatus(resp.status, "Could not download deltas.");
-          Crypto.PBEdecrypt.async(Crypto, cont,
-                                  resp.responseText,
-				  this._cryptoId,
-				  status.deltasEncryption);
-          let data = yield;
-          allDeltas = this._json.decode(data);
-          deltas = allDeltas.slice(this._snapshot.version - status.snapVersion);
-  
-        } else if (this._snapshot.version == status.maxVersion) {
-          snap.data = Utils.deepCopy(this._snapshot.data);
+      let status = this._json.decode(resp.responseText);
+      let deltas, allDeltas;
+      let snap = new SnapshotStore();
 
-          // FIXME: could optimize this case by caching deltas file
-          this._log.info("Downloading server deltas");
-          this._dav.GET(this.deltasFile, cont);
-          resp = yield;
-          Utils.checkStatus(resp.status, "Could not download deltas.");
-          Crypto.PBEdecrypt.async(Crypto, cont,
-                                  resp.responseText,
-				  this._cryptoId,
-				  status.deltasEncryption);
-          let data = yield;
-          allDeltas = this._json.decode(data);
-          deltas = [];
-  
-        } else { // this._snapshot.version > status.maxVersion
-          this._log.error("Server snapshot is older than local snapshot");
-          return;
-        }
-  
-        for (var i = 0; i < deltas.length; i++) {
-	  snap.applyCommands(deltas[i]);
-        }
-  
-        ret.status = 0;
-        ret.formatVersion = status.formatVersion;
-        ret.maxVersion = status.maxVersion;
-        ret.snapVersion = status.snapVersion;
-        ret.snapEncryption = status.snapEncryption;
-        ret.deltasEncryption = status.deltasEncryption;
-        ret.snapshot = snap.data;
-        ret.deltas = allDeltas;
-        this._core.detectUpdates(cont, this._snapshot.data, snap.data);
-        ret.updates = yield;
-      } break;
-
-      case 404: {
-        this._log.info("Server has no status file, Initial upload to server");
-  
-        this._snapshot.data = this._store.wrap();
-        this._snapshot.version = 0;
-        this._snapshot.GUID = null; // in case there are other snapshots out there
-
-        this._fullUpload.async(this, cont);
-        let uploadStatus = yield;
-        if (!uploadStatus)
-          return;
-  
-        this._log.info("Initial upload to server successful");
-        this._snapshot.save();
-  
-        ret.status = 0;
-        ret.formatVersion = STORAGE_FORMAT_VERSION;
-        ret.maxVersion = this._snapshot.version;
-        ret.snapVersion = this._snapshot.version;
-        ret.snapEncryption = Crypto.defaultAlgorithm;
-        ret.deltasEncryption = Crypto.defaultAlgorithm;
-        ret.snapshot = Utils.deepCopy(this._snapshot.data);
-        ret.deltas = [];
-        ret.updates = [];
-      } break;
-
-      default:
-        this._log.error("Could not get status file: unknown HTTP status code " +
-                        status);
+      // Bail out if the server has a newer format version than we can parse
+      if (status.formatVersion > STORAGE_FORMAT_VERSION) {
+        this._log.error("Server uses storage format v" + status.formatVersion +
+                  ", this client understands up to v" + STORAGE_FORMAT_VERSION);
         break;
       }
 
-    } catch (e) {
-      if (e != 'checkStatus failed' &&
-          e != 'decrypt failed')
-	this._log.error("Exception caught: " + (e.message? e.message : e));
+      if (status.formatVersion == 0) {
+        ret.snapEncryption = status.snapEncryption = "none";
+        ret.deltasEncryption = status.deltasEncryption = "none";
+      }
+
+      if (status.GUID != this._snapshot.GUID) {
+        this._log.info("Remote/local sync GUIDs do not match.  " +
+                    "Forcing initial sync.");
+        this._log.debug("Remote: " + status.GUID);
+        this._log.debug("Local: " + this._snapshot.GUID);
+        this._store.resetGUIDs();
+        this._snapshot.data = {};
+        this._snapshot.version = -1;
+        this._snapshot.GUID = status.GUID;
+      }
+
+      if (this._snapshot.version < status.snapVersion) {
+        this._log.trace("Local snapshot version < server snapVersion");
+
+        if (this._snapshot.version >= 0)
+          this._log.info("Local snapshot is out of date");
+
+        this._log.info("Downloading server snapshot");
+        this._dav.GET(this.snapshotFile, self.cb);
+        resp = yield;
+        Utils.ensureStatus(resp.status, "Could not download snapshot.");
+        Crypto.PBEdecrypt.async(Crypto, self.cb,
+                                resp.responseText,
+      			  this._cryptoId,
+      			  status.snapEncryption);
+        let data = yield;
+        snap.data = this._json.decode(data);
+
+        this._log.info("Downloading server deltas");
+        this._dav.GET(this.deltasFile, self.cb);
+        resp = yield;
+        Utils.ensureStatus(resp.status, "Could not download deltas.");
+        Crypto.PBEdecrypt.async(Crypto, self.cb,
+                                resp.responseText,
+      			  this._cryptoId,
+      			  status.deltasEncryption);
+        data = yield;
+        allDeltas = this._json.decode(data);
+        deltas = this._json.decode(data);
+
+      } else if (this._snapshot.version >= status.snapVersion &&
+                 this._snapshot.version < status.maxVersion) {
+        this._log.trace("Server snapVersion <= local snapshot version < server maxVersion");
+        snap.data = Utils.deepCopy(this._snapshot.data);
+
+        this._log.info("Downloading server deltas");
+        this._dav.GET(this.deltasFile, self.cb);
+        resp = yield;
+        Utils.ensureStatus(resp.status, "Could not download deltas.");
+        Crypto.PBEdecrypt.async(Crypto, self.cb,
+                                resp.responseText,
+      			  this._cryptoId,
+      			  status.deltasEncryption);
+        let data = yield;
+        allDeltas = this._json.decode(data);
+        deltas = allDeltas.slice(this._snapshot.version - status.snapVersion);
+
+      } else if (this._snapshot.version == status.maxVersion) {
+        this._log.trace("Local snapshot version == server maxVersion");
+        snap.data = Utils.deepCopy(this._snapshot.data);
 
-    } finally {
-      Utils.generatorDone(this, self, onComplete, ret)
-      yield; // onComplete is responsible for closing the generator
+        // FIXME: could optimize this case by caching deltas file
+        this._log.info("Downloading server deltas");
+        this._dav.GET(this.deltasFile, self.cb);
+        resp = yield;
+        Utils.ensureStatus(resp.status, "Could not download deltas.");
+        Crypto.PBEdecrypt.async(Crypto, self.cb,
+                                resp.responseText,
+      			  this._cryptoId,
+      			  status.deltasEncryption);
+        let data = yield;
+        allDeltas = this._json.decode(data);
+        deltas = [];
+
+      } else { // this._snapshot.version > status.maxVersion
+        this._log.error("Server snapshot is older than local snapshot");
+        break;
+      }
+
+      for (var i = 0; i < deltas.length; i++) {
+        snap.applyCommands(deltas[i]);
+      }
+
+      ret.status = 0;
+      ret.formatVersion = status.formatVersion;
+      ret.maxVersion = status.maxVersion;
+      ret.snapVersion = status.snapVersion;
+      ret.snapEncryption = status.snapEncryption;
+      ret.deltasEncryption = status.deltasEncryption;
+      ret.snapshot = snap.data;
+      ret.deltas = allDeltas;
+      this._core.detectUpdates(self.cb, this._snapshot.data, snap.data);
+      ret.updates = yield;
+    } break;
+
+    case 404: {
+      this._log.info("Server has no status file, Initial upload to server");
+
+      this._snapshot.data = this._store.wrap();
+      this._snapshot.version = 0;
+      this._snapshot.GUID = null; // in case there are other snapshots out there
+
+      this._fullUpload.async(this, self.cb);
+      let uploadStatus = yield;
+      if (!uploadStatus)
+        break;
+
+      this._log.info("Initial upload to server successful");
+      this._snapshot.save();
+
+      ret.status = 0;
+      ret.formatVersion = STORAGE_FORMAT_VERSION;
+      ret.maxVersion = this._snapshot.version;
+      ret.snapVersion = this._snapshot.version;
+      ret.snapEncryption = Crypto.defaultAlgorithm;
+      ret.deltasEncryption = Crypto.defaultAlgorithm;
+      ret.snapshot = Utils.deepCopy(this._snapshot.data);
+      ret.deltas = [];
+      ret.updates = [];
+    } break;
+
+    default:
+      this._log.error("Could not get status file: unknown HTTP status code " +
+                      status);
+      break;
     }
-    this._log.warn("generator not properly closed");
+
+    self.done(ret)
   },
 
-  _fullUpload: function Engine__fullUpload(onComplete) {
-    let [self, cont] = yield;
+  _fullUpload: function Engine__fullUpload() {
+    let self = yield;
     let ret = false;
 
-    try {
-      Crypto.PBEencrypt.async(Crypto, cont,
-                              this._snapshot.serialize(),
-			      this._cryptoId);
-      let data = yield;
-      this._dav.PUT(this.snapshotFile, data, cont);
-      resp = yield;
-      Utils.checkStatus(resp.status, "Could not upload snapshot.");
+    let gen = Crypto.PBEencrypt.async(Crypto, self.cb,
+                                      this._snapshot.serialize(),
+      		                this._cryptoId);
+    let data = yield;
+    if (gen.failed) throw "Encryption failed.";
+      
+    this._dav.PUT(this.snapshotFile, data, self.cb);
+    resp = yield;
+    Utils.ensureStatus(resp.status, "Could not upload snapshot.");
 
-      this._dav.PUT(this.deltasFile, "[]", cont);
-      resp = yield;
-      Utils.checkStatus(resp.status, "Could not upload deltas.");
-
-      let c = 0;
-      for (GUID in this._snapshot.data)
-        c++;
+    this._dav.PUT(this.deltasFile, "[]", self.cb);
+    resp = yield;
+    Utils.ensureStatus(resp.status, "Could not upload deltas.");
 
-      this._dav.PUT(this.statusFile,
-                    this._json.encode(
-                      {GUID: this._snapshot.GUID,
-                       formatVersion: STORAGE_FORMAT_VERSION,
-                       snapVersion: this._snapshot.version,
-                       maxVersion: this._snapshot.version,
-                       snapEncryption: Crypto.defaultAlgorithm,
-                       deltasEncryption: "none",
-                       itemCount: c}), cont);
-      resp = yield;
-      Utils.checkStatus(resp.status, "Could not upload status file.");
+    let c = 0;
+    for (GUID in this._snapshot.data)
+      c++;
 
-      this._log.info("Full upload to server successful");
-      ret = true;
+    this._dav.PUT(this.statusFile,
+                  this._json.encode(
+                    {GUID: this._snapshot.GUID,
+                     formatVersion: STORAGE_FORMAT_VERSION,
+                     snapVersion: this._snapshot.version,
+                     maxVersion: this._snapshot.version,
+                     snapEncryption: Crypto.defaultAlgorithm,
+                     deltasEncryption: "none",
+                     itemCount: c}), self.cb);
+    resp = yield;
+    Utils.ensureStatus(resp.status, "Could not upload status file.");
 
-    } catch (e) {
-      if (e != 'checkStatus failed')
-	this._log.error("Exception caught: " + (e.message? e.message : e));
-
-    } finally {
-      Utils.generatorDone(this, self, onComplete, ret)
-      yield; // onComplete is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    this._log.info("Full upload to server successful");
+    ret = true;
+    self.done(ret)
   },
 
   sync: function Engine_sync(onComplete) {
     return this._sync.async(this, onComplete);
   },
 
   resetServer: function Engine_resetServer(onComplete) {
     return this._resetServer.async(this, onComplete);
--- a/services/sync/modules/identity.js
+++ b/services/sync/modules/identity.js
@@ -61,16 +61,20 @@ function Identity(realm, username, passw
 }
 Identity.prototype = {
   get realm() { return this._realm; },
   set realm(value) { this._realm = value; },
 
   get username() { return this._username; },
   set username(value) { this._username = value; },
 
+  _key: null,
+  get key() { return this._key; },
+  set key(value) { this._key = value; },
+
   _password: null,
   get password() {
     if (this._password === null)
       return findPassword(this.realm, this.username);
     return this._password;
   },
   set password(value) {
     setPassword(this.realm, this.username, value);
--- a/services/sync/modules/log4moz.js
+++ b/services/sync/modules/log4moz.js
@@ -473,17 +473,17 @@ Log4MozService.prototype = {
   },
 
   newFileAppender: function LogSvc_newAppender(kind, file, formatter) {
     switch (kind) {
     case "file":
       return new FileAppender(file, formatter);
     case "rotating":
       // FIXME: hardcoded constants
-      return new RotatingFileAppender(file, formatter, ONE_MEGABYTE * 5, 0);
+      return new RotatingFileAppender(file, formatter, ONE_MEGABYTE * 2, 0);
     default:
       dump("log4moz: unknown appender kind: " + kind);
       return;
     }
   },
 
   newFormatter: function LogSvc_newFormatter(kind) {
     switch (kind) {
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -44,19 +44,20 @@ const Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/constants.js");
 Cu.import("resource://weave/util.js");
 Cu.import("resource://weave/crypto.js");
 Cu.import("resource://weave/engines.js");
 Cu.import("resource://weave/dav.js");
 Cu.import("resource://weave/identity.js");
-
+Cu.import("resource://weave/async.js");
 
-Function.prototype.async = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
+let Crypto = new WeaveCrypto();
 
 /*
  * Service singleton
  * Main entry point into Weave's sync framework
  */
 
 function WeaveSyncService() { this._init(); }
 WeaveSyncService.prototype = {
@@ -298,182 +299,184 @@ WeaveSyncService.prototype = {
   },
 
   _unlock: function WeaveSync__unlock() {
     this._locked = false;
     this._log.debug("Service lock released");
     this._os.notifyObservers(null, "weave:service-unlock:success", "");
   },
 
-  _login: function WeaveSync__login(onComplete) {
-    let [self, cont] = yield;
-    let success = false;
+  _login: function WeaveSync__login() {
+    let self = yield;
 
     try {
       this._log.debug("Logging in");
       this._os.notifyObservers(null, "weave:service-login:start", "");
 
-      if (!this.username) {
-        this._log.warn("No username set, login failed");
-	return;
-      }
-      if (!this.password) {
-        this._log.warn("No password given or found in password manager");
-	return;
-      }
+      if (!this.username)
+        throw "No username set, login failed";
+      if (!this.password)
+        throw "No password given or found in password manager";
 
       let serverURL = this._prefs.getCharPref("serverURL");
       this._dav.baseURL = serverURL + "user/" + this.userPath + "/";
       this._log.info("Using server URL: " + this._dav.baseURL);
 
-      this._dav.login.async(this._dav, cont, this.username, this.password);
-      success = yield;
+      this._dav.login.async(this._dav, self.cb, this.username, this.password);
+      let success = yield;
 
       // FIXME: we want to limit this to when we get a 404!
       if (!success) {
         this._log.debug("Attempting to create user directory");
 
         this._dav.baseURL = serverURL;
-        this._dav.MKCOL("user/" + this.userPath, cont);
+        this._dav.MKCOL("user/" + this.userPath, self.cb);
         let ret = yield;
+        if (!ret)
+          throw "Could not create user directory.  Got status: " + ret.status;
+
+        this._log.debug("Successfully created user directory.  Re-attempting login.");
+        this._dav.baseURL = serverURL + "user/" + this.userPath + "/";
+        this._dav.login.async(this._dav, self.cb, this.username, this.password);
+        success = yield;
+        if (!success)
+          throw "Created user directory, but login still failed.  Aborting.";
+      }
+
+      this._dav.GET("private/privkey", self.cb);
+      let keyResp = yield;
+      Utils.ensureStatus(keyResp.status,
+                         "Could not get private key from server", [[200,300],404]);
+
+      if (keyResp.status != 404) {
+        this._cryptoId.key = keyResp.responseText;
 
-        if (ret.status == 201) {
-          this._log.debug("Successfully created user directory.  Re-attempting login.");
-          this._dav.baseURL = serverURL + "user/" + this.userPath + "/";
-          this._dav.login.async(this._dav, cont, this.username, this.password);
-          success = yield;
-        } else {
-          this._log.debug("Could not create user directory.  Got status: " + ret.status);
-        }
+      } else {
+        // generate a new key
+        this._log.debug("Generating new RSA key");
+        Crypto.RSAkeygen.async(Crypto, self.cb, this._cryptoId.password);
+        let [privkey, pubkey] = yield;
+
+        this._cryptoId.key = privkey;
+
+        this._dav.MKCOL("private/", self.cb);
+        ret = yield;
+        if (!ret)
+          throw "Could not create private key directory";
+
+        this._dav.MKCOL("public/", self.cb);
+        ret = yield;
+        if (!ret)
+          throw "Could not create public key directory";
+
+        this._dav.PUT("private/privkey", privkey, self.cb);
+        ret = yield;
+        Utils.ensureStatus(ret.status, "Could not upload private key");
+
+        this._dav.PUT("public/pubkey", pubkey, self.cb);
+        ret = yield;
+        Utils.ensureStatus(ret.status, "Could not upload public key");
       }
 
+      this._passphrase = null;
+      this._os.notifyObservers(null, "weave:service-login:success", "");
+      self.done(true);
+
     } catch (e) {
-      this._log.error("Exception caught: " + e.message);
-
-    } finally {
-      this._passphrase = null;
-      if (success) {
-        //this._log.debug("Login successful"); // chrome prints this too, hm
-        this._os.notifyObservers(null, "weave:service-login:success", "");
-      } else {
-        //this._log.debug("Login error");
-        this._os.notifyObservers(null, "weave:service-login:error", "");
-      }
-      Utils.generatorDone(this, self, onComplete, success);
-      yield; // onComplete is responsible for closing the generator
+      this._log.warn(Async.exceptionStr(self, e));
+      this._log.trace(e.trace);
+      this._os.notifyObservers(null, "weave:service-login:error", "");
+      self.done(false);
     }
-    this._log.warn("generator not properly closed");
   },
 
-  _resetLock: function WeaveSync__resetLock(onComplete) {
-    let [self, cont] = yield;
+  _resetLock: function WeaveSync__resetLock() {
+    let self = yield;
     let success = false;
 
     try {
       this._log.debug("Resetting server lock");
       this._os.notifyObservers(null, "weave:server-lock-reset:start", "");
 
-      this._dav.forceUnlock.async(this._dav, cont);
+      this._dav.forceUnlock.async(this._dav, self.cb);
       success = yield;
 
     } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+      throw e;
 
     } finally {
       if (success) {
         this._log.debug("Server lock reset successful");
         this._os.notifyObservers(null, "weave:server-lock-reset:success", "");
       } else {
         this._log.debug("Server lock reset failed");
         this._os.notifyObservers(null, "weave:server-lock-reset:error", "");
       }
-      Utils.generatorDone(this, self, onComplete, success);
-      yield; // generatorDone is responsible for closing the generator
+      self.done(success);
     }
-    this._log.warn("generator not properly closed");
   },
 
   _sync: function WeaveSync__sync() {
-    let [self, cont] = yield;
+    let self = yield;
     let success = false;
 
     try {
       if (!this._lock())
 	return;
 
       this._os.notifyObservers(null, "weave:service:sync:start", "");
 
       if (this._prefs.getBoolPref("bookmarks")) {
-        this._bmkEngine.sync(cont);
+        this._bmkEngine.sync(self.cb);
         yield;
       }
       if (this._prefs.getBoolPref("history")) {
-        this._histEngine.sync(cont);
+        this._histEngine.sync(self.cb);
         yield;
       }
 
       success = true;
       this._unlock();
 
     } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+      throw e;
 
     } finally {
       if (success)
         this._os.notifyObservers(null, "weave:service:sync:success", "");
       else
         this._os.notifyObservers(null, "weave:service:sync:error", "");
-      Utils.generatorDone(this, self);
-      yield; // generatorDone is responsible for closing the generator
+      self.done();
     }
-    this._log.warn("generator not properly closed");
   },
 
   _resetServer: function WeaveSync__resetServer() {
-    let [self, cont] = yield;
+    let self = yield;
 
-    try {
-      if (!this._lock())
-	return;
-
-      this._bmkEngine.resetServer(cont);
-      this._histEngine.resetServer(cont);
+    if (!this._lock())
+      return;
 
-      this._unlock();
-
-    } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    this._bmkEngine.resetServer(self.cb);
+    this._histEngine.resetServer(self.cb);
 
-    } finally {
-      Utils.generatorDone(this, self);
-      yield; // generatorDone is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    this._unlock();
+    self.done();
   },
 
   _resetClient: function WeaveSync__resetClient() {
-    let [self, cont] = yield;
+    let self = yield;
 
-    try {
-      if (!this._lock())
-	return;
-
-      this._bmkEngine.resetClient(cont);
-      this._histEngine.resetClient(cont);
+    if (!this._lock())
+      return;
 
-      this._unlock();
-
-    } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+    this._bmkEngine.resetClient(self.cb);
+    this._histEngine.resetClient(self.cb);
 
-    } finally {
-      Utils.generatorDone(this, self);
-      yield; // generatorDone is responsible for closing the generator
-    }
-    this._log.warn("generator not properly closed");
+    this._unlock();
+    self.done();
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupports]),
 
   // nsIObserver
 
   observe: function WeaveSync__observe(subject, topic, data) {
     if (topic != "nsPref:changed")
--- a/services/sync/modules/syncCores.js
+++ b/services/sync/modules/syncCores.js
@@ -40,18 +40,19 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/constants.js");
 Cu.import("resource://weave/util.js");
+Cu.import("resource://weave/async.js");
 
-Function.prototype.async = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
 
 /*
  * SyncCore objects
  * Sync cores deal with diff creation and conflict resolution.
  * Tree data structures where all nodes have GUIDs only need to be
  * subclassed for each data type to implement commandLike and
  * itemExists.
  */
@@ -84,19 +85,19 @@ SyncCore.prototype = {
 
   _nodeParentsInt: function SC__nodeParentsInt(GUID, tree, parents) {
     if (!tree[GUID] || !tree[GUID].parentGUID)
       return parents;
     parents.push(tree[GUID].parentGUID);
     return this._nodeParentsInt(tree[GUID].parentGUID, tree, parents);
   },
 
-  _detectUpdates: function SC__detectUpdates(onComplete, a, b) {
-    let [self, cont] = yield;
-    let listener = new Utils.EventListener(cont);
+  _detectUpdates: function SC__detectUpdates(a, b) {
+    let self = yield;
+    let listener = new Utils.EventListener(self.cb);
     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
     let cmds = [];
 
     try {
       for (let GUID in a) {
 
         timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
@@ -111,16 +112,17 @@ SyncCore.prototype = {
                      depth: parents.length, parents: parents,
                      data: edits.props});
         } else {
           let parents = this._nodeParents(GUID, a); // ???
           cmds.push({action: "remove", GUID: GUID,
                      depth: parents.length, parents: parents});
         }
       }
+
       for (let GUID in b) {
 
         timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
         yield; // Yield to main loop
 
         if (GUID in a)
           continue;
         let parents = this._nodeParents(GUID, b);
@@ -136,24 +138,22 @@ SyncCore.prototype = {
         if (a.index > b.index)
           return -1;
         if (a.index < b.index)
           return 1;
         return 0; // should never happen, but not a big deal if it does
       });
 
     } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+      throw e;
 
     } finally {
       timer = null;
-      Utils.generatorDone(this, self, onComplete, cmds);
-      yield; // onComplete is responsible for closing the generator
+      self.done(cmds);
     }
-    this._log.warn("generator not properly closed");
   },
 
   _commandLike: function SC__commandLike(a, b) {
     this._log.error("commandLike needs to be subclassed");
 
     // Check that neither command is null, and verify that the GUIDs
     // are different (otherwise we need to check for edits)
     if (!a || !b || a.GUID == b.GUID)
@@ -211,19 +211,19 @@ SyncCore.prototype = {
     }
   },
 
   _itemExists: function SC__itemExists(GUID) {
     this._log.error("itemExists needs to be subclassed");
     return false;
   },
 
-  _reconcile: function SC__reconcile(onComplete, listA, listB) {
-    let [self, cont] = yield;
-    let listener = new Utils.EventListener(cont);
+  _reconcile: function SC__reconcile(listA, listB) {
+    let self = yield;
+    let listener = new Utils.EventListener(self.cb);
     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
     let propagations = [[], []];
     let conflicts = [[], []];
     let ret = {propagations: propagations, conflicts: conflicts};
     this._log.debug("Reconciling " + listA.length +
 		    " against " + listB.length + "commands");
 
@@ -292,24 +292,23 @@ SyncCore.prototype = {
   
       timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
       yield; // Yield to main loop
   
       this._getPropagations(listB, conflicts[1], propagations[0]);
       ret = {propagations: propagations, conflicts: conflicts};
 
     } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e));
+      this._log.error("Exception caught: " + (e.message? e.message : e) +
+                      " - " + (e.location? e.location : "_reconcile"));
 
     } finally {
       timer = null;
-      Utils.generatorDone(this, self, onComplete, ret);
-      yield; // onComplete is responsible for closing the generator
+      self.done(ret);
     }
-    this._log.warn("generator not properly closed");
   },
 
   // Public methods
 
   detectUpdates: function SC_detectUpdates(onComplete, a, b) {
     return this._detectUpdates.async(this, onComplete, a, b);
   },
 
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -108,31 +108,53 @@ let Utils = {
       ret = {};
       for (let key in thing)
         ret[key] = Utils.deepCopy(thing[key]);
     }
 
     return ret;
   },
 
+  exceptionStr: function Weave_exceptionStr(e) {
+    let message = e.message? e.message : e;
+    let location = e.location? " (" + e.location + ")" : "";
+    return message + location;
+  },
+
+  stackTrace: function Weave_stackTrace(stackFrame, str) {
+    if (stackFrame.caller)
+      str = Utils.stackTrace(stackFrame.caller, str);
+
+    if (!str)
+      str = "";
+    str += stackFrame + "\n";
+
+    return str;
+  },
+
   checkStatus: function Weave_checkStatus(code, msg, ranges) {
     if (!ranges)
       ranges = [[200,300]];
 
     for (let i = 0; i < ranges.length; i++) {
       rng = ranges[i];
       if (typeof(rng) == "object" && code >= rng[0] && code < rng[1])
-        return;
+        return true;
       else if (typeof(rng) == "integer" && code == rng)
-        return;
+        return true;
     }
 
     let log = Log4Moz.Service.getLogger("Service.Util");
     log.error(msg + " Error code: " + code);
-    throw 'checkStatus failed';
+    return false;
+  },
+
+  ensureStatus: function Weave_ensureStatus(args) {
+    if (!Utils.checkStatus.apply(Utils, arguments))
+      throw 'checkStatus failed';
   },
 
   makeURI: function Weave_makeURI(URIString) {
     if (URIString === null || URIString == "")
       return null;
     let ioservice = Cc["@mozilla.org/network/io-service;1"].
       getService(Ci.nsIIOService);
     return ioservice.newURI(URIString, null, null);
@@ -142,83 +164,16 @@ let Utils = {
     let root = xmlDoc.ownerDocument == null ?
       xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement
     let nsResolver = xmlDoc.createNSResolver(root);
 
     return xmlDoc.evaluate(xpathString, xmlDoc, nsResolver,
                            Ci.nsIDOMXPathResult.ANY_TYPE, null);
   },
 
-  bind2: function Weave_bind2(object, method) {
-    return function innerBind() { return method.apply(object, arguments); }
-  },
-
-  // Meant to be used like this in code that imports this file:
-  //
-  // Function.prototype.async = generatorAsync;
-  //
-  // So that you can do:
-  //
-  // gen = fooGen.async(...);
-  // ret = yield;
-  //
-  // where fooGen is a generator function, and gen is the running generator.
-  // ret is whatever the generator 'returns' via generatorDone().
-
-  generatorAsync: function Weave_generatorAsync(self, extra_args) {
-    try {
-      let args = Array.prototype.slice.call(arguments, 1);
-      let gen = this.apply(self, args);
-      gen.next(); // must initialize before sending
-      gen.send([gen, function(data) {Utils.continueGenerator(gen, data);}]);
-      return gen;
-    } catch (e) {
-      if (e instanceof StopIteration) {
-        dump("async warning: generator stopped unexpectedly");
-        return null;
-      } else {
-        dump("Exception caught: " + e.message);
-      }
-    }
-  },
-
-  continueGenerator: function Weave_continueGenerator(generator, data) {
-    try { generator.send(data); }
-    catch (e) {
-      if (e instanceof StopIteration)
-        dump("continueGenerator warning: generator stopped unexpectedly");
-      else
-        dump("Exception caught: " + e.message);
-    }
-  },
-
-  // generators created using Function.async can't simply call the
-  // callback with the return value, since that would cause the calling
-  // function to end up running (after the yield) from inside the
-  // generator.  Instead, generators can call this method which sets up
-  // a timer to call the callback from a timer (and cleans up the timer
-  // to avoid leaks).  It also closes generators after the timeout, to
-  // keep things clean.
-  generatorDone: function Weave_generatorDone(object, generator, callback, retval) {
-    if (object._timer)
-      throw "Called generatorDone when there is a timer already set."
-
-    let cb = Utils.bind2(object, function(event) {
-      generator.close();
-      generator = null;
-      object._timer = null;
-      if (callback)
-        callback(retval);
-    });
-
-    object._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    object._timer.initWithCallback(new Utils.EventListener(cb),
-                                   0, object._timer.TYPE_ONE_SHOT);
-  },
-
   runCmd: function Weave_runCmd() {
     var binary;
     var args = [];
 
     for (let i = 0; i < arguments.length; ++i) {
       args.push(arguments[i]);
     }
 
@@ -313,16 +268,20 @@ let Utils = {
   readStream: function Weave_readStream(is) {
     let ret = "", str = {};
     while (is.readString(4096, str) != 0) {
       ret += str.value;
     }
     return ret;
   },
 
+  bind2: function Async_bind2(object, method) {
+    return function innerBind() { return method.apply(object, arguments); }
+  },
+
   /*
    * Event listener object
    * Used to handle XMLHttpRequest and nsITimer callbacks
    */
 
   EventListener: function Weave_EventListener(handler, eventName) {
     this._handler = handler;
     this._eventName = eventName;
@@ -336,12 +295,12 @@ Utils.EventListener.prototype = {
   // DOM event listener
   handleEvent: function EL_handleEvent(event) {
     this._log.trace("Handling event " + this._eventName);
     this._handler(event);
   },
 
   // nsITimerCallback
   notify: function EL_notify(timer) {
-    this._log.trace("Timer fired");
+    //this._log.trace("Timer fired");
     this._handler(timer);
   }
 }