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/engine/polling_constants.h" 9 #include "sync/protocol/sync.pb.h" 10 11 namespace syncer { 12 namespace sessions { 13 14 namespace { 15 16 // Delays for syncer nudges. 17 const int kDefaultNudgeDelayMilliseconds = 200; 18 const int kSlowNudgeDelayMilliseconds = 2000; 19 const int kDefaultSessionsCommitDelaySeconds = 10; 20 const int kSyncRefreshDelayMilliseconds = 500; 21 const int kSyncSchedulerDelayMilliseconds = 250; 22 23 base::TimeDelta GetDefaultDelayForType(ModelType model_type, 24 base::TimeDelta minimum_delay) { 25 switch (model_type) { 26 case AUTOFILL: 27 // Accompany types rely on nudges from other types, and hence have long 28 // nudge delays. 29 return base::TimeDelta::FromSeconds(kDefaultShortPollIntervalSeconds); 30 case BOOKMARKS: 31 case PREFERENCES: 32 // Types with sometimes automatic changes get longer delays to allow more 33 // coalescing. 34 return base::TimeDelta::FromMilliseconds(kSlowNudgeDelayMilliseconds); 35 case SESSIONS: 36 case FAVICON_IMAGES: 37 case FAVICON_TRACKING: 38 // Types with navigation triggered changes get longer delays to allow more 39 // coalescing. 40 return base::TimeDelta::FromSeconds(kDefaultSessionsCommitDelaySeconds); 41 default: 42 return minimum_delay; 43 } 44 } 45 46 } // namespace 47 48 size_t NudgeTracker::kDefaultMaxPayloadsPerType = 10; 49 50 NudgeTracker::NudgeTracker() 51 : type_tracker_deleter_(&type_trackers_), 52 invalidations_enabled_(false), 53 invalidations_out_of_sync_(true), 54 minimum_local_nudge_delay_( 55 base::TimeDelta::FromMilliseconds(kDefaultNudgeDelayMilliseconds)), 56 local_refresh_nudge_delay_( 57 base::TimeDelta::FromMilliseconds(kSyncRefreshDelayMilliseconds)), 58 remote_invalidation_nudge_delay_( 59 base::TimeDelta::FromMilliseconds(kSyncSchedulerDelayMilliseconds)) { 60 ModelTypeSet protocol_types = ProtocolTypes(); 61 // Default initialize all the type trackers. 62 for (ModelTypeSet::Iterator it = protocol_types.First(); it.Good(); 63 it.Inc()) { 64 type_trackers_.insert(std::make_pair(it.Get(), new DataTypeTracker())); 65 } 66 } 67 68 NudgeTracker::~NudgeTracker() { } 69 70 bool NudgeTracker::IsSyncRequired() const { 71 if (IsRetryRequired()) 72 return true; 73 74 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 75 it != type_trackers_.end(); ++it) { 76 if (it->second->IsSyncRequired()) { 77 return true; 78 } 79 } 80 81 return false; 82 } 83 84 bool NudgeTracker::IsGetUpdatesRequired() const { 85 if (invalidations_out_of_sync_) 86 return true; 87 88 if (IsRetryRequired()) 89 return true; 90 91 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 92 it != type_trackers_.end(); ++it) { 93 if (it->second->IsGetUpdatesRequired()) { 94 return true; 95 } 96 } 97 return false; 98 } 99 100 bool NudgeTracker::IsRetryRequired() const { 101 if (sync_cycle_start_time_.is_null()) 102 return false; 103 104 if (current_retry_time_.is_null()) 105 return false; 106 107 return current_retry_time_ <= sync_cycle_start_time_; 108 } 109 110 void NudgeTracker::RecordSuccessfulSyncCycle() { 111 // If a retry was required, we've just serviced it. Unset the flag. 112 if (IsRetryRequired()) 113 current_retry_time_ = base::TimeTicks(); 114 115 // A successful cycle while invalidations are enabled puts us back into sync. 116 invalidations_out_of_sync_ = !invalidations_enabled_; 117 118 for (TypeTrackerMap::iterator it = type_trackers_.begin(); 119 it != type_trackers_.end(); ++it) { 120 it->second->RecordSuccessfulSyncCycle(); 121 } 122 } 123 124 base::TimeDelta NudgeTracker::RecordLocalChange(ModelTypeSet types) { 125 // Start with the longest delay. 126 base::TimeDelta delay = 127 base::TimeDelta::FromMilliseconds(kDefaultShortPollIntervalSeconds); 128 for (ModelTypeSet::Iterator type_it = types.First(); type_it.Good(); 129 type_it.Inc()) { 130 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type_it.Get()); 131 DCHECK(tracker_it != type_trackers_.end()); 132 133 // Only if the type tracker has a valid delay (non-zero) that is shorter 134 // than the calculated delay do we update the calculated delay. 135 base::TimeDelta type_delay = tracker_it->second->RecordLocalChange(); 136 if (type_delay == base::TimeDelta()) { 137 type_delay = GetDefaultDelayForType(type_it.Get(), 138 minimum_local_nudge_delay_); 139 } 140 if (type_delay < delay) 141 delay = type_delay; 142 } 143 return delay; 144 } 145 146 base::TimeDelta NudgeTracker::RecordLocalRefreshRequest(ModelTypeSet types) { 147 for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) { 148 TypeTrackerMap::iterator tracker_it = type_trackers_.find(it.Get()); 149 DCHECK(tracker_it != type_trackers_.end()); 150 tracker_it->second->RecordLocalRefreshRequest(); 151 } 152 return local_refresh_nudge_delay_; 153 } 154 155 base::TimeDelta NudgeTracker::RecordRemoteInvalidation( 156 syncer::ModelType type, 157 scoped_ptr<InvalidationInterface> invalidation) { 158 // Forward the invalidations to the proper recipient. 159 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type); 160 DCHECK(tracker_it != type_trackers_.end()); 161 tracker_it->second->RecordRemoteInvalidation(invalidation.Pass()); 162 return remote_invalidation_nudge_delay_; 163 } 164 165 void NudgeTracker::RecordInitialSyncRequired(syncer::ModelType type) { 166 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type); 167 DCHECK(tracker_it != type_trackers_.end()); 168 tracker_it->second->RecordInitialSyncRequired(); 169 } 170 171 void NudgeTracker::OnInvalidationsEnabled() { 172 invalidations_enabled_ = true; 173 } 174 175 void NudgeTracker::OnInvalidationsDisabled() { 176 invalidations_enabled_ = false; 177 invalidations_out_of_sync_ = true; 178 } 179 180 void NudgeTracker::SetTypesThrottledUntil( 181 ModelTypeSet types, 182 base::TimeDelta length, 183 base::TimeTicks now) { 184 for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) { 185 TypeTrackerMap::iterator tracker_it = type_trackers_.find(it.Get()); 186 tracker_it->second->ThrottleType(length, now); 187 } 188 } 189 190 void NudgeTracker::UpdateTypeThrottlingState(base::TimeTicks now) { 191 for (TypeTrackerMap::iterator it = type_trackers_.begin(); 192 it != type_trackers_.end(); ++it) { 193 it->second->UpdateThrottleState(now); 194 } 195 } 196 197 bool NudgeTracker::IsAnyTypeThrottled() const { 198 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 199 it != type_trackers_.end(); ++it) { 200 if (it->second->IsThrottled()) { 201 return true; 202 } 203 } 204 return false; 205 } 206 207 bool NudgeTracker::IsTypeThrottled(ModelType type) const { 208 DCHECK(type_trackers_.find(type) != type_trackers_.end()); 209 return type_trackers_.find(type)->second->IsThrottled(); 210 } 211 212 base::TimeDelta NudgeTracker::GetTimeUntilNextUnthrottle( 213 base::TimeTicks now) const { 214 DCHECK(IsAnyTypeThrottled()) << "This function requires a pending unthrottle"; 215 216 // Return min of GetTimeUntilUnthrottle() values for all IsThrottled() types. 217 base::TimeDelta time_until_next_unthrottle = base::TimeDelta::Max(); 218 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 219 it != type_trackers_.end(); ++it) { 220 if (it->second->IsThrottled()) { 221 time_until_next_unthrottle = std::min( 222 time_until_next_unthrottle, it->second->GetTimeUntilUnthrottle(now)); 223 } 224 } 225 DCHECK(!time_until_next_unthrottle.is_max()); 226 227 return time_until_next_unthrottle; 228 } 229 230 ModelTypeSet NudgeTracker::GetThrottledTypes() const { 231 ModelTypeSet result; 232 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 233 it != type_trackers_.end(); ++it) { 234 if (it->second->IsThrottled()) { 235 result.Put(it->first); 236 } 237 } 238 return result; 239 } 240 241 ModelTypeSet NudgeTracker::GetNudgedTypes() const { 242 ModelTypeSet result; 243 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 244 it != type_trackers_.end(); ++it) { 245 if (it->second->HasLocalChangePending()) { 246 result.Put(it->first); 247 } 248 } 249 return result; 250 } 251 252 ModelTypeSet NudgeTracker::GetNotifiedTypes() const { 253 ModelTypeSet result; 254 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 255 it != type_trackers_.end(); ++it) { 256 if (it->second->HasPendingInvalidation()) { 257 result.Put(it->first); 258 } 259 } 260 return result; 261 } 262 263 ModelTypeSet NudgeTracker::GetRefreshRequestedTypes() const { 264 ModelTypeSet result; 265 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 266 it != type_trackers_.end(); ++it) { 267 if (it->second->HasRefreshRequestPending()) { 268 result.Put(it->first); 269 } 270 } 271 return result; 272 } 273 274 void NudgeTracker::SetLegacyNotificationHint( 275 ModelType type, 276 sync_pb::DataTypeProgressMarker* progress) const { 277 DCHECK(type_trackers_.find(type) != type_trackers_.end()); 278 type_trackers_.find(type)->second->SetLegacyNotificationHint(progress); 279 } 280 281 sync_pb::GetUpdatesCallerInfo::GetUpdatesSource NudgeTracker::GetLegacySource() 282 const { 283 // There's an order to these sources: NOTIFICATION, DATATYPE_REFRESH, LOCAL, 284 // RETRY. The server makes optimization decisions based on this field, so 285 // it's important to get this right. Setting it wrong could lead to missed 286 // updates. 287 // 288 // This complexity is part of the reason why we're deprecating 'source' in 289 // favor of 'origin'. 290 bool has_invalidation_pending = false; 291 bool has_refresh_request_pending = false; 292 bool has_commit_pending = false; 293 bool is_initial_sync_required = false; 294 bool has_retry = IsRetryRequired(); 295 296 for (TypeTrackerMap::const_iterator it = type_trackers_.begin(); 297 it != type_trackers_.end(); ++it) { 298 const DataTypeTracker& tracker = *it->second; 299 if (!tracker.IsThrottled() && tracker.HasPendingInvalidation()) { 300 has_invalidation_pending = true; 301 } 302 if (!tracker.IsThrottled() && tracker.HasRefreshRequestPending()) { 303 has_refresh_request_pending = true; 304 } 305 if (!tracker.IsThrottled() && tracker.HasLocalChangePending()) { 306 has_commit_pending = true; 307 } 308 if (!tracker.IsThrottled() && tracker.IsInitialSyncRequired()) { 309 is_initial_sync_required = true; 310 } 311 } 312 313 if (has_invalidation_pending) { 314 return sync_pb::GetUpdatesCallerInfo::NOTIFICATION; 315 } else if (has_refresh_request_pending) { 316 return sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH; 317 } else if (is_initial_sync_required) { 318 // Not quite accurate, but good enough for our purposes. This setting of 319 // SOURCE is just a backward-compatibility hack anyway. 320 return sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH; 321 } else if (has_commit_pending) { 322 return sync_pb::GetUpdatesCallerInfo::LOCAL; 323 } else if (has_retry) { 324 return sync_pb::GetUpdatesCallerInfo::RETRY; 325 } else { 326 return sync_pb::GetUpdatesCallerInfo::UNKNOWN; 327 } 328 } 329 330 void NudgeTracker::FillProtoMessage( 331 ModelType type, 332 sync_pb::GetUpdateTriggers* msg) const { 333 DCHECK(type_trackers_.find(type) != type_trackers_.end()); 334 335 // Fill what we can from the global data. 336 msg->set_invalidations_out_of_sync(invalidations_out_of_sync_); 337 338 // Delegate the type-specific work to the DataTypeTracker class. 339 type_trackers_.find(type)->second->FillGetUpdatesTriggersMessage(msg); 340 } 341 342 void NudgeTracker::SetSyncCycleStartTime(base::TimeTicks now) { 343 sync_cycle_start_time_ = now; 344 345 // If current_retry_time_ is still set, that means we have an old retry time 346 // left over from a previous cycle. For example, maybe we tried to perform 347 // this retry, hit a network connection error, and now we're in exponential 348 // backoff. In that case, we want this sync cycle to include the GU retry 349 // flag so we leave this variable set regardless of whether or not there is an 350 // overwrite pending. 351 if (!current_retry_time_.is_null()) { 352 return; 353 } 354 355 // If do not have a current_retry_time_, but we do have a next_retry_time_ and 356 // it is ready to go, then we set it as the current_retry_time_. It will stay 357 // there until a GU retry has succeeded. 358 if (!next_retry_time_.is_null() && 359 next_retry_time_ <= sync_cycle_start_time_) { 360 current_retry_time_ = next_retry_time_; 361 next_retry_time_ = base::TimeTicks(); 362 } 363 } 364 365 void NudgeTracker::SetHintBufferSize(size_t size) { 366 for (TypeTrackerMap::iterator it = type_trackers_.begin(); 367 it != type_trackers_.end(); ++it) { 368 it->second->UpdatePayloadBufferSize(size); 369 } 370 } 371 372 void NudgeTracker::SetNextRetryTime(base::TimeTicks retry_time) { 373 next_retry_time_ = retry_time; 374 } 375 376 void NudgeTracker::OnReceivedCustomNudgeDelays( 377 const std::map<ModelType, base::TimeDelta>& delay_map) { 378 for (std::map<ModelType, base::TimeDelta>::const_iterator iter = 379 delay_map.begin(); 380 iter != delay_map.end(); 381 ++iter) { 382 ModelType type = iter->first; 383 DCHECK(syncer::ProtocolTypes().Has(type)); 384 TypeTrackerMap::iterator type_iter = type_trackers_.find(type); 385 if (type_iter == type_trackers_.end()) 386 continue; 387 388 if (iter->second > minimum_local_nudge_delay_) { 389 type_iter->second->UpdateLocalNudgeDelay(iter->second); 390 } else { 391 type_iter->second->UpdateLocalNudgeDelay( 392 GetDefaultDelayForType(type, 393 minimum_local_nudge_delay_)); 394 } 395 } 396 } 397 398 void NudgeTracker::SetDefaultNudgeDelay(base::TimeDelta nudge_delay) { 399 minimum_local_nudge_delay_ = nudge_delay; 400 } 401 402 } // namespace sessions 403 } // namespace syncer 404