Bug 664792 - Tune sync intervals according to user behaviour. r=philikon

Part 6: Fix the idle/back observers, have Service.startOver() reset SyncScheduler values
This commit is contained in:
Marina Samuel 2011-06-29 03:48:55 +02:00
parent f8845fdf67
commit 2d53fd1955
5 changed files with 195 additions and 84 deletions

View File

@ -52,22 +52,28 @@ Cu.import("resource://services-sync/main.js"); // So we can get to Service fo
let SyncScheduler = {
_log: Log4Moz.repository.getLogger("Sync.SyncScheduler"),
// Since a user has a single device by default, they are
// treated as idle.
idle: true,
/**
* The nsITimer object that schedules the next sync. See scheduleNextSync().
*/
syncTimer: null,
hasIncomingItems: false,
numClients: 0,
setDefaults: function setDefaults() {
// A user is non-idle on startup by default.
this.idle = false;
this.hasIncomingItems = false;
this.numClients = 0;
this.nextSync = 0,
this.syncInterval = SINGLE_USER_SYNC;
this.syncThreshold = SINGLE_USER_THRESHOLD;
},
nextSync: 0,
syncInterval: SINGLE_USER_SYNC,
syncThreshold: SINGLE_USER_THRESHOLD,
get globalScore() Svc.Prefs.get("globalScore", 0),
set globalScore(value) Svc.Prefs.set("globalScore", value),
init: function init() {
this.setDefaults();
Svc.Obs.add("weave:engine:score:updated", this);
Svc.Obs.add("network:offline-status-changed", this);
Svc.Obs.add("weave:service:sync:start", this);
@ -150,12 +156,17 @@ let SyncScheduler = {
break;
case "idle":
this.idle = true;
adjustSyncInterval();
// Adjust the interval for future syncs. This won't actually have any
// effect until the next pending sync (which will happen soon since we
// were just active.)
this.adjustSyncInterval();
break;
case "back":
Utils.nextTick(Weave.Service.sync, Weave.Service);
this.idle = false;
adjustSyncInterval();
// Trigger a sync if we have multiple clients.
if (this.numClients > 1) {
Utils.nextTick(Weave.Service.sync, Weave.Service);
}
break;
}
},
@ -284,7 +295,7 @@ let SyncScheduler = {
}
this._log.trace("Next sync in " + Math.ceil(interval / 1000) + " sec.");
Utils.namedTimer(this._syncIfMPUnlocked, interval, this, "_syncTimer");
Utils.namedTimer(this._syncIfMPUnlocked, interval, this, "syncTimer");
// Save the next sync time in-case sync is disabled (logout/offline/etc.)
this.nextSync = Date.now() + interval;
@ -334,8 +345,8 @@ let SyncScheduler = {
this._log.debug("Clearing sync triggers.");
// Clear out any scheduled syncs
if (this._syncTimer)
this._syncTimer.clear();
if (this.syncTimer)
this.syncTimer.clear();
}
};

View File

@ -1025,6 +1025,7 @@ WeaveSvc.prototype = {
this._ignorePrefObserver = true;
Svc.Prefs.resetBranch("");
this._ignorePrefObserver = false;
SyncScheduler.setDefaults();
Svc.Prefs.set("lastversion", WEAVE_VERSION);
// Find weave logins and remove them.

View File

@ -10,25 +10,37 @@ Svc.DefaultPrefs.set("registerEngines", "");
Cu.import("resource://services-sync/service.js");
function sync_httpd_setup() {
let global = new ServerWBO("global", {
syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
engines: {clients: {version: Clients.version,
syncID: Clients.syncID}}
});
let clientsColl = new ServerCollection({}, true);
// Tracking info/collections.
let collectionsHelper = track_collections_helper();
let upd = collectionsHelper.with_updated_collection;
return httpd_setup({
"/1.1/johndoe/storage/meta/global":
upd("meta", (new ServerWBO("global", {})).handler()),
"/1.1/johndoe/storage/meta/global": upd("meta", global.handler()),
"/1.1/johndoe/info/collections": collectionsHelper.handler,
"/1.1/johndoe/storage/crypto/keys":
upd("crypto", (new ServerWBO("keys")).handler()),
"/1.1/johndoe/storage/clients": upd("clients", (new ServerCollection()).handler())
"/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler())
});
}
function setUp() {
Service.username = "johndoe";
Service.password = "ilovejane";
Service.passphrase = "sekrit";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/";
generateNewKeys();
let serverKeys = CollectionKeys.asWBO("crypto", "keys");
serverKeys.encrypt(Service.syncKeyBundle);
return serverKeys.upload(Service.cryptoKeysURL);
}
function run_test() {
@ -52,13 +64,14 @@ add_test(function test_successful_sync_adjustSyncInterval() {
setUp();
// Confirm defaults
do_check_true(SyncScheduler.idle);
do_check_false(SyncScheduler.idle);
do_check_false(SyncScheduler.numClients > 1);
do_check_eq(SyncScheduler.syncInterval, SINGLE_USER_SYNC);
do_check_false(SyncScheduler.hasIncomingItems);
_("Test as long as numClients <= 1 our sync interval is SINGLE_USER.");
// idle == true && numClients <= 1 && hasIncomingItems == false
SyncScheduler.idle = true;
Service.sync();
do_check_eq(syncSuccesses, 1);
do_check_true(SyncScheduler.idle);
@ -134,14 +147,9 @@ add_test(function test_successful_sync_adjustSyncInterval() {
Records.clearCache();
Svc.Prefs.resetBranch("");
SyncScheduler.setDefaults();
Clients.resetClient();
// Reset defaults
SyncScheduler.idle = true;
SyncScheduler.updateClientMode();
SyncScheduler.syncInterval = SINGLE_USER_SYNC;
SyncScheduler.hasIncomingItems = false;
server.stop(run_next_test);
});
@ -166,13 +174,14 @@ add_test(function test_unsuccessful_sync_adjustSyncInterval() {
setUp();
// Confirm defaults
do_check_true(SyncScheduler.idle);
do_check_false(SyncScheduler.idle);
do_check_false(SyncScheduler.numClients > 1);
do_check_eq(SyncScheduler.syncInterval, SINGLE_USER_SYNC);
do_check_false(SyncScheduler.hasIncomingItems);
_("Test as long as numClients <= 1 our sync interval is SINGLE_USER.");
// idle == true && numClients <= 1 && hasIncomingItems == false
SyncScheduler.idle = true;
Service.sync();
do_check_eq(syncFailures, 1);
do_check_true(SyncScheduler.idle);
@ -253,13 +262,38 @@ add_test(function test_unsuccessful_sync_adjustSyncInterval() {
Records.clearCache();
Svc.Prefs.resetBranch("");
SyncScheduler.setDefaults();
Clients.resetClient();
Service._lockedSync = origLockedSync;
// Reset defaults
SyncScheduler.idle = true;
SyncScheduler.syncInterval = SINGLE_USER_SYNC;
SyncScheduler.hasIncomingItems = false;
server.stop(run_next_test);
});
add_test(function test_back_triggers_sync() {
let server = sync_httpd_setup();
setUp();
// Single device: no sync triggered.
SyncScheduler.idle = true;
SyncScheduler.observe(null, "back", IDLE_TIME);
do_check_false(SyncScheduler.idle);
// Multiple devices: sync is triggered.
Clients._store.create({id: "foo", cleartext: "bar"});
SyncScheduler.updateClientMode();
Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
Records.clearCache();
Svc.Prefs.resetBranch("");
SyncScheduler.setDefaults();
Clients.resetClient();
server.stop(run_next_test);
});
SyncScheduler.idle = true;
SyncScheduler.observe(null, "back", IDLE_TIME);
do_check_false(SyncScheduler.idle);
});

View File

@ -1,5 +1,7 @@
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/policies.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/util.js");
function BlaEngine() {
@ -17,7 +19,12 @@ BlaEngine.prototype = {
Engines.register(BlaEngine);
function test_removeClientData() {
function run_test() {
initTestLogging("Trace");
run_next_test();
}
add_test(function test_removeClientData() {
let engine = Engines.get("bla");
// No cluster URL = no removal.
@ -29,11 +36,27 @@ function test_removeClientData() {
do_check_false(engine.removed);
Service.startOver();
do_check_true(engine.removed);
}
run_next_test();
});
function run_test() {
initTestLogging("Trace");
add_test(function test_reset_SyncScheduler() {
// Some non-defualt values for SyncScheduler's attributes.
SyncScheduler.idle = true;
SyncScheduler.hasIncomingItems = true;
SyncScheduler.numClients = 42;
SyncScheduler.nextSync = Date.now();
SyncScheduler.syncThreshold = MULTI_DEVICE_THRESHOLD;
SyncScheduler.syncInterval = MULTI_DEVICE_ACTIVE_SYNC;
test_removeClientData();
}
Service.startOver();
do_check_false(SyncScheduler.idle);
do_check_false(SyncScheduler.hasIncomingItems);
do_check_eq(SyncScheduler.numClients, 0);
do_check_eq(SyncScheduler.nextSync, 0);
do_check_eq(SyncScheduler.syncInterval, SINGLE_USER_SYNC);
do_check_eq(SyncScheduler.syncThreshold, SINGLE_USER_THRESHOLD);
run_next_test();
});

View File

@ -81,16 +81,16 @@ add_test(function test_updateClientMode() {
do_check_eq(SyncScheduler.syncThreshold, SINGLE_USER_THRESHOLD);
do_check_eq(SyncScheduler.syncInterval, SINGLE_USER_SYNC);
do_check_false(SyncScheduler.numClients > 1);
do_check_true(SyncScheduler.idle);
do_check_false(SyncScheduler.idle);
// Trigger a change in interval & threshold by adding a client.
Clients._store.create({id: "foo", cleartext: "bar"});
SyncScheduler.updateClientMode();
do_check_eq(SyncScheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
do_check_eq(SyncScheduler.syncInterval, MULTI_DEVICE_IDLE_SYNC);
do_check_eq(SyncScheduler.syncInterval, MULTI_DEVICE_ACTIVE_SYNC);
do_check_true(SyncScheduler.numClients > 1);
do_check_true(SyncScheduler.idle);
do_check_false(SyncScheduler.idle);
// Resets the number of clients to 0.
Clients.resetClient();
@ -101,9 +101,10 @@ add_test(function test_updateClientMode() {
do_check_eq(SyncScheduler.syncThreshold, SINGLE_USER_THRESHOLD);
do_check_eq(SyncScheduler.syncInterval, SINGLE_USER_SYNC);
do_check_false(SyncScheduler.numClients > 1);
do_check_true(SyncScheduler.idle);
do_check_false(SyncScheduler.idle);
Svc.Prefs.resetBranch("");
SyncScheduler.setDefaults();
Clients.resetClient();
run_next_test();
});
@ -142,6 +143,7 @@ add_test(function test_masterpassword_locked_retry_interval() {
SyncScheduler.scheduleAtInterval = SyncScheduler._scheduleAtInterval;
Svc.Prefs.resetBranch("");
SyncScheduler.setDefaults();
Clients.resetClient();
server.stop(run_next_test);
});
@ -165,6 +167,7 @@ add_test(function test_calculateBackoff() {
Status.backoffInterval = 0;
Svc.Prefs.resetBranch("");
SyncScheduler.setDefaults();
Clients.resetClient();
run_next_test();
});
@ -174,8 +177,7 @@ add_test(function test_scheduleNextSync() {
let initial_nextSync;
// The estimated syncInterval value after calling scheduleNextSync.
let syncInterval;
let syncInterval;
// Test backoffInterval is 0 as expected.
do_check_eq(Status.backoffInterval, 0);
@ -193,7 +195,8 @@ add_test(function test_scheduleNextSync() {
// syncInterval is smaller than expectedInterval
// since some time has passed.
do_check_true(syncInterval <= expectedInterval);
SyncScheduler._syncTimer.clear();
do_check_eq(SyncScheduler.syncTimer.delay, expectedInterval);
SyncScheduler.syncTimer.clear();
_("Test setting sync interval when nextSync != 0");
@ -208,61 +211,75 @@ add_test(function test_scheduleNextSync() {
// syncInterval is smaller than expectedInterval
// since some time has passed.
do_check_true(syncInterval <= expectedInterval);
SyncScheduler._syncTimer.clear();
do_check_eq(SyncScheduler.syncTimer.delay, expectedInterval);
SyncScheduler.syncTimer.clear();
SyncScheduler.setDefaults();
run_next_test();
});
add_test(function test_handleSyncError() {
let scheduleNextSyncCalled = 0;
let scheduleAtIntervalCalled = 0;
let server = sync_httpd_setup();
setUp();
let origLockedSync = Service._lockedSync;
Service._lockedSync = function () {
// Force a sync fail.
Service._loggedIn = false;
origLockedSync.call(Service);
};
SyncScheduler.scheduleNextSync = function () {
scheduleNextSyncCalled++;
}
SyncScheduler.scheduleAtInterval = function () {
scheduleAtIntervalCalled++;
}
// Ensure expected initial environment.
_("Ensure expected initial environment.");
do_check_eq(SyncScheduler._syncErrors, 0);
do_check_false(Status.enforceBackoff);
do_check_eq(SyncScheduler.syncInterval, SINGLE_USER_SYNC);
do_check_eq(Status.backoffInterval, 0);
// Call handleSyncError several times & ensure it
// functions correctly.
SyncScheduler.handleSyncError();
do_check_eq(scheduleNextSyncCalled, 1);
do_check_eq(scheduleAtIntervalCalled, 0);
// Trigger sync with an error several times & observe
// functionality of handleSyncError()
_("Test first error calls scheduleNextSync on default interval");
Service.sync();
do_check_true(SyncScheduler.nextSync <= Date.now() + SINGLE_USER_SYNC);
do_check_eq(SyncScheduler.syncTimer.delay, SINGLE_USER_SYNC);
do_check_eq(SyncScheduler._syncErrors, 1);
do_check_false(Status.enforceBackoff);
SyncScheduler.syncTimer.clear();
SyncScheduler.handleSyncError();
do_check_eq(scheduleNextSyncCalled, 2);
do_check_eq(scheduleAtIntervalCalled, 0);
_("Test second error still calls scheduleNextSync on default interval");
Service.sync();
do_check_true(SyncScheduler.nextSync <= Date.now() + SINGLE_USER_SYNC);
do_check_eq(SyncScheduler.syncTimer.delay, SINGLE_USER_SYNC);
do_check_eq(SyncScheduler._syncErrors, 2);
do_check_false(Status.enforceBackoff);
SyncScheduler.syncTimer.clear();
SyncScheduler.handleSyncError();
do_check_eq(scheduleNextSyncCalled, 2);
do_check_eq(scheduleAtIntervalCalled, 1);
_("Test third error sets Status.enforceBackoff and calls scheduleAtInterval");
Service.sync();
let maxInterval = SyncScheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
do_check_eq(Status.backoffInterval, 0);
do_check_true(SyncScheduler.nextSync <= (Date.now() + maxInterval));
do_check_true(SyncScheduler.syncTimer.delay <= maxInterval);
do_check_eq(SyncScheduler._syncErrors, 3);
do_check_true(Status.enforceBackoff);
// Status.enforceBackoff is false but there are still errors.
Status.resetBackoff();
do_check_false(Status.enforceBackoff);
do_check_eq(SyncScheduler._syncErrors, 3);
SyncScheduler.handleSyncError();
do_check_eq(scheduleNextSyncCalled, 2);
do_check_eq(scheduleAtIntervalCalled, 2);
SyncScheduler.syncTimer.clear();
_("Test fourth error still calls scheduleAtInterval even if enforceBackoff was reset");
Service.sync();
maxInterval = SyncScheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL);
do_check_true(SyncScheduler.nextSync <= Date.now() + maxInterval);
do_check_true(SyncScheduler.syncTimer.delay <= maxInterval);
do_check_eq(SyncScheduler._syncErrors, 4);
do_check_true(Status.enforceBackoff);
SyncScheduler.syncTimer.clear();
Svc.Prefs.resetBranch("");
Clients.resetClient();
run_next_test();
Service._lockedSync = origLockedSync;
SyncScheduler.setDefaults();
server.stop(run_next_test);
});
add_test(function test_client_sync_finish_updateClientMode() {
@ -272,19 +289,18 @@ add_test(function test_client_sync_finish_updateClientMode() {
// Confirm defaults.
do_check_eq(SyncScheduler.syncThreshold, SINGLE_USER_THRESHOLD);
do_check_eq(SyncScheduler.syncInterval, SINGLE_USER_SYNC);
do_check_false(SyncScheduler.numClients > 1);
do_check_true(SyncScheduler.idle);
do_check_false(SyncScheduler.idle);
// Trigger a change in interval & threshold by adding a client.
Clients._store.create({id: "foo", cleartext: "bar"});
do_check_eq(SyncScheduler.numClients, 1);
do_check_false(SyncScheduler.numClients > 1);
SyncScheduler.updateClientMode();
Service.sync();
do_check_eq(SyncScheduler.syncThreshold, MULTI_DEVICE_THRESHOLD);
do_check_eq(SyncScheduler.syncInterval, MULTI_DEVICE_IDLE_SYNC);
do_check_eq(SyncScheduler.syncInterval, MULTI_DEVICE_ACTIVE_SYNC);
do_check_true(SyncScheduler.numClients > 1);
do_check_true(SyncScheduler.idle);
do_check_false(SyncScheduler.idle);
// Resets the number of clients to 0.
Clients.resetClient();
@ -295,9 +311,10 @@ add_test(function test_client_sync_finish_updateClientMode() {
do_check_eq(SyncScheduler.syncThreshold, SINGLE_USER_THRESHOLD);
do_check_eq(SyncScheduler.syncInterval, SINGLE_USER_SYNC);
do_check_false(SyncScheduler.numClients > 1);
do_check_true(SyncScheduler.idle);
do_check_false(SyncScheduler.idle);
Svc.Prefs.resetBranch("");
SyncScheduler.setDefaults();
Clients.resetClient();
server.stop(run_next_test);
});
@ -305,7 +322,11 @@ add_test(function test_client_sync_finish_updateClientMode() {
add_test(function test_sync_at_startup() {
Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() {
Svc.Obs.remove("weave:service:sync:finish", onSyncFinish);
Service.resetClient();
Svc.Prefs.resetBranch("");
SyncScheduler.setDefaults();
Clients.resetClient();
server.stop(run_next_test);
});
@ -314,3 +335,24 @@ add_test(function test_sync_at_startup() {
Service.delayedAutoConnect(0);
});
add_test(function test_idle_adjustSyncInterval() {
// Confirm defaults.
do_check_eq(SyncScheduler.idle, false);
// Single device: nothing changes.
SyncScheduler.observe(null, "idle", IDLE_TIME);
do_check_eq(SyncScheduler.idle, true);
do_check_eq(SyncScheduler.syncInterval, SINGLE_USER_SYNC);
// Multiple devices: switch to idle interval.
SyncScheduler.idle = false;
Clients._store.create({id: "foo", cleartext: "bar"});
SyncScheduler.updateClientMode();
SyncScheduler.observe(null, "idle", IDLE_TIME);
do_check_eq(SyncScheduler.idle, true);
do_check_eq(SyncScheduler.syncInterval, MULTI_DEVICE_IDLE_SYNC);
SyncScheduler.setDefaults();
run_next_test();
});