1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "sync/sessions/data_type_tracker.h" 6 7 #include "base/logging.h" 8 #include "sync/internal_api/public/base/invalidation.h" 9 #include "sync/notifier/invalidation_util.h" 10 #include "sync/notifier/single_object_invalidation_set.h" 11 #include "sync/sessions/nudge_tracker.h" 12 13 namespace syncer { 14 namespace sessions { 15 16 DataTypeTracker::DataTypeTracker(const invalidation::ObjectId& object_id) 17 : local_nudge_count_(0), 18 local_refresh_request_count_(0), 19 payload_buffer_size_(NudgeTracker::kDefaultMaxPayloadsPerType), 20 drop_tracker_(object_id) { } 21 22 DataTypeTracker::~DataTypeTracker() { } 23 24 void DataTypeTracker::RecordLocalChange() { 25 local_nudge_count_++; 26 } 27 28 void DataTypeTracker::RecordLocalRefreshRequest() { 29 local_refresh_request_count_++; 30 } 31 32 namespace { 33 34 bool IsInvalidationVersionLessThan( 35 const Invalidation& a, 36 const Invalidation& b) { 37 InvalidationVersionLessThan comparator; 38 return comparator(a, b); 39 } 40 41 } // namespace 42 43 void DataTypeTracker::RecordRemoteInvalidations( 44 const SingleObjectInvalidationSet& invalidations) { 45 // Merge the incoming invalidations into our list of pending invalidations. 46 // 47 // We won't use STL algorithms here because our concept of equality doesn't 48 // quite fit the expectations of set_intersection. In particular, two 49 // invalidations can be equal according to the SingleObjectInvalidationSet's 50 // rules (ie. have equal versions), but still have different AckHandle values 51 // and need to be acknowledged separately. 52 // 53 // The invalidaitons service can only track one outsanding invalidation per 54 // type and version, so the acknowledgement here should be redundant. We'll 55 // acknowledge them anyway since it should do no harm, and makes this code a 56 // bit easier to test. 57 // 58 // Overlaps should be extremely rare for most invalidations. They can happen 59 // for unknown version invalidations, though. 60 SingleObjectInvalidationSet::const_iterator incoming_it = 61 invalidations.begin(); 62 SingleObjectInvalidationSet::const_iterator existing_it = 63 pending_invalidations_.begin(); 64 65 while (incoming_it != invalidations.end()) { 66 // Keep existing_it ahead of incoming_it. 67 while (existing_it != pending_invalidations_.end() 68 && IsInvalidationVersionLessThan(*existing_it, *incoming_it)) { 69 existing_it++; 70 } 71 72 if (existing_it != pending_invalidations_.end() 73 && !IsInvalidationVersionLessThan(*incoming_it, *existing_it) 74 && !IsInvalidationVersionLessThan(*existing_it, *incoming_it)) { 75 // Incoming overlaps with existing. Either both are unknown versions 76 // (likely) or these two have the same version number (very unlikely). 77 // Acknowledge and overwrite existing. 78 SingleObjectInvalidationSet::const_iterator old_inv = existing_it; 79 existing_it++; 80 old_inv->Acknowledge(); 81 pending_invalidations_.Erase(old_inv); 82 pending_invalidations_.Insert(*incoming_it); 83 incoming_it++; 84 } else { 85 DCHECK(existing_it == pending_invalidations_.end() 86 || IsInvalidationVersionLessThan(*incoming_it, *existing_it)); 87 // The incoming_it points at a version not in the pending_invalidations_ 88 // list. Add it to the list then increment past it. 89 pending_invalidations_.Insert(*incoming_it); 90 incoming_it++; 91 } 92 } 93 94 // Those incoming invalidations may have caused us to exceed our buffer size. 95 // Trim some items from our list, if necessary. 96 while (pending_invalidations_.GetSize() > payload_buffer_size_) { 97 pending_invalidations_.begin()->Drop(&drop_tracker_); 98 pending_invalidations_.Erase(pending_invalidations_.begin()); 99 } 100 } 101 102 void DataTypeTracker::RecordSuccessfulSyncCycle() { 103 // If we were throttled, then we would have been excluded from this cycle's 104 // GetUpdates and Commit actions. Our state remains unchanged. 105 if (IsThrottled()) 106 return; 107 108 local_nudge_count_ = 0; 109 local_refresh_request_count_ = 0; 110 111 // TODO(rlarocque): If we want this to be correct even if we should happen to 112 // crash before writing all our state, we should wait until the results of 113 // this sync cycle have been written to disk before updating the invalidations 114 // state. See crbug.com/324996. 115 for (SingleObjectInvalidationSet::const_iterator it = 116 pending_invalidations_.begin(); 117 it != pending_invalidations_.end(); ++it) { 118 it->Acknowledge(); 119 } 120 pending_invalidations_.Clear(); 121 122 if (drop_tracker_.IsRecoveringFromDropEvent()) { 123 drop_tracker_.RecordRecoveryFromDropEvent(); 124 } 125 } 126 127 // This limit will take effect on all future invalidations received. 128 void DataTypeTracker::UpdatePayloadBufferSize(size_t new_size) { 129 payload_buffer_size_ = new_size; 130 } 131 132 bool DataTypeTracker::IsSyncRequired() const { 133 return !IsThrottled() && (HasLocalChangePending() || IsGetUpdatesRequired()); 134 } 135 136 bool DataTypeTracker::IsGetUpdatesRequired() const { 137 return !IsThrottled() && 138 (HasRefreshRequestPending() || HasPendingInvalidation()); 139 } 140 141 bool DataTypeTracker::HasLocalChangePending() const { 142 return local_nudge_count_ > 0; 143 } 144 145 bool DataTypeTracker::HasRefreshRequestPending() const { 146 return local_refresh_request_count_ > 0; 147 } 148 149 bool DataTypeTracker::HasPendingInvalidation() const { 150 return !pending_invalidations_.IsEmpty() 151 || drop_tracker_.IsRecoveringFromDropEvent(); 152 } 153 154 void DataTypeTracker::SetLegacyNotificationHint( 155 sync_pb::DataTypeProgressMarker* progress) const { 156 DCHECK(!IsThrottled()) 157 << "We should not make requests if the type is throttled."; 158 159 if (!pending_invalidations_.IsEmpty() && 160 !pending_invalidations_.back().is_unknown_version()) { 161 // The old-style source info can contain only one hint per type. We grab 162 // the most recent, to mimic the old coalescing behaviour. 163 progress->set_notification_hint(pending_invalidations_.back().payload()); 164 } else if (HasLocalChangePending()) { 165 // The old-style source info sent up an empty string (as opposed to 166 // nothing at all) when the type was locally nudged, but had not received 167 // any invalidations. 168 progress->set_notification_hint(""); 169 } 170 } 171 172 void DataTypeTracker::FillGetUpdatesTriggersMessage( 173 sync_pb::GetUpdateTriggers* msg) const { 174 // Fill the list of payloads, if applicable. The payloads must be ordered 175 // oldest to newest, so we insert them in the same order as we've been storing 176 // them internally. 177 for (SingleObjectInvalidationSet::const_iterator it = 178 pending_invalidations_.begin(); 179 it != pending_invalidations_.end(); ++it) { 180 if (!it->is_unknown_version()) { 181 msg->add_notification_hint(it->payload()); 182 } 183 } 184 185 msg->set_server_dropped_hints( 186 pending_invalidations_.StartsWithUnknownVersion()); 187 msg->set_client_dropped_hints(drop_tracker_.IsRecoveringFromDropEvent()); 188 msg->set_local_modification_nudges(local_nudge_count_); 189 msg->set_datatype_refresh_nudges(local_refresh_request_count_); 190 } 191 192 bool DataTypeTracker::IsThrottled() const { 193 return !unthrottle_time_.is_null(); 194 } 195 196 base::TimeDelta DataTypeTracker::GetTimeUntilUnthrottle( 197 base::TimeTicks now) const { 198 if (!IsThrottled()) { 199 NOTREACHED(); 200 return base::TimeDelta::FromSeconds(0); 201 } 202 return std::max(base::TimeDelta::FromSeconds(0), 203 unthrottle_time_ - now); 204 } 205 206 void DataTypeTracker::ThrottleType(base::TimeDelta duration, 207 base::TimeTicks now) { 208 unthrottle_time_ = std::max(unthrottle_time_, now + duration); 209 } 210 211 void DataTypeTracker::UpdateThrottleState(base::TimeTicks now) { 212 if (now >= unthrottle_time_) { 213 unthrottle_time_ = base::TimeTicks(); 214 } 215 } 216 217 } // namespace sessions 218 } // namespace syncer 219