New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Emit completion ops for tombstones #53
Conversation
It's unclear if `flag_{for_upload, as_merged}` and `skip_upload` refer to local or remote items. This commit renames them to be more consistent: * `SetLocalUnmerged` (formerly `FlagForUpload`) marks a local item as needing to be merged. * `SetLocalMerged` (formerly `SkipUpload`) removes the "needs merge" flag from a local item. * `SetRemoteMerged` (formerly `FlagAsMerged`) removes the "needs merge" flag from a _remote_ item. This commit also renames `Upload` to `UploadItems`, since we'll be adding completion ops for uploading tombstones next.
This commit adds completion ops for deleting local items (to apply remote tombstones), inserting new tombstones (to delete non-syncable and invalid items), and uploading tombstones (to avoid an extra table scan when staging outgoing tombstones). These ops also help avoid extra work when applying tombstones for items that don't exist locally, or uploading tombstones for items that don't exist remotely.
src/merge.rs
Outdated
(Record::Unmentioned, Record::Deleted) => { | ||
// The item isn't mentioned locally, but is deleted | ||
// remotely. This is the case for a first sync, and will | ||
// change if we store tombstones (bug 1343103). For now, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does mean we'll need to merge all tombstones again if we ever decide to store them, but we can cross that bridge later. Marking remote tombstones as merged also means we can filter them out in subsequent merges, which is going to make application more efficient.
src/merge.rs
Outdated
// The item isn't mentioned locally, but exists remotely. | ||
// This is the case for invalid remote-only items. Upload a | ||
// tombstone, but don't flag it as remotely merged; we'll do | ||
// that when we store the uploaded tombstone. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is assuming we write records back to the mirror, like Desktop does now—but pretty much all completion ops assume a database like Desktop Places, so that's fine.
src/merge.rs
Outdated
} | ||
(Record::Deleted, Record::Unmentioned) => { | ||
// Uploading a tombstone for a nonexistent item. Nothing | ||
// to do here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might as well upload it. We don't have a "remove tombstone" op, and not uploading here means there's a difference between what we upload, and what's in moz_bookmarks_deleted
. Ditto for (Record::Deleted, Record::Deleted)
below.
/// A node in a bookmark tree that knows its parent and children, and | ||
/// dereferences to its item. | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Node<'t>(&'t Tree, &'t TreeEntry); | ||
|
||
impl<'t> Node<'t> { | ||
/// Returns the item for this node. | ||
#[inline] | ||
pub fn item(&self) -> &'t Item { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes lifetimes easier—Deref
returns a reference tied to the node's lifetime (&self
), but tying it to the entry's lifetime (&'t
) means we can store references to the item and its fields.
We now emit ops in two steps. The first step removes all local tombstones, and flags remote tombstones as merged, for all revived items. The second handles the actual deletion, on either or both sides. * Don't add deletions from local and remote trees that aren't mentioned in `delete_remotely` and `delete_locally`, because we emit different completion ops for those. * Change `Tree::guids` to only return GUIDs that exist in the tree, not deleted GUIDs. * Change `Tree::deletions` to return a `HashSet` instead of an iterator, so that we can use the `difference` method. * Add `Tree::exists`. * Remove `Tree::record_for_guid`. * Add `CompletionOps::summarize` for tests and logging. * Remove `StructureCounts::merged_deletions`, since it's not accurate anymore.
This commit adds `MergedRoot::completion_ops_with_signal`, and implements `MergedRoot::completion_ops` in terms of it.
Codecov Report
@@ Coverage Diff @@
## master #53 +/- ##
==========================================
- Coverage 75.14% 74.98% -0.16%
==========================================
Files 7 7
Lines 2655 2726 +71
==========================================
+ Hits 1995 2044 +49
- Misses 660 682 +22
Continue to review full report at Codecov.
|
@@ -68,6 +68,7 @@ pub enum TelemetryEvent { | |||
pub struct TreeStats { | |||
pub time: Duration, | |||
pub items: usize, | |||
pub deletions: usize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
merged_deletions
is gone, but we can still report the number of local and remote deletions in shutdown hangs.
@@ -50,22 +50,12 @@ pub struct StructureCounts { | |||
/// Total number of nodes in the merged tree, excluding the | |||
/// root. | |||
pub merged_nodes: usize, | |||
/// Total number of deletions to apply, local and remote. | |||
pub merged_deletions: usize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed because it's not accurate anymore—tombstones on one side for items that don't exist on the other won't be merged anymore. We'll handle them specially when we build the list of completion ops.
ops.flag_as_merged.push(flag_as_merged); | ||
accumulate(signal, &mut ops, self.node(), 1, false)?; | ||
|
||
// Clean up tombstones for local and remote items that are revived on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These works like this:
- Any local tombstones that aren't in
delete_remotely
are removed. They're for revived (deleted locally, but changed remotely) items. - Any tombstones on both sides (if you delete the same bookmark on two devices while they're both offline, then sync) are removed and flagged as remotely merged.
- Any remote tombstones for items that aren't in
delete_locally
, and that don't exist locally, are flagged as remotely merged. The extra check avoids a redundantUPDATE items SET needsMerge = 1
for the remote tombstone. Since we'll be reuploading the local item, anyway, it doesn't make sense to update itsneedsMerge
flag, only to replace the whole row later when we write the uploaded item back into the mirror. - Any accepted deletions for items that exist on both sides must be because the items are invalid or non-syncable (left pane roots and queries). We delete the item locally, insert a tombstone into
moz_bookmarks_deleted
in case upload is interrupted, and flag the tombstone for upload. - Any deletions for items that only exist locally must be either incoming tombstones (
{ id: "xyz", deleted: true }
), or local non-syncable items. Delete them, and also flag the remote tombstone as merged if one exists. - Any deletions for items that only exist remotely must be either tombstones in
moz_bookmarks_deleted
that we're uploading, or deletions of invalid or non-syncable remote items (left pane on the server, bookmarks with invalid URLs, and such). Insert a tombstone intomoz_bookmarks_deleted
if one doesn't already exist, and flag it for upload.
} | ||
|
||
/// Returns a printable summary of all completion ops to apply. | ||
pub fn summarize(&self) -> Vec<String> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to make testing easier, but I can also imagine adding these to trace logs.
Instead of panicking if a GUID in `delete_locally` or `delete_remotely` doesn't exist on both sides, we now clean up local tombstones, and flag the remote tombstone as merged. With this change, we no longer need to filter out local and remote tombstones for items that don't exist on the other side. That means we can revert `Tree::guids` to return all GUIDs, including deletions, and have `MergedRoot::{local, remote}_deletions` return a more accurate set of tombstones for logging.
This commit adds completion ops for deleting local items (to apply
remote tombstones), inserting new tombstones (to delete non-syncable
and invalid items), and uploading tombstones (to avoid an extra table
scan when staging outgoing tombstones).
These ops also help avoid extra work when applying tombstones for items
that don't exist locally, or uploading tombstones for items that don't
exist remotely.