Closed Bug 676404 Opened 14 years ago Closed 14 years ago

Refactor Sync send/receive command functionality into Clients engine

Categories

(Firefox :: Sync, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla8

People

(Reporter: gps, Assigned: gps)

References

Details

(Whiteboard: [fixed in services][qa-])

Attachments

(1 file, 4 obsolete files)

Currently, all the code dealing with commands is in the service module. The service module is being deprecated and we would like to move the command code elsewhere. The clients engine is the logical place for it. This bug will track the movement of the commands code into the clients engine.
(In reply to comment #0) \o/ for detailed bugs! > The service module is being deprecated... This isn't strictly true. There'll probably always be something like Service; the trick is just that it should have only a very tightly defined area of ownership, but due to its storied past it's been a dumping ground for functionality. A lot of Marina's recent work has been pulling stuff out of Service, so it's been losing a lot of weight! Keep it up!
OS: Mac OS X → All
Hardware: x86 → All
Two-parter, too: it would be nice if this didn't just cover prepCommand (sending), but also incoming command processing (which now lives in Service._lockedSync, and possibly is smeared elsewhere).
Summary: Refactor Command Code Into Clients Engine → Refactor Sync send/receive command functionality into Clients engine
(In reply to comment #2) > Two-parter, too: it would be nice if this didn't just cover prepCommand > (sending), but also incoming command processing (which now lives in > Service._lockedSync, and possibly is smeared elsewhere). philikon and I talked about this yesterday. I'll be moving the main handler function over to Clients. However, the low-level APIs, such as wipeClient() and resetClient() are tied into Service pretty well, so they will remain there for the time being.
OS: All → Mac OS X
Hardware: All → x86
(In reply to comment #3) > However, the low-level APIs, such as wipeClient() > and resetClient() are tied into Service pretty well, so they will remain > there for the time being. Sounds good to me.
OS: Mac OS X → All
Hardware: x86 → All
This is my first stab at moving command code to the clients engine. I took the opportunity to rename some functions and to change the send command API to support single client targets (before, we only supported broadcasting). This feature will be required by push tab to device. The patch is missing at least 1 unit test: verification that commands actually make their way to the server and propagate to other clients. This is forthcoming. (It is worth mentioning that prior to this, the commands API was not directly tested, sadly.) I'm not asking for a review at this time because the patch isn't complete. But if anyone wants to do an informal review, I would welcome it.
* Fixes bug in sending to all clients not calling proper function. * Adds unit test verifying commands propagate during sync
Attachment #550741 - Attachment is obsolete: true
Comment on attachment 550741 [details] [diff] [review] Migrate Command Code to Clients Engine, v1 Review of attachment 550741 [details] [diff] [review]: ----------------------------------------------------------------- Lots of nits for you :) ::: services/sync/modules/engines/clients.js @@ +192,5 @@ > + * A hash of valid commands that the client knows about. The key is a command > + * and the value is a hash containing information about the command such as > + * number of arguments and description. > + */ > + _commands: [ This whole thing is awful. Can we align this all, maybe replace the reduce, get rid of that Iterator, add {} where appropriate, and in general make it not suck? We're breaking blame, so we might as well fix the code. @@ +219,5 @@ > + * > + * @param command Command string to send > + * @param args Array of arguments for command > + */ > + _sendCommandToAllClients: function sendCommandToAllClients(command, args) { Why are these _-prefixed? They seem like public API calls. Especially when the function name doesn't have the "_" :) @@ +239,5 @@ > + > + let client = this._store._remoteClients[clientId]; > + if (!client) { > + this._log.warning("Unknown remote client ID specified: " + clientId); > + return; What's your justification for not throwing here? @@ +242,5 @@ > + this._log.warning("Unknown remote client ID specified: " + clientId); > + return; > + } > + > + // Helper to determine if the client already has this command Please punctuate your comments. @@ +243,5 @@ > + return; > + } > + > + // Helper to determine if the client already has this command > + let notDupe = function(other) other.command != command || Please use conventional function syntax here ({ return ... }) to make Emacs happy for Philipp. @@ +244,5 @@ > + } > + > + // Helper to determine if the client already has this command > + let notDupe = function(other) other.command != command || > + JSON.stringify(other.args) != JSON.stringify(args); Can we use Utils.deepEqual here instead of stringifying? (Microbenchmark would tell you which way is better.) @@ +252,5 @@ > + command: command, > + args: args, > + }; > + > + if (client.commands == null) {} please. (And when you do, cuddle the else.) @@ +268,5 @@ > + > + /** > + * Check if the local client has any remote commands and perform them. > + * > + * @return False to abort sync `false` is a keyword. No caps. @@ +271,5 @@ > + * > + * @return False to abort sync > + */ > + processIncomingCommands: function processIncomingCommands() { > + this._notify("clients:process-commands", "", function() { Where is _notify coming from? @@ +338,5 @@ > + > + if (clientId) { > + this._sendCommandToClient(command, args, clientId); > + } > + else { Please cuddle your elses. ::: services/sync/modules/service.js @@ +1822,5 @@ > // Clear out any server data > this.wipeServer(engines); > > // Only wipe the engines provided > if (engines) {}. ::: services/sync/tests/unit/test_clients_engine.js @@ +250,5 @@ > + store.create(rec); > + let remoteRecord = store.createRecord(remoteId, "clients"); > + > + let action = "testCommand"; > + let args = [ "foo", "bar" ]; Nit: cuddle the [], please. @@ +311,5 @@ > + } > + else { > + do_check_eq(newRecord.commands, undefined); > + > + if (store._tracker) {}.
Attachment #550741 - Attachment is obsolete: false
> @@ +219,5 @@ > > + * > > + * @param command Command string to send > > + * @param args Array of arguments for command > > + */ > > + _sendCommandToAllClients: function sendCommandToAllClients(command, args) { > > Why are these _-prefixed? They seem like public API calls. Especially when > the function name doesn't have the "_" :) They were public before. However, everything that sends commands was using prepCommand(), which I renamed to verifyAndSendCommand(). Since verification is good, I decided to make the non-verification functions non-public. I think the only debate to have is whether verifyAndSendCommand() should be renamed SendCommand(), unroll _sendCommandToAllClients(), and rename _sendCommand() to have a more private name. > @@ +239,5 @@ > > + > > + let client = this._store._remoteClients[clientId]; > > + if (!client) { > > + this._log.warning("Unknown remote client ID specified: " + clientId); > > + return; > > What's your justification for not throwing here? I have none. I'll defer to someone with more expertise to recommend the proper behavior, which I think you did. > @@ +243,5 @@ > > + return; > > + } > > + > > + // Helper to determine if the client already has this command > > + let notDupe = function(other) other.command != command || > > Please use conventional function syntax here ({ return ... }) to make Emacs > happy for Philipp. This was copied verbatim from Service, but I'll comply. > @@ +244,5 @@ > > + } > > + > > + // Helper to determine if the client already has this command > > + let notDupe = function(other) other.command != command || > > + JSON.stringify(other.args) != JSON.stringify(args); > > Can we use Utils.deepEqual here instead of stringifying? Also copied verbatim, but easy to address. > @@ +252,5 @@ > > + command: command, > > + args: args, > > + }; > > + > > + if (client.commands == null) > > {} please. (And when you do, cuddle the else.) Is that always the rule? I see plenty of exceptions of if's lacking braces throughout the tree. > @@ +271,5 @@ > > + * > > + * @return False to abort sync > > + */ > > + processIncomingCommands: function processIncomingCommands() { > > + this._notify("clients:process-commands", "", function() { > > Where is _notify coming from? Magic from Engine. I scratched my head the first time too.
(In reply to comment #8) > They were public before. However, everything that sends commands was using > prepCommand(), which I renamed to verifyAndSendCommand(). Since verification > is good, I decided to make the non-verification functions non-public. Sounds fair; just make the names agree. > I think the only debate to have is whether verifyAndSendCommand() should be > renamed SendCommand(), unroll _sendCommandToAllClients(), and rename > _sendCommand() to have a more private name. That sounds about right. Would also allow for _sendCommandToAllClients to be less inefficient -- right now it does all the message construction work (e.g., stringify) over and over. Fortunately it's not a high-throughput function, so I didn't bother calling it out. > > What's your justification for not throwing here? > > I have none. I'll defer to someone with more expertise to recommend the > proper behavior, which I think you did. Heh. It's certainly a non-continuable condition. Just make sure the callers catch. Throw an Error object, please: Bug 675130. > This was copied verbatim from Service, but I'll comply. Yeah, none of these are comments about you; it's just good to fix code whenever you'll be breaking blame anyway. > Is that always the rule? I see plenty of exceptions of if's lacking braces > throughout the tree. https://developer.mozilla.org/En/Mozilla_Coding_Style_Guide#Control_structures > Magic from Engine. I scratched my head the first time too. Ah yes. Thought it was just a mispaste from Service.
This patch incorporates most, if not all, feedback. It also includes a few more unit tests that cover previously untested functionality.
Attachment #550741 - Attachment is obsolete: true
Attachment #550770 - Attachment is obsolete: true
Attachment #550821 - Flags: review?(rnewman)
Comment on attachment 550821 [details] [diff] [review] Migrate Command Code to Clients Engine, v3 Review of attachment 550821 [details] [diff] [review]: ----------------------------------------------------------------- A few things to deal with. Clearing flag. ::: services/sync/modules/engines/clients.js @@ +197,5 @@ > + ["resetAll", 0, "Clear temporary local data for all engines"], > + ["resetEngine", 1, "Clear temporary local data for engine"], > + ["wipeAll", 0, "Delete all client data for all engines"], > + ["wipeEngine", 1, "Delete all client data for engine"], > + ["logout", 0, "Log out client"], Trailing comma. @@ +198,5 @@ > + ["resetEngine", 1, "Clear temporary local data for engine"], > + ["wipeAll", 0, "Delete all client data for all engines"], > + ["wipeEngine", 1, "Delete all client data for engine"], > + ["logout", 0, "Log out client"], > + ].reduce(function __commands(commands, entry) { You can use destructuring here: ].reduce(function __commands(commands, [name, args, desc]) { commands[name] = { args: args, desc: desc }; I'd almost be tempted to just kill this code and embed: _commands: { resetAll: { args: 0, desc: "Clear temporary local data for all engines" }, ... Up to you. @@ +207,5 @@ > + return commands; > + }, {}), > + > + /** > + * Remove any commands for the local client and mark it for upload . @@ +215,5 @@ > + this._tracker.addChangedID(this.localID); > + }, > + > + /** > + * Sends a command+args pair to a specific client . :) @@ +239,5 @@ > + command: command, > + args: args, > + }; > + > + if (client.commands == null) { You can abbreviate this to if (client.commands) { ... } because null and undefined are both ideal targets for this clause. @@ +241,5 @@ > + }; > + > + if (client.commands == null) { > + client.commands = [action]; > + // Add the new action if there are no duplicates. If you're commenting an 'else', either put the comment (with correct indentation) inside the clause, or break the cuddle. (That's my personal rule, at least.) I.e., one of these two: if (foo) { // Ah, we have a foo. bar(); } // No foo? Do something else. else { ... } @@ +297,5 @@ > + > + /** > + * Validates and sends a command to a client or all clients. > + * > + * Call this does not actuallt sync the command data to the server. If the Typo. ::: services/sync/modules/service.js @@ +1822,5 @@ > // Clear out any server data > this.wipeServer(engines); > > // Only wipe the engines provided > if (engines) {}. @@ +1828,2 @@ > // Tell the remote machines to wipe themselves > else {}. ::: services/sync/tests/unit/test_clients_engine.js @@ +284,5 @@ > + ["wipeEngine", [ "tabs" ], true ], > + ["wipeEngine", [], false], > + ["logout", [], true ], > + ["logout", [ "foo" ], false], > + ["__UNKNOWN__", [], false], Trailing comma. @@ +287,5 @@ > + ["logout", [ "foo" ], false], > + ["__UNKNOWN__", [], false], > + ]; > + > + for (let [action, args, expectedResult] in testCommands) { Err, this doesn't seem like it'll be correct. Remember, `for` across an array iterates across indices, so this will bind `action` to each index, leaving the others undefined. I think you mean `for each`. @@ +306,5 @@ > + let command = newRecord.commands[0]; > + do_check_eq(command.command, action); > + do_check_eq(command.args, args); > + > + do_check_neq(store._tracker.changedIDs[remoteId], undefined); TEST-UNEXPECTED-FAIL | /Users/rnewman/moz/hg/services-central/testing/xpcshell/head.js | TypeError: store._tracker is undefined - See following stack: @@ +310,5 @@ > + do_check_neq(store._tracker.changedIDs[remoteId], undefined); > + } else { > + do_check_eq(newRecord.commands, undefined); > + > + if (store._tracker) {}. @@ +335,5 @@ > + Clients.sendCommand(action, args, remoteId); > + > + let newRecord = store._remoteClients[remoteId]; > + do_check_eq(newRecord.commands.length, 1); > + Please extend this to using a command with actual arguments, and different instances of equal arguments. E.g., let args1 = ["foo", {x: 2}]; let args2 = ["foo", {x: 1}]; ... You're only testing the simplest case of notDupe. @@ +351,5 @@ > + Clients.sendCommand("wipeAll", [], id); > + } catch (ex) { > + thrown = true; > + } > + Our usual idiom is: let err; try { throw new Error("Whatever"); } catch (ex) { err = ex; } do_check_eq(ex.message, "Whatever"); Perhaps you have some other error, and that's what's actually throwing...
Attachment #550821 - Flags: review?(rnewman)
I think I addressed everything.
Attachment #550821 - Attachment is obsolete: true
Attachment #550859 - Flags: review?(rnewman)
Comment on attachment 550859 [details] [diff] [review] Migrate Command Code to Clients Engine, v4 Review of attachment 550859 [details] [diff] [review]: ----------------------------------------------------------------- Just nits. 109 test passes. Good job. I will fix nits and land, unless you have a pressing reason to wait. ::: services/sync/modules/engines/clients.js @@ +308,5 @@ > + */ > + sendCommand: function sendCommand(command, args, clientId) { > + let commandData = this._commands[command]; > + // Don't send commands that we don't know about. > + if (commandData == null) { !commandData. ::: services/sync/tests/unit/test_clients_engine.js @@ +275,5 @@ > + let store = Clients._store; > + > + let testCommands = [ > + ["resetAll", [], true ], > + ["resetAll", [ "foo" ], false], Nit: could you cuddle these? @@ +345,5 @@ > + newRecord.commands = []; > + > + action = "resetEngine"; > + Clients.sendCommand(action, [{ x: "foo" }], remoteId); > + Clients.sendCommand(action, [{ x: "bar" }], remoteId); Additional cloned line here, please: Clients.sendCommand(action, [{ x: "bar" }], remoteId); Result should still be length 2.
Attachment #550859 - Flags: review?(rnewman) → review+
Feel free to land yourself!
Whiteboard: [fixed in services][qa-]
Investigating two TPS timeouts as a result of this change.
Backed out until we can figure out the two TPS failures. (This is not a failure of process; we're OK using s-c to catch this sort of thing.) Backout: https://hg.mozilla.org/services/services-central/rev/92df1f6de0c6 Greg, this is reproducible on my local machine, so it should be for you. Take a look tomorrow, would you? I will upload my with-nits-fixed patch momentarily.
Whiteboard: [fixed in services][qa-] → [qa-]
(In reply to comment #17) > I will upload my with-nits-fixed patch momentarily. Rev as committed: https://hg.mozilla.org/services/services-central/raw-rev/3c22aef81976 Greg, start there!
The failing TPS tests are: * test_bug563989.js * test_history_collision.js Relevant log entries from bug563989: launching firefox for phase phase4 with args ['-tps', '/tmp/tps_test_hhwfZ4', '-tpsphase', u'4', '-tpslogfile', '/home/mozauto/tps.log'] 2011-08-05 17:39:26 CROSSWEAVE INFO: Weave version: 1.11.0 2011-08-05 17:39:26 CROSSWEAVE INFO: Firefox builddate: 20110804163121 2011-08-05 17:39:26 CROSSWEAVE INFO: Firefox version: 8.0a1 2011-08-05 17:39:26 CROSSWEAVE INFO: setting client.name to profile2 2011-08-05 17:39:26 CROSSWEAVE INFO: ----------event observed: sessionstore-windows-restored 2011-08-05 17:39:26 CROSSWEAVE INFO: starting action: [null] 2011-08-05 17:39:26 CROSSWEAVE INFO: executing Sync 2011-08-05 17:39:27 CROSSWEAVE INFO: ----------event observed: weave:service:sync:error 2011-08-05 17:39:27 CROSSWEAVE INFO: sync error; retrying... 2011-08-05 17:39:27 CROSSWEAVE INFO: starting action: [null] 2011-08-05 17:39:27 CROSSWEAVE INFO: executing Sync 2011-08-05 17:39:28 CROSSWEAVE INFO: ----------event observed: weave:service:sync:finish 1312504767289 Sync.Service DEBUG Exception: aborting sync, process commands said so No traceback available And with history_collision: launching firefox for phase phase3 with args ['-tps', '/tmp/tps_test_5GGnO2', '-tpsphase', u'3', '-tpslogfile', '/home/mozauto/tps.log'] 2011-08-05 17:44:42 CROSSWEAVE INFO: Weave version: 1.11.0 2011-08-05 17:44:42 CROSSWEAVE INFO: Firefox builddate: 20110804163121 2011-08-05 17:44:42 CROSSWEAVE INFO: Firefox version: 8.0a1 2011-08-05 17:44:42 CROSSWEAVE INFO: setting client.name to profile1 2011-08-05 17:44:42 CROSSWEAVE INFO: ----------event observed: sessionstore-windows-restored 2011-08-05 17:44:42 CROSSWEAVE INFO: starting action: [null] 2011-08-05 17:44:42 CROSSWEAVE INFO: executing Sync 2011-08-05 17:44:43 CROSSWEAVE INFO: ----------event observed: weave:service:sync:error 2011-08-05 17:44:43 CROSSWEAVE INFO: sync error; retrying... 2011-08-05 17:44:43 CROSSWEAVE INFO: starting action: [null] 2011-08-05 17:44:43 CROSSWEAVE INFO: executing Sync 2011-08-05 17:44:44 CROSSWEAVE INFO: ----------event observed: weave:service:sync:finish 1312505083410 Sync.Service DEBUG Exception: aborting sync, process commands said so No traceback available The patterns are very similar. I'm guessing we're dealing with the same bug in both. One part of the logs that stands out to me is the "starting action: [null]". All other "starting action" entries start with null, but also have more array elements. I'll dig in.
There was a bug in processIncomingCommands() where the commands were cleared before obtaining the set of commands to process. There was no unit test coverage, so we didn't catch it. The bug was fixed and a unit test was added. Additionally, Weave was not defined because main.js wasn't imported. That bug has also been squashed. I verified that all unit and TPS tests pass with this patch.
Attachment #550859 - Attachment is obsolete: true
Attachment #551083 - Flags: review?(rnewman)
Comment on attachment 551083 [details] [diff] [review] Migrate Command Code to Clients Engine, v5 Review of attachment 551083 [details] [diff] [review]: ----------------------------------------------------------------- Ouch, I should have caught that! Still, this is why we have tests :D Good job, gps. Will land this momentarily.
Attachment #551083 - Flags: review?(rnewman) → review+
> Ouch, I should have caught that! Still, this is why we have tests :D You should have caught it!? It was me who introduced the bug and foolishly had faith in the unit test coverage! I can't believe we had no unit tests around command execution (including wipe client, wipe server, and logout)! If these broke, it would be a very bad day for privacy, and pandas around the world would cry.
(In reply to Gregory Szorc [:gps] from comment #22) > I can't believe we had no unit tests > around command execution (including wipe client, wipe server, and logout)! Welcome to the Sync team! BLAME LABS CODE. ;) When I started, we had perhaps 50 test suites running. Now we're over 100. Philipp wrote most of those first 50 when he started. We're heading in the right direction.
Relanded: http://hg.mozilla.org/services/services-central/rev/845ed0ee7123 Had to manually apply part of the patch due to DOS EOL in test_clients_engine.js. Didn't dos2unix the whole file, because blame has value.
Whiteboard: [qa-] → [fixed in services][qa-]
(In reply to Gregory Szorc [:gps] from comment #20) > I verified that all unit and TPS tests pass with this patch. Are you sure? Those emails just now report the same failures.
I am sure they pass. From my local machine: TEST-PASS | test_bug563989.js phase1: PASS phase2: PASS phase3: PASS phase4: PASS Test Summary TEST-PASS | test_bug563989.js | My current tree is: changeset: 73695:845ed0ee7123 tag: tip parent: 73693:92df1f6de0c6 user: Gregory Szorc <gps@mozilla.com> date: Thu Aug 04 16:19:02 2011 -0700 summary: Bug 676404 - Migrate command APIs from Service to Clients engine. (relanded) r=rnewman
Also: TEST-PASS | test_history_collision.js phase1: PASS phase2: PASS phase3: PASS phase4: PASS Test Summary TEST-PASS | test_history_collision.js | I think TPS is on crack.
First local run: TEST-UNEXPECTED-FAIL | test_bug563989.js | test did not complete phase1: PASS phase2: PASS phase3: PASS phase4: unknown Running again now.
Turns out that - processCommands: function WeaveSvc_processCommands() - this._notify("process-commands", "", function() { had turned into + processIncomingCommands: function processIncomingCommands() { + this._notify("clients:process-commands", "", function() { which is rather different: note the additional "{". I missed it, and Greg didn't know the difference. I put together a bustage fix, added a trivial test line to make sure that processIncomingCommands returns a real boolean, not undefined, and ran TPS to verify. Pushed without review: https://hg.mozilla.org/services/services-central/rev/7f68a2651582 TPS looks happy. Phew.
Status: NEW → RESOLVED
Closed: 14 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla8
Component: Firefox Sync: Backend → Sync
Product: Cloud Services → Firefox
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: