1 // Copyright (c) 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/nudge_tracker.h" 6 7 #include "base/basictypes.h" 8 #include "sync/internal_api/public/base/invalidation.h" 9 #include "sync/notifier/invalidation_util.h" 10 #include "sync/notifier/object_id_invalidation_map.h" 11 #include "sync/protocol/sync.pb.h" 12 13 namespace syncer { 14 namespace sessions { 15 16 size_t NudgeTracker::kDefaultMaxPayloadsPerType = 10; 17 18 NudgeTracker::NudgeTracker() 19 : invalidations_enabled_(false), 20 invalidations_out_of_sync_(true) { 21 ModelTypeSet protocol_types = ProtocolTypes(); 22 // Default initialize all the type trackers. 23 for (ModelTypeSet::Iterator it = protocol_types.First(); it.Good(); 24 it.Inc()) { 25 invalidation::ObjectId id; 26 if (!RealModelTypeToObjectId(it.Get(), &id)) { 27 NOTREACHED(); 28 } else { 29 type_trackers_.insert(std::make_pair(it.Get(), DataTypeTracker(id))); 30 } 31 } 32 } 33 34 NudgeTracker::~NudgeTracker() { } 35 36 bool NudgeTracker::IsSyncRequired() const { 37 if (IsRetryRequired()) 38 return true; 39 40 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 41 it != type_trackers_.end(); ++it) { 42 if (it->second.IsSyncRequired()) { 43 return true; 44 } 45 } 46 47 return false; 48 } 49 50 bool NudgeTracker::IsGetUpdatesRequired() const { 51 if (invalidations_out_of_sync_) 52 return true; 53 54 if (IsRetryRequired()) 55 return true; 56 57 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 58 it != type_trackers_.end(); ++it) { 59 if (it->second.IsGetUpdatesRequired()) { 60 return true; 61 } 62 } 63 return false; 64 } 65 66 bool NudgeTracker::IsRetryRequired() const { 67 if (sync_cycle_start_time_.is_null()) 68 return false; 69 70 if (current_retry_time_.is_null()) 71 return false; 72 73 return current_retry_time_ < sync_cycle_start_time_; 74 } 75 76 void NudgeTracker::RecordSuccessfulSyncCycle() { 77 // If a retry was required, we've just serviced it. Unset the flag. 78 if (IsRetryRequired()) 79 current_retry_time_ = base::TimeTicks(); 80 81 // A successful cycle while invalidations are enabled puts us back into sync. 82 invalidations_out_of_sync_ = !invalidations_enabled_; 83 84 for (TypeTrackerMap::iterator it = type_trackers_.begin(); 85 it != type_trackers_.end(); ++it) { 86 it->second.RecordSuccessfulSyncCycle(); 87 } 88 } 89 90 void NudgeTracker::RecordLocalChange(ModelTypeSet types) { 91 for (ModelTypeSet::Iterator type_it = types.First(); type_it.Good(); 92 type_it.Inc()) { 93 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type_it.Get()); 94 DCHECK(tracker_it != type_trackers_.end()); 95 tracker_it->second.RecordLocalChange(); 96 } 97 } 98 99 void NudgeTracker::RecordLocalRefreshRequest(ModelTypeSet types) { 100 for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) { 101 TypeTrackerMap::iterator tracker_it = type_trackers_.find(it.Get()); 102 DCHECK(tracker_it != type_trackers_.end()); 103 tracker_it->second.RecordLocalRefreshRequest(); 104 } 105 } 106 107 void NudgeTracker::RecordRemoteInvalidation( 108 const ObjectIdInvalidationMap& invalidation_map) { 109 // Be very careful here. The invalidations acknowledgement system requires a 110 // sort of manual memory management. We'll leak a small amount of memory if 111 // we fail to acknowledge or drop any of these incoming invalidations. 112 113 ObjectIdSet id_set = invalidation_map.GetObjectIds(); 114 for (ObjectIdSet::iterator it = id_set.begin(); it != id_set.end(); ++it) { 115 ModelType type; 116 117 // This should never happen. If it does, we'll start to leak memory. 118 if (!ObjectIdToRealModelType(*it, &type)) { 119 NOTREACHED() 120 << "Object ID " << ObjectIdToString(*it) 121 << " does not map to valid model type"; 122 continue; 123 } 124 125 // Forward the invalidations to the proper recipient. 126 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type); 127 DCHECK(tracker_it != type_trackers_.end()); 128 tracker_it->second.RecordRemoteInvalidations( 129 invalidation_map.ForObject(*it)); 130 } 131 } 132 133 void NudgeTracker::OnInvalidationsEnabled() { 134 invalidations_enabled_ = true; 135 } 136 137 void NudgeTracker::OnInvalidationsDisabled() { 138 invalidations_enabled_ = false; 139 invalidations_out_of_sync_ = true; 140 } 141 142 void NudgeTracker::SetTypesThrottledUntil( 143 ModelTypeSet types, 144 base::TimeDelta length, 145 base::TimeTicks now) { 146 for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) { 147 TypeTrackerMap::iterator tracker_it = type_trackers_.find(it.Get()); 148 tracker_it->second.ThrottleType(length, now); 149 } 150 } 151 152 void NudgeTracker::UpdateTypeThrottlingState(base::TimeTicks now) { 153 for (TypeTrackerMap::iterator it = type_trackers_.begin(); 154 it != type_trackers_.end(); ++it) { 155 it->second.UpdateThrottleState(now); 156 } 157 } 158 159 bool NudgeTracker::IsAnyTypeThrottled() const { 160 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 161 it != type_trackers_.end(); ++it) { 162 if (it->second.IsThrottled()) { 163 return true; 164 } 165 } 166 return false; 167 } 168 169 bool NudgeTracker::IsTypeThrottled(ModelType type) const { 170 DCHECK(type_trackers_.find(type) != type_trackers_.end()); 171 return type_trackers_.find(type)->second.IsThrottled(); 172 } 173 174 base::TimeDelta NudgeTracker::GetTimeUntilNextUnthrottle( 175 base::TimeTicks now) const { 176 DCHECK(IsAnyTypeThrottled()) << "This function requires a pending unthrottle"; 177 178 // Return min of GetTimeUntilUnthrottle() values for all IsThrottled() types. 179 base::TimeDelta time_until_next_unthrottle = base::TimeDelta::Max(); 180 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 181 it != type_trackers_.end(); ++it) { 182 if (it->second.IsThrottled()) { 183 time_until_next_unthrottle = 184 std::min(time_until_next_unthrottle, 185 it->second.GetTimeUntilUnthrottle(now)); 186 } 187 } 188 DCHECK(!time_until_next_unthrottle.is_max()); 189 190 return time_until_next_unthrottle; 191 } 192 193 ModelTypeSet NudgeTracker::GetThrottledTypes() const { 194 ModelTypeSet result; 195 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 196 it != type_trackers_.end(); ++it) { 197 if (it->second.IsThrottled()) { 198 result.Put(it->first); 199 } 200 } 201 return result; 202 } 203 204 ModelTypeSet NudgeTracker::GetNudgedTypes() const { 205 ModelTypeSet result; 206 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 207 it != type_trackers_.end(); ++it) { 208 if (it->second.HasLocalChangePending()) { 209 result.Put(it->first); 210 } 211 } 212 return result; 213 } 214 215 ModelTypeSet NudgeTracker::GetNotifiedTypes() const { 216 ModelTypeSet result; 217 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 218 it != type_trackers_.end(); ++it) { 219 if (it->second.HasPendingInvalidation()) { 220 result.Put(it->first); 221 } 222 } 223 return result; 224 } 225 226 ModelTypeSet NudgeTracker::GetRefreshRequestedTypes() const { 227 ModelTypeSet result; 228 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 229 it != type_trackers_.end(); ++it) { 230 if (it->second.HasRefreshRequestPending()) { 231 result.Put(it->first); 232 } 233 } 234 return result; 235 } 236 237 void NudgeTracker::SetLegacyNotificationHint( 238 ModelType type, 239 sync_pb::DataTypeProgressMarker* progress) const { 240 DCHECK(type_trackers_.find(type) != type_trackers_.end()); 241 type_trackers_.find(type)->second.SetLegacyNotificationHint(progress); 242 } 243 244 sync_pb::GetUpdatesCallerInfo::GetUpdatesSource NudgeTracker::GetLegacySource() 245 const { 246 // There's an order to these sources: NOTIFICATION, DATATYPE_REFRESH, LOCAL, 247 // RETRY. The server makes optimization decisions based on this field, so 248 // it's important to get this right. Setting it wrong could lead to missed 249 // updates. 250 // 251 // This complexity is part of the reason why we're deprecating 'source' in 252 // favor of 'origin'. 253 bool has_invalidation_pending = false; 254 bool has_refresh_request_pending = false; 255 bool has_commit_pending = false; 256 bool has_retry = IsRetryRequired(); 257 258 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 259 it != type_trackers_.end(); ++it) { 260 const DataTypeTracker& tracker = it->second; 261 if (!tracker.IsThrottled() && tracker.HasPendingInvalidation()) { 262 has_invalidation_pending = true; 263 } 264 if (!tracker.IsThrottled() && tracker.HasRefreshRequestPending()) { 265 has_refresh_request_pending = true; 266 } 267 if (!tracker.IsThrottled() && tracker.HasLocalChangePending()) { 268 has_commit_pending = true; 269 } 270 } 271 272 if (has_invalidation_pending) { 273 return sync_pb::GetUpdatesCallerInfo::NOTIFICATION; 274 } else if (has_refresh_request_pending) { 275 return sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH; 276 } else if (has_commit_pending) { 277 return sync_pb::GetUpdatesCallerInfo::LOCAL; 278 } else if (has_retry) { 279 return sync_pb::GetUpdatesCallerInfo::RETRY; 280 } else { 281 return sync_pb::GetUpdatesCallerInfo::UNKNOWN; 282 } 283 } 284 285 void NudgeTracker::FillProtoMessage( 286 ModelType type, 287 sync_pb::GetUpdateTriggers* msg) const { 288 DCHECK(type_trackers_.find(type) != type_trackers_.end()); 289 290 // Fill what we can from the global data. 291 msg->set_invalidations_out_of_sync(invalidations_out_of_sync_); 292 293 // Delegate the type-specific work to the DataTypeTracker class. 294 type_trackers_.find(type)->second.FillGetUpdatesTriggersMessage(msg); 295 } 296 297 void NudgeTracker::SetSyncCycleStartTime(base::TimeTicks now) { 298 sync_cycle_start_time_ = now; 299 300 // If current_retry_time_ is still set, that means we have an old retry time 301 // left over from a previous cycle. For example, maybe we tried to perform 302 // this retry, hit a network connection error, and now we're in exponential 303 // backoff. In that case, we want this sync cycle to include the GU retry 304 // flag so we leave this variable set regardless of whether or not there is an 305 // overwrite pending. 306 if (!current_retry_time_.is_null()) { 307 return; 308 } 309 310 // If do not have a current_retry_time_, but we do have a next_retry_time_ and 311 // it is ready to go, then we set it as the current_retry_time_. It will stay 312 // there until a GU retry has succeeded. 313 if (!next_retry_time_.is_null() && 314 next_retry_time_ < sync_cycle_start_time_) { 315 current_retry_time_ = next_retry_time_; 316 next_retry_time_ = base::TimeTicks(); 317 } 318 } 319 320 void NudgeTracker::SetHintBufferSize(size_t size) { 321 for (TypeTrackerMap::iterator it = type_trackers_.begin(); 322 it != type_trackers_.end(); ++it) { 323 it->second.UpdatePayloadBufferSize(size); 324 } 325 } 326 327 void NudgeTracker::SetNextRetryTime(base::TimeTicks retry_time) { 328 next_retry_time_ = retry_time; 329 } 330 331 } // namespace sessions 332 } // namespace syncer 333