Home | History | Annotate | Download | only in sessions
      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